2023-01-27 21:37:20 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2020-02-21 16:13:21 +00:00
|
|
|
|
2024-04-28 04:18:18 +00:00
|
|
|
package netmon
|
2020-02-21 16:13:21 +00:00
|
|
|
|
|
|
|
import (
|
2021-03-26 16:09:12 +00:00
|
|
|
"encoding/json"
|
2021-11-20 01:04:35 +00:00
|
|
|
"net"
|
2022-07-26 03:55:44 +00:00
|
|
|
"net/netip"
|
2020-02-21 16:13:21 +00:00
|
|
|
"testing"
|
2023-03-04 00:05:23 +00:00
|
|
|
|
|
|
|
"tailscale.com/tstest"
|
2020-02-21 16:13:21 +00:00
|
|
|
)
|
|
|
|
|
2020-04-10 02:10:55 +00:00
|
|
|
func TestGetState(t *testing.T) {
|
|
|
|
st, err := GetState()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2021-03-26 16:09:12 +00:00
|
|
|
j, err := json.MarshalIndent(st, "", "\t")
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("JSON: %v", err)
|
|
|
|
}
|
|
|
|
t.Logf("Got: %s", j)
|
2020-10-02 19:07:00 +00:00
|
|
|
t.Logf("As string: %s", st)
|
2020-02-21 16:13:21 +00:00
|
|
|
}
|
2020-07-06 17:34:52 +00:00
|
|
|
|
|
|
|
func TestLikelyHomeRouterIP(t *testing.T) {
|
2023-03-04 00:05:23 +00:00
|
|
|
ipnet := func(s string) net.Addr {
|
|
|
|
ip, ipnet, err := net.ParseCIDR(s)
|
|
|
|
ipnet.IP = ip
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
return ipnet
|
|
|
|
}
|
|
|
|
|
|
|
|
mockInterfaces := []Interface{
|
|
|
|
// Interface that's not running
|
|
|
|
{
|
|
|
|
Interface: &net.Interface{
|
|
|
|
Index: 1,
|
|
|
|
MTU: 1500,
|
|
|
|
Name: "down0",
|
|
|
|
Flags: net.FlagBroadcast | net.FlagMulticast,
|
|
|
|
},
|
|
|
|
AltAddrs: []net.Addr{
|
|
|
|
ipnet("10.0.0.100/8"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Interface that's up, but only has an IPv6 address
|
|
|
|
{
|
|
|
|
Interface: &net.Interface{
|
|
|
|
Index: 2,
|
|
|
|
MTU: 1500,
|
|
|
|
Name: "ipsixonly0",
|
|
|
|
Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast | net.FlagRunning,
|
|
|
|
},
|
|
|
|
AltAddrs: []net.Addr{
|
|
|
|
ipnet("76f9:2e7d:55dd:48e1:48d0:763a:b591:b1bc/64"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Fake interface with a gateway to the internet
|
|
|
|
{
|
|
|
|
Interface: &net.Interface{
|
|
|
|
Index: 3,
|
|
|
|
MTU: 1500,
|
|
|
|
Name: "fake0",
|
|
|
|
Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast | net.FlagRunning,
|
|
|
|
},
|
|
|
|
AltAddrs: []net.Addr{
|
|
|
|
ipnet("23a1:99c9:3a88:1d29:74d4:957b:2133:3f4e/64"),
|
|
|
|
ipnet("192.168.7.100/24"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mock out the responses from netInterfaces()
|
|
|
|
tstest.Replace(t, &altNetInterfaces, func() ([]Interface, error) {
|
|
|
|
return mockInterfaces, nil
|
|
|
|
})
|
|
|
|
|
|
|
|
// Mock out the likelyHomeRouterIP to return a known gateway.
|
2023-12-19 21:27:52 +00:00
|
|
|
tstest.Replace(t, &likelyHomeRouterIP, func() (netip.Addr, netip.Addr, bool) {
|
|
|
|
return netip.MustParseAddr("192.168.7.1"), netip.Addr{}, true
|
2023-03-04 00:05:23 +00:00
|
|
|
})
|
|
|
|
|
2020-07-06 20:51:17 +00:00
|
|
|
gw, my, ok := LikelyHomeRouterIP()
|
|
|
|
if !ok {
|
2023-03-04 00:05:23 +00:00
|
|
|
t.Fatal("expected success")
|
2020-07-06 20:51:17 +00:00
|
|
|
}
|
|
|
|
t.Logf("myIP = %v; gw = %v", my, gw)
|
2023-03-04 00:05:23 +00:00
|
|
|
|
|
|
|
if want := netip.MustParseAddr("192.168.7.1"); gw != want {
|
|
|
|
t.Errorf("got gateway %v; want %v", gw, want)
|
|
|
|
}
|
|
|
|
if want := netip.MustParseAddr("192.168.7.100"); my != want {
|
|
|
|
t.Errorf("got self IP %v; want %v", my, want)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify that no IP is returned if there are no IPv4 addresses on
|
|
|
|
// local interfaces.
|
|
|
|
t.Run("NoIPv4Addrs", func(t *testing.T) {
|
|
|
|
tstest.Replace(t, &mockInterfaces, []Interface{
|
|
|
|
// Interface that's up, but only has an IPv6 address
|
|
|
|
{
|
|
|
|
Interface: &net.Interface{
|
|
|
|
Index: 2,
|
|
|
|
MTU: 1500,
|
|
|
|
Name: "en0",
|
|
|
|
Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast | net.FlagRunning,
|
|
|
|
},
|
|
|
|
AltAddrs: []net.Addr{
|
|
|
|
ipnet("76f9:2e7d:55dd:48e1:48d0:763a:b591:b1bc/64"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
_, _, ok := LikelyHomeRouterIP()
|
|
|
|
if ok {
|
|
|
|
t.Fatal("expected no success")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
net/interfaces: ensure we return valid 'self' IP in LikelyHomeRouterIP
Before this fix, LikelyHomeRouterIP could return a 'self' IP that
doesn't correspond to the gateway address, since it picks the first
private address when iterating over the set interfaces as the 'self' IP,
without checking that the address corresponds with the
previously-detected gateway.
This behaviour was introduced by accident in aaf2df7, where we deleted
the following code:
for _, prefix := range privatev4s {
if prefix.Contains(gateway) && prefix.Contains(ip) {
myIP = ip
ok = true
return
}
}
Other than checking that 'gateway' and 'ip' were private IP addresses
(which were correctly replaced with a call to the netip.Addr.IsPrivate
method), it also implicitly checked that both 'gateway' and 'ip' were a
part of the *same* prefix, and thus likely to be the same interface.
Restore that behaviour by explicitly checking pfx.Contains(gateway),
which, given that the 'ip' variable is derived from our prefix 'pfx',
ensures that the 'self' IP will correspond to the returned 'gateway'.
Fixes #10466
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Iddd2ee70cefb9fb40071986fefeace9ca2441ee6
2023-12-05 14:44:32 +00:00
|
|
|
// https://github.com/tailscale/tailscale/issues/10466
|
|
|
|
func TestLikelyHomeRouterIP_Prefix(t *testing.T) {
|
|
|
|
ipnet := func(s string) net.Addr {
|
|
|
|
ip, ipnet, err := net.ParseCIDR(s)
|
|
|
|
ipnet.IP = ip
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
return ipnet
|
|
|
|
}
|
|
|
|
|
|
|
|
mockInterfaces := []Interface{
|
|
|
|
// Valid and running interface that doesn't have a route to the
|
|
|
|
// internet, and comes before the interface that does.
|
|
|
|
{
|
|
|
|
Interface: &net.Interface{
|
|
|
|
Index: 1,
|
|
|
|
MTU: 1500,
|
|
|
|
Name: "docker0",
|
|
|
|
Flags: net.FlagUp |
|
|
|
|
net.FlagBroadcast |
|
|
|
|
net.FlagMulticast |
|
|
|
|
net.FlagRunning,
|
|
|
|
},
|
|
|
|
AltAddrs: []net.Addr{
|
|
|
|
ipnet("172.17.0.0/16"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Fake interface with a gateway to the internet.
|
|
|
|
{
|
|
|
|
Interface: &net.Interface{
|
|
|
|
Index: 2,
|
|
|
|
MTU: 1500,
|
|
|
|
Name: "fake0",
|
|
|
|
Flags: net.FlagUp |
|
|
|
|
net.FlagBroadcast |
|
|
|
|
net.FlagMulticast |
|
|
|
|
net.FlagRunning,
|
|
|
|
},
|
|
|
|
AltAddrs: []net.Addr{
|
|
|
|
ipnet("192.168.7.100/24"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mock out the responses from netInterfaces()
|
|
|
|
tstest.Replace(t, &altNetInterfaces, func() ([]Interface, error) {
|
|
|
|
return mockInterfaces, nil
|
|
|
|
})
|
|
|
|
|
|
|
|
// Mock out the likelyHomeRouterIP to return a known gateway.
|
2023-12-19 21:27:52 +00:00
|
|
|
tstest.Replace(t, &likelyHomeRouterIP, func() (netip.Addr, netip.Addr, bool) {
|
|
|
|
return netip.MustParseAddr("192.168.7.1"), netip.Addr{}, true
|
net/interfaces: ensure we return valid 'self' IP in LikelyHomeRouterIP
Before this fix, LikelyHomeRouterIP could return a 'self' IP that
doesn't correspond to the gateway address, since it picks the first
private address when iterating over the set interfaces as the 'self' IP,
without checking that the address corresponds with the
previously-detected gateway.
This behaviour was introduced by accident in aaf2df7, where we deleted
the following code:
for _, prefix := range privatev4s {
if prefix.Contains(gateway) && prefix.Contains(ip) {
myIP = ip
ok = true
return
}
}
Other than checking that 'gateway' and 'ip' were private IP addresses
(which were correctly replaced with a call to the netip.Addr.IsPrivate
method), it also implicitly checked that both 'gateway' and 'ip' were a
part of the *same* prefix, and thus likely to be the same interface.
Restore that behaviour by explicitly checking pfx.Contains(gateway),
which, given that the 'ip' variable is derived from our prefix 'pfx',
ensures that the 'self' IP will correspond to the returned 'gateway'.
Fixes #10466
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Iddd2ee70cefb9fb40071986fefeace9ca2441ee6
2023-12-05 14:44:32 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
gw, my, ok := LikelyHomeRouterIP()
|
|
|
|
if !ok {
|
|
|
|
t.Fatal("expected success")
|
|
|
|
}
|
|
|
|
t.Logf("myIP = %v; gw = %v", my, gw)
|
|
|
|
|
|
|
|
if want := netip.MustParseAddr("192.168.7.1"); gw != want {
|
|
|
|
t.Errorf("got gateway %v; want %v", gw, want)
|
|
|
|
}
|
|
|
|
if want := netip.MustParseAddr("192.168.7.100"); my != want {
|
|
|
|
t.Errorf("got self IP %v; want %v", my, want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-04 00:05:23 +00:00
|
|
|
func TestLikelyHomeRouterIP_NoMocks(t *testing.T) {
|
|
|
|
// Verify that this works properly when called on a real live system,
|
|
|
|
// without any mocks.
|
|
|
|
gw, my, ok := LikelyHomeRouterIP()
|
|
|
|
t.Logf("LikelyHomeRouterIP: gw=%v my=%v ok=%v", gw, my, ok)
|
2020-07-06 17:34:52 +00:00
|
|
|
}
|
2021-03-27 01:38:05 +00:00
|
|
|
|
2021-06-18 00:49:15 +00:00
|
|
|
func TestIsUsableV6(t *testing.T) {
|
2021-03-27 01:38:05 +00:00
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
ip string
|
|
|
|
want bool
|
|
|
|
}{
|
|
|
|
{"first ULA", "fc00::1", true},
|
|
|
|
{"Tailscale", "fd7a:115c:a1e0::1", false},
|
|
|
|
{"Cloud Run", "fddf:3978:feb1:d745::1", true},
|
2023-08-23 18:48:05 +00:00
|
|
|
{"zeros", "0::0", false},
|
2021-03-27 01:38:05 +00:00
|
|
|
{"Link Local", "fe80::1", false},
|
|
|
|
{"Global", "2602::1", true},
|
2021-07-28 18:30:06 +00:00
|
|
|
{"IPv4 public", "192.0.2.1", false},
|
|
|
|
{"IPv4 private", "192.168.1.1", false},
|
2021-03-27 01:38:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
2022-07-26 03:55:44 +00:00
|
|
|
if got := isUsableV6(netip.MustParseAddr(test.ip)); got != test.want {
|
2021-06-18 00:49:15 +00:00
|
|
|
t.Errorf("isUsableV6(%s) = %v, want %v", test.name, got, test.want)
|
2021-03-27 01:38:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-11-20 01:04:35 +00:00
|
|
|
|
2021-12-29 18:37:33 +00:00
|
|
|
func TestStateString(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
s *State
|
|
|
|
want string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "typical_linux",
|
|
|
|
s: &State{
|
|
|
|
DefaultRouteInterface: "eth0",
|
|
|
|
Interface: map[string]Interface{
|
|
|
|
"eth0": {
|
|
|
|
Interface: &net.Interface{
|
|
|
|
Flags: net.FlagUp,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"wlan0": {
|
|
|
|
Interface: &net.Interface{},
|
|
|
|
},
|
2023-08-23 18:48:05 +00:00
|
|
|
"lo": {
|
|
|
|
Interface: &net.Interface{},
|
|
|
|
},
|
2021-12-29 18:37:33 +00:00
|
|
|
},
|
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
|
|
|
InterfaceIPs: map[string][]netip.Prefix{
|
2023-04-17 22:38:24 +00:00
|
|
|
"eth0": {
|
2022-07-26 03:55:44 +00:00
|
|
|
netip.MustParsePrefix("10.0.0.2/8"),
|
2021-12-29 18:37:33 +00:00
|
|
|
},
|
2023-08-23 18:48:05 +00:00
|
|
|
"lo": {},
|
2021-12-29 18:37:33 +00:00
|
|
|
},
|
|
|
|
HaveV4: true,
|
|
|
|
},
|
|
|
|
want: `interfaces.State{defaultRoute=eth0 ifs={eth0:[10.0.0.2/8]} v4=true v6=false}`,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "default_desc",
|
|
|
|
s: &State{
|
|
|
|
DefaultRouteInterface: "foo",
|
|
|
|
Interface: map[string]Interface{
|
|
|
|
"foo": {
|
|
|
|
Desc: "a foo thing",
|
2023-08-23 18:48:05 +00:00
|
|
|
Interface: &net.Interface{
|
|
|
|
Flags: net.FlagUp,
|
|
|
|
},
|
2021-12-29 18:37:33 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2023-08-23 18:48:05 +00:00
|
|
|
want: `interfaces.State{defaultRoute=foo (a foo thing) ifs={foo:[]} v4=false v6=false}`,
|
2021-12-29 18:37:33 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
got := tt.s.String()
|
|
|
|
if got != tt.want {
|
|
|
|
t.Errorf("wrong\n got: %s\nwant: %s\n", got, tt.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2023-08-23 18:48:05 +00:00
|
|
|
|
|
|
|
// tests (*State).Equal
|
|
|
|
func TestEqual(t *testing.T) {
|
2024-04-16 20:06:13 +00:00
|
|
|
pfxs := func(addrs ...string) (ret []netip.Prefix) {
|
|
|
|
for _, addr := range addrs {
|
|
|
|
ret = append(ret, netip.MustParsePrefix(addr))
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2023-08-23 18:48:05 +00:00
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
s1, s2 *State
|
|
|
|
want bool // implies !wantMajor
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
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-interface-ips-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: "altaddrs-changed",
|
|
|
|
s1: &State{
|
|
|
|
Interface: map[string]Interface{
|
|
|
|
"foo": {AltAddrs: []net.Addr{&net.TCPAddr{IP: net.ParseIP("1.2.3.4")}}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
s2: &State{
|
|
|
|
Interface: map[string]Interface{
|
|
|
|
"foo": {AltAddrs: []net.Addr{&net.TCPAddr{IP: net.ParseIP("5.6.7.8")}}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: false,
|
|
|
|
},
|
2024-04-16 20:06:13 +00:00
|
|
|
|
|
|
|
// See tailscale/corp#19124
|
|
|
|
{
|
|
|
|
name: "interface-removed",
|
|
|
|
s1: &State{
|
|
|
|
InterfaceIPs: map[string][]netip.Prefix{
|
|
|
|
"rmnet16": pfxs("2607:1111:2222:3333:4444:5555:6666:7777/64"),
|
|
|
|
"rmnet17": pfxs("2607:9999:8888:7777:666:5555:4444:3333/64"),
|
|
|
|
"tun0": pfxs("100.64.1.2/32", "fd7a:115c:a1e0::1/128"),
|
|
|
|
"v4-rmnet16": pfxs("192.0.0.4/32"),
|
|
|
|
"wlan0": pfxs("10.0.0.111/24"), // removed below
|
|
|
|
},
|
|
|
|
},
|
|
|
|
s2: &State{
|
|
|
|
InterfaceIPs: map[string][]netip.Prefix{
|
|
|
|
"rmnet16": pfxs("2607:1111:2222:3333:4444:5555:6666:7777/64"),
|
|
|
|
"rmnet17": pfxs("2607:9999:8888:7777:666:5555:4444:3333/64"),
|
|
|
|
"tun0": pfxs("100.64.1.2/32", "fd7a:115c:a1e0::1/128"),
|
|
|
|
"v4-rmnet16": pfxs("192.0.0.4/32"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: false,
|
|
|
|
},
|
2023-08-23 18:48:05 +00:00
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
if got := tt.s2.Equal(tt.s1); got != tt.want {
|
|
|
|
t.Errorf("Equal = %v; want %v", got, tt.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|