mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-10-17 05:21:15 +08:00
Fix channel open/close/failed synchronization, make sure channel returns correct results, minor clean up
This commit is contained in:
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user