gw-02 — Analysis
The design questions behind terminating L7, and what to be ready to defend in a review.
Required behaviors of an L7 terminator
- Unambiguous framing. Exactly one body-delimitation rule per
message. Reject messages that specify both
Content-LengthandTransfer-Encoding: chunked(smuggling surface), or normalize to one before forwarding. - Correct h2 demux. Frames from interleaved streams must be routed
to the right per-stream state machine by
Stream Identifier; stream IDs are monotonic and parity-typed (odd=client, even=server). - Flow-control accounting. Track per-stream and per-connection
windows; never send DATA beyond the peer's advertised window; emit
WINDOW_UPDATEas you consume. - HPACK consistency. Encoder and decoder dynamic tables must stay in lockstep across header rewrites; bound the table size.
- Graceful close. Use
GOAWAY(h2) to drain rather than abruptly closing the TCP connection mid-stream.
Design decisions in the lab parser
-
Parse, don't fully implement. The
steps/build a read-only h2 frame parser: it decodes the 9-byte header, classifies frame types, and tracks per-stream state — enough to reason about multiplexing, HPACK, and flow control, without re-implementing a production codec. The real thing isgolang.org/x/net/http2; the lab's job is to make its mechanics legible. -
Static-table HPACK first, dynamic table second. We decode using the 61-entry static table and literal headers before adding the dynamic table, because the dynamic table is where the subtle state-sync bugs live and you want to see the table grow.
-
Surface the multiplexing trap experimentally. A step runs many concurrent gRPC calls over one h2 connection through an L4 vs an L7 balancer and shows the load distribution differ — turning the interview talking point into a reproduced result.
Tradeoffs worth flagging
-
L7 termination forfeits zero-copy. The moment you parse, you copy through userspace and hold per-message state. That's the price of routing/retries/auth. For pure pass-through (e.g., opaque TLS passthrough by SNI) you stay L4 and keep
splice(gw-07). -
h2 to the origin is a double-edged sword. One multiplexed connection per origin curbs connection churn (gw-04) and removes h1 HOL — but concentrates load and re-introduces TCP HOL; a single packet loss stalls all streams to that origin. You trade connection efficiency for blast-radius-per-connection.
-
HTTP/3's UDP changes your whole operational model. New LB hashing (connection ID, not 4-tuple), new DDoS posture, new observability (no TCP counters), userspace CPU cost. Often worth it at the client edge (mobile), rarely worth it origin-side first.
-
Trailers complicate everything. gRPC status in trailers means your access logs, metrics, and retry logic must read past the body. A gateway that classifies success by HTTP status alone will misreport gRPC health (gw-06, gw-11).
What production adds beyond this lab
- A full, fuzzed codec (h2 frame parsers are a rich attack surface: HPACK bombs, CONTINUATION floods, RST_STREAM floods → the "Rapid Reset" CVE-2023-44487 class of DoS).
- Protocol downgrade/upgrade bridging (h2 client ↔ h1 origin, h3 client ↔ h2 origin) with correct semantics for trailers, 1xx, and Expect/Continue.
- 0-RTT replay protection for h3 (non-idempotent requests must not be accepted in early data).
- Per-route protocol policy (force h2 to this cluster, h1 to that one) driven by the control plane (gw-08).