mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-26 03:25:35 +00:00
01286af82b
Currently, we get the "likely home router" gateway IP and then iterate through all IPs for all interfaces trying to match IPs to determine the source IP. However, on many platforms we know what interface the gateway is through, and thus we don't need to iterate through all interfaces checking IPs. Instead, use the IP address of the associated interface. This better handles the case where we have multiple interfaces on a system all connected to the same gateway, and where the first interface that we visit (as iterated by ForeachInterfaceAddress) isn't also the default internet route. Updates #8992 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: I8632f577f1136930f4ec60c76376527a19a47d1f
395 lines
8.9 KiB
Go
395 lines
8.9 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package interfaces
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net"
|
|
"net/netip"
|
|
"testing"
|
|
|
|
"tailscale.com/tstest"
|
|
)
|
|
|
|
func TestGetState(t *testing.T) {
|
|
st, err := GetState()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
j, err := json.MarshalIndent(st, "", "\t")
|
|
if err != nil {
|
|
t.Errorf("JSON: %v", err)
|
|
}
|
|
t.Logf("Got: %s", j)
|
|
t.Logf("As string: %s", st)
|
|
}
|
|
|
|
func TestLikelyHomeRouterIP(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{
|
|
// 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.
|
|
tstest.Replace(t, &likelyHomeRouterIP, func() (netip.Addr, netip.Addr, bool) {
|
|
return netip.MustParseAddr("192.168.7.1"), netip.Addr{}, true
|
|
})
|
|
|
|
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)
|
|
}
|
|
|
|
// 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")
|
|
}
|
|
})
|
|
}
|
|
|
|
// 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.
|
|
tstest.Replace(t, &likelyHomeRouterIP, func() (netip.Addr, netip.Addr, bool) {
|
|
return netip.MustParseAddr("192.168.7.1"), netip.Addr{}, true
|
|
})
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func TestIsUsableV6(t *testing.T) {
|
|
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},
|
|
{"zeros", "0::0", false},
|
|
{"Link Local", "fe80::1", false},
|
|
{"Global", "2602::1", true},
|
|
{"IPv4 public", "192.0.2.1", false},
|
|
{"IPv4 private", "192.168.1.1", false},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if got := isUsableV6(netip.MustParseAddr(test.ip)); got != test.want {
|
|
t.Errorf("isUsableV6(%s) = %v, want %v", test.name, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
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{},
|
|
},
|
|
"lo": {
|
|
Interface: &net.Interface{},
|
|
},
|
|
},
|
|
InterfaceIPs: map[string][]netip.Prefix{
|
|
"eth0": {
|
|
netip.MustParsePrefix("10.0.0.2/8"),
|
|
},
|
|
"lo": {},
|
|
},
|
|
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",
|
|
Interface: &net.Interface{
|
|
Flags: net.FlagUp,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: `interfaces.State{defaultRoute=foo (a foo thing) ifs={foo:[]} v4=false v6=false}`,
|
|
},
|
|
}
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsInterestingIP(t *testing.T) {
|
|
tests := []struct {
|
|
ip string
|
|
want bool
|
|
}{
|
|
{"fd7a:115c:a1e0:ab12:4843:cd96:624a:4603", true}, // Tailscale private ULA
|
|
{"fd15:bbfa:c583:4fce:f4fb:4ff:fe1a:4148", true}, // Other private ULA
|
|
{"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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// tests (*State).Equal
|
|
func TestEqual(t *testing.T) {
|
|
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,
|
|
},
|
|
}
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|