server: Add --http-listen-address and --dccp-listen-address.
authorAndre Noll <maan@tuebingen.mpg.de>
Sat, 17 Mar 2018 01:11:01 +0000 (02:11 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Tue, 17 Apr 2018 07:29:57 +0000 (09:29 +0200)
Currently the http and the dccp sender accept connections on all IP
addresses. This commit makes it possible to configure the senders to
listen only on the specified subset of addresses.

To make this work, the sender_status structure has to be extended to
store an array of file descriptors rather than just a single one.
Several places need to iterate over all listening sockets of the
sender. The new FOR_EACH_LISTEN_FD macro helps to avoid duplicating
the corresponding loop.

The status part of the sender command now prints the listening
addresses instead of just on/off. This is why we also maintain the
ascii representation of the listening addresses in the sender_status
structure and introduce the format_url() helper to format host/port
pairs.

dccp_send.c
http_send.c
m4/lls/server.suite.m4
net.c
net.h
send.h
send_common.c

index 69ba65f..52ec6b7 100644 (file)
@@ -42,8 +42,11 @@ struct dccp_fec_client {
 static void dccp_pre_select(int *max_fileno, fd_set *rfds,
                __a_unused fd_set *wfds)
 {
-       if (dss->listen_fd >= 0)
-               para_fd_set(dss->listen_fd, rfds, max_fileno);
+       unsigned n;
+
+       FOR_EACH_LISTEN_FD(n, dss)
+               if (dss->listen_fds[n] >= 0)
+                       para_fd_set(dss->listen_fds[n], rfds, max_fileno);
 }
 
 /**
@@ -240,6 +243,7 @@ void dccp_send_init(struct sender *s)
        s->client_cmds[SENDER_delete] = NULL;
 
        init_sender_status(dss, OPT_RESULT(DCCP_ACCESS),
+               OPT_RESULT(DCCP_LISTEN_ADDRESS),
                OPT_UINT32_VAL(DCCP_PORT), OPT_UINT32_VAL(DCCP_MAX_CLIENTS),
                OPT_GIVEN(DCCP_DEFAULT_DENY));
        generic_com_on(dss, IPPROTO_DCCP);
index 210f85a..f53e6d8 100644 (file)
@@ -193,10 +193,13 @@ static void http_post_select(fd_set *rfds, __a_unused fd_set *wfds)
 static void http_pre_select(int *max_fileno, fd_set *rfds, fd_set *wfds)
 {
        struct sender_client *sc, *tmp;
+       unsigned n;
 
-       if (hss->listen_fd < 0)
-               return;
-       para_fd_set(hss->listen_fd, rfds, max_fileno);
+       FOR_EACH_LISTEN_FD(n, hss) {
+               if (hss->listen_fds[n] < 0)
+                       continue;
+               para_fd_set(hss->listen_fds[n], rfds, max_fileno);
+       }
        list_for_each_entry_safe(sc, tmp, &hss->client_list, node) {
                struct private_http_sender_data *phsd = sc->private_data;
                if (phsd->status == HTTP_CONNECTED) /* need to recv get request */
@@ -261,6 +264,7 @@ void http_send_init(struct sender *s)
        s->client_cmds[SENDER_delete] = NULL;
 
        init_sender_status(hss, OPT_RESULT(HTTP_ACCESS),
+               OPT_RESULT(HTTP_LISTEN_ADDRESS),
                OPT_UINT32_VAL(HTTP_PORT), OPT_UINT32_VAL(HTTP_MAX_CLIENTS),
                OPT_GIVEN(HTTP_DEFAULT_DENY));
        if (OPT_GIVEN(HTTP_NO_AUTOSTART))
index 89cf352..ca38aca 100644 (file)
@@ -171,6 +171,18 @@ version-string = GIT_VERSION()
        [option http]
                summary = Options for the http sender
                flag ignored
+       [option http-listen-address]
+               summary = listening addresses of the http sender
+               arg_info = required_arg
+               arg_type = string
+               typestr = addr
+               flag multiple
+               [help]
+                       The http sender of para_server listens on this port for incoming data
+                       connections. This option controls on which addresses the http sender
+                       should listen. See the documentation of the --listen-address above
+                       for the format of the address argument and the defaults.
+               [/help]
        [option http-port]
                summary = TCP port for http streaming
                arg_info = required_arg
@@ -178,9 +190,9 @@ version-string = GIT_VERSION()
                typestr = portnumber
                default_val = 8000
                [help]
-                       The http sender of para_server listens on this port for incoming
-                       connections. Clients are expected to send the usual http request
-                       message such as 'GET / HTTP/'.
+                       This option has the same meaning as --port, but applies to http
+                       data connections and applies to the addresses specified as arguments
+                       to --http-listen-address.
                [/help]
        [option http-default-deny]
                summary = make the http access control list a whitelist
@@ -229,6 +241,15 @@ version-string = GIT_VERSION()
        [option dccp]
                summary = Options for the dccp sender
                flag ignored
+       [option dccp-listen-address]
+               summary = listening addresses of the dccp sender
+               arg_info = required_arg
+               arg_type = string
+               typestr = addr
+               flag multiple
+               [help]
+                       Like --http-listen-address, but for the dccp sender.
+               [/help]
        [option dccp-port]
                summary = port for dccp streaming
                arg_info = required_arg
diff --git a/net.c b/net.c
index 672e09e..ba19408 100644 (file)
--- a/net.c
+++ b/net.c
@@ -180,6 +180,36 @@ failed:
        return NULL;
 }
 
+/**
+ * Pretty-print a host/port pair.
+ *
+ * \param url NULL, or any string accepted by \ref parse_url().
+ * \param default_port Applies if url has no port.
+ *
+ * If the url argument is NULL, the function returns the string
+ * 0.0.0.0:default_port. Otherwise it calls \ref parse_url() to check the
+ * syntax of the input string given by url. On errors the string "?" is
+ * returned. Otherwise, if url contains a port, a copy of url is returned. If
+ * no port was supplied, a colon and the default port are appended to url.
+ *
+ * \return In all cases the returned string is a allocated with malloc(3) and
+ * has to be freed by the caller.
+ */
+char *format_url(const char *url, int default_port)
+{
+       char host[MAX_HOSTLEN];
+       int url_port;
+
+       if (!url)
+               return make_message("0.0.0.0:%d", default_port);
+       if (!parse_url(url, host, sizeof(host), &url_port))
+               return make_message("?");
+       if (url_port < 0)
+               return make_message("%s:%d", url, default_port);
+       else
+               return para_strdup(url);
+}
+
 /**
  * Stringify port number, resolve into service name where defined.
  *
diff --git a/net.h b/net.h
index 0e93653..2256f37 100644 (file)
--- a/net.h
+++ b/net.h
@@ -71,6 +71,7 @@ extern char *parse_cidr(const char *cidr,
                        char *addr, ssize_t addrlen, int32_t *netmask);
 extern char *parse_url(const char *url,
                       char *host, ssize_t hostlen, int32_t *port);
+char *format_url(const char *url, int default_port);
 extern const char *stringify_port(int port, const char *transport);
 /**
  * Ensure that string conforms to the IPv4 address format.
diff --git a/send.h b/send.h
index 84f35f9..cff1abd 100644 (file)
--- a/send.h
+++ b/send.h
@@ -160,10 +160,14 @@ struct fec_client_parms {
 
 /** Describes the current status of one paraslash sender. */
 struct sender_status {
-       /** The file descriptor of the socket this sender is listening on. */
-       int listen_fd;
-       /** The TCP/DCCP port used by this sender. */
-       int port;
+       /** Number of sockets to listen on, size of the two arrays below. */
+       unsigned num_listen_fds;
+       /** Derived from --http-listen-address and --dccp-listen-address. */
+       char **listen_addresses;
+       /** Default TCP/DCCP port number for addresses w/o port. */
+       int default_port;
+       /** The socket fd(s) this sender is listening on. */
+       int *listen_fds;
        /** The current number of simultaneous connections. */
        int num_clients;
        /** The maximal number of simultaneous connections. */
@@ -176,11 +180,15 @@ struct sender_status {
        struct list_head client_list;
 };
 
+/** Iterate over all listening addresses of the http/dccp sender. */
+#define FOR_EACH_LISTEN_FD(_n, _ss) for (_n = 0; _n < (_ss)->num_listen_fds; _n++)
+
 void shutdown_client(struct sender_client *sc, struct sender_status *ss);
 void shutdown_clients(struct sender_status *ss);
 void init_sender_status(struct sender_status *ss,
-               const struct lls_opt_result *acl_opt_result, int port,
-               int max_clients, int default_deny);
+               const struct lls_opt_result *acl_opt_result,
+               const struct lls_opt_result *listen_address_opt_result,
+               int default_port, int max_clients, int default_deny);
 char *generic_sender_status(struct sender_status *ss, const char *name);
 void generic_com_allow(struct sender_command_data *scd,
                struct sender_status *ss);
index 5a9ddf6..f7770a0 100644 (file)
@@ -103,20 +103,38 @@ int send_queued_chunks(int fd, struct chunk_queue *cq)
  *
  * \param ss The struct to initialize.
  * \param acl_opt_result Contains array of --{http|dccp}-access arguments.
- * \param port The tcp or dccp port to listen on.
+ * \param listen_address_opt_result Where to listen on.
+ * \param default_port Used for addresses with no specified port.
  * \param max_clients The maximal number of simultaneous connections.
  * \param default_deny Whether a blacklist should be used for access control.
  */
 void init_sender_status(struct sender_status *ss,
-               const struct lls_opt_result *acl_opt_result, int port,
-               int max_clients, int default_deny)
+               const struct lls_opt_result *acl_opt_result,
+               const struct lls_opt_result *listen_address_opt_result,
+               int default_port, int max_clients, int default_deny)
 {
        int i;
+       unsigned n = lls_opt_given(listen_address_opt_result);
 
-       ss->listen_fd = -1;
-       INIT_LIST_HEAD(&ss->client_list);
-       ss->port = port;
+       if (n == 0) {
+               ss->num_listen_fds = 1;
+               ss->listen_addresses = para_malloc(sizeof(char *));
+               ss->listen_addresses[0] = NULL;
+               ss->listen_fds = para_malloc(sizeof(int));
+               ss->listen_fds[0] = -1;
+       } else {
+               ss->num_listen_fds = n;
+               ss->listen_addresses = para_malloc(n * sizeof(char *));
+               ss->listen_fds = para_malloc(n * sizeof(int));
+               FOR_EACH_LISTEN_FD(i, ss) {
+                       ss->listen_addresses[i] = para_strdup(lls_string_val(i,
+                               listen_address_opt_result));
+                       ss->listen_fds[i] = -1;
+               }
+       }
+       ss->default_port = default_port;
 
+       INIT_LIST_HEAD(&ss->client_list);
        /* Initialize an access control list */
        INIT_LIST_HEAD(&ss->acl);
        for (i = 0; i < lls_opt_given(acl_opt_result); i++) {
@@ -144,24 +162,35 @@ void init_sender_status(struct sender_status *ss,
  */
 char *generic_sender_status(struct sender_status *ss, const char *name)
 {
-       char *clnts = NULL, *ret;
+       char *clnts = NULL, *ret, *addr = NULL;
        struct sender_client *sc, *tmp_sc;
-
+       unsigned n;
        char *acl_contents = acl_get_contents(&ss->acl);
+
        list_for_each_entry_safe(sc, tmp_sc, &ss->client_list, node) {
                char *tmp = make_message("%s%s ", clnts? clnts : "", sc->name);
                free(clnts);
                clnts = tmp;
        }
+       FOR_EACH_LISTEN_FD(n, ss) {
+               char *url = format_url(ss->listen_addresses[n], ss->default_port);
+               char *tmp = make_message("%s%s%s (fd %d)", addr?
+                       addr : "", addr? ", " : "", url,
+                       ss->listen_fds[n]);
+               free(url);
+               free(addr);
+               addr = tmp;
+       }
        ret = make_message(
-               "status: %s\n"
-               "port: %s\n"
+               "listening address(es): %s\n"
+               "default port: %s\n"
                "number of connected clients: %d\n"
                "maximal number of clients: %d%s\n"
                "connected clients: %s\n"
                "access %s list: %s\n",
-               (ss->listen_fd >= 0)? "on" : "off",
-               stringify_port(ss->port, strcmp(name, "http") ? "dccp" : "tcp"),
+               addr,
+               stringify_port(ss->default_port,
+                       strcmp(name, "http")? "dccp" : "tcp"),
                ss->num_clients,
                ss->max_clients,
                ss->max_clients > 0? "" : " (unlimited)",
@@ -215,29 +244,39 @@ void generic_com_deny(struct sender_command_data *scd,
  */
 void generic_com_on(struct sender_status *ss, unsigned protocol)
 {
-       int fd, ret;
+       int ret;
+       unsigned n;
 
-       if (ss->listen_fd >= 0)
-               return;
-       ret = para_listen_simple(protocol, ss->port);
-       if (ret < 0) {
-               PARA_ERROR_LOG("could not listen on port %d: %s\n", ss->port,
-                       para_strerror(-ret));
-               return;
-       }
-       fd = ret;
-       ret = mark_fd_nonblocking(fd);
-       if (ret < 0) {
-               PARA_ERROR_LOG("could not set %s socket fd for port %d to "
-                       "nonblocking mode: %s\n",
-                       protocol == IPPROTO_TCP? "TCP" : "DCCP", ss->port,
-                       para_strerror(-ret));
-               close(fd);
-               return;
+       FOR_EACH_LISTEN_FD(n, ss) {
+               if (ss->listen_fds[n] >= 0)
+                       continue;
+               ret = para_listen(protocol, ss->listen_addresses[n],
+                       ss->default_port);
+               if (ret < 0) {
+                       char *url = format_url(ss->listen_addresses[n],
+                               ss->default_port);
+                       PARA_ERROR_LOG("could not listen on %s %s: %s\n",
+                               protocol == IPPROTO_TCP? "TCP" : "DCCP",
+                               url, para_strerror(-ret));
+                       free(url);
+                       continue;
+               }
+               ss->listen_fds[n] = ret;
+               ret = mark_fd_nonblocking(ss->listen_fds[n]);
+               if (ret < 0) {
+                       char *url = format_url(ss->listen_addresses[n],
+                               ss->default_port);
+                       PARA_ERROR_LOG("could not set %s socket fd for %s to "
+                               "nonblocking mode: %s\n",
+                               protocol == IPPROTO_TCP? "TCP" : "DCCP", url,
+                               para_strerror(-ret));
+                       free(url);
+                       close(ss->listen_fds[n]);
+                       ss->listen_fds[n] = -1;
+                       continue;
+               }
+               add_close_on_fork_list(ss->listen_fds[n]);
        }
-       add_close_on_fork_list(fd);
-       ss->listen_fd = ret;
-       return;
 }
 
 /**
@@ -251,23 +290,26 @@ void generic_com_on(struct sender_status *ss, unsigned protocol)
  */
 void generic_com_off(struct sender_status *ss)
 {
-       if (ss->listen_fd < 0)
-               return;
-       PARA_NOTICE_LOG("closing port %d\n", ss->port);
-       close(ss->listen_fd);
-       del_close_on_fork_list(ss->listen_fd);
-       shutdown_clients(ss);
-       ss->listen_fd = -1;
+       unsigned n;
+
+       FOR_EACH_LISTEN_FD(n, ss) {
+               if (ss->listen_fds[n] < 0)
+                       return;
+               close(ss->listen_fds[n]);
+               del_close_on_fork_list(ss->listen_fds[n]);
+               shutdown_clients(ss);
+               ss->listen_fds[n] = -1;
+       }
 }
 
 /**
- * Accept a connection on the socket this server is listening on.
+ * Accept a connection on the socket(s) this server is listening on.
  *
  * \param ss The sender whose listening fd is ready for reading.
  * \param rfds Passed to para_accept(),
  *
- * This calls para_accept() and performs the following actions on the resulting
- * file descriptor fd:
+ * This accepts incoming connections on any of the listening sockets of the
+ * server. If there is a connection pending, the function
  *
  *     - Checks whether the maximal number of connections are exceeded.
  *     - Sets \a fd to nonblocking mode.
@@ -292,36 +334,40 @@ struct sender_client *accept_sender_client(struct sender_status *ss, fd_set *rfd
 {
        struct sender_client *sc;
        int fd, ret;
+       unsigned n;
 
-       if (ss->listen_fd < 0)
-               return NULL;
-       ret = para_accept(ss->listen_fd, rfds, NULL, 0, &fd);
-       if (ret < 0)
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
-       if (ret <= 0)
-               return NULL;
-       ret = -E_MAX_CLIENTS;
-       if (ss->max_clients > 0 && ss->num_clients >= ss->max_clients)
-               goto err_out;
-       ret = mark_fd_nonblocking(fd);
-       if (ret < 0)
-               goto err_out;
-       ret = acl_check_access(fd, &ss->acl, ss->default_deny);
-       if (ret < 0)
-               goto err_out;
-       ss->num_clients++;
-       sc = para_calloc(sizeof(*sc));
-       sc->fd = fd;
-       sc->name = para_strdup(remote_name(fd));
-       sc->cq = cq_new(MAX_CQ_BYTES);
-       para_list_add(&sc->node, &ss->client_list);
-       add_close_on_fork_list(fd);
-       PARA_INFO_LOG("accepted client #%d: %s (fd %d)\n", ss->num_clients,
-               sc->name, fd);
-       return sc;
-err_out:
-       PARA_WARNING_LOG("%s\n", para_strerror(-ret));
-       close(fd);
+       FOR_EACH_LISTEN_FD(n, ss) {
+               if (ss->listen_fds[n] < 0)
+                       continue;
+               ret = para_accept(ss->listen_fds[n], rfds, NULL, 0, &fd);
+               if (ret < 0)
+                       goto warn;
+               if (ret == 0)
+                       continue;
+               ret = -E_MAX_CLIENTS;
+               if (ss->max_clients > 0 && ss->num_clients >= ss->max_clients)
+                       goto close_fd_and_warn;
+               ret = mark_fd_nonblocking(fd);
+               if (ret < 0)
+                       goto close_fd_and_warn;
+               ret = acl_check_access(fd, &ss->acl, ss->default_deny);
+               if (ret < 0)
+                       goto close_fd_and_warn;
+               ss->num_clients++;
+               sc = para_calloc(sizeof(*sc));
+               sc->fd = fd;
+               sc->name = para_strdup(remote_name(fd));
+               sc->cq = cq_new(MAX_CQ_BYTES);
+               para_list_add(&sc->node, &ss->client_list);
+               add_close_on_fork_list(fd);
+               PARA_INFO_LOG("accepted client #%d: %s (fd %d)\n", ss->num_clients,
+                       sc->name, fd);
+               return sc;
+close_fd_and_warn:
+               close(fd);
+warn:
+               PARA_WARNING_LOG("%s\n", para_strerror(-ret));
+       }
        return NULL;
 }