2
0

ForwardedPortDynamic.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. using System;
  2. using System.Buffers.Binary;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Net.Sockets;
  7. using System.Text;
  8. using System.Threading;
  9. using Renci.SshNet.Abstractions;
  10. using Renci.SshNet.Channels;
  11. using Renci.SshNet.Common;
  12. namespace Renci.SshNet
  13. {
  14. /// <summary>
  15. /// Provides functionality for forwarding connections from the client to destination servers via the SSH server,
  16. /// also known as dynamic port forwarding.
  17. /// </summary>
  18. public class ForwardedPortDynamic : ForwardedPort
  19. {
  20. private ForwardedPortStatus _status;
  21. /// <summary>
  22. /// Holds a value indicating whether the current instance is disposed.
  23. /// </summary>
  24. /// <value>
  25. /// <see langword="true"/> if the current instance is disposed; otherwise, <see langword="false"/>.
  26. /// </value>
  27. private bool _isDisposed;
  28. /// <summary>
  29. /// Gets the bound host.
  30. /// </summary>
  31. public string BoundHost { get; }
  32. /// <summary>
  33. /// Gets the bound port.
  34. /// </summary>
  35. public uint BoundPort { get; }
  36. private Socket _listener;
  37. private CountdownEvent _pendingChannelCountdown;
  38. /// <summary>
  39. /// Gets a value indicating whether port forwarding is started.
  40. /// </summary>
  41. /// <value>
  42. /// <see langword="true"/> if port forwarding is started; otherwise, <see langword="false"/>.
  43. /// </value>
  44. public override bool IsStarted
  45. {
  46. get { return _status == ForwardedPortStatus.Started; }
  47. }
  48. /// <summary>
  49. /// Initializes a new instance of the <see cref="ForwardedPortDynamic"/> class.
  50. /// </summary>
  51. /// <param name="port">The port.</param>
  52. public ForwardedPortDynamic(uint port)
  53. : this(string.Empty, port)
  54. {
  55. }
  56. /// <summary>
  57. /// Initializes a new instance of the <see cref="ForwardedPortDynamic"/> class.
  58. /// </summary>
  59. /// <param name="host">The host.</param>
  60. /// <param name="port">The port.</param>
  61. public ForwardedPortDynamic(string host, uint port)
  62. {
  63. BoundHost = host;
  64. BoundPort = port;
  65. _status = ForwardedPortStatus.Stopped;
  66. }
  67. /// <summary>
  68. /// Starts local port forwarding.
  69. /// </summary>
  70. protected override void StartPort()
  71. {
  72. if (!ForwardedPortStatus.ToStarting(ref _status))
  73. {
  74. return;
  75. }
  76. try
  77. {
  78. InternalStart();
  79. }
  80. catch (Exception)
  81. {
  82. _status = ForwardedPortStatus.Stopped;
  83. throw;
  84. }
  85. }
  86. /// <summary>
  87. /// Stops local port forwarding, and waits for the specified timeout until all pending
  88. /// requests are processed.
  89. /// </summary>
  90. /// <param name="timeout">The maximum amount of time to wait for pending requests to finish processing.</param>
  91. protected override void StopPort(TimeSpan timeout)
  92. {
  93. timeout.EnsureValidTimeout();
  94. if (!ForwardedPortStatus.ToStopping(ref _status))
  95. {
  96. return;
  97. }
  98. // signal existing channels that the port is closing
  99. base.StopPort(timeout);
  100. // prevent new requests from getting processed
  101. StopListener();
  102. // wait for open channels to close
  103. InternalStop(timeout);
  104. // mark port stopped
  105. _status = ForwardedPortStatus.Stopped;
  106. }
  107. /// <summary>
  108. /// Ensures the current instance is not disposed.
  109. /// </summary>
  110. /// <exception cref="ObjectDisposedException">The current instance is disposed.</exception>
  111. protected override void CheckDisposed()
  112. {
  113. ThrowHelper.ThrowObjectDisposedIf(_isDisposed, this);
  114. }
  115. /// <summary>
  116. /// Releases unmanaged and - optionally - managed resources.
  117. /// </summary>
  118. /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
  119. protected override void Dispose(bool disposing)
  120. {
  121. if (_isDisposed)
  122. {
  123. return;
  124. }
  125. base.Dispose(disposing);
  126. InternalDispose(disposing);
  127. _isDisposed = true;
  128. }
  129. private void InternalStart()
  130. {
  131. InitializePendingChannelCountdown();
  132. var ip = IPAddress.Any;
  133. if (!string.IsNullOrEmpty(BoundHost))
  134. {
  135. ip = Dns.GetHostAddresses(BoundHost)[0];
  136. }
  137. var ep = new IPEndPoint(ip, (int)BoundPort);
  138. _listener = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
  139. _listener.Bind(ep);
  140. _listener.Listen(5);
  141. Session.ErrorOccured += Session_ErrorOccured;
  142. Session.Disconnected += Session_Disconnected;
  143. // consider port started when we're listening for inbound connections
  144. _status = ForwardedPortStatus.Started;
  145. StartAccept(e: null);
  146. }
  147. private void StartAccept(SocketAsyncEventArgs e)
  148. {
  149. if (e is null)
  150. {
  151. #pragma warning disable CA2000 // Dispose objects before losing scope
  152. e = new SocketAsyncEventArgs();
  153. #pragma warning restore CA2000 // Dispose objects before losing scope
  154. e.Completed += AcceptCompleted;
  155. }
  156. else
  157. {
  158. // clear the socket as we're reusing the context object
  159. e.AcceptSocket = null;
  160. }
  161. // only accept new connections while we are started
  162. if (IsStarted)
  163. {
  164. try
  165. {
  166. if (!_listener.AcceptAsync(e))
  167. {
  168. AcceptCompleted(sender: null, e);
  169. }
  170. }
  171. catch (ObjectDisposedException)
  172. {
  173. if (_status == ForwardedPortStatus.Stopping || _status == ForwardedPortStatus.Stopped)
  174. {
  175. // ignore ObjectDisposedException while stopping or stopped
  176. return;
  177. }
  178. throw;
  179. }
  180. }
  181. }
  182. private void AcceptCompleted(object sender, SocketAsyncEventArgs e)
  183. {
  184. if (e.SocketError is SocketError.OperationAborted or SocketError.NotSocket)
  185. {
  186. // server was stopped
  187. return;
  188. }
  189. // capture client socket
  190. var clientSocket = e.AcceptSocket;
  191. if (e.SocketError != SocketError.Success)
  192. {
  193. // accept new connection
  194. StartAccept(e);
  195. // dispose broken client socket
  196. CloseClientSocket(clientSocket);
  197. return;
  198. }
  199. // accept new connection
  200. StartAccept(e);
  201. // process connection
  202. ProcessAccept(clientSocket);
  203. }
  204. private void ProcessAccept(Socket clientSocket)
  205. {
  206. // close the client socket if we're no longer accepting new connections
  207. if (!IsStarted)
  208. {
  209. CloseClientSocket(clientSocket);
  210. return;
  211. }
  212. // capture the countdown event that we're adding a count to, as we need to make sure that we'll be signaling
  213. // that same instance; the instance field for the countdown event is re-initialized when the port is restarted
  214. // and at that time there may still be pending requests
  215. var pendingChannelCountdown = _pendingChannelCountdown;
  216. pendingChannelCountdown.AddCount();
  217. try
  218. {
  219. using (var channel = Session.CreateChannelDirectTcpip())
  220. {
  221. channel.Exception += Channel_Exception;
  222. if (!HandleSocks(channel, clientSocket, Session.ConnectionInfo.Timeout))
  223. {
  224. CloseClientSocket(clientSocket);
  225. return;
  226. }
  227. // start receiving from client socket (and sending to server)
  228. channel.Bind();
  229. }
  230. }
  231. catch (Exception exp)
  232. {
  233. RaiseExceptionEvent(exp);
  234. CloseClientSocket(clientSocket);
  235. }
  236. finally
  237. {
  238. // take into account that CountdownEvent has since been disposed; when stopping the port we
  239. // wait for a given time for the channels to close, but once that timeout period has elapsed
  240. // the CountdownEvent will be disposed
  241. try
  242. {
  243. _ = pendingChannelCountdown.Signal();
  244. }
  245. catch (ObjectDisposedException)
  246. {
  247. // Ignore any ObjectDisposedException
  248. }
  249. }
  250. }
  251. /// <summary>
  252. /// Initializes the <see cref="CountdownEvent"/>.
  253. /// </summary>
  254. /// <remarks>
  255. /// <para>
  256. /// When the port is started for the first time, a <see cref="CountdownEvent"/> is created with an initial count
  257. /// of <c>1</c>.
  258. /// </para>
  259. /// <para>
  260. /// On subsequent (re)starts, we'll dispose the current <see cref="CountdownEvent"/> and create a new one with
  261. /// initial count of <c>1</c>.
  262. /// </para>
  263. /// </remarks>
  264. private void InitializePendingChannelCountdown()
  265. {
  266. var original = Interlocked.Exchange(ref _pendingChannelCountdown, new CountdownEvent(1));
  267. original?.Dispose();
  268. }
  269. private bool HandleSocks(IChannelDirectTcpip channel, Socket clientSocket, TimeSpan timeout)
  270. {
  271. Closing += closeClientSocket;
  272. try
  273. {
  274. var version = SocketAbstraction.ReadByte(clientSocket, timeout);
  275. switch (version)
  276. {
  277. case -1:
  278. // SOCKS client closed connection
  279. return false;
  280. case 4:
  281. return HandleSocks4(clientSocket, channel, timeout);
  282. case 5:
  283. return HandleSocks5(clientSocket, channel, timeout);
  284. default:
  285. throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "SOCKS version {0} is not supported.", version));
  286. }
  287. }
  288. catch (SocketException ex)
  289. {
  290. // ignore exception thrown by interrupting the blocking receive as part of closing
  291. // the forwarded port
  292. #if NETFRAMEWORK
  293. if (ex.SocketErrorCode != SocketError.Interrupted)
  294. {
  295. RaiseExceptionEvent(ex);
  296. }
  297. #else
  298. // Since .NET 5 the exception has been changed.
  299. // more info https://github.com/dotnet/runtime/issues/41585
  300. if (ex.SocketErrorCode != SocketError.ConnectionAborted)
  301. {
  302. RaiseExceptionEvent(ex);
  303. }
  304. #endif
  305. return false;
  306. }
  307. finally
  308. {
  309. // interrupt of blocking receive is now handled by channel (SOCKS4 and SOCKS5)
  310. // or no longer necessary
  311. Closing -= closeClientSocket;
  312. }
  313. #pragma warning disable SA1300 // Element should begin with upper-case letter
  314. void closeClientSocket(object sender, EventArgs args)
  315. {
  316. CloseClientSocket(clientSocket);
  317. }
  318. #pragma warning restore SA1300 // Element should begin with upper-case letter
  319. }
  320. private static void CloseClientSocket(Socket clientSocket)
  321. {
  322. if (clientSocket.Connected)
  323. {
  324. try
  325. {
  326. clientSocket.Shutdown(SocketShutdown.Send);
  327. }
  328. catch (Exception)
  329. {
  330. // ignore exception when client socket was already closed
  331. }
  332. }
  333. clientSocket.Dispose();
  334. }
  335. /// <summary>
  336. /// Interrupts the listener, and unsubscribes from <see cref="Session"/> events.
  337. /// </summary>
  338. private void StopListener()
  339. {
  340. // close listener socket
  341. _listener?.Dispose();
  342. // unsubscribe from session events
  343. var session = Session;
  344. if (session is not null)
  345. {
  346. session.ErrorOccured -= Session_ErrorOccured;
  347. session.Disconnected -= Session_Disconnected;
  348. }
  349. }
  350. /// <summary>
  351. /// Waits for pending channels to close.
  352. /// </summary>
  353. /// <param name="timeout">The maximum time to wait for the pending channels to close.</param>
  354. private void InternalStop(TimeSpan timeout)
  355. {
  356. _ = _pendingChannelCountdown.Signal();
  357. if (!_pendingChannelCountdown.Wait(timeout))
  358. {
  359. // TODO: log as warning
  360. DiagnosticAbstraction.Log("Timeout waiting for pending channels in dynamic forwarded port to close.");
  361. }
  362. }
  363. private void InternalDispose(bool disposing)
  364. {
  365. if (disposing)
  366. {
  367. var listener = _listener;
  368. if (listener is not null)
  369. {
  370. _listener = null;
  371. listener.Dispose();
  372. }
  373. var pendingRequestsCountdown = _pendingChannelCountdown;
  374. if (pendingRequestsCountdown is not null)
  375. {
  376. _pendingChannelCountdown = null;
  377. pendingRequestsCountdown.Dispose();
  378. }
  379. }
  380. }
  381. private void Session_Disconnected(object sender, EventArgs e)
  382. {
  383. var session = Session;
  384. if (session is not null)
  385. {
  386. StopPort(session.ConnectionInfo.Timeout);
  387. }
  388. }
  389. private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
  390. {
  391. var session = Session;
  392. if (session is not null)
  393. {
  394. StopPort(session.ConnectionInfo.Timeout);
  395. }
  396. }
  397. private void Channel_Exception(object sender, ExceptionEventArgs e)
  398. {
  399. RaiseExceptionEvent(e.Exception);
  400. }
  401. private bool HandleSocks4(Socket socket, IChannelDirectTcpip channel, TimeSpan timeout)
  402. {
  403. var commandCode = SocketAbstraction.ReadByte(socket, timeout);
  404. if (commandCode == -1)
  405. {
  406. // SOCKS client closed connection
  407. return false;
  408. }
  409. var portBuffer = new byte[2];
  410. if (SocketAbstraction.Read(socket, portBuffer, 0, portBuffer.Length, timeout) == 0)
  411. {
  412. // SOCKS client closed connection
  413. return false;
  414. }
  415. var port = BinaryPrimitives.ReadUInt16BigEndian(portBuffer);
  416. var ipBuffer = new byte[4];
  417. if (SocketAbstraction.Read(socket, ipBuffer, 0, ipBuffer.Length, timeout) == 0)
  418. {
  419. // SOCKS client closed connection
  420. return false;
  421. }
  422. var ipAddress = new IPAddress(ipBuffer);
  423. var username = ReadString(socket, timeout);
  424. if (username is null)
  425. {
  426. // SOCKS client closed connection
  427. return false;
  428. }
  429. var host = ipAddress.ToString();
  430. RaiseRequestReceived(host, port);
  431. channel.Open(host, port, this, socket);
  432. SocketAbstraction.SendByte(socket, 0x00);
  433. if (channel.IsOpen)
  434. {
  435. SocketAbstraction.SendByte(socket, 0x5a);
  436. SocketAbstraction.Send(socket, portBuffer, 0, portBuffer.Length);
  437. SocketAbstraction.Send(socket, ipBuffer, 0, ipBuffer.Length);
  438. return true;
  439. }
  440. // signal that request was rejected or failed
  441. SocketAbstraction.SendByte(socket, 0x5b);
  442. return false;
  443. }
  444. private bool HandleSocks5(Socket socket, IChannelDirectTcpip channel, TimeSpan timeout)
  445. {
  446. var authenticationMethodsCount = SocketAbstraction.ReadByte(socket, timeout);
  447. if (authenticationMethodsCount == -1)
  448. {
  449. // SOCKS client closed connection
  450. return false;
  451. }
  452. var authenticationMethods = new byte[authenticationMethodsCount];
  453. if (SocketAbstraction.Read(socket, authenticationMethods, 0, authenticationMethods.Length, timeout) == 0)
  454. {
  455. // SOCKS client closed connection
  456. return false;
  457. }
  458. if (authenticationMethods.Min() == 0)
  459. {
  460. // no user authentication is one of the authentication methods supported
  461. // by the SOCKS client
  462. SocketAbstraction.Send(socket, new byte[] { 0x05, 0x00 }, 0, 2);
  463. }
  464. else
  465. {
  466. // the SOCKS client requires authentication, which we currently do not support
  467. SocketAbstraction.Send(socket, new byte[] { 0x05, 0xFF }, 0, 2);
  468. // we continue business as usual but expect the client to close the connection
  469. // so one of the subsequent reads should return -1 signaling that the client
  470. // has effectively closed the connection
  471. }
  472. var version = SocketAbstraction.ReadByte(socket, timeout);
  473. if (version == -1)
  474. {
  475. // SOCKS client closed connection
  476. return false;
  477. }
  478. if (version != 5)
  479. {
  480. throw new ProxyException("SOCKS5: Version 5 is expected.");
  481. }
  482. var commandCode = SocketAbstraction.ReadByte(socket, timeout);
  483. if (commandCode == -1)
  484. {
  485. // SOCKS client closed connection
  486. return false;
  487. }
  488. var reserved = SocketAbstraction.ReadByte(socket, timeout);
  489. if (reserved == -1)
  490. {
  491. // SOCKS client closed connection
  492. return false;
  493. }
  494. if (reserved != 0)
  495. {
  496. throw new ProxyException("SOCKS5: 0 is expected for reserved byte.");
  497. }
  498. var addressType = SocketAbstraction.ReadByte(socket, timeout);
  499. if (addressType == -1)
  500. {
  501. // SOCKS client closed connection
  502. return false;
  503. }
  504. var host = GetSocks5Host(addressType, socket, timeout);
  505. if (host is null)
  506. {
  507. // SOCKS client closed connection
  508. return false;
  509. }
  510. var portBuffer = new byte[2];
  511. if (SocketAbstraction.Read(socket, portBuffer, 0, portBuffer.Length, timeout) == 0)
  512. {
  513. // SOCKS client closed connection
  514. return false;
  515. }
  516. var port = BinaryPrimitives.ReadUInt16BigEndian(portBuffer);
  517. RaiseRequestReceived(host, port);
  518. channel.Open(host, port, this, socket);
  519. var socksReply = CreateSocks5Reply(channel.IsOpen);
  520. SocketAbstraction.Send(socket, socksReply, 0, socksReply.Length);
  521. return true;
  522. }
  523. private static string GetSocks5Host(int addressType, Socket socket, TimeSpan timeout)
  524. {
  525. switch (addressType)
  526. {
  527. case 0x01: // IPv4
  528. {
  529. var addressBuffer = new byte[4];
  530. if (SocketAbstraction.Read(socket, addressBuffer, 0, 4, timeout) == 0)
  531. {
  532. // SOCKS client closed connection
  533. return null;
  534. }
  535. var ipv4 = new IPAddress(addressBuffer);
  536. return ipv4.ToString();
  537. }
  538. case 0x03: // Domain name
  539. {
  540. var length = SocketAbstraction.ReadByte(socket, timeout);
  541. if (length == -1)
  542. {
  543. // SOCKS client closed connection
  544. return null;
  545. }
  546. var addressBuffer = new byte[length];
  547. if (SocketAbstraction.Read(socket, addressBuffer, 0, addressBuffer.Length, timeout) == 0)
  548. {
  549. // SOCKS client closed connection
  550. return null;
  551. }
  552. var hostName = SshData.Ascii.GetString(addressBuffer, 0, addressBuffer.Length);
  553. return hostName;
  554. }
  555. case 0x04: // IPv6
  556. {
  557. var addressBuffer = new byte[16];
  558. if (SocketAbstraction.Read(socket, addressBuffer, 0, 16, timeout) == 0)
  559. {
  560. // SOCKS client closed connection
  561. return null;
  562. }
  563. var ipv6 = new IPAddress(addressBuffer);
  564. return ipv6.ToString();
  565. }
  566. default:
  567. throw new ProxyException(string.Format(CultureInfo.InvariantCulture, "SOCKS5: Address type '{0}' is not supported.", addressType));
  568. }
  569. }
  570. private static byte[] CreateSocks5Reply(bool channelOpen)
  571. {
  572. var socksReply = new byte[// SOCKS version
  573. 1 +
  574. // Reply field
  575. 1 +
  576. // Reserved; fixed: 0x00
  577. 1 +
  578. // Address type; fixed: 0x01
  579. 1 +
  580. // IPv4 server bound address; fixed: {0x00, 0x00, 0x00, 0x00}
  581. 4 +
  582. // server bound port; fixed: {0x00, 0x00}
  583. 2];
  584. socksReply[0] = 0x05;
  585. if (channelOpen)
  586. {
  587. socksReply[1] = 0x00; // succeeded
  588. }
  589. else
  590. {
  591. socksReply[1] = 0x01; // general SOCKS server failure
  592. }
  593. // reserved
  594. socksReply[2] = 0x00;
  595. // IPv4 address type
  596. socksReply[3] = 0x01;
  597. return socksReply;
  598. }
  599. /// <summary>
  600. /// Reads a null terminated string from a socket.
  601. /// </summary>
  602. /// <param name="socket">The <see cref="Socket"/> to read from.</param>
  603. /// <param name="timeout">The timeout to apply to individual reads.</param>
  604. /// <returns>
  605. /// The <see cref="string"/> read, or <see langword="null"/> when the socket was closed.
  606. /// </returns>
  607. private static string ReadString(Socket socket, TimeSpan timeout)
  608. {
  609. var text = new StringBuilder();
  610. var buffer = new byte[1];
  611. while (true)
  612. {
  613. if (SocketAbstraction.Read(socket, buffer, 0, 1, timeout) == 0)
  614. {
  615. // SOCKS client closed connection
  616. return null;
  617. }
  618. var byteRead = buffer[0];
  619. if (byteRead == 0)
  620. {
  621. // end of the string
  622. break;
  623. }
  624. _ = text.Append((char)byteRead);
  625. }
  626. return text.ToString();
  627. }
  628. /// <summary>
  629. /// Finalizes an instance of the <see cref="ForwardedPortDynamic"/> class.
  630. /// </summary>
  631. ~ForwardedPortDynamic()
  632. {
  633. Dispose(disposing: false);
  634. }
  635. }
  636. }