]> git.pond.sub.org Git - empserver/commitdiff
Support for IPv6:
authorMarkus Armbruster <armbru@pond.sub.org>
Wed, 28 Dec 2005 18:50:08 +0000 (18:50 +0000)
committerMarkus Armbruster <armbru@pond.sub.org>
Wed, 28 Dec 2005 18:50:08 +0000 (18:50 +0000)
(tcp_listen, player_addrlen): New.  IPv4-only code factored out of
player_init().
(player_init): Use them.
(player_accept): Address family independence.
(tcp_connect): New.
(main): Use it.
(hostaddr, hostconnect, hostport): Internal linkage.

configure.ac
include/prototypes.h
src/client/host.c
src/client/main.c
src/client/misc.h
src/lib/gen/tcp_listen.c [new file with mode: 0644]
src/lib/player/accept.c

index 8424084cc9290c1c1dbd5f2112b726220f7eb94f..e130c61a115fdaaa7ddb01e11a3ad330b1ca0b65 100644 (file)
@@ -79,6 +79,7 @@ LIBS=$save_LIBS
 
 ### Checks for library functions
 
+AC_CHECK_FUNCS(getaddrinfo)
 MY_FUNC_MAKECONTEXT
 
 
index e6e098a4a9addb0f06900bee20c05bcd9efe642f..6b96f04f1410218cb9b8f8579fb6a032b4b29c13 100644 (file)
@@ -392,6 +392,7 @@ extern int parse(char *, char **, char **, char *, char **);
 extern int ldround(double, int);
 extern int roundintby(int, int);
 extern int scthash(register int, register int, int);
+extern int tcp_listen(char *, char *, size_t *);
 /* plur.c */
 extern s_char *numstr(s_char buf[], int n);
 extern s_char *esplur(int n);
index 4c5c8a0cda87243b13fa0db4da61bd71a6d00a83..c72fa0f46a9d437780963065b75b8a4eaaa181a9 100644 (file)
  *  Known contributors to this file:
  *     Dave Pare, 1989
  *     Steve McClure, 1998
+ *     Markus Armbruster, 2005
  */
 
 #include <config.h>
 
 #include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
 #include <io.h>
 #include <winsock.h>
 #endif
-#include "misc.h"
+
+#ifdef HAVE_GETADDRINFO
+/*
+ * Inspired by example code from W. Richard Stevens: UNIX Network
+ * Programming, Vol. 1
+ */
 
 int
+tcp_connect(char *host, char *serv)
+{
+    int sockfd, n;
+    struct addrinfo hints, *res, *ressave;
+
+    memset(&hints, 0, sizeof(struct addrinfo));
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+
+    if ((n = getaddrinfo(host, serv, &hints, &res)) != 0) {
+       fprintf(stderr, "Can't connect to %s:%s: %s\n",
+               host, serv, gai_strerror(n));
+       exit(1);
+    }
+    ressave = res;
+
+    do {
+       sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+       if (sockfd < 0)
+           continue;           /* ignore this one */
+
+       if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
+           break;              /* success */
+
+       close(sockfd);          /* ignore this one */
+    } while ((res = res->ai_next) != NULL);
+
+    if (res == NULL) {         /* errno set from final connect() */
+       fprintf(stderr, "Can't connect to %s:%s: %s\n",
+               host, serv, strerror(errno));
+       exit(1);
+    }
+
+    freeaddrinfo(ressave);
+
+    return sockfd;
+}
+
+#else  /* !HAVE_GETADDRINFO */
+
+static int
 hostaddr(char *name, struct sockaddr_in *addr)
 {
     struct hostent *hp;
@@ -68,7 +117,7 @@ hostaddr(char *name, struct sockaddr_in *addr)
     return 1;
 }
 
-int
+static int
 hostport(char *name, struct sockaddr_in *addr)
 {
     struct servent *sp;
@@ -86,9 +135,10 @@ hostport(char *name, struct sockaddr_in *addr)
     return 1;
 }
 
-int
+static int
 hostconnect(struct sockaddr_in *addr)
 {
+    /* FIXME should attempt connect to all addresses of multi-homed host, not just 1st */
     int s;
 
     s = socket(AF_INET, SOCK_STREAM, 0);
@@ -108,3 +158,26 @@ hostconnect(struct sockaddr_in *addr)
     }
     return s;
 }
