Implement the WireGuard data plane plugin

The first IP plugin, built on the data plane boundary the core already had.

Plugin:
- one X25519 key per network in the plugin's own wireguard.sqlite, separate
  from the iroh identity and from the network secret; a damaged store is an
  error, never a silently regenerated identity
- deterministic IPv6 ULA overlay: every member derives the same /64 from the
  network id and its own /128 from its WireGuard public key, so no
  coordinator allocates addresses
- AllowedIPs are derived locally, never taken from a peer's announcement, so
  a member cannot claim another member's overlay address; a mismatched claim
  is rejected
- bounded, versioned, validated announcement carried as the existing opaque
  capability payload, which the core still never parses
- each agent builds its own full-mesh configuration (N-1 peers) and
  reconciles on every change and on a timer, repairing drift
- WireguardBackend abstraction: RecordingBackend in memory, and WgToolBackend
  driving real wg/ip on Linux, split into a pure planner plus parsers and a
  thin executor so everything interesting is testable without root

Core, three generic additions the plugin needed:
- IpPlugin::on_network_activated, so per-network state is ready before peers
- PluginContext for re-announcements and error reports from plugin tasks,
  with errors counted by the owning network runtime
- IpPlugin::shutdown, awaited with a grace period, so system objects go away

94 tests pass offline with no privileges: 35 new WireGuard unit tests and 12
integration tests over real iroh connections. The real wg/ip backend needs
root and is behind --ignored in tests/wireguard_system.rs; it was not run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
tsunagi
2026-09-21 11:07:31 +01:00
co-authored by Claude Opus 5
parent 7cea9afa37
commit ea7aaa2b69
28 changed files with 4790 additions and 46 deletions
+12 -7
View File
@@ -12,12 +12,16 @@ authentication, participant announcements, capability exchange and — later —
state synchronisation and delivery of IP-plugin data.
**Data plane.** Separate plugins create IP connectivity. WireGuard is the first
planned one; none exists yet. Plugin keys, configuration and lifecycle are
separate from iroh identity and from the network secret. The core moves an
opaque, bounded payload and never parses it.
one and is implemented — see [wireguard.md](wireguard.md). Plugin keys,
configuration and lifecycle are separate from iroh identity and from the
network secret. The core moves an opaque, bounded payload and never parses it.
Only control messages travel over iroh. User IP traffic is not tunnelled
through it.
through it; WireGuard packets travel over WireGuard's own UDP sockets.
A plugin talks to the core through three narrow hooks — `on_network_activated`,
a `PluginContext` for re-announcements and error reports, and a bounded
`shutdown` — so the core never learns anything protocol-specific.
A data plane failure never stops the daemon: the control plane keeps running
and the agent stays manageable.
@@ -32,11 +36,12 @@ and the agent stays manageable.
| `proto` | message format, handshake, membership proof, protocol limits |
| `agent` | agent and per-network lifecycle, reconnect, in-process message routing |
| `storage` | mandatory state and the separately recoverable cache |
| `dataplane` | the minimal contract future IP plugins implement |
| `dataplane` | the contract IP plugins implement, plus the WireGuard plugin |
Abstractions exist only where something is really substituted or really needs
isolating for tests: `NetworkDiscovery` and `IpPlugin`. Everything else is a
concrete type.
isolating for tests: `NetworkDiscovery`, `IpPlugin`, and `WireguardBackend`
(which is what lets the plugin be tested in full without root). Everything else
is a concrete type.
## Runtime shape