Use multiple listening sockets, allow multiple bindaddrs.
authorEmil Mikulic <emikulic@gmail.com>
Sat, 4 Jun 2011 10:21:56 +0000 (20:21 +1000)
committerEmil Mikulic <emikulic@gmail.com>
Sat, 4 Jun 2011 14:07:06 +0000 (00:07 +1000)
darkstat.c
http.c
http.h

index b4dd39b..a66db83 100644 (file)
 #include "daylog.h"
 #include "db.h"
 #include "dns.h"
+#include "err.h"
 #include "http.h"
 #include "hosts_db.h"
 #include "localip.h"
 #include "ncache.h"
 #include "pidfile.h"
 
-#include "err.h"
-#include <arpa/inet.h>
-#include <sys/socket.h>
-#include <netdb.h>
 #include <assert.h>
 #include <errno.h>
 #include <signal.h>
@@ -111,25 +108,7 @@ unsigned short opt_bindport = 667;
 static void cb_port(const char *arg)
 { opt_bindport = (unsigned short)parsenum(arg, 65536); }
 
-const char *opt_bindaddr = NULL;
-static void cb_bindaddr(const char *arg)
-{
-   struct addrinfo hints, *ai;
-
-   memset(&hints, 0, sizeof(hints));
-   hints.ai_flags = AI_PASSIVE;
-#ifdef AI_ADDRCONFIG
-   hints.ai_flags |= AI_ADDRCONFIG;
-#endif
-   hints.ai_family = AF_UNSPEC;
-   hints.ai_socktype = SOCK_STREAM;
-
-   if (getaddrinfo(arg, NULL, &hints, &ai))
-      errx(1, "malformed address \"%s\"", arg);
-
-   freeaddrinfo(ai);
-   opt_bindaddr = arg;
-}
+static void cb_bindaddr(const char *arg) { http_add_bindaddr(arg); }
 
 const char *opt_filter = NULL;
 static void cb_filter(const char *arg) { opt_filter = arg; }
@@ -230,7 +209,7 @@ static struct cmdline_arg cmdline_args[] = {
    {"--no-macs",      NULL,              cb_no_macs,      0},
    {"--no-lastseen",  NULL,              cb_no_lastseen,  0},
    {"-p",             "port",            cb_port,         0},
-   {"-b",             "bindaddr",        cb_bindaddr,     0},
+   {"-b",             "bindaddr",        cb_bindaddr,     -1},
    {"-f",             "filter",          cb_filter,       0},
    {"-l",             "network/netmask", cb_local,        0},
    {"--chroot",       "dir",             cb_chroot,       0},
@@ -305,7 +284,9 @@ parse_sub_cmdline(const int argc, char * const *argv)
             exit(EXIT_FAILURE);
          }
 
