SOCKSProxy.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. #if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
  2. using System;
  3. using System.IO;
  4. using System.Text;
  5. using BestHTTP.Authentication;
  6. using BestHTTP.Extensions;
  7. using BestHTTP.PlatformSupport.Memory;
  8. using BestHTTP.PlatformSupport.Text;
  9. namespace BestHTTP
  10. {
  11. internal enum SOCKSVersions : byte
  12. {
  13. Unknown = 0x00,
  14. V5 = 0x05
  15. }
  16. /// <summary>
  17. /// https://tools.ietf.org/html/rfc1928
  18. /// The values currently defined for METHOD are:
  19. /// o X'00' NO AUTHENTICATION REQUIRED
  20. /// o X'01' GSSAPI
  21. /// o X'02' USERNAME/PASSWORD
  22. /// o X'03' to X'7F' IANA ASSIGNED
  23. /// o X'80' to X'FE' RESERVED FOR PRIVATE METHODS
  24. /// o X'FF' NO ACCEPTABLE METHODS
  25. /// </summary>
  26. internal enum SOCKSMethods : byte
  27. {
  28. NoAuthenticationRequired = 0x00,
  29. GSSAPI = 0x01,
  30. UsernameAndPassword = 0x02,
  31. NoAcceptableMethods = 0xFF
  32. }
  33. internal enum SOCKSReplies : byte
  34. {
  35. Succeeded = 0x00,
  36. GeneralSOCKSServerFailure = 0x01,
  37. ConnectionNotAllowedByRuleset = 0x02,
  38. NetworkUnreachable = 0x03,
  39. HostUnreachable = 0x04,
  40. ConnectionRefused = 0x05,
  41. TTLExpired = 0x06,
  42. CommandNotSupported = 0x07,
  43. AddressTypeNotSupported = 0x08
  44. }
  45. internal enum SOCKSAddressTypes
  46. {
  47. IPV4 = 0x00,
  48. DomainName = 0x03,
  49. IPv6 = 0x04
  50. }
  51. public sealed class SOCKSProxy : Proxy
  52. {
  53. public SOCKSProxy(Uri address, Credentials credentials)
  54. : base(address, credentials)
  55. { }
  56. internal override string GetRequestPath(Uri uri)
  57. {
  58. return uri.GetRequestPathAndQueryURL();
  59. }
  60. internal override bool SetupRequest(HTTPRequest request)
  61. {
  62. return false;
  63. }
  64. internal override void Connect(Stream stream, HTTPRequest request)
  65. {
  66. var buffer = BufferPool.Get(1024, true);
  67. try
  68. {
  69. int count = 0;
  70. // https://tools.ietf.org/html/rfc1928
  71. // The client connects to the server, and sends a version
  72. // identifier/method selection message:
  73. //
  74. // +----+----------+----------+
  75. // |VER | NMETHODS | METHODS |
  76. // +----+----------+----------+
  77. // | 1 | 1 | 1 to 255 |
  78. // +----+----------+----------+
  79. //
  80. // The VER field is set to X'05' for this version of the protocol. The
  81. // NMETHODS field contains the number of method identifier octets that
  82. // appear in the METHODS field.
  83. //
  84. buffer[count++] = (byte)SOCKSVersions.V5;
  85. if (this.Credentials != null)
  86. {
  87. buffer[count++] = 0x02; // method count
  88. buffer[count++] = (byte)SOCKSMethods.UsernameAndPassword;
  89. buffer[count++] = (byte)SOCKSMethods.NoAuthenticationRequired;
  90. }
  91. else
  92. {
  93. buffer[count++] = 0x01; // method count
  94. buffer[count++] = (byte)SOCKSMethods.NoAuthenticationRequired;
  95. }
  96. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  97. HTTPManager.Logger.Information("SOCKSProxy", string.Format("Sending method negotiation - count: {0} buffer: {1} ", count.ToString(), BufferToHexStr(buffer, count)), request.Context);
  98. // Write negotiation
  99. stream.Write(buffer, 0, count);
  100. // Read result
  101. count = stream.Read(buffer, 0, buffer.Length);
  102. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  103. HTTPManager.Logger.Information("SOCKSProxy", string.Format("Negotiation response - count: {0} buffer: {1} ", count.ToString(), BufferToHexStr(buffer, count)), request.Context);
  104. // The server selects from one of the methods given in METHODS, and
  105. // sends a METHOD selection message:
  106. //
  107. // +----+--------+
  108. // |VER | METHOD |
  109. // +----+--------+
  110. // | 1 | 1 |
  111. // +----+--------+
  112. //
  113. // If the selected METHOD is X'FF', none of the methods listed by the
  114. // client are acceptable, and the client MUST close the connection.
  115. //
  116. // The values currently defined for METHOD are:
  117. //
  118. // o X'00' NO AUTHENTICATION REQUIRED
  119. // o X'01' GSSAPI
  120. // o X'02' USERNAME/PASSWORD
  121. // o X'03' to X'7F' IANA ASSIGNED
  122. // o X'80' to X'FE' RESERVED FOR PRIVATE METHODS
  123. // o X'FF' NO ACCEPTABLE METHODS
  124. //
  125. // The client and server then enter a method-specific sub-negotiation.
  126. SOCKSVersions version = (SOCKSVersions)buffer[0];
  127. SOCKSMethods method = (SOCKSMethods)buffer[1];
  128. // Expected result:
  129. // 1.) Received bytes' count is 2: version + preferred method
  130. // 2.) Version must be 5
  131. // 3.) Preferred method must NOT be 0xFF
  132. if (count != 2)
  133. throw new Exception(string.Format("SOCKS Proxy - Expected read count: 2! count: {0} buffer: {1}" + count.ToString(), BufferToHexStr(buffer, count)));
  134. else if (version != SOCKSVersions.V5)
  135. throw new Exception("SOCKS Proxy - Expected version: 5, received version: " + buffer[0].ToString("X2"));
  136. else if (method == SOCKSMethods.NoAcceptableMethods)
  137. throw new Exception("SOCKS Proxy - Received 'NO ACCEPTABLE METHODS' (0xFF)");
  138. else
  139. {
  140. HTTPManager.Logger.Information("SOCKSProxy", "Method negotiation over. Method: " + method.ToString(), request.Context);
  141. switch (method)
  142. {
  143. case SOCKSMethods.NoAuthenticationRequired:
  144. // nothing to do
  145. break;
  146. case SOCKSMethods.UsernameAndPassword:
  147. if (this.Credentials.UserName.Length > 255)
  148. throw new Exception(string.Format("SOCKS Proxy - Credentials.UserName too long! {0} > 255", this.Credentials.UserName.Length.ToString()));
  149. if (this.Credentials.Password.Length > 255)
  150. throw new Exception(string.Format("SOCKS Proxy - Credentials.Password too long! {0} > 255", this.Credentials.Password.Length.ToString()));
  151. // https://tools.ietf.org/html/rfc1929 : Username/Password Authentication for SOCKS V5
  152. // Once the SOCKS V5 server has started, and the client has selected the
  153. // Username/Password Authentication protocol, the Username/Password
  154. // subnegotiation begins. This begins with the client producing a
  155. // Username/Password request:
  156. //
  157. // +----+------+----------+------+----------+
  158. // |VER | ULEN | UNAME | PLEN | PASSWD |
  159. // +----+------+----------+------+----------+
  160. // | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
  161. // +----+------+----------+------+----------+
  162. HTTPManager.Logger.Information("SOCKSProxy", "starting sub-negotiation", request.Context);
  163. count = 0;
  164. buffer[count++] = 0x01; // version of sub negotiation
  165. WriteString(buffer, ref count, this.Credentials.UserName);
  166. WriteString(buffer, ref count, this.Credentials.Password);
  167. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  168. HTTPManager.Logger.Information("SOCKSProxy", string.Format("Sending username and password sub-negotiation - count: {0} buffer: {1} ", count.ToString(), BufferToHexStr(buffer, count)), request.Context);
  169. // Write negotiation
  170. stream.Write(buffer, 0, count);
  171. // Read result
  172. count = stream.Read(buffer, 0, buffer.Length);
  173. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  174. HTTPManager.Logger.Information("SOCKSProxy", string.Format("Username and password sub-negotiation response - count: {0} buffer: {1} ", count.ToString(), BufferToHexStr(buffer, count)), request.Context);
  175. // The server verifies the supplied UNAME and PASSWD, and sends the
  176. // following response:
  177. //
  178. // +----+--------+
  179. // |VER | STATUS |
  180. // +----+--------+
  181. // | 1 | 1 |
  182. // +----+--------+
  183. // A STATUS field of X'00' indicates success. If the server returns a
  184. // `failure' (STATUS value other than X'00') status, it MUST close the
  185. // connection.
  186. bool success = buffer[1] == 0;
  187. if (count != 2)
  188. throw new Exception(string.Format("SOCKS Proxy - Expected read count: 2! count: {0} buffer: {1}" + count.ToString(), BufferToHexStr(buffer, count)));
  189. else if (!success)
  190. throw new Exception("SOCKS proxy: username+password authentication failed!");
  191. HTTPManager.Logger.Information("SOCKSProxy", "Authenticated!", request.Context);
  192. break;
  193. case SOCKSMethods.GSSAPI:
  194. throw new Exception("SOCKS proxy: GSSAPI not supported!");
  195. case SOCKSMethods.NoAcceptableMethods:
  196. throw new Exception("SOCKS proxy: No acceptable method");
  197. }
  198. }
  199. // The SOCKS request is formed as follows:
  200. //
  201. // +----+-----+-------+------+----------+----------+
  202. // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
  203. // +----+-----+-------+------+----------+----------+
  204. // | 1 | 1 | X'00' | 1 | Variable | 2 |
  205. // +----+-----+-------+------+----------+----------+
  206. //
  207. // Where:
  208. //
  209. // o VER protocol version: X'05'
  210. // o CMD
  211. // o CONNECT X'01'
  212. // o BIND X'02'
  213. // o UDP ASSOCIATE X'03'
  214. // o RSV RESERVED
  215. // o ATYP address type of following address
  216. // o IP V4 address: X'01'
  217. // o DOMAINNAME: X'03'
  218. // o IP V6 address: X'04'
  219. // o DST.ADDR desired destination address
  220. // o DST.PORT desired destination port in network octet
  221. // order
  222. count = 0;
  223. buffer[count++] = (byte)SOCKSVersions.V5; // version: 5
  224. buffer[count++] = 0x01; // command: connect
  225. buffer[count++] = 0x00; // reserved, bust be 0x00
  226. if (request.CurrentUri.IsHostIsAnIPAddress())
  227. {
  228. bool isIPV4 = Extensions.Extensions.IsIpV4AddressValid(request.CurrentUri.Host);
  229. buffer[count++] = isIPV4 ? (byte)SOCKSAddressTypes.IPV4 : (byte)SOCKSAddressTypes.IPv6;
  230. var ipAddress = System.Net.IPAddress.Parse(request.CurrentUri.Host);
  231. var ipBytes = ipAddress.GetAddressBytes();
  232. WriteBytes(buffer, ref count, ipBytes); // destination address
  233. }
  234. else
  235. {
  236. buffer[count++] = (byte)SOCKSAddressTypes.DomainName;
  237. // The first octet of the address field contains the number of octets of name that
  238. // follow, there is no terminating NUL octet.
  239. WriteString(buffer, ref count, request.CurrentUri.Host);
  240. }
  241. // destination port in network octet order
  242. buffer[count++] = (byte)((request.CurrentUri.Port >> 8) & 0xFF);
  243. buffer[count++] = (byte)(request.CurrentUri.Port & 0xFF);
  244. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  245. HTTPManager.Logger.Information("SOCKSProxy", string.Format("Sending connect request - count: {0} buffer: {1} ", count.ToString(), BufferToHexStr(buffer, count)), request.Context);
  246. stream.Write(buffer, 0, count);
  247. count = stream.Read(buffer, 0, buffer.Length);
  248. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  249. HTTPManager.Logger.Information("SOCKSProxy", string.Format("Connect response - count: {0} buffer: {1} ", count.ToString(), BufferToHexStr(buffer, count)), request.Context);
  250. // The SOCKS request information is sent by the client as soon as it has
  251. // established a connection to the SOCKS server, and completed the
  252. // authentication negotiations. The server evaluates the request, and
  253. // returns a reply formed as follows:
  254. //
  255. // +----+-----+-------+------+----------+----------+
  256. // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
  257. // +----+-----+-------+------+----------+----------+
  258. // | 1 | 1 | X'00' | 1 | Variable | 2 |
  259. // +----+-----+-------+------+----------+----------+
  260. //
  261. // Where:
  262. // o VER protocol version: X'05'
  263. // o REP Reply field:
  264. // o X'00' succeeded
  265. // o X'01' general SOCKS server failure
  266. // o X'02' connection not allowed by ruleset
  267. // o X'03' Network unreachable
  268. // o X'04' Host unreachable
  269. // o X'05' Connection refused
  270. // o X'06' TTL expired
  271. // o X'07' Command not supported
  272. // o X'08' Address type not supported
  273. // o X'09' to X'FF' unassigned
  274. // o RSV RESERVED
  275. // o ATYP address type of following address
  276. // o IP V4 address: X'01'
  277. // o DOMAINNAME: X'03'
  278. // o IP V6 address: X'04'
  279. // o BND.ADDR server bound address
  280. // o BND.PORT server bound port in network octet order
  281. //
  282. // Fields marked RESERVED (RSV) must be set to X'00'.
  283. version = (SOCKSVersions)buffer[0];
  284. SOCKSReplies reply = (SOCKSReplies)buffer[1];
  285. // at least 10 bytes expected as a result
  286. if (count < 10)
  287. throw new Exception(string.Format("SOCKS proxy: not enough data returned by the server. Expected count is at least 10 bytes, server returned {0} bytes! content: {1}", count.ToString(), BufferToHexStr(buffer, count)));
  288. else if (reply != SOCKSReplies.Succeeded)
  289. throw new Exception("SOCKS proxy error: " + reply.ToString());
  290. HTTPManager.Logger.Information("SOCKSProxy", "Connected!", request.Context);
  291. }
  292. finally
  293. {
  294. BufferPool.Release(buffer);
  295. }
  296. }
  297. private void WriteString(byte[] buffer, ref int count, string str)
  298. {
  299. // Get the bytes
  300. int byteCount = Encoding.UTF8.GetByteCount(str);
  301. if (byteCount > 255)
  302. throw new Exception(string.Format("SOCKS Proxy - String is too large ({0}) to fit in 255 bytes!", byteCount.ToString()));
  303. // number of bytes
  304. buffer[count++] = (byte)byteCount;
  305. // and the bytes itself
  306. Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, count);
  307. count += byteCount;
  308. }
  309. private void WriteBytes(byte[] buffer, ref int count, byte[] bytes)
  310. {
  311. Array.Copy(bytes, 0, buffer, count, bytes.Length);
  312. count += bytes.Length;
  313. }
  314. private string BufferToHexStr(byte[] buffer, int count)
  315. {
  316. StringBuilder sb = StringBuilderPool.Get(count * 2); //new StringBuilder(count * 2);
  317. for (int i = 0; i < count; ++i)
  318. sb.AppendFormat("0x{0} ", buffer[i].ToString("X2"));
  319. return StringBuilderPool.ReleaseAndGrab(sb);
  320. }
  321. }
  322. }
  323. #endif