Selaa lähdekoodia

Improve exception messages.

drieseng 4 vuotta sitten
vanhempi
sitoutus
e8db68c371

+ 4 - 5
src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsEmpty.cs

@@ -75,15 +75,14 @@ namespace Renci.SshNet.Tests.Classes.Connection
         {
             base.TearDown();
 
-            if (_clientSocket != null)
+            if (_proxyServer != null)
             {
-                _clientSocket.Shutdown(SocketShutdown.Send);
-                _clientSocket.Close();
+                _proxyServer.Dispose();
             }
 
-            if (_proxyServer != null)
+            if (_clientSocket != null)
             {
-                _proxyServer.Dispose();
+                _clientSocket.Close();
             }
         }
 

+ 2 - 2
src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ConnectionClosedByServer_NoDataSentByServer.cs

@@ -7,7 +7,7 @@ using System.Collections.Generic;
 using System.Net;
 using System.Net.Sockets;
 
-namespace Renci.SshNet.Tests.Classes
+namespace Renci.SshNet.Tests.Classes.Connection
 {
     [TestClass]
     public class ProtocolVersionExchangeTest_ConnectionClosedByServer_NoDataSentByServer
@@ -58,7 +58,7 @@ namespace Renci.SshNet.Tests.Classes
             _server.BytesReceived += (bytes, socket) =>
                 {
                     _dataReceivedByServer.AddRange(bytes);
-                    socket.Shutdown(SocketShutdown.Both);
+                    socket.Shutdown(SocketShutdown.Send);
                 };
             _server.Disconnected += (socket) => _clientDisconnected = true;
 

+ 4 - 2
src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerDoesNotRespondWithIdentificationStringBeforeTimeout.cs

@@ -10,7 +10,7 @@ using System.Net.Sockets;
 using System.Text;
 using System.Threading;
 
-namespace Renci.SshNet.Tests.Classes
+namespace Renci.SshNet.Tests.Classes.Connection
 {
     [TestClass]
     public class ProtocolVersionExchangeTest_ServerDoesNotRespondWithIdentificationStringBeforeTimeout
@@ -65,8 +65,10 @@ namespace Renci.SshNet.Tests.Classes
                 {
                     _dataReceivedByServer.AddRange(bytes);
                     socket.Send(Encoding.UTF8.GetBytes("Welcome!\r\n"));
+                    /*
                     Thread.Sleep(_timeout.Add(TimeSpan.FromMilliseconds(50)));
-                    socket.Shutdown(SocketShutdown.Both);
+                    socket.Shutdown(SocketShutdown.Send);
+                    */
                 };
             _server.Disconnected += (socket) => _clientDisconnected = true;
 

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseContainsNullCharacter.cs

@@ -9,7 +9,7 @@ using System.Net;
 using System.Net.Sockets;
 using System.Text;
 
-namespace Renci.SshNet.Tests.Classes
+namespace Renci.SshNet.Tests.Classes.Connection
 {
     [TestClass]
     public class ProtocolVersionExchangeTest_ServerResponseContainsNullCharacter

+ 7 - 4
src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseInvalid_SshIdentificationOnlyContainsProtocolVersion.cs

@@ -9,7 +9,7 @@ using System.Net;
 using System.Net.Sockets;
 using System.Text;
 
-namespace Renci.SshNet.Tests.Classes
+namespace Renci.SshNet.Tests.Classes.Connection
 {
     [TestClass]
     public class ProtocolVersionExchangeTest_ServerResponseInvalid_SshIdentificationOnlyContainsProtocolVersion
@@ -63,7 +63,7 @@ namespace Renci.SshNet.Tests.Classes
                 {
                     _dataReceivedByServer.AddRange(bytes);
                     socket.Send(_serverIdentification);
-                    socket.Shutdown(SocketShutdown.Both);
+                    socket.Shutdown(SocketShutdown.Send);
                 };
             _server.Disconnected += (socket) => _clientDisconnected = true;
 
@@ -89,8 +89,11 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void StartShouldHaveThrownSshConnectionException()
         {
-            var expectedMessage = "Server response does not contain SSH protocol identification:" + Environment.NewLine +
-                                  "  00000000  53 53 48 2D 32 2E 30 0D 0A                       SSH-2.0..";
+            var expectedMessage = string.Format("The server response does not contain an SSH protocol identification:{0}{0}" +
+                                                "  00000000  53 53 48 2D 32 2E 30 0D 0A                       SSH-2.0..{0}{0}" +
+                                                "More information is available here:{0}" +
+                                                "https://tools.ietf.org/html/rfc4253#section-4.2",
+                                                Environment.NewLine);
 
             Assert.IsNotNull(_actualException);
             Assert.IsNull(_actualException.InnerException);

+ 2 - 2
src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_Comments.cs

@@ -9,7 +9,7 @@ using System.Net;
 using System.Net.Sockets;
 using System.Text;
 
-namespace Renci.SshNet.Tests.Classes
+namespace Renci.SshNet.Tests.Classes.Connection
 {
     [TestClass]
     public class ProtocolVersionExchangeTest_ServerResponseValid_Comments
@@ -63,7 +63,7 @@ namespace Renci.SshNet.Tests.Classes
                 {
                     _dataReceivedByServer.AddRange(bytes);
                     socket.Send(_serverIdentification);
-                    socket.Shutdown(SocketShutdown.Both);
+                    socket.Shutdown(SocketShutdown.Send);
                 };
             _server.Disconnected += (socket) => _clientDisconnected = true;
 

+ 2 - 2
src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_NoComments.cs

@@ -9,7 +9,7 @@ using System.Net;
 using System.Net.Sockets;
 using System.Text;
 
-namespace Renci.SshNet.Tests.Classes
+namespace Renci.SshNet.Tests.Classes.Connection
 {
     [TestClass]
     public class ProtocolVersionExchangeTest_ServerResponseValid_NoComments
@@ -63,7 +63,7 @@ namespace Renci.SshNet.Tests.Classes
                 {
                     _dataReceivedByServer.AddRange(bytes);
                     socket.Send(_serverIdentification);
-                    socket.Shutdown(SocketShutdown.Both);
+                    socket.Shutdown(SocketShutdown.Send);
                 };
             _server.Disconnected += (socket) => _clientDisconnected = true;
 

+ 114 - 16
src/Renci.SshNet.Tests/Common/AsyncSocketListener.cs

@@ -18,6 +18,7 @@ namespace Renci.SshNet.Tests.Common
         private Thread _receiveThread;
         private bool _started;
         private object _syncLock;
+        private string _stackTrace;
 
         public delegate void BytesReceivedHandler(byte[] bytesReceived, Socket socket);
         public delegate void ConnectedHandler(Socket socket);
@@ -56,6 +57,8 @@ namespace Renci.SshNet.Tests.Common
 
             _receiveThread = new Thread(StartListener);
             _receiveThread.Start(_listener);
+
+            _stackTrace = Environment.StackTrace;
         }
 
         public void Stop()
@@ -78,7 +81,10 @@ namespace Renci.SshNet.Tests.Common
                     }
 
                     DrainSocket(connectedClient);
+                    connectedClient.Dispose();
                 }
+
+                _connectedClients.Clear();
             }
 
             if (_listener != null)
@@ -116,26 +122,62 @@ namespace Renci.SshNet.Tests.Common
             _acceptCallbackDone.Set();
 
             // Get the socket that listens for inbound connections
-            var listener = (Socket) ar.AsyncState;
+            var listener = (Socket)ar.AsyncState;
 
             // Get the socket that handles the client request
-            var handler = listener.EndAccept(ar);
+            Socket handler;
+
+            try
+            {
+                handler = listener.EndAccept(ar);
+            }
+            catch (ObjectDisposedException ex)
+            {
+                // The listener is stopped through a Dispose() call, which in turn causes
+                // Socket.EndAccept(IAsyncResult) to throw an ObjectDisposedException
+                //
+                // Since we consider this ObjectDisposedException normal when the listener
+                // is being stopped, we only write a message to stderr if the listener
+                // is considered to be up and running
+                if (_started)
+                {
+                    Console.Error.WriteLine("[{0}] Failure accepting new connection: {1}",
+                                            typeof(AsyncSocketListener).FullName,
+                                            ex);
+                }
+
+                return;
+            }
 
             // Signal new connection
             SignalConnected(handler);
 
-            // Register client socket
-            _connectedClients.Add(handler);
+            lock (_syncLock)
+            {
+                // Register client socket
+                _connectedClients.Add(handler);
+            }
+
+            var state = new SocketStateObject(handler);
 
             try
             {
-                var state = new SocketStateObject(handler);
                 handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state);
             }
-            catch (ObjectDisposedException)
+            catch (ObjectDisposedException ex)
             {
-                // when the socket is closed, an ObjectDisposedException is thrown
-                // by Socket.EndAccept(IAsyncResult)
+                // The listener is stopped through a Dispose() call, which in turn causes
+                // Socket.BeginReceive(...) to throw an ObjectDisposedException
+                //
+                // Since we consider this ObjectDisposedException normal when the listener
+                // is being stopped, we only write a message to stderr if the listener
+                // is considered to be up and running
+                if (_started)
+                {
+                    Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
+                                            typeof(AsyncSocketListener).FullName,
+                                            ex);
+                }
             }
         }
 
