Add optional options= argument to Listen to add listener-specific

socket options.

Reimplement "use_specific_errors" listener flag under generic
ap_listen_rec flags field holding all listener-specific options.

* include/ap_listen.h: Add AP_LISTEN_* flags.
  (ap_listen_rec): Rename use_specific_errors to flags.

* server/listen.c (make_sock): Set APR_SO_FREEBIND if
  AP_LISTEN_FREEBIND flag is set on listener; set APR_SO_REUSEPORT
  unconditionally if AP_LISTEN_REUSEPORT is set.
  (alloc_listener): Take flags argument.
  (ap_setup_listeners): Set AP_LISTEN_SPECIFIC_ERRORS flag here.
  (ap_set_listener): Parse optional options=... argument, catch
  typos and fail if protocol name contains a "=".
  (ap_duplicate_listeners): Duplicate flags.

Submitted by: jkaluza, Lubos Uhliarik <luhliari redhat.com>, jorton
PR: 61865
Github: closes #114


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1876865 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Joe Orton
2020-04-23 08:26:26 +00:00
parent 48aa76e1fc
commit a42f3692b1
6 changed files with 125 additions and 20 deletions

View File

@ -1,6 +1,10 @@
-*- coding: utf-8 -*-
Changes with Apache 2.5.1
*) core: Add optional "options=" argument to Listen. Supported
keywords are "freebind" and "reuseport". PR 61865.
[Jan Kaluza, Lubos Uhliarik <luhliari redhat.com>, Joe Orton]
*) config: Allow for environment variable substitution with default value,
for when the variable is not defined, using format ${VAR?=default value}.
[Yann Ylavic]

View File

@ -182,12 +182,13 @@ 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 optional <code>options=</code> argument is available in httpd 2.5.1 and later.</compatibility>
<usage>
<p>The <directive>Listen</directive> directive instructs Apache httpd to
@ -254,8 +255,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>

View File

@ -38,6 +38,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.
*
@ -73,10 +78,9 @@ struct ap_listen_rec {
ap_slave_t *slave;
/**
* Allow the accept_func to return a wider set of return codes
* Various AP_LISTEN_* flags.
*/
int use_specific_errors;
apr_uint32_t flags;
};
/**

View File

@ -628,14 +628,15 @@
* 20200331.2 (2.5.1-dev) Add ap_proxy_should_override to mod_proxy.h
* 20200331.3 (2.5.1-dev) Add ap_parse_request_line() and
* ap_check_request_header()
* 20200420.0 (2.5.1-dev) Add flags to listen_rec in place of use_specific_errors
*/
#define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */
#ifndef MODULE_MAGIC_NUMBER_MAJOR
#define MODULE_MAGIC_NUMBER_MAJOR 20200331
#define MODULE_MAGIC_NUMBER_MAJOR 20200420
#endif
#define MODULE_MAGIC_NUMBER_MINOR 3 /* 0...n */
#define MODULE_MAGIC_NUMBER_MINOR 0 /* 0...n */
/**
* Determine if the server's current MODULE_MAGIC_NUMBER is at least a

View File

@ -323,7 +323,7 @@ AP_DECLARE(apr_status_t) ap_unixd_accept(void **accepted, ap_listen_rec *lr,
}
/* Let the caller handle slightly more varied return values */
if (lr->use_specific_errors && ap_accept_error_is_nonfatal(status)) {
if ((lr->flags & AP_LISTEN_SPECIFIC_ERRORS) && ap_accept_error_is_nonfatal(status)) {
return status;
}

View File

@ -150,7 +150,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,
@ -166,6 +167,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()
"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) {
@ -467,7 +483,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;
@ -511,6 +527,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;
@ -795,7 +812,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;
if (ap_accept_errors_nonfatal) lr->flags |= AP_LISTEN_SPECIFIC_ERRORS;
num_listeners++;
found = 0;
for (ls = s; ls && !found; ls = ls->next) {
@ -885,6 +902,7 @@ 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;
duplr->flags = lr->flags;
stat = apr_socket_create(&duplr->sd, duplr->bind_addr->family,
SOCK_STREAM, 0, p);
if (stat != APR_SUCCESS) {
@ -1015,20 +1033,48 @@ AP_DECLARE(int) ap_accept_error_is_nonfatal(apr_status_t 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);
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) {
@ -1058,17 +1104,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) {
@ -1077,7 +1155,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,