summaryrefslogtreecommitdiffhomepage
path: root/net/netmon/interfaces_darwin_test.go
blob: e4b84a144a432f08851252bb0995b06482dcbfa2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

package netmon

import (
	"io"
	"net/netip"
	"os/exec"
	"testing"

	"go4.org/mem"
	"tailscale.com/util/lineiter"
	"tailscale.com/version"
)

func TestLikelyHomeRouterIPSyscallExec(t *testing.T) {
	syscallIP, _, syscallOK := likelyHomeRouterIPBSDFetchRIB()
	netstatIP, netstatIf, netstatOK := likelyHomeRouterIPDarwinExec()

	if syscallOK != netstatOK || syscallIP != netstatIP {
		t.Errorf("syscall() = %v, %v, netstat = %v, %v",
			syscallIP, syscallOK,
			netstatIP, netstatOK,
		)
	}

	if !syscallOK {
		return
	}

	def, err := defaultRoute()
	if err != nil {
		t.Errorf("defaultRoute() error: %v", err)
	}

	if def.InterfaceName != netstatIf {
		t.Errorf("syscall default route interface %s differs from netstat %s", def.InterfaceName, netstatIf)
	}
}

/*
Parse out 10.0.0.1 and en0 from:

$ netstat -r -n -f inet
Routing tables

Internet:
Destination        Gateway            Flags        Netif Expire
default            10.0.0.1           UGSc           en0
default            link#14            UCSI         utun2
10/16              link#4             UCS            en0      !
10.0.0.1/32        link#4             UCS            en0      !
...
*/
func likelyHomeRouterIPDarwinExec() (ret netip.Addr, netif string, ok bool) {
	if version.IsMobile() {
		// Don't try to do subprocesses on iOS. Ends up with log spam like:
		// kernel: "Sandbox: IPNExtension(86580) deny(1) process-fork"
		// This is why we have likelyHomeRouterIPDarwinSyscall.
		return ret, "", false
	}
	cmd := exec.Command("/usr/sbin/netstat", "-r", "-n", "-f", "inet")
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return
	}
	if err := cmd.Start(); err != nil {
		return
	}
	defer cmd.Wait()
	defer io.Copy(io.Discard, stdout) // clear the pipe to prevent hangs

	var f []mem.RO
	for lr := range lineiter.Reader(stdout) {
		lineb, err := lr.Value()
		if err != nil {
			break
		}
		line := mem.B(lineb)
		if !mem.Contains(line, mem.S("default")) {
			continue
		}
		f = mem.AppendFields(f[:0], line)
		if len(f) < 4 || !f[0].EqualString("default") {
			continue
		}
		ipm, flagsm, netifm := f[1], f[2], f[3]
		if !mem.Contains(flagsm, mem.S("G")) {
			continue
		}
		if mem.Contains(flagsm, mem.S("I")) {
			continue
		}
		ip, err := netip.ParseAddr(string(mem.Append(nil, ipm)))
		if err == nil && ip.IsPrivate() {
			ret = ip
			netif = netifm.StringCopy()
			// We've found what we're looking for.
			break
		}
	}
	return ret, netif, ret.IsValid()
}

func TestFetchRoutingTable(t *testing.T) {
	// Issue 1345: this used to be flaky on darwin.
	for range 20 {
		_, err := fetchRoutingTable()
		if err != nil {
			t.Fatal(err)
		}
	}
}

func TestUpdateLastKnownDefaultRouteInterface(t *testing.T) {
	// Pick some interface on the machine
	interfaces, err := netInterfaces()
	if err != nil || len(interfaces) == 0 {
		t.Fatalf("netInterfaces() error: %v", err)
	}

	// Set it as our last known default route interface
	ifName := interfaces[0].Name
	UpdateLastKnownDefaultRouteInterface(ifName)

	// And make sure we can get it back
	route, err := OSDefaultRoute()
	if err != nil {
		t.Fatalf("OSDefaultRoute() error: %v", err)
	}
	want, got := ifName, route.InterfaceName
	if want != got {
		t.Errorf("OSDefaultRoute() = %q, want %q", got, want)
	}
}