< Summary

Information
Class: Renci.SshNet.Connection.Socks5Connector
Assembly: Renci.SshNet
File(s): \home\appveyor\projects\ssh-net\src\Renci.SshNet\Connection\Socks5Connector.cs
Line coverage
84%
Covered lines: 119
Uncovered lines: 22
Coverable lines: 141
Total lines: 269
Line coverage: 84.3%
Branch coverage
50%
Covered branches: 19
Total branches: 38
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
.ctor(...)100%1100%
HandleProxyConnect(...)46.66%3073.13%
CreateSocks5UserNameAndPasswordAuthenticationRequest(...)100%4100%
CreateSocks5ConnectionRequest(...)100%1100%
GetSocks5DestinationAddress(...)25%466.66%

File(s)

\home\appveyor\projects\ssh-net\src\Renci.SshNet\Connection\Socks5Connector.cs

#LineLine coverage
 1using System;
 2using System.Net.Sockets;
 3
 4using Renci.SshNet.Abstractions;
 5using Renci.SshNet.Common;
 6
 7namespace Renci.SshNet.Connection
 8{
 9    /// <summary>
 10    /// Establishes a tunnel via a SOCKS5 proxy server.
 11    /// </summary>
 12    /// <remarks>
 13    /// https://en.wikipedia.org/wiki/SOCKS#SOCKS5.
 14    /// </remarks>
 15    internal sealed class Socks5Connector : ProxyConnector
 16    {
 17        public Socks5Connector(ISocketFactory socketFactory)
 11118            : base(socketFactory)
 11119        {
 11120        }
 21
 22        /// <summary>
 23        /// Establishes a connection to the server via a SOCKS5 proxy.
 24        /// </summary>
 25        /// <param name="connectionInfo">The connection information.</param>
 26        /// <param name="socket">The <see cref="Socket"/>.</param>
 27        protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket)
 8728        {
 8729            var greeting = new byte[]
 8730                {
 8731                    // SOCKS version number
 8732                    0x05,
 8733
 8734                    // Number of supported authentication methods
 8735                    0x02,
 8736
 8737                    // No authentication
 8738                    0x00,
 8739
 8740                    // Username/Password authentication
 8741                    0x02
 8742                };
 8743            SocketAbstraction.Send(socket, greeting);
 44
 8745            var socksVersion = SocketReadByte(socket);
 8746            if (socksVersion != 0x05)
 1247            {
 1248                throw new ProxyException(string.Format("SOCKS Version '{0}' is not supported.", socksVersion));
 49            }
 50
 7551            var authenticationMethod = SocketReadByte(socket);
 7552            switch (authenticationMethod)
 53            {
 54                case 0x00:
 55                    // No authentication
 1556                    break;
 57                case 0x02:
 58                    // Create username/password authentication request
 6059                    var authenticationRequest = CreateSocks5UserNameAndPasswordAuthenticationRequest(connectionInfo.Prox
 60
 61                    // Send authentication request
 3062                    SocketAbstraction.Send(socket, authenticationRequest);
 63
 64                    // Read authentication result
 3065                    var authenticationResult = SocketAbstraction.Read(socket, 2, connectionInfo.Timeout);
 66
 3067                    if (authenticationResult[0] != 0x01)
 068                    {
 069                        throw new ProxyException("SOCKS5: Server authentication version is not valid.");
 70                    }
 71
 3072                    if (authenticationResult[1] != 0x00)
 1573                    {
 1574                        throw new ProxyException("SOCKS5: Username/Password authentication failed.");
 75                    }
 76
 1577                    break;
 78                case 0xFF:
 079                    throw new ProxyException("SOCKS5: No acceptable authentication methods were offered.");
 80                default:
 081                    throw new ProxyException($"SOCKS5: Chosen authentication method '0x{authenticationMethod:x2}' is not
 82            }
 83
 3084            var connectionRequest = CreateSocks5ConnectionRequest(connectionInfo.Host, (ushort) connectionInfo.Port);
 3085            SocketAbstraction.Send(socket, connectionRequest);
 86
 87            // Read Server SOCKS5 version
 3088            if (SocketReadByte(socket) != 5)
 089            {
 090                throw new ProxyException("SOCKS5: Version 5 is expected.");
 91            }
 92
 93            // Read response code
 3094            var status = SocketReadByte(socket);
 95
 3096            switch (status)
 97            {
 98                case 0x00:
 3099                    break;
 100                case 0x01:
 0101                    throw new ProxyException("SOCKS5: General failure.");
 102                case 0x02:
 0103                    throw new ProxyException("SOCKS5: Connection not allowed by ruleset.");
 104                case 0x03:
 0105                    throw new ProxyException("SOCKS5: Network unreachable.");
 106                case 0x04:
 0107                    throw new ProxyException("SOCKS5: Host unreachable.");
 108                case 0x05:
 0109                    throw new ProxyException("SOCKS5: Connection refused by destination host.");
 110                case 0x06:
 0111                    throw new ProxyException("SOCKS5: TTL expired.");
 112                case 0x07:
 0113                    throw new ProxyException("SOCKS5: Command not supported or protocol error.");
 114                case 0x08:
 0115                    throw new ProxyException("SOCKS5: Address type not supported.");
 116                default:
 0117                    throw new ProxyException("SOCKS5: Not valid response.");
 118            }
 119
 120            // Read reserved byte
 30121            if (SocketReadByte(socket) != 0)
 0122            {
 0123                throw new ProxyException("SOCKS5: 0 byte is expected.");
 124            }
 125
 30126            var addressType = SocketReadByte(socket);
 30127            switch (addressType)
 128            {
 129                case 0x01:
 15130                    var ipv4 = new byte[4];
 15131                    _ = SocketRead(socket, ipv4, 0, 4);
 15132                    break;
 133                case 0x04:
 15134                    var ipv6 = new byte[16];
 15135                    _ =SocketRead(socket, ipv6, 0, 16);
 15136                    break;
 137                default:
 0138                    throw new ProxyException(string.Format("Address type '{0}' is not supported.", addressType));
 139            }
 140
 30141            var port = new byte[2];
 142
 143            // Read 2 bytes to be ignored
 30144            _ = SocketRead(socket, port, 0, 2);
 30145        }
 146
 147        /// <summary>
 148        /// https://tools.ietf.org/html/rfc1929.
 149        /// </summary>
 150        private static byte[] CreateSocks5UserNameAndPasswordAuthenticationRequest(string username, string password)
 60151        {
 60152            if (username.Length > byte.MaxValue)
 15153            {
 15154                throw new ProxyException("Proxy username is too long.");
 155            }
 156
 45157            if (password.Length > byte.MaxValue)
 15158            {
 15159                throw new ProxyException("Proxy password is too long.");
 160            }
 161
 30162            var authenticationRequest = new byte[// Version of the negotiation
 30163                                                 1 +
 30164
 30165                                                 // Length of the username
 30166                                                 1 +
 30167
 30168                                                 // Username
 30169                                                 username.Length +
 30170
 30171                                                 // Length of the password
 30172                                                 1 +
 30173
 30174                                                 // Password
 30175                                                 password.Length];
 176
 30177            var index = 0;
 178
 179            // Version of the negiotiation
 30180            authenticationRequest[index++] = 0x01;
 181
 182            // Length of the username
 30183            authenticationRequest[index++] = (byte) username.Length;
 184
 185            // Username
 30186            _ = SshData.Ascii.GetBytes(username, 0, username.Length, authenticationRequest, index);
 30187            index += username.Length;
 188
 189            // Length of the password
 30190            authenticationRequest[index++] = (byte) password.Length;
 191
 192            // Password
 30193            _ =SshData.Ascii.GetBytes(password, 0, password.Length, authenticationRequest, index);
 194
 30195            return authenticationRequest;
 30196        }
 197
 198        private static byte[] CreateSocks5ConnectionRequest(string hostname, ushort port)
 30199        {
 30200            var addressBytes = GetSocks5DestinationAddress(hostname, out var addressType);
 201
 30202            var connectionRequest = new byte[// SOCKS version number
 30203                                             1 +
 30204
 30205                                             // Command code
 30206                                             1 +
 30207
 30208                                             // Reserved
 30209                                             1 +
 30210
 30211                                             // Address type
 30212                                             1 +
 30213
 30214                                             // Address
 30215                                             addressBytes.Length +
 30216
 30217                                             // Port number
 30218                                             2];
 219
 30220            var index = 0;
 221
 222            // SOCKS version number
 30223            connectionRequest[index++] = 0x05;
 224
 225            // Command code
 30226            connectionRequest[index++] = 0x01; // establish a TCP/IP stream connection
 227
 228            // Reserved
 30229            connectionRequest[index++] = 0x00;
 230
 231            // Address type
 30232            connectionRequest[index++] = addressType;
 233
 234            // Address
 30235            Buffer.BlockCopy(addressBytes, 0, connectionRequest, index, addressBytes.Length);
 30236            index += addressBytes.Length;
 237
 238            // Port number
 30239            Pack.UInt16ToBigEndian(port, connectionRequest, index);
 240
 30241            return connectionRequest;
 30242        }
 243
 244        private static byte[] GetSocks5DestinationAddress(string hostname, out byte addressType)
 30245        {
 30246            var ip = DnsAbstraction.GetHostAddresses(hostname)[0];
 247
 248            byte[] address;
 249
 250#pragma warning disable IDE0010 // Add missing cases
 30251            switch (ip.AddressFamily)
 252            {
 253                case AddressFamily.InterNetwork:
 30254                    addressType = 0x01; // IPv4
 30255                    address = ip.GetAddressBytes();
 30256                    break;
 257                case AddressFamily.InterNetworkV6:
 0258                    addressType = 0x04; // IPv6
 0259                    address = ip.GetAddressBytes();
 0260                    break;
 261                default:
 0262                    throw new ProxyException(string.Format("SOCKS5: IP address '{0}' is not supported.", ip));
 263            }
 264#pragma warning restore IDE0010 // Add missing cases
 265
 30266            return address;
 30267        }
 268    }
 269}