@@ -143,7 +185,7 @@ namespace Renci.SshNet.Tests.Common
         {
             // Retrieve the state object and the handler socket
             // from the asynchronous state object
-            var state = (SocketStateObject) ar.AsyncState;
+            var state = (SocketStateObject)ar.AsyncState;
             var handler = state.Socket;
 
             int bytesRead;
@@ -152,11 +194,38 @@ namespace Renci.SshNet.Tests.Common
                 // Read data from the client socket.
                 bytesRead = handler.EndReceive(ar);
             }
-            catch (ObjectDisposedException)
+            catch (SocketException ex)
             {
-                // when the socket is closed, the callback will be invoked for any pending BeginReceive
-                // we could use the Socket.Connected property to detect this here, but the proper thing
-                // to do is invoke EndReceive knowing that it will throw an ObjectDisposedException
+                // The listener is stopped through a Dispose() call, which in turn causes
+                // Socket.EndReceive(...) to throw a SocketException or
+                // ObjectDisposedException
+                //
+                // Since we consider such an exception normal when the listener is being
+                // stopped, we only write a message to stderr if the listener is considered
+                // to be up and running
+                if (_started)
+                {
+                    Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
+                                            typeof(AsyncSocketListener).FullName,
+                                            ex);
+                }
+                return;
+            }
+            catch (ObjectDisposedException ex)
+            {
+                // The listener is stopped through a Dispose() call, which in turn causes
+                // Socket.EndReceive(...) to throw a SocketException or
+                // ObjectDisposedException
+                //
+                // Since we consider such an exception normal when the listener is being
+                // stopped, we only write a message to stderr if the listener is considered
+                // to be up and running
+                if (_started)
+                {
+                    Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
+                                            typeof(AsyncSocketListener).FullName,
+                                            ex);
+                }
                 return;
             }
 
