From 5003ac7fe9b3709daa624d087bb1cc2ee93a8a3a Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Wed, 19 Apr 2023 09:12:33 +0200 Subject: [PATCH] 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 --- doc/configuration.txt | 41 ++++++++++++++++++++++++++++---------- reg-tests/ssl/ssl_alpn.vtc | 10 +++++----- src/cfgparse.c | 24 ++++++++++++++++++++++ 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index d1313b168f..d098695e31 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -4875,7 +4875,7 @@ bind / [, ...] [param*] bind "fd@${FD_APP1}" 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 sun_path length is used for the address length. Some other programs @@ -14606,21 +14606,30 @@ alpn 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 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. - Versions of OpenSSL prior to 1.0.2 didn't support ALPN and only supposed the - now obsolete NPN extension. At the time of writing this, most 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. If both HTTP/2 and HTTP/1.1 - are expected to be supported, both versions can be advertised, in order of - preference, like below : - - bind :443 ssl crt pub.pem alpn h2,http/1.1 + initial NPN extension. At the protocol layer, ALPN is required to enable + HTTP/2 on an HTTPS frontend and HTTP/3 on a QUIC frontend. However, when such + frontends have none of "npn", "alpn" and "no-alpn" set, a default value of + "h2,http/1.1" will be used for a regular HTTPS frontend, and "h3" for a QUIC + frontend. Versions of OpenSSL prior to 1.0.2 didn't support ALPN and only + supposed the now obsolete NPN extension. At the time of writing this, most + 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 # explicitly disable HTTP/1.1 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). Each "alpn" statement will replace a previous one. In order to remove them, 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 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. @@ -14828,6 +14837,12 @@ crt-list never match except if no other certificate matches. This way the first 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: cert1.pem !* # comment @@ -15013,7 +15028,11 @@ no-alpn 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 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 This setting is only available when support for OpenSSL was built in. It diff --git a/reg-tests/ssl/ssl_alpn.vtc b/reg-tests/ssl/ssl_alpn.vtc index ea1b8a60e7..dfc63ac043 100644 --- a/reg-tests/ssl/ssl_alpn.vtc +++ b/reg-tests/ssl/ssl_alpn.vtc @@ -117,7 +117,7 @@ client c1 -connect ${h1_clearfe_sock} { txreq -url "/10" rxresp expect resp.status == 200 - expect resp.http.x-alpn == "_" + expect resp.http.x-alpn == "_http/1.1" expect resp.http.x-ver == "_1.1" txreq -url "/11" @@ -150,8 +150,8 @@ client c1 -connect ${h1_clearfe_sock} { txreq -url "/20" rxresp expect resp.status == 200 - expect resp.http.x-alpn == "_" - expect resp.http.x-ver == "_1.1" + expect resp.http.x-alpn == "_h2" + expect resp.http.x-ver == "_2.0" txreq -url "/21" rxresp @@ -183,8 +183,8 @@ client c1 -connect ${h1_clearfe_sock} { txreq -url "/30" rxresp expect resp.status == 200 - expect resp.http.x-alpn == "_" - expect resp.http.x-ver == "_1.1" + expect resp.http.x-alpn == "_h2" + expect resp.http.x-ver == "_2.0" txreq -url "/31" rxresp diff --git a/src/cfgparse.c b/src/cfgparse.c index ce9932b0ad..72dde1ea6e 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -2937,6 +2937,30 @@ init_proxies_list_stage1: free(bind_conf->ssl_conf.alpn_str); 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) { #ifdef OPENSSL_NPN_NEGOTIATED -- 2.39.5