diff --git a/docs/manual/mod/mpm_common.xml b/docs/manual/mod/mpm_common.xml index cab75eed306..11a19485253 100644 --- a/docs/manual/mod/mpm_common.xml +++ b/docs/manual/mod/mpm_common.xml @@ -183,13 +183,14 @@ of the daemon Listen IP addresses and ports that the server listens to -Listen [IP-address:]portnumber [protocol] +Listen [IP-address:]portnumber [protocol] [options=flag[,flag..]] server config eventworker preforkmpm_winnt mpm_netwarempmt_os2 The protocol argument was added in 2.1.5 +The optional options= argument is available in httpd 2.4.66 and later.

The Listen directive instructs Apache httpd to @@ -247,8 +248,25 @@ Listen 192.170.2.5:8000 Listen 192.170.2.1:8443 https +

The optional options=flag,flag... argument can be + used to enable certain socket options for the listening port. + These options are not required for most configurations and should + be used with care. Availability of each flag varies across + operating systems. The available flags are:

+ + + Error condition - Multiple Listen directives for the same ip + Multiple Listen directives for the same IP address and port will result in an Address already in use error message. @@ -889,4 +907,35 @@ client connections
+ +AcceptErrorsNonFatal +Treat some errors accepting a new connection as non-fatal +to the httpd process. +AcceptErrorsNonFatal ON +OFF (ECONNREFUSED, ECONNABORTED, ECONNRESET cause the process to + exit) +server config +eventworker +prefork + +2.4.66 and later + + +

The AcceptErrorsNonFatal alters the servers + behavior under some rare errors that may occur while accepting a new + client connection. By default, the child process handling a request + will gracefully exit when nearly any socket error occurs during the + accept() system call. This is to ensure a potentially unhealthy + child process does not try to take on more new connections.

+ +

With AcceptErrorsNonFatal set to "ON", + the process will not begin to exit if the accept() error is + ECONNREFUSED, ECONNABORTED, or ECONNRESET.

