Skip to content

Commit

Permalink
[Tor] Towards Whonix & Tails (take 2) (WalletWasabi#12991)
Browse files Browse the repository at this point in the history
* `TorProcessManager`: Extract `RestartingLoopForBundledTorAsync`
* Tor: Introduce `--UseOnlyRunningTor` command line option
* Modify `UseTor` to be an enum
  • Loading branch information
kiminuo authored May 14, 2024
1 parent 0c6ae1a commit 1be84e5
Show file tree
Hide file tree
Showing 18 changed files with 267 additions and 70 deletions.
45 changes: 43 additions & 2 deletions WalletWasabi.Daemon/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ [ nameof(RegTestCoordinatorUri)] = (
GetNullableStringValue("RegTestCoordinatorUri", PersistentConfig.RegTestCoordinatorUri, cliArgs)),
[ nameof(UseTor)] = (
"All the communications go through the Tor network",
GetBoolValue("UseTor", PersistentConfig.UseTor, cliArgs)),
GetTorModeValue("UseTor", PersistentConfig.UseTor, cliArgs)),
[ nameof(TorFolder)] = (
"Folder where Tor binary is located",
GetNullableStringValue("TorFolder", null, cliArgs)),
Expand Down Expand Up @@ -159,7 +159,7 @@ [ nameof(CoordinatorIdentifier)] = (
public string? MainNetCoordinatorUri => GetEffectiveValue<NullableStringValue, string?>(nameof(MainNetCoordinatorUri));
public string? TestNetCoordinatorUri => GetEffectiveValue<NullableStringValue, string?>(nameof(TestNetCoordinatorUri));
public string? RegTestCoordinatorUri => GetEffectiveValue<NullableStringValue, string?>(nameof(RegTestCoordinatorUri));
public bool UseTor => GetEffectiveValue<BoolValue, bool>(nameof(UseTor)) && Network != Network.RegTest;
public TorMode UseTor => Network == Network.RegTest ? TorMode.Disabled : GetEffectiveValue<TorModeValue, TorMode>(nameof(UseTor));
public string? TorFolder => GetEffectiveValue<NullableStringValue, string?>(nameof(TorFolder));
public int TorSocksPort => GetEffectiveValue<IntValue, int>(nameof(TorSocksPort));
public int TorControlPort => GetEffectiveValue<IntValue, int>(nameof(TorControlPort));
Expand Down Expand Up @@ -380,6 +380,46 @@ private static LogModeArrayValue GetLogModeArrayValue(string key, LogMode[] arra
return new LogModeArrayValue(arrayValues, arrayValues, ValueSource.Disk);
}

private static TorModeValue GetTorModeValue(string key, object value, string[] cliArgs)
{
TorMode computedValue;

string? stringValue = value.ToString();

if (stringValue is null)
{
throw new ArgumentException($"Could not convert '{value}' to a string value.");
}
else if (stringValue.Equals("true", StringComparison.OrdinalIgnoreCase))
{
computedValue = TorMode.Enabled;
}
else if (stringValue.Equals("false", StringComparison.OrdinalIgnoreCase))
{
computedValue = TorMode.Disabled;
}
else if (Enum.TryParse(stringValue, out TorMode parsedTorMode))
{
computedValue = parsedTorMode;
}
else
{
throw new ArgumentException($"Could not convert '{value}' to a valid {nameof(TorMode)} value.");
}

if (GetOverrideValue(key, cliArgs, out string? overrideValue, out ValueSource? valueSource))
{
if (!Enum.TryParse(overrideValue, out TorMode parsedOverrideValue))
{
throw new ArgumentException($"Could not convert overridden value '{overrideValue}' to a valid {nameof(TorMode)} value.");
}

return new TorModeValue(computedValue, parsedOverrideValue, valueSource.Value);
}

return new TorModeValue(computedValue, computedValue, ValueSource.Disk);
}

private static bool GetOverrideValue(string key, string[] cliArgs, [NotNullWhen(true)] out string? overrideValue, [NotNullWhen(true)] out ValueSource? valueSource)
{
// CLI arguments have higher precedence than environment variables.
Expand Down Expand Up @@ -471,6 +511,7 @@ private record StringValue(string Value, string EffectiveValue, ValueSource Valu
private record NullableStringValue(string? Value, string? EffectiveValue, ValueSource ValueSource) : ITypedValue<string?>;
private record StringArrayValue(string[] Value, string[] EffectiveValue, ValueSource ValueSource) : ITypedValue<string[]>;
private record LogModeArrayValue(LogMode[] Value, LogMode[] EffectiveValue, ValueSource ValueSource) : ITypedValue<LogMode[]>;
private record TorModeValue(TorMode Value, TorMode EffectiveValue, ValueSource ValueSource) : ITypedValue<TorMode>;
private record NetworkValue(Network Value, Network EffectiveValue, ValueSource ValueSource) : ITypedValue<Network>;
private record MoneyValue(Money Value, Money EffectiveValue, ValueSource ValueSource) : ITypedValue<Money>;
private record EndPointValue(EndPoint Value, EndPoint EffectiveValue, ValueSource ValueSource) : ITypedValue<EndPoint>;
Expand Down
20 changes: 14 additions & 6 deletions WalletWasabi.Daemon/Global.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
using WalletWasabi.WebClients.BuyAnything;
using WalletWasabi.WebClients.ShopWare;
using WalletWasabi.Wallets.FilterProcessor;
using WalletWasabi.Models;

namespace WalletWasabi.Daemon;

Expand All @@ -54,7 +55,8 @@ public Global(string dataDir, string configFilePath, Config config)
TorSettings = new TorSettings(
DataDir,
distributionFolderPath: EnvironmentHelpers.GetFullBaseDirectory(),
Config.TerminateTorOnExit,
terminateOnExit: Config.TerminateTorOnExit,
torMode: Config.UseTor,
socksPort: config.TorSocksPort,
controlPort: config.TorControlPort,
torFolder: config.TorFolder,
Expand Down Expand Up @@ -102,7 +104,7 @@ public Global(string dataDir, string configFilePath, Config config)
var p2p = new P2pNetwork(
Network,
Config.GetBitcoinP2pEndPoint(),
Config.UseTor ? TorSettings.SocksEndpoint : null,
Config.UseTor != TorMode.Disabled ? TorSettings.SocksEndpoint : null,
Path.Combine(DataDir, "BitcoinP2pNetwork"),
BitcoinStore);
if (!Config.BlockOnlyMode)
Expand Down Expand Up @@ -185,8 +187,9 @@ public Global(string dataDir, string configFilePath, Config config)

private WasabiHttpClientFactory BuildHttpClientFactory(Func<Uri> backendUriGetter) =>
new(
Config.UseTor ? TorSettings.SocksEndpoint : null,
backendUriGetter);
Config.UseTor != TorMode.Disabled ? TorSettings.SocksEndpoint : null,
backendUriGetter,
torControlAvailable: Config.UseTor == TorMode.Enabled);

public async Task InitializeNoWalletAsync(bool initializeSleepInhibitor, TerminateService terminateService, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -311,7 +314,7 @@ private async Task StartRpcServerAsync(TerminateService terminateService, Cancel

private async Task StartTorProcessManagerAsync(CancellationToken cancellationToken)
{
if (Config.UseTor && Network != Network.RegTest)
if (Config.UseTor != TorMode.Disabled)
{
TorManager = new TorProcessManager(TorSettings);
await TorManager.StartAsync(attempts: 3, cancellationToken).ConfigureAwait(false);
Expand All @@ -333,7 +336,12 @@ private async Task StartTorProcessManagerAsync(CancellationToken cancellationTok
}
}

HostedServices.Register<TorMonitor>(() => new TorMonitor(period: TimeSpan.FromMinutes(1), torProcessManager: TorManager, httpClientFactory: HttpClientFactory), nameof(TorMonitor));
// Do not monitor Tor when Tor is an already running service.
if (TorSettings.TorMode == TorMode.Enabled)
{
HostedServices.Register<TorMonitor>(() => new TorMonitor(period: TimeSpan.FromMinutes(1), torProcessManager: TorManager, httpClientFactory: HttpClientFactory), nameof(TorMonitor));
}

HostedServices.Register<TorStatusChecker>(() => TorStatusChecker, "Tor Network Checker");
}
}
Expand Down
12 changes: 9 additions & 3 deletions WalletWasabi.Daemon/PersistentConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@ public record PersistentConfig : IConfigNg
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? RegTestCoordinatorUri { get; init; }

[DefaultValue(true)]
/// <remarks>
/// For backward compatibility this was changed to an object.
/// Only strings (new) and booleans (old) are supported.
/// </remarks>
[DefaultValue("Enabled")]
[JsonPropertyName("UseTor")]
public bool UseTor { get; init; } = true;
public object UseTor { get; init; } = "Enabled";

[DefaultValue(false)]
[JsonPropertyName("TerminateTorOnExit")]
Expand Down Expand Up @@ -116,6 +120,8 @@ public record PersistentConfig : IConfigNg

public bool DeepEquals(PersistentConfig other)
{
bool useTorIsEqual = UseTor.ToString() == other.UseTor.ToString();

return
Network == other.Network &&
MainNetBackendUri == other.MainNetBackendUri &&
Expand All @@ -124,7 +130,7 @@ public bool DeepEquals(PersistentConfig other)
MainNetCoordinatorUri == other.MainNetCoordinatorUri &&
TestNetCoordinatorUri == other.TestNetCoordinatorUri &&
RegTestCoordinatorUri == other.RegTestCoordinatorUri &&
UseTor == other.UseTor &&
useTorIsEqual &&
TerminateTorOnExit == other.TerminateTorOnExit &&
DownloadNewVersion == other.DownloadNewVersion &&
StartLocalBitcoinCoreOnStartup == other.StartLocalBitcoinCoreOnStartup &&
Expand Down
7 changes: 4 additions & 3 deletions WalletWasabi.Fluent/Models/UI/ApplicationSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using WalletWasabi.Fluent.Infrastructure;
using WalletWasabi.Helpers;
using WalletWasabi.Logging;
using WalletWasabi.Models;
using WalletWasabi.Userfacing;

namespace WalletWasabi.Fluent.Models.UI;
Expand Down Expand Up @@ -50,7 +51,7 @@ public partial class ApplicationSettings : ReactiveObject
[AutoNotify] private FeeDisplayUnit _selectedFeeDisplayUnit;
[AutoNotify] private bool _runOnSystemStartup;
[AutoNotify] private bool _hideOnClose;
[AutoNotify] private bool _useTor;
[AutoNotify] private TorMode _useTor;
[AutoNotify] private bool _terminateTorOnExit;
[AutoNotify] private bool _downloadNewVersion;

Expand Down Expand Up @@ -92,7 +93,7 @@ public ApplicationSettings(string persistentConfigFilePath, PersistentConfig per
: FeeDisplayUnit.Satoshis;
_runOnSystemStartup = _uiConfig.RunOnSystemStartup;
_hideOnClose = _uiConfig.HideOnClose;
_useTor = _startupConfig.UseTor;
_useTor = config.UseTor;
_terminateTorOnExit = _startupConfig.TerminateTorOnExit;
_downloadNewVersion = _startupConfig.DownloadNewVersion;

Expand Down Expand Up @@ -242,7 +243,7 @@ private PersistentConfig ApplyChanges(PersistentConfig config)
// General
result = result with
{
UseTor = UseTor,
UseTor = UseTor.ToString(),
TerminateTorOnExit = TerminateTorOnExit,
DownloadNewVersion = DownloadNewVersion,
};
Expand Down
6 changes: 3 additions & 3 deletions WalletWasabi.Fluent/Models/Wallets/HealthMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public HealthMonitor(IApplicationSettings applicationSettings, ITorStatusChecker
// Tor Status
synchronizer.WhenAnyValue(x => x.TorStatus)
.ObserveOn(RxApp.MainThreadScheduler)
.Select(status => UseTor ? status : TorStatus.TurnedOff)
.Select(status => UseTor != TorMode.Disabled ? status : TorStatus.TurnedOff)
.BindTo(this, x => x.TorStatus)
.DisposeWith(Disposables);

Expand Down Expand Up @@ -137,7 +137,7 @@ public HealthMonitor(IApplicationSettings applicationSettings, ITorStatusChecker

public ICollection<Issue> TorIssues => _torIssues.Value;

public bool UseTor { get; }
public TorMode UseTor { get; }
public bool UseBitcoinCore { get; }

private CompositeDisposable Disposables { get; } = new();
Expand All @@ -164,7 +164,7 @@ private HealthMonitorState GetState()
return HealthMonitorState.UpdateAvailable;
}

var torConnected = !UseTor || TorStatus == TorStatus.Running;
var torConnected = UseTor == TorMode.Disabled || TorStatus == TorStatus.Running;
if (torConnected && BackendStatus == BackendStatus.Connected && IsP2pConnected)
{
return HealthMonitorState.Ready;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using WalletWasabi.Fluent.ViewModels.SearchBar.SearchItems;
using WalletWasabi.Fluent.ViewModels.SearchBar.Settings;
using WalletWasabi.Fluent.ViewModels.SearchBar.Sources;
using WalletWasabi.Models;

namespace WalletWasabi.Fluent.ViewModels.SearchBar;

Expand Down Expand Up @@ -52,8 +53,8 @@ private IEnumerable<ISearchItem> GetSettingsItems()
icon: "nav_settings_regular",
priority: 7,
isEnabled,
nestedItemConfiguration: new NestedItemConfiguration<bool>(
isDisplayed: isVisible => isVisible,
nestedItemConfiguration: new NestedItemConfiguration<TorMode>(
isDisplayed: mode => mode != TorMode.Disabled,
item: new ContentSearchItem(
content: Setting(selector: x => x.TerminateTorOnExit),
name: "Terminate Tor when Wasabi shuts down",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using WalletWasabi.Fluent.Infrastructure;
using WalletWasabi.Fluent.Models.UI;
using WalletWasabi.Fluent.ViewModels.Navigation;
using WalletWasabi.Models;

namespace WalletWasabi.Fluent.ViewModels.Settings;

Expand Down Expand Up @@ -54,4 +55,7 @@ public GeneralSettingsTabViewModel(IApplicationSettings settings)

public IEnumerable<FeeDisplayUnit> FeeDisplayUnits =>
Enum.GetValues(typeof(FeeDisplayUnit)).Cast<FeeDisplayUnit>();

public IEnumerable<TorMode> TorModes =>
Enum.GetValues(typeof(TorMode)).Cast<TorMode>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ private async Task OnPasteAsync(bool pasteIfInvalid = true)
Uri.IsWellFormedUriString(endPoint, UriKind.Absolute))
{
var payjoinEndPointUri = new Uri(endPoint);
if (!Services.Config.UseTor)
if (Services.Config.UseTor != TorMode.Disabled)
{
if (payjoinEndPointUri.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase))
{
Expand Down
12 changes: 10 additions & 2 deletions WalletWasabi.Fluent/Views/Settings/GeneralSettingsTabView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:settings="using:WalletWasabi.Fluent.ViewModels.Settings"
xmlns:conv="using:WalletWasabi.Fluent.Converters"
xmlns:basemodels="using:WalletWasabi.Models"
xmlns:models="clr-namespace:WalletWasabi.Fluent.Models"
mc:Ignorable="d" d:DesignWidth="428" d:DesignHeight="371"
x:Class="WalletWasabi.Fluent.Views.Settings.GeneralSettingsTabView"
Expand Down Expand Up @@ -45,10 +46,17 @@

<DockPanel>
<TextBlock Text="Network anonymization (Tor)" />
<ToggleSwitch IsChecked="{Binding Settings.UseTor}" />
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding TorModes}" SelectedItem="{Binding Settings.UseTor}" Width="250">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="basemodels:TorMode">
<TextBlock Text="{Binding Converter={x:Static conv:EnumConverters.ToFriendlyName}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DockPanel>

<DockPanel IsVisible="{Binding Settings.UseTor}">
<!--<DockPanel IsVisible="{Binding Settings.UseTor}">-->
<DockPanel IsVisible="{Binding Settings.UseTor, ConverterParameter={x:Static basemodels:TorMode.Enabled}, Converter={x:Static conv:EnumToBoolConverter.Instance}}">
<TextBlock Text="Terminate Tor when Wasabi shuts down" />
<ToggleSwitch IsChecked="{Binding Settings.TerminateTorOnExit}" />
</DockPanel>
Expand Down
2 changes: 1 addition & 1 deletion WalletWasabi.Tests/UnitTests/Bases/ConfigManagerNgTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ static string GetConfigString(string localBitcoinCoreDataDir)
"MainNetBackendUri": "https://api.wasabiwallet.io/",
"TestNetClearnetBackendUri": "https://api.wasabiwallet.co/",
"RegTestBackendUri": "http://localhost:37127/",
"UseTor": true,
"UseTor": "Enabled",
"TerminateTorOnExit": false,
"TorBridges": [],
"DownloadNewVersion": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public async Task UseCorrectIdentitiesAsync()
TorTcpConnectionFactory tcpConnectionFactory = mockTcpConnectionFactory.Object;

// Use implementation of TorHttpPool and only replace SendCoreAsync behavior.
Mock<TorHttpPool> mockTorHttpPool = new(MockBehavior.Loose, tcpConnectionFactory) { CallBase = true };
Mock<TorHttpPool> mockTorHttpPool = new(MockBehavior.Loose, tcpConnectionFactory, true) { CallBase = true };
mockTorHttpPool.Setup(x => x.SendCoreAsync(It.IsAny<TorTcpConnection>(), It.IsAny<HttpRequestMessage>(), It.IsAny<Uri>(), It.IsAny<CancellationToken>()))
.Returns((TorTcpConnection tcpConnection, HttpRequestMessage request, Uri requestUriOverride, CancellationToken cancellationToken) =>
{
Expand Down Expand Up @@ -373,7 +373,7 @@ public async Task PersonCircuitLifetimeAsync()
Mock<TorTcpConnectionFactory> mockTcpConnectionFactory = new(MockBehavior.Strict, new IPEndPoint(IPAddress.Loopback, 7777));
mockTcpConnectionFactory.Setup(c => c.ConnectAsync(It.IsAny<Uri>(), aliceCircuit, It.IsAny<CancellationToken>())).ReturnsAsync(aliceConnection);

Mock<TorHttpPool> mockTorHttpPool = new(MockBehavior.Loose, mockTcpConnectionFactory.Object) { CallBase = true };
Mock<TorHttpPool> mockTorHttpPool = new(MockBehavior.Loose, mockTcpConnectionFactory.Object, true) { CallBase = true };
mockTorHttpPool.Setup(x => x.SendCoreAsync(It.IsAny<TorTcpConnection>(), It.IsAny<HttpRequestMessage>(), It.IsAny<Uri>(), It.IsAny<CancellationToken>()))
.Returns((TorTcpConnection tcpConnection, HttpRequestMessage request, Uri requestUriOverride, CancellationToken cancellationToken) =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using WalletWasabi.Daemon;
using WalletWasabi.Fluent.Models;
using WalletWasabi.Fluent.Models.UI;
using WalletWasabi.Models;

namespace WalletWasabi.Tests.UnitTests.ViewModels.UIContext;

Expand All @@ -25,7 +26,7 @@ public class NullApplicationSettings : IApplicationSettings
public FeeDisplayUnit SelectedFeeDisplayUnit { get; set; }
public bool RunOnSystemStartup { get; set; }
public bool HideOnClose { get; set; }
public bool UseTor { get; set; }
public TorMode UseTor { get; set; }
public bool TerminateTorOnExit { get; set; }
public bool DownloadNewVersion { get; set; }
public bool PrivacyMode { get; set; }
Expand Down
24 changes: 24 additions & 0 deletions WalletWasabi/Models/TorMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace WalletWasabi.Models;

/// <summary>
/// Modes for how we interact with Tor.
/// </summary>
public enum TorMode
{
/// <summary>Tor is disabled. Clearnet is used instead.</summary>
[FriendlyName("Disabled")]
Disabled,

/// <summary>Use running Tor or start a new Tor process if it is not running.</summary>
/// <remarks>In this mode, Wasabi app is the owner of its Tor process.</remarks>
[FriendlyName("Enabled")]
Enabled,

/// <summary>Use only running Tor process.</summary>
/// <remarks>
/// In this mode, Wasabi app is not the owner of its Tor process.
/// <para>Useful for distributions like Whonix or Tails where starting a new Tor process is not a good option.</para>
/// </remarks>
[FriendlyName("Enabled (connect-only mode)")]
EnabledOnlyRunning,
}
Loading

0 comments on commit 1be84e5

Please sign in to comment.