-         arg->num_seen++;
+         if (arg->num_seen != -1) /* accept more than one */
+            arg->num_seen++;
+
          if (arg->arg_name == NULL) {
             arg->callback(NULL);
             parse_sub_cmdline(argc-1, argv+1);
@@ -417,7 +398,7 @@ main(int argc, char **argv)
    /* do this first as it forks - minimize memory use */
    if (opt_want_dns) dns_init(opt_privdrop_user);
    cap_init(opt_interface, opt_filter, opt_want_promisc); /* needs root */
-   http_init(opt_bindaddr, opt_bindport, /*maxconn=*/-1);
+   http_listen(opt_bindport);
    ncache_init(); /* must do before chroot() */
 
    privdrop(opt_chroot_dir, opt_privdrop_user);
diff --git a/http.c b/http.c
index 585d96e..91db646 100644 (file)
--- a/http.c
+++ b/http.c
@@ -45,9 +45,11 @@ static const char encoding_gzip[] = "gzip";
 
 static const char server[] = PACKAGE_NAME "/" PACKAGE_VERSION;
 static int idletime = 60;
-static int sockin = -1;             /* socket to accept connections from */
 #define MAX_REQUEST_LENGTH 4000
 
+static int *insocks = NULL;
+static unsigned int insock_num = 0;
+
 #ifndef min
 #define min(a,b) (((a) < (b)) ? (a) : (b))
 #endif
@@ -89,6 +91,13 @@ struct connection {
 static LIST_HEAD(conn_list_head, connection) connlist =
     LIST_HEAD_INITIALIZER(conn_list_head);
 
+struct bindaddr_entry {
+    STAILQ_ENTRY(bindaddr_entry) entries;
+    const char *s;
+};
+static STAILQ_HEAD(bindaddrs_head, bindaddr_entry) bindaddrs =
+    STAILQ_HEAD_INITIALIZER(bindaddrs);
+
 /* ---------------------------------------------------------------------------
  * Decode URL by converting %XX (where XX are hexadecimal digits) to the
  * character it represents.  Don't forget to free the return value.
@@ -296,7 +305,7 @@ static struct connection *new_connection(void)
 /* ---------------------------------------------------------------------------
  * Accept a connection from sockin and add it to the connection queue.
  */
-static void accept_connection(void)
+static void accept_connection(const int sockin)
 {
     struct sockaddr_storage addrin;
     socklen_t sin_size;
@@ -879,8 +888,10 @@ static struct addrinfo *get_bind_addr(
 
     memset(&hints, 0, sizeof(hints));
     hints.ai_family = AF_UNSPEC;
+#ifdef linux
     if (bindaddr == NULL)
         hints.ai_family = AF_INET6; /* dual stack socket */
+#endif
     hints.ai_socktype = SOCK_STREAM;
     hints.ai_flags = AI_PASSIVE;
 #ifdef AI_ADDRCONFIG
@@ -892,23 +903,23 @@ static struct addrinfo *get_bind_addr(
             bindaddr ? bindaddr : "NULL", portstr, gai_strerror(ret));
     if (ai == NULL)
         err(1, "getaddrinfo() returned NULL pointer");
-    if (ai->ai_next != NULL)
-        warnx("getaddrinfo() returned multiple addresses");
     return ai;
 }
 
-/* --------------------------------------------------------------------------
- * Initialize the sockin global.  This is the socket that we accept
- * connections from.  Pass -1 as max_conn for system limit.
- */
-void http_init(const char *bindaddr, const unsigned short bindport,
-    const int max_conn)
+void http_add_bindaddr(const char *bindaddr)
 {
-    struct addrinfo *ai;
-    char ipaddr[INET6_ADDRSTRLEN];
-    int sockopt, ret;
+    struct bindaddr_entry *ent;
+
+    ent = xmalloc(sizeof(*ent));
+    ent->s = bindaddr;
+    STAILQ_INSERT_TAIL(&bindaddrs, ent, entries);
+}
 
-    ai = get_bind_addr(bindaddr, bindport);
+static void http_listen_one(struct addrinfo *ai,
+    const unsigned short bindport)
+{
+    char ipaddr[INET6_ADDRSTRLEN];
+    int sockin, sockopt, ret;
 
     /* create incoming socket */
     if ((sockin = socket(ai->ai_family, SOCK_STREAM, 0)) == -1)
@@ -920,14 +931,6 @@ void http_init(const char *bindaddr, const unsigned short bindport,
             &sockopt, sizeof(sockopt)) == -1)
         err(1, "can't set SO_REUSEADDR");
 
-    /* dual stack socket */
-    if (ai->ai_family == AF_INET6) {
-        sockopt = 0;
-        if (setsockopt(sockin, IPPROTO_IPV6, IPV6_V6ONLY,
-                &sockopt, sizeof(sockopt)) == -1)
-            err(1, "can't unset IPV6_V6ONLY");
-    }
-
     /* format address into ipaddr string */
     if ((ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, ipaddr,
                            sizeof(ipaddr), NULL, 0, NI_NUMERICHOST)) != 0)
@@ -937,17 +940,44 @@ void http_init(const char *bindaddr, const unsigned short bindport,
     if (bind(sockin, ai->ai_addr, ai->ai_addrlen) == -1)
         err(1, "bind(\"%s\") failed", ipaddr);
 
+    /* listen on socket */
+    if (listen(sockin, -1) == -1)
+        err(1, "listen() failed");
+
     verbosef("listening on http://%s%s%s:%u/",
         (ai->ai_family == AF_INET6) ? "[" : "",
         ipaddr,
         (ai->ai_family == AF_INET6) ? "]" : "",
         bindport);
 
-    freeaddrinfo(ai);
+    /* add to insocks */
+    insocks = xrealloc(insocks, sizeof(*insocks) * (insock_num + 1));
+    insocks[insock_num++] = sockin;
+}
 
-    /* listen on socket */
-    if (listen(sockin, max_conn) == -1)
-        err(1, "listen() failed");
+/* Initialize the http sockets and listen on them. */
+void http_listen(const unsigned short bindport)
+{
+    /* If the user didn't specify any bind addresses, add a NULL.
+     * This will become a wildcard.
+     */
+    if (STAILQ_EMPTY(&bindaddrs))
+        http_add_bindaddr(NULL);
+
+    /* Listen on every specified interface. */
+    while (!STAILQ_EMPTY(&bindaddrs)) {
+        struct bindaddr_entry *bindaddr = STAILQ_FIRST(&bindaddrs);
+        struct addrinfo *ai, *ais = get_bind_addr(bindaddr->s, bindport);
+
+        /* There could be multiple addresses returned, handle them all. */
+        for (ai = ais; ai; ai = ai->ai_next)
+            http_listen_one(ai, bindport);
+
+        freeaddrinfo(ais);
+
+        STAILQ_REMOVE_HEAD(&bindaddrs, entries);
+        free(bindaddr);
+    }
 
     /* ignore SIGPIPE */
     if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
@@ -965,11 +995,13 @@ http_fd_set(fd_set *recv_set, fd_set *send_set, int *max_fd,
 {
     struct connection *conn, *next;
     int minidle = idletime + 1;
+    unsigned int i;
 
     #define MAX_FD_SET(sock, fdset) do { \
         FD_SET(sock, fdset); *max_fd = max(*max_fd, sock); } while(0)
 
-    MAX_FD_SET(sockin, recv_set);
+    for (i=0; i<insock_num; i++)
+        MAX_FD_SET(insocks[i], recv_set);
 
     LIST_FOREACH_SAFE(conn, &connlist, entries, next)
     {
@@ -1035,8 +1067,11 @@ http_fd_set(fd_set *recv_set, fd_set *send_set, int *max_fd,
 void http_poll(fd_set *recv_set, fd_set *send_set)
 {
     struct connection *conn;
+    unsigned int i;
 
-    if (FD_ISSET(sockin, recv_set)) accept_connection();
+    for (i=0; i<insock_num; i++)
+        if (FD_ISSET(insocks[i], recv_set))
+            accept_connection(insocks[i]);
 
     LIST_FOREACH(conn, &connlist, entries)
     switch (conn->state)
@@ -1063,7 +1098,21 @@ void http_poll(fd_set *recv_set, fd_set *send_set)
 }
 
 void http_stop(void) {
-    close(sockin);
+    struct connection *conn;
+    unsigned int i;
+
+    /* Close listening sockets. */
+    for (i=0; i<insock_num; i++)
+        close(insocks[i]);
+    free(insocks);
+    insocks = NULL;
+
+    /* Close in-flight connections. */
+    LIST_FOREACH(conn, &connlist, entries) {
+        LIST_REMOVE(conn, entries);
+        free_connection(conn);
+        free(conn);
+    }
 }
 
 /* vim:set ts=4 sw=4 et tw=78: */
diff --git a/http.h b/http.h
index 7987b33..df2a8e7 100644 (file)
--- a/http.h
+++ b/http.h
@@ -8,8 +8,8 @@
 #include <sys/select.h>
 #include <netinet/in.h>
 
-void http_init(const char * bindaddr, const unsigned short bindport,
-   const int max_conn);
+void http_add_bindaddr(const char *bindaddr);
+void http_listen(const unsigned short bindport);
 void http_fd_set(fd_set *recv_set, fd_set *send_set, int *max_fd,
    struct timeval *timeout, int *need_timeout);
 void http_poll(fd_set *read_set, fd_set *write_set);