Use connector
MODIFY
WalletWasabi.Tests/IntegrationTests/TorTests.cs
WalletWasabi.Tests/IntegrationTests/TorTests.cs
@@ -194,10 +194,10 @@ public class TorTests : IAsyncLifetime
public async Task TorRunningAsync()
{
TorTcpConnectionFactory client1 = new(new IPEndPoint(IPAddress.Loopback, 37150));
- Assert.True(await client1.IsTorRunningAsync());
+ Assert.True(await client1.IsTorRunningAsync(CancellationToken.None));
TorTcpConnectionFactory client2 = new(new IPEndPoint(IPAddress.Loopback, 9054));
- Assert.False(await client2.IsTorRunningAsync());
+ Assert.False(await client2.IsTorRunningAsync(CancellationToken.None));
}
private async Task<List<string>> QBitTestAsync(TorHttpClient client, int times, bool alterRequests = false)

MODIFY
WalletWasabi.Tests/UnitTests/Tor/TorProcessManagerTests.cs
WalletWasabi.Tests/UnitTests/Tor/TorProcessManagerTests.cs
@@ -43,7 +43,7 @@ public class TorProcessManagerTests
// Set up Tor process manager.
Mock<TorTcpConnectionFactory> mockTcpConnectionFactory = new(MockBehavior.Strict, DummyTorControlEndpoint);
- mockTcpConnectionFactory.Setup(c => c.IsTorRunningAsync())
+ mockTcpConnectionFactory.Setup(c => c.IsTorRunningAsync(CancellationToken.None))
.ReturnsAsync(false);
// Mock TorProcessManager.

MODIFY
WalletWasabi/Tor/Http/TorHttpClient.cs
WalletWasabi/Tor/Http/TorHttpClient.cs
@@ -87,8 +87,8 @@ public class TorHttpClient : IHttpClient
}
}
- public Task<bool> IsTorRunningAsync()
+ public Task<bool> IsTorRunningAsync(CancellationToken cancel)
{
- return TorHttpPool.IsTorRunningAsync();
+ return TorHttpPool.IsTorRunningAsync(cancel);
}
}

MODIFY
WalletWasabi/Tor/Socks5/Pool/TorHttpPool.cs
WalletWasabi/Tor/Socks5/Pool/TorHttpPool.cs
@@ -64,9 +64,9 @@ public class TorHttpPool : IDisposable
public static DateTimeOffset? TorDoesntWorkSince { get; private set; }
- public Task<bool> IsTorRunningAsync()
+ public Task<bool> IsTorRunningAsync(CancellationToken cancel)
{
- return TcpConnectionFactory.IsTorRunningAsync();
+ return TcpConnectionFactory.IsTorRunningAsync(cancel);
}
public static Exception? LatestTorException { get; private set; } = null;

ADD
WalletWasabi/Tor/Socks5/TcpClientSocks5Connector.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using WalletWasabi.Tor.Socks5.Exceptions;
+
+namespace WalletWasabi.Tor.Socks5;
+
+public static class TcpClientSocks5Connector
+{
+ /// <summary>
+ /// Establishes TCP connection with Tor SOCKS5 endpoint.
+ /// </summary>
+ /// <param name="builder">Runs in between creation of TCP client and connection to the end point.</param>
+ /// <exception cref="ArgumentException">This should never happen.</exception>
+ /// <exception cref="TorException">When connection to Tor SOCKS5 endpoint fails.</exception>
+ public static async Task<TcpClient> ConnectAsync(EndPoint endPoint, CancellationToken cancel, Action<TcpClient>? builder = null)
+ {
+ try
+ {
+ return await TcpClientConnector.ConnectAsync(endPoint, cancel, builder).ConfigureAwait(false);
+ }
+ catch (SocketException ex) when (ex.ErrorCode is 10061 or 111 or 61)
+ {
+ // 10061 ~ "No connection could be made because the target machine actively refused it" on Windows.
+ // 111 ~ "Connection refused" on Linux.
+ // 61 ~ "Connection refused" on macOS.
+ throw new TorConnectionException($"Could not connect to Tor SOCKSPort at '{endPoint}'. Is Tor running?", ex);
+ }
+ }
+}

