Skip to content

Cherry-picks for 10.2.0 RC (2026-08-04) - #13495

Merged
cmcfarlen merged 8 commits into
apache:10.2.xfrom
cmcfarlen:10.2.x-picks-20260804
Aug 5, 2026
Merged

Cherry-picks for 10.2.0 RC (2026-08-04)#13495
cmcfarlen merged 8 commits into
apache:10.2.xfrom
cmcfarlen:10.2.x-picks-20260804

Conversation

@cmcfarlen

Copy link
Copy Markdown
Contributor

Cherry-picks of PRs at "For v10.2.0" in the ATS v10.2.x project, ahead of the 10.2.0 release candidate.

All picked with git cherry-pick -x in master merge order; every pick applied cleanly (no conflicts) and each diffstat matches the master commit.

PR Title
#13465 Add QMux support (HTTP/3 over TLS/TCP)
#13426 header_rewrite: add POST_REMAP_HOOK support
#13486 header_rewrite: inherit default hook support
#13473 Fix certifier test permissions
#13488 Fix wrong variable in eventloop events max metric
#13480 Fix enabling per-server metrics that disables the outbound keep-alive minimum
#13410 Cache empty chunked responses
#13492 Track minimum-only origin connections

Local build clean; unit tests pass except the known macOS-local test_jsonrpcserver flake.

Draft to get a full CI run before fast-forwarding 10.2.x.

maskit and others added 8 commits August 5, 2026 07:29
* Add QMux support (HTTP/3 over TLS/TCP)

HTTP/3 requires UDP, which is blocked or degraded on many networks.
QMux carries QUIC stream multiplexing over a TLS/TCP connection so
HTTP/3 can be served where UDP is unavailable. Server side only.

The transport is abstracted behind the existing QUICConnection and
QUICStreamIO interfaces, so HTTP/3 session and application handling is
reused unchanged. QMux is offered via ALPN "h3qx-01" on TLS ports.

The two transports are now selected independently of the QUIC backend.
ENABLE_QUIC carries QUIC over UDP and defaults to on whenever a backend
is available; ENABLE_QMUX carries it over TLS/TCP and defaults to off.
Either can be enabled without the other, and QMux requires quiche built
with qmux support.

* Report QMux build support

AuTests need a stable feature flag to skip QMux coverage when ATS is
built without the optional transport.

This exposes TS_USE_QMUX through traffic_layout alongside the existing
QUIC and TLS feature flags.

* Add QMux Go client AuTest

QMux needs an interoperable client test to prove that HTTP/3 can run
over TLS/TCP and proxy multiple transactions with request and response
bodies.

This adds a class-based AuTest with a qmux-go client and Proxy Verifier
origin. The client sends three transactions on one session, verifies
forwarded headers and bodies, and checks a 300-kilobyte response byte
for byte. Compatibility shims cover qmux-go v0.2.0 wire gaps.

* Resume QMux reads across buffer blocks

A partial QMux record at the end of the 32 KB input buffer prevents
TLS from reading the rest of the record, stalling larger request bodies.

Set the input watermark to the maximum QMux record size so the buffer can
append a block and complete records that span block boundaries.

* Address copilot comments

* Reclaim QMux connection VIOs after Http3App construction

Http3App's constructor runs the generic ProxySession start-up
(HQSession::start()), which claims the netvc's read/write VIOs for
itself. Moving qmux_con->start() before that construction, to
address an earlier review comment about the app racing the
transport bridge, let that claim win instead of QMuxConnection's,
silently disabling QMux's connection-level I/O and crashing on the
first subsequent write.

Construct the app, reclaim the VIOs for QMuxConnection right after,
then start the app. This keeps the app from generating stream I/O
before the transport is wired up while ensuring QMuxConnection ends
up owning the VIOs it depends on.

* Flush qmux transport params before checking established streams

