wgengine/router: windows: set SkipAsSource on IPv6 LL addresses

Link-local addresses on the Tailscale interface are not routable.
Ideally they would be removed, however, a concern exists that the
operating system will attempt to re-add them which would lead to
thrashing.

Setting SkipAsSource attempts to avoid production of packets using the
address as a source in any default behaviors.

Before, in powershell: `ping (hostname)` would ping the link-local
address of the Tailscale interface, and fail.
After: `ping (hostname)` now pings the link-local address on the next
highest priority metric local interface.

Fixes #4647
Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
James Tucker 2022-06-14 11:16:01 -07:00 committed by James Tucker
parent 8c5c87be26
commit 76256d22d8
2 changed files with 31 additions and 27 deletions

View File

@ -574,26 +574,8 @@ func deltaNets(a, b []*net.IPNet) (add, del []*net.IPNet) {
return
}
func excludeIPv6LinkLocal(in []*net.IPNet) (out []*net.IPNet) {
out = in[:0]
for _, n := range in {
if len(n.IP) == 16 && n.IP.IsLinkLocalUnicast() {
// Windows creates a fixed link-local address for wintun,
// which doesn't seem to route correctly. Unfortunately, LLMNR returns this
// address for lookups by the hostname, and Windows prefers using it.
// This means that local traffic addressed to the machine's hostname breaks.
//
// While we otherwise preserve link-local addresses, we delete
// this one to force lookups to use a working address.
//
// See: https://github.com/tailscale/tailscale/issues/4647
if ip, ok := netaddr.FromStdIP(n.IP); !ok || wintunLinkLocal != ip {
continue // filter this IPNet
}
}
out = append(out, n)
}
return out
func isIPv6LinkLocal(in *net.IPNet) bool {
return len(in.IP) == 16 && in.IP.IsLinkLocalUnicast()
}
// ipAdapterUnicastAddressToIPNet converts windows.IpAdapterUnicastAddress to net.IPNet.
@ -622,14 +604,27 @@ func unicastIPNets(ifc *winipcfg.IPAdapterAddresses) []*net.IPNet {
// doing the minimum number of AddAddresses & DeleteAddress calls.
// This avoids the full FlushAddresses.
//
// Any IPv6 link-local addresses are not deleted.
// Any IPv6 link-local addresses are not deleted out of caution as some
// configurations may repeatedly re-add them. Link-local addresses are adjusted
// to set SkipAsSource. SkipAsSource prevents the addresses from being addded to
// DNS locally or remotely and from being picked as a source address for
// outgoing packets with unspecified sources. See #4647 and
// https://web.archive.org/web/20200912120956/https://devblogs.microsoft.com/scripting/use-powershell-to-change-ip-behavior-with-skipassource/
func syncAddresses(ifc *winipcfg.IPAdapterAddresses, want []*net.IPNet) error {
var erracc error
got := unicastIPNets(ifc)
add, del := deltaNets(got, want)
del = excludeIPv6LinkLocal(del)
ll := make([]*net.IPNet, 0)
for _, a := range del {
// do not delete link-local addresses, and collect them for later
// applying SkipAsSource.
if isIPv6LinkLocal(a) {
ll = append(ll, a)
continue
}
err := ifc.LUID.DeleteIPAddress(*a)
if err != nil {
erracc = fmt.Errorf("deleting IP %q: %w", *a, err)
@ -643,6 +638,20 @@ func syncAddresses(ifc *winipcfg.IPAdapterAddresses, want []*net.IPNet) error {
}
}
for _, a := range ll {
mib, err := ifc.LUID.IPAddress(a.IP)
if err != nil {
erracc = fmt.Errorf("setting skip-as-source on IP %q: unable to retrieve MIB: %w", *a, err)
continue
}
if !mib.SkipAsSource {
mib.SkipAsSource = true
if err := mib.Set(); err != nil {
erracc = fmt.Errorf("setting skip-as-source on IP %q: unable to set MIB: %w", *a, err)
}
}
}
return erracc
}

View File

@ -163,11 +163,6 @@ func TestDeltaNets(t *testing.T) {
b: nets("100.84.36.11/32[4]"),
wantDel: nets("fe80::99d0:ec2d:b2e7:536b/64"),
},
{
a: excludeIPv6LinkLocal(nets("100.84.36.11/32", "fe80::99d0:ec2d:b2e7:536b/64")),
b: nets("100.84.36.11/32"),
wantDel: nets("fe80::99d0:ec2d:b2e7:536b/64"),
},
{
a: []*net.IPNet{
{