/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "apr_network_io.h" #include "apr_strings.h" #define APR_WANT_STRFUNC #include "apr_want.h" #include "apr_version.h" #include "ap_config.h" #include "httpd.h" #include "http_main.h" #include "http_config.h" #include "http_core.h" #include "ap_listen.h" #include "http_log.h" #include "mpm_common.h" #include #if APR_HAVE_UNISTD_H #include #endif /* we know core's module_index is 0 */ #undef APLOG_MODULE_INDEX #define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX AP_DECLARE_DATA ap_listen_rec *ap_listeners = NULL; /* Let ap_num_listen_buckets be global so that it can * be printed by ap_log_mpm_common(), but keep the listeners * buckets static since it is used only here to close them * all (including duplicated) with ap_close_listeners(). */ AP_DECLARE_DATA int ap_num_listen_buckets; static ap_listen_rec **ap_listen_buckets; /* Determine once, at runtime, whether or not SO_REUSEPORT * is usable on this platform, and hence whether or not * listeners can be duplicated (if configured). */ AP_DECLARE_DATA int ap_have_so_reuseport = -1; /* Whether some accept() errors are non-fatal to the process */ AP_DECLARE_DATA int ap_accept_errors_nonfatal = 0; static ap_listen_rec *old_listeners; static int ap_listenbacklog; static int ap_listencbratio; static int send_buffer_size; static int receive_buffer_size; #ifdef HAVE_SYSTEMD static int use_systemd = -1; #endif /* TODO: make_sock is just begging and screaming for APR abstraction */ static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server, int do_bind_listen) { apr_socket_t *s = server->sd; int one = 1; #if APR_HAVE_IPV6 #ifdef AP_ENABLE_V4_MAPPED int v6only_setting = (server->flags & AP_LISTEN_V6ONLY) ? 1 : 0; #else int v6only_setting = 1; #endif #endif apr_status_t stat; #ifndef WIN32 stat = apr_socket_opt_set(s, APR_SO_REUSEADDR, one); if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) { ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(00067) "make_sock: for address %pI, apr_socket_opt_set: (SO_REUSEADDR)", server->bind_addr); apr_socket_close(s); return stat; } #endif stat = apr_socket_opt_set(s, APR_SO_KEEPALIVE, one); if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) { ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(00068) "make_sock: for address %pI, apr_socket_opt_set: (SO_KEEPALIVE)", server->bind_addr); apr_socket_close(s); return stat; } /* * To send data over high bandwidth-delay connections at full * speed we must force the TCP window to open wide enough to keep the * pipe full. The default window size on many systems * is only 4kB. Cross-country WAN connections of 100ms * at 1Mb/s are not impossible for well connected sites. * If we assume 100ms cross-country latency, * a 4kB buffer limits throughput to 40kB/s. * * To avoid this problem I've added the SendBufferSize directive * to allow the web master to configure send buffer size. * * The trade-off of larger buffers is that more kernel memory * is consumed. YMMV, know your customers and your network! * * -John Heidemann 25-Oct-96 * * If no size is specified, use the kernel default. */ if (send_buffer_size) { stat = apr_socket_opt_set(s, APR_SO_SNDBUF, send_buffer_size); if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) { ap_log_perror(APLOG_MARK, APLOG_WARNING, stat, p, APLOGNO(00070) "make_sock: failed to set SendBufferSize for " "address %pI, using default", server->bind_addr); /* not a fatal error */ } } if (receive_buffer_size) { stat = apr_socket_opt_set(s, APR_SO_RCVBUF, receive_buffer_size); if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) { ap_log_perror(APLOG_MARK, APLOG_WARNING, stat, p, APLOGNO(00071) "make_sock: failed to set ReceiveBufferSize for " "address %pI, using default", server->bind_addr); /* not a fatal error */ } } #if APR_TCP_NODELAY_INHERITED ap_sock_disable_nagle(s); #endif #if defined(SO_REUSEPORT) if (server->flags & AP_LISTEN_REUSEPORT || (ap_have_so_reuseport && ap_listencbratio > 0)) { int thesock; apr_os_sock_get(&thesock, s); if (setsockopt(thesock, SOL_SOCKET, SO_REUSEPORT, (void *)&one, sizeof(int)) < 0) { stat = apr_get_netos_error(); ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(02638) "make_sock: for address %pI, apr_socket_opt_set: " "(SO_REUSEPORT)", server->bind_addr); apr_socket_close(s); return stat; } } #endif #if defined(APR_SO_FREEBIND) if (server->flags & AP_LISTEN_FREEBIND) { if (apr_socket_opt_set(s, APR_SO_FREEBIND, one) < 0) { stat = apr_get_netos_error(); ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(10236) "make_sock: apr_socket_opt_set: " "error setting APR_SO_FREEBIND"); apr_socket_close(s); return stat; } } #endif if (do_bind_listen) { #if APR_HAVE_IPV6 if (server->bind_addr->family == APR_INET6) { stat = apr_socket_opt_set(s, APR_IPV6_V6ONLY, v6only_setting); if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) { ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(00069) "make_sock: for address %pI, apr_socket_opt_set: " "(IPV6_V6ONLY)", server->bind_addr); apr_socket_close(s); return stat; } } #endif if ((stat = apr_socket_bind(s, server->bind_addr)) != APR_SUCCESS) { ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, stat, p, APLOGNO(00072) "make_sock: could not bind to address %pI", server->bind_addr); apr_socket_close(s); return stat; } if ((stat = apr_socket_listen(s, ap_listenbacklog)) != APR_SUCCESS) { ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, stat, p, APLOGNO(00073) "make_sock: unable to listen for connections " "on address %pI", server->bind_addr); apr_socket_close(s); return stat; } } #ifdef WIN32 /* I seriously doubt that this would work on Unix; I have doubts that * it entirely solves the problem on Win32. However, since setting * reuseaddr on the listener -prior- to binding the socket has allowed * us to attach to the same port as an already running instance of * Apache, or even another web server, we cannot identify that this * port was exclusively granted to this instance of Apache. * * So set reuseaddr, but do not attempt to do so until we have the * parent listeners successfully bound. */ stat = apr_socket_opt_set(s, APR_SO_REUSEADDR, one); if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) { ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(00074) "make_sock: for address %pI, apr_socket_opt_set: (SO_REUSEADDR)", server->bind_addr); apr_socket_close(s); return stat; } #endif server->sd = s; server->active = 1; server->accept_func = NULL; return APR_SUCCESS; } static const char* find_accf_name(server_rec *s, const char *proto) { const char* accf; core_server_config *conf = ap_get_core_module_config(s->module_config); if (!proto) { return NULL; } accf = apr_table_get(conf->accf_map, proto); if (accf && !strcmp("none", accf)) { return NULL; } return accf; } static void ap_apply_accept_filter(apr_pool_t *p, ap_listen_rec *lis, server_rec *server) { apr_socket_t *s = lis->sd; const char *accf; apr_status_t rv; const char *proto; proto = lis->protocol; if (!proto) { proto = ap_get_server_protocol(server); } accf = find_accf_name(server, proto); if (accf) { #if APR_HAS_SO_ACCEPTFILTER /* In APR 1.x, the 2nd and 3rd parameters are char * instead of * const char *, so make a copy of those args here. */ rv = apr_socket_accept_filter(s, apr_pstrdup(p, accf), apr_pstrdup(p, "")); if (rv != APR_SUCCESS && !APR_STATUS_IS_ENOTIMPL(rv)) { ap_log_perror(APLOG_MARK, APLOG_WARNING, rv, p, APLOGNO(00075) "Failed to enable the '%s' Accept Filter", accf); } #else rv = apr_socket_opt_set(s, APR_TCP_DEFER_ACCEPT, 30); if (rv != APR_SUCCESS && !APR_STATUS_IS_ENOTIMPL(rv)) { ap_log_perror(APLOG_MARK, APLOG_WARNING, rv, p, APLOGNO(00076) "Failed to enable APR_TCP_DEFER_ACCEPT"); } #endif } } static apr_status_t close_listeners_on_exec(void *v) { ap_close_listeners(); return APR_SUCCESS; } #ifdef HAVE_SYSTEMD static apr_status_t alloc_systemd_listener(process_rec * process, int fd, const char *proto, ap_listen_rec **out_rec) { apr_status_t rv; struct sockaddr sa; socklen_t len = sizeof(struct sockaddr); apr_os_sock_info_t si; ap_listen_rec *rec; *out_rec = NULL; memset(&si, 0, sizeof(si)); rv = getsockname(fd, &sa, &len); if (rv != 0) { rv = apr_get_netos_error(); ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, process->pool, APLOGNO(02489) "getsockname on %d failed.", fd); return rv; } si.os_sock = &fd; si.family = sa.sa_family; si.local = &sa; si.type = SOCK_STREAM; si.protocol = APR_PROTO_TCP; rec = apr_palloc(process->pool, sizeof(ap_listen_rec)); rec->active = 0; rec->next = 0; rv = apr_os_sock_make(&rec->sd, &si, process->pool); if (rv != APR_SUCCESS) { ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, process->pool, APLOGNO(02490) "apr_os_sock_make on %d failed.", fd); return rv; } rv = apr_socket_addr_get(&rec->bind_addr, APR_LOCAL, rec->sd); if (rv != APR_SUCCESS) { ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, process->pool, APLOGNO(02491) "apr_socket_addr_get on %d failed.", fd); return rv; } rec->protocol = apr_pstrdup(process->pool, proto); *out_rec = rec; return make_sock(process->pool, rec, 0); } static const char *set_systemd_listener(process_rec *process, apr_port_t port, const char *proto) { ap_listen_rec *last, *new; apr_status_t rv; APR_OPTIONAL_FN_TYPE(ap_find_systemd_socket) *find_systemd_socket; int fd; find_systemd_socket = APR_RETRIEVE_OPTIONAL_FN(ap_find_systemd_socket); if (!find_systemd_socket) return "Systemd socket activation is used, but mod_systemd is probably " "not loaded"; fd = find_systemd_socket(process, port); if (fd < 0) { return "Systemd socket activation is used, but this port is not " "configured in systemd"; } last = ap_listeners; while (last && last->next) { last = last->next; } rv = alloc_systemd_listener(process, fd, proto, &new); if (rv != APR_SUCCESS) { return "Failed to setup socket passed by systemd using socket activation"; } if (last == NULL) { ap_listeners = new; } else { last->next = new; } return NULL; } #endif /* HAVE_SYSTEMD */ /* Returns non-zero if socket address SA matches hostname, port and * scope_id. p is used for temporary allocations. */ static int match_address(const apr_sockaddr_t *sa, const char *hostname, apr_port_t port, const char *scope_id, apr_pool_t *p) { const char *old_scope = NULL; #if APR_VERSION_AT_LEAST(1,7,0) /* To be clever here we could correctly match numeric and * non-numeric zone ids. Ignore failure, old_scope will be left * as NULL. */ (void) apr_sockaddr_zone_get(sa, &old_scope, NULL, p); #endif return port == sa->port && ((!hostname && !sa->hostname) || (hostname && sa->hostname && !strcmp(sa->hostname, hostname))) && ((!scope_id && !old_scope) || (scope_id && old_scope && !strcmp(scope_id, old_scope))); } /* ### This logic doesn't cope with DNS changes across a restart. */ static int find_listeners(ap_listen_rec **from, ap_listen_rec **to, const char *addr, apr_port_t port, const char *scope_id, apr_pool_t *temp_pool) { int found = 0; while (*from) { apr_sockaddr_t *sa = (*from)->bind_addr; /* Some listeners are not real so they will not have a bind_addr. */ if (sa) { ap_listen_rec *new; /* Re-use the existing record if it matches completely * against an existing listener. */ if (match_address(sa, addr, port, scope_id, temp_pool)) { found = 1; if (!to) { break; } new = *from; *from = new->next; new->next = *to; *to = new; continue; } } from = &(*from)->next; } return found; } static const char *alloc_listener(process_rec *process, const char *addr, apr_port_t port, const char* proto, const char *scope_id, void *slave, apr_pool_t *temp_pool, apr_uint32_t flags) { ap_listen_rec *last; apr_status_t status; apr_sockaddr_t *sa; /* see if we've got a listener for this address:port, which is an error */ if (find_listeners(&ap_listeners, NULL, addr, port, scope_id, temp_pool)) { return "Cannot define multiple Listeners on the same IP:port"; } /* see if we've got an old listener for this address:port */ if (find_listeners(&old_listeners, &ap_listeners, addr, port, scope_id, temp_pool)) { if (ap_listeners->slave != slave) { return "Cannot define a slave on the same IP:port as a Listener"; } return NULL; } if ((status = apr_sockaddr_info_get(&sa, addr, APR_UNSPEC, port, 0, process->pool)) != APR_SUCCESS) { ap_log_perror(APLOG_MARK, APLOG_CRIT, status, process->pool, APLOGNO(00077) "alloc_listener: failed to set up sockaddr for %s", addr); return "Listen setup failed"; } /* Initialize to our last configured ap_listener. */ last = ap_listeners; while (last && last->next) { last = last->next; } while (sa) { ap_listen_rec *new; /* this has to survive restarts */ new = apr_palloc(process->pool, sizeof(ap_listen_rec)); new->active = 0; new->next = 0; new->bind_addr = sa; new->protocol = apr_pstrdup(process->pool, proto); new->flags = flags; /* Go to the next sockaddr. */ sa = sa->next; status = apr_socket_create(&new->sd, new->bind_addr->family, SOCK_STREAM, 0, process->pool); #if APR_HAVE_IPV6 /* What could happen is that we got an IPv6 address, but this system * doesn't actually support IPv6. Try the next address. */ if (status != APR_SUCCESS && !addr && new->bind_addr->family == APR_INET6) { continue; } #endif if (status != APR_SUCCESS) { ap_log_perror(APLOG_MARK, APLOG_CRIT, status, process->pool, APLOGNO(00078) "alloc_listener: failed to get a socket for %s", addr); return "Listen setup failed"; } #if APR_VERSION_AT_LEAST(1,7,0) if (scope_id) { status = apr_sockaddr_zone_set(new->bind_addr, scope_id); if (status) { ap_log_perror(APLOG_MARK, APLOG_CRIT, status, process->pool, APLOGNO(10102) "alloc_listener: failed to set scope for %pI to %s", new->bind_addr, scope_id); return "Listen step failed"; } } #endif /* We need to preserve the order returned by getaddrinfo() */ if (last == NULL) { ap_listeners = last = new; } else { last->next = new; last = new; } new->slave = slave; } return NULL; } /* Evaluates to true if the (apr_sockaddr_t *) addr argument is the * IPv4 match-any-address, 0.0.0.0. */ #define IS_INADDR_ANY(addr) ((addr)->family == APR_INET \ && (addr)->sa.sin.sin_addr.s_addr == INADDR_ANY) /* Evaluates to true if the (apr_sockaddr_t *) addr argument is the * IPv6 match-any-address, [::]. */ #define IS_IN6ADDR_ANY(addr) ((addr)->family == APR_INET6 \ && IN6_IS_ADDR_UNSPECIFIED(&(addr)->sa.sin6.sin6_addr)) /** * Create, open, listen, and bind all sockets. * @param process The process record for the currently running server * @return The number of open sockets */ static int open_listeners(apr_pool_t *pool) { ap_listen_rec *lr; ap_listen_rec *next; ap_listen_rec *previous; int num_open; const char *userdata_key = "ap_open_listeners"; void *data; #if AP_NONBLOCK_WHEN_MULTI_LISTEN int use_nonblock; #endif /* Don't allocate a default listener. If we need to listen to a * port, then the user needs to have a Listen directive in their * config file. */ num_open = 0; previous = NULL; for (lr = ap_listeners; lr; previous = lr, lr = lr->next) { if (lr->active) { ++num_open; } else { #if APR_HAVE_IPV6 ap_listen_rec *cur; int v6only_setting; int skip = 0; /* If we have the unspecified IPv4 address (0.0.0.0) and * the unspecified IPv6 address (::) is next, we need to * swap the order of these in the list. We always try to * bind to IPv6 first, then IPv4, since an IPv6 socket * might be able to receive IPv4 packets if V6ONLY is not * enabled, but never the other way around. * Note: In some configurations, the unspecified IPv6 address * could be even later in the list. This logic only corrects * the situation where it is next in the list, such as when * apr_sockaddr_info_get() returns an IPv4 and an IPv6 address, * in that order. */ if (lr->next != NULL && IS_INADDR_ANY(lr->bind_addr) && lr->bind_addr->port == lr->next->bind_addr->port && IS_IN6ADDR_ANY(lr->next->bind_addr)) { /* Exchange lr and lr->next */ next = lr->next; lr->next = next->next; next->next = lr; if (previous) { previous->next = next; } else { ap_listeners = next; } lr = next; } /* If we are trying to bind to 0.0.0.0 and a previous listener * was :: on the same port and in turn that socket does not have * the IPV6_V6ONLY flag set; we must skip the current attempt to * listen (which would generate an error). IPv4 will be handled * on the established IPv6 socket. */ if (IS_INADDR_ANY(lr->bind_addr) && previous) { for (cur = ap_listeners; cur != lr; cur = cur->next) { if (lr->bind_addr->port == cur->bind_addr->port && IS_IN6ADDR_ANY(cur->bind_addr) && apr_socket_opt_get(cur->sd, APR_IPV6_V6ONLY, &v6only_setting) == APR_SUCCESS && v6only_setting == 0) { /* Remove the current listener from the list */ previous->next = lr->next; lr = previous; /* maintain current value of previous after * post-loop expression is evaluated */ skip = 1; break; } } if (skip) { continue; } } #endif if (make_sock(pool, lr, 1) == APR_SUCCESS) { ++num_open; } else { #if APR_HAVE_IPV6 /* If we tried to bind to ::, and the next listener is * on 0.0.0.0 with the same port, don't give a fatal * error. The user will still get a warning from make_sock * though. */ if (lr->next != NULL && IS_IN6ADDR_ANY(lr->bind_addr) && lr->bind_addr->port == lr->next->bind_addr->port && IS_INADDR_ANY(lr->next->bind_addr)) { /* Remove the current listener from the list */ if (previous) { previous->next = lr->next; } else { ap_listeners = lr->next; } /* Although we've removed ourselves from the list, * we need to make sure that the next iteration won't * consider "previous" a working IPv6 '::' socket. * Changing the family is enough to make sure the * conditions before make_sock() fail. */ lr->bind_addr->family = AF_INET; continue; } #endif /* fatal error */ return -1; } } } /* close the old listeners */ ap_close_listeners_ex(old_listeners); old_listeners = NULL; #if AP_NONBLOCK_WHEN_MULTI_LISTEN /* if multiple listening sockets, make them non-blocking so that * if select()/poll() reports readability for a reset connection that * is already forgotten about by the time we call accept, we won't * be hung until another connection arrives on that port */ use_nonblock = (ap_listeners && ap_listeners->next); for (lr = ap_listeners; lr; lr = lr->next) { apr_status_t status; status = apr_socket_opt_set(lr->sd, APR_SO_NONBLOCK, use_nonblock); if (status != APR_SUCCESS) { ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, status, pool, APLOGNO(00079) "unable to control socket non-blocking status"); return -1; } } #endif /* AP_NONBLOCK_WHEN_MULTI_LISTEN */ /* we come through here on both passes of the open logs phase * only register the cleanup once... otherwise we try to close * listening sockets twice when cleaning up prior to exec */ apr_pool_userdata_get(&data, userdata_key, pool); if (!data) { apr_pool_userdata_set((const void *)1, userdata_key, apr_pool_cleanup_null, pool); apr_pool_cleanup_register(pool, NULL, apr_pool_cleanup_null, close_listeners_on_exec); } return num_open ? 0 : -1; } AP_DECLARE(int) ap_setup_listeners(server_rec *s) { server_rec *ls; server_addr_rec *addr; ap_listen_rec *lr; int num_listeners = 0; const char* proto; int found; #ifdef HAVE_SYSTEMD APR_OPTIONAL_FN_TYPE(ap_systemd_listen_fds) *systemd_listen_fds; #endif for (ls = s; ls; ls = ls->next) { proto = ap_get_server_protocol(ls); if (!proto) { found = 0; /* No protocol was set for this vhost, * use the default for this listener. */ for (addr = ls->addrs; addr && !found; addr = addr->next) { for (lr = ap_listeners; lr; lr = lr->next) { if (apr_sockaddr_equal(lr->bind_addr, addr->host_addr) && lr->bind_addr->port == addr->host_port) { ap_set_server_protocol(ls, lr->protocol); found = 1; break; } } } if (!found) { /* TODO: set protocol defaults per-Port, eg 25=smtp */ ap_set_server_protocol(ls, "http"); } } } #ifdef HAVE_SYSTEMD if (use_systemd) { const char *userdata_key = "ap_open_systemd_listeners"; void *data; /* clear the environment on our second run * so that none of our future children get confused. */ apr_pool_userdata_get(&data, userdata_key, s->process->pool); if (!data) { apr_pool_userdata_set((const void *)1, userdata_key, apr_pool_cleanup_null, s->process->pool); } else { systemd_listen_fds = APR_RETRIEVE_OPTIONAL_FN(ap_systemd_listen_fds); if (systemd_listen_fds != NULL) { systemd_listen_fds(1); } } } else #endif { if (open_listeners(s->process->pool)) { return 0; } } for (lr = ap_listeners; lr; lr = lr->next) { if (ap_accept_errors_nonfatal) lr->flags |= AP_LISTEN_SPECIFIC_ERRORS; num_listeners++; found = 0; for (ls = s; ls && !found; ls = ls->next) { for (addr = ls->addrs; addr && !found; addr = addr->next) { if (apr_sockaddr_equal(lr->bind_addr, addr->host_addr) && lr->bind_addr->port == addr->host_port) { found = 1; ap_apply_accept_filter(s->process->pool, lr, ls); } } } if (!found) { ap_apply_accept_filter(s->process->pool, lr, s); } } return num_listeners; } AP_DECLARE(apr_status_t) ap_duplicate_listeners(apr_pool_t *p, server_rec *s, ap_listen_rec ***buckets, int *num_buckets) { static int warn_once; int i; apr_status_t stat; int use_nonblock = 0; ap_listen_rec *lr; if (*num_buckets < 1) { *num_buckets = 1; if (ap_listencbratio > 0) { #ifdef _SC_NPROCESSORS_ONLN if (ap_have_so_reuseport) { int num_online_cores = sysconf(_SC_NPROCESSORS_ONLN), val = num_online_cores / ap_listencbratio; if (val > 1) { *num_buckets = val; } ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(02819) "Using %i listeners bucket(s) based on %i " "online CPU cores and a ratio of %i", *num_buckets, num_online_cores, ap_listencbratio); } else #endif if (!warn_once) { ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(02820) "ListenCoresBucketsRatio ignored without " "SO_REUSEPORT and _SC_NPROCESSORS_ONLN " "support: using a single listeners bucket"); warn_once = 1; } } } *buckets = apr_pcalloc(p, *num_buckets * sizeof(ap_listen_rec *)); (*buckets)[0] = ap_listeners; for (i = 1; i < *num_buckets; i++) { ap_listen_rec *last = NULL; lr = ap_listeners; while (lr) { ap_listen_rec *duplr; char *hostname; apr_port_t port; apr_sockaddr_t *sa; #ifdef HAVE_SYSTEMD if (use_systemd) { int thesock; apr_os_sock_get(&thesock, lr->sd); if ((stat = alloc_systemd_listener(s->process, thesock, lr->protocol, &duplr)) != APR_SUCCESS) { return stat; } } else #endif { duplr = apr_palloc(p, sizeof(ap_listen_rec)); duplr->slave = NULL; duplr->protocol = apr_pstrdup(p, lr->protocol); hostname = apr_pstrdup(p, lr->bind_addr->hostname); port = lr->bind_addr->port; stat = apr_sockaddr_info_get(&sa, hostname, APR_UNSPEC, port, 0, p); if (stat != APR_SUCCESS) { ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(10397) "failure looking up %s to duplicate " "listening socket", hostname); return stat; } duplr->bind_addr = sa; duplr->next = NULL; duplr->flags = lr->flags; stat = apr_socket_create(&duplr->sd, duplr->bind_addr->family, SOCK_STREAM, 0, p); if (stat != APR_SUCCESS) { ap_log_perror(APLOG_MARK, APLOG_CRIT, 0, p, APLOGNO(02640) "ap_duplicate_listeners: for address %pI, " "cannot duplicate a new socket!", duplr->bind_addr); return stat; } make_sock(p, duplr, 1); } #if AP_NONBLOCK_WHEN_MULTI_LISTEN use_nonblock = (ap_listeners && ap_listeners->next); stat = apr_socket_opt_set(duplr->sd, APR_SO_NONBLOCK, use_nonblock); if (stat != APR_SUCCESS) { ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(02641) "unable to control socket non-blocking status"); return stat; } #endif ap_apply_accept_filter(p, duplr, s); if (last == NULL) { (*buckets)[i] = last = duplr; } else { last->next = duplr; last = duplr; } lr = lr->next; } } ap_listen_buckets = *buckets; ap_num_listen_buckets = *num_buckets; return APR_SUCCESS; } AP_DECLARE_NONSTD(void) ap_close_listeners(void) { int i; ap_close_listeners_ex(ap_listeners); /* Start from index 1 since either ap_duplicate_listeners() * was called and ap_listen_buckets[0] == ap_listeners, or * it wasn't and ap_num_listen_buckets == 0. */ for (i = 1; i < ap_num_listen_buckets; i++) { ap_close_listeners_ex(ap_listen_buckets[i]); } } AP_DECLARE_NONSTD(void) ap_close_listeners_ex(ap_listen_rec *listeners) { ap_listen_rec *lr; for (lr = listeners; lr; lr = lr->next) { apr_socket_close(lr->sd); lr->active = 0; } } AP_DECLARE_NONSTD(int) ap_close_selected_listeners(ap_slave_t *slave) { ap_listen_rec *lr; int n = 0; for (lr = ap_listeners; lr; lr = lr->next) { if (lr->slave != slave) { apr_socket_close(lr->sd); lr->active = 0; } else { ++n; } } return n; } AP_DECLARE(void) ap_listen_pre_config(void) { old_listeners = ap_listeners; ap_listeners = NULL; ap_listen_buckets = NULL; ap_num_listen_buckets = 0; ap_listenbacklog = DEFAULT_LISTENBACKLOG; ap_listencbratio = 0; /* Check once whether or not SO_REUSEPORT is supported. */ if (ap_have_so_reuseport < 0) { /* This is limited to Linux with defined SO_REUSEPORT (ie. 3.9+) for * now since the implementation evenly distributes connections across * all the listening threads/processes. * * *BSDs have SO_REUSEPORT too but with a different semantic: the first * wildcard address bound socket or the last non-wildcard address bound * socket will receive connections (no evenness guarantee); the rest of * the sockets bound to the same port will not. * This can't (always) work for httpd. * * TODO: latests DragonFlyBSD's SO_REUSEPORT (seems to?) have the same * semantic as Linux, so we may need HAVE_SO_REUSEPORT available from * configure.in some day. */ #if defined(SO_REUSEPORT) && defined(__linux__) apr_socket_t *sock; if (apr_socket_create(&sock, APR_UNSPEC, SOCK_STREAM, 0, ap_pglobal) == APR_SUCCESS) { int thesock, on = 1; apr_os_sock_get(&thesock, sock); ap_have_so_reuseport = (setsockopt(thesock, SOL_SOCKET, SO_REUSEPORT, (void *)&on, sizeof(int)) == 0); apr_socket_close(sock); } else #endif ap_have_so_reuseport = 0; } } AP_DECLARE(int) ap_accept_error_is_nonfatal(apr_status_t status) { return APR_STATUS_IS_ECONNREFUSED(status) || APR_STATUS_IS_ECONNABORTED(status) || APR_STATUS_IS_ECONNRESET(status); } /* Parse optional flags argument for Listen. Currently just boolean * flags handled; would need to be extended to incorporate * ListenBacklog */ static const char *parse_listen_flags(apr_pool_t *temp_pool, const char *arg, apr_uint32_t *flags_out) { apr_uint32_t flags = 0; char *str = apr_pstrdup(temp_pool, arg), *token, *state = NULL; token = apr_strtok(str, ",", &state); while (token) { if (ap_cstr_casecmp(token, "freebind") == 0) flags |= AP_LISTEN_FREEBIND; else if (ap_cstr_casecmp(token, "reuseport") == 0) flags |= AP_LISTEN_REUSEPORT; else if (ap_cstr_casecmp(token, "v6only") == 0) flags |= AP_LISTEN_V6ONLY; else return apr_psprintf(temp_pool, "Unknown Listen option '%s' in '%s'", token, arg); token = apr_strtok(NULL, ",", &state); } *flags_out = flags; return NULL; } AP_DECLARE_NONSTD(const char *) ap_set_listener(cmd_parms *cmd, void *dummy, int argc, char *const argv[]) { char *host, *scope_id, *proto = NULL; apr_port_t port; apr_status_t rv; const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); apr_uint32_t flags = 0; #ifdef HAVE_SYSTEMD APR_OPTIONAL_FN_TYPE(ap_systemd_listen_fds) *systemd_listen_fds; #endif if (err != NULL) { return err; } if (argc < 1 || argc > 3) { return "Listen requires 1-3 arguments."; } #ifdef HAVE_SYSTEMD if (use_systemd == -1) { systemd_listen_fds = APR_RETRIEVE_OPTIONAL_FN(ap_systemd_listen_fds); if (systemd_listen_fds != NULL) { use_systemd = systemd_listen_fds(0) > 0; } else { use_systemd = 0; } } #endif rv = apr_parse_addr_port(&host, &scope_id, &port, argv[0], cmd->pool); if (rv != APR_SUCCESS) { return "Invalid address or port"; } if (host && !strcmp(host, "*")) { host = NULL; } #if !APR_VERSION_AT_LEAST(1,7,0) if (scope_id) { return apr_pstrcat(cmd->pool, "Scope ID in address '", argv[0], "' not supported with APR " APR_VERSION_STRING, NULL); } #endif if (!port) { return "Port must be specified"; } if (argc == 3) { if (strncasecmp(argv[2], "options=", 8)) { return "Third argument to Listen must be options=..."; } err = parse_listen_flags(cmd->temp_pool, argv[2] + 8, &flags); if (err) { return err; } proto = argv[1]; } if (argc == 2) { /* 2-arg form is either 'Listen host:port options=...' or * 'Listen host:port protocol' */ if (strncasecmp(argv[1], "options=", 8) == 0) { err = parse_listen_flags(cmd->temp_pool, argv[1] + 8, &flags); if (err) { return err; } } else { proto = argv[1]; } } /* Catch case where 2-arg form has typoed options=X and doesn't * match above. */ if (proto && ap_strchr_c(proto, '=') != NULL) { return apr_psprintf(cmd->pool, "Invalid protocol name '%s'", proto); } else if (proto) { proto = apr_pstrdup(cmd->pool, proto); ap_str_tolower(proto); } else { if (port == 443) { proto = "https"; } else { proto = "http"; } } #ifdef HAVE_SYSTEMD if (use_systemd) { return set_systemd_listener(cmd->server->process, port, proto); } #endif return alloc_listener(cmd->server->process, host, port, proto, scope_id, NULL, cmd->temp_pool, flags); } AP_DECLARE_NONSTD(const char *) ap_set_listenbacklog(cmd_parms *cmd, void *dummy, const char *arg) { int b; const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err != NULL) { return err; } b = atoi(arg); if (b < 1) { return "ListenBacklog must be > 0"; } ap_listenbacklog = b; return NULL; } AP_DECLARE_NONSTD(const char *) ap_set_listencbratio(cmd_parms *cmd, void *dummy, const char *arg) { int b; const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err != NULL) { return err; } b = atoi(arg); if (b < 1) { return "ListenCoresBucketsRatio must be > 0"; } ap_listencbratio = b; return NULL; } AP_DECLARE_NONSTD(const char *) ap_set_send_buffer_size(cmd_parms *cmd, void *dummy, const char *arg) { int s = atoi(arg); const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err != NULL) { return err; } if (s < 512 && s != 0) { return "SendBufferSize must be >= 512 bytes, or 0 for system default."; } send_buffer_size = s; return NULL; } AP_DECLARE_NONSTD(const char *) ap_set_accept_errors_nonfatal(cmd_parms *cmd, void *dummy, int flag) { const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err != NULL) { return err; } ap_accept_errors_nonfatal = flag; return NULL; } AP_DECLARE_NONSTD(const char *) ap_set_receive_buffer_size(cmd_parms *cmd, void *dummy, const char *arg) { int s = atoi(arg); const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err != NULL) { return err; } if (s < 512 && s != 0) { return "ReceiveBufferSize must be >= 512 bytes, or 0 for system default."; } receive_buffer_size = s; return NULL; }