@@ -13,8 +13,10 @@ import (
1313 "net/netip"
1414 "os"
1515 "strings"
16+ "sync"
1617 "syscall"
1718
19+ lru "github.com/hashicorp/golang-lru/v2"
1820 "golang.org/x/net/route"
1921 "golang.org/x/sys/unix"
2022 "tailscale.com/envknob"
@@ -33,60 +35,154 @@ var bindToInterfaceByRouteEnv = envknob.RegisterBool("TS_BIND_TO_INTERFACE_BY_RO
3335
3436var errInterfaceStateInvalid = errors .New ("interface state invalid" )
3537
36- // controlLogf marks c as necessary to dial in a separate network namespace.
37- //
38- // It's intentionally the same signature as net.Dialer.Control
39- // and net.ListenConfig.Control.
38+ // routeCache caches the results of interfaceIndexFor calls to avoid
39+ // spamming the AF_ROUTE socket. This is used for soft
40+ // isolation mode where we do many route lookups.
41+ type routeCacheEntry struct {
42+ ifIndex int
43+ err error
44+ }
45+
46+ var (
47+ routeCache * lru.Cache [string , routeCacheEntry ]
48+ routeCacheOnce sync.Once
49+ )
50+
51+ func getRouteCache () * lru.Cache [string , routeCacheEntry ] {
52+ routeCacheOnce .Do (func () {
53+ routeCache , _ = lru.New [string , routeCacheEntry ](256 )
54+ })
55+ return routeCache
56+ }
57+
58+ // ClearRouteCache clears the route cache. This should be called by the
59+ // network monitor when a link changes occur.
60+ func ClearRouteCache () {
61+ getRouteCache ().Purge ()
62+ }
63+
64+ // isInterfaceCoderInterface can be swapped out in tests.
65+ var isInterfaceCoderInterface func (int ) bool = isInterfaceCoderInterfaceDefault
66+
67+ func isInterfaceCoderInterfaceDefault (idx int ) bool {
68+ _ , tsif , err := interfaces .Coder ()
69+ return err == nil && tsif != nil && tsif .Index == idx
70+ }
71+
72+ // controlLogf binds c to the default interface if it would otherwise
73+ // be bound to the Coder interface.
4074func controlLogf (logf logger.Logf , netMon * netmon.Monitor , network , address string , c syscall.RawConn ) error {
41- if isLocalhost (address ) {
42- // Don't bind to an interface for localhost connections.
75+ if ! shouldBindToDefaultInterface (logf , netMon , address ) {
4376 return nil
4477 }
4578
46- if disableBindConnToInterface . Load () {
47- logf ( "netns_darwin: binding connection to interfaces disabled" )
79+ idx , err := getDefaultInterfaceIndex ( logf , netMon )
80+ if err != nil {
4881 return nil
4982 }
5083
51- idx , err := getInterfaceIndex (logf , netMon , address )
84+ return bindConnToInterface (c , network , address , idx , logf )
85+ }
86+
87+ // parseAddrForRouting returns the IP address for the given address, or an invalid
88+ // address if the address is not specified.
89+ func parseAddrForRouting (address string ) (netip.Addr , error ) {
90+ host , _ , err := net .SplitHostPort (address )
5291 if err != nil {
53- // callee logged
54- return nil
92+ return netip.Addr {}, fmt .Errorf ("invalid address %q: %w" , address , err )
93+ }
94+ if host == "" {
95+ // netip.ParseAddr("") will fail
96+ return netip.Addr {}, nil
5597 }
5698
57- return bindConnToInterface (c , network , address , idx , logf )
99+ addr , err := netip .ParseAddr (host )
100+ if err != nil {
101+ return netip.Addr {}, fmt .Errorf ("invalid address %q: %w" , address , err )
102+ }
103+ if addr .Zone () != "" {
104+ // Addresses with zones *can* be represented as a route lookup with extra
105+ // effort, but we don't use or support them currently.
106+ return netip.Addr {}, fmt .Errorf ("invalid address %q, has zone: %q" , address , addr .Zone ())
107+ }
108+ if addr .IsUnspecified () {
109+ // This covers the cases of 0.0.0.0 and [::].
110+ return netip.Addr {}, nil
111+ }
112+
113+ return addr , nil
58114}
59115
60- func getInterfaceIndex (logf logger.Logf , netMon * netmon.Monitor , address string ) (int , error ) {
61- // Helper so we can log errors.
62- defaultIdx := func () (int , error ) {
63- if netMon == nil {
64- idx , err := interfaces .DefaultRouteInterfaceIndex ()
65- if err != nil {
66- // It's somewhat common for there to be no default gateway route
67- // (e.g. on a phone with no connectivity), don't log those errors
68- // since they are expected.
69- if ! errors .Is (err , interfaces .ErrNoGatewayIndexFound ) {
70- logf ("[unexpected] netns: DefaultRouteInterfaceIndex: %v" , err )
71- }
72- return - 1 , err
73- }
74- return idx , nil
116+ func shouldBindToDefaultInterface (logf logger.Logf , _ * netmon.Monitor , address string ) bool {
117+ if isLocalhost (address ) {
118+ // Don't bind to an interface for localhost connections.
119+ return false
120+ }
121+
122+ if coderSoftIsolation .Load () {
123+ addr , err := parseAddrForRouting (address )
124+ if err != nil {
125+ logf ("[unexpected] netns: Coder soft isolation: error parsing address %q, binding to default: %v" , address , err )
126+ return true
75127 }
76- state := netMon . InterfaceState ()
77- if state == nil {
78- return - 1 , errInterfaceStateInvalid
128+ if ! addr . IsValid () {
129+ // Unspecified addresses should not be bound to any interface.
130+ return false
79131 }
80132
81- if iface , ok := state .Interface [state .DefaultRouteInterface ]; ok {
82- return iface .Index , nil
133+ // Ask Darwin routing table to find the best interface for this address
134+ // by using cached route lookups to avoid spamming the AF_ROUTE socket.
135+ idx , err := getBestInterfaceCached (addr )
136+ if err != nil {
137+ logf ("[unexpected] netns: Coder soft isolation: error getting best interface, binding to default: %v" , err )
138+ return true
83139 }
140+
141+ if isInterfaceCoderInterface (idx ) {
142+ logf ("[unexpected] netns: Coder soft isolation: detected socket destined for Coder interface, binding to default" )
143+ return true
144+ }
145+
146+ // It doesn't look like our own interface, so we don't need to bind the
147+ // socket to the default interface.
148+ return false
149+ }
150+
151+ // The default isolation behavior is to always bind to the default
152+ // interface.
153+ return true
154+ }
155+
156+ func getDefaultInterfaceIndex (logf logger.Logf , netMon * netmon.Monitor ) (int , error ) {
157+ if netMon == nil {
158+ idx , err := interfaces .DefaultRouteInterfaceIndex ()
159+ if err != nil {
160+ // It's somewhat common for there to be no default gateway route
161+ // (e.g. on a phone with no connectivity), don't log those errors
162+ // since they are expected.
163+ if ! errors .Is (err , interfaces .ErrNoGatewayIndexFound ) {
164+ logf ("[unexpected] netns: DefaultRouteInterfaceIndex: %v" , err )
165+ }
166+ return - 1 , err
167+ }
168+ return idx , nil
169+ }
170+
171+ state := netMon .InterfaceState ()
172+ if state == nil {
84173 return - 1 , errInterfaceStateInvalid
85174 }
86175
176+ if iface , ok := state .Interface [state .DefaultRouteInterface ]; ok {
177+ return iface .Index , nil
178+ }
179+ return - 1 , errInterfaceStateInvalid
180+ }
181+
182+ func getInterfaceIndex (logf logger.Logf , netMon * netmon.Monitor , address string ) (int , error ) {
87183 useRoute := bindToInterfaceByRoute .Load () || bindToInterfaceByRouteEnv ()
88184 if ! useRoute {
89- return defaultIdx ( )
185+ return getDefaultInterfaceIndex ( logf , netMon )
90186 }
91187
92188 host , _ , err := net .SplitHostPort (address )
@@ -99,21 +195,21 @@ func getInterfaceIndex(logf logger.Logf, netMon *netmon.Monitor, address string)
99195 addr , err := netip .ParseAddr (host )
100196 if err != nil {
101197 logf ("[unexpected] netns: error parsing address %q: %v" , host , err )
102- return defaultIdx ( )
198+ return getDefaultInterfaceIndex ( logf , netMon )
103199 }
104200
105201 idx , err := interfaceIndexFor (addr , true /* canRecurse */ )
106202 if err != nil {
107203 logf ("netns: error in interfaceIndexFor: %v" , err )
108- return defaultIdx ( )
204+ return getDefaultInterfaceIndex ( logf , netMon )
109205 }
110206
111207 // Verify that we didn't just choose the Coder interface;
112208 // if so, we fall back to binding from the default.
113209 _ , tsif , err2 := interfaces .Coder ()
114210 if err2 == nil && tsif != nil && tsif .Index == idx {
115211 logf ("[unexpected] netns: interfaceIndexFor returned Coder interface" )
116- return defaultIdx ( )
212+ return getDefaultInterfaceIndex ( logf , netMon )
117213 }
118214
119215 return idx , err
@@ -225,6 +321,31 @@ func interfaceIndexFor(addr netip.Addr, canRecurse bool) (int, error) {
225321 return 0 , fmt .Errorf ("no valid address found" )
226322}
227323
324+ // getBestInterfaceCached returns the interface index that we should bind to in
325+ // order to send traffic to the provided address, using a cache to avoid
326+ // spamming the AF_ROUTE socket.
327+ func getBestInterfaceCached (addr netip.Addr ) (int , error ) {
328+ cache := getRouteCache ()
329+ key := addr .String ()
330+
331+ // Check cache first
332+ if entry , ok := cache .Get (key ); ok {
333+ return entry .ifIndex , entry .err
334+ }
335+
336+ // Cache miss, do the actual lookup
337+ idx , err := interfaceIndexFor (addr , true /* canRecurse */ )
338+
339+ // Cache the result
340+ entry := routeCacheEntry {
341+ ifIndex : idx ,
342+ err : err ,
343+ }
344+ cache .Add (key , entry )
345+
346+ return idx , err
347+ }
348+
228349// SetListenConfigInterfaceIndex sets lc.Control such that sockets are bound
229350// to the provided interface index.
230351func SetListenConfigInterfaceIndex (lc * net.ListenConfig , ifIndex int ) error {
0 commit comments