is_established() for qmux mode is qmux_transport_params_sent &&
qmux_transport_params_received. _handle_write() checked it before
_flush_quiche_output(), which is what can flip sent to true. On the
call where establishment completes this way, a stream already queued
(e.g. the HTTP/3 control stream) missed its flush window, and nothing
else was guaranteed to trigger another one -- if the peer waits on
that stream before sending anything further, both sides stall until
idle timeout.

Flush once before the streams check when not yet established, so a
transition to established within this call is visible to it.

* Default ENABLE_QMUX to AUTO when quiche has qmux support

ENABLE_QUICHE is a plain ON/OFF option with no AUTO state, so building
with quiche never turned QMux on by itself -- ENABLE_QMUX had its own
hardcoded OFF default regardless of whether the linked quiche was built
with qmux support. This was the one auto_option() in the QUIC/QMux
chain that didn't actually auto-detect anything, unlike
ENABLE_OPENSSL_QUIC's AUTO default.

quiche.h always declares quiche_config_enable_qmux() regardless of
whether the library was actually built with the qmux feature, so
detecting support requires a real compile-and-link check against
quiche::quiche, not a header-only one -- CheckQuicheHasQmux.cmake
mirrors CheckOpenSSLHasNativeQuic.cmake's shape for this reason.

* Remove ENABLE_OPENSSL_QUIC; fix premature QUIC backend status message

ENABLE_OPENSSL_QUIC gated a capability of the mandatory OpenSSL
dependency behind its own ON/OFF/AUTO option, unlike every other OpenSSL
capability check in this file (SSLLIB_IS_BORINGSSL, SSLLIB_HAS_QUIC_TLS_CBS,
etc.), which are plain detected variables with no option of their own.
Since OpenSSL is always linked regardless, and OpenSSL-native QUIC and
quiche are mutually exclusive by TLS-library requirement (quiche needs
BoringSSL or the TLS callback compat shim, neither of which implements
the upstream-OpenSSL-3.5+ native QUIC API), the flag never actually
selected between two live backends -- disabling it had the same effect
as disabling the QUIC transport outright via ENABLE_QUIC, just through
a separate, asymmetric path that left a misleading "Using OpenSSL
native QUIC" status line and no warning when the backend was flagged
available but nothing was configured to serve it.

TS_HAS_OPENSSL_QUIC is now set directly from the same detection logic,
folded into the other capability checks already living in this file.
The "Using ... QUIC transport" status message moves to after
auto_option(QUIC ...) decides TS_USE_QUIC, so it reflects what's
actually enabled rather than what's merely detected.

* Close QMux connections immediately on a fatal quiche_conn_recv() error

quiche_conn_recv() returning anything other than QUICHE_ERR_DONE means
quiche has already classified the received bytes as an unrecoverable
per-connection protocol violation and started its own internal
close/drain sequence internally (every non-Done error path in
recv_qmux() calls self.close() before returning) -- it is never used
to mean "incomplete record, wait for more bytes" in this quiche fork
(both incomplete-header and incomplete-record cases are mapped to
QUICHE_ERR_DONE explicitly).

_handle_read() previously only logged this case and left the
connection to be caught by the next scheduled quiche_conn_on_timeout()
tick, which notices via quiche_conn_is_closed(). That works, but
lingers for up to the connection's drain timeout doing nothing useful,
and leaves the now-unparseable bytes sitting in the read buffer for
that whole window. Calling close_quic_connection() immediately reaches
the same end state without the wait: quiche_conn_close() is a safe
no-op here since quiche already set its own close reason internally,
and the pending CLOSE frame gets flushed to the peer right away instead
of on the next natural write event.

---------

Co-authored-by: bneradt <bneradt@yahooinc.com>
(cherry picked from commit bac05da)
header_rewrite can attach rulesets to most transaction hooks, but not to
TS_HTTP_POST_REMAP_HOOK. For a global (plugin.config) configuration, that
leaves no hook that sees the remapped request before the cache lookup:

