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
+16 -4
View File
@@ -19,8 +19,16 @@ Keep these separate. Crossing them is the main thing to review for.
- **Control plane vs data plane.** iroh carries control messages only. User IP
traffic is never tunnelled through it. The core must never parse a plugin's
payload — see `src/dataplane.rs`. Do not advertise WireGuard as available
transport before it exists; tests use an explicitly test-only capability id.
payload — see `src/dataplane/mod.rs`. Only
`src/dataplane/wireguard/announcement.rs` interprets WireGuard payloads, and
only after bounding every field. A data plane failure must never stop the
control plane.
- **Derived, not claimed.** A WireGuard peer's `AllowedIPs` are always derived
locally from its public key. Never take them from what the peer announces, or
a member can route another member's traffic to itself.
- **Plugins own their system objects.** A plugin creates and removes its own
interface and nothing else. An interface that already exists and is not ours
is refused, never adopted. Never touch routing, DNS or firewall settings.
- **Device identity vs network identity.** The iroh endpoint id is the device's
public key. `NetworkId` is derived from name + secret only. Never conflate
them, and never let one change the other.
@@ -80,7 +88,7 @@ Keep these separate. Crossing them is the main thing to review for.
| `src/proto/` | framing, message formats, membership handshake |
| `src/net.rs` | iroh endpoint adapter and observability snapshots |
| `src/agent/` | agent lifecycle, per-network runtimes, sessions, events, status |
| `src/dataplane.rs` | the contract future IP plugins implement |
| `src/dataplane/` | the contract IP plugins implement, and the WireGuard plugin |
| `tests/` | integration tests; `tests/common/` is the shared harness |
Add abstractions only at real substitution or testing boundaries. Do not add a
@@ -98,7 +106,11 @@ trait per struct. Prefer one crate with clear modules over many small crates.
happen.
- The default suite must pass with no internet, no DHT, no public relay, no
administrator rights and no changes to OS network settings. Anything needing
the internet stays out of the default set.
the internet, or root, stays out of the default set — the real WireGuard
backend's tests live in `tests/wireguard_system.rs` behind `--ignored`.
- The WireGuard backend may be substituted (`RecordingBackend`). Its key
handling, announcements, derived addressing, configuration builder and
reconciliation may not.
- Running several library instances in one process is not a test of several
system processes; do not describe it as one.