Skip to content

Commit 5273d4b

Browse files
authored
chore: softer soft isolation on windows (#91)
1 parent 5a2ef8e commit 5273d4b

File tree

2 files changed

+43
-98
lines changed

2 files changed

+43
-98
lines changed

net/netns/netns_windows.go

Lines changed: 17 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"math/bits"
99
"net"
1010
"net/netip"
11-
"strconv"
1211
"strings"
1312
"syscall"
1413

@@ -17,6 +16,7 @@ import (
1716
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
1817
"tailscale.com/net/interfaces"
1918
"tailscale.com/net/netmon"
19+
"tailscale.com/net/tsaddr"
2020
"tailscale.com/types/logger"
2121
)
2222

@@ -31,17 +31,6 @@ func interfaceIndex(iface *winipcfg.IPAdapterAddresses) uint32 {
3131
return iface.IfIndex
3232
}
3333

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-
4534
func control(logf logger.Logf, netMon *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
4635
return func(network, address string, c syscall.RawConn) error {
4736
return controlLogf(logf, netMon, network, address, c)
@@ -98,30 +87,17 @@ func shouldBindToDefaultInterface(logf logger.Logf, address string) bool {
9887
}
9988

10089
if coderSoftIsolation.Load() {
101-
sockAddr, err := getSockAddr(address)
90+
addr, err := getAddr(address)
10291
if err != nil {
103-
logf("[unexpected] netns: Coder soft isolation: error getting sockaddr for %q, binding to default: %v", address, err)
92+
logf("[unexpected] netns: Coder soft isolation: error getting addr for %q, binding to default: %v", address, err)
10493
return true
10594
}
106-
if sockAddr == nil {
107-
// Unspecified addresses should not be bound to any interface.
95+
if !addr.IsValid() || addr.IsUnspecified() {
96+
// Invalid or unspecified addresses should not be bound to any
97+
// interface.
10898
return false
10999
}
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)) {
100+
if tsaddr.IsCoderIP(addr) {
125101
logf("[unexpected] netns: Coder soft isolation: detected socket destined for Coder interface, binding to default")
126102
return true
127103
}
@@ -187,47 +163,31 @@ func nativeToBigEndian(i uint32) uint32 {
187163
return bits.ReverseBytes32(i)
188164
}
189165

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)
166+
// getAddr returns the netip.Addr for the given address, or an invalid address
167+
// if the address is not specified. Use addr.IsValid() to check for this.
168+
func getAddr(address string) (netip.Addr, error) {
169+
host, _, err := net.SplitHostPort(address)
194170
if err != nil {
195-
return nil, fmt.Errorf("invalid address %q: %w", address, err)
171+
return netip.Addr{}, fmt.Errorf("invalid address %q: %w", address, err)
196172
}
197173
if host == "" {
198174
// netip.ParseAddr("") will fail
199-
return nil, nil
175+
return netip.Addr{}, nil
200176
}
201177

202178
addr, err := netip.ParseAddr(host)
203179
if err != nil {
204-
return nil, fmt.Errorf("invalid address %q: %w", address, err)
180+
return netip.Addr{}, fmt.Errorf("invalid address %q: %w", address, err)
205181
}
206182
if addr.Zone() != "" {
207183
// Addresses with zones *can* be represented as a Sockaddr with extra
208184
// effort, but we don't use or support them currently.
209-
return nil, fmt.Errorf("invalid address %q, has zone: %w", address, err)
185+
return netip.Addr{}, fmt.Errorf("invalid address %q, has zone: %w", address, err)
210186
}
211187
if addr.IsUnspecified() {
212188
// 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)
189+
return netip.Addr{}, nil
219190
}
220191

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)
192+
return addr, nil
233193
}

net/netns/netns_windows_test.go

Lines changed: 26 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
package netns
55

66
import (
7-
"strconv"
7+
"fmt"
88
"testing"
99

10-
"golang.org/x/sys/windows"
10+
"tailscale.com/net/tsaddr"
1111
)
1212

1313
func TestShouldBindToDefaultInterface(t *testing.T) {
@@ -34,59 +34,44 @@ func TestShouldBindToDefaultInterface(t *testing.T) {
3434

3535
t.Run("CoderSoftIsolation", func(t *testing.T) {
3636
SetCoderSoftIsolation(true)
37-
getBestInterface = func(addr windows.Sockaddr, idx *uint32) error {
38-
*idx = 1
39-
return nil
40-
}
4137
t.Cleanup(func() {
4238
SetCoderSoftIsolation(false)
43-
getBestInterface = windows.GetBestInterfaceEx
4439
})
4540

4641
tests := []struct {
47-
address string
48-
isCoderInterface bool
49-
want bool
42+
address string
43+
want bool
5044
}{
51-
// isCoderInterface shouldn't even matter for localhost since it has
52-
// a special exemption.
53-
{"127.0.0.1:0", false, false},
54-
{"127.0.0.1:0", true, false},
55-
{"127.0.0.1:1234", false, false},
56-
{"127.0.0.1:1234", true, false},
57-
58-
{"1.2.3.4:0", false, false},
59-
{"1.2.3.4:0", true, true},
60-
{"1.2.3.4:1234", false, false},
61-
{"1.2.3.4:1234", true, true},
45+
// localhost should still not bind to any interface.
46+
{"127.0.0.1:0", false},
47+
{"127.0.0.1:0", false},
48+
{"127.0.0.1:1234", false},
49+
{"127.0.0.1:1234", false},
6250

6351
// Unspecified addresses should not be bound to any interface.
64-
{":1234", false, false},
65-
{":1234", true, false},
66-
{"0.0.0.0:1234", false, false},
67-
{"0.0.0.0:1234", true, false},
68-
{"[::]:1234", false, false},
69-
{"[::]:1234", true, false},
52+
{":1234", false},
53+
{":1234", false},
54+
{"0.0.0.0:1234", false},
55+
{"0.0.0.0:1234", false},
56+
{"[::]:1234", false},
57+
{"[::]:1234", false},
7058

7159
// Special cases should always bind to default:
72-
{"[::%eth0]:1234", false, true}, // zones are not supported
73-
{"1.2.3.4:", false, true}, // port is empty
74-
{"1.2.3.4:a", false, true}, // port is not a number
75-
{"1.2.3.4:-1", false, true}, // port is negative
76-
{"1.2.3.4:65536", false, true}, // port is too large
60+
{"[::%eth0]:1234", true}, // zones are not supported
61+
{"a:1234", true}, // not an IP
62+
63+
// Coder IPs should bind to default.
64+
{fmt.Sprintf("[%s]:8080", tsaddr.CoderServiceIPv6()), true},
65+
{fmt.Sprintf("[%s]:8080", tsaddr.CoderV6Range().Addr().Next()), true},
7766

67+
// Non-Coder IPs should not bind to default.
68+
{fmt.Sprintf("[%s]:8080", tsaddr.TailscaleServiceIPv6()), false},
69+
{fmt.Sprintf("%s:8080", tsaddr.TailscaleServiceIP()), false},
70+
{"1.2.3.4:8080", false},
7871
}
7972

8073
for _, test := range tests {
81-
name := test.address + " (isCoderInterface=" + strconv.FormatBool(test.isCoderInterface) + ")"
82-
t.Run(name, func(t *testing.T) {
83-
isInterfaceCoderInterface = func(_ int) bool {
84-
return test.isCoderInterface
85-
}
86-
defer func() {
87-
isInterfaceCoderInterface = isInterfaceCoderInterfaceDefault
88-
}()
89-
74+
t.Run(test.address, func(t *testing.T) {
9075
got := shouldBindToDefaultInterface(t.Logf, test.address)
9176
if got != test.want {
9277
t.Errorf("want %v, got %v", test.want, got)

0 commit comments

Comments
 (0)