| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- using System;
- using System.Buffers.Binary;
- using System.Diagnostics;
- using System.Net;
- using System.Net.Sockets;
- using System.Text;
- using Renci.SshNet.Abstractions;
- using Renci.SshNet.Common;
- namespace Renci.SshNet.Connection
- {
- /// <summary>
- /// Establishes a tunnel via a SOCKS5 proxy server.
- /// </summary>
- /// <remarks>
- /// https://en.wikipedia.org/wiki/SOCKS#SOCKS5.
- /// </remarks>
- internal sealed class Socks5Connector : ProxyConnector
- {
- public Socks5Connector(ISocketFactory socketFactory)
- : base(socketFactory)
- {
- }
- /// <summary>
- /// Establishes a connection to the server via a SOCKS5 proxy.
- /// </summary>
- /// <param name="connectionInfo">The connection information.</param>
- /// <param name="socket">The <see cref="Socket"/>.</param>
- protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket)
- {
- var greeting = new byte[]
- {
- // SOCKS version number
- 0x05,
- // Number of supported authentication methods
- 0x02,
- // No authentication
- 0x00,
- // Username/Password authentication
- 0x02
- };
- SocketAbstraction.Send(socket, greeting);
- var socksVersion = SocketReadByte(socket, connectionInfo.Timeout);
- if (socksVersion != 0x05)
- {
- throw new ProxyException(string.Format("SOCKS Version '{0}' is not supported.", socksVersion));
- }
- var authenticationMethod = SocketReadByte(socket, connectionInfo.Timeout);
- switch (authenticationMethod)
- {
- case 0x00:
- // No authentication
- break;
- case 0x02:
- // Create username/password authentication request
- var authenticationRequest = CreateSocks5UserNameAndPasswordAuthenticationRequest(connectionInfo.ProxyUsername, connectionInfo.ProxyPassword);
- // Send authentication request
- SocketAbstraction.Send(socket, authenticationRequest);
- // Read authentication result
- var authenticationResult = SocketAbstraction.Read(socket, 2, connectionInfo.Timeout);
- if (authenticationResult[0] != 0x01)
- {
- throw new ProxyException("SOCKS5: Server authentication version is not valid.");
- }
- if (authenticationResult[1] != 0x00)
- {
- throw new ProxyException("SOCKS5: Username/Password authentication failed.");
- }
- break;
- case 0xFF:
- throw new ProxyException("SOCKS5: No acceptable authentication methods were offered.");
- default:
- throw new ProxyException($"SOCKS5: Chosen authentication method '0x{authenticationMethod:x2}' is not supported.");
- }
- var connectionRequest = CreateSocks5ConnectionRequest(connectionInfo.Host, (ushort)connectionInfo.Port);
- SocketAbstraction.Send(socket, connectionRequest);
- // Read Server SOCKS5 version
- if (SocketReadByte(socket, connectionInfo.Timeout) != 5)
- {
- throw new ProxyException("SOCKS5: Version 5 is expected.");
- }
- // Read response code
- var status = SocketReadByte(socket, connectionInfo.Timeout);
- switch (status)
- {
- case 0x00:
- break;
- case 0x01:
- throw new ProxyException("SOCKS5: General failure.");
- case 0x02:
- throw new ProxyException("SOCKS5: Connection not allowed by ruleset.");
- case 0x03:
- throw new ProxyException("SOCKS5: Network unreachable.");
- case 0x04:
- throw new ProxyException("SOCKS5: Host unreachable.");
- case 0x05:
- throw new ProxyException("SOCKS5: Connection refused by destination host.");
- case 0x06:
- throw new ProxyException("SOCKS5: TTL expired.");
- case 0x07:
- throw new ProxyException("SOCKS5: Command not supported or protocol error.");
- case 0x08:
- throw new ProxyException("SOCKS5: Address type not supported.");
- default:
- throw new ProxyException("SOCKS5: Not valid response.");
- }
- // Read reserved byte
- if (SocketReadByte(socket, connectionInfo.Timeout) != 0)
- {
- throw new ProxyException("SOCKS5: 0 byte is expected.");
- }
- var addressType = SocketReadByte(socket, connectionInfo.Timeout);
- switch (addressType)
- {
- case 0x01:
- var ipv4 = new byte[4];
- _ = SocketRead(socket, ipv4, 0, 4, connectionInfo.Timeout);
- break;
- case 0x04:
- var ipv6 = new byte[16];
- _ = SocketRead(socket, ipv6, 0, 16, connectionInfo.Timeout);
- break;
- default:
- throw new ProxyException(string.Format("Address type '{0}' is not supported.", addressType));
- }
- var port = new byte[2];
- // Read 2 bytes to be ignored
- _ = SocketRead(socket, port, 0, 2, connectionInfo.Timeout);
- }
- /// <summary>
- /// https://tools.ietf.org/html/rfc1929.
- /// </summary>
- private static byte[] CreateSocks5UserNameAndPasswordAuthenticationRequest(string username, string password)
- {
- if (username.Length > byte.MaxValue)
- {
- throw new ProxyException("Proxy username is too long.");
- }
- if (password.Length > byte.MaxValue)
- {
- throw new ProxyException("Proxy password is too long.");
- }
- var authenticationRequest = new byte[// Version of the negotiation
- 1 +
- // Length of the username
- 1 +
- // Username
- username.Length +
- // Length of the password
- 1 +
- // Password
- password.Length];
- var index = 0;
- // Version of the negotiation
- authenticationRequest[index++] = 0x01;
- // Length of the username
- authenticationRequest[index++] = (byte)username.Length;
- // Username
- _ = SshData.Ascii.GetBytes(username, 0, username.Length, authenticationRequest, index);
- index += username.Length;
- // Length of the password
- authenticationRequest[index++] = (byte)password.Length;
- // Password
- _ = SshData.Ascii.GetBytes(password, 0, password.Length, authenticationRequest, index);
- return authenticationRequest;
- }
- private static byte[] CreateSocks5ConnectionRequest(string hostname, ushort port)
- {
- var addressBytes = GetSocks5DestinationAddress(hostname, out var addressType);
- var connectionRequest = new byte[// SOCKS version number
- 1 +
- // Command code
- 1 +
- // Reserved
- 1 +
- // Address type
- 1 +
- // Address
- addressBytes.Length +
- // Port number
- 2];
- var index = 0;
- // SOCKS version number
- connectionRequest[index++] = 0x05;
- // Command code
- connectionRequest[index++] = 0x01; // establish a TCP/IP stream connection
- // Reserved
- connectionRequest[index++] = 0x00;
- // Address type
- connectionRequest[index++] = addressType;
- // Address
- Buffer.BlockCopy(addressBytes, 0, connectionRequest, index, addressBytes.Length);
- index += addressBytes.Length;
- // Port number
- BinaryPrimitives.WriteUInt16BigEndian(connectionRequest.AsSpan(index), port);
- return connectionRequest;
- }
- private static byte[] GetSocks5DestinationAddress(string hostname, out byte addressType)
- {
- if (IPAddress.TryParse(hostname, out var ipAddress))
- {
- Debug.Assert(ipAddress.AddressFamily is AddressFamily.InterNetwork or AddressFamily.InterNetworkV6);
- addressType = ipAddress.AddressFamily == AddressFamily.InterNetwork
- ? (byte)0x01 // IPv4
- : (byte)0x04; // IPv6
- return ipAddress.GetAddressBytes();
- }
- addressType = 0x03; // Domain name
- var byteCount = Encoding.UTF8.GetByteCount(hostname);
- if (byteCount > byte.MaxValue)
- {
- throw new ProxyException(string.Format("SOCKS5: SOCKS 5 cannot support host names longer than 255 chars ('{0}').", hostname));
- }
- var address = new byte[1 + byteCount];
- address[0] = (byte)byteCount;
- _ = Encoding.UTF8.GetBytes(hostname, 0, hostname.Length, address, 1);
- return address;
- }
- }
- }
|