@ -574,26 +574,8 @@ func deltaNets(a, b []*net.IPNet) (add, del []*net.IPNet) {
return
return
}
}
func excludeIPv6LinkLocal ( in [ ] * net . IPNet ) ( out [ ] * net . IPNet ) {
func isIPv6LinkLocal ( in * net . IPNet ) bool {
out = in [ : 0 ]
return len ( in . IP ) == 16 && in . IP . IsLinkLocalUnicast ( )
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
}
}
// ipAdapterUnicastAddressToIPNet converts windows.IpAdapterUnicastAddress to net.IPNet.
// 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.
// doing the minimum number of AddAddresses & DeleteAddress calls.
// This avoids the full FlushAddresses.
// 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 {
func syncAddresses ( ifc * winipcfg . IPAdapterAddresses , want [ ] * net . IPNet ) error {
var erracc error
var erracc error
got := unicastIPNets ( ifc )
got := unicastIPNets ( ifc )
add , del := deltaNets ( got , want )
add , del := deltaNets ( got , want )
del = excludeIPv6LinkLocal ( del )
ll := make ( [ ] * net . IPNet , 0 )
for _ , a := range del {
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 )
err := ifc . LUID . DeleteIPAddress ( * a )
if err != nil {
if err != nil {
erracc = fmt . Errorf ( "deleting IP %q: %w" , * a , err )
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
return erracc
}
}