Logo Search packages:      
Sourcecode: xconq version File versions  Download package

socket.c

/* 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 <sys/stat.h>
#include <signal.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/time.h>
#include <errno.h>
#include <fcntl.h>

#include <sys/file.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#ifndef __CYGWIN32__
# include <netinet/tcp.h>
#endif
#include <sys/ioctl.h>

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;
}

Generated by  Doxygen 1.6.0   Back to index