throughput: TUN vnet-header GSO/GRO offload (lever 4b) #56

Merged
vxfemboy merged 11 commits from feat/throughput-tun-offload-4b into main 2026-07-12 21:47:02 +00:00
vxfemboy commented 2026-07-12 20:20:08 +00:00 (Migrated from github.com)

What

Batches yipd's own TUN device I/O via a virtio_net_hdr (GSO/GRO offload) on the default poll path, to cut the per-packet TUN-device cost that post-4a profiling identified as the largest remaining single-core cost. Lever 4b of the throughput campaign (4a send GSO ✓ #554b TUN offload → 4c MTU).

Why (post-4a re-profile)

After 4a merged, perf on the target boxes showed the send path was no longer dominant. The new top costs were on the TUN boundary: receiver tun_chr_write_iter ~20% (one write() per decrypted packet, each re-injected through the kernel net stack) and sender TUN read ~7%. Critically, recvmmsg was 0.03% — so the originally-planned recv-GRO was dropped (the re-profile refuted it) in favor of this.

How

The coalescing is entirely local to each box's yipd↔kernel-TUN boundary — no wire-format, FEC, AEAD, or replay change (each wire datagram stays one encrypted MTU packet).

  • crates/yip-io/src/tun_offload.rs (new): the virtio_net_hdr codec, IPv4/TCP parse + checksum helpers, an RX userspace-GRO coalescer (merges consecutive same-flow TCP segments into one GSO super-frame the kernel re-segments on write), a TX splitter (software-segments kernel-GRO'd reads back into MTU packets with per-segment seq/checksum), and partial-checksum completion for F_NEEDS_CSUM reads.
  • crates/yip-device: opens the TUN with IFF_VNET_HDR + TUNSETOFFLOAD, gated on a want_vnet_hdr intent (poll driver only; uring and QUIC keep a plain TUN, so the fd framing always matches its consumer), with feature-fallback to plain on EINVAL.
  • crates/yip-io/src/poll.rs, bin/yipd: drain_tun splits on read, drain_udp coalesces on write (flushing at end-of-burst), threaded through run_poll; yipd passes want_vnet_hdr and the negotiated state.

Non-coalescible traffic (UDP, pings, flow changes, out-of-order) passes through as singletons at zero cost; an unsupported kernel falls back to plain per-packet TUN I/O. unsafe stays confined to yip-io/yip-device; yipd remains #![forbid(unsafe_code)].

Results

Spike (gate PASSED): on the target kernel, coalesced GSO TUN writes are 13.8× cheaper per segment than per-packet writes; the write succeeded, confirming the virtio_net_hdr constants.

Real-hardware A/B (two 1-core AMD EPYC virtio VPSes, bulk TCP -P32, perf on the receiver):

metric baseline (no offload) 4b (offload)
receiver tun_chr_write_iter 19.0% 14.6%
throughput 51.7 Mbit/s 56.4 Mbit/s

The mechanism works — coalescing engages under load and cuts the targeted TUN-write cost ~23%. Honest caveat: end-to-end throughput barely moves on that 24 ms-RTT / same-core-iperf path because it's RTT/window/contention-capped, not TUN-CPU-bound at ~50 Mbit/s. The full win lands on low-RTT / high-throughput single flows where TUN-write is the actual bottleneck and same-flow bursts are dense. It's a safe reduction of a real cost with upside on faster paths, costing nothing where it can't coalesce. Full analysis in crates/yip-bench/RESULTS.md.

Correctness

  • cargo test --workspace 0 failures; tun_offload has ~14 unit tests including a byte-exact split↔coalesce round-trip.
  • netns ping / ping_under_loss (10%) / arq_recovers_bulk_loss PASS under both poll and uring drivers.
  • TCP-in-tunnel verified data-intact (the coalescing path end-to-end).

A bug the process caught

The netns gate found 0% bulk delivery (while pings passed — subtle). Root cause: vnet-hdr TUN reads deliver large packets with incomplete L4 checksums (F_NEEDS_CSUM — the kernel offloads them), which split_gro passed through raw, so yipd shipped bad-checksum packets the far kernel silently dropped. The Task-0 spike had only tested the write-side checksum semantics. Fixed with complete_partial_csum + a regression test.

Design docs

  • Spec: docs/superpowers/specs/2026-07-12-throughput-tun-offload-design.md
  • Plan: docs/superpowers/plans/2026-07-12-throughput-tun-offload.md

🤖 Generated with Claude Code

https://claude.ai/code/session_01RVP6NnbDMAg1iTsMMTfL86

## What Batches yipd's own TUN device I/O via a `virtio_net_hdr` (GSO/GRO offload) on the default poll path, to cut the per-packet TUN-device cost that post-4a profiling identified as the largest remaining single-core cost. Lever **4b** of the throughput campaign (4a send GSO ✓ #55 → **4b TUN offload** → 4c MTU). ## Why (post-4a re-profile) After 4a merged, `perf` on the target boxes showed the send path was no longer dominant. The new top costs were on the **TUN boundary**: receiver `tun_chr_write_iter` ~20% (one `write()` per decrypted packet, each re-injected through the kernel net stack) and sender TUN read ~7%. Critically, **`recvmmsg` was 0.03%** — so the originally-planned recv-GRO was dropped (the re-profile refuted it) in favor of this. ## How The coalescing is **entirely local to each box's yipd↔kernel-TUN boundary — no wire-format, FEC, AEAD, or replay change** (each wire datagram stays one encrypted MTU packet). - **`crates/yip-io/src/tun_offload.rs` (new):** the `virtio_net_hdr` codec, IPv4/TCP parse + checksum helpers, an **RX userspace-GRO coalescer** (merges consecutive same-flow TCP segments into one GSO super-frame the kernel re-segments on write), a **TX splitter** (software-segments kernel-GRO'd reads back into MTU packets with per-segment seq/checksum), and **partial-checksum completion** for `F_NEEDS_CSUM` reads. - **`crates/yip-device`:** opens the TUN with `IFF_VNET_HDR` + `TUNSETOFFLOAD`, gated on a `want_vnet_hdr` intent (poll driver only; **`uring` and QUIC keep a plain TUN**, so the fd framing always matches its consumer), with feature-fallback to plain on `EINVAL`. - **`crates/yip-io/src/poll.rs`, `bin/yipd`:** `drain_tun` splits on read, `drain_udp` coalesces on write (flushing at end-of-burst), threaded through `run_poll`; yipd passes `want_vnet_hdr` and the negotiated state. Non-coalescible traffic (UDP, pings, flow changes, out-of-order) passes through as **singletons at zero cost**; an unsupported kernel falls back to plain per-packet TUN I/O. `unsafe` stays confined to `yip-io`/`yip-device`; `yipd` remains `#![forbid(unsafe_code)]`. ## Results **Spike (gate PASSED):** on the target kernel, coalesced GSO TUN writes are **13.8× cheaper per segment** than per-packet writes; the write succeeded, confirming the `virtio_net_hdr` constants. **Real-hardware A/B** (two 1-core AMD EPYC virtio VPSes, bulk TCP `-P32`, `perf` on the receiver): | metric | baseline (no offload) | 4b (offload) | |---|---|---| | receiver `tun_chr_write_iter` | 19.0% | **14.6%** | | throughput | 51.7 Mbit/s | 56.4 Mbit/s | The mechanism **works** — coalescing engages under load and cuts the targeted TUN-write cost ~23%. **Honest caveat:** end-to-end throughput barely moves on that 24 ms-RTT / same-core-`iperf` path because it's RTT/window/contention-capped, *not* TUN-CPU-bound at ~50 Mbit/s. The full win lands on **low-RTT / high-throughput single flows** where TUN-write is the actual bottleneck and same-flow bursts are dense. It's a safe reduction of a real cost with upside on faster paths, costing nothing where it can't coalesce. Full analysis in `crates/yip-bench/RESULTS.md`. ## Correctness - `cargo test --workspace` 0 failures; `tun_offload` has ~14 unit tests including a **byte-exact split↔coalesce round-trip**. - netns `ping` / `ping_under_loss` (10%) / `arq_recovers_bulk_loss` PASS under **both** poll and uring drivers. - **TCP-in-tunnel verified data-intact** (the coalescing path end-to-end). ## A bug the process caught The netns gate found **0% bulk delivery** (while pings passed — subtle). Root cause: vnet-hdr TUN *reads* deliver large packets with **incomplete L4 checksums** (`F_NEEDS_CSUM` — the kernel offloads them), which `split_gro` passed through raw, so yipd shipped bad-checksum packets the far kernel silently dropped. The Task-0 spike had only tested the *write*-side checksum semantics. Fixed with `complete_partial_csum` + a regression test. ## Design docs - Spec: `docs/superpowers/specs/2026-07-12-throughput-tun-offload-design.md` - Plan: `docs/superpowers/plans/2026-07-12-throughput-tun-offload.md` 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01RVP6NnbDMAg1iTsMMTfL86
Sign in to join this conversation.
No description provided.