ADR 0009: Pluggable Overlay Underlay (ipip / gre, then fou / gue)
Status
Proposed; Accepted for experimental implementation — 2026-06-01.
Builds on the CloudEdge overlay/SAM dataplane (ADR 0006, Selective Address Mobility) and the zone-independent PMTU/MSS clamp (#53/#68). Experimental.
Context
The CloudEdge overlay (OverlayPeer) currently uses WireGuard as its only
implemented underlay. Over a trusted private underlay — ExpressRoute,
DirectConnect, FastConnect, VPC/VNet peering — WireGuard's encryption is redundant
and its ~80-byte overhead is pure cost. We want to let the operator pick a lighter,
lower-overhead L3 transport when the underlay is already trusted, without
changing how addresses are delivered.
The overlay is already abstracted at the right seams (confirmed in code):
- Delivery is underlay-independent.
hybrid.RouteTarget(peer)maps anOverlayPeer.Underlay.Typeto(device, gateway), and the/32delivery routes (RemoteAddressClaim/HybridRoute) point at that device. Adding a transport is a newswitchcase. - MTU / MSS clamp is parameterized.
hybrid.EstimateMTU = underlayMTU(interface) − overheadFor(type); the zone-independent clamp followsEstimateMTU. A new transport just needs an overhead value and an interface MTU; the clamp auto-follows.
The only real gap: device creation is WireGuard-specific (a dedicated
WireGuardInterface Kind + controller). New L3 transports need an equivalent
"create the tunnel device" resource + controller.
Decision
New Kind TunnelInterface (hybrid.routerd.net/v1alpha1)
Mirrors WireGuardInterface: a resource that owns one OS tunnel device's desired
state. OverlayPeer.Underlay stays the delivery-selection reference;
TunnelInterface is the device desired state — a clean split (inline fields on
OverlayPeer would proliferate device specs per peer and make device
ownership/idempotency/delete ambiguous).
Phase 1 fields:
mode:ipip | gre.local,remote: underlay (physical) endpoint IPs (required).address: overlay inner address (optional; otherwise set by theipv4-static-addresscontroller, as for WireGuard).mtu(optional),ttl(optional, default 64),key(GRE only; if set, +4 overhead).trustedUnderlay: true— required (see Safety).
OverlayPeer.Underlay.Type enum gains ipip, gre; .Interface names the
TunnelInterface.
New controller tunnel
A framework.FuncController reconciling TunnelInterface (Linux only in Phase 1;
other platforms report an unsupported status rather than erroring the chain):
- argv-based
ipinvocations (not string-concatenated shell), idempotent viaip link show→ add/modify/ip link del:ip link add <dev> type ipip|gre local <L> remote <R> ttl <t> [key <k>]ip link set <dev> mtu <m> up
- Address handled by the existing
ipv4-static-addresscontroller (as for WireGuard). - Status: phase, device, mode, local, remote, mtu.
Overhead, delivery, MTU
overheadFor:ipip = 20,gre = 24(outer IPv4 20 + GRE base 4); GREkeyadds 4.RouteTarget:ipip,gre→(device, "")(the/32route points at the tunnel device, like WireGuard).EstimateMTUand the PMTU/MSS clamp follow automatically; thepathMTUResourceMTUfallback gains aTunnelInterfacedefault (orspec.mtuis honoured).
Validation
OverlayPeer.Underlay.Typeenum +=ipip,gre.TunnelInterface:mode ∈ {ipip, gre};local/remoterequired, valid IPs;trustedUnderlay == truerequired (reject with a clear message otherwise); MTU/ TTL/key ranges.
Safety (hard invariant)
ipip, gre, fou, gue are unencrypted and unauthenticated — fundamentally
unlike WireGuard. They are only safe over an already-trusted underlay.
- WireGuard stays the default.
- A
TunnelInterfaceis rejected unless it setstrustedUnderlay: true— an explicit operator acknowledgment that the underlay carries plaintext. Docs/doctor warnings alone are too weak; this is a validation gate.
Phasing
- Phase 1 (this ADR's scope):
TunnelInterfaceKind +tunnelcontroller (Linuxipip/gre) +trustedUnderlaygate +RouteTarget/overhead/MTU + validation + unit/fixture tests + an example config. Tests include the deletion ordering invariant: removing theOverlayPeer/claim drops the/32route, and removing theTunnelInterfaceyields a device-delete plan; route install must tolerate a missing device. - Phase 2:
fou/gue(IPIP/GRE over UDP). To preserve theoverheadFor(type)seam, represent them as combined type strings (ipip-fou,gre-fou,ipip-gue,gre-gue) rather than a standalonefou/guetype (a barefouhas no inner mode / GRE-key context, so MTU would be ambiguous). Adds theip fou addencap-port setup; document the minimal-header overhead assumption with an escape hatch (overhead override / explicitmtu). - Phase 3: FreeBSD (
giffor ipip,gre) — different config/status surface, so not crammed into the Linux controller. - Phase 4: firewall auto-holes (raw
ipip= IP proto 4,gre= IP proto 47,fou/gue= UDP) +doctor hybridchecks.
Consequences
- The operator gains a lighter overlay transport for trusted underlays; delivery and MSS clamp are unchanged and auto-follow the new overhead.
- The encryption trade-off is explicit and gated (
trustedUnderlay: true), so the lighter transports cannot be selected by accident over an untrusted path. TunnelInterfaceis a general device-desired-state resource that Phases 2–3 extend (encap, FreeBSD) without touching the delivery/MTU seams.- No change to WireGuard behaviour or to existing deployments (default unchanged).