2
0
Эх сурвалжийг харах

Improve internal server window size managment
Fix exception handeling when multiple threads share one channel that can caus application hanging
Improve cipher perfomance
Other fixes

olegkap_cp 12 жил өмнө
parent
commit
8934b5d156

+ 1 - 1
Renci.SshClient/Renci.SshNet.Tests/Classes/Messages/Connection/ChannelExtendedDataMessageTest.cs

@@ -29,7 +29,7 @@ namespace Renci.SshNet.Tests.Classes.Messages.Connection
         public void ChannelExtendedDataMessageConstructorTest1()
         {
             uint localChannelNumber = 0; // TODO: Initialize to an appropriate value
-            ChannelExtendedDataMessage target = new ChannelExtendedDataMessage(localChannelNumber);
+            //ChannelExtendedDataMessage target = new ChannelExtendedDataMessage(localChannelNumber, null, null);
             Assert.Inconclusive("TODO: Implement code to verify target");
         }
     }

+ 1 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/SshClientTest.cs

@@ -49,6 +49,7 @@ namespace Renci.SshNet.Tests.Classes
                 client.HostKeyReceived += delegate(object sender, HostKeyEventArgs e)
                 {
                     hostKeyValidated = true;
+
                     if (e.FingerPrint.SequenceEqual(new byte[] { 0x00, 0x01, 0x02, 0x03 }))
                     {
                         e.CanTrust = true;

+ 56 - 20
Renci.SshClient/Renci.SshNet/Channels/Channel.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Linq;
 using System.Threading;
 using Renci.SshNet.Common;
 using Renci.SshNet.Messages;
@@ -12,14 +13,16 @@ namespace Renci.SshNet.Channels
     /// </summary>
     internal abstract class Channel : IDisposable
     {
-        private EventWaitHandle _channelClosedWaitHandle = new AutoResetEvent(false);
+        private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(false);
 
-        private EventWaitHandle _channelWindowAdjustWaitHandle = new AutoResetEvent(false);
+        private EventWaitHandle _channelServerWindowAdjustWaitHandle = new ManualResetEvent(false);
 
         private EventWaitHandle _errorOccuredWaitHandle = new ManualResetEvent(false);
 
         private EventWaitHandle _disconnectedWaitHandle = new ManualResetEvent(false);
 
+        private object _serverWindowSizeLock = new object();
+
         private bool _closeMessageSent = false;
 
         private uint _initialWindowSize = 0x100000;
@@ -266,7 +269,11 @@ namespace Renci.SshNet.Channels
         /// <param name="bytesToAdd">The bytes to add.</param>
         protected virtual void OnWindowAdjust(uint bytesToAdd)
         {
-            this.ServerWindowSize += bytesToAdd;
+            lock (this._serverWindowSizeLock)
+            {
+                this.ServerWindowSize += bytesToAdd;
+            }
+            this._channelServerWindowAdjustWaitHandle.Set();
         }
 
         /// <summary>
@@ -410,20 +417,36 @@ namespace Renci.SshNet.Channels
         /// Sends channel data message to the servers.
         /// </summary>
         /// <remarks>This method takes care of managing the window size.</remarks>
-        /// <param name="message">Channel data message.</param>
+        /// <param name="message">Channel data message.</param>        
         protected void SendMessage(ChannelDataMessage message)
         {
             //  Send channel messages only while channel is open
             if (!this.IsOpen)
                 return;
 
-            if (this.ServerWindowSize < 1)
+            var messageLength = message.Data.Length;
+            do
             {
-                //  Wait for window to be adjust
-                this._session.WaitHandle(this._channelWindowAdjustWaitHandle);
-            }
+                lock (this._serverWindowSizeLock)
+                {
+                    var serverWindowSize = this.ServerWindowSize;
+                    if (serverWindowSize < messageLength)
+                    {
+                        //  Wait for window to be big enough for this message
+                        this._channelServerWindowAdjustWaitHandle.Reset();
+                    }
+                    else
+                    {
+                        this.ServerWindowSize -= (uint)messageLength;
+                        break;
+                    }
+                }
+
+                //  Wait for window to change
+                this.WaitHandle(this._channelServerWindowAdjustWaitHandle);
+
+            } while (true);
 
-            this.ServerWindowSize -= (uint)message.Data.Length;
             this._session.SendMessage(message);
         }
 
@@ -438,13 +461,29 @@ namespace Renci.SshNet.Channels
             if (!this.IsOpen)
                 return;
 
-            if (this.ServerWindowSize < 1)
+            var messageLength = message.Data.Length;
+            do
             {
-                //  Wait for window to be adjust
-                this._session.WaitHandle(this._channelWindowAdjustWaitHandle);
-            }
+                lock (this._serverWindowSizeLock)
+                {
+                    var serverWindowSize = this.ServerWindowSize;
+                    if (serverWindowSize < messageLength)
+                    {
+                        //  Wait for window to be big enough for this message
+                        this._channelServerWindowAdjustWaitHandle.Reset();
+                    }
+                    else
+                    {
+                        this.ServerWindowSize -= (uint)messageLength;
+                        break;
+                    }
+                }
+
+                //  Wait for window to change
+                this.WaitHandle(this._channelServerWindowAdjustWaitHandle);
+
+            } while (true);
 
-            this.ServerWindowSize -= (uint)message.Data.Length;
             this._session.SendMessage(message);
         }
 
