]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: server: add ws keyword
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Mon, 18 Oct 2021 12:40:29 +0000 (14:40 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 3 Nov 2021 15:24:48 +0000 (16:24 +0100)
Implement parsing for the server keyword 'ws'. This is used to configure
the mode of selection for websocket protocol. The configuration
documentation has been updated.

A new regtest has been created to test the proper behavior of the
keyword.

doc/configuration.txt
doc/management.txt
reg-tests/http-messaging/common.pem [new symlink]
reg-tests/http-messaging/srv_ws.vtc [new file with mode: 0644]
src/server.c

index 1399bf51092bc7875f660662100d0af352b08306..42efa329ee0a07fb4a3d0dfb7c4e8594a05dfe73 100644 (file)
@@ -14329,6 +14329,8 @@ alpn <protocols>
 
        server 127.0.0.1:443 ssl crt pub.pem alpn h2,http/1.1
 
+  See also "ws" to use an alternative ALPN for websocket streams.
+
 backup
   When "backup" is present on a server line, the server is only used in load
   balancing when all other non-backup servers are unavailable. Requests coming
@@ -14942,6 +14944,8 @@ proto <name>
   Idea behind this option is to bypass the selection of the best multiplexer's
   protocol for all connections established to this server.
 
+  See also "ws" to use an alternative protocol for websocket streams.
+
 redir <prefix>
   The "redir" parameter enables the redirection mode for all GET and HEAD
   requests addressing this server. This means that instead of having HAProxy
@@ -15274,6 +15278,26 @@ weight <weight>
   can both grow and shrink, for instance between 10 and 100 to leave enough
   room above and below for later adjustments.
 
+ws { auto | h1 | h2 }
+  This option allows to configure the protocol used when relaying websocket
+  streams. This is most notably useful when using an HTTP/2 backend without the
+  support for H2 websockets through the RFC8441.
+
+  The default mode is "auto". This will reuse the same protocol as the main
+  one. The only difference is when using ALPN. In this case, it can try to
+  downgrade the ALPN to "http/1.1" only for websocket streams if the configured
+  server ALPN contains it.
+
+  The value "h1" is used to force HTTP/1.1 for websockets streams, through ALPN
+  if SSL ALPN is activated for the server. Similarly, "h2" can be used to
+  force HTTP/2.0 websockets. Use this value with care : the server must support
+  RFC8441 or an error will be reported by haproxy when relaying websockets.
+
+  Note that NPN is not taken into account as its usage has been deprecated in
+  favor of the ALPN extension.
+
+  See also "alpn" and "proto".
+
 
 5.3. Server IP address resolution using DNS
 -------------------------------------------
index 8b7d9cd29b9b9cbe782411315a5a43ca850ccfec..c85ae1fee6cda1c10da70398d38b9d8932b733c3 100644 (file)
@@ -1561,6 +1561,7 @@ add server <backend>/<server> [args]*
   - verify
   - verifyhost
   - weight
+  - ws
 
   Their syntax is similar to the server line from the configuration file,
   please refer to their individual documentation for details.
diff --git a/reg-tests/http-messaging/common.pem b/reg-tests/http-messaging/common.pem
new file mode 120000 (symlink)
index 0000000..a4433d5
--- /dev/null
@@ -0,0 +1 @@
+../ssl/common.pem
\ No newline at end of file
diff --git a/reg-tests/http-messaging/srv_ws.vtc b/reg-tests/http-messaging/srv_ws.vtc
new file mode 100644 (file)
index 0000000..bce12f6
--- /dev/null
@@ -0,0 +1,181 @@
+# This reg-test checks websocket support in regards with the server keyword
+# 'ws'
+
+varnishtest "h2 backend websocket management via server keyword"
+
+feature ignore_unknown_macro
+
+#REQUIRE_VERSION=2.5
+#REQUIRE_OPTION=OPENSSL
+
+# haproxy server
+haproxy hapsrv -conf {
+       defaults
+               mode http
+               timeout connect 5s
+               timeout client  5s
+               timeout server  5s
+
+       frontend fe
+               bind "fd@${fe}"
+               bind "fd@${fessl}" ssl crt ${testdir}/common.pem alpn h2,http/1.1
+               capture request header sec-websocket-key len 128
+               http-request set-var(txn.ver) req.ver
+               use_backend be
+
+       backend be
+               # define websocket ACL
+               acl ws_handshake hdr(upgrade) -m str websocket
+
+               # handle non-ws streams
+               http-request return status 200 if !ws_handshake
+
+               # handle ws streams
+               #capture request header sec-websocket-key len 128
+               http-request return status 200 hdr connection upgrade hdr upgrade websocket hdr sec-websocket-accept "%[capture.req.hdr(0),concat(258EAFA5-E914-47DA-95CA-C5AB0DC85B11,,),sha1,base64]" if ws_handshake
+               http-after-response set-status 101 if { status eq 200 } { res.hdr(upgrade) -m str websocket }
+               http-after-response set-header x-backend-protocol "%[var(txn.ver)]"
+} -start
+
+# haproxy LB
+haproxy hap -conf {
+       defaults
+               mode http
+               timeout connect 1s
+               timeout client  1s
+               timeout server  1s
+
+       # proto X ws h1 -> websocket on h1
+       listen li
+               bind "fd@${li}"
+               server hap_srv ${hapsrv_fe_addr}:${hapsrv_fe_port} proto h2 ws h1
+
+       # proto X ws h2 -> websocket on h2
+       listen lih2
+               bind "fd@${lih2}"
+               server hap_srv ${hapsrv_fe_addr}:${hapsrv_fe_port} proto h2 ws h2
+
+       # alpn h2,http/1.1 ws h2 -> websocket on h2
+       listen lisslh2
+               bind "fd@${lisslh2}"
+               server hap_srv ${hapsrv_fessl_addr}:${hapsrv_fessl_port} ssl verify none alpn h2,http/1.1 ws h2
+               http-response set-header x-alpn "%[ssl_bc_alpn]"
+
+       # ws auto -> websocket on h1
+       listen liauto
+               bind "fd@${liauto}"
+               server hap_srv ${hapsrv_fe_addr}:${hapsrv_fe_port}
+
+       # alpn h2,http/1.1 ws auto -> websocket on h1
+       listen lissl
+               bind "fd@${lissl}"
+               server hap_srv ${hapsrv_fessl_addr}:${hapsrv_fessl_port} ssl verify none alpn h2,http/1.1 ws auto
+               http-response set-header x-alpn "%[ssl_bc_alpn]"
+       # alpn h2,http/1.1 ws auto -> websocket on h1
+       listen lisslauto
+               bind "fd@${lisslauto}"
+               server hap_srv ${hapsrv_fessl_addr}:${hapsrv_fessl_port} ssl verify none alpn h2,http/1.1
+               http-response set-header x-alpn "%[ssl_bc_alpn]"
+
+       # proto h2 ws auto -> websocket on h2
+       listen liauto2
+               bind "fd@${liauto2}"
+               server hap_srv ${hapsrv_fe_addr}:${hapsrv_fe_port} proto h2
+
+       # alpn h2 ws auto -> websocket on h2
+       listen lisslauto2
+               bind "fd@${lisslauto2}"
+               server hap_srv ${hapsrv_fessl_addr}:${hapsrv_fessl_port} ssl verify none alpn h2 ws auto
+               http-response set-header x-alpn "%[ssl_bc_alpn]"
+} -start
+
+client c1 -connect ${hap_li_sock} {
+       txreq \
+         -req "GET" \
+         -url "/" \
+         -hdr "host: 127.0.0.1" \
+         -hdr "connection: upgrade" \
+         -hdr "upgrade: websocket" \
+         -hdr "sec-websocket-key: dGhlIHNhbXBsZSBub25jZQ=="
+       rxresp
+       expect resp.status == 101
+       expect resp.http.x-backend-protocol == "1.1"
+} -run
+
+client c1.2 -connect ${hap_lih2_sock} {
+       txreq \
+         -req "GET" \
+         -url "/" \
+         -hdr "host: 127.0.0.1" \
+         -hdr "connection: upgrade" \
+         -hdr "upgrade: websocket" \
+         -hdr "sec-websocket-key: dGhlIHNhbXBsZSBub25jZQ=="
+       rxresp
+       expect resp.status == 101
+       expect resp.http.x-backend-protocol == "2.0"
+} -run
+
+client c1.3 -connect ${hap_liauto_sock} {
+       txreq \
+         -req "GET" \
+         -url "/" \
+         -hdr "host: 127.0.0.1" \
+         -hdr "connection: upgrade" \
+         -hdr "upgrade: websocket" \
+         -hdr "sec-websocket-key: dGhlIHNhbXBsZSBub25jZQ=="
+       rxresp
+       expect resp.status == 101
+       expect resp.http.x-backend-protocol == "1.1"
+} -run
+
+client c1.4 -connect ${hap_liauto2_sock} {
+       txreq \
+         -req "GET" \
+         -url "/" \
+         -hdr "host: 127.0.0.1" \
+         -hdr "connection: upgrade" \
+         -hdr "upgrade: websocket" \
+         -hdr "sec-websocket-key: dGhlIHNhbXBsZSBub25jZQ=="
+       rxresp
+       expect resp.status == 101
+       expect resp.http.x-backend-protocol == "2.0"
+} -run
+
+client c2 -connect ${hap_lisslauto_sock} {
+       txreq \
+         -req "GET" \
+         -url "/" \
+         -hdr "host: 127.0.0.1" \
+         -hdr "connection: upgrade" \
+         -hdr "upgrade: websocket" \
+         -hdr "sec-websocket-key: dGhlIHNhbXBsZSBub25jZQ=="
+       rxresp
+       expect resp.status == 101
+       expect resp.http.x-alpn == "http/1.1"
+} -run
+
+client c2.2 -connect ${hap_lisslauto2_sock} {
+       txreq \
+         -req "GET" \
+         -url "/" \
+         -hdr "host: 127.0.0.1" \
+         -hdr "connection: upgrade" \
+         -hdr "upgrade: websocket" \
+         -hdr "sec-websocket-key: dGhlIHNhbXBsZSBub25jZQ=="
+       rxresp
+       expect resp.status == 101
+       expect resp.http.x-alpn == "h2"
+} -run
+
+client c2.3 -connect ${hap_lisslh2_sock} {
+       txreq \
+         -req "GET" \
+         -url "/" \
+         -hdr "host: 127.0.0.1" \
+         -hdr "connection: upgrade" \
+         -hdr "upgrade: websocket" \
+         -hdr "sec-websocket-key: dGhlIHNhbXBsZSBub25jZQ=="
+       rxresp
+       expect resp.status == 101
+       expect resp.http.x-alpn == "h2"
+} -run
index f6e3aa94399bb02363a3a6e6983230949eae8539..600dbfd04f751506a0f5e40e9c8b04f1daa25b97 100644 (file)
@@ -505,6 +505,33 @@ static int srv_parse_error_limit(char **args, int *cur_arg,
        return 0;
 }
 
+/* Parse the "ws" keyword */
+static int srv_parse_ws(char **args, int *cur_arg,
+                        struct proxy *curproxy, struct server *newsrv, char **err)
+{
+       if (!args[*cur_arg + 1]) {
+               memprintf(err, "'%s' expects 'auto', 'h1' or 'h2' value", args[*cur_arg]);
+               return ERR_ALERT | ERR_FATAL;
+       }
+
+       if (strcmp(args[*cur_arg + 1], "h1") == 0) {
+               newsrv->ws = SRV_WS_H1;
+       }
+       else if (strcmp(args[*cur_arg + 1], "h2") == 0) {
+               newsrv->ws = SRV_WS_H2;
+       }
+       else if (strcmp(args[*cur_arg + 1], "auto") == 0) {
+               newsrv->ws = SRV_WS_AUTO;
+       }
+       else {
+               memprintf(err, "'%s' has to be 'auto', 'h1' or 'h2'", args[*cur_arg]);
+               return ERR_ALERT | ERR_FATAL;
+       }
+
+
+       return 0;
+}
+
 /* Parse the "init-addr" server keyword */
 static int srv_parse_init_addr(char **args, int *cur_arg,
                                struct proxy *curproxy, struct server *newsrv, char **err)
@@ -1733,6 +1760,7 @@ static struct srv_kw_list srv_kws = { "ALL", { }, {
        { "disabled",            srv_parse_disabled,            0,  1,  1 }, /* Start the server in 'disabled' state */
        { "enabled",             srv_parse_enabled,             0,  1,  1 }, /* Start the server in 'enabled' state */
        { "error-limit",         srv_parse_error_limit,         1,  1,  1 }, /* Configure the consecutive count of check failures to consider a server on error */
+       { "ws",                  srv_parse_ws,                  1,  1,  1 }, /* websocket protocol */
        { "id",                  srv_parse_id,                  1,  0,  1 }, /* set id# of server */
        { "init-addr",           srv_parse_init_addr,           1,  1,  0 }, /* */
        { "log-proto",           srv_parse_log_proto,           1,  1,  0 }, /* Set the protocol for event messages, only relevant in a ring section */