]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 'refs/heads/t/listen-address'
authorAndre Noll <maan@tuebingen.mpg.de>
Mon, 28 May 2018 16:29:06 +0000 (18:29 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Mon, 28 May 2018 16:31:26 +0000 (18:31 +0200)
A short series which adds options for para_server that allow the user
to specify the listening addresses of the passive sockets.

The resolution of the conflicts in server.c and send_common.c have
been well tested and were cooking for a couple of weeks.

* refs/heads/t/listen-address:
  server: Add --http-listen-address and --dccp-listen-address.
  server: Implement --listen-address for control service.

1  2 
NEWS.md
dccp_send.c
http_send.c
m4/lls/server.suite.m4
send.h
send_common.c
server.c

diff --combined NEWS.md
index d193a1a3072ebc0d71f53691fc589fba3643f04d,d0bd6585a3a2f04be5ec262d580bfb8800733498..4a837ed8fe77e42ee889757c17805bb8addf9e7e
+++ b/NEWS.md
@@@ -1,23 -1,6 +1,26 @@@
  NEWS
  ====
  
 +-------------------------------------------
 +0.6.2 (to be accounced) "elastic diversity"
 +-------------------------------------------
 +
 +- para_gui no longer waits up to one second to update the screen when
 +  the geometry of the terminal changes.
 +- Minor documentation improvements.
 +- Improvements to the crypto subsystem.
 +- The server subcommand "task" has been deprecated. It still works,
 +  but prints nothing. It will be removed in the next major release.
 +- Server log output is now serialized, avoiding issues with partial
 +  lines.
 +- It is now possible to switch to a different afs database by changing
 +  the server configuration and sending SIGHUP to the server process.
++- New server options: --listen--address, --http-listen-address and
++  --dccp-listen-address. These options restrict the set of listening
++  addresses of the TCP and DCCP sockets of the server process.
 +
 +Download: [tarball](./releases/paraslash-git.tar.xz)
 +
  ----------------------------------------
  0.6.1 (2017-09-23) "segmented iteration"
  ----------------------------------------
diff --combined dccp_send.c
index 770ac601047a892025724bbb7e677a7d8bcaed18,52ec6b73d2328732290c274c89df0091ddb0b7aa..496895a509f15466e7430af454c784a09bc3ce7e
@@@ -28,6 -28,9 +28,6 @@@
  #include "sched.h"
  #include "vss.h"
  #include "fd.h"
 -#include "close_on_fork.h"
 -#include "chunk_queue.h"
 -#include "acl.h"
  
  static struct sender_status dccp_sender_status, *dss = &dccp_sender_status;
  
@@@ -39,8 -42,11 +39,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);
  }
  
  /**
@@@ -80,12 -86,6 +83,12 @@@ static void dccp_shutdown_clients(void
                dccp_shutdown_client(sc);
  }
  
 +static void dccp_shutdown(void)
 +{
 +      dccp_shutdown_clients();
 +      generic_acl_deplete(&dss->acl);
 +}
 +
  /** * Obtain current MPS according to RFC 4340, sec. 14. */
  static int dccp_init_fec(struct sender_client *sc)
  {
@@@ -218,39 -218,33 +221,40 @@@ static char *dccp_status(void
        return result;
  }
  
 -/**
 - * The init function of the dccp sender.
 - *
 - * \param s pointer to the dccp sender struct.
 - *
 - * It initializes all function pointers of \a s and starts
 - * listening on the given port.
 +/*
 + * Initialize the client list and the access control list and listen on the
 + * dccp port.
   */
 -void dccp_send_init(struct sender *s)
 +static void dccp_send_init(void)
  {
 -      s->status = dccp_status;
 -      s->send = NULL;
 -      s->pre_select = dccp_pre_select;
 -      s->post_select = dccp_post_select;
 -      s->shutdown_clients = dccp_shutdown_clients;
 -      s->resolve_target = NULL;
 -      s->help = generic_sender_help;
 -      s->client_cmds[SENDER_on] = dccp_com_on;
 -      s->client_cmds[SENDER_off] = dccp_com_off;
 -      s->client_cmds[SENDER_deny] = dccp_com_deny;
 -      s->client_cmds[SENDER_allow] = dccp_com_allow;
 -      s->client_cmds[SENDER_add] = NULL;
 -      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);
  }
 +
 +/**
 + * The DCCP sender.
 + *
 + * This sender offers congestion control not available in plain TCP. Most
 + * methods of the sender structure are implemented as simple wrappers for the
 + * generic functions defined in \ref send_common.c. Like UDP streams, DCCP
 + * streams are sent FEC-encoded.
 + */
 +const struct sender dccp_sender = {
 +      .name = "dccp",
 +      .init = dccp_send_init,
 +      .shutdown = dccp_shutdown,
 +      .pre_select = dccp_pre_select,
 +      .post_select = dccp_post_select,
 +      .shutdown_clients = dccp_shutdown_clients,
 +      .client_cmds = {
 +              [SENDER_on] = dccp_com_on,
 +              [SENDER_off] = dccp_com_off,
 +              [SENDER_deny] = dccp_com_deny,
 +              [SENDER_allow] = dccp_com_allow,
 +      },
 +      .help = generic_sender_help,
 +      .status = dccp_status,
 +};
