diff --git a/Renci.SshClient/Renci.SshClient/Channels/Channel.cs b/Renci.SshClient/Renci.SshClient/Channels/Channel.cs index 8b01b4c9..9a514df9 100644 --- a/Renci.SshClient/Renci.SshClient/Channels/Channel.cs +++ b/Renci.SshClient/Renci.SshClient/Channels/Channel.cs @@ -1,6 +1,5 @@ using System; using System.Threading; -using Renci.SshClient.Common; using Renci.SshClient.Messages; using Renci.SshClient.Messages.Connection; @@ -16,20 +15,34 @@ namespace Renci.SshClient.Channels private uint _maximumPacketSize = 0x4000; - private int _chanelOpenFailedAttempts = 0; + /// + /// Counts faile channel open attempts + /// + private int _failedOpenAttempts; + + /// + /// Indicates weither close channel message was sent + /// + private bool _closeMessageSent = false; public abstract ChannelTypes ChannelType { get; } - public uint ClientChannelNumber { get; set; } + public uint ClientChannelNumber { get; private set; } - public uint ServerChannelNumber { get; set; } + public uint ServerChannelNumber { get; private set; } - public uint WindowSize { get; set; } + public uint WindowSize { get; private set; } - public uint PacketSize { get; set; } + public uint PacketSize { get; private set; } public bool IsOpen { get; private set; } + public event EventHandler Opened; + + public event EventHandler Closed; + + public event EventHandler OpenFailed; + protected Session Session { get; private set; } public Channel(Session session, uint channelId, uint windowSize, uint packetSize) @@ -61,13 +74,12 @@ namespace Renci.SshClient.Channels public virtual void Open() { - this.Session.MessageReceived += SessionInfo_MessageReceived; - // Open session channel if (!this.IsOpen) { this.SendChannelOpenMessage(); this.Session.WaitHandle(this._channelOpenWaitHandle); + // TODO: Throw exception if channel could not be open, this can happend after sever failed open attempts } } @@ -75,10 +87,7 @@ namespace Renci.SshClient.Channels { if (this.IsOpen) { - this.SendMessage(new ChannelCloseMessage - { - ChannelNumber = this.ServerChannelNumber, - }); + this.SendChannelCloseMessage(); // Wait for channel to be closed this.Session.WaitHandle(this._channelClosedWaitHandle); @@ -87,6 +96,11 @@ namespace Renci.SshClient.Channels this.CloseCleanup(); } + internal void HandleChannelMessage(ChannelMessage message) + { + this.HandleMessage((dynamic)message); + } + protected virtual void OnChannelData(string data) { } @@ -105,7 +119,6 @@ namespace Renci.SshClient.Channels protected virtual void OnChannelClose() { - // TODO: Channel is closed when it sent and received Close message } protected virtual void OnChannelFailed(uint reasonCode, string description) @@ -117,17 +130,6 @@ namespace Renci.SshClient.Channels this.Session.SendMessage(message); } - private void SessionInfo_MessageReceived(object sender, MessageReceivedEventArgs e) - { - ChannelMessage message = e.Message as ChannelMessage; - - // Handle only messages belong to this channel or channel open confirmation - if (message.ChannelNumber == this.ClientChannelNumber || e.Message is ChannelOpenConfirmationMessage) - { - this.HandleMessage((dynamic)e.Message); - } - } - #region Message handlers private void HandleMessage(T message) where T : Message @@ -137,30 +139,33 @@ namespace Renci.SshClient.Channels private void HandleMessage(ChannelOpenConfirmationMessage message) { - // Make sure we open channel only for requested channel number - if (this.ClientChannelNumber != message.ChannelNumber) - return; - this.ServerChannelNumber = message.ServerChannelNumber; - this.IsOpen = true; this.WindowSize = message.InitialWindowSize; this.PacketSize = message.MaximumPacketSize; + + this.IsOpen = true; + + this.RaiseOpened(); + this._channelOpenWaitHandle.Set(); } private void HandleMessage(ChannelOpenFailureMessage message) { - if (_chanelOpenFailedAttempts > this.Session.ConnectionInfo.RetryAttempts) + if (this._failedOpenAttempts < this.Session.ConnectionInfo.RetryAttempts) { - this.IsOpen = false; - - this._channelOpenWaitHandle.Set(); - this.OnChannelFailed(message.ReasonCode, message.Description); + this.SendChannelOpenMessage(); + this._failedOpenAttempts++; } else { - // Send channel open message again - this.SendChannelOpenMessage(); + this.RaiseOpenFailed(); + + this.CloseCleanup(); + + this.OnChannelFailed(message.ReasonCode, message.Description); + + this._channelOpenWaitHandle.Set(); } } @@ -215,6 +220,10 @@ namespace Renci.SshClient.Channels private void HandleMessage(ChannelCloseMessage message) { + this.OnChannelClose(); + + this.RaiseClosed(); + this.CloseCleanup(); this._channelClosedWaitHandle.Set(); @@ -238,7 +247,31 @@ namespace Renci.SshClient.Channels #endregion - private void SendChannelOpenMessage() + private void RaiseOpened() + { + if (this.Opened != null) + { + this.Opened(this, new ChannelEventArgs(this.ClientChannelNumber)); + } + } + + private void RaiseClosed() + { + if (this.Closed != null) + { + this.Closed(this, new ChannelEventArgs(this.ClientChannelNumber)); + } + } + + private void RaiseOpenFailed() + { + if (this.OpenFailed != null) + { + this.OpenFailed(this, new ChannelEventArgs(this.ClientChannelNumber)); + } + } + + internal void SendChannelOpenMessage() { this.SendMessage(new ChannelOpenMessage { @@ -249,14 +282,31 @@ namespace Renci.SshClient.Channels }); } + private void SendChannelCloseMessage() + { + if (this._closeMessageSent) + return; + + lock (this) + { + if (this._closeMessageSent) + return; + + this.SendMessage(new ChannelCloseMessage + { + ChannelNumber = this.ServerChannelNumber, + }); + this._closeMessageSent = true; + } + } + private void CloseCleanup() { - if (this.IsOpen) - { - this.IsOpen = false; + if (!this.IsOpen) + return; - this.Session.MessageReceived -= SessionInfo_MessageReceived; - } + this.IsOpen = false; + this.SendChannelCloseMessage(); } #region IDisposable Members diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelEventArgs.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelEventArgs.cs new file mode 100644 index 00000000..b7d7fc01 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Renci.SshClient.Channels +{ + internal class ChannelEventArgs : EventArgs + { + public uint ChannelNumber { get; private set; } + + public ChannelEventArgs(uint channelNumber) + { + this.ChannelNumber = channelNumber; + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Common/BlockingStack.cs b/Renci.SshClient/Renci.SshClient/Common/BlockingStack.cs index c1938a19..b9b195fc 100644 --- a/Renci.SshClient/Renci.SshClient/Common/BlockingStack.cs +++ b/Renci.SshClient/Renci.SshClient/Common/BlockingStack.cs @@ -6,8 +6,6 @@ namespace Renci.SshClient.Common { public class BlockingStack : IEnumerable, ICollection, IEnumerable { - //private EventWaitHandle _updated = new AutoResetEvent(false); - private Stack _stack; public BlockingStack() @@ -18,7 +16,6 @@ namespace Renci.SshClient.Common public BlockingStack(IEnumerable collection) { this._stack = new Stack(collection); - //this._updated.Set(); } public BlockingStack(int capacity) @@ -85,7 +82,6 @@ namespace Renci.SshClient.Common while (this._stack.Count == 0) { Monitor.Wait(this); - //this._updated.WaitOne(); } var result = this._stack.Pop(); Monitor.PulseAll(this); @@ -98,7 +94,6 @@ namespace Renci.SshClient.Common lock (this) { this._stack.Push(item); - //this._updated.Set(); Monitor.PulseAll(this); } } diff --git a/Renci.SshClient/Renci.SshClient/ConnectionInfo.cs b/Renci.SshClient/Renci.SshClient/ConnectionInfo.cs index f1dcdbcf..b4bf2090 100644 --- a/Renci.SshClient/Renci.SshClient/ConnectionInfo.cs +++ b/Renci.SshClient/Renci.SshClient/ConnectionInfo.cs @@ -23,7 +23,7 @@ // Set default connection values this.Port = 22; this.Timeout = 1000 * 10; // Set default timeout to 10 sec - this.RetryAttempts = 3; + this.RetryAttempts = 10; this.MaxSessions = 10; } } diff --git a/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj b/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj index 9d3db1bd..1bb4cc31 100644 --- a/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj +++ b/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj @@ -61,6 +61,7 @@ + diff --git a/Renci.SshClient/Renci.SshClient/Session.cs b/Renci.SshClient/Renci.SshClient/Session.cs index 3422799f..4ebafd2b 100644 --- a/Renci.SshClient/Renci.SshClient/Session.cs +++ b/Renci.SshClient/Renci.SshClient/Session.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Net; @@ -76,7 +75,7 @@ namespace Renci.SshClient /// /// Keeps track of all open channels /// - private IDictionary _openChannels = new Dictionary(); + private Dictionary _openChannels = new Dictionary(); /// /// Exception that need to be thrown by waiting thread @@ -88,11 +87,6 @@ namespace Renci.SshClient /// private bool _isAuthenticated; - /// - /// Specifies weither socket is connected or not - /// - private bool _isSocketConnected; - /// /// holds number to be used for session channels /// @@ -149,7 +143,7 @@ namespace Renci.SshClient { get { - return this._isSocketConnected && this._isAuthenticated; + return this._socket != null && this._socket.Connected && this._isAuthenticated; } } @@ -180,7 +174,10 @@ namespace Renci.SshClient this.ClientVersion = string.Format("SSH-2.0-Renci.SshClient.{0}", this.GetType().Assembly.GetName().Version); // Prepopulate chanel numbers that will be used - System.Threading.Tasks.Parallel.For(0, connectionInfo.MaxSessions, (i) => { this._channelNumbers.Push((uint)i); }); + for (int i = connectionInfo.MaxSessions - 1; i > 0; i--) + { + this._channelNumbers.Push((uint)i - 1 + 100); + } } private static IDictionary> _channels = new Dictionary>() @@ -202,7 +199,20 @@ namespace Renci.SshClient throw new InvalidOperationException("Not connected"); } - return _channels[typeof(T)](this, this._channelNumbers.WaitAndPop()) as T; + T channel; + var clientChannelId = this._channelNumbers.WaitAndPop(); + + lock (this._openChannels) + { + channel = _channels[typeof(T)](this, clientChannelId) as T; + + channel.Closed += Channel_Closed; + channel.OpenFailed += Channel_Closed; + + this._openChannels.Add(clientChannelId, channel); + } + + return channel; } public void Connect() @@ -245,10 +255,6 @@ namespace Renci.SshClient this._socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1); - this._isSocketConnected = true; - - //this._socketStream = new NetworkStream(this._socket); - // Get server version from the server, // ignore text lines which are sent before if any using (var ns = new NetworkStream(this._socket)) @@ -384,7 +390,7 @@ namespace Renci.SshClient /// The message. internal virtual void SendMessage(Message message) { - if (!this._isSocketConnected) + if (!this._socket.Connected) return; // TODO: Refactor so we lock only _outboundPacketSequence and _inboundPacketSequence relevant operations @@ -432,7 +438,6 @@ namespace Renci.SshClient var encryptedData = packetData.ToList(); if (this.ClientCipher != null) { - //encryptedData = new List(this.Encrypt(packetData)); encryptedData = new List(this.ClientCipher.Encrypt(packetData)); } @@ -461,14 +466,13 @@ namespace Renci.SshClient /// protected virtual Message ReceiveMessage() { - if (!this._isSocketConnected) + if (!this._socket.Connected) return null; // No lock needed since all messages read by only one thread List decryptedData; - //var blockSize = this.Decryption == null ? (byte)8 : (byte)this.Decryption.InputBlockSize; var blockSize = this.ServerCipher == null ? (byte)8 : (byte)this.ServerCipher.BlockSize; // Read packet lenght first @@ -480,7 +484,6 @@ namespace Renci.SshClient } else { - //decryptedData = new List(this.Decrypt(data)); decryptedData = new List(this.ServerCipher.Decrypt(data)); } @@ -503,7 +506,6 @@ namespace Renci.SshClient } else { - //decryptedData.AddRange(this.Decrypt(data)); decryptedData.AddRange(this.ServerCipher.Decrypt(data)); } bytesToRead -= blockSize; @@ -543,14 +545,16 @@ namespace Renci.SshClient protected virtual void HandleMessage(T message) where T : Message { // Do nothing as message could be proccessed by other module + if (message is ChannelMessage) + { + this.HandleMessage(message as ChannelMessage); + } } #region Handle transport messages protected virtual void HandleMessage(DisconnectMessage message) { - this._isSocketConnected = false; // Connection is terminated after this message - this.DisconnectCleanup(); } @@ -637,6 +641,25 @@ namespace Renci.SshClient #endregion + #region Handle channel messages + + protected virtual void HandleMessage(ChannelMessage message) + { + var channel = this._openChannels[message.ChannelNumber]; + channel.HandleChannelMessage(message); + } + + private void Channel_Closed(object sender, ChannelEventArgs e) + { + lock (this._openChannels) + { + this._openChannels.Remove(e.ChannelNumber); + this._channelNumbers.Push(e.ChannelNumber); + } + } + + #endregion + /// /// Disconnects client from the server with specified reason code. /// @@ -669,11 +692,11 @@ namespace Renci.SshClient while (totalBytesRead < length) { - //int bytesRead = this._socketStream.Read(buffer, 0, length); int bytesRead = this._socket.Receive(buffer, length, SocketFlags.None); if (bytesRead == 0) { + this._socket.Close(); throw new IOException("Unable to read data to the transport connection: An established connection was aborted by the software in your host machine"); } @@ -697,17 +720,7 @@ namespace Renci.SshClient /// The data. protected void Write(byte[] data) { - try - { - //this._socketStream.Write(data, 0, data.Length); - //this._socketStream.Flush(); - this._socket.Send(data); - } - catch (Exception) - { - this._isSocketConnected = false; - throw; - } + this._socket.Send(data); } /// @@ -787,21 +800,12 @@ namespace Renci.SshClient /// private void DisconnectCleanup() { - // Stop running listener thread - - - //if (this._socketStream != null) - // this._socketStream.Close(); - // Close all open channels if any if (this.IsConnected) { - foreach (var channelId in this._openChannels.Values) + foreach (var channel in this._openChannels.Values) { - this.SendMessage(new ChannelCloseMessage - { - ChannelNumber = channelId, - }); + channel.Close(); } } @@ -810,8 +814,6 @@ namespace Renci.SshClient { this._socket.Close(); } - - this._isSocketConnected = false; } /// @@ -821,7 +823,7 @@ namespace Renci.SshClient { try { - while (this._isSocketConnected) + while (this._socket.Connected) { dynamic message = this.ReceiveMessage(); @@ -830,8 +832,6 @@ namespace Renci.SshClient throw new NullReferenceException("The 'message' variable cannot be null"); } - Debug.WriteLine(((Message)message).MessageType); - // Handle session messages first this.HandleMessage(message); @@ -841,7 +841,7 @@ namespace Renci.SshClient } catch (IOException) { - this._isSocketConnected = false; + // Ignore this error since socket was disconected } catch (Exception exp) { @@ -869,43 +869,6 @@ namespace Renci.SshClient } } - /// - /// Handles message. - /// - /// The message. - private void HandleMessage(ChannelOpenConfirmationMessage message) - { - // Keep track of open channels - lock (this._openChannels) - { - this._openChannels.Add(message.ChannelNumber, message.ServerChannelNumber); - } - } - - /// - /// Handles message. - /// - /// The message. - private void HandleMessage(ChannelCloseMessage message) - { - // Keep track of open channels - lock (this._openChannels) - { - this._openChannels.Remove(message.ChannelNumber); - this._channelNumbers.Push(message.ChannelNumber); - } - } - - private void HandleMessage(ChannelFailureMessage message) - { - // Keep track of open channels - lock (this._openChannels) - { - this._openChannels.Remove(message.ChannelNumber); - this._channelNumbers.Push(message.ChannelNumber); - } - } - #region IDisposable Members private bool disposed = false; @@ -926,11 +889,6 @@ namespace Renci.SshClient // and unmanaged resources. if (disposing) { - //if (this._socketStream != null) - //{ - // this._socket.Dispose(); - //} - // Dispose managed resources. if (this._socket != null) { @@ -968,6 +926,5 @@ namespace Renci.SshClient } #endregion - } }