Skip to content
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

[Tor] Towards Whonix & Tails #12979

Closed
Closed
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
4 changes: 4 additions & 0 deletions WalletWasabi.Daemon/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
[ nameof(UseTor)] = (
"All the communications go through the Tor network",
GetBoolValue("UseTor", PersistentConfig.UseTor, cliArgs)),
[ nameof(UseOnlyRunningTor)] = (
"Connect to an already running Tor instance without attempting to run the bundled Tor",
GetBoolValue("UseOnlyRunningTor", false, cliArgs)),

Check warning on line 64 in WalletWasabi.Daemon/Config.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

❌ Getting worse: Large Method

Config increases from 116 to 119 lines of code, threshold = 70. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.
[ nameof(TorFolder)] = (
"Folder where Tor binary is located",
GetNullableStringValue("TorFolder", null, cliArgs)),
Expand Down Expand Up @@ -155,6 +158,7 @@
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 bool UseOnlyRunningTor => GetEffectiveValue<BoolValue, bool>(nameof(UseOnlyRunningTor));
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
13 changes: 10 additions & 3 deletions WalletWasabi.Daemon/Global.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.Extensions.Caching.Memory;

Check notice on line 1 in WalletWasabi.Daemon/Global.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

ℹ Getting worse: Overall Code Complexity

The mean cyclomatic complexity increases from 4.83 to 4.92, threshold = 4. This file has many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more conditionals.
using NBitcoin;
using Nito.AsyncEx;
using System;
Expand Down Expand Up @@ -54,7 +54,8 @@
TorSettings = new TorSettings(
DataDir,
distributionFolderPath: EnvironmentHelpers.GetFullBaseDirectory(),
Config.TerminateTorOnExit,
terminateOnExit: Config.TerminateTorOnExit,
useOnlyRunningTor: Config.UseOnlyRunningTor,

Check warning on line 58 in WalletWasabi.Daemon/Global.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

❌ Getting worse: Large Method

Global increases from 73 to 74 lines of code, threshold = 70. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.
socksPort: config.TorSocksPort,
controlPort: config.TorControlPort,
torFolder: config.TorFolder,
Expand Down Expand Up @@ -186,7 +187,8 @@
private WasabiHttpClientFactory BuildHttpClientFactory(Func<Uri> backendUriGetter) =>
new(
Config.UseTor ? TorSettings.SocksEndpoint : null,
backendUriGetter);
backendUriGetter,
torControlAvailable: !TorSettings.UseOnlyRunningTor);

public async Task InitializeNoWalletAsync(bool initializeSleepInhibitor, TerminateService terminateService, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -333,7 +335,12 @@
}
}

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.UseOnlyRunningTor)
{
HostedServices.Register<TorMonitor>(() => new TorMonitor(period: TimeSpan.FromMinutes(1), torProcessManager: TorManager, httpClientFactory: HttpClientFactory), nameof(TorMonitor));
}

HostedServices.Register<TorStatusChecker>(() => TorStatusChecker, "Tor Network Checker");
}
}
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
30 changes: 21 additions & 9 deletions WalletWasabi/Tor/Socks5/Pool/TorHttpPool.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;

Check notice on line 1 in WalletWasabi/Tor/Socks5/Pool/TorHttpPool.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

ℹ Getting worse: Overall Code Complexity

The mean cyclomatic complexity increases from 5.44 to 5.63, threshold = 4. This file has many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more conditionals.
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
Expand Down Expand Up @@ -41,15 +41,16 @@
SingleWriter = false,
};

public TorHttpPool(EndPoint endpoint)
: this(new TorTcpConnectionFactory(endpoint))
public TorHttpPool(EndPoint endpoint, bool torControlAvailable = true)
: this(new TorTcpConnectionFactory(endpoint), torControlAvailable)
{
}

/// <summary>Constructor that helps in tests.</summary>
internal TorHttpPool(TorTcpConnectionFactory tcpConnectionFactory)
internal TorHttpPool(TorTcpConnectionFactory tcpConnectionFactory, bool torControlAvailable = true)
{
TcpConnectionFactory = tcpConnectionFactory;
TorControlAvailable = torControlAvailable;
PreBuildingRequestChannel = Channel.CreateUnbounded<TorPrebuildCircuitRequest>(Options);
PreBuildingLoopTask = Task.Run(PreBuildingLoopAsync);
}
Expand All @@ -72,7 +73,7 @@
private object ConnectionsLock { get; } = new();

private TorTcpConnectionFactory TcpConnectionFactory { get; }

public bool TorControlAvailable { get; }
public static DateTimeOffset? TorDoesntWorkSince { get; private set; }

public Task<bool> IsTorRunningAsync(CancellationToken cancel)
Expand Down Expand Up @@ -407,47 +408,53 @@

private async Task<TorTcpConnection?> CreateNewConnectionAsync(Uri requestUri, INamedCircuit circuit, CancellationToken cancellationToken)
{
lock (ConnectionsLock)
if (TorControlAvailable)
{
TorStreamsBeingBuilt[circuit.Name] = null;
lock (ConnectionsLock)
{
TorStreamsBeingBuilt[circuit.Name] = null;
}
}

TorTcpConnection? connection = null;

try
{
connection = await TcpConnectionFactory.ConnectAsync(requestUri, circuit, cancellationToken).ConfigureAwait(false);
Logger.LogTrace($"[NEW {connection}]['{requestUri}'] Created new Tor SOCKS5 connection.");
}
catch (TorConnectCommandFailedException e) when (e.RepField == RepField.TtlExpired)
{
Logger.LogTrace($"['{requestUri}'] TTL expired occurred. Probably some relay/networking problem. No need to worry too much.");
throw;
}
catch (TorException e)
{
Logger.LogTrace($"['{requestUri}'][ERROR] Failed to create a new pool connection.", e);
throw;
}
catch (OperationCanceledException)
{
Logger.LogTrace($"['{requestUri}'] Operation was canceled.");
throw;
}
catch (Exception e)
{
Logger.LogTrace($"['{requestUri}'][EXCEPTION] {e}");
throw;
}
finally
{
lock (ConnectionsLock)
{
if (TorStreamsBeingBuilt.Remove(circuit.Name, out TorStreamInfo? streamInfo))
if (TorControlAvailable)
{
if (connection is not null && streamInfo is not null)
if (TorStreamsBeingBuilt.Remove(circuit.Name, out TorStreamInfo? streamInfo))
{
connection.SetLatestStreamInfo(streamInfo, ifEmpty: true);
if (connection is not null && streamInfo is not null)
{
connection.SetLatestStreamInfo(streamInfo, ifEmpty: true);
}

Check warning on line 457 in WalletWasabi/Tor/Socks5/Pool/TorHttpPool.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

❌ Getting worse: Complex Method

CreateNewConnectionAsync increases in cyclomatic complexity from 10 to 12, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
}
}

Expand Down Expand Up @@ -524,6 +531,11 @@
/// </remarks>
public void ReportStreamStatus(string streamUsername, StreamStatusFlag streamStatus, string circuitID)
{
if (!TorControlAvailable)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@yahiheb FYI, this was wrong it was if (TorControlAvailable) so we could report exceptions when we shouldn't. That's probably what you reported over Slack.

{
throw new UnreachableException("This method can be called only if Wasabi was run with Tor Control support.");
}

lock (ConnectionsLock)
{
// If the key is not present, then are not the ones starting that Tor stream.
Expand Down
3 changes: 2 additions & 1 deletion WalletWasabi/Tor/TorMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
namespace WalletWasabi.Tor;

/// <summary>Monitors Tor process bootstrap and reachability of Wasabi Backend.</summary>
/// <remarks>Tor Monitor can only work if Tor Control is available.</remarks>
public class TorMonitor : PeriodicRunner
{
public static readonly TimeSpan CheckIfRunningAfterTorMisbehavedFor = TimeSpan.FromMinutes(10);
Expand Down Expand Up @@ -107,7 +108,7 @@ private async Task MonitorEventsAsync(CancellationToken cancellationToken)
using CancellationTokenSource linkedCts2 = CancellationTokenSource.CreateLinkedTokenSource(linkedCts.Token, torTerminatedCancellationToken);

Logger.LogInfo("Starting Tor bootstrap monitor…");
await torControlClient.SubscribeEventsAsync(EventNames, linkedCts2.Token).ConfigureAwait(false);
await torControlClient!.SubscribeEventsAsync(EventNames, linkedCts2.Token).ConfigureAwait(false);
bool circuitEstablished = false;

await foreach (TorControlReply reply in torControlClient.ReadEventsAsync(linkedCts2.Token).ConfigureAwait(false))
Expand Down
127 changes: 98 additions & 29 deletions WalletWasabi/Tor/TorProcessManager.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;

Check notice on line 1 in WalletWasabi/Tor/TorProcessManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

✅ Getting better: Overall Code Complexity

The mean cyclomatic complexity decreases from 4.55 to 4.43, threshold = 4. This file has many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more conditionals.
using System.Diagnostics;
using System.IO;
using System.Linq;
Expand All @@ -21,7 +22,7 @@
internal const string TorProcessStartedByDifferentUser = "Tor was started by another user and we can't use it nor kill it.";

/// <summary>Task completion source returning a cancellation token which is canceled when Tor process is terminated.</summary>
private volatile TaskCompletionSource<(CancellationToken, TorControlClient)> _tcs = new();
private volatile TaskCompletionSource<(CancellationToken, TorControlClient?)> _tcs = new();

public TorProcessManager(TorSettings settings) :
this(settings, new(settings.SocksEndpoint))
Expand Down Expand Up @@ -50,14 +51,20 @@
private TorSettings Settings { get; }
private TorTcpConnectionFactory TcpConnectionFactory { get; }

/// <remarks>Guarded by <see cref="StateLock"/>.</remarks>
/// <remarks>
/// Only set if <see cref="TorSettings.UseOnlyRunningTor"/> is not on.
/// <para>Guarded by <see cref="StateLock"/>.</para>
/// </remarks>
private ProcessAsync? TorProcess { get; set; }

/// <remarks>Guarded by <see cref="StateLock"/>.</remarks>
/// <remarks>
/// Only set if <see cref="TorSettings.UseOnlyRunningTor"/> is not on.
/// <para>Guarded by <see cref="StateLock"/>.</para>
/// </remarks>
private TorControlClient? TorControlClient { get; set; }

/// <inheritdoc cref="StartAsync(int, CancellationToken)"/>
public Task<(CancellationToken, TorControlClient)> StartAsync(CancellationToken cancellationToken)
public Task<(CancellationToken, TorControlClient?)> StartAsync(CancellationToken cancellationToken)
{
return StartAsync(attempts: 1, cancellationToken);
}
Expand All @@ -68,7 +75,7 @@
/// <remarks>This method must be called exactly once.</remarks>
/// <exception cref="OperationCanceledException">When the operation is cancelled by the user.</exception>
/// <exception cref="InvalidOperationException">When all attempts are tried without success.</exception>
public async Task<(CancellationToken, TorControlClient)> StartAsync(int attempts, CancellationToken cancellationToken)
public async Task<(CancellationToken, TorControlClient?)> StartAsync(int attempts, CancellationToken cancellationToken)
{
LoopTask = RestartingLoopAsync(cancellationToken);

Expand All @@ -94,7 +101,7 @@
/// <summary>Waits until Tor process is fully started or until it is stopped for some reason.</summary>
/// <returns>Cancellation token which is canceled once Tor process terminates or once <paramref name="cancellationToken"/> is canceled.</returns>
/// <remarks>This is useful to set up Tor control monitors that need to be restarted once Tor process is started again.</remarks>
public Task<(CancellationToken, TorControlClient)> WaitForNextAttemptAsync(CancellationToken cancellationToken)
public Task<(CancellationToken, TorControlClient?)> WaitForNextAttemptAsync(CancellationToken cancellationToken)
{
return _tcs.Task.WaitAsync(cancellationToken);
}
Expand All @@ -103,182 +110,219 @@
/// <param name="globalCancellationToken">Application lifetime cancellation token.</param>
private async Task RestartingLoopAsync(CancellationToken globalCancellationToken)
{
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(globalCancellationToken, LoopCts.Token);
if (Settings.UseOnlyRunningTor)
{
await RestartingLoopForRunningTorAsync(globalCancellationToken).ConfigureAwait(false);
}
else
{
await RestartingLoopForBundledTorAsync(globalCancellationToken).ConfigureAwait(false);
}
}

private async Task RestartingLoopForRunningTorAsync(CancellationToken globalCancellationToken)
{
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(globalCancellationToken, LoopCts.Token);
CancellationToken cancellationToken = linkedCts.Token;

bool detectedTorState = false;
CancellationTokenSource torStoppedCts = new();

while (!cancellationToken.IsCancellationRequested)
{
bool isTorRunning = await TcpConnectionFactory.IsTorRunningAsync(cancellationToken).ConfigureAwait(false);

if (detectedTorState && isTorRunning) // Case: Still running.
{
// No-op.
}
else if (!detectedTorState && isTorRunning) // Case: Started running.
{
_tcs.SetResult((torStoppedCts.Token, null));
}
else if (detectedTorState && !isTorRunning) // Case: Stopped running.
{
bool willWaitForTorRestart = !cancellationToken.IsCancellationRequested;
OnTorStop(willWaitForTorRestart, exception: null, torStoppedCts, globalCancellationToken);
torStoppedCts = new();
}

detectedTorState = isTorRunning;

// We don't use Tor Control client with an already running Tor, so we just use a simple sleep mechanism.
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);

if (cancellationToken.IsCancellationRequested)
{
Logger.LogDebug("User canceled operation.");
break;
}
}

torStoppedCts.Dispose();
}

Check warning on line 163 in WalletWasabi/Tor/TorProcessManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

❌ New issue: Complex Method

RestartingLoopForRunningTorAsync has a cyclomatic complexity of 9, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

Check warning on line 163 in WalletWasabi/Tor/TorProcessManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

❌ New issue: Bumpy Road Ahead

RestartingLoopForRunningTorAsync has 3 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

/// <summary>
/// Keeps starting Tor OS process.
/// </summary>
private async Task RestartingLoopForBundledTorAsync(CancellationToken globalCancellationToken)
{
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(globalCancellationToken, LoopCts.Token);
CancellationToken cancellationToken = linkedCts.Token;

while (!cancellationToken.IsCancellationRequested)
{
ProcessAsync? process = null;
TorControlClient? controlClient = null;
Exception? exception = null;
bool setNewTcs = true;

// Use CancellationTokenSource to signal that Tor process terminated.
using CancellationTokenSource cts = new();

try
{
// Is Tor already running? Either our Tor process from previous Wasabi Wallet run or possibly user's own Tor.
bool isAlreadyRunning = await TcpConnectionFactory.IsTorRunningAsync(cancellationToken).ConfigureAwait(false);

if (isAlreadyRunning)
{
Logger.LogInfo($"Tor is already running on {Settings.SocksEndpoint}");
controlClient = await InitTorControlAsync(cancellationToken).ConfigureAwait(false);

// Tor process can crash even between these two commands too.
int processId = await controlClient.GetTorProcessIdAsync(cancellationToken).ConfigureAwait(false);
process = new ProcessAsync(Process.GetProcessById(processId));

try
{
// Note: This is a workaround how to check whether we have sufficient permissions for the process.
// Especially, we want to make sure that Tor is running under our user and not a different one.
// Example situation: Tor is run under admin account but then the app is run under a non-privileged account.
nint _ = process.Handle;
}
catch (Exception ex)
{
throw new NotSupportedException(TorProcessStartedByDifferentUser, ex);
}

TorControlReply clientTransportPluginReply = await controlClient.GetConfAsync(keyword: "ClientTransportPlugin", cancellationToken).ConfigureAwait(false);
if (!clientTransportPluginReply.Success)
{
throw new InvalidOperationException("Tor control failed to report the current transport plugin.");
}

// Check if the bridges in the running Tor instance are the same as user requested.
TorControlReply bridgeReply = await controlClient.GetConfAsync(keyword: "Bridge", cancellationToken).ConfigureAwait(false);
if (!bridgeReply.Success)
{
throw new InvalidOperationException("Tor control failed to report active bridges.");
}

// Compare as two unordered sets.
string[] currentBridges = bridgeReply.ResponseLines.Where(x => x != "Bridge").Select(x => x.Split('=', 2)[1]).Order().ToArray();
bool areBridgesAsRequired = currentBridges.SequenceEqual(Settings.Bridges.Order());

if (!areBridgesAsRequired)
{
Logger.LogInfo("Tor bridges of the running Tor instance are different than required. Restarting Tor.");
await controlClient.SignalShutdownAsync(cancellationToken).ConfigureAwait(false);
continue;
}
}
else
{
string arguments = Settings.GetCmdArguments();
Logger.LogTrace($"Starting Tor with arguments: {arguments}");

process = StartProcess(arguments);

bool isRunning = await EnsureRunningAsync(process, cancellationToken).ConfigureAwait(false);

if (!isRunning)
{
Logger.LogTrace("Failed to start Tor process. Trying again.");
continue;
}

controlClient = await InitTorControlAsync(cancellationToken).ConfigureAwait(false);
}

Logger.LogInfo("Tor is running.");

// Only now we know that Tor process is fully started.
lock (StateLock)
{
TorProcess = process;
TorControlClient = controlClient;

_tcs.SetResult((cts.Token, controlClient));
}

await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);

Logger.LogDebug("Tor process exited.");
}
catch (OperationCanceledException)
{
Logger.LogDebug("User canceled operation.");
setNewTcs = false;
break;
}
catch (TorControlException ex)
{
Logger.LogDebug("Tor control failed to initialize.", ex);

// If Tor control fails to initialize, we want to try to start Tor again and initialize Tor control again.
if (process is not null)
{
Logger.LogDebug("Attempt to kill the running Tor process.");
process.Kill();
}
else
{
// If Tor was already started, we don't have Tor process ID (pid), so it's harder to kill it.
Process[] torProcesses = GetTorProcesses();

bool killAttempt = false;

foreach (Process torProcess in torProcesses)
{
try
{
// This throws if we can't access MainModule of an elevated process from a non elevated one.
if (torProcess.MainModule?.FileName == Settings.TorBinaryFilePath)
{
Logger.LogInfo("Kill running Tor process to restart it again.");
killAttempt = true;
torProcess.Kill();
}
}
catch
{
}
}

// Tor was started by another user and we can't kill it.
if (torProcesses.Length == 0 || !killAttempt)
{
Logger.LogDebug("Failed to find the Tor process in the list of processes.");
setNewTcs = false;
exception = new NotSupportedException(TorProcessStartedByDifferentUser, ex);
throw exception;
}
}
}
catch (Exception ex)
{
Logger.LogError("Unexpected problem in starting Tor.", ex);
setNewTcs = false;
exception = ex;
throw;
}
finally
{
TaskCompletionSource<(CancellationToken, TorControlClient)> originalTcs = _tcs;

if (setNewTcs)
{
// (1) and (2) must be in this order. Otherwise, there is a race condition risk of getting invalid CT by clients.
TaskCompletionSource<(CancellationToken, TorControlClient)> newTcs = new();
originalTcs = Interlocked.Exchange(ref _tcs, newTcs); // (1)
}

cts.Cancel(); // (2)

if (exception is not null)
{
originalTcs.TrySetException(exception);
}
else
{
originalTcs.TrySetCanceled(globalCancellationToken);
}

cts.Dispose();
OnTorStop(setNewTcs, exception, cts, globalCancellationToken);

Check notice on line 325 in WalletWasabi/Tor/TorProcessManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

✅ No longer an issue: Complex Method

RestartingLoopAsync is no longer above the threshold for cyclomatic complexity. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

Check warning on line 325 in WalletWasabi/Tor/TorProcessManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

❌ New issue: Complex Method

RestartingLoopForBundledTorAsync has a cyclomatic complexity of 19, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

Check notice on line 325 in WalletWasabi/Tor/TorProcessManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

✅ No longer an issue: Bumpy Road Ahead

RestartingLoopAsync is no longer above the threshold for logical blocks with deeply nested code. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

Check warning on line 325 in WalletWasabi/Tor/TorProcessManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

❌ New issue: Bumpy Road Ahead

RestartingLoopForBundledTorAsync has 3 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

Check notice on line 325 in WalletWasabi/Tor/TorProcessManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

✅ No longer an issue: Deep, Nested Complexity

RestartingLoopAsync is no longer above the threshold for nested complexity depth. This function contains deeply nested logic such as if statements and/or loops. The deeper the nesting, the lower the code health.

Check notice on line 325 in WalletWasabi/Tor/TorProcessManager.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

ℹ New issue: Deep, Nested Complexity

RestartingLoopForBundledTorAsync has a nested complexity depth of 4, threshold = 4. This function contains deeply nested logic such as if statements and/or loops. The deeper the nesting, the lower the code health.

if (controlClient is not null)
{
Expand All @@ -296,6 +340,31 @@
}
}

private void OnTorStop(bool willWaitForTorRestart, Exception? exception, CancellationTokenSource torStoppedCts, CancellationToken globalCancellationToken)
{
TaskCompletionSource<(CancellationToken, TorControlClient?)> originalTcs = _tcs;

if (willWaitForTorRestart)
{
// (1) and (2) must be in this order. Otherwise, there is a race condition risk of getting invalid CT by clients.
TaskCompletionSource<(CancellationToken, TorControlClient?)> newTcs = new();
originalTcs = Interlocked.Exchange(ref _tcs, newTcs); // (1)
}

torStoppedCts.Cancel(); // (2)

if (exception is not null)
{
originalTcs.TrySetException(exception);
}
else
{
originalTcs.TrySetCanceled(globalCancellationToken);
}

torStoppedCts.Dispose();
}

internal virtual Process[] GetTorProcesses()
{
return Process.GetProcessesByName(TorSettings.TorBinaryFileName);
Expand Down
14 changes: 13 additions & 1 deletion WalletWasabi/Tor/TorSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,47 +27,59 @@
string dataDir,
string distributionFolderPath,
bool terminateOnExit,
bool useOnlyRunningTor = false,
int socksPort = DefaultSocksPort,
int controlPort = DefaultControlPort,
string? torFolder = null,
string[]? bridges = null,
int? owningProcessId = null,
bool log = true)
{
IsCustomTorFolder = torFolder is not null;

bool defaultWasabiTorPorts = socksPort == DefaultSocksPort && controlPort == DefaultControlPort;

// Use different ports when user overrides Tor folder to avoid accessing the same control_auth_cookie file.
if (IsCustomTorFolder && defaultWasabiTorPorts)
{
socksPort = 37152;
controlPort = 37153;
}

TorBinaryDir = torFolder ?? Path.Combine(MicroserviceHelpers.GetBinaryFolder(), "Tor");
TorBinaryFilePath = GetTorBinaryFilePath(TorBinaryDir);
TorTransportPluginsDir = Path.Combine(TorBinaryDir, "PluggableTransports");

TorDataDir = Path.Combine(dataDir, "tordata2");
SocksEndpoint = new IPEndPoint(IPAddress.Loopback, socksPort);
ControlEndpoint = new IPEndPoint(IPAddress.Loopback, controlPort);
Bridges = bridges ?? [];
OwningProcessId = owningProcessId;

CookieAuthFilePath = defaultWasabiTorPorts
? Path.Combine(dataDir, $"control_auth_cookie")
: Path.Combine(dataDir, $"control_auth_cookie_{socksPort}_{controlPort}");

LogFilePath = Path.Combine(dataDir, "TorLogs.txt");
IoHelpers.EnsureContainingDirectoryExists(LogFilePath);
DistributionFolder = distributionFolderPath;
TerminateOnExit = terminateOnExit;

if (useOnlyRunningTor && terminateOnExit)
{
Logger.LogWarning("Wasabi is instructed to use a running Tor process. Terminate on exit was disabled.");
}

UseOnlyRunningTor = useOnlyRunningTor;
TerminateOnExit = useOnlyRunningTor ? false : terminateOnExit;

Check warning on line 74 in WalletWasabi/Tor/TorSettings.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

❌ New issue: Complex Method

TorSettings has a cyclomatic complexity of 10, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

Check notice on line 74 in WalletWasabi/Tor/TorSettings.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

ℹ Getting worse: Constructor Over-Injection

TorSettings increases from 9 to 10 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.
Log = log;
GeoIpPath = Path.Combine(DistributionFolder, "Tor", "Geoip", "geoip");
GeoIp6Path = Path.Combine(DistributionFolder, "Tor", "Geoip", "geoip6");
}

/// <summary><c>true</c> if Wasabi should not attempt to run any Tor process (bundled) or specified via <c>--TorFolder</c> command line option.</summary>
public bool UseOnlyRunningTor { get; }

/// <summary><c>true</c> if user specified a custom Tor folder to run a (possibly) different Tor binary than the bundled Tor, <c>false</c> otherwise.</summary>
public bool IsCustomTorFolder { get; }

Expand Down
Loading
Loading