diff --combined http_send.c
index 4d612285aeff00f7d8d153be5c9f1743f3fa9425,f53e6d8cc5100176cf54b19de057375eb4960cb8..330b45ac1b3ec675a497796be614c07c0bbd9c36
@@@ -26,6 -26,7 +26,6 @@@
  #include "close_on_fork.h"
  #include "fd.h"
  #include "chunk_queue.h"
 -#include "acl.h"
  
  /** Message sent to clients that do not send a valid get request. */
  #define HTTP_ERR_MSG "HTTP/1.0 400 Bad Request\n"
@@@ -76,12 -77,6 +76,12 @@@ static void http_shutdown_clients(void
        shutdown_clients(hss);
  }
  
 +static void http_shutdown(void)
 +{
 +      http_shutdown_clients();
 +      generic_acl_deplete(&hss->acl);
 +}
 +
  static int queue_chunk_or_shutdown(struct sender_client *sc,
                struct sender_status *ss, const char *buf, size_t num_bytes)
  {
@@@ -198,10 -193,13 +198,13 @@@ static void http_post_select(fd_set *rf
  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 */
@@@ -241,44 -239,35 +244,45 @@@ static char *http_status(void
        return generic_sender_status(hss, "http");
  }
  
 -/**
 - * The init function of the http sender.
 - *
 - * \param s Pointer to the http sender struct.
 - *
 - * It initializes all function pointers of \a s, the client list and the access
 - * control list. If the autostart option was given, the tcp port is opened.
 +/*
 + * Initialize the client list and the access control list, and optionally
 + * listen on the tcp port.
   */
 -void http_send_init(struct sender *s)
 +static void http_send_init(void)
  {
 -      s->status = http_status;
 -      s->send = http_send;
 -      s->pre_select = http_pre_select;
 -      s->post_select = http_post_select;
 -      s->shutdown_clients = http_shutdown_clients;
 -      s->resolve_target = NULL;
 -      s->help = generic_sender_help;
 -      s->client_cmds[SENDER_on] = http_com_on;
 -      s->client_cmds[SENDER_off] = http_com_off;
 -      s->client_cmds[SENDER_deny] = http_com_deny;
 -      s->client_cmds[SENDER_allow] = http_com_allow;
 -      s->client_cmds[SENDER_add] = NULL;
 -      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))
                return;
        generic_com_on(hss, IPPROTO_TCP);
  }
 +
 +/**
 + * The HTTP sender.
 + *
 + * This is the only sender which does not FEC-encode the stream. This is not
 + * necessary because HTTP sits on top of TCP, a reliable transport which
 + * retransmits lost packets automatically. The sender employs per-client queues
 + * which queue chunks of audio data if they can not be sent immediately because
 + * the write operation would block. Most methods of the sender are implemented
 + * as wrappers for the generic functions defined in \ref send_common.c.
 + */
 +const struct sender http_sender = {
 +      .name = "http",
 +      .init = http_send_init,
 +      .shutdown = http_shutdown,
 +      .pre_select = http_pre_select,
 +      .post_select = http_post_select,
 +      .send = http_send,
 +      .shutdown_clients = http_shutdown_clients,
 +      .client_cmds = {
 +              [SENDER_on] = http_com_on,
 +              [SENDER_off] = http_com_off,
 +              [SENDER_deny] = http_com_deny,
 +              [SENDER_allow] = http_com_allow,
 +      },
 +      .help = generic_sender_help,
 +      .status = http_status,
 +};
