using System; using System.Threading; using Renci.SshNet.Channels; using Renci.SshNet.Messages.Connection; using Renci.SshNet.Common; using System.Diagnostics; using System.Globalization; namespace Renci.SshNet { /// /// Provides functionality for remote port forwarding /// public partial class ForwardedPortRemote : ForwardedPort, IDisposable { private bool _requestStatus; private EventWaitHandle _globalRequestResponse = new AutoResetEvent(false); /// /// Starts remote port forwarding. /// public override void Start() { base.Start(); // If port already started don't start it again if (this.IsStarted) return; this.Session.RegisterMessage("SSH_MSG_REQUEST_FAILURE"); this.Session.RegisterMessage("SSH_MSG_REQUEST_SUCCESS"); this.Session.RegisterMessage("SSH_MSG_CHANNEL_OPEN"); this.Session.RequestSuccessReceived += Session_RequestSuccess; this.Session.RequestFailureReceived += Session_RequestFailure; this.Session.ChannelOpenReceived += Session_ChannelOpening; // Send global request to start direct tcpip this.Session.SendMessage(new GlobalRequestMessage(GlobalRequestName.TcpIpForward, true, this.BoundHost, this.BoundPort)); this.Session.WaitHandle(this._globalRequestResponse); if (!this._requestStatus) { // If request failed don't handle channel opening for this request this.Session.ChannelOpenReceived -= Session_ChannelOpening; throw new SshException(string.Format(CultureInfo.CurrentCulture, "Port forwarding for '{0}' port '{1}' failed to start.", this.Host, this.Port)); } else { this.IsStarted = true; } } /// /// Stops remote port forwarding. /// public override void Stop() { base.Stop(); // If port not started you cant stop it if (!this.IsStarted) return; // Send global request to cancel direct tcpip this.Session.SendMessage(new GlobalRequestMessage(GlobalRequestName.CancelTcpIpForward, true, this.BoundHost, this.BoundPort)); this.Session.WaitHandle(this._globalRequestResponse); this.Session.RequestSuccessReceived -= Session_RequestSuccess; this.Session.RequestFailureReceived -= Session_RequestFailure; this.Session.ChannelOpenReceived -= Session_ChannelOpening; this.IsStarted = false; } private void Session_ChannelOpening(object sender, MessageEventArgs e) { // Ensure that this is corresponding request var info = e.Message.Info as ForwardedTcpipChannelInfo; if (info != null) { if (info.ConnectedAddress == this.BoundHost && info.ConnectedPort == this.BoundPort) { this.ExecuteThread(() => { try { this.RaiseRequestReceived(info.OriginatorAddress, info.OriginatorPort); var channel = this.Session.CreateChannel(e.Message.LocalChannelNumber, e.Message.InitialWindowSize, e.Message.MaximumPacketSize); channel.Bind(this.Host, this.Port); } catch (Exception exp) { this.RaiseExceptionEvent(exp); } }); } } } private void Session_RequestFailure(object sender, System.EventArgs e) { this._requestStatus = false; this._globalRequestResponse.Set(); } private void Session_RequestSuccess(object sender, MessageEventArgs e) { this._requestStatus = true; if (this.BoundPort == 0) { this.BoundPort = (e.Message.BoundPort == null) ? 0 : e.Message.BoundPort.Value; } this._globalRequestResponse.Set(); } partial void ExecuteThread(Action action); #region IDisposable Members private bool _isDisposed = false; /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { // Check to see if Dispose has already been called. if (!this._isDisposed) { // If disposing equals true, dispose all managed // and unmanaged resources. if (disposing) { // Dispose managed resources. if (this._globalRequestResponse != null) { this._globalRequestResponse.Dispose(); this._globalRequestResponse = null; } } // Note disposing has been done. _isDisposed = true; } } /// /// Releases unmanaged resources and performs other cleanup operations before the /// is reclaimed by garbage collection. /// ~ForwardedPortRemote() { // Do not re-create Dispose clean-up code here. // Calling Dispose(false) is optimal in terms of // readability and maintainability. Dispose(false); } #endregion } }