Public Access
Add port forwarding: local, remote and dynamic, per terminal session
A host can now carry port forwards (-L, -R, -D), saved inside its encrypted payload as RelayTicketRequest.PortForwardId already assumed, so they sync and merge per forward like tags and pinned paths. The host schema moves to version 8 only for hosts that carry forwards. ISshConnection gains StartForwardAsync over SSH.NET's forwarded ports. Every listener binds loopback and there is no bind-address field: a forward is something any member of a shared keychain can edit. A remote forward's target is resolved here, IPv4 first, rather than by SSH.NET's constructor, which takes the first DNS answer and can send ::1. Forwards start with every terminal opened to the host and stop with its connection; a reconnect starts them again. The session sidebar lists them with state, failure reason and an on/off switch, the status bar counts the listening ones, and the host editor and detail pane gain a PORT FORWARDS section. A forward that cannot start fails alone. The phone does not start forwards yet: it has no surface showing what is listening, and its More screen now says so. No byte counts, since SSH.NET keeps none.
This commit is contained in:
1 parent
c8848ec1e3
commit
f05deb4fbc
36 files changed
+2776
-42
No files matched your search
@@ -599,6 +599,53 @@ public sealed class TerminalWorkspaceTests
|
||||
await WaitUntilAsync(() => Volatile.Read(ref reattachedCount) > 0);
|
||||
}
|
||||
|
||||
// ---- Port forwards ----
|
||||
|
||||
[Fact]
|
||||
public async Task AForward_RunsOverTheConnectionOfTheSessionItNames()
|
||||
{
|
||||
var connections = new FakeConnectionFactory(blockShellReads: true);
|
||||
|
||||
await using var workspace = CreateWorkspace(connections);
|
||||
|
||||
await workspace.OpenSessionAsync(
|
||||
Request(), TerminalSize.Default, progress: null, TestContext.Current.CancellationToken);
|
||||
var second = await workspace.OpenSessionAsync(
|
||||
Request(), TerminalSize.Default, progress: null, TestContext.Current.CancellationToken);
|
||||
|
||||
var spec = new SshPortForwardSpec(SshPortForwardKind.Local, 5432, "db.internal", 5432);
|
||||
|
||||
await using var forward = await workspace.StartForwardAsync(
|
||||
second, spec, TestContext.Current.CancellationToken);
|
||||
|
||||
forward.Spec.ShouldBe(spec);
|
||||
connections.Connections[0].Forwards.ShouldBeEmpty();
|
||||
connections.Connections[1].Forwards.ShouldBe([spec]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AForwardOnASessionThatIsNotRunning_IsRefused()
|
||||
{
|
||||
var connections = new FakeConnectionFactory(bytesPerShell: 0);
|
||||
|
||||
await using var workspace = CreateWorkspace(connections);
|
||||
|
||||
var ended = await workspace.OpenSessionAsync(
|
||||
Request(), TerminalSize.Default, progress: null, TestContext.Current.CancellationToken);
|
||||
|
||||
await WaitUntilAsync(() => !workspace.IsSessionLive(ended));
|
||||
|
||||
var spec = new SshPortForwardSpec(SshPortForwardKind.Dynamic, 1080);
|
||||
|
||||
await Should.ThrowAsync<InvalidOperationException>(async () =>
|
||||
await workspace.StartForwardAsync(ended, spec, TestContext.Current.CancellationToken));
|
||||
|
||||
await Should.ThrowAsync<InvalidOperationException>(async () =>
|
||||
await workspace.StartForwardAsync(999, spec, TestContext.Current.CancellationToken));
|
||||
|
||||
connections.Connections[0].Forwards.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
// ---- Helpers ----
|
||||
|
||||
/// <remarks>
|
||||
|
||||
Reference in new issue
Block a user