mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-10-18 22:44:56 +08:00
933613e31c
* Update to MSTest 4 * fix TestMethodForPlatformAttribute replace Execute with ExecuteAsync and fix MSTEST0057 https://learn.microsoft.com/en-us/dotnet/core/testing/mstest-analyzers/mstest0057 (link currently dead) * fix compilation error * fix MSTEST0037 https://learn.microsoft.com/en-us/dotnet/core/testing/mstest-analyzers/mstest0037 * fix MSTEST0052 https://learn.microsoft.com/en-us/dotnet/core/testing/mstest-analyzers/mstest0052 * fix MSTEST0045 Fixing this properly would require the tests to respect testContext.CancellationToken. I'm not sure this is worth fixing or how to even do it for the sync methods. https://learn.microsoft.com/en-us/dotnet/core/testing/mstest-analyzers/mstest0045 * fix MSTEST0001 I assume that parallelization would break a lot of stuff. https://learn.microsoft.com/en-us/dotnet/core/testing/mstest-analyzers/mstest0001 * Workaround for new Sonar warnings because of MSTest4 * revert analyzer fixes in OrderedDictionaryTest * use custom sync console logger for MSTest to work around https://github.com/microsoft/testfx/issues/6457 * remove redundant args --------- Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com> Co-authored-by: Rob Hague <rob.hague00@gmail.com>
558 lines
21 KiB
C#
558 lines
21 KiB
C#
using Renci.SshNet.Common;
|
|
using Renci.SshNet.IntegrationTests.Common;
|
|
using Renci.SshNet.Messages.Transport;
|
|
|
|
namespace Renci.SshNet.IntegrationTests
|
|
{
|
|
[TestClass]
|
|
public class ConnectivityTests : IntegrationTestBase
|
|
{
|
|
private AuthenticationMethodFactory _authenticationMethodFactory;
|
|
private IConnectionInfoFactory _connectionInfoFactory;
|
|
private IConnectionInfoFactory _adminConnectionInfoFactory;
|
|
private RemoteSshdConfig _remoteSshdConfig;
|
|
private SshConnectionDisruptor _sshConnectionDisruptor;
|
|
|
|
[TestInitialize]
|
|
public void SetUp()
|
|
{
|
|
_authenticationMethodFactory = new AuthenticationMethodFactory();
|
|
_connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort, _authenticationMethodFactory);
|
|
_adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort);
|
|
_remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig();
|
|
_sshConnectionDisruptor = new SshConnectionDisruptor(_adminConnectionInfoFactory);
|
|
}
|
|
|
|
[TestCleanup]
|
|
public void TearDown()
|
|
{
|
|
_remoteSshdConfig?.Reset();
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Common_CreateMoreChannelsThanMaxSessions()
|
|
{
|
|
var connectionInfo = _connectionInfoFactory.Create();
|
|
connectionInfo.MaxSessions = 2;
|
|
|
|
using (var client = new SshClient(connectionInfo))
|
|
{
|
|
client.Connect();
|
|
|
|
// create one more channel than the maximum number of sessions
|
|
// as that would block indefinitely when creating the last channel
|
|
// if the channel would not be properly closed
|
|
for (var i = 0; i < connectionInfo.MaxSessions + 1; i++)
|
|
{
|
|
using (var stream = client.CreateShellStream("vt220", 20, 20, 20, 20, 20))
|
|
{
|
|
stream.WriteLine("echo test");
|
|
stream.ReadLine();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Common_DisposeAfterLossOfNetworkConnectivity()
|
|
{
|
|
var hostNetworkConnectionDisabled = false;
|
|
SshConnectionRestorer disruptor = null;
|
|
try
|
|
{
|
|
Exception errorOccurred = null;
|
|
int count = 0;
|
|
using (var client = new SftpClient(_connectionInfoFactory.Create()))
|
|
{
|
|
client.ErrorOccurred += (sender, args) =>
|
|
{
|
|
Console.WriteLine("Exception " + count++);
|
|
Console.WriteLine(args.Exception);
|
|
errorOccurred = args.Exception;
|
|
};
|
|
client.Connect();
|
|
|
|
disruptor = _sshConnectionDisruptor.BreakConnections();
|
|
hostNetworkConnectionDisabled = true;
|
|
WaitForConnectionInterruption(client);
|
|
}
|
|
|
|
Assert.IsNotNull(errorOccurred);
|
|
Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
|
|
|
|
var connectionException = (SshConnectionException)errorOccurred;
|
|
Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
|
|
Assert.IsNull(connectionException.InnerException);
|
|
Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
|
|
}
|
|
finally
|
|
{
|
|
if (hostNetworkConnectionDisabled)
|
|
{
|
|
disruptor?.RestoreConnections();
|
|
disruptor?.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Common_DetectLossOfNetworkConnectivityThroughKeepAlive()
|
|
{
|
|
using (var client = new SftpClient(_connectionInfoFactory.Create()))
|
|
{
|
|
Exception errorOccurred = null;
|
|
int count = 0;
|
|
client.ErrorOccurred += (sender, args) =>
|
|
{
|
|
Console.WriteLine("Exception " + count++);
|
|
Console.WriteLine(args.Exception);
|
|
errorOccurred = args.Exception;
|
|
};
|
|
client.KeepAliveInterval = new TimeSpan(0, 0, 0, 0, 50);
|
|
client.Connect();
|
|
|
|
var disruptor = _sshConnectionDisruptor.BreakConnections();
|
|
|
|
try
|
|
{
|
|
WaitForConnectionInterruption(client);
|
|
|
|
Assert.IsFalse(client.IsConnected);
|
|
|
|
Assert.IsNotNull(errorOccurred);
|
|
Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
|
|
|
|
var connectionException = (SshConnectionException)errorOccurred;
|
|
Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
|
|
Assert.IsNull(connectionException.InnerException);
|
|
Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
|
|
}
|
|
finally
|
|
{
|
|
disruptor?.RestoreConnections();
|
|
disruptor?.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void WaitForConnectionInterruption(SftpClient client)
|
|
{
|
|
for (var i = 0; i < 500; i++)
|
|
{
|
|
if (!client.IsConnected)
|
|
{
|
|
break;
|
|
}
|
|
|
|
Thread.Sleep(100);
|
|
}
|
|
|
|
// After interruption, you have to wait for the events to propagate.
|
|
Thread.Sleep(100);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Common_DetectConnectionResetThroughSftpInvocation()
|
|
{
|
|
using (var client = new SftpClient(_connectionInfoFactory.Create()))
|
|
{
|
|
client.KeepAliveInterval = TimeSpan.FromSeconds(1);
|
|
client.OperationTimeout = TimeSpan.FromSeconds(60);
|
|
ManualResetEvent errorOccurredSignaled = new ManualResetEvent(false);
|
|
Exception errorOccurred = null;
|
|
client.ErrorOccurred += (sender, args) =>
|
|
{
|
|
errorOccurred = args.Exception;
|
|
errorOccurredSignaled.Set();
|
|
};
|
|
client.Connect();
|
|
|
|
var disruptor = _sshConnectionDisruptor.BreakConnections();
|
|
|
|
try
|
|
{
|
|
WaitForConnectionInterruption(client);
|
|
client.ListDirectory("/");
|
|
Assert.Fail();
|
|
}
|
|
catch (SshConnectionException ex)
|
|
{
|
|
Assert.IsNull(ex.InnerException);
|
|
Assert.AreEqual("Client not connected.", ex.Message);
|
|
|
|
Assert.IsNotNull(errorOccurred);
|
|
Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
|
|
|
|
var connectionException = (SshConnectionException)errorOccurred;
|
|
Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
|
|
Assert.IsNull(connectionException.InnerException);
|
|
Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
|
|
}
|
|
finally
|
|
{
|
|
disruptor.RestoreConnections();
|
|
disruptor.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Common_LossOfNetworkConnectivityDisconnectAndConnect()
|
|
{
|
|
bool vmNetworkConnectionDisabled = false;
|
|
SshConnectionRestorer disruptor = null;
|
|
try
|
|
{
|
|
using (var client = new SftpClient(_connectionInfoFactory.Create()))
|
|
{
|
|
Exception errorOccurred = null;
|
|
client.ErrorOccurred += (sender, args) => errorOccurred = args.Exception;
|
|
|
|
client.Connect();
|
|
|
|
disruptor = _sshConnectionDisruptor.BreakConnections();
|
|
vmNetworkConnectionDisabled = true;
|
|
|
|
WaitForConnectionInterruption(client);
|
|
// disconnect while network connectivity is lost
|
|
client.Disconnect();
|
|
|
|
Assert.IsFalse(client.IsConnected);
|
|
|
|
disruptor.RestoreConnections();
|
|
vmNetworkConnectionDisabled = false;
|
|
|
|
// connect when network connectivity is restored
|
|
client.Connect();
|
|
client.ChangeDirectory(client.WorkingDirectory);
|
|
client.Dispose();
|
|
|
|
Assert.IsNotNull(errorOccurred);
|
|
Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
|
|
|
|
var connectionException = (SshConnectionException)errorOccurred;
|
|
Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
|
|
Assert.IsNull(connectionException.InnerException);
|
|
Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (vmNetworkConnectionDisabled)
|
|
{
|
|
disruptor.RestoreConnections();
|
|
}
|
|
disruptor?.Dispose();
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Common_DetectLossOfNetworkConnectivityThroughSftpInvocation()
|
|
{
|
|
using (var client = new SftpClient(_connectionInfoFactory.Create()))
|
|
{
|
|
ManualResetEvent errorOccurredSignaled = new ManualResetEvent(false);
|
|
Exception errorOccurred = null;
|
|
client.ErrorOccurred += (sender, args) =>
|
|
{
|
|
errorOccurred = args.Exception;
|
|
errorOccurredSignaled.Set();
|
|
};
|
|
client.Connect();
|
|
|
|
var disruptor = _sshConnectionDisruptor.BreakConnections();
|
|
try
|
|
{
|
|
WaitForConnectionInterruption(client);
|
|
client.ListDirectory("/");
|
|
Assert.Fail();
|
|
}
|
|
catch (SshConnectionException ex)
|
|
{
|
|
Assert.IsNull(ex.InnerException);
|
|
Assert.AreEqual("Client not connected.", ex.Message);
|
|
|
|
Assert.IsNotNull(errorOccurred);
|
|
Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
|
|
|
|
var connectionException = (SshConnectionException)errorOccurred;
|
|
Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
|
|
Assert.IsNull(connectionException.InnerException);
|
|
Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
|
|
}
|
|
finally
|
|
{
|
|
disruptor.RestoreConnections();
|
|
disruptor.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SftpClient_HandleSftpSessionClose()
|
|
{
|
|
using (var client = new SftpClient(_connectionInfoFactory.Create()))
|
|
{
|
|
client.Connect();
|
|
Assert.IsTrue(client.IsConnected);
|
|
|
|
client.SftpSession.Disconnect();
|
|
Assert.IsFalse(client.IsConnected);
|
|
|
|
client.Connect();
|
|
Assert.IsTrue(client.IsConnected);
|
|
|
|
client.Disconnect();
|
|
Assert.IsFalse(client.IsConnected);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task SftpClient_HandleSftpSessionCloseAsync()
|
|
{
|
|
using (var client = new SftpClient(_connectionInfoFactory.Create()))
|
|
{
|
|
await client.ConnectAsync(CancellationToken.None);
|
|
Assert.IsTrue(client.IsConnected);
|
|
|
|
client.SftpSession.Disconnect();
|
|
Assert.IsFalse(client.IsConnected);
|
|
|
|
await client.ConnectAsync(CancellationToken.None);
|
|
Assert.IsTrue(client.IsConnected);
|
|
|
|
client.Disconnect();
|
|
Assert.IsFalse(client.IsConnected);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SftpClient_HandleSftpSessionAbortByServer()
|
|
{
|
|
using (var client = new SftpClient(_connectionInfoFactory.Create()))
|
|
{
|
|
client.Connect();
|
|
Assert.IsTrue(client.IsConnected);
|
|
|
|
_sshConnectionDisruptor.BreakConnections();
|
|
WaitForConnectionInterruption(client);
|
|
Assert.IsFalse(client.IsConnected);
|
|
|
|
client.Connect();
|
|
Assert.IsTrue(client.IsConnected);
|
|
|
|
foreach (var file in client.ListDirectory("."))
|
|
{
|
|
}
|
|
|
|
client.Disconnect();
|
|
Assert.IsFalse(client.IsConnected);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task SftpClient_HandleSftpSessionAbortByServerAsync()
|
|
{
|
|
using (var client = new SftpClient(_connectionInfoFactory.Create()))
|
|
{
|
|
await client.ConnectAsync(CancellationToken.None);
|
|
Assert.IsTrue(client.IsConnected);
|
|
|
|
_sshConnectionDisruptor.BreakConnections();
|
|
WaitForConnectionInterruption(client);
|
|
Assert.IsFalse(client.IsConnected);
|
|
|
|
await client.ConnectAsync(CancellationToken.None);
|
|
Assert.IsTrue(client.IsConnected);
|
|
|
|
await foreach (var file in client.ListDirectoryAsync(".", CancellationToken.None))
|
|
{
|
|
}
|
|
|
|
client.Disconnect();
|
|
Assert.IsFalse(client.IsConnected);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Common_DetectSessionKilledOnServer()
|
|
{
|
|
using (var client = new SftpClient(_connectionInfoFactory.Create()))
|
|
{
|
|
ManualResetEvent errorOccurredSignaled = new ManualResetEvent(false);
|
|
Exception errorOccurred = null;
|
|
client.ErrorOccurred += (sender, args) =>
|
|
{
|
|
errorOccurred = args.Exception;
|
|
errorOccurredSignaled.Set();
|
|
};
|
|
client.Connect();
|
|
|
|
// Kill the server session
|
|
using (var adminClient = new SshClient(_adminConnectionInfoFactory.Create()))
|
|
{
|
|
adminClient.Connect();
|
|
|
|
var command = $"sudo ps --no-headers -u {client.ConnectionInfo.Username} -f | grep \"{client.ConnectionInfo.Username}@notty\" | awk '{{print $2}}' | xargs sudo kill -9";
|
|
var sshCommand = adminClient.CreateCommand(command);
|
|
var result = sshCommand.Execute();
|
|
Assert.AreEqual(0, sshCommand.ExitStatus, sshCommand.Error);
|
|
}
|
|
|
|
Assert.IsTrue(errorOccurredSignaled.WaitOne(200));
|
|
Assert.IsNotNull(errorOccurred);
|
|
Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
|
|
Assert.IsNull(errorOccurred.InnerException);
|
|
Assert.AreEqual("An established connection was aborted by the server.", errorOccurred.Message);
|
|
Assert.IsFalse(client.IsConnected);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Common_HostKeyValidation_Failure()
|
|
{
|
|
using (var client = new SshClient(_connectionInfoFactory.Create()))
|
|
{
|
|
client.HostKeyReceived += (sender, e) => { e.CanTrust = false; };
|
|
|
|
var ex = Assert.Throws<SshConnectionException>(client.Connect);
|
|
|
|
Assert.AreEqual(DisconnectReason.KeyExchangeFailed, ex.DisconnectReason);
|
|
Assert.AreEqual("Host key could not be verified.", ex.Message);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Common_HostKeyAlgorithms_NoMatch()
|
|
{
|
|
using (var client = new SshClient(_connectionInfoFactory.Create()))
|
|
{
|
|
client.ConnectionInfo.HostKeyAlgorithms.Clear();
|
|
|
|
var ex = Assert.Throws<SshConnectionException>(client.Connect);
|
|
|
|
Assert.AreEqual(DisconnectReason.KeyExchangeFailed, ex.DisconnectReason);
|
|
Assert.StartsWith("No matching host key algorithm", ex.Message);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Common_HostKeyValidation_Success()
|
|
{
|
|
byte[] host_rsa_key_openssh_fingerprint =
|
|
{
|
|
0x3d, 0x90, 0xd8, 0x0d, 0xd5, 0xe0, 0xb6, 0x13,
|
|
0x42, 0x7c, 0x78, 0x1e, 0x19, 0xa3, 0x99, 0x2b
|
|
};
|
|
|
|
var hostValidationSuccessful = false;
|
|
|
|
using (var client = new SshClient(_connectionInfoFactory.Create()))
|
|
{
|
|
client.HostKeyReceived += (sender, e) =>
|
|
{
|
|
if (host_rsa_key_openssh_fingerprint.Length == e.FingerPrint.Length)
|
|
{
|
|
for (var i = 0; i < host_rsa_key_openssh_fingerprint.Length; i++)
|
|
{
|
|
if (host_rsa_key_openssh_fingerprint[i] != e.FingerPrint[i])
|
|
{
|
|
e.CanTrust = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
hostValidationSuccessful = e.CanTrust;
|
|
}
|
|
else
|
|
{
|
|
e.CanTrust = false;
|
|
}
|
|
};
|
|
client.Connect();
|
|
}
|
|
|
|
Assert.IsTrue(hostValidationSuccessful);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Common_HostKeyValidationSHA256_Success()
|
|
{
|
|
var hostValidationSuccessful = false;
|
|
|
|
using (var client = new SshClient(_connectionInfoFactory.Create()))
|
|
{
|
|
client.HostKeyReceived += (sender, e) =>
|
|
{
|
|
if (e.FingerPrintSHA256 == "9fa6vbz64gimzsGZ/xZi3aaYE1o7E96iU2NjcfQNGwI")
|
|
{
|
|
hostValidationSuccessful = e.CanTrust;
|
|
}
|
|
else
|
|
{
|
|
e.CanTrust = false;
|
|
}
|
|
};
|
|
client.Connect();
|
|
}
|
|
|
|
Assert.IsTrue(hostValidationSuccessful);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Common_HostKeyValidationMD5_Success()
|
|
{
|
|
var hostValidationSuccessful = false;
|
|
|
|
using (var client = new SshClient(_connectionInfoFactory.Create()))
|
|
{
|
|
client.HostKeyReceived += (sender, e) =>
|
|
{
|
|
if (e.FingerPrintMD5 == "3d:90:d8:0d:d5:e0:b6:13:42:7c:78:1e:19:a3:99:2b")
|
|
{
|
|
hostValidationSuccessful = e.CanTrust;
|
|
}
|
|
else
|
|
{
|
|
e.CanTrust = false;
|
|
}
|
|
};
|
|
client.Connect();
|
|
}
|
|
|
|
Assert.IsTrue(hostValidationSuccessful);
|
|
}
|
|
/// <summary>
|
|
/// Verifies whether we handle a disconnect initiated by the SSH server (through a SSH_MSG_DISCONNECT message).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// We force this by only configuring <c>keyboard-interactive</c> as authentication method, while <c>ChallengeResponseAuthentication</c>
|
|
/// is not enabled. This causes OpenSSH to terminate the connection because there are no authentication methods left.
|
|
/// </remarks>
|
|
[TestMethod]
|
|
public void Common_ServerRejectsConnection()
|
|
{
|
|
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive")
|
|
.Update()
|
|
.Restart();
|
|
|
|
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserKeyboardInteractiveAuthenticationMethod());
|
|
using (var client = new SftpClient(connectionInfo))
|
|
{
|
|
try
|
|
{
|
|
client.Connect();
|
|
Assert.Fail();
|
|
}
|
|
catch (SshConnectionException ex)
|
|
{
|
|
Assert.AreEqual(DisconnectReason.ProtocolError, ex.DisconnectReason);
|
|
Assert.IsNull(ex.InnerException);
|
|
Assert.AreEqual("The connection was closed by the server: no authentication methods enabled (ProtocolError).", ex.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|