summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2023-08-23 11:48:05 -0700
committerBrad Fitzpatrick <bradfitz@tailscale.com>2023-08-23 11:48:05 -0700
commit4800b25dab0b61828d277ecf3f7e098f4de4aea3 (patch)
treecef65e15beec73cf4a10d6f4754467b51f6a4a50
parent9089efea06200e6c9ddb323a21308ba076fab1e2 (diff)
downloadtailscale-bradfitz/ignore_ula.tar.xz
tailscale-bradfitz/ignore_ula.zip
net/interfaces: ignore non-Tailscale ULA address changes for state equalitybradfitz/ignore_ula
Updates #9040 Change-Id: I7fe66a23039c6347ae5458745b709e7ebdcce245 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
-rw-r--r--net/interfaces/interfaces.go18
-rw-r--r--net/interfaces/interfaces_test.go131
2 files changed, 146 insertions, 3 deletions
diff --git a/net/interfaces/interfaces.go b/net/interfaces/interfaces.go
index 792385317..8c8719081 100644
--- a/net/interfaces/interfaces.go
+++ b/net/interfaces/interfaces.go
@@ -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)
diff --git a/net/interfaces/interfaces_test.go b/net/interfaces/interfaces_test.go
index bb93d38bf..ab896bf77 100644
--- a/net/interfaces/interfaces_test.go
+++ b/net/interfaces/interfaces_test.go
@@ -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)
+ }
+ })
+ }
+}