Skip to content

Commit 53b0c99

Browse files
committed
WIP: Unix Socket Local & Remote Forwarding
netstandard2.1 includes Unix-Socket-Support, which can also be used on Windows OS since 1803. Add UnixDomainSocketEndPoint Forwarding to Remote and Local Forwardings, as in OpenSSH.
1 parent 17f02b8 commit 53b0c99

20 files changed

+1349
-28
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ project.lock.json
2222

2323
# Build outputs
2424
build/target/
25+
26+
# Rider Directory
27+
.idea/

src/Renci.SshNet/Abstractions/SocketAbstraction.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,21 @@ public static Socket Connect(IPEndPoint remoteEndpoint, TimeSpan connectTimeout)
5454
return socket;
5555
}
5656

57+
#if FEATURE_UNIX_SOCKETS
58+
public static Socket Connect(UnixDomainSocketEndPoint remoteEndpoint, TimeSpan connectTimeout)
59+
{
60+
var socket = new Socket(remoteEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Unspecified);
61+
ConnectCore(socket, remoteEndpoint, connectTimeout, true);
62+
return socket;
63+
}
64+
#endif
65+
5766
public static void Connect(Socket socket, IPEndPoint remoteEndpoint, TimeSpan connectTimeout)
5867
{
5968
ConnectCore(socket, remoteEndpoint, connectTimeout, false);
6069
}
6170

