/// <summary> /// When Tor receives a "RESOLVE" SOCKS command, it initiates /// a remote lookup of the hostname provided as the target address in the SOCKS /// request. /// </summary> internal async Task <IPAddress> ResolveAsync(string host) { // https://gitweb.torproject.org/torspec.git/tree/socks-extensions.txt#n44 host = Guard.NotNullOrEmptyOrWhitespace(nameof(host), host, true); if (TorSocks5EndPoint is null) { var hostAddresses = await Dns.GetHostAddressesAsync(host); return(hostAddresses.First()); } var cmd = CmdField.Resolve; var dstAddr = new AddrField(host); var dstPort = new PortField(0); var resolveRequest = new TorSocks5Request(cmd, dstAddr, dstPort); var sendBuffer = resolveRequest.ToBytes(); var receiveBuffer = await SendAsync(sendBuffer); var resolveResponse = new TorSocks5Response(); resolveResponse.FromBytes(receiveBuffer); if (resolveResponse.Rep != RepField.Succeeded) { throw new TorSocks5FailureResponseException(resolveResponse.Rep); } return(IPAddress.Parse(resolveResponse.BndAddr.DomainOrIPv4)); }
/// <param name="host">IPv4 or domain</param> public async Task ConnectToDestinationAsync(string host, int port, CancellationToken cancellationToken = default) { Logger.LogDebug($"> {nameof(host)}={host}, {nameof(port)}={port}"); host = Guard.NotNullOrEmptyOrWhitespace(nameof(host), host, true); Guard.MinimumAndNotNull(nameof(port), port, 0); try { var connectionRequest = new TorSocks5Request(cmd: CmdField.Connect, new AddrField(host), new PortField(port)); var sendBuffer = connectionRequest.ToBytes(); var receiveBuffer = await SendAsync(sendBuffer, receiveBufferSize : null, cancellationToken).ConfigureAwait(false); var connectionResponse = new TorSocks5Response(); connectionResponse.FromBytes(receiveBuffer); if (connectionResponse.Rep != RepField.Succeeded) { // https://www.ietf.org/rfc/rfc1928.txt // When a reply(REP value other than X'00') indicates a failure, the // SOCKS server MUST terminate the TCP connection shortly after sending // the reply.This must be no more than 10 seconds after detecting the // condition that caused a failure. DisposeTcpClient(); Logger.LogWarning($"Connection response indicates a failure. Actual response is: '{connectionResponse.Rep}'. Request: {host}:{port}."); throw new TorSocks5FailureResponseException(connectionResponse.Rep); } // Do not check the Bnd. Address and Bnd. Port. because Tor does not seem to return any, ever. It returns zeros instead. // Generally also do not check anything but the success response, according to Socks5 RFC // If the reply code(REP value of X'00') indicates a success, and the // request was either a BIND or a CONNECT, the client may now start // passing data. If the selected authentication method supports // encapsulation for the purposes of integrity, authentication and / or // confidentiality, the data are encapsulated using the method-dependent // encapsulation.Similarly, when data arrives at the SOCKS server for // 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 ({host}:{port})", e); throw; } finally { Logger.LogDebug("<"); } }
/// <summary> /// Tor attempts to find the canonical hostname for that IPv4 record /// </summary> internal async Task <string> ReverseResolveAsync(IPAddress iPv4) { // https://gitweb.torproject.org/torspec.git/tree/socks-extensions.txt#n55 Guard.NotNull(nameof(iPv4), iPv4); if (TorSocks5EndPoint is null) // Only Tor is iPv4 dependent { var host = await Dns.GetHostEntryAsync(iPv4); return(host.HostName); } Guard.Same($"{nameof(iPv4)}.{nameof(iPv4.AddressFamily)}", AddressFamily.InterNetwork, iPv4.AddressFamily); var cmd = CmdField.ResolvePtr; var dstAddr = new AddrField(iPv4.ToString()); var dstPort = new PortField(0); var resolveRequest = new TorSocks5Request(cmd, dstAddr, dstPort); var sendBuffer = resolveRequest.ToBytes(); var receiveBuffer = await SendAsync(sendBuffer); var resolveResponse = new TorSocks5Response(); resolveResponse.FromBytes(receiveBuffer); if (resolveResponse.Rep != RepField.Succeeded) { throw new TorSocks5FailureResponseException(resolveResponse.Rep); } return(resolveResponse.BndAddr.DomainOrIPv4); }
/// <param name="host">IPv4 or domain</param> internal async Task ConnectToDestinationAsync(string host, int port, bool isRecursiveCall = false) { host = Guard.NotNullOrEmptyOrWhitespace(nameof(host), host, true); Guard.MinimumAndNotNull(nameof(port), port, 0); if (TorSocks5EndPoint is null) { using (await AsyncLock.LockAsync().ConfigureAwait(false)) { TcpClient?.Dispose(); TcpClient = IPAddress.TryParse(host, out IPAddress ip) ? new TcpClient(ip.AddressFamily) : new TcpClient(); await TcpClient.ConnectAsync(host, port).ConfigureAwait(false); Stream = TcpClient.GetStream(); RemoteEndPoint = TcpClient.Client.RemoteEndPoint; } return; } var cmd = CmdField.Connect; var dstAddr = new AddrField(host); DestinationHost = dstAddr.DomainOrIPv4; var dstPort = new PortField(port); DestinationPort = dstPort.DstPort; var connectionRequest = new TorSocks5Request(cmd, dstAddr, dstPort); var sendBuffer = connectionRequest.ToBytes(); var receiveBuffer = await SendAsync(sendBuffer, isRecursiveCall : isRecursiveCall).ConfigureAwait(false); var connectionResponse = new TorSocks5Response(); connectionResponse.FromBytes(receiveBuffer); if (connectionResponse.Rep != RepField.Succeeded) { // https://www.ietf.org/rfc/rfc1928.txt // When a reply(REP value other than X'00') indicates a failure, the // SOCKS server MUST terminate the TCP connection shortly after sending // the reply.This must be no more than 10 seconds after detecting the // condition that caused a failure. DisposeTcpClient(); throw new TorSocks5FailureResponseException(connectionResponse.Rep); } // Do not check the Bnd. Address and Bnd. Port. because Tor does not seem to return any, ever. It returns zeros instead. // Generally also do not check anything but the success response, according to Socks5 RFC // If the reply code(REP value of X'00') indicates a success, and the // request was either a BIND or a CONNECT, the client may now start // passing data. If the selected authentication method supports // encapsulation for the purposes of integrity, authentication and / or // confidentiality, the data are encapsulated using the method-dependent // encapsulation.Similarly, when data arrives at the SOCKS server for // the client, the server MUST encapsulate the data as appropriate for // the authentication method in use. }