+
+int
+tcp_connect(char *host, char *serv)
+{
+    struct sockaddr_in sin;
+    int sock;
+
+    if (!hostport(serv, &sin)) {
+       fprintf(stderr, "Can't resolve Empire port %s\n", serv);
+       exit(1);
+    }
+    if (!hostaddr(host, &sin)) {
+       fprintf(stderr, "Can't resolve Empire host %s\n", host);
+       exit(1);
+    }
+    if ((sock = hostconnect(&sin)) < 0) {
+       fprintf(stderr, "Can't connect to %s:%s: %s\n",
+               serv, host, strerror(errno));
+       exit(1);
+    }
+    return sock;
+}
+#endif
index 73bcf0fc285e582e843d1043b576212b032dcfe2..106bbde0369a88b56f87227d0299ea5d23691e61 100644 (file)
@@ -49,7 +49,6 @@
 #endif
 #include <signal.h>
 #include <errno.h>
-#include <time.h>
 #ifndef _WIN32
 #include <sys/socket.h>
 #include <sys/time.h>
@@ -99,7 +98,6 @@ main(int ac, char **av)
     char *ptr;
     char *auxout_fname;
     FILE *auxout_fp;
-    struct sockaddr_in sin;
     int n;
     char *cname;
     char *pname;
@@ -161,21 +159,10 @@ main(int ac, char **av)
     port = getenv("EMPIREPORT");
     if (!port)
        port = empireport;
-    if (!hostport(port, &sin)) {
-       fprintf(stderr, "Can't resolve Empire port %s\n", port);
-       exit(1);
-    }
     host = getenv("EMPIREHOST");
     if (!host)
        host = empirehost;
-    if (!hostaddr(host, &sin)) {
-       fprintf(stderr, "Can't resolve Empire host %s\n", host);
-       exit(1);
-    }
-    if ((sock = hostconnect(&sin)) < 0) {
-       perror("Can't connect to Empire server");
-       exit(1);
-    }
+    sock = tcp_connect(host, port);
     cname = getenv("COUNTRY");
     if (ac > 1)
        cname = argv[1];
index ae19f8fe377a2d68ed00e22ddbb56032c0afbac3..a3423273b7db19385ceee7beb1710edfdaabff26 100644 (file)
@@ -73,9 +73,7 @@ void putse(void);
 #endif
 int recvline(int s, char *buf);
 int expect(int s, int match, char *buf);
-int hostaddr(char *name, struct sockaddr_in *addr);
-int hostconnect(struct sockaddr_in *addr);
-int hostport(char *name, struct sockaddr_in *addr);
+int tcp_connect(char *, char *);
 int login(int s, char *uname, char *cname, char *cpass, int kill_proc, int);
 void saveargv(int ac, char **src, char **dst);
 void sendcmd(int s, char *cmd, char *arg);
