feat(wgtunnel): render operator OOB /32 into wg-felhom AllowedIPs (H1 Part 1, [OF-1])

renderConf appends a validated, deterministically-SORTED oob_peer_ip /32 so the
operator peer survives self-heal/restart (a runtime wg set was wiped by self-heal).
WireWireguard gains oob_peer_ip (omitempty — absent = byte-identical pre-H1 conf).
Non-hollow tests: sorted+byte-stable, lower-IP-sorts-first, absent-identical,
invalid/v6 rejected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-05 22:08:04 +02:00
parent 4b7c5bf128
commit effff53f99
3 changed files with 92 additions and 1 deletions
+50
View File
@@ -210,6 +210,56 @@ PersistentKeepalive = 25
}
}
// TestRenderConf_OOBPeerAppendedSortedAndStable is the [OF-1] core: when oob_peer_ip is set the
// operator /32 joins AllowedIPs, DETERMINISTICALLY SORTED, and two renders are BYTE-IDENTICAL (the
// conf-hash stability the reconcile loop depends on — trap 1). Absent → byte-identical to pre-H1.
func TestRenderConf_OOBPeerAppendedSortedAndStable(t *testing.T) {
// operator IP that sorts AFTER the pbs IP (10.77.0.1 < 10.77.0.250).
block := testBlock("ignored")
block.OOBPeerIP = "10.77.0.250"
c1, err := renderConf(block, vectorPrivB64, "167.233.158.164")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(c1, "AllowedIPs = 10.77.0.1/32, 10.77.0.250/32\n") {
t.Fatalf("OOB /32 not appended sorted:\n%s", c1)
}
// byte-stable across renders (no per-tick flapping).
c2, _ := renderConf(block, vectorPrivB64, "167.233.158.164")
if c1 != c2 {
t.Fatal("two renders of the same block differ — conf-hash would flap every tick")
}
// operator IP that sorts BEFORE the pbs IP → it must come FIRST (proves the sort, not append order).
lower := testBlock("ignored")
lower.OOBPeerIP = "10.76.0.9"
cl, err := renderConf(lower, vectorPrivB64, "167.233.158.164")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(cl, "AllowedIPs = 10.76.0.9/32, 10.77.0.1/32\n") {
t.Fatalf("lower OOB IP not sorted first:\n%s", cl)
}
// absent oob_peer_ip → byte-identical to the pre-H1 single-/32 line.
none := testBlock("ignored")
cn, _ := renderConf(none, vectorPrivB64, "167.233.158.164")
if !strings.Contains(cn, "AllowedIPs = 10.77.0.1/32\n") {
t.Fatalf("absent oob must render the single pbs /32:\n%s", cn)
}
// invalid oob_peer_ip is a HARD error (never a silently-dropped widening).
bad := testBlock("ignored")
bad.OOBPeerIP = "not-an-ip"
if _, err := renderConf(bad, vectorPrivB64, "167.233.158.164"); err == nil {
t.Error("invalid oob_peer_ip accepted")
}
bad.OOBPeerIP = "2a01:4f8::1" // v6 → refused (arc is v4-pinned)
if _, err := renderConf(bad, vectorPrivB64, "167.233.158.164"); err == nil {
t.Error("IPv6 oob_peer_ip accepted")
}
}
func TestScenarioA_RegisterThenApplyOrdering(t *testing.T) {
fh := &fakeHub{}
m, rr, active, _ := testManager(t, fh)