@@ -165,7 +234,21 @@ namespace Renci.SshNet.Tests.Common
                 var bytesReceived = new byte[bytesRead];
                 Array.Copy(state.Buffer, bytesReceived, bytesRead);
                 SignalBytesReceived(bytesReceived, handler);
-                handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state);
+
+                try
+                {
+                    handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state);
+                }
+                catch (SocketException ex)
+                {
+                    if (!_started)
+                    {
+                        throw new Exception("BeginReceive while stopping!", ex);
+                    }
+
+                    throw new Exception("BeginReceive while started!: " + ex.SocketErrorCode + " " + _stackTrace, ex);
+                }
+
             }
             else
             {
@@ -175,8 +258,23 @@ namespace Renci.SshNet.Tests.Common
                 {
                     lock (_syncLock)
                     {
-                        handler.Shutdown(SocketShutdown.Send);
-                        handler.Close();
+                        if (!_started)
+                        {
+                            return;
+                        }
+                        try
+                        {
+                            handler.Shutdown(SocketShutdown.Send);
+                            handler.Close();
+                        }
+                        catch (SocketException ex)
+                        {
+                            throw new Exception("Exception in ReadCallback: " + ex.SocketErrorCode + " " + _stackTrace, ex);
+                        }
+                        catch (Exception ex)
+                        {
+                            throw new Exception("Exception in ReadCallback: " + _stackTrace, ex);
+                        }
 
                         _connectedClients.Remove(handler);
                     }

+ 14 - 3
src/Renci.SshNet/Connection/ProtocolVersionExchange.cs

@@ -13,6 +13,9 @@ namespace Renci.SshNet.Connection
     /// <summary>
     /// Handles the SSH protocol version exchange.
     /// </summary>
+    /// <remarks>
+    /// https://tools.ietf.org/html/rfc4253#section-4.2
+    /// </remarks>
     internal class ProtocolVersionExchange : IProtocolVersionExchange
     {
         private const byte Null = 0x00;
@@ -49,10 +52,14 @@ namespace Renci.SshNet.Connection
                 {
                     if (bytesReceived.Count == 0)
                     {
-                        throw new SshConnectionException("Server response does not contain SSH protocol identification. Connection to remote server was closed before any data was received.", DisconnectReason.ConnectionLost);
+                        throw new SshConnectionException("The server response does not contain an SSH protocol identification. Connection to remote server was closed before any data was received.", DisconnectReason.ConnectionLost);
                     }
 
-                    throw new SshConnectionException(string.Format("Server response does not contain SSH protocol identification:{0}{1}", Environment.NewLine, PacketDump.Create(bytesReceived, 2)),
+                    throw new SshConnectionException(string.Format("The server response does not contain an SSH protocol identification:{0}{0}{1}{0}{0}" +
+                                                                   "More information is available here:{0}" +
+                                                                   "https://tools.ietf.org/html/rfc4253#section-4.2",
+                                                                   Environment.NewLine,
+                                                                   PacketDump.Create(bytesReceived, 2)),
                                                      DisconnectReason.ProtocolError);
                 }
 
@@ -112,7 +119,11 @@ namespace Renci.SshNet.Connection
                 if (byteRead == Null)
                 {
                     throw new SshConnectionException(string.Format(CultureInfo.InvariantCulture,
-                                                                   "The identification string contains a null character at position 0x{0:X8}:{1}{2}",
+                                                                   "The server response contains a null character at position 0x{0:X8}:{1}{1}{2}{1}{1}" +
+                                                                   "A server must not send a null character before the Protocol Version Exchange is{1}" +
+                                                                   "complete.{1}{1}" +
+                                                                   "More information is available here:{1}" +
+                                                                   "https://tools.ietf.org/html/rfc4253#section-4.2",
                                                                    buffer.Count,
                                                                    Environment.NewLine,
                                                                    PacketDump.Create(buffer.ToArray(), 2)));