SslServer.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Diagnostics;
  4. using System.Net;
  5. using System.Net.Sockets;
  6. using System.Text;
  7. using System.Threading;
  8. namespace NetCoreServer
  9. {
  10. /// <summary>
  11. /// SSL server is used to connect, disconnect and manage SSL sessions
  12. /// </summary>
  13. /// <remarks>Thread-safe</remarks>
  14. public class SslServer : IDisposable
  15. {
  16. /// <summary>
  17. /// Initialize SSL server with a given IP address and port number
  18. /// </summary>
  19. /// <param name="context">SSL context</param>
  20. /// <param name="address">IP address</param>
  21. /// <param name="port">Port number</param>
  22. public SslServer(SslContext context, IPAddress address, int port) : this(context, new IPEndPoint(address, port)) {}
  23. /// <summary>
  24. /// Initialize SSL server with a given IP address and port number
  25. /// </summary>
  26. /// <param name="context">SSL context</param>
  27. /// <param name="address">IP address</param>
  28. /// <param name="port">Port number</param>
  29. public SslServer(SslContext context, string address, int port) : this(context, new IPEndPoint(IPAddress.Parse(address), port)) {}
  30. /// <summary>
  31. /// Initialize SSL server with a given DNS endpoint
  32. /// </summary>
  33. /// <param name="context">SSL context</param>
  34. /// <param name="endpoint">DNS endpoint</param>
  35. public SslServer(SslContext context, DnsEndPoint endpoint) : this(context, endpoint as EndPoint, endpoint.Host, endpoint.Port) {}
  36. /// <summary>
  37. /// Initialize SSL server with a given IP endpoint
  38. /// </summary>
  39. /// <param name="context">SSL context</param>
  40. /// <param name="endpoint">IP endpoint</param>
  41. public SslServer(SslContext context, IPEndPoint endpoint) : this(context, endpoint as EndPoint, endpoint.Address.ToString(), endpoint.Port) {}
  42. /// <summary>
  43. /// Initialize SSL server with a given SSL context, endpoint, address and port
  44. /// </summary>
  45. /// <param name="context">SSL context</param>
  46. /// <param name="endpoint">Endpoint</param>
  47. /// <param name="address">Server address</param>
  48. /// <param name="port">Server port</param>
  49. private SslServer(SslContext context, EndPoint endpoint, string address, int port)
  50. {
  51. Id = Guid.NewGuid();
  52. Address = address;
  53. Port = port;
  54. Context = context;
  55. Endpoint = endpoint;
  56. }
  57. /// <summary>
  58. /// Server Id
  59. /// </summary>
  60. public Guid Id { get; }
  61. /// <summary>
  62. /// SSL server address
  63. /// </summary>
  64. public string Address { get; }
  65. /// <summary>
  66. /// SSL server port
  67. /// </summary>
  68. public int Port { get; }
  69. /// <summary>
  70. /// SSL context
  71. /// </summary>
  72. public SslContext Context { get; }
  73. /// <summary>
  74. /// Endpoint
  75. /// </summary>
  76. public EndPoint Endpoint { get; private set; }
  77. /// <summary>
  78. /// Number of sessions connected to the server
  79. /// </summary>
  80. public long ConnectedSessions { get { return Sessions.Count; } }
  81. /// <summary>
  82. /// Number of bytes pending sent by the server
  83. /// </summary>
  84. public long BytesPending { get { return _bytesPending; } }
  85. /// <summary>
  86. /// Number of bytes sent by the server
  87. /// </summary>
  88. public long BytesSent { get { return _bytesSent; } }
  89. /// <summary>
  90. /// Number of bytes received by the server
  91. /// </summary>
  92. public long BytesReceived { get { return _bytesReceived; } }
  93. /// <summary>
  94. /// Option: acceptor backlog size
  95. /// </summary>
  96. /// <remarks>
  97. /// This option will set the listening socket's backlog size
  98. /// </remarks>
  99. public int OptionAcceptorBacklog { get; set; } = 1024;
  100. /// <summary>
  101. /// Option: dual mode socket
  102. /// </summary>
  103. /// <remarks>
  104. /// Specifies whether the Socket is a dual-mode socket used for both IPv4 and IPv6.
  105. /// Will work only if socket is bound on IPv6 address.
  106. /// </remarks>
  107. public bool OptionDualMode { get; set; }
  108. /// <summary>
  109. /// Option: keep alive
  110. /// </summary>
  111. /// <remarks>
  112. /// This option will setup SO_KEEPALIVE if the OS support this feature
  113. /// </remarks>
  114. public bool OptionKeepAlive { get; set; }
  115. /// <summary>
  116. /// Option: TCP keep alive time
  117. /// </summary>
  118. /// <remarks>
  119. /// The number of seconds a TCP connection will remain alive/idle before keepalive probes are sent to the remote
  120. /// </remarks>
  121. public int OptionTcpKeepAliveTime { get; set; } = -1;
  122. /// <summary>
  123. /// Option: TCP keep alive interval
  124. /// </summary>
  125. /// <remarks>
  126. /// The number of seconds a TCP connection will wait for a keepalive response before sending another keepalive probe
  127. /// </remarks>
  128. public int OptionTcpKeepAliveInterval { get; set; } = -1;
  129. /// <summary>
  130. /// Option: TCP keep alive retry count
  131. /// </summary>
  132. /// <remarks>
  133. /// The number of TCP keep alive probes that will be sent before the connection is terminated
  134. /// </remarks>
  135. public int OptionTcpKeepAliveRetryCount { get; set; } = -1;
  136. /// <summary>
  137. /// Option: no delay
  138. /// </summary>
  139. /// <remarks>
  140. /// This option will enable/disable Nagle's algorithm for SSL protocol
  141. /// </remarks>
  142. public bool OptionNoDelay { get; set; }
  143. /// <summary>
  144. /// Option: reuse address
  145. /// </summary>
  146. /// <remarks>
  147. /// This option will enable/disable SO_REUSEADDR if the OS support this feature
  148. /// </remarks>
  149. public bool OptionReuseAddress { get; set; }
  150. /// <summary>
  151. /// Option: enables a socket to be bound for exclusive access
  152. /// </summary>
  153. /// <remarks>
  154. /// This option will enable/disable SO_EXCLUSIVEADDRUSE if the OS support this feature
  155. /// </remarks>
  156. public bool OptionExclusiveAddressUse { get; set; }
  157. /// <summary>
  158. /// Option: receive buffer size
  159. /// </summary>
  160. public int OptionReceiveBufferSize { get; set; } = 8192;
  161. /// <summary>
  162. /// Option: send buffer size
  163. /// </summary>
  164. public int OptionSendBufferSize { get; set; } = 8192;
  165. #region Start/Stop server
  166. // Server acceptor
  167. private Socket _acceptorSocket;
  168. private SocketAsyncEventArgs _acceptorEventArg;
  169. // Server statistic
  170. internal long _bytesPending;
  171. internal long _bytesSent;
  172. internal long _bytesReceived;
  173. /// <summary>
  174. /// Is the server started?
  175. /// </summary>
  176. public bool IsStarted { get; private set; }
  177. /// <summary>
  178. /// Is the server accepting new clients?
  179. /// </summary>
  180. public bool IsAccepting { get; private set; }
  181. /// <summary>
  182. /// Create a new socket object
  183. /// </summary>
  184. /// <remarks>
  185. /// Method may be override if you need to prepare some specific socket object in your implementation.
  186. /// </remarks>
  187. /// <returns>Socket object</returns>
  188. protected virtual Socket CreateSocket()
  189. {
  190. return new Socket(Endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  191. }
  192. /// <summary>
  193. /// Start the server
  194. /// </summary>
  195. /// <returns>'true' if the server was successfully started, 'false' if the server failed to start</returns>
  196. public virtual bool Start()
  197. {
  198. Debug.Assert(!IsStarted, "SSL server is already started!");
  199. if (IsStarted)
  200. return false;
  201. // Setup acceptor event arg
  202. _acceptorEventArg = new SocketAsyncEventArgs();
  203. _acceptorEventArg.Completed += OnAsyncCompleted;
  204. // Create a new acceptor socket
  205. _acceptorSocket = CreateSocket();
  206. // Update the acceptor socket disposed flag
  207. IsSocketDisposed = false;
  208. // Apply the option: reuse address
  209. _acceptorSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, OptionReuseAddress);
  210. // Apply the option: exclusive address use
  211. _acceptorSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, OptionExclusiveAddressUse);
  212. // Apply the option: dual mode (this option must be applied before listening)
  213. if (_acceptorSocket.AddressFamily == AddressFamily.InterNetworkV6)
  214. _acceptorSocket.DualMode = OptionDualMode;
  215. // Bind the acceptor socket to the endpoint
  216. _acceptorSocket.Bind(Endpoint);
  217. // Refresh the endpoint property based on the actual endpoint created
  218. Endpoint = _acceptorSocket.LocalEndPoint;
  219. // Call the server starting handler
  220. OnStarting();
  221. // Start listen to the acceptor socket with the given accepting backlog size
  222. _acceptorSocket.Listen(OptionAcceptorBacklog);
  223. // Reset statistic
  224. _bytesPending = 0;
  225. _bytesSent = 0;
  226. _bytesReceived = 0;
  227. // Update the started flag
  228. IsStarted = true;
  229. // Call the server started handler
  230. OnStarted();
  231. // Perform the first server accept
  232. IsAccepting = true;
  233. StartAccept(_acceptorEventArg);
  234. return true;
  235. }
  236. /// <summary>
  237. /// Stop the server
  238. /// </summary>
  239. /// <returns>'true' if the server was successfully stopped, 'false' if the server is already stopped</returns>
  240. public virtual bool Stop()
  241. {
  242. Debug.Assert(IsStarted, "SSL server is not started!");
  243. if (!IsStarted)
  244. return false;
  245. // Stop accepting new clients
  246. IsAccepting = false;
  247. // Reset acceptor event arg
  248. _acceptorEventArg.Completed -= OnAsyncCompleted;
  249. // Call the server stopping handler
  250. OnStopping();
  251. try
  252. {
  253. // Close the acceptor socket
  254. _acceptorSocket.Close();
  255. // Dispose the acceptor socket
  256. _acceptorSocket.Dispose();
  257. // Dispose event arguments
  258. _acceptorEventArg.Dispose();
  259. // Update the acceptor socket disposed flag
  260. IsSocketDisposed = true;
  261. }
  262. catch (ObjectDisposedException) {}
  263. // Disconnect all sessions
  264. DisconnectAll();
  265. // Update the started flag
  266. IsStarted = false;
  267. // Call the server stopped handler
  268. OnStopped();
  269. return true;
  270. }
  271. /// <summary>
  272. /// Restart the server
  273. /// </summary>
  274. /// <returns>'true' if the server was successfully restarted, 'false' if the server failed to restart</returns>
  275. public virtual bool Restart()
  276. {
  277. if (!Stop())
  278. return false;
  279. while (IsStarted)
  280. Thread.Yield();
  281. return Start();
  282. }
  283. #endregion
  284. #region Accepting clients
  285. /// <summary>
  286. /// Start accept a new client connection
  287. /// </summary>
  288. private void StartAccept(SocketAsyncEventArgs e)
  289. {
  290. // Socket must be cleared since the context object is being reused
  291. e.AcceptSocket = null;
  292. // Async accept a new client connection
  293. if (!_acceptorSocket.AcceptAsync(e))
  294. ProcessAccept(e);
  295. }
  296. /// <summary>
  297. /// Process accepted client connection
  298. /// </summary>
  299. private void ProcessAccept(SocketAsyncEventArgs e)
  300. {
  301. if (e.SocketError == SocketError.Success)
  302. {
  303. // Create a new session to register
  304. var session = CreateSession();
  305. // Register the session
  306. RegisterSession(session);
  307. // Connect new session
  308. session.Connect(e.AcceptSocket);
  309. }
  310. else
  311. SendError(e.SocketError);
  312. // Accept the next client connection
  313. if (IsAccepting)
  314. StartAccept(e);
  315. }
  316. /// <summary>
  317. /// This method is the callback method associated with Socket.AcceptAsync()
  318. /// operations and is invoked when an accept operation is complete
  319. /// </summary>
  320. private void OnAsyncCompleted(object sender, SocketAsyncEventArgs e)
  321. {
  322. if (IsSocketDisposed)
  323. return;
  324. ProcessAccept(e);
  325. }
  326. #endregion
  327. #region Session factory
  328. /// <summary>
  329. /// Create SSL session factory method
  330. /// </summary>
  331. /// <returns>SSL session</returns>
  332. protected virtual SslSession CreateSession() { return new SslSession(this); }
  333. #endregion
  334. #region Session management
  335. /// <summary>
  336. /// Server sessions
  337. /// </summary>
  338. protected readonly ConcurrentDictionary<Guid, SslSession> Sessions = new ConcurrentDictionary<Guid, SslSession>();
  339. /// <summary>
  340. /// Disconnect all connected sessions
  341. /// </summary>
  342. /// <returns>'true' if all sessions were successfully disconnected, 'false' if the server is not started</returns>
  343. public virtual bool DisconnectAll()
  344. {
  345. if (!IsStarted)
  346. return false;
  347. // Disconnect all sessions
  348. foreach (var session in Sessions.Values)
  349. session.Disconnect();
  350. return true;
  351. }
  352. /// <summary>
  353. /// Find a session with a given Id
  354. /// </summary>
  355. /// <param name="id">Session Id</param>
  356. /// <returns>Session with a given Id or null if the session it not connected</returns>
  357. public SslSession FindSession(Guid id)
  358. {
  359. // Try to find the required session
  360. return Sessions.TryGetValue(id, out SslSession result) ? result : null;
  361. }
  362. /// <summary>
  363. /// Register a new session
  364. /// </summary>
  365. /// <param name="session">Session to register</param>
  366. internal void RegisterSession(SslSession session)
  367. {
  368. // Register a new session
  369. Sessions.TryAdd(session.Id, session);
  370. }
  371. /// <summary>
  372. /// Unregister session by Id
  373. /// </summary>
  374. /// <param name="id">Session Id</param>
  375. internal void UnregisterSession(Guid id)
  376. {
  377. // Unregister session by Id
  378. Sessions.TryRemove(id, out SslSession _);
  379. }
  380. #endregion
  381. #region Multicasting
  382. /// <summary>
  383. /// Multicast data to all connected sessions
  384. /// </summary>
  385. /// <param name="buffer">Buffer to multicast</param>
  386. /// <returns>'true' if the data was successfully multicasted, 'false' if the data was not multicasted</returns>
  387. public virtual bool Multicast(byte[] buffer) => Multicast(buffer.AsSpan());
  388. /// <summary>
  389. /// Multicast data to all connected clients
  390. /// </summary>
  391. /// <param name="buffer">Buffer to multicast</param>
  392. /// <param name="offset">Buffer offset</param>
  393. /// <param name="size">Buffer size</param>
  394. /// <returns>'true' if the data was successfully multicasted, 'false' if the data was not multicasted</returns>
  395. public virtual bool Multicast(byte[] buffer, long offset, long size) => Multicast(buffer.AsSpan((int)offset, (int)size));
  396. /// <summary>
  397. /// Multicast data to all connected clients
  398. /// </summary>
  399. /// <param name="buffer">Buffer to send as a span of bytes</param>
  400. /// <returns>'true' if the data was successfully multicasted, 'false' if the data was not multicasted</returns>
  401. public virtual bool Multicast(ReadOnlySpan<byte> buffer)
  402. {
  403. if (!IsStarted)
  404. return false;
  405. if (buffer.IsEmpty)
  406. return true;
  407. // Multicast data to all sessions
  408. foreach (var session in Sessions.Values)
  409. session.SendAsync(buffer);
  410. return true;
  411. }
  412. /// <summary>
  413. /// Multicast text to all connected clients
  414. /// </summary>
  415. /// <param name="text">Text string to multicast</param>
  416. /// <returns>'true' if the text was successfully multicasted, 'false' if the text was not multicasted</returns>
  417. public virtual bool Multicast(string text) => Multicast(Encoding.UTF8.GetBytes(text));
  418. /// <summary>
  419. /// Multicast text to all connected clients
  420. /// </summary>
  421. /// <param name="text">Text to multicast as a span of characters</param>
  422. /// <returns>'true' if the text was successfully multicasted, 'false' if the text was not multicasted</returns>
  423. public virtual bool Multicast(ReadOnlySpan<char> text) => Multicast(Encoding.UTF8.GetBytes(text.ToArray()));
  424. #endregion
  425. #region Server handlers
  426. /// <summary>
  427. /// Handle server starting notification
  428. /// </summary>
  429. protected virtual void OnStarting() {}
  430. /// <summary>
  431. /// Handle server started notification
  432. /// </summary>
  433. protected virtual void OnStarted() {}
  434. /// <summary>
  435. /// Handle server stopping notification
  436. /// </summary>
  437. protected virtual void OnStopping() {}
  438. /// <summary>
  439. /// Handle server stopped notification
  440. /// </summary>
  441. protected virtual void OnStopped() {}
  442. /// <summary>
  443. /// Handle session connecting notification
  444. /// </summary>
  445. /// <param name="session">Connecting session</param>
  446. protected virtual void OnConnecting(SslSession session) {}
  447. /// <summary>
  448. /// Handle session connected notification
  449. /// </summary>
  450. /// <param name="session">Connected session</param>
  451. protected virtual void OnConnected(SslSession session) {}
  452. /// <summary>
  453. /// Handle session handshaking notification
  454. /// </summary>
  455. /// <param name="session">Handshaking session</param>
  456. protected virtual void OnHandshaking(SslSession session) {}
  457. /// <summary>
  458. /// Handle session handshaked notification
  459. /// </summary>
  460. /// <param name="session">Handshaked session</param>
  461. protected virtual void OnHandshaked(SslSession session) {}
  462. /// <summary>
  463. /// Handle session disconnecting notification
  464. /// </summary>
  465. /// <param name="session">Disconnecting session</param>
  466. protected virtual void OnDisconnecting(SslSession session) {}
  467. /// <summary>
  468. /// Handle session disconnected notification
  469. /// </summary>
  470. /// <param name="session">Disconnected session</param>
  471. protected virtual void OnDisconnected(SslSession session) {}
  472. /// <summary>
  473. /// Handle error notification
  474. /// </summary>
  475. /// <param name="error">Socket error code</param>
  476. protected virtual void OnError(SocketError error) {}
  477. internal void OnConnectingInternal(SslSession session) { OnConnecting(session); }
  478. internal void OnConnectedInternal(SslSession session) { OnConnected(session); }
  479. internal void OnHandshakingInternal(SslSession session) { OnHandshaking(session); }
  480. internal void OnHandshakedInternal(SslSession session) { OnHandshaked(session); }
  481. internal void OnDisconnectingInternal(SslSession session) { OnDisconnecting(session); }
  482. internal void OnDisconnectedInternal(SslSession session) { OnDisconnected(session); }
  483. #endregion
  484. #region Error handling
  485. /// <summary>
  486. /// Send error notification
  487. /// </summary>
  488. /// <param name="error">Socket error code</param>
  489. private void SendError(SocketError error)
  490. {
  491. // Skip disconnect errors
  492. if ((error == SocketError.ConnectionAborted) ||
  493. (error == SocketError.ConnectionRefused) ||
  494. (error == SocketError.ConnectionReset) ||
  495. (error == SocketError.OperationAborted) ||
  496. (error == SocketError.Shutdown))
  497. return;
  498. OnError(error);
  499. }
  500. #endregion
  501. #region IDisposable implementation
  502. /// <summary>
  503. /// Disposed flag
  504. /// </summary>
  505. public bool IsDisposed { get; private set; }
  506. /// <summary>
  507. /// Acceptor socket disposed flag
  508. /// </summary>
  509. public bool IsSocketDisposed { get; private set; } = true;
  510. // Implement IDisposable.
  511. public void Dispose()
  512. {
  513. Dispose(true);
  514. GC.SuppressFinalize(this);
  515. }
  516. protected virtual void Dispose(bool disposingManagedResources)
  517. {
  518. // The idea here is that Dispose(Boolean) knows whether it is
  519. // being called to do explicit cleanup (the Boolean is true)
  520. // versus being called due to a garbage collection (the Boolean
  521. // is false). This distinction is useful because, when being
  522. // disposed explicitly, the Dispose(Boolean) method can safely
  523. // execute code using reference type fields that refer to other
  524. // objects knowing for sure that these other objects have not been
  525. // finalized or disposed of yet. When the Boolean is false,
  526. // the Dispose(Boolean) method should not execute code that
  527. // refer to reference type fields because those objects may
  528. // have already been finalized."
  529. if (!IsDisposed)
  530. {
  531. if (disposingManagedResources)
  532. {
  533. // Dispose managed resources here...
  534. Stop();
  535. }
  536. // Dispose unmanaged resources here...
  537. // Set large fields to null here...
  538. // Mark as disposed.
  539. IsDisposed = true;
  540. }
  541. }
  542. #endregion
  543. }
  544. }