diff --combined m4/lls/server.suite.m4
index 6ffdc0078ad15bc02043dda9cd953fb758347ac5,ca38aca7ad1ec795eeed3555802961749e072c35..be8f02f5fa4bda5101f0b2ffa67f5e4332ec0b64
@@@ -34,18 -34,45 +34,45 @@@ version-string = GIT_VERSION(
        m4_include(log-timing.m4)
        m4_include(color.m4)
        m4_include(per-command-options-section.m4)
+       [option listen-address]
+               summary = local listening addresses for the control service
+               arg_info = required_arg
+               arg_type = string
+               typestr = addr
+               flag multiple
+               [help]
+                       para_server listens on a TCP socket for incoming connections from
+                       para_client or para_audiod. This option controls on which addresses
+                       the server should listen. If the option is not given, the server
+                       listens on all local addresses (INADDR_ANY for IPv4 addresses,
+                       IN6ADDR_ANY_INIT for IPv6 addresses).
+                       The argument specifies an IPv4 or an IPv6 address, either a numerical
+                       network address (for IPv4, numbers-and-dots notation as supported
+                       by inet_aton(3); for IPv6, hexadecimal string format as supported
+                       by inet_pton(3)), or a network hostname, whose network addresses is
+                       looked up and resolved. The address can optionally include a port
+                       number. For addresses for which no port number is given, the argument
+                       of the --port option (see below) is implied.
+                       This option may be given multiple times. The server will then listen
+                       on each of the specified addresses.
+                       Examples: 10.10.1.1, 10.10.1.2:2991, localhost, localhost:2991,
+                       [::1]:2991, [badc0de::1].
+               [/help]
        [option port]
                short_opt = p
-               summary = listening port of the paraslash control service
+               summary = listening port of the control service
                arg_info = required_arg
                arg_type = uint32
                typestr = portnumber
                default_val = 2990
                [help]
-                       para_server listens on this TCP port for incoming connections
-                       from clients such as para_client. If the default port is changed,
-                       the corresponding option of para_client must be used to connect
-                       to para_server.
+                       This option applies only to addresses given to --listen-address
+                       (see above) which do no include a port number. If the default port
+                       is changed, the corresponding option of para_client must be used to
+                       connect to para_server.
                [/help]
        [option user-list]
                summary = file which contains user names and credentials
        [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
                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
                [help]
                        The default is to use blacklists, i.e. connections to the http sender
 -                      are allowed unless the connecting host matches a pattern given by a
 -                      http-access option. This allows to use access control the other way
 -                      round: Connections are denied from hosts which are not explicitly
 -                      allowed by one or more http-access options.
 +                      are allowed unless the connecting host matches a pattern given by
 +                      a http-access option. This option allows using access control lists
 +                      the other way round: Connections are denied from hosts which are not
 +                      explicitly allowed by one or more http-access options.
                [/help]
        [option http-access]
                summary = add an entry to the http access control list
        [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
                        (path MTU) of an incoming connection, i.e. on the largest packet size
                        that can be transmitted without causing fragmentation.
  
 -                      This option allows to use a value less than the MPS in order to
 -                      fine-tune application performance. Values greater than the MPS of an
 -                      incoming connection can not be set.
 +                      This option allows values less than the MPS in order to fine-tune
 +                      application performance. Values greater than the MPS of an incoming
 +                      connection can not be set.
                [/help]
        [option dccp-data-slices-per-group]
                summary = the number of non-redundant slices per FEC group
diff --combined send.h
index 7a4c01bcb6cc1c80c77e06bc14b164dce8b3304d,cff1abd52c23a40f3eebfbbb173a99522ad2a6a4..67b47e48d9e7fd0b96fa4c5c7abd95594c6e2cf0
--- 1/send.h
--- 2/send.h
+++ b/send.h
@@@ -27,11 -27,14 +27,11 @@@ struct sender 
        /** The name of the sender. */
        const char *name;
        /**
 -       * The init function of this sender.
 -       *
 -       * It must fill in all function pointers of \a s as well as the \a
 -       * client_cmds array, see below. It should also do all necessary
 -       * preparations to init this sending facility, for example it could
 -       * open a tcp port.
 +       * Parse the command line options and initialize this sender (e.g.,
 +       * initialize target or access control lists, listen on a network
 +       * socket, etc.).
         */
 -      void (*init)(struct sender *s);
 +      void (*init)(void);
        /**
         * Return the help text of this sender.
         *
@@@ -85,8 -88,6 +85,8 @@@
         * the clients aware of the end-of-file condition.
         */
        void (*shutdown_clients)(void);
 +      /** Dellocate all resources. Only called on exit. */
 +      void (*shutdown)(void);
        /**
         * Array of function pointers for the sender subcommands.
         *
        int (*resolve_target)(const char *, struct sender_command_data *);
  };
  
 +/** NULL-terminated list, defined in \ref vss.c. */
 +extern const struct sender * const senders[];
 +/** Iterate over all senders. */
 +#define FOR_EACH_SENDER(_i) for ((_i) = 0; senders[(_i)]; (_i)++)
 +
  /** Describes one client, connected to a paraslash sender. */
  struct sender_client {
        /** The file descriptor of the client. */
@@@ -164,10 -160,14 +164,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. */
        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);
  void generic_com_deny(struct sender_command_data *scd,
                struct sender_status *ss);
  void generic_com_on(struct sender_status *ss, unsigned protocol);
 +void generic_acl_deplete(struct list_head *acl);
  void generic_com_off(struct sender_status *ss);
  char *generic_sender_help(void);
  struct sender_client *accept_sender_client(struct sender_status *ss, fd_set *rfds);
diff --combined send_common.c
index 61a12c827342758fa97949db669872e8b2d504b8,f7770a01394e9b0089ff4b5dc20b2befcf54e5bb..24b14ab8ed50b90c956fc6578199f8a4da3f2c4e
@@@ -46,10 -46,8 +46,10 @@@ void shutdown_client(struct sender_clie
  {
        PARA_INFO_LOG("shutting down %s on fd %d\n", sc->name, sc->fd);
        free(sc->name);
 -      close(sc->fd);
 -      del_close_on_fork_list(sc->fd);
 +      if (!process_is_command_handler()) {
 +              close(sc->fd);
 +              del_close_on_fork_list(sc->fd);
 +      }
        cq_destroy(sc->cq);
        list_del(&sc->node);
        free(sc->private_data);
@@@ -105,20 -103,38 +105,38 @@@ int send_queued_chunks(int fd, struct c
   *
   * \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++) {
   */
  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)",
@@@ -190,22 -217,6 +219,22 @@@ void generic_com_allow(struct sender_co
        acl_allow(scd->host, scd->netmask, &ss->acl, ss->default_deny);
  }
  
 +/**
 + * Empty the access control list of a sender.
 + *
 + * \param acl The access control list of the sender.
 + *
 + * This is called from the ->shutdown methods of the http and the dccp sender.
 + */
 +void generic_acl_deplete(struct list_head *acl)
 +{
 +      /*
 +       * Since default_deny is false, the ACL is considered a blacklist. A
 +       * netmask of zero matches any IP address, so this call empties the ACL.
 +       */
 +      acl_allow("0.0.0.0", 0 /* netmask */, acl, 0 /* default_deny */);
 +}
 +
  /**
   * Deny connections from the given range of IP addresses.
   *
@@@ -233,29 -244,39 +262,39 @@@ void generic_com_deny(struct sender_com
   */
  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 = fd;
-       return;
  }
  
  /**
   */
  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.
@@@ -310,36 -334,40 +352,40 @@@ struct sender_client *accept_sender_cli
  {
        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;
  }
  
diff --combined server.c
index f19fc996ecc7355ddf24379e242fcec4da287dd6,9c11b23cbb4801f15e08ae3c1a0a559322b38578..a344b77440751a6e7017d9d7e641390a36eb157f
+++ b/server.c
@@@ -100,39 -100,17 +100,40 @@@ uint32_t afs_socket_cookie
  /** The mutex protecting the shared memory area containing the mmd struct. */
  int mmd_mutex;
  
 +/* Serializes log output. */
 +static int log_mutex;
 +
  static struct sched sched;
  static struct signal_task *signal_task;
  
  /** The process id of the audio file selector process. */
  pid_t afs_pid = 0;
  
 +/* The the main server process (parent of afs and the command handlers). */
 +static pid_t server_pid;
 +
 +/**
 + * Tell whether the executing process is a command handler.
 + *
 + * Cleanup on exit must be performed differently for command handlers.
 + *
 + * \return True if the pid of the executing process is neither the server pid
 + * nor the afs pid.
 + */
 +bool process_is_command_handler(void)
 +{
 +      pid_t pid = getpid();
 +
 +      return pid != afs_pid && pid != server_pid;
 +}
 +
  /** The task responsible for server command handling. */
  struct server_command_task {
-       /** TCP port on which para_server listens for connections. */
-       int listen_fd;
+       unsigned num_listen_fds; /* only one by default */
+       /** TCP socket(s) on which para_server listens for connections. */
+       int *listen_fds;
 +      /* File descriptor for the accepted socket. */
 +      int child_fd;
        /** Copied from para_server's main function. */
        int argc;
        /** Argument vector passed to para_server's main function. */
@@@ -154,17 -132,9 +155,17 @@@ char *server_get_tasks(void
        return get_task_list(&sched);
  }
  
 -/*
 - * setup shared memory area and get mutex for locking
 - */
 +static void pre_log_hook(void)
 +{
 +      mutex_lock(log_mutex);
 +}
 +
 +static void post_log_hook(void)
 +{
 +      mutex_unlock(log_mutex);
 +}
 +
 +/* Setup shared memory area and init mutexes */
  static void init_ipc_or_die(void)
  {
        void *shm;
        if (ret < 0)
                goto err_out;
        mmd_mutex = ret;
 +      ret = mutex_new();
 +      if (ret < 0)
 +              goto destroy_mmd_mutex;
 +      log_mutex = ret;
  
        mmd->num_played = 0;
        mmd->num_commands = 0;
        mmd->vss_status_flags = VSS_NEXT;
        mmd->new_vss_status_flags = VSS_NEXT;
        return;
 +destroy_mmd_mutex:
 +      mutex_destroy(mmd_mutex);
  err_out:
        PARA_EMERG_LOG("%s\n", para_strerror(-ret));
        exit(EXIT_FAILURE);
@@@ -283,7 -247,7 +284,7 @@@ success
                daemon_set_flag(DF_LOG_TIMING);
        daemon_set_priority(OPT_UINT32_VAL(PRIORITY));
        if (user_list_file)
 -              init_user_list(user_list_file);
 +              user_list_init(user_list_file);
        ret = 1;
  free_cf:
        free(cf);
@@@ -311,12 -275,8 +312,12 @@@ static void handle_sighup(void
  
  static int signal_post_select(struct sched *s, __a_unused void *context)
  {
 -      int signum = para_next_signal(&s->rfds);
 +      int ret, signum;
  
 +      ret = task_get_notification(signal_task->task);
 +      if (ret < 0)
 +              return ret;
 +      signum = para_next_signal(&s->rfds);
        switch (signum) {
        case 0:
                return 0;
        case SIGCHLD:
                for (;;) {
                        pid_t pid;
 -                      int ret = para_reap_child(&pid);
 +                      ret = para_reap_child(&pid);
                        if (ret <= 0)
                                break;
                        if (pid != afs_pid)
                PARA_EMERG_LOG("terminating on signal %d\n", signum);
                kill(0, SIGTERM);
                /*
 -               * We must wait for afs because afs catches SIGINT/SIGTERM.
 -               * Before reacting to the signal, afs might want to use the
 +               * We must wait for all of our children to die. For the afs
 +               * process or a command handler might want to use the
                 * shared memory area and the mmd mutex.  If we destroy this
                 * mutex too early and afs tries to lock the shared memory
                 * area, the call to mutex_lock() will fail and terminate the
                 * afs process. This leads to dirty osl tables.
 -               *
 -               * There's no such problem with the other children of the
 -               * server process (the command handlers) as these reset their
 -               * SIGINT/SIGTERM handlers to the default action, i.e.  these
 -               * processes get killed immediately by the above kill().
                 */
 -              PARA_INFO_LOG("waiting for afs (pid %d) to die\n",
 -                      (int)afs_pid);
 -              waitpid(afs_pid, NULL, 0);
 +              PARA_INFO_LOG("waiting for child processes to die\n");
 +              mutex_unlock(mmd_mutex);
 +              while (wait(NULL) != -1 || errno != ECHILD)
 +                      ; /* still at least one child alive */
 +              mutex_lock(mmd_mutex);
  cleanup:
                free(mmd->afd.afhi.chunk_table);
 -              close_listed_fds();
 -              mutex_destroy(mmd_mutex);
 -              shm_detach(mmd);
 -              exit(EXIT_FAILURE);
 +              task_notify_all(s, E_DEADLY_SIGNAL);
 +              return -E_DEADLY_SIGNAL;
        }
        return 0;
  }
@@@ -382,22 -347,22 +383,22 @@@ static void init_signal_task(void
  
  static void command_pre_select(struct sched *s, void *context)
  {
+       unsigned n;
        struct server_command_task *sct = context;
-       para_fd_set(sct->listen_fd, &s->rfds, &s->max_fileno);
+       for (n = 0; n < sct->num_listen_fds; n++)
+               para_fd_set(sct->listen_fds[n], &s->rfds, &s->max_fileno);
  }
  
- static int command_post_select(struct sched *s, void *context)
 -static int command_task_accept(unsigned listen_idx, fd_set *rfds,
++static int command_task_accept(unsigned listen_idx, struct sched *s,
+               struct server_command_task *sct)
  {
-       struct server_command_task *sct = context;
        int new_fd, ret, i;
        char *peer_name;
        pid_t child_pid;
        uint32_t *chunk_table;
  
-       ret = task_get_notification(sct->task);
-       if (ret < 0)
-               return ret;
-       ret = para_accept(sct->listen_fd, &s->rfds, NULL, 0, &new_fd);
 -      ret = para_accept(sct->listen_fds[listen_idx], rfds, NULL, 0, &new_fd);
++      ret = para_accept(sct->listen_fds[listen_idx], &s->rfds, NULL, 0, &new_fd);
        if (ret <= 0)
                goto out;
        mmd->num_connects++;
        PARA_INFO_LOG("accepted connection from %s\n", peer_name);
        /* mmd might already have changed at this point */
        free(chunk_table);
 -      alarm(ALARM_TIMEOUT);
 -      close_listed_fds();
 -      signal_shutdown(signal_task);
 +      sct->child_fd = new_fd;
        /*
         * put info on who we are serving into argv[0] to make
         * client ip visible in top/ps
                memset(sct->argv[i], 0, strlen(sct->argv[i]));
        i = sct->argc - 1 - lls_num_inputs(cmdline_lpr);
        sprintf(sct->argv[i], "para_server (serving %s)", peer_name);
 -      handle_connect(new_fd);
 -      /* never reached*/
 +      /* ask other tasks to terminate */
 +      task_notify_all(s, E_CHILD_CONTEXT);
 +      /*
 +       * After we return, the scheduler calls server_select() with a minimal
 +       * timeout value, because the remaining tasks have a notification
 +       * pending. Next it calls the ->post_select method of these tasks,
 +       * which will return negative in view of the notification. This causes
 +       * schedule() to return as there are no more runnable tasks.
 +       *
 +       * Note that semaphores are not inherited across a fork(), so we don't
 +       * hold the lock at this point. Since server_select() drops the lock
 +       * prior to calling para_select(), we need to acquire it here.
 +       */
 +      mutex_lock(mmd_mutex);
 +      return -E_CHILD_CONTEXT;
  out:
        if (ret < 0)
                PARA_CRIT_LOG("%s\n", para_strerror(-ret));
        return 0;
  }
  
 -              ret = command_task_accept(n, &s->rfds, sct);
+ static int command_post_select(struct sched *s, void *context)
+ {
+       struct server_command_task *sct = context;
+       unsigned n;
+       int ret;
++      ret = task_get_notification(sct->task);
++      if (ret < 0)
++              return ret;
+       for (n = 0; n < sct->num_listen_fds; n++) {
 -static void init_server_command_task(int argc, char **argv)
++              ret = command_task_accept(n, s, sct);
+               if (ret < 0) {
+                       free(sct->listen_fds);
+                       return ret;
+               }
+       }
+       return 0;
+ }
 +static void init_server_command_task(struct server_command_task *sct,
 +              int argc, char **argv)
  {
        int ret;
 -      static struct server_command_task server_command_task_struct,
 -              *sct = &server_command_task_struct;
+       unsigned n;
+       uint32_t port = OPT_UINT32_VAL(PORT);
  
 -
        PARA_NOTICE_LOG("initializing tcp command socket\n");
 +      sct->child_fd = -1;
        sct->argc = argc;
        sct->argv = argv;
-       ret = para_listen_simple(IPPROTO_TCP, OPT_UINT32_VAL(PORT));
-       if (ret < 0)
-               goto err;
-       sct->listen_fd = ret;
-       ret = mark_fd_nonblocking(sct->listen_fd);
-       if (ret < 0)
-               goto err;
-       add_close_on_fork_list(sct->listen_fd); /* child doesn't need the listener */
+       if (!OPT_GIVEN(LISTEN_ADDRESS)) {
+               sct->num_listen_fds = 1;
+               sct->listen_fds = para_malloc(sizeof(int));
+               ret = para_listen_simple(IPPROTO_TCP, port);
+               if (ret < 0)
+                       goto err;
+               sct->listen_fds[0] = ret;
+       } else {
+               sct->num_listen_fds = OPT_GIVEN(LISTEN_ADDRESS);
+               sct->listen_fds = para_malloc(sct->num_listen_fds * sizeof(int));
+               for (n = 0; n < OPT_GIVEN(LISTEN_ADDRESS); n++) {
+                       const char *arg;
+                       arg = lls_string_val(n, OPT_RESULT(LISTEN_ADDRESS));
+                       ret = para_listen(IPPROTO_TCP, arg, port);
+                       if (ret < 0)
+                               goto err;
+                       sct->listen_fds[n] = ret;
+               }
+       }
+       for (n = 0; n < sct->num_listen_fds; n++) {
+               ret = mark_fd_nonblocking(sct->listen_fds[n]);
+               if (ret < 0)
+                       goto err;
+               /* child doesn't need the listener */
+               add_close_on_fork_list(sct->listen_fds[n]);
+       }
        sct->task = task_register(&(struct task_info) {
                .name = "server command",
                .pre_select = command_pre_select,
@@@ -505,8 -497,6 +546,8 @@@ static int init_afs(int argc, char **ar
                int i;
  
                afs_pid = getpid();
 +              crypt_shutdown();
 +              user_list_deplete();
                for (i = argc - 1; i >= 0; i--)
                        memset(argv[i], 0, strlen(argv[i]));
                i = argc - lls_num_inputs(cmdline_lpr) - 1;
@@@ -544,7 -534,7 +585,7 @@@ static void handle_help_flags(void
        exit(EXIT_SUCCESS);
  }
  
 -static void server_init(int argc, char **argv)
 +static void server_init(int argc, char **argv, struct server_command_task *sct)
  {
        int ret, afs_socket, daemon_pipe = -1;
        char *errctx;
        /* become daemon */
        if (OPT_GIVEN(DAEMON))
                daemon_pipe = daemonize(true /* parent waits for SIGTERM */);
 -      init_random_seed_or_die();
 +      server_pid = getpid();
 +      crypt_init();
        daemon_log_welcome("server");
 -      init_ipc_or_die(); /* init mmd struct and mmd->lock */
 +      init_ipc_or_die(); /* init mmd struct, mmd and log mutex */
        daemon_set_start_time();
 +      daemon_set_hooks(pre_log_hook, post_log_hook);
        PARA_NOTICE_LOG("initializing audio format handlers\n");
        afh_init();
  
        para_unblock_signal(SIGCHLD);
        PARA_NOTICE_LOG("initializing virtual streaming system\n");
        vss_init(afs_socket, &sched);
 -      init_server_command_task(argc, argv);
 +      init_server_command_task(sct, argc, argv);
        if (daemon_pipe >= 0) {
                if (write(daemon_pipe, "\0", 1) < 0) {
                        PARA_EMERG_LOG("daemon_pipe: %s", strerror(errno));
@@@ -644,21 -632,6 +685,21 @@@ static int server_select(int max_fileno
        return ret;
  }
  
 +/**
 + * Deallocate all lopsub parse results.
 + *
 + * The server allocates a parse result for command line options and optionally
 + * a second parse result for the effective configuration, defined by merging
 + * the command line options with the options stored in the configuration file.
 + * This function frees both structures.
 + */
 +void free_lpr(void)
 +{
 +      lls_free_parse_result(server_lpr, CMD_PTR);
 +      if (server_lpr != cmdline_lpr)
 +              lls_free_parse_result(cmdline_lpr, CMD_PTR);
 +}
 +
  /**
   * The main function of para_server.
   *
  int main(int argc, char *argv[])
  {
        int ret;
 +      struct server_command_task server_command_task_struct,
 +              *sct = &server_command_task_struct;
  
        sched.default_timeout.tv_sec = 1;
        sched.select_function = server_select;
  
 -      server_init(argc, argv);
 +      server_init(argc, argv, sct);
        mutex_lock(mmd_mutex);
        ret = schedule(&sched);
 +      /*
 +       * We hold the mmd lock: it was re-acquired in server_select()
 +       * after the select call.
 +       */
 +      mutex_unlock(mmd_mutex);
        sched_shutdown(&sched);
 -      lls_free_parse_result(server_lpr, CMD_PTR);
 -      if (server_lpr != cmdline_lpr)
 -              lls_free_parse_result(cmdline_lpr, CMD_PTR);
 -      if (ret < 0)
 -              PARA_EMERG_LOG("%s\n", para_strerror(-ret));
 +      crypt_shutdown();
 +      signal_shutdown(signal_task);
 +      if (!process_is_command_handler()) { /* parent (server) */
 +              mutex_destroy(mmd_mutex);
 +              daemon_set_hooks(NULL, NULL); /* only one process remaining */
 +              mutex_destroy(log_mutex);
 +              deplete_close_on_fork_list();
 +              if (ret < 0)
 +                      PARA_EMERG_LOG("%s\n", para_strerror(-ret));
 +      } else {
 +              alarm(ALARM_TIMEOUT);
 +              close_listed_fds();
 +              ret = handle_connect(sct->child_fd);
 +      }
 +      vss_shutdown();
 +      shm_detach(mmd);
 +      user_list_deplete();
 +      free_lpr();
        exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
  }