+ + Some third-party firwewall software components may inject errors + into accept() processing, using return codes not specified by the + operating system +
+
+ diff --git a/include/ap_listen.h b/include/ap_listen.h index d5ed9685c54..4603085822f 100644 --- a/include/ap_listen.h +++ b/include/ap_listen.h @@ -39,6 +39,11 @@ typedef struct ap_slave_t ap_slave_t; typedef struct ap_listen_rec ap_listen_rec; typedef apr_status_t (*accept_function)(void **csd, ap_listen_rec *lr, apr_pool_t *ptrans); +/* Flags for ap_listen_rec.flags */ +#define AP_LISTEN_SPECIFIC_ERRORS (0x0001) +#define AP_LISTEN_FREEBIND (0x0002) +#define AP_LISTEN_REUSEPORT (0x0004) + /** * @brief Apache's listeners record. * @@ -72,6 +77,11 @@ struct ap_listen_rec { const char* protocol; ap_slave_t *slave; + + /** + * Various AP_LISTEN_* flags. + */ + apr_uint32_t flags; }; /** @@ -80,6 +90,9 @@ struct ap_listen_rec { AP_DECLARE_DATA extern ap_listen_rec *ap_listeners; AP_DECLARE_DATA extern int ap_num_listen_buckets; AP_DECLARE_DATA extern int ap_have_so_reuseport; +AP_DECLARE_DATA extern int ap_accept_errors_nonfatal; + +AP_DECLARE(int) ap_accept_error_is_nonfatal(apr_status_t rv); /** * Setup all of the defaults for the listener list @@ -153,6 +166,10 @@ APR_DECLARE_OPTIONAL_FN(int, #endif +AP_DECLARE_NONSTD(const char *) ap_set_accept_errors_nonfatal(cmd_parms *cmd, + void *dummy, + int flag); + #define LISTEN_COMMANDS \ AP_INIT_TAKE1("ListenBacklog", ap_set_listenbacklog, NULL, RSRC_CONF, \ "Maximum length of the queue of pending connections, as used by listen(2)"), \ @@ -163,8 +180,9 @@ AP_INIT_TAKE_ARGV("Listen", ap_set_listener, NULL, RSRC_CONF, \ AP_INIT_TAKE1("SendBufferSize", ap_set_send_buffer_size, NULL, RSRC_CONF, \ "Send buffer size in bytes"), \ AP_INIT_TAKE1("ReceiveBufferSize", ap_set_receive_buffer_size, NULL, \ - RSRC_CONF, "Receive buffer size in bytes") - + RSRC_CONF, "Receive buffer size in bytes"), \ +AP_INIT_FLAG("AcceptErrorsNonFatal", ap_set_accept_errors_nonfatal, NULL, \ + RSRC_CONF, "Some accept() errors are not fatal to the process") #ifdef __cplusplus } #endif diff --git a/include/ap_mmn.h b/include/ap_mmn.h index 8164fe06511..c687e464c21 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -618,7 +618,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20120211 #endif -#define MODULE_MAGIC_NUMBER_MINOR 141 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 142 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a diff --git a/os/unix/unixd.c b/os/unix/unixd.c index a304f2d4feb..efd454ba0c6 100644 --- a/os/unix/unixd.c +++ b/os/unix/unixd.c @@ -321,6 +321,12 @@ AP_DECLARE(apr_status_t) ap_unixd_accept(void **accepted, ap_listen_rec *lr, if (APR_STATUS_IS_EINTR(status)) { return status; } + + /* Let the caller handle slightly more varied return values */ + if ((lr->flags & AP_LISTEN_SPECIFIC_ERRORS) && ap_accept_error_is_nonfatal(status)) { + return status; + } + /* Our old behaviour here was to continue after accept() * errors. But this leads us into lots of troubles * because most of the errors are quite fatal. For diff --git a/server/listen.c b/server/listen.c index cb7e59eb34c..e5a2325e512 100644 --- a/server/listen.c +++ b/server/listen.c @@ -55,6 +55,9 @@ static ap_listen_rec **ap_listen_buckets; */ 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; @@ -143,7 +146,8 @@ static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server, int do_bind_ #endif #if defined(SO_REUSEPORT) - if (ap_have_so_reuseport && ap_listencbratio > 0) { + 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, @@ -159,6 +163,21 @@ static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server, int do_bind_ } #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) { @@ -438,7 +457,7 @@ static int find_listeners(ap_listen_rec **from, ap_listen_rec **to, 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_pool_t *temp_pool, apr_uint32_t flags) { ap_listen_rec *last; apr_status_t status; @@ -482,6 +501,7 @@ static const char *alloc_listener(process_rec *process, const char *addr, 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; @@ -773,6 +793,7 @@ AP_DECLARE(int) ap_setup_listeners(server_rec *s) } 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) { @@ -862,8 +883,10 @@ AP_DECLARE(apr_status_t) ap_duplicate_listeners(apr_pool_t *p, server_rec *s, apr_sockaddr_info_get(&sa, hostname, APR_UNSPEC, port, 0, p); duplr->bind_addr = sa; duplr->next = NULL; - if ((stat = apr_socket_create(&duplr->sd, duplr->bind_addr->family, - SOCK_STREAM, 0, p)) != APR_SUCCESS) { + 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_socket: for address %pI, " "cannot duplicate a new socket!", @@ -983,23 +1006,61 @@ AP_DECLARE(void) ap_listen_pre_config(void) } } + +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 + 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; + char *host, *scope_id, *proto = NULL; apr_port_t port; apr_status_t rv; const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); #ifdef HAVE_SYSTEMD APR_OPTIONAL_FN_TYPE(ap_systemd_listen_fds) *systemd_listen_fds; #endif + apr_uint32_t flags = 0; if (err != NULL) { return err; } - if (argc < 1 || argc > 2) { - return "Listen requires 1 or 2 arguments."; + if (argc < 1 || argc > 3) { + return "Listen requires 1-3 arguments."; } #ifdef HAVE_SYSTEMD if (use_systemd == -1) { @@ -1034,17 +1095,49 @@ AP_DECLARE_NONSTD(const char *) ap_set_listener(cmd_parms *cmd, void *dummy, return "Port must be specified"; } - if (argc != 2) { + 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"; } } - else { - proto = apr_pstrdup(cmd->pool, argv[1]); - ap_str_tolower(proto); - } #ifdef HAVE_SYSTEMD if (use_systemd) { @@ -1053,7 +1146,7 @@ AP_DECLARE_NONSTD(const char *) ap_set_listener(cmd_parms *cmd, void *dummy, #endif return alloc_listener(cmd->server->process, host, port, proto, - scope_id, NULL, cmd->temp_pool); + scope_id, NULL, cmd->temp_pool, flags); } AP_DECLARE_NONSTD(const char *) ap_set_listenbacklog(cmd_parms *cmd, @@ -1115,6 +1208,18 @@ AP_DECLARE_NONSTD(const char *) ap_set_send_buffer_size(cmd_parms *cmd, 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) diff --git a/server/mpm/event/event.c b/server/mpm/event/event.c index 050d823809b..24f8bfaeee3 100644 --- a/server/mpm/event/event.c +++ b/server/mpm/event/event.c @@ -2013,6 +2013,10 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) resource_shortage = 1; signal_threads(ST_GRACEFUL); } + else if (ap_accept_error_is_nonfatal(rc)) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rc, ap_server_conf, + "accept() on client socket failed"); + } if (csd != NULL) { conns_this_child--; diff --git a/server/mpm/prefork/prefork.c b/server/mpm/prefork/prefork.c index b5adb57bea1..59caa04eba0 100644 --- a/server/mpm/prefork/prefork.c +++ b/server/mpm/prefork/prefork.c @@ -650,7 +650,12 @@ static void child_main(int child_num_arg, int child_bucket) /* resource shortage or should-not-occur occurred */ clean_child_exit(APEXIT_CHILDSICK); } - else if (status != APR_SUCCESS) { + if (ap_accept_error_is_nonfatal(status)) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, status, ap_server_conf, + "accept() on client socket failed"); + } + + if (status != APR_SUCCESS) { continue; } diff --git a/server/mpm/worker/worker.c b/server/mpm/worker/worker.c index 315371de121..23fe726f1ea 100644 --- a/server/mpm/worker/worker.c +++ b/server/mpm/worker/worker.c @@ -678,6 +678,10 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t *thd, void * dummy) resource_shortage = 1; signal_threads(ST_GRACEFUL); } + else if (ap_accept_error_is_nonfatal(rv)) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, + "accept() on client socket failed"); + } if ((rv = SAFE_ACCEPT(apr_proc_mutex_unlock(my_bucket->mutex))) != APR_SUCCESS) {