Socks5Connector.cs 10 KB

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