READ_REQUEST_HDR_HOOK / READ_REQUEST_PRE_REMAP_HOOK run before remapping,
so they only ever see the pristine request.
REMAP_PSEUDO_HOOK sees the remapped request before the lookup, but is valid
only in a remap context.
SEND_REQUEST_HDR_HOOK sees the remapped request, but fires after the lookup
and only when the request is forwarded to an origin. It cannot influence the
lookup, and it never runs on a cache hit.
That window is where anything feeding the cache lookup has to run. The cachekey
plugin registers TS_HTTP_POST_REMAP_HOOK for exactly this reason
(plugins/cachekey/plugin.cc:114): its remap-time path (TSRemapDoRemap)
covers the per-remap case, and a global instance needs the remapped request
before TSCacheUrlSet is consumed by the lookup.

This is the hook a planned header_rewrite cache-key operator needs, for the
same reason: setting the cache key is only meaningful between remapping and the
cache lookup, and SEND_REQUEST_HDR_HOOK is already too late.

This PR adds a POST_REMAP_HOOK hook condition and wires it end to end:

Recognize the POST_REMAP_HOOK keyword in the parser.
Handle the POST_REMAP event so rulesets run at that hook.
Gather the post-remap request headers for the hook.
Allow operators and conditions on the hook.
Covered by a parser unit test and an end-to-end autest.

(cherry picked from commit ae4e19e)
Operators intended for every hook maintained private copies of the
default allowlist. New hooks added to Statement could therefore remain
unavailable for plugin controls and transaction or session state,
causing otherwise valid configurations to be rejected.

This patch lets those operators inherit Statement's default allowlist
and extends the POST_REMAP AuTest to exercise a state operator. Future
default hooks will now be available without updating duplicate lists.

(cherry picked from commit e013fde)
Certifier tests fail in root-run CI because ATS cannot update the
copied serial file or certificate store. Local owner-run tests mask the
problem.

This problem is addressed in this patch by giving the unprivileged ATS
process the required access to the serial file and certificate store in
each certifier scenario.

(cherry picked from commit be113cd)
Slice::record_event_count() assigned the loop-execution count instead of
the event count, so proxy.process.eventloop.events.max.* has always
reported how many loops ran rather than the largest number of events
dispatched in a single loop.

(cherry picked from commit a2ff4ae)
… minimum (apache#13480)

* Fix enabling per-server metrics that disables the outbound keep-alive minimum

* int to auto from copilot

* Add autest

(cherry picked from commit bc0bca7)
Empty chunked responses can complete without starting a cache write VIO,
or can start with an unknown length that is later finalized at zero.
ATS treats both as empty unwritten entries and sends later requests back
to origin. AuTests can also cross a log-rolling boundary before checking
custom logs, producing an unrelated intermittent failure.

This patch starts zero-byte cache writes and recognizes successfully
closed write VIOs whose final length is zero. This preserves the
empty-document state while keeping header-only cache updates distinct.

This also disables log rolling for stale-response log assertions and
covers negative and successful empty responses.

Fixes: apache#11313
(cherry picked from commit d0119c4)
A per-server keep-alive minimum without a connection maximum or
per-server metrics created a tracker group but never reserved it.
The count remained zero, so idle origin sessions were retained
without limit and their eventual release reported invalid accounting.

Reserve the tracker whenever the keep-alive minimum needs it, and add
replay coverage that lets surplus pooled sessions expire while
preserving the configured minimum.

(cherry picked from commit 339614a)
@cmcfarlen
cmcfarlen force-pushed the 10.2.x-picks-20260804 branch from b2c1908 to 817c7df Compare August 5, 2026 12:30
@cmcfarlen

Copy link
Copy Markdown
Contributor Author

[approve ci]

@cmcfarlen

Copy link
Copy Markdown
Contributor Author

[approve ci autest 2]

@cmcfarlen
cmcfarlen merged commit 817c7df into apache:10.2.x Aug 5, 2026
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants