Skip to content

chore: remove soft isolation configurability #19069

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 6 additions & 14 deletions tailnet/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,6 @@ type Options struct {
BlockEndpoints bool
Logger slog.Logger
ListenPort uint16
// UseSoftNetIsolation enables our homemade soft isolation feature in the
// netns package. This option will only be considered if TUNDev is set.
//
// The Coder soft isolation mode is a workaround to allow Coder Connect to
// connect to Coder servers behind corporate VPNs, and relaxes some of the
// loop protections that come with Tailscale.
//
// When soft isolation is disabled, the netns package will function as
// normal and route all traffic through the default interface (and block all
// traffic to other VPN interfaces) on macOS and Windows.
UseSoftNetIsolation bool

// CaptureHook is a callback that captures Disco packets and packets sent
// into the tailnet tunnel.
Expand Down Expand Up @@ -169,10 +158,13 @@ func NewConn(options *Options) (conn *Conn, err error) {
}

useNetNS := options.TUNDev != nil
useSoftIsolation := useNetNS && options.UseSoftNetIsolation
options.Logger.Debug(context.Background(), "network isolation configuration", slog.F("use_netns", useNetNS), slog.F("use_soft_isolation", useSoftIsolation))
options.Logger.Debug(context.Background(), "network isolation configuration", slog.F("use_netns", useNetNS))
netns.SetEnabled(useNetNS)
netns.SetCoderSoftIsolation(useSoftIsolation)
// The Coder soft isolation mode is a workaround to allow Coder Connect to
// connect to Coder servers behind corporate VPNs, and relaxes some of the
// loop protections that come with Tailscale.
// See the comment above the netns function for more details.
netns.SetCoderSoftIsolation(useNetNS)

var telemetryStore *TelemetryStore
if options.TelemetrySink != nil {
Expand Down
16 changes: 7 additions & 9 deletions vpn/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,13 @@ func NewClient() Client {
}

type Options struct {
Headers http.Header
Logger slog.Logger
UseSoftNetIsolation bool
DNSConfigurator dns.OSConfigurator
Router router.Router
TUNDevice tun.Device
WireguardMonitor *netmon.Monitor
UpdateHandler tailnet.UpdatesHandler
Headers http.Header
Logger slog.Logger
DNSConfigurator dns.OSConfigurator
Router router.Router
TUNDevice tun.Device
WireguardMonitor *netmon.Monitor
UpdateHandler tailnet.UpdatesHandler
}

type derpMapRewriter struct {
Expand Down Expand Up @@ -164,7 +163,6 @@ func (*client) NewConn(initCtx context.Context, serverURL *url.URL, token string
DERPForceWebSockets: connInfo.DERPForceWebSockets,
Logger: options.Logger,
BlockEndpoints: connInfo.DisableDirectConnections,
UseSoftNetIsolation: options.UseSoftNetIsolation,
DNSConfigurator: options.DNSConfigurator,
Router: options.Router,
TUNDev: options.TUNDevice,
Expand Down
2 changes: 1 addition & 1 deletion vpn/speaker_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestMain(m *testing.M) {
goleak.VerifyTestMain(m, testutil.GoleakOptions...)
}

const expectedHandshake = "codervpn tunnel 1.3\n"
const expectedHandshake = "codervpn tunnel 1.2\n"

// TestSpeaker_RawPeer tests the speaker with a peer that we simulate by directly making reads and
// writes to the other end of the pipe. There should be at least one test that does this, rather
Expand Down
15 changes: 7 additions & 8 deletions vpn/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,13 @@ func (t *Tunnel) start(req *StartRequest) error {
svrURL,
apiToken,
&Options{
Headers: header,
Logger: t.clientLogger,
UseSoftNetIsolation: req.GetTunnelUseSoftNetIsolation(),
DNSConfigurator: networkingStack.DNSConfigurator,
Router: networkingStack.Router,
TUNDevice: networkingStack.TUNDevice,
WireguardMonitor: networkingStack.WireguardMonitor,
UpdateHandler: t,
Headers: header,
Logger: t.clientLogger,
DNSConfigurator: networkingStack.DNSConfigurator,
Router: networkingStack.Router,
TUNDevice: networkingStack.TUNDevice,
WireguardMonitor: networkingStack.WireguardMonitor,
UpdateHandler: t,
},
)
if err != nil {
Expand Down
99 changes: 28 additions & 71 deletions vpn/tunnel_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ package vpn

import (
"context"
"encoding/json"
"maps"
"net"
"net/http"
"net/netip"
"net/url"
"slices"
Expand All @@ -24,51 +22,32 @@ import (
"github.com/coder/quartz"

maputil "github.com/coder/coder/v2/coderd/util/maps"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/tailnet"
"github.com/coder/coder/v2/tailnet/proto"
"github.com/coder/coder/v2/testutil"
)

func newFakeClient(ctx context.Context, t *testing.T) *fakeClient {
return &fakeClient{
t: t,
ctx: ctx,
connCh: make(chan *fakeConn, 1),
}
}

func newFakeClientWithOptsCh(ctx context.Context, t *testing.T) *fakeClient {
return &fakeClient{
t: t,
ctx: ctx,
connCh: make(chan *fakeConn, 1),
optsCh: make(chan *Options, 1),
t: t,
ctx: ctx,
ch: make(chan *fakeConn, 1),
}
}

type fakeClient struct {
t *testing.T
ctx context.Context
connCh chan *fakeConn
optsCh chan *Options // options will be written to this channel if it's not nil
t *testing.T
ctx context.Context
ch chan *fakeConn
}

var _ Client = (*fakeClient)(nil)

func (f *fakeClient) NewConn(_ context.Context, _ *url.URL, _ string, opts *Options) (Conn, error) {
if f.optsCh != nil {
select {
case <-f.ctx.Done():
return nil, f.ctx.Err()
case f.optsCh <- opts:
}
}

func (f *fakeClient) NewConn(context.Context, *url.URL, string, *Options) (Conn, error) {
select {
case <-f.ctx.Done():
return nil, f.ctx.Err()
case conn := <-f.connCh:
case conn := <-f.ch:
return conn, nil
}
}
Expand Down Expand Up @@ -155,53 +134,37 @@ func TestTunnel_StartStop(t *testing.T) {
t.Parallel()

ctx := testutil.Context(t, testutil.WaitShort)
client := newFakeClientWithOptsCh(ctx, t)
client := newFakeClient(ctx, t)
conn := newFakeConn(tailnet.WorkspaceUpdate{}, time.Time{})

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

errCh := make(chan error, 1)
var resp *TunnelMessage
// When: we start the tunnel
telemetry := codersdk.CoderDesktopTelemetry{
DeviceID: "device001",
DeviceOS: "macOS",
CoderDesktopVersion: "0.24.8",
}
telemetryJSON, err := json.Marshal(telemetry)
require.NoError(t, err)
go func() {
r, err := mgr.unaryRPC(ctx, &ManagerMessage{
Msg: &ManagerMessage_Start{
Start: &StartRequest{
TunnelFileDescriptor: 2,
// Use default value for TunnelUseSoftNetIsolation
CoderUrl: "https://coder.example.com",
ApiToken: "fakeToken",
CoderUrl: "https://coder.example.com",
ApiToken: "fakeToken",
Headers: []*StartRequest_Header{
{Name: "X-Test-Header", Value: "test"},
},
DeviceOs: telemetry.DeviceOS,
DeviceId: telemetry.DeviceID,
CoderDesktopVersion: telemetry.CoderDesktopVersion,
DeviceOs: "macOS",
DeviceId: "device001",
CoderDesktopVersion: "0.24.8",
},
},
})
resp = r
errCh <- err
}()

// Then: `NewConn` is called
opts := testutil.RequireReceive(ctx, t, client.optsCh)
require.Equal(t, http.Header{
"X-Test-Header": {"test"},
codersdk.CoderDesktopTelemetryHeader: {string(telemetryJSON)},
}, opts.Headers)
require.False(t, opts.UseSoftNetIsolation) // the default is false
testutil.RequireSend(ctx, t, client.connCh, conn)

// Then: `NewConn` is called,
testutil.RequireSend(ctx, t, client.ch, conn)
// And: a response is received
err = testutil.TryReceive(ctx, t, errCh)
err := testutil.TryReceive(ctx, t, errCh)
require.NoError(t, err)
_, ok := resp.Msg.(*TunnelMessage_Start)
require.True(t, ok)
Expand Down Expand Up @@ -234,7 +197,7 @@ func TestTunnel_PeerUpdate(t *testing.T) {
wsID1 := uuid.UUID{1}
wsID2 := uuid.UUID{2}

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

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

// When: we start the tunnel
errCh := make(chan error, 1)
var resp *TunnelMessage
go func() {
r, err := mgr.unaryRPC(ctx, &ManagerMessage{
Msg: &ManagerMessage_Start{
Start: &StartRequest{
TunnelFileDescriptor: 2,
TunnelUseSoftNetIsolation: true,
CoderUrl: "https://coder.example.com",
ApiToken: "fakeToken",
TunnelFileDescriptor: 2,
CoderUrl: "https://coder.example.com",
ApiToken: "fakeToken",
},
},
})
resp = r
errCh <- err
}()

// Then: `NewConn` is called
opts := testutil.RequireReceive(ctx, t, client.optsCh)
require.True(t, opts.UseSoftNetIsolation)
testutil.RequireSend(ctx, t, client.connCh, conn)
testutil.RequireSend(ctx, t, client.ch, conn)
err := testutil.TryReceive(ctx, t, errCh)
require.NoError(t, err)
_, ok := resp.Msg.(*TunnelMessage_Start)
Expand Down Expand Up @@ -334,7 +291,7 @@ func TestTunnel_NetworkSettings(t *testing.T) {
resp = r
errCh <- err
}()
testutil.RequireSend(ctx, t, client.connCh, conn)
testutil.RequireSend(ctx, t, client.ch, conn)
err := testutil.TryReceive(ctx, t, errCh)
require.NoError(t, err)
_, ok := resp.Msg.(*TunnelMessage_Start)
Expand Down Expand Up @@ -475,7 +432,7 @@ func TestTunnel_sendAgentUpdate(t *testing.T) {
resp = r
errCh <- err
}()
testutil.RequireSend(ctx, t, client.connCh, conn)
testutil.RequireSend(ctx, t, client.ch, conn)
err := testutil.TryReceive(ctx, t, errCh)
require.NoError(t, err)
_, ok := resp.Msg.(*TunnelMessage_Start)
Expand Down Expand Up @@ -646,7 +603,7 @@ func TestTunnel_sendAgentUpdateReconnect(t *testing.T) {
resp = r
errCh <- err
}()
testutil.RequireSend(ctx, t, client.connCh, conn)
testutil.RequireSend(ctx, t, client.ch, conn)
err := testutil.TryReceive(ctx, t, errCh)
require.NoError(t, err)
_, ok := resp.Msg.(*TunnelMessage_Start)
Expand Down Expand Up @@ -746,7 +703,7 @@ func TestTunnel_sendAgentUpdateWorkspaceReconnect(t *testing.T) {
resp = r
errCh <- err
}()
testutil.RequireSend(ctx, t, client.connCh, conn)
testutil.RequireSend(ctx, t, client.ch, conn)
err := testutil.TryReceive(ctx, t, errCh)
require.NoError(t, err)
_, ok := resp.Msg.(*TunnelMessage_Start)
Expand Down Expand Up @@ -849,7 +806,7 @@ func TestTunnel_slowPing(t *testing.T) {
resp = r
errCh <- err
}()
testutil.RequireSend(ctx, t, client.connCh, conn)
testutil.RequireSend(ctx, t, client.ch, conn)
err := testutil.TryReceive(ctx, t, errCh)
require.NoError(t, err)
_, ok := resp.Msg.(*TunnelMessage_Start)
Expand Down Expand Up @@ -938,7 +895,7 @@ func TestTunnel_stopMidPing(t *testing.T) {
resp = r
errCh <- err
}()
testutil.RequireSend(ctx, t, client.connCh, conn)
testutil.RequireSend(ctx, t, client.ch, conn)
err := testutil.TryReceive(ctx, t, errCh)
require.NoError(t, err)
_, ok := resp.Msg.(*TunnelMessage_Start)
Expand Down
4 changes: 1 addition & 3 deletions vpn/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ var CurrentSupportedVersions = RPCVersionList{
// - preferred_derp: The server that DERP relayed connections are
// using, if they're not using P2P.
// - preferred_derp_latency: The latency to the preferred DERP
// 1.3 adds:
// - tunnel_use_soft_net_isolation to the StartRequest
{Major: 1, Minor: 3},
{Major: 1, Minor: 2},
},
}

Expand Down
Loading
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy