| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
Share HTTP accept properties (#13514) Each HTTP protocol acceptor keeps a separate copy of proxy-port properties, so adding or consuming one requires protocol-specific plumbing and can leave newer protocols without configured defaults. This patch introduces a common HTTP acceptor base that shares one immutable property set per proxy port and retains the source HttpProxyPort. Sessions track that acceptor, while transactions copy mutable outbound settings at transaction start so overrides stay isolated. Handoff paths keep their acceptors alive for the resulting session. Fixes: #3427 | 23 天前 | |
clang-format v18 + modified configs (#11285) | 2 年前 | |
Share HTTP accept properties (#13514) Each HTTP protocol acceptor keeps a separate copy of proxy-port properties, so adding or consuming one requires protocol-specific plumbing and can leave newer protocols without configured defaults. This patch introduces a common HTTP acceptor base that shares one immutable property set per proxy port and retains the source HttpProxyPort. Sessions track that acceptor, while transactions copy mutable outbound settings at transaction start so overrides stay isolated. Handoff paths keep their acceptors alive for the resulting session. Fixes: #3427 | 23 天前 | |
clang-format v18 + modified configs (#11285) | 2 年前 | |
clang-format v18 + modified configs (#11285) | 2 年前 | |
Unified include paths (#10671) This PR does the following: * Updates the common include path used by all modules. * The #include lines in code are updated to the relative path to the updated include paths or to where the including files are. * Most include_directories are cleaned up in cmake files. The common include path are the following: # Common includes for everyone include_directories(${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/include) Also, since we are here,moved the private header (P_xxx) and test headers from include/records back to the src/records. | 2 年前 | |
Contention-aware per-event send budget for QUICStream (#13554) * Make QUICStream per-event send budget contention-aware A single stream on a QUIC connection could only send 16KB per write event, a fixed fairness quantum that protects sibling streams from being starved. That protection has a real cost when a connection has little or no contention: a single-stream, multi-megabyte response needs dozens of separate event-loop round trips to drain, each paying real per-call overhead, even though there's nobody else to be fair to. This scales the per-event budget by how many streams were writable in the previous event on the connection: down toward the original 16KB floor under real contention, up toward a new 256KB ceiling when a stream has the write path to itself. The count is remembered rather than recomputed each event, since quiche's writable-stream iterator is one-shot with no free count, and recomputing it would cost as much as the round trips being eliminated. A brief one-event lag when contention changes abruptly is bounded and self-correcting, never a starvation risk. Benchmarking a 1MB single-stream response over QUIC (h2load, -m 1) went from 812.7 MB/s to 1553.0 MB/s; a small (8KB) object, already under one turn's floor, was unaffected, confirming the fix targets exactly the large single-stream case without disturbing anything else. * Eliminate per-request allocations in the HTTP/3 frame-handling path Http3Transaction is constructed once per request and was allocating and freeing five separate objects on the heap for its own private, fixed-lifetime framers/handlers (header framer, data framer, protocol enforcer, header/data VIO adaptors) -- these become plain value members instead, owned directly by the transaction. Http3FrameHandler::interests() ran on every handler registration and constructed and returned a fresh std::vector by value each time, even though the interest set is fixed per handler type; it now returns a reference to a function-local static vector built once. Http3FrameDispatcher's per-type handler list moves from a std::vector (heap-allocated on first push) to a small fixed-size inline array sized for the real maximum handler count. Http3HeadersFrame gains a constructor that shares the caller's IOBufferReader via a clone instead of copying the header block into a freshly malloc'd buffer, used on the receive path where the block is already sitting in an IOBuffer. Unrelated one-line fix: QUICNetProcessor was checking dbg_ctl_vv_quiche with tag_on(), which only tests whether the debug tag pattern matches and ignores whether debug output is globally enabled -- switched to on() so a coincidental tag match doesn't install quiche's trace-level logger unconditionally. * Apply contention-aware send budget to QMux's write path too QMuxConnection::_handle_write_streams() is the third of the three QUICStream::send_data() call sites, alongside QUICNetVConnection and OpenSSLQUICNetVConnection -- it was left on the fixed 16KB-per-event cap when the other two call sites moved to the contention-aware budget, since QUICStream::compute_fair_send_budget() didn't exist on the branch QMux's source lives on until this rebase brought both together. Same pattern as QUICNetVConnection::_handle_write_ready(): a _last_writable_stream_count member remembers the previous event's writable-stream count to size this event's budget, no extra allocation or extra quiche_conn_writable() call versus today's code. * Remove dead HTTP/3 frame construction paths Http3UnknownFrame was never instantiated anywhere -- no allocator for it exists (unlike every other frame type), and Http3FrameFactory::create()'s fallback for an unrecognized frame type constructs a plain Http3Frame directly instead. The whole subclass, both its constructors, and its to_io_buffer_block() override were orphaned code, predating this branch. Http3HeadersFrame's ats_unique_buf constructor and the create_headers_frame(const uint8_t*, size_t) factory overload that built it were also unreachable in production: Http3HeaderFramer.cc, their only real caller, has used the IOBufferReader*-based overload since this file's original introduction. The one place still exercising the owned-copy constructor was a unit test constructing it directly; removed along with it since the sibling "via factory" section already covers identical serialization output through the live construction path. * Take Http3HeadersFrame's send-path reader by reference, matching Data Http3DataFrame's send constructor takes IOBufferReader&, with its factory dereferencing a pointer to call it. Http3HeadersFrame's send constructor -- added when the reader-cloning path replaced the old copy-into-owned-buffer one -- took IOBufferReader* directly instead, with no actual need for pointer semantics: the constructor only calls clone() once, which works identically through a reference. Aligned it to the same &-parameter/factory-dereferences convention already used by every other IOBufferReader-taking constructor in this file (both frame types' receive-path constructors, and Data's send constructor). * Fix multi-backend build break and defensive-init gap from review _last_writable_stream_count was declared unconditionally but only read/written by the quiche write path, breaking the OpenSSL-native-QUIC build under -Werror=unused-private-field. Http3FrameDispatcher's new _handlers array lacked the same zero-initializer as its paired count array, a latent fragility if the count-gated read path is ever changed. Found in a deep review pass across both build backends. * Fix use-after-free in HEADERS frame test The test freed the source MIOBuffer while the frame it produced was still alive, so the frame's destructor deallocated a reader into already-freed memory when it went out of scope. Reset the frame before freeing the buffer, per the constructor's own documented lifetime requirement. * Cap carried-over pending stream data to the event's send budget A pending send block left over from a partial write in one event could be sized under that event's (larger) budget. If contention increased before the next event drained it, the stream would submit the whole remaining block regardless of the new, smaller budget, letting it exceed the fairness cap other streams are relying on. * Fix stale contention count driving per-event QUIC send budget compute_fair_send_budget() divides a per-event pool across a connection's writable streams, but the divisor was always the previous event's count, not the current one. That meant a connection's very first write-ready event (no previous count) or any event right after contention swung sharply up assumed near-zero contention, handing every real writable stream the full pool. Both _handle_write_ready() and _handle_write_streams() now count the actual writable streams before sizing that event's budget instead of carrying an estimate across events. quiche's writable() is a pure, side-effect-free snapshot, so the extra pass costs one bounded iterator drain, not a correctness or ordering risk. Also rename MAX_STREAM_SEND_BYTES_PER_EVENT to MAX_CONNECTION_SEND_BYTES_PER_EVENT: despite the name, it was never a per-stream ceiling, only the connection-wide pool compute_fair_send_ budget() divides among streams. The old name misdescribed its own role and made the sizing logic harder to reason about on review. | 16 天前 | |
Contention-aware per-event send budget for QUICStream (#13554) * Make QUICStream per-event send budget contention-aware A single stream on a QUIC connection could only send 16KB per write event, a fixed fairness quantum that protects sibling streams from being starved. That protection has a real cost when a connection has little or no contention: a single-stream, multi-megabyte response needs dozens of separate event-loop round trips to drain, each paying real per-call overhead, even though there's nobody else to be fair to. This scales the per-event budget by how many streams were writable in the previous event on the connection: down toward the original 16KB floor under real contention, up toward a new 256KB ceiling when a stream has the write path to itself. The count is remembered rather than recomputed each event, since quiche's writable-stream iterator is one-shot with no free count, and recomputing it would cost as much as the round trips being eliminated. A brief one-event lag when contention changes abruptly is bounded and self-correcting, never a starvation risk. Benchmarking a 1MB single-stream response over QUIC (h2load, -m 1) went from 812.7 MB/s to 1553.0 MB/s; a small (8KB) object, already under one turn's floor, was unaffected, confirming the fix targets exactly the large single-stream case without disturbing anything else. * Eliminate per-request allocations in the HTTP/3 frame-handling path Http3Transaction is constructed once per request and was allocating and freeing five separate objects on the heap for its own private, fixed-lifetime framers/handlers (header framer, data framer, protocol enforcer, header/data VIO adaptors) -- these become plain value members instead, owned directly by the transaction. Http3FrameHandler::interests() ran on every handler registration and constructed and returned a fresh std::vector by value each time, even though the interest set is fixed per handler type; it now returns a reference to a function-local static vector built once. Http3FrameDispatcher's per-type handler list moves from a std::vector (heap-allocated on first push) to a small fixed-size inline array sized for the real maximum handler count. Http3HeadersFrame gains a constructor that shares the caller's IOBufferReader via a clone instead of copying the header block into a freshly malloc'd buffer, used on the receive path where the block is already sitting in an IOBuffer. Unrelated one-line fix: QUICNetProcessor was checking dbg_ctl_vv_quiche with tag_on(), which only tests whether the debug tag pattern matches and ignores whether debug output is globally enabled -- switched to on() so a coincidental tag match doesn't install quiche's trace-level logger unconditionally. * Apply contention-aware send budget to QMux's write path too QMuxConnection::_handle_write_streams() is the third of the three QUICStream::send_data() call sites, alongside QUICNetVConnection and OpenSSLQUICNetVConnection -- it was left on the fixed 16KB-per-event cap when the other two call sites moved to the contention-aware budget, since QUICStream::compute_fair_send_budget() didn't exist on the branch QMux's source lives on until this rebase brought both together. Same pattern as QUICNetVConnection::_handle_write_ready(): a _last_writable_stream_count member remembers the previous event's writable-stream count to size this event's budget, no extra allocation or extra quiche_conn_writable() call versus today's code. * Remove dead HTTP/3 frame construction paths Http3UnknownFrame was never instantiated anywhere -- no allocator for it exists (unlike every other frame type), and Http3FrameFactory::create()'s fallback for an unrecognized frame type constructs a plain Http3Frame directly instead. The whole subclass, both its constructors, and its to_io_buffer_block() override were orphaned code, predating this branch. Http3HeadersFrame's ats_unique_buf constructor and the create_headers_frame(const uint8_t*, size_t) factory overload that built it were also unreachable in production: Http3HeaderFramer.cc, their only real caller, has used the IOBufferReader*-based overload since this file's original introduction. The one place still exercising the owned-copy constructor was a unit test constructing it directly; removed along with it since the sibling "via factory" section already covers identical serialization output through the live construction path. * Take Http3HeadersFrame's send-path reader by reference, matching Data Http3DataFrame's send constructor takes IOBufferReader&, with its factory dereferencing a pointer to call it. Http3HeadersFrame's send constructor -- added when the reader-cloning path replaced the old copy-into-owned-buffer one -- took IOBufferReader* directly instead, with no actual need for pointer semantics: the constructor only calls clone() once, which works identically through a reference. Aligned it to the same &-parameter/factory-dereferences convention already used by every other IOBufferReader-taking constructor in this file (both frame types' receive-path constructors, and Data's send constructor). * Fix multi-backend build break and defensive-init gap from review _last_writable_stream_count was declared unconditionally but only read/written by the quiche write path, breaking the OpenSSL-native-QUIC build under -Werror=unused-private-field. Http3FrameDispatcher's new _handlers array lacked the same zero-initializer as its paired count array, a latent fragility if the count-gated read path is ever changed. Found in a deep review pass across both build backends. * Fix use-after-free in HEADERS frame test The test freed the source MIOBuffer while the frame it produced was still alive, so the frame's destructor deallocated a reader into already-freed memory when it went out of scope. Reset the frame before freeing the buffer, per the constructor's own documented lifetime requirement. * Cap carried-over pending stream data to the event's send budget A pending send block left over from a partial write in one event could be sized under that event's (larger) budget. If contention increased before the next event drained it, the stream would submit the whole remaining block regardless of the new, smaller budget, letting it exceed the fairness cap other streams are relying on. * Fix stale contention count driving per-event QUIC send budget compute_fair_send_budget() divides a per-event pool across a connection's writable streams, but the divisor was always the previous event's count, not the current one. That meant a connection's very first write-ready event (no previous count) or any event right after contention swung sharply up assumed near-zero contention, handing every real writable stream the full pool. Both _handle_write_ready() and _handle_write_streams() now count the actual writable streams before sizing that event's budget instead of carrying an estimate across events. quiche's writable() is a pure, side-effect-free snapshot, so the extra pass costs one bounded iterator drain, not a correctness or ordering risk. Also rename MAX_STREAM_SEND_BYTES_PER_EVENT to MAX_CONNECTION_SEND_BYTES_PER_EVENT: despite the name, it was never a per-stream ceiling, only the connection-wide pool compute_fair_send_ budget() divides among streams. The old name misdescribed its own role and made the sizing logic harder to reason about on review. | 16 天前 | |
Contention-aware per-event send budget for QUICStream (#13554) * Make QUICStream per-event send budget contention-aware A single stream on a QUIC connection could only send 16KB per write event, a fixed fairness quantum that protects sibling streams from being starved. That protection has a real cost when a connection has little or no contention: a single-stream, multi-megabyte response needs dozens of separate event-loop round trips to drain, each paying real per-call overhead, even though there's nobody else to be fair to. This scales the per-event budget by how many streams were writable in the previous event on the connection: down toward the original 16KB floor under real contention, up toward a new 256KB ceiling when a stream has the write path to itself. The count is remembered rather than recomputed each event, since quiche's writable-stream iterator is one-shot with no free count, and recomputing it would cost as much as the round trips being eliminated. A brief one-event lag when contention changes abruptly is bounded and self-correcting, never a starvation risk. Benchmarking a 1MB single-stream response over QUIC (h2load, -m 1) went from 812.7 MB/s to 1553.0 MB/s; a small (8KB) object, already under one turn's floor, was unaffected, confirming the fix targets exactly the large single-stream case without disturbing anything else. * Eliminate per-request allocations in the HTTP/3 frame-handling path Http3Transaction is constructed once per request and was allocating and freeing five separate objects on the heap for its own private, fixed-lifetime framers/handlers (header framer, data framer, protocol enforcer, header/data VIO adaptors) -- these become plain value members instead, owned directly by the transaction. Http3FrameHandler::interests() ran on every handler registration and constructed and returned a fresh std::vector by value each time, even though the interest set is fixed per handler type; it now returns a reference to a function-local static vector built once. Http3FrameDispatcher's per-type handler list moves from a std::vector (heap-allocated on first push) to a small fixed-size inline array sized for the real maximum handler count. Http3HeadersFrame gains a constructor that shares the caller's IOBufferReader via a clone instead of copying the header block into a freshly malloc'd buffer, used on the receive path where the block is already sitting in an IOBuffer. Unrelated one-line fix: QUICNetProcessor was checking dbg_ctl_vv_quiche with tag_on(), which only tests whether the debug tag pattern matches and ignores whether debug output is globally enabled -- switched to on() so a coincidental tag match doesn't install quiche's trace-level logger unconditionally. * Apply contention-aware send budget to QMux's write path too QMuxConnection::_handle_write_streams() is the third of the three QUICStream::send_data() call sites, alongside QUICNetVConnection and OpenSSLQUICNetVConnection -- it was left on the fixed 16KB-per-event cap when the other two call sites moved to the contention-aware budget, since QUICStream::compute_fair_send_budget() didn't exist on the branch QMux's source lives on until this rebase brought both together. Same pattern as QUICNetVConnection::_handle_write_ready(): a _last_writable_stream_count member remembers the previous event's writable-stream count to size this event's budget, no extra allocation or extra quiche_conn_writable() call versus today's code. * Remove dead HTTP/3 frame construction paths Http3UnknownFrame was never instantiated anywhere -- no allocator for it exists (unlike every other frame type), and Http3FrameFactory::create()'s fallback for an unrecognized frame type constructs a plain Http3Frame directly instead. The whole subclass, both its constructors, and its to_io_buffer_block() override were orphaned code, predating this branch. Http3HeadersFrame's ats_unique_buf constructor and the create_headers_frame(const uint8_t*, size_t) factory overload that built it were also unreachable in production: Http3HeaderFramer.cc, their only real caller, has used the IOBufferReader*-based overload since this file's original introduction. The one place still exercising the owned-copy constructor was a unit test constructing it directly; removed along with it since the sibling "via factory" section already covers identical serialization output through the live construction path. * Take Http3HeadersFrame's send-path reader by reference, matching Data Http3DataFrame's send constructor takes IOBufferReader&, with its factory dereferencing a pointer to call it. Http3HeadersFrame's send constructor -- added when the reader-cloning path replaced the old copy-into-owned-buffer one -- took IOBufferReader* directly instead, with no actual need for pointer semantics: the constructor only calls clone() once, which works identically through a reference. Aligned it to the same &-parameter/factory-dereferences convention already used by every other IOBufferReader-taking constructor in this file (both frame types' receive-path constructors, and Data's send constructor). * Fix multi-backend build break and defensive-init gap from review _last_writable_stream_count was declared unconditionally but only read/written by the quiche write path, breaking the OpenSSL-native-QUIC build under -Werror=unused-private-field. Http3FrameDispatcher's new _handlers array lacked the same zero-initializer as its paired count array, a latent fragility if the count-gated read path is ever changed. Found in a deep review pass across both build backends. * Fix use-after-free in HEADERS frame test The test freed the source MIOBuffer while the frame it produced was still alive, so the frame's destructor deallocated a reader into already-freed memory when it went out of scope. Reset the frame before freeing the buffer, per the constructor's own documented lifetime requirement. * Cap carried-over pending stream data to the event's send budget A pending send block left over from a partial write in one event could be sized under that event's (larger) budget. If contention increased before the next event drained it, the stream would submit the whole remaining block regardless of the new, smaller budget, letting it exceed the fairness cap other streams are relying on. * Fix stale contention count driving per-event QUIC send budget compute_fair_send_budget() divides a per-event pool across a connection's writable streams, but the divisor was always the previous event's count, not the current one. That meant a connection's very first write-ready event (no previous count) or any event right after contention swung sharply up assumed near-zero contention, handing every real writable stream the full pool. Both _handle_write_ready() and _handle_write_streams() now count the actual writable streams before sizing that event's budget instead of carrying an estimate across events. quiche's writable() is a pure, side-effect-free snapshot, so the extra pass costs one bounded iterator drain, not a correctness or ordering risk. Also rename MAX_STREAM_SEND_BYTES_PER_EVENT to MAX_CONNECTION_SEND_BYTES_PER_EVENT: despite the name, it was never a per-stream ceiling, only the connection-wide pool compute_fair_send_ budget() divides among streams. The old name misdescribed its own role and made the sizing logic harder to reason about on review. | 16 天前 | |
Contention-aware per-event send budget for QUICStream (#13554) * Make QUICStream per-event send budget contention-aware A single stream on a QUIC connection could only send 16KB per write event, a fixed fairness quantum that protects sibling streams from being starved. That protection has a real cost when a connection has little or no contention: a single-stream, multi-megabyte response needs dozens of separate event-loop round trips to drain, each paying real per-call overhead, even though there's nobody else to be fair to. This scales the per-event budget by how many streams were writable in the previous event on the connection: down toward the original 16KB floor under real contention, up toward a new 256KB ceiling when a stream has the write path to itself. The count is remembered rather than recomputed each event, since quiche's writable-stream iterator is one-shot with no free count, and recomputing it would cost as much as the round trips being eliminated. A brief one-event lag when contention changes abruptly is bounded and self-correcting, never a starvation risk. Benchmarking a 1MB single-stream response over QUIC (h2load, -m 1) went from 812.7 MB/s to 1553.0 MB/s; a small (8KB) object, already under one turn's floor, was unaffected, confirming the fix targets exactly the large single-stream case without disturbing anything else. * Eliminate per-request allocations in the HTTP/3 frame-handling path Http3Transaction is constructed once per request and was allocating and freeing five separate objects on the heap for its own private, fixed-lifetime framers/handlers (header framer, data framer, protocol enforcer, header/data VIO adaptors) -- these become plain value members instead, owned directly by the transaction. Http3FrameHandler::interests() ran on every handler registration and constructed and returned a fresh std::vector by value each time, even though the interest set is fixed per handler type; it now returns a reference to a function-local static vector built once. Http3FrameDispatcher's per-type handler list moves from a std::vector (heap-allocated on first push) to a small fixed-size inline array sized for the real maximum handler count. Http3HeadersFrame gains a constructor that shares the caller's IOBufferReader via a clone instead of copying the header block into a freshly malloc'd buffer, used on the receive path where the block is already sitting in an IOBuffer. Unrelated one-line fix: QUICNetProcessor was checking dbg_ctl_vv_quiche with tag_on(), which only tests whether the debug tag pattern matches and ignores whether debug output is globally enabled -- switched to on() so a coincidental tag match doesn't install quiche's trace-level logger unconditionally. * Apply contention-aware send budget to QMux's write path too QMuxConnection::_handle_write_streams() is the third of the three QUICStream::send_data() call sites, alongside QUICNetVConnection and OpenSSLQUICNetVConnection -- it was left on the fixed 16KB-per-event cap when the other two call sites moved to the contention-aware budget, since QUICStream::compute_fair_send_budget() didn't exist on the branch QMux's source lives on until this rebase brought both together. Same pattern as QUICNetVConnection::_handle_write_ready(): a _last_writable_stream_count member remembers the previous event's writable-stream count to size this event's budget, no extra allocation or extra quiche_conn_writable() call versus today's code. * Remove dead HTTP/3 frame construction paths Http3UnknownFrame was never instantiated anywhere -- no allocator for it exists (unlike every other frame type), and Http3FrameFactory::create()'s fallback for an unrecognized frame type constructs a plain Http3Frame directly instead. The whole subclass, both its constructors, and its to_io_buffer_block() override were orphaned code, predating this branch. Http3HeadersFrame's ats_unique_buf constructor and the create_headers_frame(const uint8_t*, size_t) factory overload that built it were also unreachable in production: Http3HeaderFramer.cc, their only real caller, has used the IOBufferReader*-based overload since this file's original introduction. The one place still exercising the owned-copy constructor was a unit test constructing it directly; removed along with it since the sibling "via factory" section already covers identical serialization output through the live construction path. * Take Http3HeadersFrame's send-path reader by reference, matching Data Http3DataFrame's send constructor takes IOBufferReader&, with its factory dereferencing a pointer to call it. Http3HeadersFrame's send constructor -- added when the reader-cloning path replaced the old copy-into-owned-buffer one -- took IOBufferReader* directly instead, with no actual need for pointer semantics: the constructor only calls clone() once, which works identically through a reference. Aligned it to the same &-parameter/factory-dereferences convention already used by every other IOBufferReader-taking constructor in this file (both frame types' receive-path constructors, and Data's send constructor). * Fix multi-backend build break and defensive-init gap from review _last_writable_stream_count was declared unconditionally but only read/written by the quiche write path, breaking the OpenSSL-native-QUIC build under -Werror=unused-private-field. Http3FrameDispatcher's new _handlers array lacked the same zero-initializer as its paired count array, a latent fragility if the count-gated read path is ever changed. Found in a deep review pass across both build backends. * Fix use-after-free in HEADERS frame test The test freed the source MIOBuffer while the frame it produced was still alive, so the frame's destructor deallocated a reader into already-freed memory when it went out of scope. Reset the frame before freeing the buffer, per the constructor's own documented lifetime requirement. * Cap carried-over pending stream data to the event's send budget A pending send block left over from a partial write in one event could be sized under that event's (larger) budget. If contention increased before the next event drained it, the stream would submit the whole remaining block regardless of the new, smaller budget, letting it exceed the fairness cap other streams are relying on. * Fix stale contention count driving per-event QUIC send budget compute_fair_send_budget() divides a per-event pool across a connection's writable streams, but the divisor was always the previous event's count, not the current one. That meant a connection's very first write-ready event (no previous count) or any event right after contention swung sharply up assumed near-zero contention, handing every real writable stream the full pool. Both _handle_write_ready() and _handle_write_streams() now count the actual writable streams before sizing that event's budget instead of carrying an estimate across events. quiche's writable() is a pure, side-effect-free snapshot, so the extra pass costs one bounded iterator drain, not a correctness or ordering risk. Also rename MAX_STREAM_SEND_BYTES_PER_EVENT to MAX_CONNECTION_SEND_BYTES_PER_EVENT: despite the name, it was never a per-stream ceiling, only the connection-wide pool compute_fair_send_ budget() divides among streams. The old name misdescribed its own role and made the sizing logic harder to reason about on review. | 16 天前 | |
clang-format v18 + modified configs (#11285) | 2 年前 | |
Contention-aware per-event send budget for QUICStream (#13554) * Make QUICStream per-event send budget contention-aware A single stream on a QUIC connection could only send 16KB per write event, a fixed fairness quantum that protects sibling streams from being starved. That protection has a real cost when a connection has little or no contention: a single-stream, multi-megabyte response needs dozens of separate event-loop round trips to drain, each paying real per-call overhead, even though there's nobody else to be fair to. This scales the per-event budget by how many streams were writable in the previous event on the connection: down toward the original 16KB floor under real contention, up toward a new 256KB ceiling when a stream has the write path to itself. The count is remembered rather than recomputed each event, since quiche's writable-stream iterator is one-shot with no free count, and recomputing it would cost as much as the round trips being eliminated. A brief one-event lag when contention changes abruptly is bounded and self-correcting, never a starvation risk. Benchmarking a 1MB single-stream response over QUIC (h2load, -m 1) went from 812.7 MB/s to 1553.0 MB/s; a small (8KB) object, already under one turn's floor, was unaffected, confirming the fix targets exactly the large single-stream case without disturbing anything else. * Eliminate per-request allocations in the HTTP/3 frame-handling path Http3Transaction is constructed once per request and was allocating and freeing five separate objects on the heap for its own private, fixed-lifetime framers/handlers (header framer, data framer, protocol enforcer, header/data VIO adaptors) -- these become plain value members instead, owned directly by the transaction. Http3FrameHandler::interests() ran on every handler registration and constructed and returned a fresh std::vector by value each time, even though the interest set is fixed per handler type; it now returns a reference to a function-local static vector built once. Http3FrameDispatcher's per-type handler list moves from a std::vector (heap-allocated on first push) to a small fixed-size inline array sized for the real maximum handler count. Http3HeadersFrame gains a constructor that shares the caller's IOBufferReader via a clone instead of copying the header block into a freshly malloc'd buffer, used on the receive path where the block is already sitting in an IOBuffer. Unrelated one-line fix: QUICNetProcessor was checking dbg_ctl_vv_quiche with tag_on(), which only tests whether the debug tag pattern matches and ignores whether debug output is globally enabled -- switched to on() so a coincidental tag match doesn't install quiche's trace-level logger unconditionally. * Apply contention-aware send budget to QMux's write path too QMuxConnection::_handle_write_streams() is the third of the three QUICStream::send_data() call sites, alongside QUICNetVConnection and OpenSSLQUICNetVConnection -- it was left on the fixed 16KB-per-event cap when the other two call sites moved to the contention-aware budget, since QUICStream::compute_fair_send_budget() didn't exist on the branch QMux's source lives on until this rebase brought both together. Same pattern as QUICNetVConnection::_handle_write_ready(): a _last_writable_stream_count member remembers the previous event's writable-stream count to size this event's budget, no extra allocation or extra quiche_conn_writable() call versus today's code. * Remove dead HTTP/3 frame construction paths Http3UnknownFrame was never instantiated anywhere -- no allocator for it exists (unlike every other frame type), and Http3FrameFactory::create()'s fallback for an unrecognized frame type constructs a plain Http3Frame directly instead. The whole subclass, both its constructors, and its to_io_buffer_block() override were orphaned code, predating this branch. Http3HeadersFrame's ats_unique_buf constructor and the create_headers_frame(const uint8_t*, size_t) factory overload that built it were also unreachable in production: Http3HeaderFramer.cc, their only real caller, has used the IOBufferReader*-based overload since this file's original introduction. The one place still exercising the owned-copy constructor was a unit test constructing it directly; removed along with it since the sibling "via factory" section already covers identical serialization output through the live construction path. * Take Http3HeadersFrame's send-path reader by reference, matching Data Http3DataFrame's send constructor takes IOBufferReader&, with its factory dereferencing a pointer to call it. Http3HeadersFrame's send constructor -- added when the reader-cloning path replaced the old copy-into-owned-buffer one -- took IOBufferReader* directly instead, with no actual need for pointer semantics: the constructor only calls clone() once, which works identically through a reference. Aligned it to the same &-parameter/factory-dereferences convention already used by every other IOBufferReader-taking constructor in this file (both frame types' receive-path constructors, and Data's send constructor). * Fix multi-backend build break and defensive-init gap from review _last_writable_stream_count was declared unconditionally but only read/written by the quiche write path, breaking the OpenSSL-native-QUIC build under -Werror=unused-private-field. Http3FrameDispatcher's new _handlers array lacked the same zero-initializer as its paired count array, a latent fragility if the count-gated read path is ever changed. Found in a deep review pass across both build backends. * Fix use-after-free in HEADERS frame test The test freed the source MIOBuffer while the frame it produced was still alive, so the frame's destructor deallocated a reader into already-freed memory when it went out of scope. Reset the frame before freeing the buffer, per the constructor's own documented lifetime requirement. * Cap carried-over pending stream data to the event's send budget A pending send block left over from a partial write in one event could be sized under that event's (larger) budget. If contention increased before the next event drained it, the stream would submit the whole remaining block regardless of the new, smaller budget, letting it exceed the fairness cap other streams are relying on. * Fix stale contention count driving per-event QUIC send budget compute_fair_send_budget() divides a per-event pool across a connection's writable streams, but the divisor was always the previous event's count, not the current one. That meant a connection's very first write-ready event (no previous count) or any event right after contention swung sharply up assumed near-zero contention, handing every real writable stream the full pool. Both _handle_write_ready() and _handle_write_streams() now count the actual writable streams before sizing that event's budget instead of carrying an estimate across events. quiche's writable() is a pure, side-effect-free snapshot, so the extra pass costs one bounded iterator drain, not a correctness or ordering risk. Also rename MAX_STREAM_SEND_BYTES_PER_EVENT to MAX_CONNECTION_SEND_BYTES_PER_EVENT: despite the name, it was never a per-stream ceiling, only the connection-wide pool compute_fair_send_ budget() divides among streams. The old name misdescribed its own role and made the sizing logic harder to reason about on review. | 16 天前 | |
HTTP/3 support via OpenSSL 3.5 (#13186) * HTTP/3 via OpenSSL 3.5 + quiche Fedora now ships OpenSSL 3.5 with the third-party QUIC TLS callback API, but quiche still links against the older quictls/BoringSSL symbols. ATS therefore could not use the system OpenSSL library for downstream HTTP/3 without dragging in a different TLS stack. This adds CMake detection for the OpenSSL callback API and provides a private compatibility layer that maps quiche's legacy hooks onto SSL_set_quic_tls_cbs. This requires static quiche in that mode so ATS resolves the shim symbols locally and links the final binaries against the system OpenSSL libraries. This also relaxes verifier-only HTTP/3 AuTest gates that do not execute curl, so those tests can run when ATS has QUIC support but the installed curl lacks HTTP/3. Unknown unidirectional HTTP/3 stream types and aliased frame-type interests also exposed gaps in shared H3 handling. This discards ignored stream data and de-duplicates handler registration so buffered input does not grow indefinitely and MAX_PUSH_ID handlers run once. * HTTP/3 via OpenSSL QUIC OpenSSL 3.5 can terminate QUIC connections directly, but ATS only had a quiche-backed HTTP/3 listener. Operators who want to use the system OpenSSL QUIC stack needed a separate downstream backend without changing the existing quiche path or origin HTTP/3 scope. This adds an optional ENABLE_OPENSSL_QUIC backend that uses OpenSSL's native QUIC listener and stream APIs for downstream HTTP/3. This keeps the backend mutually exclusive with quiche, exposes TS_HAS_OPENSSL_QUIC, and shares ATS's existing HTTP/3 stream handling above the transport. This also installs native-QUIC TLS callbacks for ALPN and SNI certificate selection before ATS has a QUIC NetVC to bind. OpenSSL native QUIC does not make a selected SSL_CTX certificate active via SSL_set_SSL_CTX alone, so this applies the selected cert, key, and chain to the connection SSL. This also broadens client-side HTTP/3 tests to run with either backend, keeps H3 streams open across informational responses, and hardens transaction cleanup when OpenSSL closes stream state before ATS finishes teardown. This caches stream identifiers, declines listener-time QUIC tickets until a NetVC is bound, and adds focused H3 lifecycle and session-ticket coverage. | 1 个月前 | |
Contention-aware per-event send budget for QUICStream (#13554) * Make QUICStream per-event send budget contention-aware A single stream on a QUIC connection could only send 16KB per write event, a fixed fairness quantum that protects sibling streams from being starved. That protection has a real cost when a connection has little or no contention: a single-stream, multi-megabyte response needs dozens of separate event-loop round trips to drain, each paying real per-call overhead, even though there's nobody else to be fair to. This scales the per-event budget by how many streams were writable in the previous event on the connection: down toward the original 16KB floor under real contention, up toward a new 256KB ceiling when a stream has the write path to itself. The count is remembered rather than recomputed each event, since quiche's writable-stream iterator is one-shot with no free count, and recomputing it would cost as much as the round trips being eliminated. A brief one-event lag when contention changes abruptly is bounded and self-correcting, never a starvation risk. Benchmarking a 1MB single-stream response over QUIC (h2load, -m 1) went from 812.7 MB/s to 1553.0 MB/s; a small (8KB) object, already under one turn's floor, was unaffected, confirming the fix targets exactly the large single-stream case without disturbing anything else. * Eliminate per-request allocations in the HTTP/3 frame-handling path Http3Transaction is constructed once per request and was allocating and freeing five separate objects on the heap for its own private, fixed-lifetime framers/handlers (header framer, data framer, protocol enforcer, header/data VIO adaptors) -- these become plain value members instead, owned directly by the transaction. Http3FrameHandler::interests() ran on every handler registration and constructed and returned a fresh std::vector by value each time, even though the interest set is fixed per handler type; it now returns a reference to a function-local static vector built once. Http3FrameDispatcher's per-type handler list moves from a std::vector (heap-allocated on first push) to a small fixed-size inline array sized for the real maximum handler count. Http3HeadersFrame gains a constructor that shares the caller's IOBufferReader via a clone instead of copying the header block into a freshly malloc'd buffer, used on the receive path where the block is already sitting in an IOBuffer. Unrelated one-line fix: QUICNetProcessor was checking dbg_ctl_vv_quiche with tag_on(), which only tests whether the debug tag pattern matches and ignores whether debug output is globally enabled -- switched to on() so a coincidental tag match doesn't install quiche's trace-level logger unconditionally. * Apply contention-aware send budget to QMux's write path too QMuxConnection::_handle_write_streams() is the third of the three QUICStream::send_data() call sites, alongside QUICNetVConnection and OpenSSLQUICNetVConnection -- it was left on the fixed 16KB-per-event cap when the other two call sites moved to the contention-aware budget, since QUICStream::compute_fair_send_budget() didn't exist on the branch QMux's source lives on until this rebase brought both together. Same pattern as QUICNetVConnection::_handle_write_ready(): a _last_writable_stream_count member remembers the previous event's writable-stream count to size this event's budget, no extra allocation or extra quiche_conn_writable() call versus today's code. * Remove dead HTTP/3 frame construction paths Http3UnknownFrame was never instantiated anywhere -- no allocator for it exists (unlike every other frame type), and Http3FrameFactory::create()'s fallback for an unrecognized frame type constructs a plain Http3Frame directly instead. The whole subclass, both its constructors, and its to_io_buffer_block() override were orphaned code, predating this branch. Http3HeadersFrame's ats_unique_buf constructor and the create_headers_frame(const uint8_t*, size_t) factory overload that built it were also unreachable in production: Http3HeaderFramer.cc, their only real caller, has used the IOBufferReader*-based overload since this file's original introduction. The one place still exercising the owned-copy constructor was a unit test constructing it directly; removed along with it since the sibling "via factory" section already covers identical serialization output through the live construction path. * Take Http3HeadersFrame's send-path reader by reference, matching Data Http3DataFrame's send constructor takes IOBufferReader&, with its factory dereferencing a pointer to call it. Http3HeadersFrame's send constructor -- added when the reader-cloning path replaced the old copy-into-owned-buffer one -- took IOBufferReader* directly instead, with no actual need for pointer semantics: the constructor only calls clone() once, which works identically through a reference. Aligned it to the same &-parameter/factory-dereferences convention already used by every other IOBufferReader-taking constructor in this file (both frame types' receive-path constructors, and Data's send constructor). * Fix multi-backend build break and defensive-init gap from review _last_writable_stream_count was declared unconditionally but only read/written by the quiche write path, breaking the OpenSSL-native-QUIC build under -Werror=unused-private-field. Http3FrameDispatcher's new _handlers array lacked the same zero-initializer as its paired count array, a latent fragility if the count-gated read path is ever changed. Found in a deep review pass across both build backends. * Fix use-after-free in HEADERS frame test The test freed the source MIOBuffer while the frame it produced was still alive, so the frame's destructor deallocated a reader into already-freed memory when it went out of scope. Reset the frame before freeing the buffer, per the constructor's own documented lifetime requirement. * Cap carried-over pending stream data to the event's send budget A pending send block left over from a partial write in one event could be sized under that event's (larger) budget. If contention increased before the next event drained it, the stream would submit the whole remaining block regardless of the new, smaller budget, letting it exceed the fairness cap other streams are relying on. * Fix stale contention count driving per-event QUIC send budget compute_fair_send_budget() divides a per-event pool across a connection's writable streams, but the divisor was always the previous event's count, not the current one. That meant a connection's very first write-ready event (no previous count) or any event right after contention swung sharply up assumed near-zero contention, handing every real writable stream the full pool. Both _handle_write_ready() and _handle_write_streams() now count the actual writable streams before sizing that event's budget instead of carrying an estimate across events. quiche's writable() is a pure, side-effect-free snapshot, so the extra pass costs one bounded iterator drain, not a correctness or ordering risk. Also rename MAX_STREAM_SEND_BYTES_PER_EVENT to MAX_CONNECTION_SEND_BYTES_PER_EVENT: despite the name, it was never a per-stream ceiling, only the connection-wide pool compute_fair_send_ budget() divides among streams. The old name misdescribed its own role and made the sizing logic harder to reason about on review. | 16 天前 | |
Contention-aware per-event send budget for QUICStream (#13554) * Make QUICStream per-event send budget contention-aware A single stream on a QUIC connection could only send 16KB per write event, a fixed fairness quantum that protects sibling streams from being starved. That protection has a real cost when a connection has little or no contention: a single-stream, multi-megabyte response needs dozens of separate event-loop round trips to drain, each paying real per-call overhead, even though there's nobody else to be fair to. This scales the per-event budget by how many streams were writable in the previous event on the connection: down toward the original 16KB floor under real contention, up toward a new 256KB ceiling when a stream has the write path to itself. The count is remembered rather than recomputed each event, since quiche's writable-stream iterator is one-shot with no free count, and recomputing it would cost as much as the round trips being eliminated. A brief one-event lag when contention changes abruptly is bounded and self-correcting, never a starvation risk. Benchmarking a 1MB single-stream response over QUIC (h2load, -m 1) went from 812.7 MB/s to 1553.0 MB/s; a small (8KB) object, already under one turn's floor, was unaffected, confirming the fix targets exactly the large single-stream case without disturbing anything else. * Eliminate per-request allocations in the HTTP/3 frame-handling path Http3Transaction is constructed once per request and was allocating and freeing five separate objects on the heap for its own private, fixed-lifetime framers/handlers (header framer, data framer, protocol enforcer, header/data VIO adaptors) -- these become plain value members instead, owned directly by the transaction. Http3FrameHandler::interests() ran on every handler registration and constructed and returned a fresh std::vector by value each time, even though the interest set is fixed per handler type; it now returns a reference to a function-local static vector built once. Http3FrameDispatcher's per-type handler list moves from a std::vector (heap-allocated on first push) to a small fixed-size inline array sized for the real maximum handler count. Http3HeadersFrame gains a constructor that shares the caller's IOBufferReader via a clone instead of copying the header block into a freshly malloc'd buffer, used on the receive path where the block is already sitting in an IOBuffer. Unrelated one-line fix: QUICNetProcessor was checking dbg_ctl_vv_quiche with tag_on(), which only tests whether the debug tag pattern matches and ignores whether debug output is globally enabled -- switched to on() so a coincidental tag match doesn't install quiche's trace-level logger unconditionally. * Apply contention-aware send budget to QMux's write path too QMuxConnection::_handle_write_streams() is the third of the three QUICStream::send_data() call sites, alongside QUICNetVConnection and OpenSSLQUICNetVConnection -- it was left on the fixed 16KB-per-event cap when the other two call sites moved to the contention-aware budget, since QUICStream::compute_fair_send_budget() didn't exist on the branch QMux's source lives on until this rebase brought both together. Same pattern as QUICNetVConnection::_handle_write_ready(): a _last_writable_stream_count member remembers the previous event's writable-stream count to size this event's budget, no extra allocation or extra quiche_conn_writable() call versus today's code. * Remove dead HTTP/3 frame construction paths Http3UnknownFrame was never instantiated anywhere -- no allocator for it exists (unlike every other frame type), and Http3FrameFactory::create()'s fallback for an unrecognized frame type constructs a plain Http3Frame directly instead. The whole subclass, both its constructors, and its to_io_buffer_block() override were orphaned code, predating this branch. Http3HeadersFrame's ats_unique_buf constructor and the create_headers_frame(const uint8_t*, size_t) factory overload that built it were also unreachable in production: Http3HeaderFramer.cc, their only real caller, has used the IOBufferReader*-based overload since this file's original introduction. The one place still exercising the owned-copy constructor was a unit test constructing it directly; removed along with it since the sibling "via factory" section already covers identical serialization output through the live construction path. * Take Http3HeadersFrame's send-path reader by reference, matching Data Http3DataFrame's send constructor takes IOBufferReader&, with its factory dereferencing a pointer to call it. Http3HeadersFrame's send constructor -- added when the reader-cloning path replaced the old copy-into-owned-buffer one -- took IOBufferReader* directly instead, with no actual need for pointer semantics: the constructor only calls clone() once, which works identically through a reference. Aligned it to the same &-parameter/factory-dereferences convention already used by every other IOBufferReader-taking constructor in this file (both frame types' receive-path constructors, and Data's send constructor). * Fix multi-backend build break and defensive-init gap from review _last_writable_stream_count was declared unconditionally but only read/written by the quiche write path, breaking the OpenSSL-native-QUIC build under -Werror=unused-private-field. Http3FrameDispatcher's new _handlers array lacked the same zero-initializer as its paired count array, a latent fragility if the count-gated read path is ever changed. Found in a deep review pass across both build backends. * Fix use-after-free in HEADERS frame test The test freed the source MIOBuffer while the frame it produced was still alive, so the frame's destructor deallocated a reader into already-freed memory when it went out of scope. Reset the frame before freeing the buffer, per the constructor's own documented lifetime requirement. * Cap carried-over pending stream data to the event's send budget A pending send block left over from a partial write in one event could be sized under that event's (larger) budget. If contention increased before the next event drained it, the stream would submit the whole remaining block regardless of the new, smaller budget, letting it exceed the fairness cap other streams are relying on. * Fix stale contention count driving per-event QUIC send budget compute_fair_send_budget() divides a per-event pool across a connection's writable streams, but the divisor was always the previous event's count, not the current one. That meant a connection's very first write-ready event (no previous count) or any event right after contention swung sharply up assumed near-zero contention, handing every real writable stream the full pool. Both _handle_write_ready() and _handle_write_streams() now count the actual writable streams before sizing that event's budget instead of carrying an estimate across events. quiche's writable() is a pure, side-effect-free snapshot, so the extra pass costs one bounded iterator drain, not a correctness or ordering risk. Also rename MAX_STREAM_SEND_BYTES_PER_EVENT to MAX_CONNECTION_SEND_BYTES_PER_EVENT: despite the name, it was never a per-stream ceiling, only the connection-wide pool compute_fair_send_ budget() divides among streams. The old name misdescribed its own role and made the sizing logic harder to reason about on review. | 16 天前 | |
Add H3 quiche traffic handling tests and provide fixes (#13213) # Overview This patch extends the HTTP/3 autest coverage, using curl, Go, Python/aioquic, and Proxy Verifier HTTP/3 clients to generate their implementations of H3 traffic. It also adds request and response bodies of various sizes, including "large" 300k bodies to exercise multiple packet, buffer, and flow control ATS HTTP/3 implementations. It also exercises interesting requests and responses, such as HEAD, 204, PUT, DELETE, OPTIONS, range responses over cached objects, and malformed HTTP/3 frame behavior. This patch also includes the various production fixes needed for these tests. # Issues Found and their Fixes ## UDP batches could stall large H3 transfers Large request and response bodies exposed a UDP receive starvation bug in the UDP read path. On systems using `recvmmsg()` with edge-triggered readiness, ATS could read one full batch of datagrams and then leave the rest queued in the kernel without another readable event to wake the QUIC stack. This changes `UDPNetProcessorInternal::read_multiple_messages_from_net()` in `src/iocore/net/UnixUDPNet.cc` to return whether the kernel supplied a full batch. `udp_read_from_net()` now processes a bounded number of full batches per event, preserving UDP batching for H3 while avoiding both unread UDP bursts and unbounded net-thread monopolization under sustained QUIC load. ## QUIC stream writes consumed data before quiche accepted it The stream write path consumed the `QUICStreamVCAdapter` write reader inside `_read()`, before `QUICStream::send_data()` knew whether `quiche_conn_stream_send()` had accepted the bytes. When quiche accepted only a partial write or returned a flow-control error, ATS could lose stream data and report write progress too early. This makes `QUICStream::send_data()` keep a pending `IOBufferBlock`/FIN pair until quiche reports successful consumption, and only then calls the new `QUICStreamAdapter::consume()` hook. The concrete reader accounting lives in `QUICStreamVCAdapter::_consume()`, while `QUICStream::has_data_to_send()`, `QUICStream::on_write()`, and `QUICNetVConnection::on_stream_updated()` make newly writable stream data schedule packet writes again. This also treats completed finite writes with only FIN left as writable stream state, so empty bodies and fully consumed bodies still close the H3 stream cleanly. ## QUIC stream reads could expose bytes beyond the VIO request The large-body tests exposed that `QUICStreamVCAdapter::_read()` could hand more data to the transaction than the read VIO requested. That was usually hidden by small bodies, but larger reads made finite request-body accounting fragile. This clamps cloned input blocks in `QUICStreamVCAdapter::_read()` to the requested and available byte count before filling the read VIO. The adapter now also checks for a missing reader before touching the read buffer, which makes late stream cleanup paths more defensive. ## H3 transaction cleanup raced with stream closure The timeout and stream lifetime tests exposed cases where an `HQTransaction` could be deleted while an event handler was still active, or while the QUIC stream adapter still had read/write cleanup to finish. That left later stream-close and timeout paths touching state that had already been torn down. This makes the transaction and stream closed state derive from the active event handlers instead of separate booleans that could drift from the adapter state. `Http3App::on_stream_close()` now calls `HQTransaction::stream_closed()` while holding the transaction mutex, and `HQTransaction::_delete_if_possible()` waits until the transaction is done, the stream is closed or no longer readable, and pending writes have flushed before deleting the transaction. ## Malformed H3 streams could leave transactions behind The aioquic edge-case probes found malformed request streams that were correctly rejected at the H3 layer but still left partially constructed transactions attached to the session. Session teardown then either asserted because the transaction list was not empty or touched the H3 session after `Http3Session` had already nulled its network connection. This adds `HQSession::_close_transactions()` and drains any remaining transactions before destroying the H3 session-specific state. It also lets `Http3App::on_stream_close()` attach a cleanup callback to the transaction so the application stream map is erased when the transaction is actually destroyed, rather than when quiche first reports stream closure. ## H3 read completion could run before headers and DATA were settled The H3 request read path could signal completion before asynchronous QPACK header decode and buffered DATA delivery had finished updating the sink VIO. That showed up around HEAD, 204, and stream-close timing because the HTTP state machine needed a stable view of whether headers were decoded and whether a request body existed. This updates `Http3HeaderVIOAdaptor::_on_qpack_decode_complete()` to add the printed header length to the sink VIO and notify `Http3Transaction::on_header_decode_complete()`, which schedules the appropriate read event. `Http3StreamDataVIOAdaptor::finalize()` now uses a persistent reader, writes buffered DATA into the sink VIO exactly once, and updates `ndone`/`nbytes` consistently before the transaction is signaled. ## Malformed H3 frames were not consistently enforced The aioquic client can write raw QUIC stream data, which exposed gaps in ATS's HTTP/3 frame validation. Reserved frames on request streams, DATA-before-HEADERS, client-created push streams, and duplicate control streams did not all reliably close the QUIC connection with an H3 application error. This adds request-stream enforcement through `Http3ProtocolEnforcer` in `Http3Transaction`, recognizes reserved HTTP/3 frame types in `Http3Frame`, and routes connection-level errors through `Http3App::_handle_error()` and `Http3Transaction::_handle_error()` to close the QUIC connection. The transaction signal path now also avoids calling the HTTP state machine through closed transactions or the initial zero-byte write VIO created before the HTTP response handler is installed. ## The QPACK static table had drifted from the standard table The HEAD, 204, and quic-go coverage exposed that ATS's static QPACK table was not the table used by external HTTP/3 implementations. The extra zstd entry and modified `accept-encoding` value in `src/proxy/http3/QPACK.cc` shifted later static indexes, so an externally encoded `:status 204` could decode as a different status. This restores the standard static table entries by using `accept-encoding: gzip, deflate, br` and removing the non-standard `content-encoding: zstd` entry. The new 204 cases in `tests/gold_tests/h3/replays/h3_proxy_verifier.replay.yaml`, `tests/gold_tests/h3/replays/h3_server_for_go_client.replay.yaml`, and `tests/gold_tests/h3/replays/h3_server_for_python_client.replay.yaml` cover this interoperability point with Proxy Verifier, quic-go, and aioquic. | 1 个月前 | |
HTTP/3: reject an unknown ALPN instead of aborting (#13631) Http3SessionAccept::accept() ended its ALPN dispatch with ink_abort("Negotiated App Name is unknown"), and an absent or empty ALPN falls into that branch, so a completed QUIC handshake carrying no ALPN extension took down traffic_server. 08d1896d6a64 removed the earlier alpn.empty() arm that used to catch it. Move the dispatch into a switch over a new select_app_type() helper and return false for the unknown case, which mainEvent() already turns into do_io_close(). Also initialise the out-pointer in QUICNetVConnection::negotiated_application_name(), which quiche leaves untouched when no protocol was negotiated. | 12 天前 | |
clang-format v18 + modified configs (#11285) | 2 年前 | |
Contention-aware per-event send budget for QUICStream (#13554) * Make QUICStream per-event send budget contention-aware A single stream on a QUIC connection could only send 16KB per write event, a fixed fairness quantum that protects sibling streams from being starved. That protection has a real cost when a connection has little or no contention: a single-stream, multi-megabyte response needs dozens of separate event-loop round trips to drain, each paying real per-call overhead, even though there's nobody else to be fair to. This scales the per-event budget by how many streams were writable in the previous event on the connection: down toward the original 16KB floor under real contention, up toward a new 256KB ceiling when a stream has the write path to itself. The count is remembered rather than recomputed each event, since quiche's writable-stream iterator is one-shot with no free count, and recomputing it would cost as much as the round trips being eliminated. A brief one-event lag when contention changes abruptly is bounded and self-correcting, never a starvation risk. Benchmarking a 1MB single-stream response over QUIC (h2load, -m 1) went from 812.7 MB/s to 1553.0 MB/s; a small (8KB) object, already under one turn's floor, was unaffected, confirming the fix targets exactly the large single-stream case without disturbing anything else. * Eliminate per-request allocations in the HTTP/3 frame-handling path Http3Transaction is constructed once per request and was allocating and freeing five separate objects on the heap for its own private, fixed-lifetime framers/handlers (header framer, data framer, protocol enforcer, header/data VIO adaptors) -- these become plain value members instead, owned directly by the transaction. Http3FrameHandler::interests() ran on every handler registration and constructed and returned a fresh std::vector by value each time, even though the interest set is fixed per handler type; it now returns a reference to a function-local static vector built once. Http3FrameDispatcher's per-type handler list moves from a std::vector (heap-allocated on first push) to a small fixed-size inline array sized for the real maximum handler count. Http3HeadersFrame gains a constructor that shares the caller's IOBufferReader via a clone instead of copying the header block into a freshly malloc'd buffer, used on the receive path where the block is already sitting in an IOBuffer. Unrelated one-line fix: QUICNetProcessor was checking dbg_ctl_vv_quiche with tag_on(), which only tests whether the debug tag pattern matches and ignores whether debug output is globally enabled -- switched to on() so a coincidental tag match doesn't install quiche's trace-level logger unconditionally. * Apply contention-aware send budget to QMux's write path too QMuxConnection::_handle_write_streams() is the third of the three QUICStream::send_data() call sites, alongside QUICNetVConnection and OpenSSLQUICNetVConnection -- it was left on the fixed 16KB-per-event cap when the other two call sites moved to the contention-aware budget, since QUICStream::compute_fair_send_budget() didn't exist on the branch QMux's source lives on until this rebase brought both together. Same pattern as QUICNetVConnection::_handle_write_ready(): a _last_writable_stream_count member remembers the previous event's writable-stream count to size this event's budget, no extra allocation or extra quiche_conn_writable() call versus today's code. * Remove dead HTTP/3 frame construction paths Http3UnknownFrame was never instantiated anywhere -- no allocator for it exists (unlike every other frame type), and Http3FrameFactory::create()'s fallback for an unrecognized frame type constructs a plain Http3Frame directly instead. The whole subclass, both its constructors, and its to_io_buffer_block() override were orphaned code, predating this branch. Http3HeadersFrame's ats_unique_buf constructor and the create_headers_frame(const uint8_t*, size_t) factory overload that built it were also unreachable in production: Http3HeaderFramer.cc, their only real caller, has used the IOBufferReader*-based overload since this file's original introduction. The one place still exercising the owned-copy constructor was a unit test constructing it directly; removed along with it since the sibling "via factory" section already covers identical serialization output through the live construction path. * Take Http3HeadersFrame's send-path reader by reference, matching Data Http3DataFrame's send constructor takes IOBufferReader&, with its factory dereferencing a pointer to call it. Http3HeadersFrame's send constructor -- added when the reader-cloning path replaced the old copy-into-owned-buffer one -- took IOBufferReader* directly instead, with no actual need for pointer semantics: the constructor only calls clone() once, which works identically through a reference. Aligned it to the same &-parameter/factory-dereferences convention already used by every other IOBufferReader-taking constructor in this file (both frame types' receive-path constructors, and Data's send constructor). * Fix multi-backend build break and defensive-init gap from review _last_writable_stream_count was declared unconditionally but only read/written by the quiche write path, breaking the OpenSSL-native-QUIC build under -Werror=unused-private-field. Http3FrameDispatcher's new _handlers array lacked the same zero-initializer as its paired count array, a latent fragility if the count-gated read path is ever changed. Found in a deep review pass across both build backends. * Fix use-after-free in HEADERS frame test The test freed the source MIOBuffer while the frame it produced was still alive, so the frame's destructor deallocated a reader into already-freed memory when it went out of scope. Reset the frame before freeing the buffer, per the constructor's own documented lifetime requirement. * Cap carried-over pending stream data to the event's send budget A pending send block left over from a partial write in one event could be sized under that event's (larger) budget. If contention increased before the next event drained it, the stream would submit the whole remaining block regardless of the new, smaller budget, letting it exceed the fairness cap other streams are relying on. * Fix stale contention count driving per-event QUIC send budget compute_fair_send_budget() divides a per-event pool across a connection's writable streams, but the divisor was always the previous event's count, not the current one. That meant a connection's very first write-ready event (no previous count) or any event right after contention swung sharply up assumed near-zero contention, handing every real writable stream the full pool. Both _handle_write_ready() and _handle_write_streams() now count the actual writable streams before sizing that event's budget instead of carrying an estimate across events. quiche's writable() is a pure, side-effect-free snapshot, so the extra pass costs one bounded iterator drain, not a correctness or ordering risk. Also rename MAX_STREAM_SEND_BYTES_PER_EVENT to MAX_CONNECTION_SEND_BYTES_PER_EVENT: despite the name, it was never a per-stream ceiling, only the connection-wide pool compute_fair_send_ budget() divides among streams. The old name misdescribed its own role and made the sizing logic harder to reason about on review. | 16 天前 | |
Contention-aware per-event send budget for QUICStream (#13554) * Make QUICStream per-event send budget contention-aware A single stream on a QUIC connection could only send 16KB per write event, a fixed fairness quantum that protects sibling streams from being starved. That protection has a real cost when a connection has little or no contention: a single-stream, multi-megabyte response needs dozens of separate event-loop round trips to drain, each paying real per-call overhead, even though there's nobody else to be fair to. This scales the per-event budget by how many streams were writable in the previous event on the connection: down toward the original 16KB floor under real contention, up toward a new 256KB ceiling when a stream has the write path to itself. The count is remembered rather than recomputed each event, since quiche's writable-stream iterator is one-shot with no free count, and recomputing it would cost as much as the round trips being eliminated. A brief one-event lag when contention changes abruptly is bounded and self-correcting, never a starvation risk. Benchmarking a 1MB single-stream response over QUIC (h2load, -m 1) went from 812.7 MB/s to 1553.0 MB/s; a small (8KB) object, already under one turn's floor, was unaffected, confirming the fix targets exactly the large single-stream case without disturbing anything else. * Eliminate per-request allocations in the HTTP/3 frame-handling path Http3Transaction is constructed once per request and was allocating and freeing five separate objects on the heap for its own private, fixed-lifetime framers/handlers (header framer, data framer, protocol enforcer, header/data VIO adaptors) -- these become plain value members instead, owned directly by the transaction. Http3FrameHandler::interests() ran on every handler registration and constructed and returned a fresh std::vector by value each time, even though the interest set is fixed per handler type; it now returns a reference to a function-local static vector built once. Http3FrameDispatcher's per-type handler list moves from a std::vector (heap-allocated on first push) to a small fixed-size inline array sized for the real maximum handler count. Http3HeadersFrame gains a constructor that shares the caller's IOBufferReader via a clone instead of copying the header block into a freshly malloc'd buffer, used on the receive path where the block is already sitting in an IOBuffer. Unrelated one-line fix: QUICNetProcessor was checking dbg_ctl_vv_quiche with tag_on(), which only tests whether the debug tag pattern matches and ignores whether debug output is globally enabled -- switched to on() so a coincidental tag match doesn't install quiche's trace-level logger unconditionally. * Apply contention-aware send budget to QMux's write path too QMuxConnection::_handle_write_streams() is the third of the three QUICStream::send_data() call sites, alongside QUICNetVConnection and OpenSSLQUICNetVConnection -- it was left on the fixed 16KB-per-event cap when the other two call sites moved to the contention-aware budget, since QUICStream::compute_fair_send_budget() didn't exist on the branch QMux's source lives on until this rebase brought both together. Same pattern as QUICNetVConnection::_handle_write_ready(): a _last_writable_stream_count member remembers the previous event's writable-stream count to size this event's budget, no extra allocation or extra quiche_conn_writable() call versus today's code. * Remove dead HTTP/3 frame construction paths Http3UnknownFrame was never instantiated anywhere -- no allocator for it exists (unlike every other frame type), and Http3FrameFactory::create()'s fallback for an unrecognized frame type constructs a plain Http3Frame directly instead. The whole subclass, both its constructors, and its to_io_buffer_block() override were orphaned code, predating this branch. Http3HeadersFrame's ats_unique_buf constructor and the create_headers_frame(const uint8_t*, size_t) factory overload that built it were also unreachable in production: Http3HeaderFramer.cc, their only real caller, has used the IOBufferReader*-based overload since this file's original introduction. The one place still exercising the owned-copy constructor was a unit test constructing it directly; removed along with it since the sibling "via factory" section already covers identical serialization output through the live construction path. * Take Http3HeadersFrame's send-path reader by reference, matching Data Http3DataFrame's send constructor takes IOBufferReader&, with its factory dereferencing a pointer to call it. Http3HeadersFrame's send constructor -- added when the reader-cloning path replaced the old copy-into-owned-buffer one -- took IOBufferReader* directly instead, with no actual need for pointer semantics: the constructor only calls clone() once, which works identically through a reference. Aligned it to the same &-parameter/factory-dereferences convention already used by every other IOBufferReader-taking constructor in this file (both frame types' receive-path constructors, and Data's send constructor). * Fix multi-backend build break and defensive-init gap from review _last_writable_stream_count was declared unconditionally but only read/written by the quiche write path, breaking the OpenSSL-native-QUIC build under -Werror=unused-private-field. Http3FrameDispatcher's new _handlers array lacked the same zero-initializer as its paired count array, a latent fragility if the count-gated read path is ever changed. Found in a deep review pass across both build backends. * Fix use-after-free in HEADERS frame test The test freed the source MIOBuffer while the frame it produced was still alive, so the frame's destructor deallocated a reader into already-freed memory when it went out of scope. Reset the frame before freeing the buffer, per the constructor's own documented lifetime requirement. * Cap carried-over pending stream data to the event's send budget A pending send block left over from a partial write in one event could be sized under that event's (larger) budget. If contention increased before the next event drained it, the stream would submit the whole remaining block regardless of the new, smaller budget, letting it exceed the fairness cap other streams are relying on. * Fix stale contention count driving per-event QUIC send budget compute_fair_send_budget() divides a per-event pool across a connection's writable streams, but the divisor was always the previous event's count, not the current one. That meant a connection's very first write-ready event (no previous count) or any event right after contention swung sharply up assumed near-zero contention, handing every real writable stream the full pool. Both _handle_write_ready() and _handle_write_streams() now count the actual writable streams before sizing that event's budget instead of carrying an estimate across events. quiche's writable() is a pure, side-effect-free snapshot, so the extra pass costs one bounded iterator drain, not a correctness or ordering risk. Also rename MAX_STREAM_SEND_BYTES_PER_EVENT to MAX_CONNECTION_SEND_BYTES_PER_EVENT: despite the name, it was never a per-stream ceiling, only the connection-wide pool compute_fair_send_ budget() divides among streams. The old name misdescribed its own role and made the sizing logic harder to reason about on review. | 16 天前 | |
Contention-aware per-event send budget for QUICStream (#13554) * Make QUICStream per-event send budget contention-aware A single stream on a QUIC connection could only send 16KB per write event, a fixed fairness quantum that protects sibling streams from being starved. That protection has a real cost when a connection has little or no contention: a single-stream, multi-megabyte response needs dozens of separate event-loop round trips to drain, each paying real per-call overhead, even though there's nobody else to be fair to. This scales the per-event budget by how many streams were writable in the previous event on the connection: down toward the original 16KB floor under real contention, up toward a new 256KB ceiling when a stream has the write path to itself. The count is remembered rather than recomputed each event, since quiche's writable-stream iterator is one-shot with no free count, and recomputing it would cost as much as the round trips being eliminated. A brief one-event lag when contention changes abruptly is bounded and self-correcting, never a starvation risk. Benchmarking a 1MB single-stream response over QUIC (h2load, -m 1) went from 812.7 MB/s to 1553.0 MB/s; a small (8KB) object, already under one turn's floor, was unaffected, confirming the fix targets exactly the large single-stream case without disturbing anything else. * Eliminate per-request allocations in the HTTP/3 frame-handling path Http3Transaction is constructed once per request and was allocating and freeing five separate objects on the heap for its own private, fixed-lifetime framers/handlers (header framer, data framer, protocol enforcer, header/data VIO adaptors) -- these become plain value members instead, owned directly by the transaction. Http3FrameHandler::interests() ran on every handler registration and constructed and returned a fresh std::vector by value each time, even though the interest set is fixed per handler type; it now returns a reference to a function-local static vector built once. Http3FrameDispatcher's per-type handler list moves from a std::vector (heap-allocated on first push) to a small fixed-size inline array sized for the real maximum handler count. Http3HeadersFrame gains a constructor that shares the caller's IOBufferReader via a clone instead of copying the header block into a freshly malloc'd buffer, used on the receive path where the block is already sitting in an IOBuffer. Unrelated one-line fix: QUICNetProcessor was checking dbg_ctl_vv_quiche with tag_on(), which only tests whether the debug tag pattern matches and ignores whether debug output is globally enabled -- switched to on() so a coincidental tag match doesn't install quiche's trace-level logger unconditionally. * Apply contention-aware send budget to QMux's write path too QMuxConnection::_handle_write_streams() is the third of the three QUICStream::send_data() call sites, alongside QUICNetVConnection and OpenSSLQUICNetVConnection -- it was left on the fixed 16KB-per-event cap when the other two call sites moved to the contention-aware budget, since QUICStream::compute_fair_send_budget() didn't exist on the branch QMux's source lives on until this rebase brought both together. Same pattern as QUICNetVConnection::_handle_write_ready(): a _last_writable_stream_count member remembers the previous event's writable-stream count to size this event's budget, no extra allocation or extra quiche_conn_writable() call versus today's code. * Remove dead HTTP/3 frame construction paths Http3UnknownFrame was never instantiated anywhere -- no allocator for it exists (unlike every other frame type), and Http3FrameFactory::create()'s fallback for an unrecognized frame type constructs a plain Http3Frame directly instead. The whole subclass, both its constructors, and its to_io_buffer_block() override were orphaned code, predating this branch. Http3HeadersFrame's ats_unique_buf constructor and the create_headers_frame(const uint8_t*, size_t) factory overload that built it were also unreachable in production: Http3HeaderFramer.cc, their only real caller, has used the IOBufferReader*-based overload since this file's original introduction. The one place still exercising the owned-copy constructor was a unit test constructing it directly; removed along with it since the sibling "via factory" section already covers identical serialization output through the live construction path. * Take Http3HeadersFrame's send-path reader by reference, matching Data Http3DataFrame's send constructor takes IOBufferReader&, with its factory dereferencing a pointer to call it. Http3HeadersFrame's send constructor -- added when the reader-cloning path replaced the old copy-into-owned-buffer one -- took IOBufferReader* directly instead, with no actual need for pointer semantics: the constructor only calls clone() once, which works identically through a reference. Aligned it to the same &-parameter/factory-dereferences convention already used by every other IOBufferReader-taking constructor in this file (both frame types' receive-path constructors, and Data's send constructor). * Fix multi-backend build break and defensive-init gap from review _last_writable_stream_count was declared unconditionally but only read/written by the quiche write path, breaking the OpenSSL-native-QUIC build under -Werror=unused-private-field. Http3FrameDispatcher's new _handlers array lacked the same zero-initializer as its paired count array, a latent fragility if the count-gated read path is ever changed. Found in a deep review pass across both build backends. * Fix use-after-free in HEADERS frame test The test freed the source MIOBuffer while the frame it produced was still alive, so the frame's destructor deallocated a reader into already-freed memory when it went out of scope. Reset the frame before freeing the buffer, per the constructor's own documented lifetime requirement. * Cap carried-over pending stream data to the event's send budget A pending send block left over from a partial write in one event could be sized under that event's (larger) budget. If contention increased before the next event drained it, the stream would submit the whole remaining block regardless of the new, smaller budget, letting it exceed the fairness cap other streams are relying on. * Fix stale contention count driving per-event QUIC send budget compute_fair_send_budget() divides a per-event pool across a connection's writable streams, but the divisor was always the previous event's count, not the current one. That meant a connection's very first write-ready event (no previous count) or any event right after contention swung sharply up assumed near-zero contention, handing every real writable stream the full pool. Both _handle_write_ready() and _handle_write_streams() now count the actual writable streams before sizing that event's budget instead of carrying an estimate across events. quiche's writable() is a pure, side-effect-free snapshot, so the extra pass costs one bounded iterator drain, not a correctness or ordering risk. Also rename MAX_STREAM_SEND_BYTES_PER_EVENT to MAX_CONNECTION_SEND_BYTES_PER_EVENT: despite the name, it was never a per-stream ceiling, only the connection-wide pool compute_fair_send_ budget() divides among streams. The old name misdescribed its own role and made the sizing logic harder to reason about on review. | 16 天前 | |
Add H3 quiche traffic handling tests and provide fixes (#13213) # Overview This patch extends the HTTP/3 autest coverage, using curl, Go, Python/aioquic, and Proxy Verifier HTTP/3 clients to generate their implementations of H3 traffic. It also adds request and response bodies of various sizes, including "large" 300k bodies to exercise multiple packet, buffer, and flow control ATS HTTP/3 implementations. It also exercises interesting requests and responses, such as HEAD, 204, PUT, DELETE, OPTIONS, range responses over cached objects, and malformed HTTP/3 frame behavior. This patch also includes the various production fixes needed for these tests. # Issues Found and their Fixes ## UDP batches could stall large H3 transfers Large request and response bodies exposed a UDP receive starvation bug in the UDP read path. On systems using `recvmmsg()` with edge-triggered readiness, ATS could read one full batch of datagrams and then leave the rest queued in the kernel without another readable event to wake the QUIC stack. This changes `UDPNetProcessorInternal::read_multiple_messages_from_net()` in `src/iocore/net/UnixUDPNet.cc` to return whether the kernel supplied a full batch. `udp_read_from_net()` now processes a bounded number of full batches per event, preserving UDP batching for H3 while avoiding both unread UDP bursts and unbounded net-thread monopolization under sustained QUIC load. ## QUIC stream writes consumed data before quiche accepted it The stream write path consumed the `QUICStreamVCAdapter` write reader inside `_read()`, before `QUICStream::send_data()` knew whether `quiche_conn_stream_send()` had accepted the bytes. When quiche accepted only a partial write or returned a flow-control error, ATS could lose stream data and report write progress too early. This makes `QUICStream::send_data()` keep a pending `IOBufferBlock`/FIN pair until quiche reports successful consumption, and only then calls the new `QUICStreamAdapter::consume()` hook. The concrete reader accounting lives in `QUICStreamVCAdapter::_consume()`, while `QUICStream::has_data_to_send()`, `QUICStream::on_write()`, and `QUICNetVConnection::on_stream_updated()` make newly writable stream data schedule packet writes again. This also treats completed finite writes with only FIN left as writable stream state, so empty bodies and fully consumed bodies still close the H3 stream cleanly. ## QUIC stream reads could expose bytes beyond the VIO request The large-body tests exposed that `QUICStreamVCAdapter::_read()` could hand more data to the transaction than the read VIO requested. That was usually hidden by small bodies, but larger reads made finite request-body accounting fragile. This clamps cloned input blocks in `QUICStreamVCAdapter::_read()` to the requested and available byte count before filling the read VIO. The adapter now also checks for a missing reader before touching the read buffer, which makes late stream cleanup paths more defensive. ## H3 transaction cleanup raced with stream closure The timeout and stream lifetime tests exposed cases where an `HQTransaction` could be deleted while an event handler was still active, or while the QUIC stream adapter still had read/write cleanup to finish. That left later stream-close and timeout paths touching state that had already been torn down. This makes the transaction and stream closed state derive from the active event handlers instead of separate booleans that could drift from the adapter state. `Http3App::on_stream_close()` now calls `HQTransaction::stream_closed()` while holding the transaction mutex, and `HQTransaction::_delete_if_possible()` waits until the transaction is done, the stream is closed or no longer readable, and pending writes have flushed before deleting the transaction. ## Malformed H3 streams could leave transactions behind The aioquic edge-case probes found malformed request streams that were correctly rejected at the H3 layer but still left partially constructed transactions attached to the session. Session teardown then either asserted because the transaction list was not empty or touched the H3 session after `Http3Session` had already nulled its network connection. This adds `HQSession::_close_transactions()` and drains any remaining transactions before destroying the H3 session-specific state. It also lets `Http3App::on_stream_close()` attach a cleanup callback to the transaction so the application stream map is erased when the transaction is actually destroyed, rather than when quiche first reports stream closure. ## H3 read completion could run before headers and DATA were settled The H3 request read path could signal completion before asynchronous QPACK header decode and buffered DATA delivery had finished updating the sink VIO. That showed up around HEAD, 204, and stream-close timing because the HTTP state machine needed a stable view of whether headers were decoded and whether a request body existed. This updates `Http3HeaderVIOAdaptor::_on_qpack_decode_complete()` to add the printed header length to the sink VIO and notify `Http3Transaction::on_header_decode_complete()`, which schedules the appropriate read event. `Http3StreamDataVIOAdaptor::finalize()` now uses a persistent reader, writes buffered DATA into the sink VIO exactly once, and updates `ndone`/`nbytes` consistently before the transaction is signaled. ## Malformed H3 frames were not consistently enforced The aioquic client can write raw QUIC stream data, which exposed gaps in ATS's HTTP/3 frame validation. Reserved frames on request streams, DATA-before-HEADERS, client-created push streams, and duplicate control streams did not all reliably close the QUIC connection with an H3 application error. This adds request-stream enforcement through `Http3ProtocolEnforcer` in `Http3Transaction`, recognizes reserved HTTP/3 frame types in `Http3Frame`, and routes connection-level errors through `Http3App::_handle_error()` and `Http3Transaction::_handle_error()` to close the QUIC connection. The transaction signal path now also avoids calling the HTTP state machine through closed transactions or the initial zero-byte write VIO created before the HTTP response handler is installed. ## The QPACK static table had drifted from the standard table The HEAD, 204, and quic-go coverage exposed that ATS's static QPACK table was not the table used by external HTTP/3 implementations. The extra zstd entry and modified `accept-encoding` value in `src/proxy/http3/QPACK.cc` shifted later static indexes, so an externally encoded `:status 204` could decode as a different status. This restores the standard static table entries by using `accept-encoding: gzip, deflate, br` and removing the non-standard `content-encoding: zstd` entry. The new 204 cases in `tests/gold_tests/h3/replays/h3_proxy_verifier.replay.yaml`, `tests/gold_tests/h3/replays/h3_server_for_go_client.replay.yaml`, and `tests/gold_tests/h3/replays/h3_server_for_python_client.replay.yaml` cover this interoperability point with Proxy Verifier, quic-go, and aioquic. | 1 个月前 | |
Enforce per-field size limit in HPACK/QPACK string decoding * Enforce per-field size limit in HPACK/QPACK string decoding The existing proxy.config.http.header_field_max_size setting limits individual header field sizes in the HTTP/1.1 parser, but the HPACK and QPACK decode paths had no equivalent check. This adds a max_string_len parameter to xpack_decode_string and passes the configured limit down through the HTTP/2 and HTTP/3 decode chains. * Address copilot comments * address copilot comments * Define 32768 as a constant | 1 个月前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 23 天前 | ||
| 2 年前 | ||
| 23 天前 | ||
| 2 年前 | ||
| 2 年前 | ||
| 2 年前 | ||
| 16 天前 | ||
| 16 天前 | ||
| 16 天前 | ||
| 16 天前 | ||
| 2 年前 | ||
| 16 天前 | ||
| 1 个月前 | ||
| 16 天前 | ||
| 16 天前 | ||
| 1 个月前 | ||
| 12 天前 | ||
| 2 年前 | ||
| 16 天前 | ||
| 16 天前 | ||
| 16 天前 | ||
| 1 个月前 | ||
| 1 个月前 |