44package netns
55
66import (
7+ "fmt"
78 "math/bits"
9+ "net"
10+ "net/netip"
11+ "strconv"
812 "strings"
913 "syscall"
1014
@@ -27,20 +31,30 @@ func interfaceIndex(iface *winipcfg.IPAdapterAddresses) uint32 {
2731 return iface .IfIndex
2832}
2933
30- func control (logger.Logf , * netmon.Monitor ) func (network , address string , c syscall.RawConn ) error {
31- return controlC
34+ // getBestInterface can be swapped out in tests.
35+ var getBestInterface func (addr windows.Sockaddr , idx * uint32 ) error = windows .GetBestInterfaceEx
36+
37+ // isInterfaceCoderInterface can be swapped out in tests.
38+ var isInterfaceCoderInterface func (int ) bool = isInterfaceCoderInterfaceDefault
39+
40+ func isInterfaceCoderInterfaceDefault (idx int ) bool {
41+ _ , tsif , err := interfaces .Coder ()
42+ return err == nil && tsif != nil && tsif .Index == idx
43+ }
44+
45+ func control (logf logger.Logf , netMon * netmon.Monitor ) func (network , address string , c syscall.RawConn ) error {
46+ return func (network , address string , c syscall.RawConn ) error {
47+ return controlLogf (logf , netMon , network , address , c )
48+ }
3249}
3350
3451// controlC binds c to the Windows interface that holds a default
3552// route, and is not the Tailscale WinTun interface.
36- func controlC (network , address string , c syscall.RawConn ) error {
37- if strings .HasPrefix (address , "127." ) {
38- // Don't bind to an interface for localhost connections,
39- // otherwise we get:
40- // connectex: The requested address is not valid in its context
41- // (The derphttp tests were failing)
53+ func controlLogf (logf logger.Logf , _ * netmon.Monitor , network , address string , c syscall.RawConn ) error {
54+ if ! shouldBindToDefaultInterface (logf , address ) {
4255 return nil
4356 }
57+
4458 canV4 , canV6 := false , false
4559 switch network {
4660 case "tcp" , "udp" :
@@ -74,6 +88,54 @@ func controlC(network, address string, c syscall.RawConn) error {
7488 return nil
7589}
7690
91+ func shouldBindToDefaultInterface (logf logger.Logf , address string ) bool {
92+ if strings .HasPrefix (address , "127." ) {
93+ // Don't bind to an interface for localhost connections,
94+ // otherwise we get:
95+ // connectex: The requested address is not valid in its context
96+ // (The derphttp tests were failing)
97+ return false
98+ }
99+
100+ if coderSoftIsolation .Load () {
101+ sockAddr , err := getSockAddr (address )
102+ if err != nil {
103+ logf ("[unexpected] netns: Coder soft isolation: error getting sockaddr for %q, binding to default: %v" , address , err )
104+ return true
105+ }
106+ if sockAddr == nil {
107+ // Unspecified addresses should not be bound to any interface.
108+ return false
109+ }
110+
111+ // Ask Windows to find the best interface for this address by consulting
112+ // the routing table.
113+ //
114+ // On macOS this value gets cached, but on Windows we don't need to
115+ // because this API is very fast and doesn't require opening an AF_ROUTE
116+ // socket.
117+ var idx uint32
118+ err = getBestInterface (sockAddr , & idx )
119+ if err != nil {
120+ logf ("[unexpected] netns: Coder soft isolation: error getting best interface, binding to default: %v" , err )
121+ return true
122+ }
123+
124+ if isInterfaceCoderInterface (int (idx )) {
125+ logf ("[unexpected] netns: Coder soft isolation: detected socket destined for Coder interface, binding to default" )
126+ return true
127+ }
128+
129+ // It doesn't look like our own interface, so we don't need to bind the
130+ // socket to the default interface.
131+ return false
132+ }
133+
134+ // The default isolation behavior is to always bind to the default
135+ // interface.
136+ return true
137+ }
138+
77139// sockoptBoundInterface is the value of IP_UNICAST_IF and IPV6_UNICAST_IF.
78140//
79141// See https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options
@@ -124,3 +186,48 @@ func nativeToBigEndian(i uint32) uint32 {
124186 }
125187 return bits .ReverseBytes32 (i )
126188}
189+
190+ // getSockAddr returns the Windows sockaddr for the given address, or nil if
191+ // the address is not specified.
192+ func getSockAddr (address string ) (windows.Sockaddr , error ) {
193+ host , port , err := net .SplitHostPort (address )
194+ if err != nil {
195+ return nil , fmt .Errorf ("invalid address %q: %w" , address , err )
196+ }
197+ if host == "" {
198+ // netip.ParseAddr("") will fail
199+ return nil , nil
200+ }
201+
202+ addr , err := netip .ParseAddr (host )
203+ if err != nil {
204+ return nil , fmt .Errorf ("invalid address %q: %w" , address , err )
205+ }
206+ if addr .Zone () != "" {
207+ // Addresses with zones *can* be represented as a Sockaddr with extra
208+ // effort, but we don't use or support them currently.
209+ return nil , fmt .Errorf ("invalid address %q, has zone: %w" , address , err )
210+ }
211+ if addr .IsUnspecified () {
212+ // This covers the cases of 0.0.0.0 and [::].
213+ return nil , nil
214+ }
215+
216+ portInt , err := strconv .ParseUint (port , 10 , 16 )
217+ if err != nil {
218+ return nil , fmt .Errorf ("invalid port %q: %w" , port , err )
219+ }
220+
221+ if addr .Is4 () {
222+ return & windows.SockaddrInet4 {
223+ Port : int (portInt ), // nolint:gosec // portInt is always in range
224+ Addr : addr .As4 (),
225+ }, nil
226+ } else if addr .Is6 () {
227+ return & windows.SockaddrInet6 {
228+ Port : int (portInt ), // nolint:gosec // portInt is always in range
229+ Addr : addr .As16 (),
230+ }, nil
231+ }
232+ return nil , fmt .Errorf ("invalid address %q, is not IPv4 or IPv6: %w" , address , err )
233+ }
0 commit comments