From: Andre Noll Date: Mon, 28 May 2018 16:29:06 +0000 (+0200) Subject: Merge branch 'refs/heads/t/listen-address' X-Git-Tag: v0.6.2~3 X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=commitdiff_plain;h=53af5c6efb309565990203fd8504a812ec9166c9;hp=e2167286448ce2ed9a01a548e7e9832563035088 Merge branch 'refs/heads/t/listen-address' 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. --- diff --git a/NEWS.md b/NEWS.md index d193a1a3..4a837ed8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -15,6 +15,9 @@ NEWS 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) diff --git a/dccp_send.c b/dccp_send.c index 770ac601..496895a5 100644 --- a/dccp_send.c +++ b/dccp_send.c @@ -39,8 +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); } /** @@ -225,6 +228,7 @@ static char *dccp_status(void) static void dccp_send_init(void) { 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); diff --git a/http_send.c b/http_send.c index 4d612285..330b45ac 100644 --- a/http_send.c +++ b/http_send.c @@ -198,10 +198,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 */ @@ -248,6 +251,7 @@ static char *http_status(void) static void http_send_init(void) { 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)) diff --git a/m4/lls/server.suite.m4 b/m4/lls/server.suite.m4 index 6ffdc007..be8f02f5 100644 --- a/m4/lls/server.suite.m4 +++ b/m4/lls/server.suite.m4 @@ -34,18 +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 @@ -144,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 @@ -151,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 @@ -202,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 1fece043..ba19408e 100644 --- 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. * @@ -502,17 +532,28 @@ int makesock(unsigned l4type, bool passive, const char *host, uint16_t port_numb * Create a passive / listening socket. * * \param l4type The transport-layer type (\p IPPROTO_xxx). - * \param port The decimal port number to listen on. + * \param addr Passed to \ref parse_url() if not NULL. + * \param port Ignored if addr contains a port number. * * \return Positive integer (socket descriptor) on success, negative value * otherwise. * * \sa \ref makesock(), ip(7), ipv6(7), bind(2), listen(2). */ -int para_listen_simple(unsigned l4type, uint16_t port) +int para_listen(unsigned l4type, const char *addr, uint16_t port) { - int ret, fd = makesock(l4type, 1, NULL, port, NULL); - + char host[MAX_HOSTLEN]; + int ret, fd, addr_port; + + if (addr) { + if (!parse_url(addr, host, sizeof(host), &addr_port)) + return -ERRNO_TO_PARA_ERROR(EINVAL); + if (addr_port > 0) + port = addr_port; + addr = host; + } + fd = makesock(l4type, true /* passive */, addr, port, + NULL /* no flowopts */); if (fd > 0) { ret = listen(fd, BACKLOG); if (ret < 0) { @@ -526,6 +567,22 @@ int para_listen_simple(unsigned l4type, uint16_t port) return fd; } +/** + * Create a socket which listens on all network addresses. + * + * \param l4type See \ref para_listen(). + * \param port See \ref para_listen(). + * + * This is a simple wrapper for \ref para_listen() which passes a NULL pointer + * as the address information. + * + * \return See \ref para_listen(). + */ +int para_listen_simple(unsigned l4type, uint16_t port) +{ + return para_listen(l4type, NULL, port); +} + /** * Determine IPv4/v6 socket address length. * \param sa Container of IPv4 or IPv6 address. diff --git a/net.h b/net.h index 143fb812..2256f376 100644 --- 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. @@ -128,6 +129,7 @@ bool sockaddr_equal(const struct sockaddr *sa1, const struct sockaddr *sa2); /** How many pending connections queue of a listening server will hold. */ #define BACKLOG 10 +int para_listen(unsigned l4type, const char *addr, uint16_t port); int para_listen_simple(unsigned l4type, uint16_t port); /** Pretty-printing of IPv4/6 socket addresses */ diff --git a/send.h b/send.h index 7a4c01bc..67b47e48 100644 --- a/send.h +++ b/send.h @@ -164,10 +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. */ @@ -180,11 +184,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); diff --git a/send_common.c b/send_common.c index 61a12c82..24b14ab8 100644 --- a/send_common.c +++ b/send_common.c @@ -105,20 +105,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); + + 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; - ss->listen_fd = -1; INIT_LIST_HEAD(&ss->client_list); - ss->port = port; - /* Initialize an access control list */ INIT_LIST_HEAD(&ss->acl); for (i = 0; i < lls_opt_given(acl_opt_result); i++) { @@ -146,24 +164,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)", @@ -233,29 +262,39 @@ void generic_com_deny(struct sender_command_data *scd, */ void generic_com_on(struct sender_status *ss, unsigned protocol) { - int fd, ret; - - 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; + int ret; + unsigned n; + + 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; } /** @@ -269,23 +308,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. @@ -310,36 +352,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; } diff --git a/server.c b/server.c index f19fc996..a344b774 100644 --- a/server.c +++ b/server.c @@ -129,8 +129,9 @@ bool process_is_command_handler(void) /** 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. */ @@ -382,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, 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], &s->rfds, NULL, 0, &new_fd); if (ret <= 0) goto out; mmd->num_connects++; @@ -459,23 +460,63 @@ out: return 0; } +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++) { + 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; + 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,