Fix channel open/close/failed synchronization, make sure channel returns correct results, minor clean up

This commit is contained in:
olegkap_cp
2010-08-26 20:46:04 +00:00
parent 3e19ae132e
commit f866e2f187
6 changed files with 159 additions and 142 deletions
@@ -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;
/// <summary>
/// Counts faile channel open attempts
/// </summary>
private int _failedOpenAttempts;
/// <summary>
/// Indicates weither close channel message was sent
/// </summary>
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<ChannelEventArgs> Opened;
public event EventHandler<ChannelEventArgs> Closed;
public event EventHandler<ChannelEventArgs> 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>(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
@@ -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;
}
}
}
@@ -6,8 +6,6 @@ namespace Renci.SshClient.Common
{
public class BlockingStack<T> : IEnumerable<T>, ICollection, IEnumerable
{
//private EventWaitHandle _updated = new AutoResetEvent(false);
private Stack<T> _stack;
public BlockingStack()
@@ -18,7 +16,6 @@ namespace Renci.SshClient.Common
public BlockingStack(IEnumerable<T> collection)
{
this._stack = new Stack<T>(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);
}
}
@@ -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;
}
}
@@ -61,6 +61,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Channels\ChannelAsyncResult.cs" />
<Compile Include="Channels\ChannelEventArgs.cs" />
<Compile Include="Common\BlockingStack.cs" />
<Compile Include="Security\Algorithm.cs" />
<Compile Include="Security\Cipher.cs" />
+51 -94
View File
@@ -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
/// <summary>
/// Keeps track of all open channels
/// </summary>
private IDictionary<uint, uint> _openChannels = new Dictionary<uint, uint>();
private Dictionary<uint, Channel> _openChannels = new Dictionary<uint, Channel>();
/// <summary>
/// Exception that need to be thrown by waiting thread
@@ -88,11 +87,6 @@ namespace Renci.SshClient
/// </summary>
private bool _isAuthenticated;
/// <summary>
/// Specifies weither socket is connected or not
/// </summary>
private bool _isSocketConnected;
/// <summary>
/// holds number to be used for session channels
/// </summary>
@@ -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<Type, Func<Session, uint, Channel>> _channels = new Dictionary<Type, Func<Session, uint, Channel>>()
@@ -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
/// <param name="message">The message.</param>
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<byte>(this.Encrypt(packetData));
encryptedData = new List<byte>(this.ClientCipher.Encrypt(packetData));
}
@@ -461,14 +466,13 @@ namespace Renci.SshClient
/// <returns></returns>
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<byte> 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<byte>(this.Decrypt(data));
decryptedData = new List<byte>(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>(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
/// <summary>
/// Disconnects client from the server with specified reason code.
/// </summary>
@@ -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
/// <param name="data">The data.</param>
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);
}
/// <summary>
@@ -787,21 +800,12 @@ namespace Renci.SshClient
/// </summary>
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;
}
/// <summary>
@@ -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
}
}
/// <summary>
/// Handles <see cref="ChannelOpenConfirmationMessage"/> message.
/// </summary>
/// <param name="message">The message.</param>
private void HandleMessage(ChannelOpenConfirmationMessage message)
{
// Keep track of open channels
lock (this._openChannels)
{
this._openChannels.Add(message.ChannelNumber, message.ServerChannelNumber);
}
}
/// <summary>
/// Handles <see cref="ChannelCloseMessage"/> message.
/// </summary>
/// <param name="message">The message.</param>
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
}
}