From: Christopher Faulet Date: Wed, 7 Oct 2026 14:36:43 +0000 (+0200) Subject: BUG/MINOR: mux-h1: search the websocket key in the right message X-Git-Tag: v3.5-dev10~133 X-Git-Url: https://git.kaiwu.me/$%7BISSUES%7D/static/gitweb.js?a=commitdiff_plain;h=545328d71da0653ad0db7345c7ffb1d74fea5db7;p=haproxy.git BUG/MINOR: mux-h1: search the websocket key in the right message When the headers of a websocket handshake are parsed, the websocket key is searched by h1_search_websocket_key() in the headers following the HTX message's first block, which is supposed to be the start-line that was just parsed. But on the server side, interim responses (1xx) may still be present in the HTX message when the final response is parsed. So when a server sends "HTTP/1.1 100 Continue\r\n\r\n" immediately followed by the "101 Switching Protocols" response in the same segment, the key is searched in the 100 response, it is not found and the response is rejected with a 502, while it is valid. Let's loop backward on the HTX message instead, which is the one that was just parsed. A reg-test was added for this. This patch could be backported to all supported versions. --- diff --git a/reg-tests/http-messaging/h1_ws_1xx_101_same_read.vtc b/reg-tests/http-messaging/h1_ws_1xx_101_same_read.vtc new file mode 100644 index 000000000..97b0a1ab5 --- /dev/null +++ b/reg-tests/http-messaging/h1_ws_1xx_101_same_read.vtc @@ -0,0 +1,38 @@ +varnishtest "H1 websocket upgrade response received just after a 1xx response" + +# When a 1xx interim response and the 101 response of a websocket upgrade are +# received at once, the websocket key must be searched in the 101 response, +# not in the interim one which is still present in the buffer. + +feature ignore_unknown_macro + +server s1 { + rxreq + expect req.http.upgrade == "websocket" + send "HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n" +} -start + +haproxy h1 -conf { + defaults + mode http + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + listen li1 + bind "fd@${li1}" + server s1 ${s1_addr}:${s1_port} +} -start + +client c1 -connect ${h1_li1_sock} { + txreq -req GET -url /chat \ + -hdr "Connection: upgrade" \ + -hdr "Upgrade: websocket" \ + -hdr "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \ + -hdr "Sec-WebSocket-Version: 13" + rxresp + expect resp.status == 100 + rxresp + expect resp.status == 101 + expect resp.http.sec-websocket-accept == "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" +} -run diff --git a/src/mux_h1.c b/src/mux_h1.c index e1f087912..605714c66 100644 --- a/src/mux_h1.c +++ b/src/mux_h1.c @@ -1951,18 +1951,21 @@ static int h1_search_websocket_key(struct h1s *h1s, struct h1m *h1m, struct htx struct ist n, v; int ws_key_found = 0; - blk = htx_get_head_blk(htx); // returns the SL that we skip - for (blk = htx_get_next_blk(htx, blk); blk; blk = htx_get_next_blk(htx, blk)) { + /* Loop backward on the message that was just parsed. because interim + * responses (1xx) may still be present before it. + */ + for (blk = htx_get_tail_blk(htx); blk; blk = htx_get_prev_blk(htx, blk)) { type = htx_get_blk_type(blk); - if (type != HTX_BLK_HDR) + if (type == HTX_BLK_REQ_SL || type == HTX_BLK_RES_SL) break; + if (type != HTX_BLK_HDR) + continue; n = htx_get_blk_name(htx, blk); v = htx_get_blk_value(htx, blk); /* Websocket key is base64 encoded of 16 bytes */ - if (isteqi(n, ist("sec-websocket-key")) && v.len == 24 && - !(h1m->flags & H1_MF_RESP)) { + if (!(h1m->flags & H1_MF_RESP) && isteqi(n, ist("sec-websocket-key")) && v.len == 24) { /* Copy the key on request side * we might need it if the server is using h2 and does * not provide the response @@ -1971,8 +1974,7 @@ static int h1_search_websocket_key(struct h1s *h1s, struct h1m *h1m, struct htx ws_key_found = 1; break; } - else if (isteqi(n, ist("sec-websocket-accept")) && - h1m->flags & H1_MF_RESP) { + else if ((h1m->flags & H1_MF_RESP) && isteqi(n, ist("sec-websocket-accept"))) { /* Need to verify the response key if the input was * generated by haproxy */