Socks5Connector.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. using Renci.SshNet.Abstractions;
  2. using Renci.SshNet.Common;
  3. using System;
  4. using System.Net.Sockets;
  5. namespace Renci.SshNet.Connection
  6. {
  7. /// <summary>
  8. /// Establishes a tunnel via a SOCKS5 proxy server.
  9. /// </summary>
  10. /// <remarks>
  11. /// https://en.wikipedia.org/wiki/SOCKS#SOCKS5
  12. /// </remarks>
  13. internal sealed class Socks5Connector : ProxyConnector
  14. {
  15. public Socks5Connector(ISocketFactory socketFactory) : base(socketFactory)
  16. {
  17. }
  18. /// <summary>
  19. /// Establishes a connection to the server via a SOCKS5 proxy.
  20. /// </summary>
  21. /// <param name="connectionInfo">The connection information.</param>
  22. /// <param name="socket">The <see cref="Socket"/>.</param>
  23. protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket)
  24. {
  25. var greeting = new byte[]
  26. {
  27. // SOCKS version number
  28. 0x05,
  29. // Number of supported authentication methods
  30. 0x02,
  31. // No authentication
  32. 0x00,
  33. // Username/Password authentication
  34. 0x02
  35. };
  36. SocketAbstraction.Send(socket, greeting);
  37. var socksVersion = SocketReadByte(socket);
  38. if (socksVersion != 0x05)
  39. {
  40. throw new ProxyException(string.Format("SOCKS Version '{0}' is not supported.", socksVersion));
  41. }
  42. var authenticationMethod = SocketReadByte(socket);
  43. switch (authenticationMethod)
  44. {
  45. case 0x00:
  46. break;
  47. case 0x02:
  48. // Create username/password authentication request
  49. var authenticationRequest = CreateSocks5UserNameAndPasswordAuthenticationRequest(connectionInfo.ProxyUsername, connectionInfo.ProxyPassword);
  50. // Send authentication request
  51. SocketAbstraction.Send(socket, authenticationRequest);
  52. // Read authentication result
  53. var authenticationResult = SocketAbstraction.Read(socket, 2, connectionInfo.Timeout);
  54. if (authenticationResult[0] != 0x01)
  55. throw new ProxyException("SOCKS5: Server authentication version is not valid.");
  56. if (authenticationResult[1] != 0x00)
  57. throw new ProxyException("SOCKS5: Username/Password authentication failed.");
  58. break;
  59. case 0xFF:
  60. throw new ProxyException("SOCKS5: No acceptable authentication methods were offered.");
  61. }
  62. var connectionRequest = CreateSocks5ConnectionRequest(connectionInfo.Host, (ushort) connectionInfo.Port);
  63. SocketAbstraction.Send(socket, connectionRequest);
  64. // Read Server SOCKS5 version
  65. if (SocketReadByte(socket) != 5)
  66. {
  67. throw new ProxyException("SOCKS5: Version 5 is expected.");
  68. }
  69. // Read response code
  70. var status = SocketReadByte(socket);
  71. switch (status)
  72. {
  73. case 0x00:
  74. break;
  75. case 0x01:
  76. throw new ProxyException("SOCKS5: General failure.");
  77. case 0x02:
  78. throw new ProxyException("SOCKS5: Connection not allowed by ruleset.");
  79. case 0x03:
  80. throw new ProxyException("SOCKS5: Network unreachable.");
  81. case 0x04:
  82. throw new ProxyException("SOCKS5: Host unreachable.");
  83. case 0x05:
  84. throw new ProxyException("SOCKS5: Connection refused by destination host.");
  85. case 0x06:
  86. throw new ProxyException("SOCKS5: TTL expired.");
  87. case 0x07:
  88. throw new ProxyException("SOCKS5: Command not supported or protocol error.");
  89. case 0x08:
  90. throw new ProxyException("SOCKS5: Address type not supported.");
  91. default:
  92. throw new ProxyException("SOCKS5: Not valid response.");
  93. }
  94. // Read reserved byte
  95. if (SocketReadByte(socket) != 0)
  96. {
  97. throw new ProxyException("SOCKS5: 0 byte is expected.");
  98. }
  99. var addressType = SocketReadByte(socket);
  100. switch (addressType)
  101. {
  102. case 0x01:
  103. var ipv4 = new byte[4];
  104. SocketRead(socket, ipv4, 0, 4);
  105. break;
  106. case 0x04:
  107. var ipv6 = new byte[16];
  108. SocketRead(socket, ipv6, 0, 16);
  109. break;
  110. default:
  111. throw new ProxyException(string.Format("Address type '{0}' is not supported.", addressType));
  112. }
  113. var port = new byte[2];
  114. // Read 2 bytes to be ignored
  115. SocketRead(socket, port, 0, 2);
  116. }
  117. /// <summary>
  118. /// https://tools.ietf.org/html/rfc1929
  119. /// </summary>
  120. private static byte[] CreateSocks5UserNameAndPasswordAuthenticationRequest(string username, string password)
  121. {
  122. if (username.Length > byte.MaxValue)
  123. throw new ProxyException("Proxy username is too long.");
  124. if (password.Length > byte.MaxValue)
  125. throw new ProxyException("Proxy password is too long.");
  126. var authenticationRequest = new byte
  127. [
  128. // Version of the negotiation
  129. 1 +
  130. // Length of the username
  131. 1 +
  132. // Username
  133. username.Length +
  134. // Length of the password
  135. 1 +
  136. // Password
  137. password.Length
  138. ];
  139. var index = 0;
  140. // Version of the negiotiation
  141. authenticationRequest[index++] = 0x01;
  142. // Length of the username
  143. authenticationRequest[index++] = (byte) username.Length;
  144. // Username
  145. SshData.Ascii.GetBytes(username, 0, username.Length, authenticationRequest, index);
  146. index += username.Length;
  147. // Length of the password
  148. authenticationRequest[index++] = (byte) password.Length;
  149. // Password
  150. SshData.Ascii.GetBytes(password, 0, password.Length, authenticationRequest, index);
  151. return authenticationRequest;
  152. }
  153. private static byte[] CreateSocks5ConnectionRequest(string hostname, ushort port)
  154. {
  155. byte addressType;
  156. var addressBytes = GetSocks5DestinationAddress(hostname, out addressType);
  157. var connectionRequest = new byte
  158. [
  159. // SOCKS version number
  160. 1 +
  161. // Command code
  162. 1 +
  163. // Reserved
  164. 1 +
  165. // Address type
  166. 1 +
  167. // Address
  168. addressBytes.Length +
  169. // Port number
  170. 2
  171. ];
  172. var index = 0;
  173. // SOCKS version number
  174. connectionRequest[index++] = 0x05;
  175. // Command code
  176. connectionRequest[index++] = 0x01; // establish a TCP/IP stream connection
  177. // Reserved
  178. connectionRequest[index++] = 0x00;
  179. // Address type
  180. connectionRequest[index++] = addressType;
  181. // Address
  182. Buffer.BlockCopy(addressBytes, 0, connectionRequest, index, addressBytes.Length);
  183. index += addressBytes.Length;
  184. // Port number
  185. Pack.UInt16ToBigEndian(port, connectionRequest, index);
  186. return connectionRequest;
  187. }
  188. private static byte[] GetSocks5DestinationAddress(string hostname, out byte addressType)
  189. {
  190. var ip = DnsAbstraction.GetHostAddresses(hostname)[0];
  191. byte[] address;
  192. switch (ip.AddressFamily)
  193. {
  194. case AddressFamily.InterNetwork:
  195. addressType = 0x01; // IPv4
  196. address = ip.GetAddressBytes();
  197. break;
  198. case AddressFamily.InterNetworkV6:
  199. addressType = 0x04; // IPv6
  200. address = ip.GetAddressBytes();
  201. break;
  202. default:
  203. throw new ProxyException(string.Format("SOCKS5: IP address '{0}' is not supported.", ip));
  204. }
  205. return address;
  206. }
  207. }
  208. }