From: Eric Covener <covener@apache.org> Date: Thu, 4 Jan 2018 15:10:45 +0000 (+0000) Subject: Add "AcceptErrorsNonFatal" directive X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=98e9503120f43ea96c824429a95edc9577d1d5a4;p=apache Add "AcceptErrorsNonFatal" directive This tweaks accept() failure processing by having ap_unixd_accept pass more errors up, and having the MPM's check against a macro to see if they are in a whitelist of non ENETDOWN/EMFILE kind of potential process-wide errors. Default behavior is still to exit. edit: MMN bump in 1820099. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1820098 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index 518f1f04f3..5e90aefc4f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,11 @@ -*- coding: utf-8 -*- Changes with Apache 2.5.1 + + *) core: Add "AcceptErrorsNonFatal" to allow ECONNREFUSED, ECONNABORTED, and + ECONNRESET during the client accept() to not trigger graceful shutdown of + the child process. [Eric Covener] + *) mod_md v1.1.7: - MDMustStaple was unable to create the necessary OpenSSL OBJ identifier on some platforms, possibly because this fails if the OID is already configured in ```openssl.cnf```, see diff --git a/docs/manual/mod/mpm_common.xml b/docs/manual/mod/mpm_common.xml index 2978b20794..d1e09eeed5 100644 --- a/docs/manual/mod/mpm_common.xml +++ b/docs/manual/mod/mpm_common.xml @@ -873,4 +873,35 @@ client connections</description> </usage> </directivesynopsis> +<directivesynopsis> +<name>AcceptErrorsNonFatal</name> +<description>Treat some errors accepting a new connection as non-fatal +to the httpd process.</description> +<syntax>AcceptErrorsNonFatal ON</syntax> +<default>OFF (ECONNREFUSED, ECONNABORTED, ECONNRESET cause the process to + exit)</default> +<contextlist><context>server config</context></contextlist> +<modulelist><module>event</module><module>worker</module> +<module>prefork</module> +</modulelist> +<compatibility>2.5.1 and later</compatibility> + +<usage> + <p>The <directive>AcceptErrorsNonFatal</directive> 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.</p> + + <p>With <directive>AcceptErrorsNonFatal</directive> set to "ON", + the process will <em>not</em> begin to exit if the accept() error is + ECONNREFUSED, ECONNABORTED, or ECONNRESET.</p> + + <note>Some third-party firwewall software components may inject errors + into accept() processing, using return codes not specified by the + operating system</note> +</usage> +</directivesynopsis> + </modulesynopsis> diff --git a/include/ap_listen.h b/include/ap_listen.h index 58c2574ff4..9cbaaa4910 100644 --- a/include/ap_listen.h +++ b/include/ap_listen.h @@ -71,6 +71,12 @@ struct ap_listen_rec { const char* protocol; ap_slave_t *slave; + + /** + * Allow the accept_func to return a wider set of return codes + */ + int use_specific_errors; + }; /** @@ -79,6 +85,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 @@ -143,6 +152,10 @@ AP_DECLARE_NONSTD(const char *) ap_set_receive_buffer_size(cmd_parms *cmd, void *dummy, const char *arg); +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)"), \ @@ -153,8 +166,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/os/unix/unixd.c b/os/unix/unixd.c index 43645f09da..b939355289 100644 --- a/os/unix/unixd.c +++ b/os/unix/unixd.c @@ -320,6 +320,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->use_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 2d1db6dc29..56308e21cc 100644 --- a/server/listen.c +++ b/server/listen.c @@ -59,6 +59,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; @@ -793,6 +796,7 @@ AP_DECLARE(int) ap_setup_listeners(server_rec *s) } for (lr = ap_listeners; lr; lr = lr->next) { + lr->use_specific_errors = ap_accept_errors_nonfatal; num_listeners++; found = 0; for (ls = s; ls && !found; ls = ls->next) { @@ -1004,6 +1008,14 @@ 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); +} + AP_DECLARE_NONSTD(const char *) ap_set_listener(cmd_parms *cmd, void *dummy, int argc, char *const argv[]) { @@ -1128,6 +1140,14 @@ 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) +{ + 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 8d33442b8c..6ccc208445 100644 --- a/server/mpm/event/event.c +++ b/server/mpm/event/event.c @@ -2068,6 +2068,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/motorz/motorz.c b/server/mpm/motorz/motorz.c index d8d9522295..be8f3020e4 100644 --- a/server/mpm/motorz/motorz.c +++ b/server/mpm/motorz/motorz.c @@ -202,6 +202,11 @@ static apr_status_t motorz_io_accept(motorz_core_t *mz, motorz_sb_t *sb) "motorz_io_accept failed"); clean_child_exit(APEXIT_CHILDSICK); } + else if (ap_accept_error_is_nonfatal(rv)) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, + "accept() on client socket failed"); + } + else { motorz_conn_t *scon = apr_pcalloc(ptrans, sizeof(motorz_conn_t)); scon->pool = ptrans; diff --git a/server/mpm/prefork/prefork.c b/server/mpm/prefork/prefork.c index 649cc97d63..e20c516c7e 100644 --- a/server/mpm/prefork/prefork.c +++ b/server/mpm/prefork/prefork.c @@ -600,7 +600,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) { + 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 (status != APR_SUCCESS) { continue; } diff --git a/server/mpm/worker/worker.c b/server/mpm/worker/worker.c index c87c003c7e..9a971ebd57 100644 --- a/server/mpm/worker/worker.c +++ b/server/mpm/worker/worker.c @@ -699,6 +699,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) {