mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-29 23:31:28 +01:00 
			
		
		
		
	fixes tailscale/corp#31299 Fixes two issues: getInterfaceIndex would occasionally race with netmon's state, returning the cached default interface index after it had be changed by NWNetworkMonitor. This had the potential to cause connections to bind to the prior default. The fix here is to preferentially use the interface index provided by NWNetworkMonitor preferentially. When no interfaces are available, macOS will set the tunnel as the default interface when an exit node is enabled, potentially causing getInterfaceIndex to return utun's index. We now guard against this when taking the defaultIdx path. Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
		
			
				
	
	
		
			137 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			137 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // 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)
 | |
| 	}
 | |
| }
 |