Compare commits

...

1 Commits

Author SHA1 Message Date
Brad Fitzpatrick
4800b25dab net/interfaces: ignore non-Tailscale ULA address changes for state equality
Updates #9040

Change-Id: I7fe66a23039c6347ae5458745b709e7ebdcce245
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-08-23 11:48:05 -07:00
2 changed files with 146 additions and 3 deletions

View File

@@ -456,7 +456,8 @@ func UseInterestingInterfaces(i Interface, ips []netip.Prefix) bool {
}
// UseInterestingIPs is an IPFilter that reports whether ip is an interesting IP address.
// An IP address is interesting if it is neither a loopback nor a link local unicast IP address.
// An IP address is interesting if it is not a loopback, link local unicast IP address,
// or non-Tailscale Unique Local Address.
func UseInterestingIPs(ip netip.Addr) bool {
return isInterestingIP(ip)
}
@@ -675,11 +676,22 @@ func anyInterestingIP(pfxs []netip.Prefix) bool {
return false
}
// ulaRange is the Unique Local IPv6 Unicast Address prefix.
// See https://datatracker.ietf.org/doc/html/rfc4193.
var ulaRange = netip.MustParsePrefix("fc00::/7")
// isInterestingIP reports whether ip is an interesting IP that we
// should log in interfaces.State logging. We don't need to show
// localhost or link-local addresses.
// loopback, link-local addresses, or non-Tailscale ULA addresses.
func isInterestingIP(ip netip.Addr) bool {
return !ip.IsLoopback() && !ip.IsLinkLocalUnicast()
if ip.IsLoopback() || ip.IsLinkLocalUnicast() {
return false
}
// Ignore non-Tailscale ULA addresses.
if ip.Is6() && ulaRange.Contains(ip) && !tsaddr.TailscaleULARange().Contains(ip) {
return false
}
return true
}
var altNetInterfaces func() ([]Interface, error)

View File

@@ -10,6 +10,7 @@ import (
"testing"
"tailscale.com/tstest"
"tailscale.com/util/mak"
)
func TestGetState(t *testing.T) {
@@ -254,3 +255,133 @@ func TestStateString(t *testing.T) {
})
}
}
func TestIsInterestingIP(t *testing.T) {
tests := []struct {
ip string
want bool
}{
{"fd7a:115c:a1e0:ab12:4843:cd96:624a:4603", true},
{"fd15:bbfa:c583:4fce:f4fb:4ff:fe1a:4148", false},
{"10.2.3.4", true},
{"127.0.0.1", false},
{"::1", false},
{"2001::2", true},
{"169.254.1.2", false},
{"fe80::1", false},
}
for _, tt := range tests {
if got := isInterestingIP(netip.MustParseAddr(tt.ip)); got != tt.want {
t.Errorf("isInterestingIP(%q) = %v, want %v", tt.ip, got, tt.want)
}
}
}
func TestEqualFiltered(t *testing.T) {
tests := []struct {
name string
s1, s2 *State
want bool
}{
{
name: "eq_nil",
want: true,
},
{
name: "nil_mix",
s2: new(State),
want: false,
},
{
name: "eq",
s1: &State{
DefaultRouteInterface: "foo",
InterfaceIPs: map[string][]netip.Prefix{
"foo": {netip.MustParsePrefix("10.0.1.2/16")},
},
},
s2: &State{
DefaultRouteInterface: "foo",
InterfaceIPs: map[string][]netip.Prefix{
"foo": {netip.MustParsePrefix("10.0.1.2/16")},
},
},
want: true,
},
{
name: "default-route-changed",
s1: &State{
DefaultRouteInterface: "foo",
InterfaceIPs: map[string][]netip.Prefix{
"foo": {netip.MustParsePrefix("10.0.1.2/16")},
},
},
s2: &State{
DefaultRouteInterface: "bar",
InterfaceIPs: map[string][]netip.Prefix{
"foo": {netip.MustParsePrefix("10.0.1.2/16")},
},
},
want: false,
},
{
name: "some-interesting-ip-changed",
s1: &State{
DefaultRouteInterface: "foo",
InterfaceIPs: map[string][]netip.Prefix{
"foo": {netip.MustParsePrefix("10.0.1.2/16")},
},
},
s2: &State{
DefaultRouteInterface: "foo",
InterfaceIPs: map[string][]netip.Prefix{
"foo": {netip.MustParsePrefix("10.0.1.3/16")},
},
},
want: false,
},
{
name: "ipv6-ula-addressed-appeared",
s1: &State{
DefaultRouteInterface: "foo",
InterfaceIPs: map[string][]netip.Prefix{
"foo": {netip.MustParsePrefix("10.0.1.2/16")},
},
},
s2: &State{
DefaultRouteInterface: "foo",
InterfaceIPs: map[string][]netip.Prefix{
"foo": {
netip.MustParsePrefix("10.0.1.2/16"),
// Brad saw this address coming & going on his home LAN, possibly
// via an Apple TV Thread routing advertisement? (Issue 9040)
netip.MustParsePrefix("fd15:bbfa:c583:4fce:f4fb:4ff:fe1a:4148/64"),
},
},
},
want: true, // ignore the IPv6 ULA address on foo
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Populate dummy interfaces where missing.
for _, s := range []*State{tt.s1, tt.s2} {
if s == nil {
continue
}
for name := range s.InterfaceIPs {
if _, ok := s.Interface[name]; !ok {
mak.Set(&s.Interface, name, Interface{Interface: &net.Interface{
Name: name,
}})
}
}
}
got := tt.s1.EqualFiltered(tt.s2, UseInterestingInterfaces, UseInterestingIPs)
if got != tt.want {
t.Errorf("got %v; want %v", got, tt.want)
}
})
}
}