Skip to content

Reassemble non-contiguous QUIC CRYPTO frames for TLS parsing - #18

Merged
thearossman merged 3 commits into
stanford-esrg:mainfrom
ntyunyayev:fix-quic-crypto-reassembly
Jul 24, 2026
Merged

Reassemble non-contiguous QUIC CRYPTO frames for TLS parsing#18
thearossman merged 3 commits into
stanford-esrg:mainfrom
ntyunyayev:fix-quic-crypto-reassembly

Conversation

@ntyunyayev

Copy link
Copy Markdown
Contributor

Chrome splits its QUIC ClientHello across multiple CRYPTO frames at non-contiguous cryptostream offsets, interleaved with PING/PADDING and spread over several Initial packets. The previous reassembly used a flat per-direction buffer with a strict in-order expected_offset and returned MissingCryptoFrames on the first offset gap, so the embedded TLS handshake never parsed for these connections.

Replace the flat buffers with per-direction sparse cryptostream maps (client_crypto/server_crypto: BTreeMap<u64, Vec>) plus client_consumed/server_consumed offset counters. parse_frames now returns each CRYPTO chunk paired with its absolute offset; the caller drops chunks into the map and feeds the TLS parser the longest contiguous [consumed..N) prefix as bytes accumulate across packets, tolerating Incomplete and skipping (rather than erroring on) a failed message so one bad parse can't block the connection.

Verified offline against a captured Chrome->google.com QUIC trace: SNI extraction goes from empty to www.google.com (matching Wireshark's reassembly), with no regression on single-frame ClientHellos.

Here is a pcap that shows that crypto frames are interleaved with ping and padding frames in Chrome:

pcap.zip

Chrome splits its QUIC ClientHello across multiple CRYPTO frames at
non-contiguous cryptostream offsets, interleaved with PING/PADDING and
spread over several Initial packets. The previous reassembly used a flat
per-direction buffer with a strict in-order expected_offset and returned
MissingCryptoFrames on the first offset gap, so the embedded TLS
handshake never parsed for these connections.

Replace the flat buffers with per-direction sparse cryptostream maps
(client_crypto/server_crypto: BTreeMap<u64, Vec<u8>>) plus
client_consumed/server_consumed offset counters. parse_frames now
returns each CRYPTO chunk paired with its absolute offset; the caller
drops chunks into the map and feeds the TLS parser the longest
contiguous [consumed..N) prefix as bytes accumulate across packets,
tolerating Incomplete and skipping (rather than erroring on) a failed
message so one bad parse can't block the connection.

Verified offline against a captured Chrome->google.com QUIC trace: SNI
extraction goes from empty to www.google.com (matching Wireshark's
reassembly), with no regression on single-frame ClientHellos.
@thearossman

Copy link
Copy Markdown
Collaborator

Thank you so much, Nikita! Can you run cargo fmt? With that, I think good to merge.

@ntyunyayev

Copy link
Copy Markdown
Contributor Author

No worries, should be good now!

@thearossman
thearossman merged commit e39fb68 into stanford-esrg:main Jul 24, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants