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
@@ -3832,11 +3832,13 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
IdentityLabel = e.IdentityLabel,
|
||||
};
|
||||
AdoptTab(adopted);
|
||||
StartForwards(adopted, e);
|
||||
RefreshConnectedHosts();
|
||||
return;
|
||||
}
|
||||
|
||||
tab.Opened(e.SessionId);
|
||||
StartForwards(tab, e);
|
||||
|
||||
// From this moment, not from when the tab first appeared — connecting is not open, and the session
|
||||
// shell's elapsed timer is about a shell that is actually running.
|
||||
@@ -3858,6 +3860,47 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
TerminalSessionOpened?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts the host's saved forwards on a tab whose session has just opened, and starts every one.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// All at once and none awaited: each row reports its own outcome, and the terminal is already usable
|
||||
/// while a remote forward waits for the server to answer. See <see cref="PortForwardViewModel"/> for why
|
||||
/// one failing stops nothing else.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The session id is captured into the start delegate rather than read off the tab when a row is
|
||||
/// toggled later. A reconnect puts a new id on the same tab, and a row from the old session that reached
|
||||
/// for the tab's current id would start its listener on a connection it was never part of.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// ◆ <b>Not on the phone.</b> This view model is shared, and the forwarding would work there unchanged —
|
||||
/// but that head has no surface listing a tab's forwards, and listeners opened on a phone that never shows
|
||||
/// them are exactly the invisible state this codebase refuses elsewhere. Its More screen says forwarding
|
||||
/// is desktop-only for now. A runtime check for the reason <c>RendererNeverStarted</c> gives.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private void StartForwards(TerminalTabViewModel tab, TerminalSessionEventArgs e)
|
||||
{
|
||||
if (OperatingSystem.IsAndroid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sessionId = e.SessionId;
|
||||
|
||||
tab.ReplaceForwards(e.Forwards.Select(rule => new PortForwardViewModel(
|
||||
rule,
|
||||
(spec, cancellationToken) => workspace.StartForwardAsync(sessionId, spec, cancellationToken),
|
||||
post)));
|
||||
|
||||
foreach (var forward in tab.Forwards)
|
||||
{
|
||||
_ = forward.StartAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Answers a connection that did not become a session.
|
||||
/// </summary>
|
||||
@@ -4658,6 +4701,17 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
vault.EditSelectedHostCommand.Execute(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The sidebar's FORWARDS affordance: opens the active tab's host for editing, where forwards are added.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The same route <see cref="PinFolderFromSidebar"/> takes, because a forward is saved on the host for
|
||||
/// the same reason a pin is. A forward added there starts with the next session to the host, not this
|
||||
/// one — the editor says so — since this session's forwards were started from the host as it was.
|
||||
/// </remarks>
|
||||
[RelayCommand]
|
||||
private void AddForwardFromSidebar() => PinFolderFromSidebar();
|
||||
|
||||
/// <summary>
|
||||
/// The SFTP tab row's click: makes one of the terminal's tabs the SFTP surface's browsed host.
|
||||
/// </summary>
|
||||
|
||||
Reference in new issue
Block a user