/* Unix/Cygwin network code for Xconq kernel. Copyright (C) 1996, 1997, 1999 Stanley T. Shebs. Xconq is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. See the file COPYING. */ /* Note that this file does not include all Xconq .h files, since it may be used with auxiliary programs. */ #include "config.h" #include "misc.h" #include "dir.h" #include "lisp.h" #include "module.h" #include "system.h" #include #include #include #include #include #include #include #include #include #include #include #ifndef __CYGWIN32__ # include #endif #include static int accept_remote_connection(void); static int wait_for(int timeout); /* The file descriptor that a host makes available for other programs to connect into. */ int public_fd; /* Flag indicating whether the public file descriptor is currently valid. */ int public_fd_valid; /* The next id for a remote program. 0 means invalid, 1 is reserved for the first host of a game, so we start at 2. */ int nextrid = 2; /* Array of file descriptors used with connections to remote programs, indexed by the remote programs' ids. */ int remote_fd[100]; int fd_valid[100]; /* Given a specification of how to open a remote connection (such as a hostname:port), and a host/join flag, set up a connection. Return a number for the method being used, or 0 for failure. */ int open_remote_connection(char *methodname, int willhost) { int port, i, tmp_fd, tmp; char *port_str, hostname[100]; struct hostent *hostent; struct sockaddr_in sockaddr; struct protoent *protoent; if (!strchr(methodname, ':')) { /* Try opening a serial port. */ #if 0 struct sgttyb sg; #endif remote_fd[2] = open(methodname, O_RDWR); fd_valid[2] = TRUE; if (remote_fd[2] < 0) init_warning("Could not open remote device"); #if 0 ioctl(remote_fd[2], TIOCGETP, &sg); sg.sg_flags = RAW; ioctl(remote_fd[2], TIOCSETP, &sg); #endif } else { /* Using TCP; extract the port number. */ port_str = strchr(methodname, ':'); port = atoi(port_str + 1); tmp_fd = socket(PF_INET, SOCK_STREAM, 0); if (tmp_fd < 0) init_warning("Can't open socket"); /* Allow rapid reuse of this port. */ tmp = 1; setsockopt (tmp_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp, sizeof(tmp)); if (willhost) { sockaddr.sin_family = PF_INET; sockaddr.sin_port = htons(port); sockaddr.sin_addr.s_addr = INADDR_ANY; public_fd = tmp_fd; public_fd_valid = TRUE; if (bind(public_fd, (struct sockaddr *) &sockaddr, sizeof(sockaddr))) init_warning("Can't bind address"); if (listen (public_fd, 1)) init_warning("Can't listen"); /* Our public socket is now available to receive connection attempts. These will appear as input on public_fd, and then accept_remote_connection will finish making the link. */ return 1; } else { /* We're trying to join an existing game. */ tmp = min (port_str - methodname, (int) sizeof hostname - 1); strncpy (hostname, methodname, tmp); hostname[tmp] = '\0'; hostent = gethostbyname (hostname); if (!hostent) { init_warning("%s: unknown host\n", hostname); return 0; } /* Try making the connection several times. */ for (i = 1; i <= 5; i++) { tmp_fd = socket(PF_INET, SOCK_STREAM, 0); if (tmp_fd < 0) { init_warning("can't create a socket"); return 0; } /* Allow rapid reuse of this port. */ tmp = 1; setsockopt(tmp_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp, sizeof(tmp)); /* Enable TCP keep alive process. */ tmp = 1; setsockopt(tmp_fd, SOL_SOCKET, SO_KEEPALIVE, (char *) &tmp, sizeof(tmp)); sockaddr.sin_family = PF_INET; sockaddr.sin_port = htons(port); memcpy(&sockaddr.sin_addr.s_addr, hostent->h_addr, sizeof (struct in_addr)); if (connect(tmp_fd, (struct sockaddr *) &sockaddr, sizeof(sockaddr)) == 0) /* Success! Break out of the retry loop. */ break; close(tmp_fd); tmp_fd = -1; /* We retry for ECONNREFUSED because that is often a temporary condition, which happens when the server is being restarted. */ if (errno != ECONNREFUSED) return 0; /* Wait a moment before trying again. */ /* (should mention to the user) */ sleep(1); } /* We know the host will be rid 1. */ remote_fd[1] = tmp_fd; fd_valid[1] = TRUE; protoent = getprotobyname ("tcp"); if (!protoent) return 0; tmp = 1; if (setsockopt(remote_fd[1], protoent->p_proto, TCP_NODELAY, (char *) &tmp, sizeof(tmp))) return 0; signal(SIGPIPE, SIG_IGN); } return 1; } return 0; } /* If a program has attempted to connect to our published socket, accept the connection and add the program to our merry little band. */ static int accept_remote_connection(void) { struct hostent *hostent; struct sockaddr_in sockaddr; int tmp; char hostname[100]; struct protoent *protoent; int new_fd; int rid; tmp = sizeof (sockaddr); new_fd = accept(public_fd, (struct sockaddr *) &sockaddr, &tmp); if (new_fd == -1) { init_warning("Accept failed"); return 0; } /* Record the new file descriptor as a valid remote program. */ rid = nextrid++; remote_fd[rid] = new_fd; fd_valid[rid] = TRUE; hostent = gethostbyaddr((char*)&sockaddr.sin_addr, sizeof(sizeof(sockaddr.sin_addr)), AF_INET); if (hostent) { memcpy(hostname, hostent->h_name, hostent->h_length); hostname[hostent->h_length] = '\0'; } else { strcpy(hostname, "unknown"); } protoent = getprotobyname("tcp"); if (!protoent) init_warning("getprotobyname"); /* Enable TCP keep alive process. */ tmp = 1; setsockopt(remote_fd[rid], SOL_SOCKET, SO_KEEPALIVE, (char *) &tmp, sizeof(tmp)); /* Tell TCP not to delay small packets. This greatly speeds up interactive response. */ tmp = 1; setsockopt(remote_fd[rid], protoent->p_proto, TCP_NODELAY, (char *) &tmp, sizeof(tmp)); /* If we don't do this, then program simply exits when the remote side dies. */ signal(SIGPIPE, SIG_IGN); #if defined(FASYNC) fcntl(remote_fd[rid], F_SETFL, FASYNC); #endif Dprintf("Remote connection from %s (#%d)\n", hostname, rid); return 1; } /* Send a given string out to a given program. Return when the whole string has been delivered. */ void low_send(int rid, char *buf) { int fd, len, cc; fd = (rid > 0 ? remote_fd[rid] : remote_fd[1]); Dprintf("low_send %d (fd %d) \"%s\"\n", rid, fd, buf); len = strlen(buf); while (len > 0) { cc = write(fd, buf, len); if (cc < 0) return; len -= cc; buf += cc; } } /* Wait for input, for a given period of time. Return an rid or a special code (negative values). */ static int wait_for(int timeout) { int rid, numfds, maxfd; struct timeval tv; fd_set readfds, exceptfds; { static int counter = 0; if (timeout > 0) { Dprintf("Waiting %d secs for input\n", timeout); } else { counter = (++counter) % 1000; #if 0 /* too much output */ if (counter == 999) Dprintf("Checking for input 999 times\n"); #endif } } FD_ZERO (&readfds); FD_ZERO (&exceptfds); tv.tv_sec = timeout; tv.tv_usec = 0; maxfd = -2; if (public_fd_valid) { FD_SET(public_fd, &readfds); maxfd = max(public_fd, maxfd); } for (rid = 1; rid < nextrid; ++rid) { if (fd_valid[rid]) { FD_SET(remote_fd[rid], &readfds); FD_SET(remote_fd[rid], &exceptfds); maxfd = max(remote_fd[rid], maxfd); } } /* (should test if maxfd < 0 still) */ while (1) { numfds = select(maxfd+1, &readfds, 0, &exceptfds, (timeout >= 0 ? &tv : 0)); if (numfds <= 0) { if (numfds == 0) return -1; else if (errno == EINTR) continue; else return -2; /* Got an error from select or poll */ } if (public_fd_valid) { if (FD_ISSET(public_fd, &readfds)) { return 0; } } for (rid = 1; rid < nextrid; ++rid) { if (fd_valid[rid]) { if (FD_ISSET(remote_fd[rid], &readfds)) { return rid; } } } /* (should never happen) */ return -1; } } /* For a given amount of time, listen for any data from our other programs. */ int low_receive(int *ridp, char *buf, int maxchars, int timeout) { int status, rid, fd, n; /* time_t */ unsigned long start_time, now; time(&start_time); while (1) { status = wait_for(timeout); if (status < 0) { return FALSE; } else if (status == 0) { accept_remote_connection(); continue; } /* We have data from one of the other programs. */ rid = status; fd = remote_fd[rid] ; n = read(fd, buf, maxchars); if (n > 0) { /* Make a string out of the received data. */ buf[n] = '\0'; *ridp = rid; return TRUE; } else if (timeout == 0) { /* We were just sniffing for anything laying around, and there wasn't anything, so return emptyhanded. */ return FALSE; } else if (timeout == -1) { /* Go around again. */ } else { time(&now); if (now > start_time + timeout) { Dprintf("%ul > %ul + %d\n", now, start_time, timeout); return FALSE /* timed out */; } } } } void close_remote_connection(int rid) { /* If we're closing before rids assigned, assume that an arg of 0 means we were trying to join and failed. */ if (rid == 0) rid = 1; if (!fd_valid[rid]) return; close(remote_fd[rid]); fd_valid[rid] = FALSE; }