62-
private static void ConnectCore(Socket socket, IPEndPoint remoteEndpoint, TimeSpan connectTimeout, bool ownsSocket)
71+
private static void ConnectCore(Socket socket, EndPoint remoteEndpoint, TimeSpan connectTimeout, bool ownsSocket)
6372
{
6473
#if FEATURE_SOCKET_EAP
6574
var connectCompleted = new ManualResetEvent(false);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
using System;
2+
using System.Net;
3+
using System.Net.Sockets;
4+
using System.Threading;
5+
using Renci.SshNet.Abstractions;
6+
using Renci.SshNet.Common;
7+
using Renci.SshNet.Messages.Connection;
8+
9+
namespace Renci.SshNet.Channels
10+
{
11+
/// <summary>
12+
/// Implements "[email protected]" SSH channel.
13+
/// </summary>
14+
internal class ChannelDirectStreamLocal : ClientChannel, IChannelDirectStreamLocal
15+
{
16+
private readonly object _socketLock = new object();
17+
18+
private EventWaitHandle _channelOpen = new AutoResetEvent(false);
19+
private EventWaitHandle _channelData = new AutoResetEvent(false);
20+
private IForwardedPort _forwardedPort;
21+
private Socket _socket;
22+
23+
/// <summary>
24+
/// Initializes a new <see cref="ChannelDirectStreamLocal"/> instance.
25+
/// </summary>
26+
/// <param name="session">The session.</param>
27+
/// <param name="localChannelNumber">The local channel number.</param>
28+
/// <param name="localWindowSize">Size of the window.</param>
29+
/// <param name="localPacketSize">Size of the packet.</param>
30+
public ChannelDirectStreamLocal(ISession session, uint localChannelNumber, uint localWindowSize, uint localPacketSize)
31+
: base(session, localChannelNumber, localWindowSize, localPacketSize)
32+
{
33+
}
34+
35+
/// <summary>
36+
/// Gets the type of the channel.
37+
/// </summary>
38+
/// <value>
39+
/// The type of the channel.
40+
/// </value>
41+
public override ChannelTypes ChannelType
42+
{
43+
get { return ChannelTypes.DirectStreamLocal; }
44+
}
45+
46+
public void Open(string remoteSocket, IForwardedPort forwardedPort, Socket socket)
47+
{
48+
if (IsOpen)
49+
throw new SshException("Channel is already open.");
50+
if (!IsConnected)
51+
throw new SshException("Session is not connected.");
52+
53+
lock (_socketLock)
54+
{
55+
_socket = socket;
56+
}
57+
_forwardedPort = forwardedPort;
58+
_forwardedPort.Closing += ForwardedPort_Closing;
59+
60+
var originatorAddress = "";
61+
var originatorPort = (uint)0;
62+
63+
if (socket.RemoteEndPoint is IPEndPoint)
64+
{
65+
var ep = (IPEndPoint)socket.RemoteEndPoint;
66+
originatorAddress = ep.Address.ToString();
67+
originatorPort = (uint) ep.Port;
68+
}
69+
70+
SendMessage(new ChannelOpenMessage(LocalChannelNumber, LocalWindowSize, LocalPacketSize,
71+
new DirectStreamLocalChannelInfo(remoteSocket, originatorAddress, originatorPort)));
72+
// Wait for channel to open
73+
WaitOnHandle(_channelOpen);
74+
}
75+
76+
/// <summary>
77+
/// Occurs as the forwarded port is being stopped.
78+
/// </summary>
79+
private void ForwardedPort_Closing(object sender, EventArgs eventArgs)
80+
{
81+
// signal to the client that we will not send anything anymore; this should also interrupt the
82+
// blocking receive in Bind if the client sends FIN/ACK in time
83+
ShutdownSocket(SocketShutdown.Send);
84+
85+
// if the FIN/ACK is not sent in time by the remote client, then interrupt the blocking receive
86+
// by closing the socket
87+
CloseSocket();
88+
}
89+
90+
/// <summary>
91+
/// Binds channel to remote host.
92+
/// </summary>
93+
public void Bind()
94+
{
95+
// Cannot bind if channel is not open
96+
if (!IsOpen)
97+
return;
98+
99+
var buffer = new byte[RemotePacketSize];
100+
101+
SocketAbstraction.ReadContinuous(_socket, buffer, 0, buffer.Length, SendData);
102+
103+
// even though the client has disconnected, we still want to properly close the
104+
// channel
105+
//
106+
// we'll do this in in Close() - invoked through Dispose(bool) - that way we have
107+
// a single place from which we send an SSH_MSG_CHANNEL_EOF message and wait for
108+
// the SSH_MSG_CHANNEL_CLOSE message
109+
}
110+
111+
/// <summary>
112+
/// Closes the socket, hereby interrupting the blocking receive in <see cref="Bind()"/>.
113+
/// </summary>
114+
private void CloseSocket()
115+
{
116+
if (_socket == null)
117+
return;
118+
119+
lock (_socketLock)
120+
{
121+
if (_socket == null)
122+
return;
123+
124+
// closing a socket actually disposes the socket, so we can safely dereference
125+
// the field to avoid entering the lock again later
126+
_socket.Dispose();
127+
_socket = null;
128+
}
129+
}
130+
131+
/// <summary>
132+
/// Shuts down the socket.
133+
/// </summary>
134+
/// <param name="how">One of the <see cref="SocketShutdown"/> values that specifies the operation that will no longer be allowed.</param>
135+
private void ShutdownSocket(SocketShutdown how)
136+
{
137+
if (_socket == null)
138+
return;
139+
140+
lock (_socketLock)
141+
{
142+
if (!_socket.IsConnected())
143+
return;
144+
145+
try
146+
{
147+
_socket.Shutdown(how);
148+
}
149+
catch (SocketException ex)
150+
{
151+
// TODO: log as warning
152+
DiagnosticAbstraction.Log("Failure shutting down socket: " + ex);
153+
}
154+
}
155+
}
156+
157+
/// <summary>
158+
/// Closes the channel, waiting for the SSH_MSG_CHANNEL_CLOSE message to be received from the server.
159+
/// </summary>
160+
protected override void Close()
161+
{
162+
var forwardedPort = _forwardedPort;
163+
if (forwardedPort != null)
164+
{
165+
forwardedPort.Closing -= ForwardedPort_Closing;
166+
_forwardedPort = null;
167+
}
168+
169+
// signal to the client that we will not send anything anymore; this will also interrupt the
170+
// blocking receive in Bind if the client sends FIN/ACK in time
171+
//
172+
// if the FIN/ACK is not sent in time, the socket will be closed after the channel is closed
173+
ShutdownSocket(SocketShutdown.Send);
174+
175+
// close the SSH channel
176+
base.Close();
177+
178+
// close the socket
179+
CloseSocket();
180+
}
181+
182+
/// <summary>
183+
/// Called when channel data is received.
184+
/// </summary>
185+
/// <param name="data">The data.</param>
186+
protected override void OnData(byte[] data)
187+
{
188+
base.OnData(data);
189+
190+
if (_socket != null)
191+
{
192+
lock (_socketLock)
193+
{
194+
if (_socket.IsConnected())
195+
{
196+
SocketAbstraction.Send(_socket, data, 0, data.Length);
197+
}
198+
}
199+
}
200+
}
201+
202+
/// <summary>
203+
/// Called when channel is opened by the server.
204+
/// </summary>
205+
/// <param name="remoteChannelNumber">The remote channel number.</param>
206+
/// <param name="initialWindowSize">Initial size of the window.</param>
207+
/// <param name="maximumPacketSize">Maximum size of the packet.</param>
208+
protected override void OnOpenConfirmation(uint remoteChannelNumber, uint initialWindowSize, uint maximumPacketSize)
209+
{
210+
base.OnOpenConfirmation(remoteChannelNumber, initialWindowSize, maximumPacketSize);
211+
212+
_channelOpen.Set();
213+
}
214+
215+
protected override void OnOpenFailure(uint reasonCode, string description, string language)
216+
{
217+
base.OnOpenFailure(reasonCode, description, language);
218+
219+
_channelOpen.Set();
220+
}
221+
222+
/// <summary>
223+
/// Called when channel has no more data to receive.
224+
/// </summary>
225+
protected override void OnEof()
226+
{
227+
base.OnEof();
228+
229+
// the channel will send no more data, and hence it does not make sense to receive
230+
// any more data from the client to send to the remote party (and we surely won't
231+
// send anything anymore)
232+
//
233+
// this will also interrupt the blocking receive in Bind()
234+
ShutdownSocket(SocketShutdown.Send);
235+
}
236+
237+
/// <summary>
238+
/// Called whenever an unhandled <see cref="Exception"/> occurs in <see cref="Session"/> causing
239+
/// the message loop to be interrupted, or when an exception occurred processing a channel message.
240+
/// </summary>
241+
protected override void OnErrorOccured(Exception exp)
242+
{
243+
base.OnErrorOccured(exp);
244+
245+
// signal to the client that we will not send anything anymore; this will also interrupt the
246+
// blocking receive in Bind if the client sends FIN/ACK in time
247+
//
248+
// if the FIN/ACK is not sent in time, the socket will be closed in Close(bool)
249+
ShutdownSocket(SocketShutdown.Send);
250+
}
251+
252+
/// <summary>
253+
/// Called when the server wants to terminate the connection immmediately.
254+
/// </summary>
255+
/// <remarks>
256+
/// The sender MUST NOT send or receive any data after this message, and
257+
/// the recipient MUST NOT accept any data after receiving this message.
258+
/// </remarks>
259+
protected override void OnDisconnected()
260+
{
261+
base.OnDisconnected();
262+
263+
// the channel will accept or send no more data, and hence it does not make sense
264+
// to accept any more data from the client (and we surely won't send anything
265+
// anymore)
266+
//
267+
// so lets signal to the client that we will not send or receive anything anymore
268+
// this will also interrupt the blocking receive in Bind()
269+
ShutdownSocket(SocketShutdown.Both);
270+
}
271+
272+
protected override void Dispose(bool disposing)
273+
{
274+
// make sure we've unsubscribed from all session events and closed the channel
275+
// before we starting disposing
276+
base.Dispose(disposing);
277+
278+
if (disposing)
279+
{
280+
if (_socket != null)
281+
{
282+
lock (_socketLock)
283+
{
284+
var socket = _socket;
285+
if (socket != null)
286+
{
287+
_socket = null;
288+
socket.Dispose();
289+
}
290+
}
291+
}
292+
293+
var channelOpen = _channelOpen;
294+
if (channelOpen != null)
295+
{
296+
_channelOpen = null;
297+
channelOpen.Dispose();
298+
}
299+
300+
var channelData = _channelData;
301+
if (channelData != null)
302+
{
303+
_channelData = null;
304+
channelData.Dispose();
305+
}
306+
}
307+
}
308+
}
309+
}

0 commit comments

Comments
 (0)