You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

WrappedSocket.cs 8.6 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. using System;
  2. using System.Net;
  3. using System.Net.Sockets;
  4. using System.Threading;
  5. namespace Shadowsocks.Util.Sockets
  6. {
  7. /*
  8. * A wrapped socket class which support both ipv4 and ipv6 based on the
  9. * connected remote endpoint.
  10. *
  11. * If the server address is host name, then it may have both ipv4 and ipv6 address
  12. * after resolving. The main idea is we don't want to resolve and choose the address
  13. * by ourself. Instead, Socket.ConnectAsync() do handle this thing internally by trying
  14. * each address and returning an established socket connection.
  15. */
  16. public class WrappedSocket
  17. {
  18. public EndPoint LocalEndPoint => _activeSocket?.LocalEndPoint;
  19. // Only used during connection and close, so it won't cost too much.
  20. private SpinLock _socketSyncLock = new SpinLock();
  21. private bool _disposed;
  22. private bool Connected => _activeSocket != null;
  23. private Socket _activeSocket;
  24. public void BeginConnect(EndPoint remoteEP, AsyncCallback callback, object state)
  25. {
  26. if (_disposed)
  27. {
  28. throw new ObjectDisposedException(GetType().FullName);
  29. }
  30. if (Connected)
  31. {
  32. throw new SocketException((int) SocketError.IsConnected);
  33. }
  34. var arg = new SocketAsyncEventArgs();
  35. arg.RemoteEndPoint = remoteEP;
  36. arg.Completed += OnTcpConnectCompleted;
  37. arg.UserToken = new TcpUserToken(callback, state);
  38. if(!Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, arg))
  39. {
  40. OnTcpConnectCompleted(this, arg);
  41. }
  42. }
  43. private class FakeAsyncResult : IAsyncResult
  44. {
  45. public bool IsCompleted { get; } = true;
  46. public WaitHandle AsyncWaitHandle { get; } = null;
  47. public object AsyncState { get; set; }
  48. public bool CompletedSynchronously { get; } = true;
  49. public Exception InternalException { get; set; } = null;
  50. }
  51. private class TcpUserToken
  52. {
  53. public AsyncCallback Callback { get; }
  54. public object AsyncState { get; }
  55. public TcpUserToken(AsyncCallback callback, object state)
  56. {
  57. Callback = callback;
  58. AsyncState = state;
  59. }
  60. }
  61. private void OnTcpConnectCompleted(object sender, SocketAsyncEventArgs args)
  62. {
  63. using (args)
  64. {
  65. args.Completed -= OnTcpConnectCompleted;
  66. var token = (TcpUserToken) args.UserToken;
  67. if (args.SocketError != SocketError.Success)
  68. {
  69. var ex = args.ConnectByNameError ?? new SocketException((int) args.SocketError);
  70. var r = new FakeAsyncResult()
  71. {
  72. AsyncState = token.AsyncState,
  73. InternalException = ex
  74. };
  75. token.Callback(r);
  76. }
  77. else
  78. {
  79. var lockTaken = false;
  80. if (!_socketSyncLock.IsHeldByCurrentThread)
  81. {
  82. _socketSyncLock.TryEnter(ref lockTaken);
  83. }
  84. try
  85. {
  86. if (Connected)
  87. {
  88. args.ConnectSocket.FullClose();
  89. }
  90. else
  91. {
  92. _activeSocket = args.ConnectSocket;
  93. if (_disposed)
  94. {
  95. _activeSocket.FullClose();
  96. }
  97. var r = new FakeAsyncResult()
  98. {
  99. AsyncState = token.AsyncState
  100. };
  101. token.Callback(r);
  102. }
  103. }
  104. finally
  105. {
  106. if (lockTaken)
  107. {
  108. _socketSyncLock.Exit();
  109. }
  110. }
  111. }
  112. }
  113. }
  114. public void EndConnect(IAsyncResult asyncResult)
  115. {
  116. if (_disposed)
  117. {
  118. throw new ObjectDisposedException(GetType().FullName);
  119. }
  120. var r = asyncResult as FakeAsyncResult;
  121. if (r == null)
  122. {
  123. throw new ArgumentException("Invalid asyncResult.", nameof(asyncResult));
  124. }
  125. if (r.InternalException != null)
  126. {
  127. throw r.InternalException;
  128. }
  129. }
  130. public void Dispose()
  131. {
  132. if (_disposed)
  133. {
  134. return;
  135. }
  136. var lockTaken = false;
  137. if (!_socketSyncLock.IsHeldByCurrentThread)
  138. {
  139. _socketSyncLock.TryEnter(ref lockTaken);
  140. }
  141. try
  142. {
  143. _disposed = true;
  144. _activeSocket?.FullClose();
  145. }
  146. finally
  147. {
  148. if (lockTaken)
  149. {
  150. _socketSyncLock.Exit();
  151. }
  152. }
  153. }
  154. public IAsyncResult BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags,
  155. AsyncCallback callback,
  156. object state)
  157. {
  158. if (_disposed)
  159. {
  160. throw new ObjectDisposedException(GetType().FullName);
  161. }
  162. if (!Connected)
  163. {
  164. throw new SocketException((int) SocketError.NotConnected);
  165. }
  166. return _activeSocket.BeginSend(buffer, offset, size, socketFlags, callback, state);
  167. }
  168. public int EndSend(IAsyncResult asyncResult)
  169. {
  170. if (_disposed)
  171. {
  172. throw new ObjectDisposedException(GetType().FullName);
  173. }
  174. if (!Connected)
  175. {
  176. throw new SocketException((int) SocketError.NotConnected);
  177. }
  178. return _activeSocket.EndSend(asyncResult);
  179. }
  180. public IAsyncResult BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags,
  181. AsyncCallback callback,
  182. object state)
  183. {
  184. if (_disposed)
  185. {
  186. throw new ObjectDisposedException(GetType().FullName);
  187. }
  188. if (!Connected)
  189. {
  190. throw new SocketException((int) SocketError.NotConnected);
  191. }
  192. return _activeSocket.BeginReceive(buffer, offset, size, socketFlags, callback, state);
  193. }
  194. public int EndReceive(IAsyncResult asyncResult)
  195. {
  196. if (_disposed)
  197. {
  198. throw new ObjectDisposedException(GetType().FullName);
  199. }
  200. if (!Connected)
  201. {
  202. throw new SocketException((int) SocketError.NotConnected);
  203. }
  204. return _activeSocket.EndReceive(asyncResult);
  205. }
  206. public void Shutdown(SocketShutdown how)
  207. {
  208. if (_disposed)
  209. {
  210. throw new ObjectDisposedException(GetType().FullName);
  211. }
  212. if (!Connected)
  213. {
  214. return;
  215. }
  216. _activeSocket.Shutdown(how);
  217. }
  218. public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, bool optionValue)
  219. {
  220. SetSocketOption(optionLevel, optionName, optionValue ? 1 : 0);
  221. }
  222. public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, int optionValue)
  223. {
  224. if (_disposed)
  225. {
  226. throw new ObjectDisposedException(GetType().FullName);
  227. }
  228. if (!Connected)
  229. {
  230. throw new SocketException((int)SocketError.NotConnected);
  231. }
  232. _activeSocket.SetSocketOption(optionLevel, optionName, optionValue);
  233. }
  234. }
  235. }