Skip to content

fix: opt-in tailscale vpn loop prevention #148

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion App/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public App()

services.AddSingleton<IDispatcherQueueManager>(_ => this);
services.AddSingleton<IDefaultNotificationHandler>(_ => this);
services.AddSingleton<ISettingsManager<CoderConnectSettings>, SettingsManager<CoderConnectSettings>>();
services.AddSingleton<ICredentialBackend>(_ =>
new WindowsCredentialBackend(WindowsCredentialBackend.CoderCredentialsTargetName));
services.AddSingleton<ICredentialManager, CredentialManager>();
Expand Down Expand Up @@ -120,7 +121,6 @@ public App()
// FileSyncListMainPage is created by FileSyncListWindow.
services.AddTransient<FileSyncListWindow>();

services.AddSingleton<ISettingsManager<CoderConnectSettings>, SettingsManager<CoderConnectSettings>>();
services.AddSingleton<IStartupManager, StartupManager>();
// SettingsWindow views and view models
services.AddTransient<SettingsViewModel>();
Expand Down
13 changes: 11 additions & 2 deletions App/Models/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public class CoderConnectSettings : ISettings<CoderConnectSettings>
/// </summary>
public bool ConnectOnLaunch { get; set; }

/// <summary>
/// When this is true Coder Connect will not attempt to protect against Tailscale loopback issues.
/// </summary>
public bool DisableTailscaleLoopProtection { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe EnableCorporateVpnSupport


/// <summary>
/// CoderConnect current settings version. Increment this when the settings schema changes.
/// In future iterations we will be able to handle migrations when the user has
Expand All @@ -46,17 +51,21 @@ public CoderConnectSettings()
Version = VERSION;

ConnectOnLaunch = false;

DisableTailscaleLoopProtection = false;
}

public CoderConnectSettings(int? version, bool connectOnLaunch)
public CoderConnectSettings(int? version, bool connectOnLaunch, bool disableTailscaleLoopProtection)
{
Version = version ?? VERSION;

ConnectOnLaunch = connectOnLaunch;

DisableTailscaleLoopProtection = disableTailscaleLoopProtection;
}

public CoderConnectSettings Clone()
{
return new CoderConnectSettings(Version, ConnectOnLaunch);
return new CoderConnectSettings(Version, ConnectOnLaunch, DisableTailscaleLoopProtection);
}
}
9 changes: 8 additions & 1 deletion App/Services/RpcController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,18 @@ public interface IRpcController : IAsyncDisposable
public class RpcController : IRpcController
{
private readonly ICredentialManager _credentialManager;
private readonly ISettingsManager<CoderConnectSettings> _settingsManager;

private readonly RaiiSemaphoreSlim _operationLock = new(1, 1);
private Speaker<ClientMessage, ServiceMessage>? _speaker;

private readonly RaiiSemaphoreSlim _stateLock = new(1, 1);
private readonly RpcModel _state = new();

public RpcController(ICredentialManager credentialManager)
public RpcController(ICredentialManager credentialManager, ISettingsManager<CoderConnectSettings> settingsManager)
{
_credentialManager = credentialManager;
_settingsManager = settingsManager;
}

public event EventHandler<RpcModel>? StateChanged;
Expand Down Expand Up @@ -156,6 +158,11 @@ public async Task StartVpn(CancellationToken ct = default)
using var _ = await AcquireOperationLockNowAsync();
AssertRpcConnected();

var coderConnectSettings = await _settingsManager.Read();
var disableTailscaleLoopProtection = coderConnectSettings.DisableTailscaleLoopProtection;
Debug.WriteLine(
$"Starting VPN with DisableTailscaleLoopProtection={disableTailscaleLoopProtection}");

var credentials = _credentialManager.GetCachedCredentials();
if (credentials.State != CredentialState.Valid)
throw new RpcOperationException(
Expand Down
21 changes: 20 additions & 1 deletion App/ViewModels/SettingsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public partial class SettingsViewModel : ObservableObject
[ObservableProperty]
public partial bool ConnectOnLaunch { get; set; }

[ObservableProperty]
public partial bool DisableTailscaleLoopProtection { get; set; }

[ObservableProperty]
public partial bool StartOnLoginDisabled { get; set; }

Expand All @@ -31,6 +34,7 @@ public SettingsViewModel(ILogger<SettingsViewModel> logger, ISettingsManager<Cod
_connectSettings = settingsManager.Read().GetAwaiter().GetResult();
StartOnLogin = startupManager.IsEnabled();
ConnectOnLaunch = _connectSettings.ConnectOnLaunch;
DisableTailscaleLoopProtection = _connectSettings.DisableTailscaleLoopProtection;

// Various policies can disable the "Start on login" option.
// We disable the option in the UI if the policy is set.
Expand All @@ -43,6 +47,21 @@ public SettingsViewModel(ILogger<SettingsViewModel> logger, ISettingsManager<Cod
}
}

partial void OnDisableTailscaleLoopProtectionChanged(bool oldValue, bool newValue)
{
if (oldValue == newValue)
return;
try
{
_connectSettings.DisableTailscaleLoopProtection = DisableTailscaleLoopProtection;
_connectSettingsManager.Write(_connectSettings);
}
catch (Exception ex)
{
_logger.LogError($"Error saving Coder Connect {nameof(DisableTailscaleLoopProtection)} settings: {ex.Message}");
}
}

partial void OnConnectOnLaunchChanged(bool oldValue, bool newValue)
{
if (oldValue == newValue)
Expand All @@ -54,7 +73,7 @@ partial void OnConnectOnLaunchChanged(bool oldValue, bool newValue)
}
catch (Exception ex)
{
_logger.LogError($"Error saving Coder Connect settings: {ex.Message}");
_logger.LogError($"Error saving Coder Connect {nameof(ConnectOnLaunch)} settings: {ex.Message}");
}
}

Expand Down
6 changes: 6 additions & 0 deletions App/Views/Pages/SettingsMainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
>
<ToggleSwitch IsOn="{x:Bind ViewModel.ConnectOnLaunch, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard Description="This setting controls whether Coder Connect should bypass VPN loop preventation mechanism. Turn this ON only when dealing with a Coder deployment behind a corporate VPN."
Copy link
Member

@deansheather deansheather Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<controls:SettingsCard Description="This setting controls whether Coder Connect should bypass VPN loop preventation mechanism. Turn this ON only when dealing with a Coder deployment behind a corporate VPN."
<controls:SettingsCard Description="This setting loosens some VPN loop protection checks in Coder Connect, allowing traffic to flow to a Coder deployment behind a corporate VPN. We do not recommend enabling this option if your deployment is not behind a corporate VPN."

Header="Disable corporate VPN loop protection"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Header="Disable corporate VPN loop protection"
Header="Enable support for corporate VPNs"

HeaderIcon="{ui:FontIcon Glyph=&#xE8AF;}"
>
<ToggleSwitch IsOn="{x:Bind ViewModel.DisableTailscaleLoopProtection, Mode=TwoWay}" />
</controls:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
Expand Down
4 changes: 2 additions & 2 deletions App/Views/SettingsWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
xmlns:winuiex="using:WinUIEx"
mc:Ignorable="d"
Title="Coder Settings"
Width="600" Height="350"
MinWidth="600" MinHeight="350">
Width="600" Height="450"
MinWidth="600" MinHeight="450">

<Window.SystemBackdrop>
<MicaBackdrop/>
Expand Down
Loading