pkg/routing: unbind WireGuard TUN from VRF on teardown/rebind (#5120)#5408
Merged
Conversation
The WireGuard apply path bound the persistent wgN TUN to a VRF only in
the non-empty routing-instance case and had no else/unbind branch, and
the WG config-removal prune in Apply reconciled only addresses. So
removing the `routing-instance` stanza from a still-configured tunnel,
or removing the whole persistent tunnel, left wgN mastered to the old
vrf-<name> indefinitely. Traffic then stays isolated in — or leaks
through — the wrong routing table after a successful commit, carried
indefinitely by the persistent interface. WG bypassed the identity-gated
appliedRI claim machinery every other tunnel uses, so there was no
symmetric unbind for a successful bind.
Route WireGuard through the same claim machinery as GRE/IPIP:
- applyWireguardTunLocked now calls reconcileVRFClaimLocked instead of
a bind-only block. A successful bind records appliedRI; an empty
desired RI on a still-configured tunnel identity-gated-unbinds.
- Extract the identity-gated unbind tail of reconcileVRFClaimLocked
into unbindVRFClaimLocked(name, link) -> retry. It clears the master
only when the link's CURRENT master IS the claimed RI's vrf- device
(never a foreign bind we do not own), clears the claim on success /
master==0 / identity-mismatch / VRF-not-found, and RETAINS the claim
(returns retry=true) on a transient VRF lookup or LinkSetNoMaster
error.
- The WG config-removal prune in Apply calls unbindVRFClaimLocked on
the KEPT persistent link and folds its retry into the existing
address-prune retry (retaining the wgConfigured entry so the next
Apply retries). A not-found device clears the claim (the binding
died with the device).
- Preserve appliedRI across the non-WG->WG same-name handoff: a reused
anchor TUN keeps its kernel master, so carrying the prior claim lets
the WG reconcile unbind an anchor->WG-no-RI device instead of
stranding it. A GRE->WG handoff recreates the link (master==0), so
the carried claim self-clears.
Add four fail-on-revert tests covering the bind + config-removal unbind,
the routing-instance-removal unbind, the foreign-master veto, and the
transient-unbind retry. Verified RED on reverted source (the removal,
RI-removal, and retry tests fail without the fix; the foreign-master
safety guard passes both ways by design). go build ./... and
go test ./pkg/routing/... are green; gofmt clean.
Update pkg/routing/README.md: the WG-removal-prune and VRF-claims
sections now document the shared unbind and drop the #1434 "VRF
membership is not unbound" residual.
Closes #5120
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015oARShYtiJJ2H4UB4nXGqi
Owner
Author
|
@copilot review |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #5120
Problem
applyWireguardTunLocked(pkg/routing/tunnel.go) bound the persistentwgNTUN to a VRF only in the non-empty routing-instance case andhad no else/unbind branch. The WG config-removal prune in
Applyreconciled only addresses (#1919), never VRF membership. WireGuard also
bypassed the identity-gated
appliedRIclaim machinery every othertunnel (GRE/IPIP/anchor) uses.
Result: removing the
routing-instancestanza from a still-configuredWG tunnel — or removing the whole persistent tunnel — left
wgNmastered to the old
vrf-<name>indefinitely. Traffic then staysisolated in / leaks through the wrong routing table after a successful
commit, carried by the persistent interface. The source comment on
origin/master explicitly conceded the gap (boundary (3), #1434).
Verified GENUINE on current origin/master (
b6703ee9a): the WG bind attunnel.gowas a bareif tc.RoutingInstance != "" { Bind... }with nounbind, and the prune loop only called
pruneAppliedAddrsLocked.Fix
Route WireGuard through the same identity-gated claim machinery as
other tunnels:
applyWireguardTunLockednow callsreconcileVRFClaimLocked(recordsappliedRIon a successful bind; identity-gated-unbinds when thedesired RI is empty on a still-configured tunnel).
reconcileVRFClaimLockedinto a sharedunbindVRFClaimLocked(name, link) -> retry. It clears the master onlywhen the link's CURRENT master is the claimed RI's
vrf-device(never a foreign bind), clears the claim on success / master==0 /
identity-mismatch / VRF-not-found, and retains the claim
(
retry=true) on a transient VRF-lookup orLinkSetNoMastererror.ApplycallsunbindVRFClaimLockedon the KEPTwgNlink and folds its retry intothe existing address-prune retry (retaining the
wgConfiguredentryfor the next Apply). A not-found device clears the claim.
appliedRIacross the non-WG→WG same-name handoff so areused anchor TUN's prior VRF claim carries into the WG reconcile
(else an anchor→WG-no-RI device would strand its old master). A
GRE→WG handoff recreates the link (master==0), so the carried claim
self-clears.
The
wgNlink itself is still KEPT on removal (#1432 S2a — tearing itflaps the live peer/session); only the stale VRF enslavement is cleared.
Tests (RED-on-revert proven)
Four new tests in
tunnel_reconcile_test.go:TestWireguardVRFUnboundOnConfigRemoval— bind, then whole-tunnelremoval →
wgNkept,LinkSetNoMasterissued, master 0, claimcleared, idempotent.
TestWireguardVRFUnboundOnRoutingInstanceRemoval— RI stanza removedfrom a still-configured tunnel → unbound.
TestWireguardVRFForeignMasterNeverUnbound— a claim-less WG tunnelwith a foreign master is never unbound (identity gate).
TestWireguardVRFUnbindTransientRetried— a transientLinkSetNoMasterfailure retains the claim +wgConfiguredandretries to success.
RED-on-revert:
git stashoftunnel.goalone (tests kept) →the removal, RI-removal, and retry tests FAIL (
appliedRIneverrecorded,
noMaster=[]). The foreign-master safety guard passes bothways by design. Restored → all four PASS.
Validation
go build ./...exit 0go test ./pkg/routing/...PASS (full package, not just new tests)go vet ./pkg/routing/...cleangofmt -lclean on touched filespkg/routing/README.md(WG-removal-prune + VRF-claimssections; dropped the Implement Multi-Tunnel WireGuard Support #1434 "VRF membership is not unbound" residual)
No cluster smoke (per task scope — control-plane netlink reconcile,
covered by the seam tests).
🤖 Generated with Claude Code
https://claude.ai/code/session_015oARShYtiJJ2H4UB4nXGqi