MEDIUM: config: set useful ALPN defaults for HTTPS and QUIC

This commit makes sure that if three is no "alpn", "npn" nor "no-alpn"
setting on a "bind" line which corresponds to an HTTPS or QUIC frontend,
we automatically turn on "h2,http/1.1" as an ALPN default for an HTTP
listener, and "h3" for a QUIC listener. This simplifies the configuration
for end users since they won't have to explicitly configure the ALPN
string to enable H2, considering that at the time of writing, HTTP/1.1
represents less than 7% of the traffic on large infrastructures. The
doc and regtests were updated. For more info, refer to the following
thread:

  https://www.mail-archive.com/haproxy@formilux.org/msg43410.html
This commit is contained in:
Willy Tarreau 2023-04-19 09:12:33 +02:00
parent de85de69ec
commit 5003ac7fe9
3 changed files with 58 additions and 15 deletions

View File

@ -4875,7 +4875,7 @@ bind /<path> [, ...] [param*]
bind "fd@${FD_APP1}" bind "fd@${FD_APP1}"
listen h3_quic_proxy listen h3_quic_proxy
bind quic4@10.0.0.1:8888 ssl crt /etc/mycrt alpn h3 bind quic4@10.0.0.1:8888 ssl crt /etc/mycrt
Note: regarding Linux's abstract namespace sockets, HAProxy uses the whole Note: regarding Linux's abstract namespace sockets, HAProxy uses the whole
sun_path length is used for the address length. Some other programs sun_path length is used for the address length. Some other programs
@ -14606,21 +14606,30 @@ alpn <protocols>
delimited list of protocol names, for instance: "http/1.1,http/1.0" (without delimited list of protocol names, for instance: "http/1.1,http/1.0" (without
quotes). This requires that the SSL library is built with support for TLS quotes). This requires that the SSL library is built with support for TLS
extensions enabled (check with haproxy -vv). The ALPN extension replaces the extensions enabled (check with haproxy -vv). The ALPN extension replaces the
initial NPN extension. ALPN is required to enable HTTP/2 on an HTTP frontend. initial NPN extension. At the protocol layer, ALPN is required to enable
Versions of OpenSSL prior to 1.0.2 didn't support ALPN and only supposed the HTTP/2 on an HTTPS frontend and HTTP/3 on a QUIC frontend. However, when such
now obsolete NPN extension. At the time of writing this, most browsers still frontends have none of "npn", "alpn" and "no-alpn" set, a default value of
support both ALPN and NPN for HTTP/2 so a fallback to NPN may still work for "h2,http/1.1" will be used for a regular HTTPS frontend, and "h3" for a QUIC
a while. But ALPN must be used whenever possible. If both HTTP/2 and HTTP/1.1 frontend. Versions of OpenSSL prior to 1.0.2 didn't support ALPN and only
are expected to be supported, both versions can be advertised, in order of supposed the now obsolete NPN extension. At the time of writing this, most
preference, like below : browsers still support both ALPN and NPN for HTTP/2 so a fallback to NPN may
still work for a while. But ALPN must be used whenever possible. Protocols
not advertised are not negotiated. For example it is possible to only accept
HTTP/2 connections with this:
bind :443 ssl crt pub.pem alpn h2,http/1.1 bind :443 ssl crt pub.pem alpn h2 # explicitly disable HTTP/1.1
QUIC supports only h3 and hq-interop as ALPN. h3 is for HTTP/3 and hq-interop QUIC supports only h3 and hq-interop as ALPN. h3 is for HTTP/3 and hq-interop
is used for http/0.9 and QUIC interop runner (see https://interop.seemann.io). is used for http/0.9 and QUIC interop runner (see https://interop.seemann.io).
Each "alpn" statement will replace a previous one. In order to remove them, Each "alpn" statement will replace a previous one. In order to remove them,
use "no-alpn". use "no-alpn".
Note that some old browsers such as Firefox 88 used to experience issues with
WebSocket over H2, and in case such a setup is encountered, it may be needed
to either explicitly disable HTTP/2 in the "alpn" string by forcing it to
"http/1.1" or "no-alpn", or to enable "h2-workaround-bogus-websocket-clients"
globally.
backlog <backlog> backlog <backlog>
Sets the socket's backlog to this value. If unspecified or 0, the frontend's Sets the socket's backlog to this value. If unspecified or 0, the frontend's
backlog is used instead, which generally defaults to the maxconn value. backlog is used instead, which generally defaults to the maxconn value.
@ -14828,6 +14837,12 @@ crt-list <file>
never match except if no other certificate matches. This way the first never match except if no other certificate matches. This way the first
declared certificate act as a fallback. declared certificate act as a fallback.
When no ALPN is set, the "bind" line's default one is used. If a "bind" line
has no "no-alpn", "alpn" nor "npn" set, a default value will be used
depending on the protocol (see "alpn" above). However if the "bind" line has
a different default, or explicitly disables ALPN using "no-alpn", it is
possible to force a specific value for a certificate.
crt-list file example: crt-list file example:
cert1.pem !* cert1.pem !*
# comment # comment
@ -15013,7 +15028,11 @@ no-alpn
Disables ALPN processing (technically speaking this sets the ALPN string to Disables ALPN processing (technically speaking this sets the ALPN string to
an empty string that will not be advertised). It permits to cancel a previous an empty string that will not be advertised). It permits to cancel a previous
occurrence of an "alpn" setting and to disable application protocol occurrence of an "alpn" setting and to disable application protocol
negotiation. See also "alpn". negotiation. It may also be used to prevent a listener from negotiating ALPN
with a client on an HTTPS or QUIC listener; by default, HTTPS listeners will
advertise "h2,http/1.1" and QUIC listeners will advertise "h3". See also
"alpn" bove. Note that when using "crt-list", a certificate may override the
"alpn" setting and re-enable its processing.
no-ca-names no-ca-names
This setting is only available when support for OpenSSL was built in. It This setting is only available when support for OpenSSL was built in. It

View File

@ -117,7 +117,7 @@ client c1 -connect ${h1_clearfe_sock} {
txreq -url "/10" txreq -url "/10"
rxresp rxresp
expect resp.status == 200 expect resp.status == 200
expect resp.http.x-alpn == "_" expect resp.http.x-alpn == "_http/1.1"
expect resp.http.x-ver == "_1.1" expect resp.http.x-ver == "_1.1"
txreq -url "/11" txreq -url "/11"
@ -150,8 +150,8 @@ client c1 -connect ${h1_clearfe_sock} {
txreq -url "/20" txreq -url "/20"
rxresp rxresp
expect resp.status == 200 expect resp.status == 200
expect resp.http.x-alpn == "_" expect resp.http.x-alpn == "_h2"
expect resp.http.x-ver == "_1.1" expect resp.http.x-ver == "_2.0"
txreq -url "/21" txreq -url "/21"
rxresp rxresp
@ -183,8 +183,8 @@ client c1 -connect ${h1_clearfe_sock} {
txreq -url "/30" txreq -url "/30"
rxresp rxresp
expect resp.status == 200 expect resp.status == 200
expect resp.http.x-alpn == "_" expect resp.http.x-alpn == "_h2"
expect resp.http.x-ver == "_1.1" expect resp.http.x-ver == "_2.0"
txreq -url "/31" txreq -url "/31"
rxresp rxresp

View File

@ -2937,6 +2937,30 @@ int check_config_validity()
free(bind_conf->ssl_conf.alpn_str); free(bind_conf->ssl_conf.alpn_str);
bind_conf->ssl_conf.alpn_str = NULL; bind_conf->ssl_conf.alpn_str = NULL;
} }
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
else if (!bind_conf->ssl_conf.alpn_str && !bind_conf->ssl_conf.npn_str &&
((bind_conf->options & BC_O_USE_SSL) || bind_conf->xprt == xprt_get(XPRT_QUIC)) &&
curproxy->mode == PR_MODE_HTTP && global.tune.bufsize >= 16384) {
/* Neither ALPN nor NPN were explicitly set nor disabled, we're
* in HTTP mode with an SSL or QUIC listener, we can enable ALPN.
* Note that it's in binary form.
*/
if (bind_conf->xprt == xprt_get(XPRT_QUIC))
bind_conf->ssl_conf.alpn_str = strdup("\002h3");
else
bind_conf->ssl_conf.alpn_str = strdup("\002h2\010http/1.1");
if (!bind_conf->ssl_conf.alpn_str) {
ha_alert("Proxy '%s': out of memory while trying to allocate a default alpn string in 'bind %s' at [%s:%d].\n",
curproxy->id, bind_conf->arg, bind_conf->file, bind_conf->line);
cfgerr++;
err_code |= ERR_FATAL | ERR_ALERT;
goto out;
}
bind_conf->ssl_conf.alpn_len = strlen(bind_conf->ssl_conf.alpn_str);
}
#endif
if (curproxy->mode == PR_MODE_HTTP && global.tune.bufsize < 16384) { if (curproxy->mode == PR_MODE_HTTP && global.tune.bufsize < 16384) {
#ifdef OPENSSL_NPN_NEGOTIATED #ifdef OPENSSL_NPN_NEGOTIATED