diff --git a/src/lib/gen/tcp_listen.c b/src/lib/gen/tcp_listen.c
new file mode 100644 (file)
index 0000000..9b2b167
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ *  Empire - A multi-player, client/server Internet based war game.
+ *  Copyright (C) 1986-2005, Dave Pare, Jeff Bailey, Thomas Ruschak,
+ *                           Ken Stevens, Steve McClure
+ *
+ *  This program 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 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  ---
+ *
+ *  See the "LEGAL", "LICENSE", "CREDITS" and "README" files for all the
+ *  related information and legal notices. It is expected that any future
+ *  projects/authors will amend these files as needed.
+ *
+ *  ---
+ *
+ *  tcp_listen.c: Create a socket and listen on it
+ * 
+ *  Known contributors to this file:
+ *     Markus Armbruster, 2005
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <unistd.h>
+
+static int cant_listen(char *, char *, const char *);
+
+int
+tcp_listen(char *host, char *serv, size_t *addrlenp)
+{
+    int fd;
+    int on = 1;
+#ifdef HAVE_GETADDRINFO
+    /*
+     * Inspired by example code from W. Richard Stevens: UNIX Network
+     * Programming, Vol. 1
+     */
+    int n;
+    struct addrinfo hints, *res, *ressave;
+
+    memset(&hints, 0, sizeof(struct addrinfo));
+    hints.ai_flags = AI_PASSIVE;
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+
+    if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
+       cant_listen(host, serv, gai_strerror(n));
+    ressave = res;
+
+    do {
+       fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+       if (fd < 0)
+           continue;           /* error, try next one */
+
+#ifndef _WIN32
+       /*
+        * SO_REUSEADDR requests to permit another bind even when the
+        * port is still in state TIME_WAIT.  Windows' SO_REUSEADDR is
+        * broken: it makes bind() succeed no matter what, even if
+        * there's another server running on the same port.  Luckily,
+        * bind() seems to be broken as well: it seems to suceed while
+        * the port in state TIME_WAIT by default; thus we get the
+        * behavior we want by not setting SO_REUSEADDR.
+        */
+       if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
+           cant_listen(host, serv, strerror(errno));
+#endif
+       if (bind(fd, res->ai_addr, res->ai_addrlen) == 0)
+           break;              /* success */
+
+       close(fd);              /* error, close and try next one */
+    } while ((res = res->ai_next) != NULL);
+
+    if (res == NULL)        /* errno from final socket() or bind() */
+       cant_listen(host, serv, strerror(errno));
+
+    if (listen(fd, SOMAXCONN) < 0)
+       cant_listen(host, serv, strerror(errno));
+
+    if (addrlenp)
+       *addrlenp = res->ai_addrlen;
+
+    freeaddrinfo(ressave);
+
+#else  /* !HAVE_GETADDRINFO */
+    struct sockaddr_in sin;
+    struct hostent *hp;
+    struct servent *sp;
+
+    memset(&sin, 0, sizeof(sin));
+    sin.sin_family = AF_INET;
+    if (!host)
+       sin.sin_addr.s_addr = htonl(INADDR_ANY);
+    else if (isdigit(*host))
+       sin.sin_addr.s_addr = inet_addr(host);
+    else {
+       hp = gethostbyname(host);
+       if (!hp)
+           cant_listen(host, serv, strerror(errno));
+       memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
+    }
+    if (isdigit(*serv))
+       sin.sin_port = htons(atoi(serv));
+    else {
+       sp = getservbyname(serv, "tcp");
+       if (!sp)
+           cant_listen(host, serv, strerror(errno));
+       sin.sin_port = sp->s_port;
+    }
+    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
+       cant_listen(host, serv, strerror(errno));
+#ifndef _WIN32
+    /* see comment on setsockopt() above */
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
+       cant_listen(host, serv, strerror(errno));
+#endif
+    if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+       cant_listen(host, serv, strerror(errno));
+    if (listen(fd, SOMAXCONN) < 0)
+       cant_listen(host, serv, strerror(errno));
+
+    if (addrlenp)
+       *addrlenp = sizeof(struct sockaddr_in);
+
+#endif /* !HAVE_GETADDRINFO */
+
+    return fd;
+}
+
+
+static int
+cant_listen(char *host, char *serv, const char *err)
+{
+    fprintf(stderr, "Can't listen on %s:%s: %s", host, serv, err);
+    exit(1);
+}
index 4747f413551b2cc67d8760b9fbac492125aa8537..1269c5b17dd4446cc80839042adf5243d490d02a 100644 (file)
@@ -53,7 +53,6 @@
 #if !defined(_WIN32)
 #include <arpa/inet.h>
 #include <sys/socket.h>
-#include <sys/wait.h>
 #include <sys/time.h>
 #include <netdb.h>
 #include <netinet/in.h>
 
 static struct emp_qelem Players;
 static int player_socket;
