Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit5319d47

Browse files
authored
chore: add support for tailscale soft isolation in VPN (#19023)
1 parent28789d7 commit5319d47

File tree

10 files changed

+205
-126
lines changed

10 files changed

+205
-126
lines changed

‎go.mod‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ replace github.com/tcnksm/go-httpstat => github.com/coder/go-httpstat v0.0.0-202
3636

3737
// There are a few minor changes we make to Tailscale that we're slowly upstreaming. Compare here:
3838
// https://github.com/tailscale/tailscale/compare/main...coder:tailscale:main
39-
replacetailscale.com =>github.com/coder/tailscalev1.1.1-0.20250611020837-f14d20d23d8c
39+
replacetailscale.com =>github.com/coder/tailscalev1.1.1-0.20250724015444-494197765996
4040

4141
// This is replaced to include
4242
// 1. a fix for a data race: c.f. https://github.com/tailscale/wireguard-go/pull/25

‎go.sum‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -926,8 +926,8 @@ github.com/coder/serpent v0.10.0 h1:ofVk9FJXSek+SmL3yVE3GoArP83M+1tX+H7S4t8BSuM=
926926
github.com/coder/serpentv0.10.0/go.mod h1:cZFW6/fP+kE9nd/oRkEHJpG6sXCtQ+AX7WMMEHv0Y3Q=
927927
github.com/coder/sshv0.0.0-20231128192721-70855dedb788 h1:YoUSJ19E8AtuUFVYBpXuOD6a/zVP3rcxezNsoDseTUw=
928928
github.com/coder/sshv0.0.0-20231128192721-70855dedb788/go.mod h1:aGQbuCLyhRLMzZF067xc84Lh7JDs1FKwCmF1Crl9dxQ=
929-
github.com/coder/tailscalev1.1.1-0.20250611020837-f14d20d23d8c h1:d/qBIi3Ez7KkopRgNtfdvTMqvqBg47d36qVfkd3C5EQ=
930-
github.com/coder/tailscalev1.1.1-0.20250611020837-f14d20d23d8c/go.mod h1:l7ml5uu7lFh5hY28lGYM4b/oFSmuPHYX6uk4RAu23Lc=
929+
github.com/coder/tailscalev1.1.1-0.20250724015444-494197765996 h1:9x+ouDw9BKW1tdGzuQOWGMT2XkWLs+QQjeCrxYuU1lo=
930+
github.com/coder/tailscalev1.1.1-0.20250724015444-494197765996/go.mod h1:l7ml5uu7lFh5hY28lGYM4b/oFSmuPHYX6uk4RAu23Lc=
931931
github.com/coder/terraform-config-inspectv0.0.0-20250107175719-6d06d90c630e h1:JNLPDi2P73laR1oAclY6jWzAbucf70ASAvf5mh2cME0=
932932
github.com/coder/terraform-config-inspectv0.0.0-20250107175719-6d06d90c630e/go.mod h1:Gz/z9Hbn+4KSp8A2FBtNszfLSdT2Tn/uAKGuVqqWmDI=
933933
github.com/coder/terraform-provider-coder/v2v2.7.1-0.20250623193313-e890833351e2 h1:vtGzECz5CyzuxMODexWdIRxhYLqyTcHafuJpH60PYhM=

‎tailnet/conn.go‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ const EnvMagicsockDebugLogging = "CODER_MAGICSOCK_DEBUG_LOGGING"
6565

6666
funcinit() {
6767
// Globally disable network namespacing. All networking happens in
68-
// userspace.
68+
// userspace unless the connection is configured to use a TUN.
69+
// NOTE: this exists in init() so it affects all connections (incl. DERP)
70+
// made by tailscale packages by default.
6971
netns.SetEnabled(false)
7072
// Tailscale, by default, "trims" the set of peers down to ones that we are
7173
// "actively" communicating with in an effort to save memory. Since
@@ -100,6 +102,18 @@ type Options struct {
100102
BlockEndpointsbool
101103
Logger slog.Logger
102104
ListenPortuint16
105+
// UseSoftNetIsolation enables our homemade soft isolation feature in the
106+
// netns package. This option will only be considered if TUNDev is set.
107+
//
108+
// The Coder soft isolation mode is a workaround to allow Coder Connect to
109+
// connect to Coder servers behind corporate VPNs, and relaxes some of the
110+
// loop protections that come with Tailscale.
111+
//
112+
// When soft isolation is disabled, the netns package will function as
113+
// normal and route all traffic through the default interface (and block all
114+
// traffic to other VPN interfaces) on macOS and Windows.
115+
UseSoftNetIsolationbool
116+
103117
// CaptureHook is a callback that captures Disco packets and packets sent
104118
// into the tailnet tunnel.
105119
CaptureHook capture.Callback
@@ -154,7 +168,11 @@ func NewConn(options *Options) (conn *Conn, err error) {
154168
returnnil,xerrors.New("At least one IP range must be provided")
155169
}
156170

157-
netns.SetEnabled(options.TUNDev!=nil)
171+
useNetNS:=options.TUNDev!=nil
172+
useSoftIsolation:=useNetNS&&options.UseSoftNetIsolation
173+
options.Logger.Debug(context.Background(),"network isolation configuration",slog.F("use_netns",useNetNS),slog.F("use_soft_isolation",useSoftIsolation))
174+
netns.SetEnabled(useNetNS)
175+
netns.SetCoderSoftIsolation(useSoftIsolation)
158176

159177
vartelemetryStore*TelemetryStore
160178
ifoptions.TelemetrySink!=nil {

‎vpn/client.go‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,14 @@ func NewClient() Client {
6969
}
7070

7171
typeOptionsstruct {
72-
Headers http.Header
73-
Logger slog.Logger
74-
DNSConfigurator dns.OSConfigurator
75-
Router router.Router
76-
TUNDevice tun.Device
77-
WireguardMonitor*netmon.Monitor
78-
UpdateHandler tailnet.UpdatesHandler
72+
Headers http.Header
73+
Logger slog.Logger
74+
UseSoftNetIsolationbool
75+
DNSConfigurator dns.OSConfigurator
76+
Router router.Router
77+
TUNDevice tun.Device
78+
WireguardMonitor*netmon.Monitor
79+
UpdateHandler tailnet.UpdatesHandler
7980
}
8081

8182
typederpMapRewriterstruct {
@@ -163,6 +164,7 @@ func (*client) NewConn(initCtx context.Context, serverURL *url.URL, token string
163164
DERPForceWebSockets:connInfo.DERPForceWebSockets,
164165
Logger:options.Logger,
165166
BlockEndpoints:connInfo.DisableDirectConnections,
167+
UseSoftNetIsolation:options.UseSoftNetIsolation,
166168
DNSConfigurator:options.DNSConfigurator,
167169
Router:options.Router,
168170
TUNDev:options.TUNDevice,

‎vpn/speaker_internal_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func TestMain(m *testing.M) {
2323
goleak.VerifyTestMain(m,testutil.GoleakOptions...)
2424
}
2525

26-
constexpectedHandshake="codervpn tunnel 1.2\n"
26+
constexpectedHandshake="codervpn tunnel 1.3\n"
2727

2828
// TestSpeaker_RawPeer tests the speaker with a peer that we simulate by directly making reads and
2929
// writes to the other end of the pipe. There should be at least one test that does this, rather

‎vpn/tunnel.go‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -271,13 +271,14 @@ func (t *Tunnel) start(req *StartRequest) error {
271271
svrURL,
272272
apiToken,
273273
&Options{
274-
Headers:header,
275-
Logger:t.clientLogger,
276-
DNSConfigurator:networkingStack.DNSConfigurator,
277-
Router:networkingStack.Router,
278-
TUNDevice:networkingStack.TUNDevice,
279-
WireguardMonitor:networkingStack.WireguardMonitor,
280-
UpdateHandler:t,
274+
Headers:header,
275+
Logger:t.clientLogger,
276+
UseSoftNetIsolation:req.GetTunnelUseSoftNetIsolation(),
277+
DNSConfigurator:networkingStack.DNSConfigurator,
278+
Router:networkingStack.Router,
279+
TUNDevice:networkingStack.TUNDevice,
280+
WireguardMonitor:networkingStack.WireguardMonitor,
281+
UpdateHandler:t,
281282
},
282283
)
283284
iferr!=nil {

‎vpn/tunnel_internal_test.go‎

Lines changed: 71 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package vpn
22

33
import (
44
"context"
5+
"encoding/json"
56
"maps"
67
"net"
8+
"net/http"
79
"net/netip"
810
"net/url"
911
"slices"
@@ -22,32 +24,51 @@ import (
2224
"github.com/coder/quartz"
2325

2426
maputil"github.com/coder/coder/v2/coderd/util/maps"
27+
"github.com/coder/coder/v2/codersdk"
2528
"github.com/coder/coder/v2/tailnet"
2629
"github.com/coder/coder/v2/tailnet/proto"
2730
"github.com/coder/coder/v2/testutil"
2831
)
2932

3033
funcnewFakeClient(ctx context.Context,t*testing.T)*fakeClient {
3134
return&fakeClient{
32-
t:t,
33-
ctx:ctx,
34-
ch:make(chan*fakeConn,1),
35+
t:t,
36+
ctx:ctx,
37+
connCh:make(chan*fakeConn,1),
38+
}
39+
}
40+
41+
funcnewFakeClientWithOptsCh(ctx context.Context,t*testing.T)*fakeClient {
42+
return&fakeClient{
43+
t:t,
44+
ctx:ctx,
45+
connCh:make(chan*fakeConn,1),
46+
optsCh:make(chan*Options,1),
3547
}
3648
}
3749

3850
typefakeClientstruct {
39-
t*testing.T
40-
ctx context.Context
41-
chchan*fakeConn
51+
t*testing.T
52+
ctx context.Context
53+
connChchan*fakeConn
54+
optsChchan*Options// options will be written to this channel if it's not nil
4255
}
4356

4457
var_Client= (*fakeClient)(nil)
4558

46-
func (f*fakeClient)NewConn(context.Context,*url.URL,string,*Options) (Conn,error) {
59+
func (f*fakeClient)NewConn(_ context.Context,_*url.URL,_string,opts*Options) (Conn,error) {
60+
iff.optsCh!=nil {
61+
select {
62+
case<-f.ctx.Done():
63+
returnnil,f.ctx.Err()
64+
casef.optsCh<-opts:
65+
}
66+
}
67+
4768
select {
4869
case<-f.ctx.Done():
4970
returnnil,f.ctx.Err()
50-
caseconn:=<-f.ch:
71+
caseconn:=<-f.connCh:
5172
returnconn,nil
5273
}
5374
}
@@ -134,37 +155,53 @@ func TestTunnel_StartStop(t *testing.T) {
134155
t.Parallel()
135156

136157
ctx:=testutil.Context(t,testutil.WaitShort)
137-
client:=newFakeClient(ctx,t)
158+
client:=newFakeClientWithOptsCh(ctx,t)
138159
conn:=newFakeConn(tailnet.WorkspaceUpdate{}, time.Time{})
139160

140161
_,mgr:=setupTunnel(t,ctx,client,quartz.NewMock(t))
141162

142163
errCh:=make(chanerror,1)
143164
varresp*TunnelMessage
144165
// When: we start the tunnel
166+
telemetry:= codersdk.CoderDesktopTelemetry{
167+
DeviceID:"device001",
168+
DeviceOS:"macOS",
169+
CoderDesktopVersion:"0.24.8",
170+
}
171+
telemetryJSON,err:=json.Marshal(telemetry)
172+
require.NoError(t,err)
145173
gofunc() {
146174
r,err:=mgr.unaryRPC(ctx,&ManagerMessage{
147175
Msg:&ManagerMessage_Start{
148176
Start:&StartRequest{
149177
TunnelFileDescriptor:2,
150-
CoderUrl:"https://coder.example.com",
151-
ApiToken:"fakeToken",
178+
// Use default value for TunnelUseSoftNetIsolation
179+
CoderUrl:"https://coder.example.com",
180+
ApiToken:"fakeToken",
152181
Headers: []*StartRequest_Header{
153182
{Name:"X-Test-Header",Value:"test"},
154183
},
155-
DeviceOs:"macOS",
156-
DeviceId:"device001",
157-
CoderDesktopVersion:"0.24.8",
184+
DeviceOs:telemetry.DeviceOS,
185+
DeviceId:telemetry.DeviceID,
186+
CoderDesktopVersion:telemetry.CoderDesktopVersion,
158187
},
159188
},
160189
})
161190
resp=r
162191
errCh<-err
163192
}()
164-
// Then: `NewConn` is called,
165-
testutil.RequireSend(ctx,t,client.ch,conn)
193+
194+
// Then: `NewConn` is called
195+
opts:=testutil.RequireReceive(ctx,t,client.optsCh)
196+
require.Equal(t, http.Header{
197+
"X-Test-Header": {"test"},
198+
codersdk.CoderDesktopTelemetryHeader: {string(telemetryJSON)},
199+
},opts.Headers)
200+
require.False(t,opts.UseSoftNetIsolation)// the default is false
201+
testutil.RequireSend(ctx,t,client.connCh,conn)
202+
166203
// And: a response is received
167-
err:=testutil.TryReceive(ctx,t,errCh)
204+
err=testutil.TryReceive(ctx,t,errCh)
168205
require.NoError(t,err)
169206
_,ok:=resp.Msg.(*TunnelMessage_Start)
170207
require.True(t,ok)
@@ -197,7 +234,7 @@ func TestTunnel_PeerUpdate(t *testing.T) {
197234
wsID1:= uuid.UUID{1}
198235
wsID2:= uuid.UUID{2}
199236

200-
client:=newFakeClient(ctx,t)
237+
client:=newFakeClientWithOptsCh(ctx,t)
201238
conn:=newFakeConn(tailnet.WorkspaceUpdate{
202239
UpsertedWorkspaces: []*tailnet.Workspace{
203240
{
@@ -211,22 +248,28 @@ func TestTunnel_PeerUpdate(t *testing.T) {
211248

212249
tun,mgr:=setupTunnel(t,ctx,client,quartz.NewMock(t))
213250

251+
// When: we start the tunnel
214252
errCh:=make(chanerror,1)
215253
varresp*TunnelMessage
216254
gofunc() {
217255
r,err:=mgr.unaryRPC(ctx,&ManagerMessage{
218256
Msg:&ManagerMessage_Start{
219257
Start:&StartRequest{
220-
TunnelFileDescriptor:2,
221-
CoderUrl:"https://coder.example.com",
222-
ApiToken:"fakeToken",
258+
TunnelFileDescriptor:2,
259+
TunnelUseSoftNetIsolation:true,
260+
CoderUrl:"https://coder.example.com",
261+
ApiToken:"fakeToken",
223262
},
224263
},
225264
})
226265
resp=r
227266
errCh<-err
228267
}()
229-
testutil.RequireSend(ctx,t,client.ch,conn)
268+
269+
// Then: `NewConn` is called
270+
opts:=testutil.RequireReceive(ctx,t,client.optsCh)
271+
require.True(t,opts.UseSoftNetIsolation)
272+
testutil.RequireSend(ctx,t,client.connCh,conn)
230273
err:=testutil.TryReceive(ctx,t,errCh)
231274
require.NoError(t,err)
232275
_,ok:=resp.Msg.(*TunnelMessage_Start)
@@ -291,7 +334,7 @@ func TestTunnel_NetworkSettings(t *testing.T) {
291334
resp=r
292335
errCh<-err
293336
}()
294-
testutil.RequireSend(ctx,t,client.ch,conn)
337+
testutil.RequireSend(ctx,t,client.connCh,conn)
295338
err:=testutil.TryReceive(ctx,t,errCh)
296339
require.NoError(t,err)
297340
_,ok:=resp.Msg.(*TunnelMessage_Start)
@@ -432,7 +475,7 @@ func TestTunnel_sendAgentUpdate(t *testing.T) {
432475
resp=r
433476
errCh<-err
434477
}()
435-
testutil.RequireSend(ctx,t,client.ch,conn)
478+
testutil.RequireSend(ctx,t,client.connCh,conn)
436479
err:=testutil.TryReceive(ctx,t,errCh)
437480
require.NoError(t,err)
438481
_,ok:=resp.Msg.(*TunnelMessage_Start)
@@ -603,7 +646,7 @@ func TestTunnel_sendAgentUpdateReconnect(t *testing.T) {
603646
resp=r
604647
errCh<-err
605648
}()
606-
testutil.RequireSend(ctx,t,client.ch,conn)
649+
testutil.RequireSend(ctx,t,client.connCh,conn)
607650
err:=testutil.TryReceive(ctx,t,errCh)
608651
require.NoError(t,err)
609652
_,ok:=resp.Msg.(*TunnelMessage_Start)
@@ -703,7 +746,7 @@ func TestTunnel_sendAgentUpdateWorkspaceReconnect(t *testing.T) {
703746
resp=r
704747
errCh<-err
705748
}()
706-
testutil.RequireSend(ctx,t,client.ch,conn)
749+
testutil.RequireSend(ctx,t,client.connCh,conn)
707750
err:=testutil.TryReceive(ctx,t,errCh)
708751
require.NoError(t,err)
709752
_,ok:=resp.Msg.(*TunnelMessage_Start)
@@ -806,7 +849,7 @@ func TestTunnel_slowPing(t *testing.T) {
806849
resp=r
807850
errCh<-err
808851
}()
809-
testutil.RequireSend(ctx,t,client.ch,conn)
852+
testutil.RequireSend(ctx,t,client.connCh,conn)
810853
err:=testutil.TryReceive(ctx,t,errCh)
811854
require.NoError(t,err)
812855
_,ok:=resp.Msg.(*TunnelMessage_Start)
@@ -895,7 +938,7 @@ func TestTunnel_stopMidPing(t *testing.T) {
895938
resp=r
896939
errCh<-err
897940
}()
898-
testutil.RequireSend(ctx,t,client.ch,conn)
941+
testutil.RequireSend(ctx,t,client.connCh,conn)
899942
err:=testutil.TryReceive(ctx,t,errCh)
900943
require.NoError(t,err)
901944
_,ok:=resp.Msg.(*TunnelMessage_Start)

‎vpn/version.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ var CurrentSupportedVersions = RPCVersionList{
2323
// - preferred_derp: The server that DERP relayed connections are
2424
// using, if they're not using P2P.
2525
// - preferred_derp_latency: The latency to the preferred DERP
26-
{Major:1,Minor:2},
26+
// 1.3 adds:
27+
// - tunnel_use_soft_net_isolation to the StartRequest
28+
{Major:1,Minor:3},
2729
},
2830
}
2931

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp