<PackageReference Include="SSH.NET" Version="2020.0.0" />

ForwardedPortRemote

Provides functionality for remote port forwarding
using Renci.SshNet.Abstractions; using Renci.SshNet.Channels; using Renci.SshNet.Common; using Renci.SshNet.Messages.Connection; using System; using System.Globalization; using System.Net; using System.Threading; namespace Renci.SshNet { public class ForwardedPortRemote : ForwardedPort, IDisposable { private ForwardedPortStatus _status; private bool _requestStatus; private EventWaitHandle _globalRequestResponse = new AutoResetEvent(false); private Renci.SshNet.Common.CountdownEvent _pendingChannelCountdown; private bool _isDisposed; public override bool IsStarted => _status == ForwardedPortStatus.Started; public IPAddress BoundHostAddress { get; set; } public string BoundHost => BoundHostAddress.ToString(); public uint BoundPort { get; set; } public IPAddress HostAddress { get; set; } public string Host => HostAddress.ToString(); public uint Port { get; set; } public ForwardedPortRemote(IPAddress boundHostAddress, uint boundPort, IPAddress hostAddress, uint port) { if (boundHostAddress == null) throw new ArgumentNullException("boundHostAddress"); if (hostAddress == null) throw new ArgumentNullException("hostAddress"); boundPort.ValidatePort("boundPort"); port.ValidatePort("port"); BoundHostAddress = boundHostAddress; BoundPort = boundPort; HostAddress = hostAddress; Port = port; _status = ForwardedPortStatus.Stopped; } public ForwardedPortRemote(uint boundPort, string host, uint port) : this(string.Empty, boundPort, host, port) { } public ForwardedPortRemote(string boundHost, uint boundPort, string host, uint port) : this(DnsAbstraction.GetHostAddresses(boundHost)[0], boundPort, DnsAbstraction.GetHostAddresses(host)[0], port) { } protected override void StartPort() { if (ForwardedPortStatus.ToStarting(ref _status)) { InitializePendingChannelCountdown(); try { base.Session.RegisterMessage("SSH_MSG_REQUEST_FAILURE"); base.Session.RegisterMessage("SSH_MSG_REQUEST_SUCCESS"); base.Session.RegisterMessage("SSH_MSG_CHANNEL_OPEN"); base.Session.RequestSuccessReceived += Session_RequestSuccess; base.Session.RequestFailureReceived += Session_RequestFailure; base.Session.ChannelOpenReceived += Session_ChannelOpening; base.Session.SendMessage(new TcpIpForwardGlobalRequestMessage(BoundHost, BoundPort)); base.Session.WaitOnHandle(_globalRequestResponse); if (!_requestStatus) throw new SshException(string.Format(CultureInfo.CurrentCulture, "Port forwarding for '{0}' port '{1}' failed to start.", new object[2] { Host, Port })); } catch (Exception) { _status = ForwardedPortStatus.Stopped; base.Session.RequestSuccessReceived -= Session_RequestSuccess; base.Session.RequestFailureReceived -= Session_RequestFailure; base.Session.ChannelOpenReceived -= Session_ChannelOpening; throw; } _status = ForwardedPortStatus.Started; } } protected override void StopPort(TimeSpan timeout) { if (ForwardedPortStatus.ToStopping(ref _status)) { base.StopPort(timeout); base.Session.SendMessage(new CancelTcpIpForwardGlobalRequestMessage(BoundHost, BoundPort)); WaitHandle.WaitAny(new WaitHandle[2] { _globalRequestResponse, base.Session.MessageListenerCompleted }, timeout); base.Session.RequestSuccessReceived -= Session_RequestSuccess; base.Session.RequestFailureReceived -= Session_RequestFailure; base.Session.ChannelOpenReceived -= Session_ChannelOpening; _pendingChannelCountdown.Signal(); _pendingChannelCountdown.Wait(timeout); _status = ForwardedPortStatus.Stopped; } } protected override void CheckDisposed() { if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); } private void Session_ChannelOpening(object sender, MessageEventArgs<ChannelOpenMessage> e) { ChannelOpenMessage channelOpenMessage = e.Message; ForwardedTcpipChannelInfo info = channelOpenMessage.Info as ForwardedTcpipChannelInfo; if (info != null && info.ConnectedAddress == BoundHost && info.ConnectedPort == BoundPort) { if (!IsStarted) base.Session.SendMessage(new ChannelOpenFailureMessage(channelOpenMessage.LocalChannelNumber, "", 1)); else ThreadAbstraction.ExecuteThread(delegate { Renci.SshNet.Common.CountdownEvent pendingChannelCountdown = _pendingChannelCountdown; pendingChannelCountdown.AddCount(); try { RaiseRequestReceived(info.OriginatorAddress, info.OriginatorPort); using (IChannelForwardedTcpip channelForwardedTcpip = base.Session.CreateChannelForwardedTcpip(channelOpenMessage.LocalChannelNumber, channelOpenMessage.InitialWindowSize, channelOpenMessage.MaximumPacketSize)) { channelForwardedTcpip.Exception += Channel_Exception; channelForwardedTcpip.Bind(new IPEndPoint(HostAddress, (int)Port), this); } } catch (Exception exception) { RaiseExceptionEvent(exception); } finally { try { pendingChannelCountdown.Signal(); } catch (ObjectDisposedException) { } } }); } } private void InitializePendingChannelCountdown() { Interlocked.Exchange(ref _pendingChannelCountdown, new Renci.SshNet.Common.CountdownEvent(1))?.Dispose(); } private void Channel_Exception(object sender, ExceptionEventArgs exceptionEventArgs) { RaiseExceptionEvent(exceptionEventArgs.Exception); } private void Session_RequestFailure(object sender, EventArgs e) { _requestStatus = false; _globalRequestResponse.Set(); } private void Session_RequestSuccess(object sender, MessageEventArgs<RequestSuccessMessage> e) { _requestStatus = true; if (BoundPort == 0) BoundPort = (e.Message.BoundPort.HasValue ? e.Message.BoundPort.Value : 0); _globalRequestResponse.Set(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected override void Dispose(bool disposing) { if (!_isDisposed) { base.Dispose(disposing); if (disposing) { ISession session = base.Session; if (session != null) { base.Session = null; session.RequestSuccessReceived -= Session_RequestSuccess; session.RequestFailureReceived -= Session_RequestFailure; session.ChannelOpenReceived -= Session_ChannelOpening; } EventWaitHandle globalRequestResponse = _globalRequestResponse; if (globalRequestResponse != null) { _globalRequestResponse = null; globalRequestResponse.Dispose(); } Renci.SshNet.Common.CountdownEvent pendingChannelCountdown = _pendingChannelCountdown; if (pendingChannelCountdown != null) { _pendingChannelCountdown = null; pendingChannelCountdown.Dispose(); } } _isDisposed = true; } } ~ForwardedPortRemote() { Dispose(false); } } }