]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MINOR: mux-h2: also emit the HPACK Dynamic Table Size Update before trailers
authorWilly Tarreau <w@1wt.eu>
Mon, 9 Nov 2026 19:14:45 +0000 (20:14 +0100)
committerWilly Tarreau <w@1wt.eu>
Mon, 9 Nov 2026 19:48:35 +0000 (20:48 +0100)
Commit 39a0a1e12 ("MEDIUM: h2/hpack: emit a Dynamic Table Size Update
after settings change") made the mux emit an HPACK Dynamic Table Size
Update at the beginning of the first header block following a change of
the peer's SETTINGS_HEADER_TABLE_SIZE (RFC7541#4.2). The previous commit
did the same for request HEADERS, but h2s_make_trailers() still never
emits it, on any side, so that if the first header block following the
change is a trailers block, a strict decoder will report a
COMPRESSION_ERROR and kill the connection.

It's quite unlikely to happen to be honest, because:
  - on the frontend, the client sends the SETTINGS frame before headers
    and has no real reason to change it afterwards ;
  - on the backend, it could theoretically happen but only on a large
    POST that receives a SETTINGS frame after the first HEADERS frame and
    acks it before the trailers HEADERS frame; it's not as if trailers to
    backends were that common anyway.

The update is now inserted before the encoded fields of the trailers when
at least one field is present. Otherwise an empty DATA frame is sent
instead of a HEADERS frame, and the update is left for the next header
block. The flags are only updated once the frame is committed, as in the
other functions.

This was verified on the backend with a server sending its SETTINGS frame
with a lower header table size after receiving a request's HEADERS frame:
the request's trailers now start with the update, which was not the case
before. The frontend case was only verified by code analysis, it uses the
same code.

Like previous patch, this could be backported to all stable versions, but
preferably only if found to be necessary. Let's just start with 3.3 and 3.4
only.

src/mux_h2.c

index 762d53f65f54a17a9be256652d6af2f3f1d0e55e..ee5ce7a909d0cd1a51ae88729dbacb16fe02c103 100644 (file)
@@ -8020,6 +8020,23 @@ static size_t h2s_make_trailers(struct h2s *h2s, struct htx *htx)
                }
        }
 
+       if (outbuf.data > 9 &&
+           (h2c->flags & (H2_CF_SHTS_UPDATED|H2_CF_DTSU_EMITTED)) == H2_CF_SHTS_UPDATED) {
+               /* SETTINGS_HEADER_TABLE_SIZE changed, we must send an HPACK
+                * dynamic table size update at the beginning of the next
+                * header block (see h2s_snd_fhdrs()). It's inserted before the
+                * encoded fields.
+                */
+               if (b_room(&outbuf) < 1) {
+                       if (b_space_wraps(mbuf))
+                               goto realign_again;
+                       goto full;
+               }
+               memmove(outbuf.area + 10, outbuf.area + 9, outbuf.data - 9);
+               outbuf.area[9] = 0x20; // HPACK DTSU 0 bytes
+               outbuf.data++;
+       }
+
        if (outbuf.data == 9) {
                /* here we have a problem, we have nothing to emit (either we
                 * received an empty trailers block followed or we removed its
@@ -8049,6 +8066,12 @@ static size_t h2s_make_trailers(struct h2s *h2s, struct htx *htx)
        h2c->flags |= H2_CF_MBUF_HAS_DATA;
        h2s->flags |= H2_SF_ES_SENT;
 
+       if (outbuf.data > 9 && (h2c->flags & H2_CF_SHTS_UPDATED)) {
+               /* was sent above (only in a HEADERS frame) */
+               h2c->flags |= H2_CF_DTSU_EMITTED;
+               h2c->flags &= ~H2_CF_SHTS_UPDATED;
+       }
+
        if (h2s->st == H2_SS_OPEN)
                h2s->st = H2_SS_HLOC;
        else