srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/PLAN.md
diff options
context:
space:
mode:
Diffstat (limited to 'PLAN.md')
-rw-r--r--PLAN.md44
1 files changed, 42 insertions, 2 deletions
diff --git a/PLAN.md b/PLAN.md
index 3fa8c1d..392e02e 100644
--- a/PLAN.md
+++ b/PLAN.md
@@ -30,8 +30,10 @@ unowned buffers) via a real-world capture pipeline.
3. [done] pcapng read/write
4. [done] Bounded channel + drop-on-backpressure between capture and render
5. [in progress] L7 dissector interface, add protocols incrementally --
- interface + DNS + HTTP + TLS SNI + mDNS + SSH banner done
- (packeteer/l7/); more protocols can still be added incrementally,
+ interface + DNS + HTTP + TLS SNI + mDNS + SSH banner + NTP + DHCP +
+ FTP + SMTP + TFTP + QUIC done (packeteer/l7/); ARP/VLAN/IGMP done
+ at the L2/L3 level too (packeteer/net/); more protocols can still
+ be added incrementally,
by design
6. [done] Filtering (-f <expr>, libpcap's own BPF compiler - see Decisions)
7. [done] Drop privileges after opening the capture handle (see Decisions)
@@ -500,3 +502,41 @@ None currently open.
re-verified against the same live traffic afterward, confirmed
clean, and a regression test locks in the exact byte pattern that
triggered it.
+- QUIC (l7/quic.hpp), decoding only what RFC 9000 actually sends in
+ cleartext at the framing level: long vs. short header form, version,
+ long-packet type (Initial/0-RTT/Handshake/Retry/Version
+ Negotiation), and both connection IDs. Everything past that --
+ packet numbers, frames, the payload - is encrypted from the first
+ protected byte onward, even for Initial packets (whose keys derive
+ via HKDF from a public per-version salt, then AES-GCM); actually
+ decrypting that is real crypto machinery this project deliberately
+ doesn't take on, the same call already made for TLS's SNI-only
+ extraction. A short-header packet's destination connection ID has no
+ length field in the packet itself - the receiver already knows it
+ from earlier connection state a passive observer doesn't have - so
+ short-header packets are reported by form alone.
+ Discovered and fixed a real, previously-latent bug while wiring this
+ in, caught by design review before it ever touched live traffic:
+ L7Registry::dissect() returned on the *first* dissector whose
+ port() matched, even if that dissector's summarize() then failed --
+ harmless while every registered port was unique, but QUIC is the
+ first protocol here to genuinely share a well-known port with
+ something else already registered (443: TLS over TCP, QUIC over
+ UDP; the registry has no transport dimension, only a port number).
+ Without the fix, tls_dissector (registered first) would silently
+ claim every port-443 lookup and return nullopt for all QUIC traffic,
+ forever, regardless of registration order past it. Fixed to try each
+ same-port dissector until one actually succeeds; locked in with
+ tests/test_dissector.cpp using two stub dissectors on a shared port,
+ independent of any real protocol's parsing logic.
+ Live-verified about as thoroughly as anything in this project: real
+ HTTP/3 traffic to google.com via `curl --http3-only` (curl here
+ links ngtcp2/nghttp3), captured on wlp1s0, correctly decoding the
+ full connection lifecycle - Initial packets (including a genuine
+ connection ID migration mid-handshake, dcid changing from
+ a2d81abd... to e2d81abd..., real QUIC behavior, not a parsing
+ artifact), Handshake packets, and finally 1-RTT short-header
+ packets - against Google's actual production QUIC implementation.
+ This also serves as a real-traffic confirmation that the
+ L7Registry fix works: without it, none of this would have decoded
+ at all, since tls_dissector claims port 443 first.