haproxy/reg-tests/http-messaging/websocket.vtc
Willy Tarreau 00941af7b7 BUG/MEDIUM: mux-h2: fix the detection of the ext connect support
As reported by Huangbin Zhan (@zhanhb) in github issue #3355, latest
commit 96f7ff4fdd ("MINOR: mux-h2: add a new message flag to indicate
ext connect support") was not correct and can break RFC8441-compliant
clients, as it did for them with a variant of Chrome 142.

The problem is that while RFC9113 says that new pseudo-headers are only
permitted with *negotiated* extensions, and RFC8441 doesn't indicate
whether or not SETTINGS_ENABLE_CONNECT_PROTOCOL is needed from clients,
it only says that clients know that servers support the extension when
seeing it in their settings and can use it, which seems to imply that
they don't need to send it to indicate their willingness to use it.
This also means that the server cannot know if a client is expected to
use it or not by default. It only know that a client is not allowed to
use it if the server didn't emit support mentioning it, which haproxy
can do using h2-workaround-bogus-websocket-clients.

Thus the fix proposed by @zhanhb is right, when presetting the flag for
the parser to indicate whether or not we're willing to accept RFC8441's
:protocol pseudo-header, we should:
  - consider the received setting on the backend side (though the
    pseudo-header is neither used nor supported there, but at least
    we pass the info regarding the support of the extension)
  - consider the configuration for the frontend (since it's the only
    place where we can decide on support or not)

This patch does just that and reverts the accompanying changes to the
regtests that made them want to see the client's setting. It must be
backported to 2.6.

In the mean time, placing this option in the global section will force
the clients to downgrade to h1:

    h2-workaround-bogus-websocket-clients

Many thanks again to @zhanbb this feedback and proposing a tested fix.
2026-05-07 17:34:39 +02:00

228 lines
5.4 KiB
Text

# This reg-test is uses to test respect of the websocket protocol according to
# rfc6455.
#
# In particular, a request/response without a websocket key must be rejected by
# haproxy. Note that in the tested case (h1 on both sides), haproxy does not
# validate the key of the server but only checks its presence.
#
# For the case h2 client/h1 server, haproxy would add the key and validates it.
# However, there is no way to check this case quickly at the moment using vtest.
varnishtest "WebSocket test"
feature ignore_unknown_macro
# valid websocket server
server s1 {
rxreq
expect req.method == "GET"
expect req.http.connection == "upgrade"
expect req.http.upgrade == "websocket"
expect req.http.sec-websocket-key == "dGhlIHNhbXBsZSBub25jZQ=="
txresp \
-status 101 \
-hdr "connection: upgrade" \
-hdr "upgrade: websocket" \
-hdr "sec-websocket-accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
recv 4
send "PONG"
} -start
# non-conformant server: no websocket key
server s2 {
rxreq
expect req.method == "GET"
expect req.http.connection == "upgrade"
expect req.http.upgrade == "websocket"
txresp \
-status 101 \
-hdr "connection: upgrade" \
-hdr "upgrade: websocket"
} -start
# haproxy instance used as a server
# generate a http/1.1 websocket response with the valid key
haproxy hap_srv -conf {
global
.if feature(THREAD)
thread-groups 1
.endif
defaults
mode http
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
listen fe1
bind "fd@${fe1}"
# reject if the request does not contains a websocket key
acl ws_handshake hdr(sec-websocket-key) -m found
http-request reject unless ws_handshake
# return a valid websocket handshake response
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]"
http-after-response set-status 101 if { status eq 200 }
} -start
# haproxy instance used as a server
# generate a http/1.1 websocket response with an invalid key
haproxy hap_srv_bad_key -conf {
global
.if feature(THREAD)
thread-groups 1
.endif
defaults
mode http
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
listen fe1
bind "fd@${fe1}"
# reject if the request does not contains a websocket key
acl ws_handshake hdr(sec-websocket-key) -m found
http-request reject unless ws_handshake
# return an invalid websocket handshake response
capture request header sec-websocket-key len 128
http-request return status 200 hdr connection upgrade hdr upgrade websocket hdr sec-websocket-accept "invalid_key"
http-after-response set-status 101 if { status eq 200 }
} -start
haproxy hap -conf {
global
.if feature(THREAD)
thread-groups 1
.endif
defaults
mode http
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
listen fe1
bind "fd@${fe1}"
server s1 ${s1_addr}:${s1_port}
listen fe2
bind "fd@${fe2}"
server s2 ${s2_addr}:${s2_port}
listen fe3
bind "fd@${fe3}" proto h2
server hap_srv ${hap_srv_fe1_addr}:${hap_srv_fe1_port}
listen fe4
bind "fd@${fe4}" proto h2
server hap_srv_bad_key ${hap_srv_bad_key_fe1_addr}:${hap_srv_bad_key_fe1_port}
} -start
# standard request
client c1 -connect ${hap_fe1_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.connection == "upgrade"
expect resp.http.upgrade == "websocket"
expect resp.http.sec-websocket-accept == "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
send "PING"
recv 4
} -run
# missing websocket key
client c2 -connect ${hap_fe1_sock} {
txreq \
-req "GET" \
-url "/" \
-hdr "host: 127.0.0.1" \
-hdr "connection: upgrade" \
-hdr "upgrade: websocket"
rxresp
expect resp.status == 400
} -run
# missing key on server side
client c3 -connect ${hap_fe2_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 == 502
} -run
# connect with http/2 on a http/1.1 websocket server
# the key must be provided by haproxy
client c4 -connect ${hap_fe3_sock} {
txpri
stream 0 {
txsettings
rxsettings
txsettings -ack
rxsettings
expect settings.ack == true
} -run
stream 1 {
txreq \
-req "CONNECT" \
-scheme "http" \
-url "/" \
-hdr ":authority" "127.0.0.1" \
-hdr ":protocol" "websocket" \
-nostrend
rxwinup
rxhdrs
expect resp.status == 200
} -run
} -run
# connect with http/2 on a http/1.1 websocket server
# however, the server will respond with an invalid key
# haproxy is responsible to reject the request and returning a 502 to the client
client c5 -connect ${hap_fe4_sock} {
txpri
stream 0 {
txsettings
rxsettings
txsettings -ack
rxsettings
expect settings.ack == true
} -run
stream 1 {
txreq \
-req "CONNECT" \
-scheme "http" \
-url "/" \
-hdr ":authority" "127.0.0.1" \
-hdr ":protocol" "websocket" \
-nostrend
rxwinup
rxhdrs
expect resp.status == 502
} -run
} -run