From: Willy Tarreau Date: Tue, 6 Oct 2026 21:22:06 +0000 (+0200) Subject: BUG/MEDIUM: mux-h2: don't send RST_STREAM for backend streams whose HEADERS were... X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=42885be079dfe3744dfbb3a8605b78a1f2783324;p=haproxy.git BUG/MEDIUM: mux-h2: don't send RST_STREAM for backend streams whose HEADERS were not sent On the backend side, h2_snd_buf() assigns the stream ID before calling h2s_snd_bhdrs() to encode the HEADERS frame. If the encoding fails, the the HEADERS frame is never sent and the stream is put in error, but an RST_STREAM is sent for this ID. The same happens via h2_do_shutr()/ h2_do_shutw() when the stream is aborted while its HEADERS frame is still blocked. But for the server, this stream is still idle, and the H2 spec says that receiving an RST_STREAM on an idle stream must be treated as a connection error, so a compliant server will then close the connection with a GOAWAY(PROTOCOL_ERROR). Commit f065d0009 ("BUG/MEDIUM: mux-h2: Don't send RST_STREAM frame for streams with no ID") already addressed the case of streams without ID, which was the first manifestation of this situation. Let's also skip the RST_STREAM for backend streams which haven't sent their HEADERS frame yet. The protocol_upgrade reg-test, which expected the RST_STREAM on the idle stream for requests which cannot be converted, was updated accordingly. The issue has existed since outgoing streams were introduced in 1.9, so the fix should be backported to all supported versions. --- diff --git a/reg-tests/http-messaging/protocol_upgrade.vtc b/reg-tests/http-messaging/protocol_upgrade.vtc index 2fe65e717..28cd66599 100644 --- a/reg-tests/http-messaging/protocol_upgrade.vtc +++ b/reg-tests/http-messaging/protocol_upgrade.vtc @@ -86,9 +86,9 @@ server srv_h2_no_ws { expect settings.ack == true } -run - stream 1 { - rxrst - } -run + # the request cannot be converted, and no frame (in particular no + # RST_STREAM) must be sent on stream 1 which is still idle + # (RFC9113#6.4). } -start # http2 server without support for RFC8441 : settings announced with value 0 @@ -105,9 +105,9 @@ server srv_h2_no_ws2 { expect settings.ack == true } -run - stream 1 { - rxrst - } -run + # the request cannot be converted, and no frame (in particular no + # RST_STREAM) must be sent on stream 1 which is still idle + # (RFC9113#6.4). } -start # http2 server with support for RFC8441 by H1 client is sending a request with payload @@ -123,9 +123,9 @@ server srv_h2_no_post { expect settings.ack == true } -run - stream 1 { - rxrst - } -run + # the request cannot be converted, and no frame (in particular no + # RST_STREAM) must be sent on stream 1 which is still idle + # (RFC9113#6.4). } -start # http/1.1 server diff --git a/src/mux_h2.c b/src/mux_h2.c index d1ce6351f..bca78bc85 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -2668,8 +2668,15 @@ static int h2s_send_rst_stream(struct h2c *h2c, struct h2s *h2s) * RST_STREAM in response to a RST_STREAM frame. * * if h2s is not assigned yet (id == 0), don't send a RST_STREAM frame. + * Similarly, on the backend side, an ID may already have been assigned + * while the HEADERS frame was not sent (e.g. failed to be encoded or + * blocked), in which case the stream is still idle for the server, + * and RFC9113#6.4 says that an RST_STREAM on an idle stream is a + * connection error, which would kill all other streams. The ID will + * simply never be used (which implicitly closes it). */ - if ((h2s->id == 0) || (h2c->dsi == h2s->id && h2c->dft == H2_FT_RST_STREAM)) { + if ((h2s->id == 0) || (h2c->dsi == h2s->id && h2c->dft == H2_FT_RST_STREAM) || + ((h2c->flags & H2_CF_IS_BACK) && !(h2s->flags & H2_SF_HEADERS_SENT))) { ret = 1; goto ignore; }