mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-10-29 15:11:12 +08:00
* Refactor logging to allow a loggerfactory per session specified in the ConnectionInfo. This commit introduces an `ILoggerFactory` to various classes, replacing the static logger factory with an instance-based approach for more flexible and session-specific logging. These changes improve the logging framework's flexibility and maintainability and allow unit testing of logging. * Improvements bases on feedback. Fixed tests. Added documentation. * Update src/Renci.SshNet/ConnectionInfo.cs --------- Co-authored-by: Rob Hague <rob.hague00@gmail.com>
269 lines
14 KiB
C#
269 lines
14 KiB
C#
using System;
|
|
using System.Globalization;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Threading;
|
|
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
|
|
using Moq;
|
|
|
|
using Renci.SshNet.Channels;
|
|
using Renci.SshNet.Common;
|
|
using Renci.SshNet.Messages;
|
|
using Renci.SshNet.Messages.Connection;
|
|
using Renci.SshNet.Tests.Common;
|
|
|
|
namespace Renci.SshNet.Tests.Classes.Channels
|
|
{
|
|
[TestClass]
|
|
public class ChannelDirectTcpipTestTest : TestBase
|
|
{
|
|
private Mock<ISession> _sessionMock;
|
|
private Mock<IForwardedPort> _forwardedPortMock;
|
|
private Mock<IConnectionInfo> _connectionInfoMock;
|
|
private uint _localChannelNumber;
|
|
private uint _localWindowSize;
|
|
private uint _localPacketSize;
|
|
private string _remoteHost;
|
|
private uint _port;
|
|
private uint _remoteWindowSize;
|
|
private uint _remotePacketSize;
|
|
private uint _remoteChannelNumber;
|
|
private TimeSpan _channelCloseTimeout;
|
|
|
|
protected override void OnInit()
|
|
{
|
|
base.OnInit();
|
|
|
|
var random = new Random();
|
|
|
|
_localWindowSize = (uint)random.Next(2000, 3000);
|
|
_localPacketSize = (uint)random.Next(1000, 2000);
|
|
_remoteHost = random.Next().ToString(CultureInfo.InvariantCulture);
|
|
_port = (uint)random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort);
|
|
_localChannelNumber = (uint)random.Next(0, int.MaxValue);
|
|
_remoteWindowSize = (uint)random.Next(0, int.MaxValue);
|
|
_remotePacketSize = (uint)random.Next(100, 200);
|
|
_remoteChannelNumber = (uint)random.Next(0, int.MaxValue);
|
|
_channelCloseTimeout = TimeSpan.FromSeconds(random.Next(10, 20));
|
|
|
|
_sessionMock = new Mock<ISession>(MockBehavior.Strict);
|
|
_sessionMock.Setup(p => p.SessionLoggerFactory).Returns(NullLoggerFactory.Instance);
|
|
_forwardedPortMock = new Mock<IForwardedPort>(MockBehavior.Strict);
|
|
_connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SocketShouldBeClosedAndBindShouldEndWhenForwardedPortSignalsClosingEvent()
|
|
{
|
|
_ = _sessionMock.Setup(p => p.IsConnected)
|
|
.Returns(true);
|
|
_ = _sessionMock.Setup(p => p.SendMessage(It.IsAny<ChannelOpenMessage>()))
|
|
.Callback<Message>(m => _sessionMock.Raise(p => p.ChannelOpenConfirmationReceived += null,
|
|
new MessageEventArgs<ChannelOpenConfirmationMessage>(
|
|
new ChannelOpenConfirmationMessage(((ChannelOpenMessage)m).LocalChannelNumber,
|
|
_remoteWindowSize,
|
|
_remotePacketSize,
|
|
_remoteChannelNumber))));
|
|
_ = _sessionMock.Setup(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()))
|
|
.Callback<WaitHandle>(p => p.WaitOne());
|
|
|
|
var localPortEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
|
|
using (var localPortListener = new AsyncSocketListener(localPortEndPoint))
|
|
{
|
|
localPortListener.Start();
|
|
|
|
localPortListener.Connected += socket =>
|
|
{
|
|
var channel = new ChannelDirectTcpip(_sessionMock.Object,
|
|
_localChannelNumber,
|
|
_localWindowSize,
|
|
_localPacketSize);
|
|
channel.Open(_remoteHost, _port, _forwardedPortMock.Object, socket);
|
|
|
|
var closeForwardedPortThread =
|
|
new Thread(() =>
|
|
{
|
|
// sleep for a short period to allow channel to actually start receiving from socket
|
|
Thread.Sleep(100);
|
|
// raise Closing event on forwarded port
|
|
_forwardedPortMock.Raise(p => p.Closing += null, EventArgs.Empty);
|
|
});
|
|
closeForwardedPortThread.Start();
|
|
|
|
channel.Bind();
|
|
|
|
closeForwardedPortThread.Join();
|
|
};
|
|
|
|
var client = new Socket(localPortEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
|
client.Connect(localPortEndPoint);
|
|
|
|
// attempt to receive from socket to verify it was shut down by channel
|
|
var buffer = new byte[16];
|
|
var bytesReceived = client.Receive(buffer, 0, buffer.Length, SocketFlags.None);
|
|
Assert.AreEqual(0, bytesReceived);
|
|
Assert.IsTrue(client.Connected);
|
|
// signal to server that we also shut down the socket at our end
|
|
client.Shutdown(SocketShutdown.Send);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SocketShouldBeClosedAndBindShouldEndWhenOnErrorOccurredIsInvoked()
|
|
{
|
|
_ = _sessionMock.Setup(p => p.IsConnected)
|
|
.Returns(true);
|
|
_ = _sessionMock.Setup(p => p.SendMessage(It.IsAny<ChannelOpenMessage>()))
|
|
.Callback<Message>(m => _sessionMock.Raise(p => p.ChannelOpenConfirmationReceived += null,
|
|
new MessageEventArgs<ChannelOpenConfirmationMessage>(
|
|
new ChannelOpenConfirmationMessage(((ChannelOpenMessage)m).LocalChannelNumber,
|
|
_remoteWindowSize,
|
|
_remotePacketSize,
|
|
_remoteChannelNumber))));
|
|
_ = _sessionMock.Setup(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()))
|
|
.Callback<WaitHandle>(p => p.WaitOne());
|
|
|
|
var localPortEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
|
|
using (var localPortListener = new AsyncSocketListener(localPortEndPoint))
|
|
{
|
|
localPortListener.Start();
|
|
|
|
localPortListener.Connected += socket =>
|
|
{
|
|
var channel = new ChannelDirectTcpip(_sessionMock.Object,
|
|
_localChannelNumber,
|
|
_localWindowSize,
|
|
_localPacketSize);
|
|
channel.Open(_remoteHost, _port, _forwardedPortMock.Object, socket);
|
|
|
|
var signalSessionErrorOccurredThread =
|
|
new Thread(() =>
|
|
{
|
|
// sleep for a short period to allow channel to actually start receiving from socket
|
|
Thread.Sleep(100);
|
|
// raise ErrorOccurred event on session
|
|
_sessionMock.Raise(s => s.ErrorOccured += null,
|
|
new ExceptionEventArgs(new SystemException()));
|
|
});
|
|
signalSessionErrorOccurredThread.Start();
|
|
|
|
channel.Bind();
|
|
|
|
signalSessionErrorOccurredThread.Join();
|
|
};
|
|
|
|
var client = new Socket(localPortEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
|
client.Connect(localPortEndPoint);
|
|
|
|
// attempt to receive from socket to verify it was shut down by channel
|
|
var buffer = new byte[16];
|
|
var bytesReceived = client.Receive(buffer, 0, buffer.Length, SocketFlags.None);
|
|
Assert.AreEqual(0, bytesReceived);
|
|
Assert.IsTrue(client.Connected);
|
|
// signal to server that we also shut down the socket at our end
|
|
client.Shutdown(SocketShutdown.Send);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SocketShouldBeClosedAndEofShouldBeSentToServerWhenClientShutsDownSocket()
|
|
{
|
|
var sequence = new MockSequence();
|
|
|
|
_ = _sessionMock.InSequence(sequence).Setup(p => p.IsConnected)
|
|
.Returns(true);
|
|
_ = _sessionMock.InSequence(sequence)
|
|
.Setup(p => p.SendMessage(It.IsAny<ChannelOpenMessage>()))
|
|
.Callback<Message>(m => _sessionMock.Raise(p => p.ChannelOpenConfirmationReceived += null,
|
|
new MessageEventArgs<ChannelOpenConfirmationMessage>(
|
|
new ChannelOpenConfirmationMessage(((ChannelOpenMessage)m).LocalChannelNumber,
|
|
_remoteWindowSize,
|
|
_remotePacketSize,
|
|
_remoteChannelNumber))));
|
|
_ = _sessionMock.InSequence(sequence)
|
|
.Setup(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()))
|
|
.Callback<WaitHandle>(p => p.WaitOne());
|
|
_ = _sessionMock.InSequence(sequence)
|
|
.Setup(p => p.IsConnected)
|
|
.Returns(true);
|
|
_ = _sessionMock.InSequence(sequence)
|
|
.Setup(p => p.TrySendMessage(It.IsAny<ChannelEofMessage>()))
|
|
.Returns(true)
|
|
.Callback<Message>(m => new Thread(() =>
|
|
{
|
|
Thread.Sleep(50);
|
|
_sessionMock.Raise(s => s.ChannelEofReceived += null,
|
|
new MessageEventArgs<ChannelEofMessage>(new ChannelEofMessage(_localChannelNumber)));
|
|
}).Start());
|
|
_ = _sessionMock.InSequence(sequence)
|
|
.Setup(p => p.IsConnected)
|
|
.Returns(true);
|
|
_ = _sessionMock.InSequence(sequence)
|
|
.Setup(p => p.TrySendMessage(It.IsAny<ChannelCloseMessage>()))
|
|
.Returns(true)
|
|
.Callback<Message>(m => new Thread(() =>
|
|
{
|
|
Thread.Sleep(50);
|
|
_sessionMock.Raise(s => s.ChannelCloseReceived += null,
|
|
new MessageEventArgs<ChannelCloseMessage>(new ChannelCloseMessage(_localChannelNumber)));
|
|
}).Start());
|
|
_ = _sessionMock.InSequence(sequence)
|
|
.Setup(p => p.ConnectionInfo)
|
|
.Returns(_connectionInfoMock.Object);
|
|
_ = _connectionInfoMock.InSequence(sequence)
|
|
.Setup(p => p.ChannelCloseTimeout)
|
|
.Returns(_channelCloseTimeout);
|
|
_ = _sessionMock.InSequence(sequence)
|
|
.Setup(p => p.TryWait(It.IsAny<EventWaitHandle>(), _channelCloseTimeout))
|
|
.Callback<WaitHandle, TimeSpan>((waitHandle, channelCloseTimeout) => waitHandle.WaitOne())
|
|
.Returns(WaitResult.Success);
|
|
|
|
var channelBindFinishedWaitHandle = new ManualResetEvent(false);
|
|
Socket handler = null;
|
|
ChannelDirectTcpip channel = null;
|
|
|
|
var localPortEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
|
|
using (var localPortListener = new AsyncSocketListener(localPortEndPoint))
|
|
{
|
|
localPortListener.Start();
|
|
|
|
localPortListener.Connected += socket =>
|
|
{
|
|
channel = new ChannelDirectTcpip(_sessionMock.Object,
|
|
_localChannelNumber,
|
|
_localWindowSize,
|
|
_localPacketSize);
|
|
channel.Open(_remoteHost, _port, _forwardedPortMock.Object, socket);
|
|
channel.Bind();
|
|
channel.Dispose();
|
|
|
|
handler = socket;
|
|
|
|
_ = channelBindFinishedWaitHandle.Set();
|
|
};
|
|
|
|
var client = new Socket(localPortEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
|
client.Connect(localPortEndPoint);
|
|
client.Shutdown(SocketShutdown.Send);
|
|
Assert.IsFalse(client.Connected);
|
|
|
|
_ = channelBindFinishedWaitHandle.WaitOne();
|
|
|
|
Assert.IsNotNull(handler);
|
|
Assert.IsFalse(handler.Connected);
|
|
|
|
_sessionMock.Verify(p => p.TrySendMessage(It.IsAny<ChannelEofMessage>()), Times.Once);
|
|
_sessionMock.Verify(p => p.TrySendMessage(It.IsAny<ChannelCloseMessage>()), Times.Once);
|
|
|
|
channel.Dispose();
|
|
|
|
_sessionMock.Verify(p => p.TrySendMessage(It.IsAny<ChannelEofMessage>()), Times.Once);
|
|
_sessionMock.Verify(p => p.TrySendMessage(It.IsAny<ChannelCloseMessage>()), Times.Once);
|
|
}
|
|
}
|
|
}
|
|
}
|