MODIFY
WalletWasabi/Tor/Socks5/TorTcpConnectionFactory.cs
WalletWasabi/Tor/Socks5/TorTcpConnectionFactory.cs
@@ -66,7 +66,7 @@ public class TorTcpConnectionFactory
/// <param name="circuit">Tor circuit we want to use in authentication.</param>
/// <param name="cancellationToken">Cancellation token to cancel the asynchronous operation.</param>
/// <returns>New <see cref="TorTcpConnection"/> instance.</returns>
- /// <exception cref="TorConnectionException">When <see cref="ConnectAsync(TcpClient, CancellationToken)"/> fails.</exception>
+ /// <exception cref="TorConnectionException">When <see cref="TcpClientSocks5Connector.ConnectAsync"/> fails.</exception>
public async Task<TorTcpConnection> ConnectAsync(string host, int port, bool useSsl, ICircuit circuit, CancellationToken cancellationToken = default)
{
TcpClient? tcpClient = null;
@@ -74,25 +74,27 @@ public class TorTcpConnectionFactory
try
{
- tcpClient = new(TorSocks5EndPoint.AddressFamily);
- tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
+ tcpClient = await TcpClientSocks5Connector.ConnectAsync(TorSocks5EndPoint, cancellationToken, client =>
+ {
+ client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
// Windows 7 does not support the API we use.
if (!(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version.Major < 10))
{
try
{
- tcpClient.Client.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 30);
- tcpClient.Client.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 5);
- tcpClient.Client.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, 5);
+ client.Client.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 30);
+ client.Client.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 5);
+ client.Client.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, 5);
}
catch (SocketException ex) when (ex.ErrorCode is 10042)
{
Logger.LogWarning("KeepAlive settings are not allowed by your OS. Ignoring.");
}
}
+ }).ConfigureAwait(false);
- transportStream = await ConnectAsync(tcpClient, cancellationToken).ConfigureAwait(false);
+ transportStream = tcpClient.GetStream();
await HandshakeAsync(tcpClient, circuit, cancellationToken).ConfigureAwait(false);
await ConnectToDestinationAsync(tcpClient, host, port, cancellationToken).ConfigureAwait(false);
@@ -115,38 +117,16 @@ public class TorTcpConnectionFactory
}
}
- /// <summary>
- /// Establishes TCP connection with Tor SOCKS5 endpoint.
- /// </summary>
- /// <exception cref="ArgumentException">This should never happen.</exception>
- /// <exception cref="TorException">When connection to Tor SOCKS5 endpoint fails.</exception>
- private async Task<NetworkStream> ConnectAsync(TcpClient tcpClient, CancellationToken cancellationToken = default)
- {
- try
- {
- await tcpClient.ConnectAsync(TorHost, TorPort, cancellationToken).ConfigureAwait(false);
- return tcpClient.GetStream();
- }
- catch (SocketException ex) when (ex.ErrorCode is 10061 or 111 or 61)
- {
- // 10061 ~ "No connection could be made because the target machine actively refused it" on Windows.
- // 111 ~ "Connection refused" on Linux.
- // 61 ~ "Connection refused" on macOS.
- throw new TorConnectionException($"Could not connect to Tor SOCKSPort at '{TorHost}:{TorPort}'. Is Tor running?", ex);
- }
- }
-
/// <summary>
/// Checks whether communication can be established with Tor over <see cref="TorSocks5EndPoint"/> endpoint.
/// </summary>
- public virtual async Task<bool> IsTorRunningAsync()
+ public virtual async Task<bool> IsTorRunningAsync(CancellationToken cancel)
{
try
{
// Internal TCP client may close, so we need a new instance here.
- using TcpClient tcpClient = new(TorSocks5EndPoint.AddressFamily);
- await ConnectAsync(tcpClient).ConfigureAwait(false);
- await HandshakeAsync(tcpClient, DefaultCircuit.Instance).ConfigureAwait(false);
+ using var tcpClient = await TcpClientSocks5Connector.ConnectAsync(TorSocks5EndPoint, cancel).ConfigureAwait(false);
+ await HandshakeAsync(tcpClient, DefaultCircuit.Instance, cancel).ConfigureAwait(false);
return true;
}

MODIFY
WalletWasabi/Tor/TcpClientConnector.cs
WalletWasabi/Tor/TcpClientConnector.cs
@@ -14,9 +14,11 @@ public static class TcpClientConnector
/// <summary>
/// Connects to end point using a TCP client.
/// </summary>
- public static async Task<TcpClient> ConnectAsync(EndPoint endPoint, CancellationToken cancel)
+ /// <param name="builder">Runs in between creation of TCP client and connection to the end point.</param>
+ public static async Task<TcpClient> ConnectAsync(EndPoint endPoint, CancellationToken cancel, Action<TcpClient>? builder = null)
{
TcpClient tcpClient = new(endPoint.AddressFamily);
+ builder?.Invoke(tcpClient);
switch (endPoint)
{
case DnsEndPoint dnsEndPoint:

MODIFY
WalletWasabi/Tor/TorMonitor.cs
WalletWasabi/Tor/TorMonitor.cs
@@ -251,7 +251,7 @@ public class TorMonitor : PeriodicRunner
if (latestTorException is TorConnectCommandFailedException e)
{
// Tor must be running for us to consider switching to the fallback address.
- bool isRunning = await HttpClient.IsTorRunningAsync().ConfigureAwait(false);
+ bool isRunning = await HttpClient.IsTorRunningAsync(token).ConfigureAwait(false);
if (!isRunning)
{

MODIFY
WalletWasabi/Tor/TorProcessManager.cs
WalletWasabi/Tor/TorProcessManager.cs
@@ -42,6 +42,7 @@ public class TorProcessManager : IAsyncDisposable
/// <summary>To stop the loop that keeps starting Tor process.</summary>
private CancellationTokenSource LoopCts { get; }
+
private TorSettings Settings { get; }
private TorTcpConnectionFactory TcpConnectionFactory { get; }
@@ -112,7 +113,7 @@ public class TorProcessManager : IAsyncDisposable
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().ConfigureAwait(false);
+ bool isAlreadyRunning = await TcpConnectionFactory.IsTorRunningAsync(cancellationToken).ConfigureAwait(false);
if (isAlreadyRunning)
{
@@ -234,7 +235,7 @@ public class TorProcessManager : IAsyncDisposable
{
i++;
- bool isRunning = await TcpConnectionFactory.IsTorRunningAsync().ConfigureAwait(false);
+ bool isRunning = await TcpConnectionFactory.IsTorRunningAsync(token).ConfigureAwait(false);
if (isRunning)
{

Use connector