using System; using System.Net; using System.Net.Sockets; using System.Threading; using Renci.SshNet.Abstractions; using Renci.SshNet.Common; using Renci.SshNet.Messages.Connection; namespace Renci.SshNet.Channels { /// /// Implements "direct-tcpip" SSH channel. /// internal partial class ChannelDirectTcpip : ClientChannel, IChannelDirectTcpip { private readonly object _socketLock = new object(); private EventWaitHandle _channelOpen = new AutoResetEvent(false); private EventWaitHandle _channelData = new AutoResetEvent(false); private IForwardedPort _forwardedPort; private Socket _socket; /// /// Initializes a new instance. /// /// The session. /// The local channel number. /// Size of the window. /// Size of the packet. public ChannelDirectTcpip(ISession session, uint localChannelNumber, uint localWindowSize, uint localPacketSize) : base(session, localChannelNumber, localWindowSize, localPacketSize) { } /// /// Gets the type of the channel. /// /// /// The type of the channel. /// public override ChannelTypes ChannelType { get { return ChannelTypes.DirectTcpip; } } public void Open(string remoteHost, uint port, IForwardedPort forwardedPort, Socket socket) { if (IsOpen) throw new SshException("Channel is already open."); if (!IsConnected) throw new SshException("Session is not connected."); _socket = socket; _forwardedPort = forwardedPort; _forwardedPort.Closing += ForwardedPort_Closing; var ep = socket.RemoteEndPoint as IPEndPoint; // open channel SendMessage(new ChannelOpenMessage(LocalChannelNumber, LocalWindowSize, LocalPacketSize, new DirectTcpipChannelInfo(remoteHost, port, ep.Address.ToString(), (uint) ep.Port))); // Wait for channel to open WaitOnHandle(_channelOpen); } /// /// Occurs as the forwarded port is being stopped. /// private void ForwardedPort_Closing(object sender, EventArgs eventArgs) { // signal to the client that we will not send anything anymore; this will also interrupt the // blocking receive in Bind if the client sends FIN/ACK in time // // if the FIN/ACK is not sent in time, the socket will be closed in Close(bool) ShutdownSocket(SocketShutdown.Send); } /// /// Binds channel to remote host. /// public void Bind() { // Cannot bind if channel is not open if (!IsOpen) return; var buffer = new byte[RemotePacketSize]; while (_socket != null && _socket.Connected) { try { var read = SocketAbstraction.Read(_socket, buffer, 0, buffer.Length, ConnectionInfo.Timeout); if (read > 0) { #if TUNING SendData(buffer, 0, read); #else SendMessage(new ChannelDataMessage(RemoteChannelNumber, buffer.Take(read).ToArray())); #endif } else { // client shut down the socket (but the server may still send data or an EOF) break; } } catch (SocketException exp) { switch (exp.SocketErrorCode) { case SocketError.WouldBlock: case SocketError.IOPending: case SocketError.NoBufferSpaceAvailable: // socket buffer is probably empty, wait and try again ThreadAbstraction.Sleep(30); break; case SocketError.ConnectionAborted: case SocketError.ConnectionReset: // connection was closed after receiving SSH_MSG_CHANNEL_CLOSE message break; case SocketError.Interrupted: // connection was closed because FIN/ACK was not received in time after // shutting down the (send part of the) socket break; default: throw; // throw any other error } } } // even though the client has disconnected, we still want to properly close the // channel // // we'll do this in in Close(bool) that way we have a single place from which we // send an SSH_MSG_CHANNEL_EOF message and wait for the SSH_MSG_CHANNEL_CLOSE // message } /// /// Closes the socket, hereby interrupting the blocking receive in . /// private void CloseSocket() { if (_socket == null) return; lock (_socketLock) { if (_socket == null) return; // closing a socket actually disposes the socket, so we can safely dereference // the field to avoid entering the lock again later _socket.Dispose(); _socket = null; } } /// /// Shuts down the socket. /// /// One of the values that specifies the operation that will no longer be allowed. private void ShutdownSocket(SocketShutdown how) { if (_socket == null) return; lock (_socketLock) { if (_socket == null || !_socket.Connected) return; _socket.Shutdown(how); } } /// /// Closes the channel, optionally waiting for the SSH_MSG_CHANNEL_CLOSE message to /// be received from the server. /// /// true to wait for the SSH_MSG_CHANNEL_CLOSE message to be received from the server; otherwise, false. protected override void Close(bool wait) { if (_forwardedPort != null) { _forwardedPort.Closing -= ForwardedPort_Closing; _forwardedPort = null; } // signal to the client that we will not send anything anymore; this will also interrupt the // blocking receive in Bind if the client sends FIN/ACK in time // // if the FIN/ACK is not sent in time, the socket will be closed after the channel is closed ShutdownSocket(SocketShutdown.Send); // close the SSH channel, and mark the channel closed base.Close(wait); // close the socket CloseSocket(); } /// /// Called when channel data is received. /// /// The data. protected override void OnData(byte[] data) { base.OnData(data); if (_socket != null && _socket.Connected) { lock (_socketLock) { if (_socket != null && _socket.Connected) { SocketAbstraction.Send(_socket, data, 0, data.Length); } } } } /// /// Called when channel is opened by the server. /// /// The remote channel number. /// Initial size of the window. /// Maximum size of the packet. protected override void OnOpenConfirmation(uint remoteChannelNumber, uint initialWindowSize, uint maximumPacketSize) { base.OnOpenConfirmation(remoteChannelNumber, initialWindowSize, maximumPacketSize); _channelOpen.Set(); } protected override void OnOpenFailure(uint reasonCode, string description, string language) { base.OnOpenFailure(reasonCode, description, language); _channelOpen.Set(); } /// /// Called when channel has no more data to receive. /// protected override void OnEof() { base.OnEof(); // the channel will send no more data, and hence it does not make sense to receive // any more data from the client to send to the remote party (and we surely won't // send anything anymore) // // this will also interrupt the blocking receive in Bind() ShutdownSocket(SocketShutdown.Send); } /// /// Called whenever an unhandled occurs in causing /// the message loop to be interrupted, or when an exception occurred processing a channel message. /// protected override void OnErrorOccured(Exception exp) { base.OnErrorOccured(exp); // signal to the client that we will not send anything anymore; this will also interrupt the // blocking receive in Bind if the client sends FIN/ACK in time // // if the FIN/ACK is not sent in time, the socket will be closed in Close(bool) ShutdownSocket(SocketShutdown.Send); } /// /// Called when the server wants to terminate the connection immmediately. /// /// /// The sender MUST NOT send or receive any data after this message, and /// the recipient MUST NOT accept any data after receiving this message. /// protected override void OnDisconnected() { base.OnDisconnected(); // the channel will accept or send no more data, and hence it does not make sense // to accept any more data from the client (and we surely won't send anything // anymore) // // // so lets signal to the client that we will not send or receive anything anymore // this will also interrupt the blocking receive in Bind() ShutdownSocket(SocketShutdown.Both); } protected override void Dispose(bool disposing) { // make sure we've unsubscribed from all session events and closed the channel // before we starting disposing base.Dispose(disposing); if (disposing) { if (_socket != null) { lock (_socketLock) { if (_socket != null) { _socket.Dispose(); _socket = null; } } } if (_channelOpen != null) { _channelOpen.Dispose(); _channelOpen = null; } if (_channelData != null) { _channelData.Dispose(); _channelData = null; } } } } }