-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support TCP/IP remote forward. (#352)
- Loading branch information
Showing
15 changed files
with
659 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// This file is part of Tmds.Ssh which is released under MIT. | ||
// See file LICENSE for full license details. | ||
|
||
namespace Tmds.Ssh; | ||
|
||
public struct RemoteConnection : IDisposable | ||
{ | ||
internal RemoteConnection(SshDataStream stream, RemoteEndPoint? remoteEndPoint) | ||
{ | ||
Stream = stream; | ||
RemoteEndPoint = remoteEndPoint; | ||
} | ||
|
||
public bool HasStream => Stream is not null; | ||
|
||
public SshDataStream MoveStream() | ||
{ | ||
var stream = Stream; | ||
Stream = null; | ||
return stream ?? throw new InvalidOperationException("There is no stream to obtain."); | ||
} | ||
|
||
public RemoteEndPoint? RemoteEndPoint { get; } | ||
public SshDataStream? Stream { get; private set; } | ||
|
||
public void Dispose() | ||
=> Stream?.Dispose(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// This file is part of Tmds.Ssh which is released under MIT. | ||
// See file LICENSE for full license details. | ||
|
||
using System.Diagnostics; | ||
using System.Threading.Channels; | ||
|
||
namespace Tmds.Ssh; | ||
|
||
public sealed class RemoteListener : IDisposable | ||
{ | ||
// Sentinel stop reasons. | ||
private static readonly Exception ConnectionClosed = new(); | ||
private static readonly Exception Disposed = new(); | ||
private static readonly Exception Stopped = new(); | ||
|
||
private readonly Channel<RemoteConnection> _connectionChannel; | ||
|
||
public RemoteEndPoint ListenEndPoint => _listenEndPoint ?? throw new InvalidOperationException("Not started"); | ||
|
||
private SshSession? _session; | ||
private RemoteEndPoint? _listenEndPoint; | ||
private Name _forwardType; | ||
private CancellationTokenRegistration _ctr; | ||
private Exception? _stopReason; | ||
|
||
public void Stop() | ||
=> Stop(Stopped); | ||
|
||
public void Dispose() | ||
=> Stop(Disposed); | ||
|
||
public async ValueTask<RemoteConnection> AcceptAsync(CancellationToken cancellationToken = default) | ||
{ | ||
while (true) | ||
{ | ||
if (!await _connectionChannel.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) | ||
{ | ||
Exception? stopReason = _stopReason; | ||
if (ReferenceEquals(stopReason, Stopped)) | ||
{ | ||
// return 'null' when the user called 'Stop' to indicate no more connections should be accepted. | ||
return default; | ||
} | ||
else if (ReferenceEquals(stopReason, Disposed)) | ||
{ | ||
throw new ObjectDisposedException(GetType().FullName); | ||
} | ||
else if (ReferenceEquals(stopReason, ConnectionClosed)) | ||
{ | ||
throw _session!.CreateCloseException(); | ||
} | ||
else | ||
{ | ||
throw new SshException($"{GetType().FullName} stopped due to an unexpected error.", stopReason); | ||
} | ||
} | ||
|
||
// TryRead may return false if we're competing with Stop. | ||
if (_connectionChannel.Reader.TryRead(out RemoteConnection remoteConnection)) | ||
{ | ||
Debug.Assert(remoteConnection.HasStream); | ||
if (remoteConnection.HasStream) | ||
{ | ||
return new RemoteConnection(remoteConnection.MoveStream(), remoteConnection.RemoteEndPoint); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private void Stop(Exception stopReason) | ||
{ | ||
if (Interlocked.CompareExchange(ref _stopReason, stopReason, null) != null) | ||
{ | ||
return; | ||
} | ||
|
||
if (_listenEndPoint is not null) | ||
{ | ||
_ctr.Dispose(); | ||
|
||
string? address = null; | ||
ushort port = 0; | ||
if (_listenEndPoint is RemoteIPListenEndPoint ipListenEndPoint) | ||
{ | ||
address = ipListenEndPoint.Address; | ||
port = (ushort)ipListenEndPoint.Port; | ||
} | ||
else | ||
{ | ||
Debug.Assert(false); | ||
} | ||
Debug.Assert(address is not null); | ||
_session?.StopRemoteForward(_forwardType, address, port); | ||
} | ||
|
||
_connectionChannel.Writer.Complete(); | ||
|
||
while (_connectionChannel.Reader.TryRead(out RemoteConnection connection)) | ||
{ | ||
Debug.Assert(connection.HasStream); | ||
connection.Dispose(); | ||
} | ||
} | ||
|
||
internal RemoteListener() | ||
{ | ||
_connectionChannel = Channel.CreateUnbounded<RemoteConnection>(); | ||
} | ||
|
||
private async Task OpenAsync(SshSession session, Name forwardType, string address, ushort port, CancellationToken cancellationToken) | ||
{ | ||
_session = session; | ||
_forwardType = forwardType; | ||
|
||
try | ||
{ | ||
port = await _session.StartRemoteForwardAsync(forwardType, address, port, _connectionChannel.Writer, cancellationToken).ConfigureAwait(false); | ||
_listenEndPoint = new RemoteIPListenEndPoint(address, port); | ||
_ctr = _session.ConnectionClosed.UnsafeRegister(o => ((RemoteListener)o!).Stop(ConnectionClosed), this); | ||
} | ||
catch (Exception ex) | ||
{ | ||
Stop(ex); | ||
|
||
throw; | ||
} | ||
} | ||
|
||
internal Task OpenTcpAsync(SshSession session, string address, int port, CancellationToken cancellationToken) | ||
{ | ||
ArgumentValidation.ValidateIPListenAddress(address); | ||
ArgumentValidation.ValidatePort(port, allowZero: true); | ||
|
||
return OpenAsync(session, AlgorithmNames.ForwardTcpIp, address, (ushort)port, cancellationToken); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.