From 8713d4acb174c40b76c0276a9bd052f84c8e2325 Mon Sep 17 00:00:00 2001 From: JackDoan Date: Tue, 8 Sep 2026 09:40:51 -0500 Subject: [PATCH] windows: Disable DAD and router discovery on the ipv6 interface Follow-up to #1871, which put a handle on the v6 interface for the first time. Nothing answers on the far side of the adapter but nebula, and nebula drops the probes. Duplicate address detection only holds the overlay address tentative for a round it can never lose, and router solicitations leave for a multicast group nebula has no host for. Both come off on the v6 handle, the same treatment wireguard-windows gives its adapter. The v4 handle is untouched: the adapter has no link layer for ARP-based conflict detection, and #1871 deliberately left the v4-only path alone. Smoke asserts DadTransmits=0 and RouterDiscovery=Disabled on the v6 interface. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01J34ExJUUPKWUvr2g5GMREi --- .github/workflows/smoke/smoke-windows.ps1 | 8 ++++++++ overlay/tun_windows.go | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/smoke/smoke-windows.ps1 b/.github/workflows/smoke/smoke-windows.ps1 index e94768a9..fbc4dede 100644 --- a/.github/workflows/smoke/smoke-windows.ps1 +++ b/.github/workflows/smoke/smoke-windows.ps1 @@ -228,6 +228,14 @@ try { Write-Host "OK: $DevName $family NlMtu=$Mtu" } + # Both are set on the same handle as the v6 NlMtu, so by now they are either applied or never will be. + Wait-Until -TimeoutSec 30 -What "$DevName IPv6 DadTransmits=0 RouterDiscovery=Disabled" -Predicate { + if ($lhProc.HasExited) { throw "lighthouse exited (code $($lhProc.ExitCode)) before the v6 interface was configured" } + $rows = @(Get-NetIPInterface -InterfaceAlias $DevName -AddressFamily IPv6 -ErrorAction SilentlyContinue) + $rows.Count -gt 0 -and -not ($rows | Where-Object { $_.DadTransmits -ne 0 -or "$($_.RouterDiscovery)" -ne 'Disabled' }) + } + Write-Host "OK: $DevName IPv6 DadTransmits=0 RouterDiscovery=Disabled" + Wait-Until -TimeoutSec 30 -What "WSL nebula1 with $Ip2" -Predicate { if ($peerProc.HasExited) { throw "peer exited (code $($peerProc.ExitCode)) before tun was ready" } $r = wsl -d $Distro -u root -- bash -c "ip -o addr show nebula1 2>/dev/null | grep -q 'inet $Ip2' && echo yes" diff --git a/overlay/tun_windows.go b/overlay/tun_windows.go index 56c18131..d869b353 100644 --- a/overlay/tun_windows.go +++ b/overlay/tun_windows.go @@ -218,7 +218,8 @@ func (t *winTun) addRoutes(logErrors bool) error { return t.setMTU(luid, foundDefault4, carriesV6) } -// setMTU applies tun.mtu per address family. The default route metric rides along on the v4 handle. +// setMTU applies tun.mtu per address family. The default route metric rides along on the v4 handle, DAD and +// router discovery come off with the v6 one. func (t *winTun) setMTU(luid winipcfg.LUID, foundDefault4, carriesV6 bool) error { ipif, err := luid.IPInterface(windows.AF_INET) if err != nil { @@ -249,6 +250,11 @@ func (t *winTun) setMTU(luid winipcfg.LUID, foundDefault4, carriesV6 bool) error } ipif6.NLMTU = uint32(t.MTU) + // Nothing answers on the far side of this adapter but nebula, which drops the probes. DAD only holds the + // address tentative for a round it can never lose, and solicitations only invite RAs we would drop anyway. + // wireguard-windows turns both off on the same handle. + ipif6.DadTransmits = 0 + ipif6.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled if err := ipif6.Set(); err != nil { return fmt.Errorf("failed to set ipv6 interface: %w", err) }