Socks5Connector
Establishes a tunnel via a SOCKS5 proxy server.
            
                using Microsoft.Extensions.Logging;
using Renci.SshNet.Abstractions;
using Renci.SshNet.Common;
using System;
using System.Buffers.Binary;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
namespace Renci.SshNet.Connection
{
    internal sealed class Socks5Connector : ProxyConnector
    {
        public Socks5Connector(ISocketFactory socketFactory, ILoggerFactory loggerFactory)
            : base(socketFactory, loggerFactory)
        {
        }
        protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket)
        {
            byte[] data = new byte[4] {
                5,
                2,
                0,
                2
            };
            SocketAbstraction.Send(socket, data);
            byte b = ConnectorBase.SocketReadByte(socket, connectionInfo.Timeout);
            if (b != 5)
                throw new ProxyException($"""{b}""");
            byte b2 = ConnectorBase.SocketReadByte(socket, connectionInfo.Timeout);
            switch (b2) {
            case 2: {
                byte[] data2 = CreateSocks5UserNameAndPasswordAuthenticationRequest(connectionInfo.ProxyUsername, connectionInfo.ProxyPassword);
                SocketAbstraction.Send(socket, data2);
                byte[] array = SocketAbstraction.Read(socket, 2, connectionInfo.Timeout);
                if (array[0] != 1)
                    throw new ProxyException("SOCKS5: Server authentication version is not valid.");
                if (array[1] != 0)
                    throw new ProxyException("SOCKS5: Username/Password authentication failed.");
                break;
            }
            case byte.MaxValue:
                throw new ProxyException("SOCKS5: No acceptable authentication methods were offered.");
            default: {
                DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(59, 1);
                defaultInterpolatedStringHandler.AppendLiteral("SOCKS5: Chosen authentication method '0x");
                defaultInterpolatedStringHandler.AppendFormatted(b2, "x2");
                defaultInterpolatedStringHandler.AppendLiteral("' is not supported.");
                throw new ProxyException(defaultInterpolatedStringHandler.ToStringAndClear());
            }
            case 0:
                break;
            }
            byte[] data3 = CreateSocks5ConnectionRequest(connectionInfo.Host, (ushort)connectionInfo.Port);
            SocketAbstraction.Send(socket, data3);
            if (ConnectorBase.SocketReadByte(socket, connectionInfo.Timeout) != 5)
                throw new ProxyException("SOCKS5: Version 5 is expected.");
            switch (ConnectorBase.SocketReadByte(socket, connectionInfo.Timeout)) {
            case 1:
                throw new ProxyException("SOCKS5: General failure.");
            case 2:
                throw new ProxyException("SOCKS5: Connection not allowed by ruleset.");
            case 3:
                throw new ProxyException("SOCKS5: Network unreachable.");
            case 4:
                throw new ProxyException("SOCKS5: Host unreachable.");
            case 5:
                throw new ProxyException("SOCKS5: Connection refused by destination host.");
            case 6:
                throw new ProxyException("SOCKS5: TTL expired.");
            case 7:
                throw new ProxyException("SOCKS5: Command not supported or protocol error.");
            case 8:
                throw new ProxyException("SOCKS5: Address type not supported.");
            default:
                throw new ProxyException("SOCKS5: Not valid response.");
            case 0: {
                if (ConnectorBase.SocketReadByte(socket, connectionInfo.Timeout) != 0)
                    throw new ProxyException("SOCKS5: 0 byte is expected.");
                byte b3 = ConnectorBase.SocketReadByte(socket, connectionInfo.Timeout);
                switch (b3) {
                case 1: {
                    byte[] buffer2 = new byte[4];
                    ConnectorBase.SocketRead(socket, buffer2, 0, 4, connectionInfo.Timeout);
                    break;
                }
                case 4: {
                    byte[] buffer = new byte[16];
                    ConnectorBase.SocketRead(socket, buffer, 0, 16, connectionInfo.Timeout);
                    break;
                }
                default:
                    throw new ProxyException($"""{b3}""");
                }
                byte[] buffer3 = new byte[2];
                ConnectorBase.SocketRead(socket, buffer3, 0, 2, connectionInfo.Timeout);
                break;
            }
            }
        }
        private static byte[] CreateSocks5UserNameAndPasswordAuthenticationRequest(string username, string password)
        {
            if (username.Length > 255)
                throw new ProxyException("Proxy username is too long.");
            if (password.Length > 255)
                throw new ProxyException("Proxy password is too long.");
            byte[] array = new byte[2 + username.Length + 1 + password.Length];
            int num = 0;
            array[num++] = 1;
            array[num++] = (byte)username.Length;
            SshData.Ascii.GetBytes(username, 0, username.Length, array, num);
            num += username.Length;
            array[num++] = (byte)password.Length;
            SshData.Ascii.GetBytes(password, 0, password.Length, array, num);
            return array;
        }
        private static byte[] CreateSocks5ConnectionRequest(string hostname, ushort port)
        {
            byte addressType;
            byte[] socks5DestinationAddress = GetSocks5DestinationAddress(hostname, out addressType);
            byte[] array = new byte[4 + socks5DestinationAddress.Length + 2];
            int num = 0;
            array[num++] = 5;
            array[num++] = 1;
            array[num++] = 0;
            array[num++] = addressType;
            Buffer.BlockCopy(socks5DestinationAddress, 0, array, num, socks5DestinationAddress.Length);
            num += socks5DestinationAddress.Length;
            BinaryPrimitives.WriteUInt16BigEndian(array.AsSpan(num), port);
            return array;
        }
        private static byte[] GetSocks5DestinationAddress(string hostname, out byte addressType)
        {
            if (IPAddress.TryParse(hostname, out IPAddress address)) {
                addressType = (byte)((address.AddressFamily == AddressFamily.InterNetwork) ? 1 : 4);
                return address.GetAddressBytes();
            }
            addressType = 3;
            int byteCount = Encoding.UTF8.GetByteCount(hostname);
            if (byteCount > 255)
                throw new ProxyException($"""{hostname}""");
            byte[] array = new byte[1 + byteCount];
            array[0] = (byte)byteCount;
            Encoding.UTF8.GetBytes(hostname, 0, hostname.Length, array, 1);
            return array;
        }
    }
}