Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions docs/manual/mod/mpm_common.xml
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,14 @@ of the daemon</description>
<name>Listen</name>
<description>IP addresses and ports that the server
listens to</description>
<syntax>Listen [<var>IP-address</var>:]<var>portnumber</var> [<var>protocol</var>]</syntax>
<syntax>Listen [<var>IP-address</var>:]<var>portnumber</var> [<var>protocol</var>] [options=<var>flag</var>[,<var>flag..</var>]]</syntax>
<contextlist><context>server config</context></contextlist>
<modulelist><module>event</module><module>worker</module>
<module>prefork</module><module>mpm_winnt</module>
<module>mpm_netware</module><module>mpmt_os2</module>
</modulelist>
<compatibility>The <var>protocol</var> argument was added in 2.1.5</compatibility>
<compatibility>The optional <code>options=</code> argument is available in httpd 2.4.66 and later.</compatibility>

<usage>
<p>The <directive>Listen</directive> directive instructs Apache httpd to
Expand Down Expand Up @@ -247,8 +248,25 @@ Listen 192.170.2.5:8000
Listen 192.170.2.1:8443 https
</highlight>

<p>The optional <var>options=flag,flag...</var> 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 <em>flag</em>s are:</p>

<ul>
<li><code>freebind</code>: The <code>IP_FREEBIND</code> socket
option is enabled, allowing a Listen directive to be used for an
address which is not (yet) available on the system. (Linux
only)</li>

<li><code>reuseport</code>: The <code>SO_REUSEPORT</code> socket
option is enabled, allowing a Listen directive to bind to a port
which may already be in use by another process.</li>
</ul>

<note><title>Error condition</title>
Multiple <directive>Listen</directive> directives for the same ip
Multiple <directive>Listen</directive> directives for the same IP
address and port will result in an <code>Address already in use</code>
error message.
</note>
Expand Down Expand Up @@ -889,4 +907,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.4.66 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>
22 changes: 20 additions & 2 deletions include/ap_listen.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -72,6 +77,11 @@ struct ap_listen_rec {
const char* protocol;

ap_slave_t *slave;

/**
* Various AP_LISTEN_* flags.
*/
apr_uint32_t flags;
};

/**
Expand All @@ -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
Expand Down Expand Up @@ -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)"), \
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion include/ap_mmn.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions os/unix/unixd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
131 changes: 118 additions & 13 deletions server/listen.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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!",
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions server/mpm/event/event.c
Original file line number Diff line number Diff line change
Expand Up @@ -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--;
Expand Down
Loading