+static int player_addrlen;
 
 void
 player_init(void)
 {
-    struct sockaddr_in sin;
-    struct hostent *hp;
-    struct servent *sp;
-    int s;
-    int val;
-
     emp_initque(&Players);
     init_player_commands();
 
-    memset(&sin, 0, sizeof(sin));
-    sin.sin_family = AF_INET;
-    if (!*listen_addr)
-       sin.sin_addr.s_addr = htonl(INADDR_ANY);
-    else if (isdigit(*listen_addr))
-       sin.sin_addr.s_addr = inet_addr(listen_addr);
-    else {
-       hp = gethostbyname(listen_addr);
-       if (!hp) {
-           logerror("Can't resolve listen address %s", listen_addr);
-           exit(1);
-       }
-       memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
-    }
-    if (isdigit(*loginport))
-       sin.sin_port = htons(atoi(loginport));
-    else {
-       sp = getservbyname(loginport, "tcp");
-       if (!sp) {
-           logerror("Can't resolve service %s", loginport);
-           exit(1);
-       }
-       sin.sin_port = sp->s_port;
-    }
-    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
-       logerror("inet socket create");
-       exit(1);
-    }
-    val = 1;
-#ifndef _WIN32
-    /*
-     * SO_REUSEADDR requests to permit another bind even when the port
-     * is still in state TIME_WAIT.  Windows' SO_REUSEADDR is broken:
-     * it makes bind() succeed no matter what, even if there's another
-     * server running on the same port.  Luckily, bind() seems to be
-     * broken as well: it seems to suceed while the port in state
-     * TIME_WAIT by default; thus we get the behavior we want by not
-     * setting SO_REUSEADDR.
-     */
-    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
-       logerror("inet socket setsockopt SO_REUSEADDR (%d)", errno);
-       exit(1);
-    }
-#endif
-    if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
-       logerror("inet socket bind");
-       exit(1);
-    }
-    if (listen(s, SOMAXCONN) < 0) {
-       logerror("inet socket listen");
-       exit(1);
-    }
-    player_socket = s;
+    player_socket = tcp_listen(*listen_addr ? listen_addr : NULL,
+                              loginport, &player_addrlen);
 }
 
 struct player *
@@ -242,7 +185,8 @@ player_wakeup(struct player *pl)
 void
 player_accept(void *unused)
 {
-    struct sockaddr_in sin;
+    struct sockaddr *sap;
+    void *inaddr;
     int s = player_socket;
     struct player *np;
     int len;
@@ -254,14 +198,19 @@ player_accept(void *unused)
     struct hostent *hostp;
 #endif
 
+    /* auto sockaddr_storage would be simpler, but less portable */
+    sap = malloc(player_addrlen);
+
     while (1) {
        empth_select(s, EMPTH_FD_READ);
-       len = sizeof(sin);
-       ns = accept(s, (struct sockaddr *)&sin, &len);
+       len = player_addrlen;
+       ns = accept(s, sap, &len);
+       /* FIXME accept() can block on some systems (RST after select() reported s ready) */
        if (ns < 0) {
            logerror("new socket accept");
            continue;
        }
+       /* FIXME SO_KEEPALIVE is useless, player_kill_idle() strikes long before */
        (void)setsockopt(ns, SOL_SOCKET, SO_KEEPALIVE, &set, sizeof(set));
        np = player_new(ns);
        if (!np) {
@@ -269,11 +218,24 @@ player_accept(void *unused)
            close(ns);
            continue;
        }
-       strcpy(np->hostaddr, inet_ntoa(sin.sin_addr));
+#ifdef HAVE_GETADDRINFO
+       inaddr = sap->sa_family == AF_INET
+           ? (void *)&((struct sockaddr_in *)sap)->sin_addr
+           : (void *)&((struct sockaddr_in6 *)sap)->sin6_addr;
+       /* Assumes that if you got getaddrinfo(), you got inet_ntop() too */
+       if (!inet_ntop(sap->sa_family, inaddr,
+                      np->hostaddr, sizeof(np->hostaddr))) {
+           logerror("inet_ntop() failed: %s", strerror(errno));
+           close(ns);
+           continue;
+       }
+#else
+       inaddr = &((struct sockaddr_in *)sap)->sin_addr;
+       strcpy(np->hostaddr, inet_ntoa(*(struct in_addr *)inaddr));
+#endif
 #ifdef RESOLVE_IPADDRESS
-       if (NULL !=
-           (hostp = gethostbyaddr(&sin.sin_addr, sizeof(sin.sin_addr),
-                                  AF_INET)))
+       hostp = gethostbyaddr(inaddr, player_addrlen, sap->sa_family);
+       if (NULL != hostp)
            strcpy(np->hostname, hostp->h_name);
 #endif /* RESOLVE_IPADDRESS */
        /* XXX may not be big enough */