Conversation
ioxd_bind_quic(port, certs, alpn) binds a QUIC port on every worker. The transport is ngtcp2's, its TLS 1.3 OpenSSL's QUIC TLS API through ngtcp2's ossl backend - one TLS stack in the process, and the same certificate store a TLS port uses: certs.c now builds a QUIC context beside each host's, chosen by SNI as before, with the port's protocols picked by ALPN. Every stream a peer opens is handed to the handler of ioxd_run_pipes as a pipe of its own, on a coroutine of its own: what the peer sent is what the pipe reads, what the handler writes goes back on the stream, and returning ends it. The port on a worker (quic/quic.c): the worker's own UDP socket (SO_REUSEPORT), one multishot recvmsg delivering every datagram into the provided buffers with the peer's address in front, connections keyed by connection id in an open-addressing table, one kernel timeout per worker at the earliest expiry of a heap of connections, and every send a GSO train of what ngtcp2 wrote (ngtcp2_conn_write_aggregate_pkt2). Unknown ids get a stateless reset, unknown versions a Version Negotiation, an Initial a connection. A cycle - a datagram, a timer - never resumes a coroutine from inside ngtcp2: callbacks queue the streams, the loop resumes them after. The stream (quic/stream.c): received bytes are copied into chunks the pipe reader pops and gives back, and giving one back opens the peer's window by as much; written bytes are copied into chunks retained until acknowledged, since ngtcp2 keeps pointers into them, with the writer parking past 256 KB unacknowledged. Streams with bytes to send take turns in the connection's pump, several sharing a packet. The pipe learned to read from and write to a link (ioxd_pipe_link): the TCP connection's ops in conn.c, the stream's in stream.c; the loop learned one more tag, TAG_CALL, a completion that calls the operation back. Build: QUIC is in when pkg-config finds libngtcp2 with its ossl backend (make QUIC=1 insists, QUIC=0 leaves it out; CMake IOXD_QUIC AUTO/ON/OFF); OpenSSL is now found through pkg-config too, so a private OpenSSL 3.5 is a PKG_CONFIG_PATH away. Without it ioxd_bind_quic says so and returns -1. Tests: tests/quic.py over aioquic against the pipe fixture, which serves the line echo on QUIC streams beside TCP when IOXD_CERTS is set - the handshake and ALPN, a line echoed, 50 streams at once, a stream held open, 2 MB through one stream read back as it arrives, a reset, a one-way stream, 40 connections at once, a refused protocol, an idle close. `make check` runs it in a QUIC build. The example playground/examples/quic_echo.c and the manual page ioxd_quic(3) show the API. Not here yet: HTTP/3 (nghttp3 on these streams), and steering a peer that changed address back to its worker - the ids this server mints already carry the worker in their first byte for a reuseport filter to read. Claude-Session: https://claude.ai/code/session_013wYnJvEFUjKEpGLkyTLt9P
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ioxd_bind_quic(port, certs, alpn): a QUIC port on every worker, ngtcp2 for the transport, OpenSSL 3.5's QUIC TLS API through ngtcp2'sosslbackend for TLS 1.3, from the same certificate store a TLS port uses (SNI as before, ALPN from the port's list). Every stream a peer opens is served by the handler ofioxd_run_pipesas a pipe of its own, on a coroutine of its own: what the peer sent is what the pipe reads, what the handler writes goes back on the stream, returning ends it. The line-echo example serves TCP and QUIC with one function.How it follows ioxide, and where it differs
ioxide's layering is kept: the core binds the UDP port and routes datagrams by connection id, the engine (ngtcp2) owns the handshake, packet protection, loss recovery and flow control, and a stream's bytes are copied out of the engine's callbacks into the reader's queue and retained for the writer until acknowledged. Two differences:
libngtcp2_crypto_osslover the same OpenSSL, andcerts.cbuilds a QUIC context beside each host's. One store, one reload, one SNI table.cid[0] mod workers), as ioxide's do, so a reuseport filter can steer a peer that changed address back to its worker; the filter and the forwarding fallback are not in this PR. A moved peer lands on whichever worker the 4-tuple hashes to and is dropped there as a stale id.The port on a worker (
lib/quic/quic.c)recvmsginto the provided buffers with the peer's address in frontngtcp2_conn_write_aggregate_pkt2wrote, as a GSO train; a kernel that refusesUDP_SEGMENTturns it off for the portThe stream (
lib/quic/stream.c)Received bytes go into chunks the pipe reader pops and gives back; giving one back opens the peer's window by as much. Written bytes go into chunks retained until acknowledged, since ngtcp2 keeps pointers into them; a writer parks past 256 KB unacknowledged. Streams with bytes to send take turns in the connection's pump, several sharing a packet.
Underneath
The pipe reads from and writes to a link (
ioxd_pipe_link): the TCP connection's ops inconn.c, the stream's instream.c. The loop has one more tag,TAG_CALL, a completion that calls its operation back, which is what the QUIC recv, sends and timer use.Build
QUIC is in when pkg-config finds
libngtcp2andlibngtcp2_crypto_ossl(make QUIC=1insists,QUIC=0leaves it out; CMakeIOXD_QUICAUTO/ON/OFF). OpenSSL is found through pkg-config too, so a private OpenSSL 3.5 is aPKG_CONFIG_PATHaway. Without QUIC,ioxd_bind_quicsays so and returns -1.Tests
tests/quic.py(aioquic) against the pipe fixture, which serves the echo on QUIC streams beside TCP whenIOXD_CERTSis set: handshake and ALPN, a line echoed, 50 streams at once, a stream held open, 2 MB through one stream read back while it arrives, a client reset, a one-way stream, 40 connections at once, a refused protocol, an idle close.make checkruns it in a QUIC build and skips it otherwise.Gate on this branch: unit, router, smoke, conformance, stress, tls_early, pipes, quic, check-tiny, tidy, QUIC=0 and TLS=0 builds, ctest, tlsfuzzer.
Not here yet
HTTP/3 (nghttp3 on these streams), and the reuseport steering above.
🤖 Generated with Claude Code
https://claude.ai/code/session_013wYnJvEFUjKEpGLkyTLt9P