@@ -496,7 +535,6 @@ namespace Renci.SshNet.Channels
             }
         }
 
-
         private void Session_Disconnected(object sender, EventArgs e)
         {
             //  If objected is disposed or being disposed don't handle this event
@@ -546,8 +584,6 @@ namespace Renci.SshNet.Channels
             if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
             {
                 this.OnWindowAdjust(e.Message.BytesToAdd);
-
-                this._channelWindowAdjustWaitHandle.Set();
             }
         }
 
@@ -672,10 +708,10 @@ namespace Renci.SshNet.Channels
                         this._channelClosedWaitHandle.Dispose();
                         this._channelClosedWaitHandle = null;
                     }
-                    if (this._channelWindowAdjustWaitHandle != null)
+                    if (this._channelServerWindowAdjustWaitHandle != null)
                     {
-                        this._channelWindowAdjustWaitHandle.Dispose();
-                        this._channelWindowAdjustWaitHandle = null;
+                        this._channelServerWindowAdjustWaitHandle.Dispose();
+                        this._channelServerWindowAdjustWaitHandle = null;
                     }
                     if (this._errorOccuredWaitHandle != null)
                     {

+ 3 - 1
Renci.SshClient/Renci.SshNet/Messages/Connection/ChannelExtendedDataMessage.cs

@@ -28,9 +28,11 @@
         /// Initializes a new instance of the <see cref="ChannelExtendedDataMessage"/> class.
         /// </summary>
         /// <param name="localChannelNumber">The local channel number.</param>
-        public ChannelExtendedDataMessage(uint localChannelNumber)
+        public ChannelExtendedDataMessage(uint localChannelNumber, uint dataTypeCode, byte[] data)
         {
             this.LocalChannelNumber = localChannelNumber;
+            this.DataTypeCode = dataTypeCode;
+            this.Data = data;
         }
 
         /// <summary>

+ 2 - 6
Renci.SshClient/Renci.SshNet/Session.cs

@@ -91,7 +91,7 @@ namespace Renci.SshNet
         /// <summary>
         /// WaitHandle to signal that exception was thrown by another thread.
         /// </summary>
-        private EventWaitHandle _exceptionWaitHandle = new AutoResetEvent(false);
+        private EventWaitHandle _exceptionWaitHandle = new ManualResetEvent(false);
 
         /// <summary>
         /// WaitHandle to signal that key exchange was completed.
@@ -637,11 +637,7 @@ namespace Renci.SshNet
             switch (EventWaitHandle.WaitAny(waitHandles, this.ConnectionInfo.Timeout))
             {
                 case 0:
-                    {
-                        var exception = this._exception;
-                        this._exception = null;
-                        throw exception;
-                    }
+                    throw this._exception;
                 case System.Threading.WaitHandle.WaitTimeout:
                     this.SendDisconnect(DisconnectReason.ByApplication, "Operation timeout");
                     throw new SshOperationTimeoutException("Session operation has timed out");

+ 4 - 0
Renci.SshClient/Renci.SshNet/Sftp/SftpFile.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Security;
 using System.Globalization;
+using Renci.SshNet.Common;
 
 namespace Renci.SshNet.Sftp
 {
@@ -27,6 +28,9 @@ namespace Renci.SshNet.Sftp
         /// <exception cref="ArgumentNullException"><paramref name="sftpSession"/> or <paramref name="fullName"/> is null.</exception>
         internal SftpFile(SftpSession sftpSession, string fullName, SftpFileAttributes attributes)
         {
+            if (sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             if (attributes == null)
                 throw new ArgumentNullException("attributes");
 

+ 3 - 0
Renci.SshClient/Renci.SshNet/Sftp/SftpFileStream.cs

@@ -209,6 +209,9 @@ namespace Renci.SshNet.Sftp
         internal SftpFileStream(SftpSession session, string path, FileMode mode, FileAccess access, int bufferSize, bool useAsync)
         {
             // Validate the parameters.
+            if (session == null)
+                throw new SshConnectionException("Client not connected.");
+
             if (path == null)
             {
                 throw new ArgumentNullException("path");

+ 52 - 20
Renci.SshClient/Renci.SshNet/SftpClient.cs

@@ -143,6 +143,9 @@ namespace Renci.SshNet
             if (path == null)
                 throw new ArgumentNullException("path");
 
+            if (this._sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             this._sftpSession.ChangeDirectory(path);
         }
 
@@ -176,6 +179,9 @@ namespace Renci.SshNet
             if (path.IsNullOrWhiteSpace())
                 throw new ArgumentException(path);
 
+            if (this._sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             var fullPath = this._sftpSession.GetCanonicalPath(path);
 
             this._sftpSession.RequestMkDir(fullPath);
@@ -194,6 +200,9 @@ namespace Renci.SshNet
             if (path.IsNullOrWhiteSpace())
                 throw new ArgumentException("path");
 
+            if (this._sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             var fullPath = this._sftpSession.GetCanonicalPath(path);
 
             this._sftpSession.RequestRmDir(fullPath);
@@ -212,6 +221,9 @@ namespace Renci.SshNet
             if (path.IsNullOrWhiteSpace())
                 throw new ArgumentException("path");
 
+            if (this._sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             var fullPath = this._sftpSession.GetCanonicalPath(path);
 
             this._sftpSession.RequestRemove(fullPath);
@@ -250,6 +262,9 @@ namespace Renci.SshNet
             if (newPath == null)
                 throw new ArgumentNullException("newPath");
 
+            if (this._sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             var oldFullPath = this._sftpSession.GetCanonicalPath(oldPath);
 
             var newFullPath = this._sftpSession.GetCanonicalPath(newPath);
@@ -281,6 +296,9 @@ namespace Renci.SshNet
             if (linkPath.IsNullOrWhiteSpace())
                 throw new ArgumentException("linkPath");
 
+            if (this._sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             var fullPath = this._sftpSession.GetCanonicalPath(path);
 
             var linkFullPath = this._sftpSession.GetCanonicalPath(linkPath);
@@ -376,6 +394,9 @@ namespace Renci.SshNet
             if (path == null)
                 throw new ArgumentNullException("path");
 
+            if (this._sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             var fullPath = this._sftpSession.GetCanonicalPath(path);
 
             var attributes = this._sftpSession.RequestLStat(fullPath);
@@ -397,6 +418,9 @@ namespace Renci.SshNet
             if (path.IsNullOrWhiteSpace())
                 throw new ArgumentException("path");
 
+            if (this._sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             var fullPath = this._sftpSession.GetFullRemotePath(path);
 
             if (this._sftpSession.RequestRealPath(fullPath, true) == null)
@@ -755,6 +779,9 @@ namespace Renci.SshNet
             if (path == null)
                 throw new ArgumentNullException("path");
 
+            if (this._sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             var fullPath = this._sftpSession.GetCanonicalPath(path);
 
             return this._sftpSession.RequestStatVfs(fullPath);
@@ -1285,6 +1312,9 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentNullException"><paramref name="path"/> is <b>null</b>.</exception>
         public SftpFileAttributes GetAttributes(string path)
         {
+            if (this._sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             var fullPath = this._sftpSession.GetCanonicalPath(path);
 
             return this._sftpSession.RequestLStat(fullPath);
@@ -1298,6 +1328,9 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentNullException"><paramref name="path"/> is <b>null</b>.</exception>
         public void SetAttributes(string path, SftpFileAttributes fileAttributes)
         {
+            if (this._sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             var fullPath = this._sftpSession.GetCanonicalPath(path);
 
             this._sftpSession.RequestSetStat(fullPath, fileAttributes);
@@ -1328,6 +1361,9 @@ namespace Renci.SshNet
             if (path == null)
                 throw new ArgumentNullException("path");
 
+            if (this._sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             var fullPath = this._sftpSession.GetCanonicalPath(path);
 
             var handle = this._sftpSession.RequestOpenDir(fullPath);
@@ -1380,6 +1416,9 @@ namespace Renci.SshNet
             if (path.IsNullOrWhiteSpace())
                 throw new ArgumentException("path");
 
+            if (this._sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             var fullPath = this._sftpSession.GetCanonicalPath(path);
 
             var handle = this._sftpSession.RequestOpen(fullPath, Flags.Read);
@@ -1434,6 +1473,9 @@ namespace Renci.SshNet
             if (path.IsNullOrWhiteSpace())
                 throw new ArgumentException("path");
 
+            if (this._sftpSession == null)
+                throw new SshConnectionException("Client not connected.");
+
             var fullPath = this._sftpSession.GetCanonicalPath(path);
 
             var handle = this._sftpSession.RequestOpen(fullPath, flags);
@@ -1444,7 +1486,7 @@ namespace Renci.SshNet
 
             var bytesRead = input.Read(buffer, 0, buffer.Length);
             var expectedResponses = 0;
-            var expectedResponsesLock = new object();
+            var responseReceivedWaitHandle = new AutoResetEvent(false);
 
             do
             {
@@ -1467,25 +1509,18 @@ namespace Renci.SshNet
                     {
                         if (s.StatusCode == StatusCodes.Ok)
                         {
-                            lock (expectedResponsesLock)
-                            {
-                                expectedResponses--;
-
-                                //  Call callback to report number of bytes written
-                                if (uploadCallback != null)
-                                {
-                                    //  Execute callback on different thread                
-                                    this.ExecuteThread(() => { uploadCallback(writtenBytes); });
-                                }
+                            expectedResponses--;
+                            responseReceivedWaitHandle.Set();
 
-                                Monitor.Pulse(expectedResponsesLock);
+                            //  Call callback to report number of bytes written
+                            if (uploadCallback != null)
+                            {
+                                //  Execute callback on different thread                
+                                this.ExecuteThread(() => { uploadCallback(writtenBytes); });
                             }
                         }
                     });
-                    lock (expectedResponsesLock)
-                    {
-                        expectedResponses++;
-                    }
+                    expectedResponses++;
 
                     offset += (uint)bytesRead;
 
@@ -1494,10 +1529,7 @@ namespace Renci.SshNet
                 else if (expectedResponses > 0)
                 {
                     //  Wait for expectedResponses to change
-                    lock (expectedResponsesLock)
-                    {
-                        Monitor.Wait(expectedResponsesLock);
-                    }
+                    this._sftpSession.WaitHandle(responseReceivedWaitHandle, this.OperationTimeout);
                 }
             } while (expectedResponses > 0 || bytesRead > 0);
 

+ 1 - 5
Renci.SshClient/Renci.SshNet/SshCommand.cs

@@ -484,11 +484,7 @@ namespace Renci.SshNet
             switch (EventWaitHandle.WaitAny(waitHandles, this.CommandTimeout))
             {
                 case 0:
-                    {
-                        var exception = this._exception;
-                        this._exception = null;
-                        throw exception;
-                    }
+                    throw this._exception;
                 case System.Threading.WaitHandle.WaitTimeout:
                     throw new SshOperationTimeoutException(string.Format(CultureInfo.CurrentCulture, "Command '{0}' has timed out.", this.CommandText));
                 default:

+ 4 - 8
Renci.SshClient/Renci.SshNet/SubsystemSession.cs

@@ -27,9 +27,9 @@ namespace Renci.SshNet.Sftp
 
         private Exception _exception;
 
-        private EventWaitHandle _errorOccuredWaitHandle = new AutoResetEvent(false);
+        private EventWaitHandle _errorOccuredWaitHandle = new ManualResetEvent(false);
 
-        private EventWaitHandle _channelClosedWaitHandle = new AutoResetEvent(false);
+        private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(false);
 
         /// <summary>
         /// Specifies a timeout to wait for operation to complete
@@ -165,11 +165,7 @@ namespace Renci.SshNet.Sftp
             switch (EventWaitHandle.WaitAny(waitHandles, operationTimeout))
             {
                 case 0:
-                    {
-                        var exception = this._exception;
-                        this._exception = null;
-                        throw exception;
-                    }
+                    throw this._exception;
                 case 1:
                     throw new SshException("Channel was closed.");
                 case System.Threading.WaitHandle.WaitTimeout:
@@ -197,7 +193,7 @@ namespace Renci.SshNet.Sftp
         #region IDisposable Members
 
         private bool _isDisposed = false;
-        
+
         /// <summary>
         /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
         /// </summary>