TorSocks5Client: Re-implement SendRequestAsync for TorSocks5Request to avoid using NetworkStream.DataAvailable.
MODIFY
WalletWasabi/Tor/Socks5/TorSocks5Client.cs
WalletWasabi/Tor/Socks5/TorSocks5Client.cs
@@ -356,54 +356,109 @@ namespace WalletWasabi.Tor.Socks5
}
else if (request is TorSocks5Request)
{
- var actualReceiveBufferSize = TcpClient.ReceiveBufferSize;
+ return await ReadRequestResponseAsync(stream, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ throw new ArgumentException("Not supported request type.", nameof(request));
+ }
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ Logger.LogTrace("Send operation was canceled.");
+ throw;
+ }
+ catch (IOException ex)
+ {
+ throw new TorConnectionException($"{nameof(TorSocks5Client)} is not connected to {RemoteEndPoint}.", ex);
+ }
+ }
+
+ /// <summary>
+ /// Reads response for <see cref="TorSocks5Request"/>.
+ /// </summary>
+ private static async Task<byte[]> ReadRequestResponseAsync(NetworkStream stream, CancellationToken cancellationToken)
+ {
+ ByteArrayBuilder builder = new(capacity: 1024);
- // Receive the response
- var receiveBuffer = new byte[actualReceiveBufferSize];
+ // Read: VER, CMD, RSV and ATYP values.
+ int byteResult = -1;
- int receiveCount = await stream.ReadAsync(receiveBuffer.AsMemory(0, actualReceiveBufferSize), cancellationToken).ConfigureAwait(false);
+ for (int i = 0; i < 4; i++)
+ {
+ byteResult = await stream.ReadByteAsync(cancellationToken).ConfigureAwait(false);
- if (receiveCount <= 0)
+ if (byteResult == -1)
{
- throw new TorConnectionException($"Not connected to Tor SOCKS5 proxy: {TorSocks5EndPoint}.");
+ throw new TorConnectionException("Failed to read first four bytes from the SOCKS5 response.");
}
- // if we could fit everything into our buffer, then return it
- if (!stream.DataAvailable)
- {
- return receiveBuffer[..receiveCount];
+
+ builder.Append((byte)byteResult);
}
- // while we have data available, start building a byte array
- var builder = new ByteArrayBuilder();
- builder.Append(receiveBuffer[..receiveCount]);
- while (stream.DataAvailable)
+ // Process last read byte which is ATYP.
+ byte addrType = (byte)byteResult;
+
+ int dstAddrLength = addrType switch
{
- Array.Clear(receiveBuffer, 0, receiveBuffer.Length);
- receiveCount = await stream.ReadAsync(receiveBuffer.AsMemory(0, actualReceiveBufferSize), cancellationToken).ConfigureAwait(false);
- if (receiveCount <= 0)
+ // IPv4.
+ 0x01 => 4,
+ // Fully-qualified domain name.
+ 0x03 => await stream.ReadByteAsync(cancellationToken).ConfigureAwait(false),
+ // IPv6.
+ 0x04 => 16,
+ _ => throw new TorConnectionException("Received unsupported ATYP value.")
+ };
+
+ if (dstAddrLength == -1)
{
- throw new TorConnectionException($"Not connected to Tor SOCKS5 proxy: {TorSocks5EndPoint}.");
+ throw new TorConnectionException("Failed to read the length of DST.ADDR from the SOCKS5 response.");
}
- builder.Append(receiveBuffer[..receiveCount]);
+
+ // Read DST.ADDR.
+ for (int i = 0; i < dstAddrLength; i++)
+ {
+ byteResult = await stream.ReadByteAsync(cancellationToken).ConfigureAwait(false);
+
+ if (byteResult == -1)
+ {
+ throw new TorConnectionException("Failed to read DST.ADDR from the SOCKS5 response.");
}
- return builder.ToArray();
+ builder.Append((byte)byteResult);
}
- else
+
+ // Read DST.PORT.
+ for (int i = 0; i < 2; i++)
{
- throw new ArgumentException("Not supported request type.", nameof(request));
+ byteResult = await stream.ReadByteAsync(cancellationToken).ConfigureAwait(false);
+
+ if (byteResult == -1)
+ {
+ throw new TorConnectionException("Failed to read DST.PORT from the SOCKS5 response.");
}
+
+ builder.Append((byte)byteResult);
}
+
+ return builder.ToArray();
}
- catch (OperationCanceledException)
+
+ private static async Task<byte[]> ReadTwoByteResponseAsync(NetworkStream stream, CancellationToken cancellationToken)
{
- Logger.LogTrace("Send operation was canceled.");
- throw;
- }
- catch (IOException ex)
+ // Read exactly "receiveBufferSize" bytes.
+ int receiveBufferSize = 2;
+ byte[] receiveBuffer = new byte[receiveBufferSize];
+
+ int unreadBytes = await stream.ReadBlockAsync(receiveBuffer, receiveBufferSize, cancellationToken).ConfigureAwait(false);
+
+ if (unreadBytes == receiveBufferSize)
{
- throw new TorConnectionException($"{nameof(TorSocks5Client)} is not connected to {RemoteEndPoint}.", ex);
+ return receiveBuffer;
}
+
+ throw new TorConnectionException($"Failed to read {receiveBufferSize} bytes as expected from Tor SOCKS5.");
}
#endregion Methods

TorSocks5Client: Re-implement SendRequestAsync for TorSocks5Request to avoid using NetworkStream.DataAvailable.