Cherry-picks for 10.2.0 RC (2026-08-04) - #13495
Draft
cmcfarlen wants to merge 8 commits into
Draft
Conversation
* 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
force-pushed
the
10.2.x-picks-20260804
branch
from
August 5, 2026 12:30
b2c1908 to
817c7df
Compare
Contributor
Author
|
[approve ci] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 -xin master merge order; every pick applied cleanly (no conflicts) and each diffstat matches the master commit.Local build clean; unit tests pass except the known macOS-local
test_jsonrpcserverflake.Draft to get a full CI run before fast-forwarding 10.2.x.