SOCKSV5Negotiator.cs 22 KB

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