@@ -13,8 +13,10 @@ import (
13
13
"net/netip"
14
14
"os"
15
15
"strings"
16
+ "sync"
16
17
"syscall"
17
18
19
+ lru "github.com/hashicorp/golang-lru/v2"
18
20
"golang.org/x/net/route"
19
21
"golang.org/x/sys/unix"
20
22
"tailscale.com/envknob"
@@ -33,60 +35,154 @@ var bindToInterfaceByRouteEnv = envknob.RegisterBool("TS_BIND_TO_INTERFACE_BY_RO
33
35
34
36
var errInterfaceStateInvalid = errors .New ("interface state invalid" )
35
37
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.
40
74
func 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 ) {
43
76
return nil
44
77
}
45
78
46
- if disableBindConnToInterface . Load () {
47
- logf ( "netns_darwin: binding connection to interfaces disabled" )
79
+ idx , err := getDefaultInterfaceIndex ( logf , netMon )
80
+ if err != nil {
48
81
return nil
49
82
}
50
83
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 )
52
91
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
55
97
}
56
98
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: %w" , address , err )
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
58
114
}
59
115
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
75
127
}
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
79
131
}
80
132
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
83
139
}
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 {
84
173
return - 1 , errInterfaceStateInvalid
85
174
}
86
175
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 ) {
87
183
useRoute := bindToInterfaceByRoute .Load () || bindToInterfaceByRouteEnv ()
88
184
if ! useRoute {
89
- return defaultIdx ( )
185
+ return getDefaultInterfaceIndex ( logf , netMon )
90
186
}
91
187
92
188
host , _ , err := net .SplitHostPort (address )
@@ -99,21 +195,21 @@ func getInterfaceIndex(logf logger.Logf, netMon *netmon.Monitor, address string)
99
195
addr , err := netip .ParseAddr (host )
100
196
if err != nil {
101
197
logf ("[unexpected] netns: error parsing address %q: %v" , host , err )
102
- return defaultIdx ( )
198
+ return getDefaultInterfaceIndex ( logf , netMon )
103
199
}
104
200
105
201
idx , err := interfaceIndexFor (addr , true /* canRecurse */ )
106
202
if err != nil {
107
203
logf ("netns: error in interfaceIndexFor: %v" , err )
108
- return defaultIdx ( )
204
+ return getDefaultInterfaceIndex ( logf , netMon )
109
205
}
110
206
111
207
// Verify that we didn't just choose the Coder interface;
112
208
// if so, we fall back to binding from the default.
113
209
_ , tsif , err2 := interfaces .Coder ()
114
210
if err2 == nil && tsif != nil && tsif .Index == idx {
115
211
logf ("[unexpected] netns: interfaceIndexFor returned Coder interface" )
116
- return defaultIdx ( )
212
+ return getDefaultInterfaceIndex ( logf , netMon )
117
213
}
118
214
119
215
return idx , err
@@ -225,6 +321,31 @@ func interfaceIndexFor(addr netip.Addr, canRecurse bool) (int, error) {
225
321
return 0 , fmt .Errorf ("no valid address found" )
226
322
}
227
323
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
+
228
349
// SetListenConfigInterfaceIndex sets lc.Control such that sockets are bound
229
350
// to the provided interface index.
230
351
func SetListenConfigInterfaceIndex (lc * net.ListenConfig , ifIndex int ) error {
0 commit comments