]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MEDIUM: mux-h2: don't send RST_STREAM for backend streams whose HEADERS were...
authorWilly Tarreau <w@1wt.eu>
Tue, 6 Oct 2026 21:22:06 +0000 (23:22 +0200)
committerWilly Tarreau <w@1wt.eu>
Mon, 9 Nov 2026 19:48:35 +0000 (20:48 +0100)
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.

reg-tests/http-messaging/protocol_upgrade.vtc
src/mux_h2.c

index 2fe65e7173e777fe0b7a7b4de8e7791bf8dc57d8..28cd665994e54ad3f33aeaeb912e9d6a57ca6a68 100644 (file)
@@ -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
index d1ce6351ff1be56661ef15251c4a99ce1a4cbf88..bca78bc85837ba7a7f1b271a49f5db34ef66d6be 100644 (file)
@@ -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;
        }