]> git.kaiwu.me - haproxy.git/commit
BUG/MEDIUM: mux-h2: also emit the HPACK dynamic table size update on requests
authorWilly Tarreau <w@1wt.eu>
Mon, 9 Nov 2026 17:53:21 +0000 (17:53 +0000)
committerWilly Tarreau <w@1wt.eu>
Mon, 9 Nov 2026 19:48:35 +0000 (20:48 +0100)
commite8eea22d0d0f1f9d96e5083a72d728c8fe5f2974
treea79f31eb6262656751fdce1757809822045f5c43
parent42885be079dfe3744dfbb3a8605b78a1f2783324
BUG/MEDIUM: mux-h2: also emit the HPACK dynamic table size update on requests

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), but only in
h2s_snd_fhdrs(), i.e. for responses sent to clients. H2_CF_SHTS_UPDATED is
also set on backend connections, but h2s_snd_bhdrs() never emits the
update. Indeed, by then the spec was still hesitant on this point and
we were not certain that this behavior would be kept, and even less
that it would be necessary at all for the backend side.

On the backend side this is almost certain to happen with servers which
advertise a lower header table size: the first request is usually sent
before the server's SETTINGS frame is received, and the next one is the
first header block after the change. Strict decoders (e.g. those based on
nghttp2 or python-h2) treat the missing update as a COMPRESSION_ERROR on
the first request sent after the server's SETTINGS was acknowledged,
which kills the connection.

This is reproducible with a python-h2 server announcing a lower header
table size: before the fix, the second request on the connection triggers
a COMPRESSION_ERROR ("Encoder did not shrink table size to within the
max") and fails, and a new connection is used for the third one; after
the fix, all requests are served over the same connection.

The update is now also emitted at the beginning of request HEADERS frames,
exactly as for responses on the frontend size, and the flags are only
updated once the frame is committed, as in h2s_snd_fhdrs().

Emitting a DTSU toward servers which did not expect one could be a concern
since some implementations were already met which do not like receiving
one (VTest in the case of the commit above). It is considered safe here
for the following reasons:

  - the update is only emitted after the server explicitly advertised
    SETTINGS_HEADER_TABLE_SIZE in its SETTINGS frame. Servers which do not
    advertise it will never see it, which is the same rule that has been
    applied to clients since 2.6.

  - nghttp2's encoder, hence curl, nghttpx, Apache's mod_proxy_http2 and
    every other client based on it, already does exactly this: on any
    SETTINGS_HEADER_TABLE_SIZE received, including one which does not
    change the value, it starts the next header block with an update
    (nghttp2_hd_deflate_change_table_size() unconditionally sets
    notify_table_size_change). This was verified on the wire with curl
    8.5.0 and nghttp2 1.59: a server advertising 4096 or 65536 receives
    an update to 4096 at the beginning of the next request. Thus any server
    advertising this setting is already routinely exposed to this opcode.

  - the only difference is the value: we always send zero since we don't
    use the dynamic table, while nghttp2 sends min(advertised, 4096). Zero
    is always valid since RFC7541#6.3 only requires the value not to
    exceed the advertised limit, and we've been sending this exact update
    to clients (including browsers) over the last 5 years.

This could be backported to all versions, but only if found necessary.
It would make sense to have it in 3.4 and 3.3 though.
src/mux_h2.c