Add CancellationTokens to TorSocks5Client methods.
MODIFY
WalletWasabi/Tor/Http/TorHttpClient.cs
WalletWasabi/Tor/Http/TorHttpClient.cs
@@ -204,20 +204,14 @@ namespace WalletWasabi.Tor.Http
{
TorSocks5Client = new TorSocks5Client(TorSocks5EndPoint);
await TorSocks5Client.ConnectAsync().ConfigureAwait(false);
- await TorSocks5Client.HandshakeAsync(IsolateStream).ConfigureAwait(false);
- await TorSocks5Client.ConnectToDestinationAsync(host, request.RequestUri.Port).ConfigureAwait(false);
+ await TorSocks5Client.HandshakeAsync(IsolateStream, cancel).ConfigureAwait(false);
+ await TorSocks5Client.ConnectToDestinationAsync(host, request.RequestUri.Port, cancel).ConfigureAwait(false);
Stream stream = TorSocks5Client.TcpClient.GetStream();
if (request.RequestUri.Scheme == "https")
{
SslStream sslStream = new SslStream(stream, leaveInnerStreamOpen: true);
-
- await sslStream
- .AuthenticateAsClientAsync(
- host,
- new X509CertificateCollection(),
- IHttpClient.SupportedSslProtocols,
- checkCertificateRevocation: true).ConfigureAwait(false);
+ await sslStream.AuthenticateAsClientAsync(host, new X509CertificateCollection(), IHttpClient.SupportedSslProtocols, true).ConfigureAwait(false);
stream = sslStream;
}

MODIFY
WalletWasabi/Tor/Socks5/TorSocks5Client.cs
WalletWasabi/Tor/Socks5/TorSocks5Client.cs
@@ -3,6 +3,7 @@ using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
+using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.Crypto.Randomness;
using WalletWasabi.Helpers;
@@ -58,6 +59,7 @@ namespace WalletWasabi.Tor.Socks5
try
{
+ // Cancellation token for ConnectAsync will be available in .NET 5.
await TcpClient.ConnectAsync(host, port.Value).ConfigureAwait(false);
}
catch (Exception ex) when (IsConnectionRefused(ex))
@@ -97,9 +99,10 @@ namespace WalletWasabi.Tor.Socks5
/// https://www.torproject.org/docs/tor-manual.html.en
/// https://gitweb.torproject.org/torspec.git/tree/socks-extensions.txt#n35
/// </summary>
- internal async Task HandshakeAsync(bool isolateStream = true)
+ internal async Task HandshakeAsync(bool isolateStream = true, CancellationToken cancellationToken = default)
{
- await HandshakeAsync(isolateStream ? RandomString.CapitalAlphaNumeric(21) : "").ConfigureAwait(false);
+ string identity = isolateStream ? RandomString.CapitalAlphaNumeric(21) : "";
+ await HandshakeAsync(identity, cancellationToken).ConfigureAwait(false);
}
/// <summary>
@@ -108,7 +111,7 @@ namespace WalletWasabi.Tor.Socks5
/// https://gitweb.torproject.org/torspec.git/tree/socks-extensions.txt#n35
/// </summary>
/// <param name="identity">Isolates streams by identity. If identity is empty string, it won't isolate stream.</param>
- private async Task HandshakeAsync(string identity)
+ private async Task HandshakeAsync(string identity, CancellationToken cancellationToken = default)
{
Logger.LogDebug($"> {nameof(identity)}={identity}");
@@ -117,7 +120,7 @@ namespace WalletWasabi.Tor.Socks5
: new MethodsField(MethodField.UsernamePassword);
byte[] sendBuffer = new VersionMethodRequest(methods).ToBytes();
- byte[] receiveBuffer = await SendAsync(sendBuffer, receiveBufferSize: 2).ConfigureAwait(false);
+ byte[] receiveBuffer = await SendAsync(sendBuffer, receiveBufferSize: 2, cancellationToken).ConfigureAwait(false);
var methodSelection = new MethodSelectionResponse();
methodSelection.FromBytes(receiveBuffer);
@@ -149,7 +152,7 @@ namespace WalletWasabi.Tor.Socks5
sendBuffer = usernamePasswordRequest.ToBytes();
Array.Clear(receiveBuffer, 0, receiveBuffer.Length);
- receiveBuffer = await SendAsync(sendBuffer, 2).ConfigureAwait(false);
+ receiveBuffer = await SendAsync(sendBuffer, receiveBufferSize: 2, cancellationToken).ConfigureAwait(false);
var userNamePasswordResponse = new UsernamePasswordResponse();
userNamePasswordResponse.FromBytes(receiveBuffer);
@@ -172,18 +175,18 @@ namespace WalletWasabi.Tor.Socks5
Logger.LogDebug("<");
}
- internal async Task ConnectToDestinationAsync(EndPoint destination)
+ internal async Task ConnectToDestinationAsync(EndPoint destination, CancellationToken cancellationToken = default)
{
if (!destination.TryGetHostAndPort(out string? host, out int? port))
{
throw new ArgumentException("Endpoint type is not supported.", nameof(destination));
}
- await ConnectToDestinationAsync(host, port.Value).ConfigureAwait(false);
+ await ConnectToDestinationAsync(host, port.Value, cancellationToken).ConfigureAwait(false);
}
/// <param name="host">IPv4 or domain</param>
- internal async Task ConnectToDestinationAsync(string host, int port)
+ internal async Task ConnectToDestinationAsync(string host, int port, CancellationToken cancellationToken = default)
{
Logger.LogDebug($"> {nameof(host)}={host}, {nameof(port)}={port}");
@@ -195,7 +198,7 @@ namespace WalletWasabi.Tor.Socks5
var connectionRequest = new TorSocks5Request(cmd: CmdField.Connect, new AddrField(host), new PortField(port));
var sendBuffer = connectionRequest.ToBytes();
- var receiveBuffer = await SendAsync(sendBuffer).ConfigureAwait(false);
+ var receiveBuffer = await SendAsync(sendBuffer, receiveBufferSize: null, cancellationToken).ConfigureAwait(false);
var connectionResponse = new TorSocks5Response();
connectionResponse.FromBytes(receiveBuffer);
@@ -224,6 +227,11 @@ namespace WalletWasabi.Tor.Socks5
// the client, the server MUST encapsulate the data as appropriate for
// the authentication method in use.
}
+ catch (OperationCanceledException)
+ {
+ Logger.LogTrace($"Connecting to destination {host}:{port} was canceled.");
+ throw;
+ }
catch (Exception e)
{
Logger.LogError("Exception was thrown when connecting to destination.", e);
@@ -301,8 +309,9 @@ namespace WalletWasabi.Tor.Socks5
/// </summary>
/// <param name="sendBuffer">Sent data</param>
/// <param name="receiveBufferSize">Maximum number of bytes expected to be received in the reply</param>
+ /// <param name="cancellationToken">Cancellation token to cancel sending.</param>
/// <returns>Reply</returns>
- public async Task<byte[]> SendAsync(byte[] sendBuffer, int? receiveBufferSize = null)
+ public async Task<byte[]> SendAsync(byte[] sendBuffer, int? receiveBufferSize = null, CancellationToken cancellationToken = default)
{
Guard.NotNullOrEmpty(nameof(sendBuffer), sendBuffer);
@@ -315,8 +324,8 @@ namespace WalletWasabi.Tor.Socks5
var stream = TcpClient.GetStream();
// Write data to the stream
- await stream.WriteAsync(sendBuffer, 0, sendBuffer.Length).ConfigureAwait(false);
- await stream.FlushAsync().ConfigureAwait(false);
+ await stream.WriteAsync(sendBuffer, 0, sendBuffer.Length, cancellationToken).ConfigureAwait(false);
+ await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
// If receiveBufferSize is null, zero or negative or bigger than TcpClient.ReceiveBufferSize
// then work with TcpClient.ReceiveBufferSize
@@ -328,7 +337,7 @@ namespace WalletWasabi.Tor.Socks5
// Receive the response
var receiveBuffer = new byte[actualReceiveBufferSize];
- int receiveCount = await stream.ReadAsync(receiveBuffer, 0, actualReceiveBufferSize).ConfigureAwait(false);
+ int receiveCount = await stream.ReadAsync(receiveBuffer, 0, actualReceiveBufferSize, cancellationToken).ConfigureAwait(false);
if (receiveCount <= 0)
{
@@ -346,7 +355,7 @@ namespace WalletWasabi.Tor.Socks5
while (stream.DataAvailable)
{
Array.Clear(receiveBuffer, 0, receiveBuffer.Length);
- receiveCount = await stream.ReadAsync(receiveBuffer, 0, actualReceiveBufferSize).ConfigureAwait(false);
+ receiveCount = await stream.ReadAsync(receiveBuffer, 0, actualReceiveBufferSize, cancellationToken).ConfigureAwait(false);
if (receiveCount <= 0)
{
throw new ConnectionException($"Not connected to Tor SOCKS5 proxy: {TorSocks5EndPoint}.");
@@ -357,6 +366,11 @@ namespace WalletWasabi.Tor.Socks5
return builder.ToArray();
}
}
+ catch (OperationCanceledException)
+ {
+ Logger.LogTrace($"Send operation was canceled.");
+ throw;
+ }
catch (IOException ex)
{
throw new ConnectionException($"{nameof(TorSocks5Client)} is not connected to {RemoteEndPoint}.", ex);

Add CancellationTokens to TorSocks5Client methods.