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
@@ -99,6 +99,8 @@ public static class HostSecretMerge
|
||||
TagIds = MergeTags(ancestor.TagIds, local.TagIds, remote.TagIds, conflicts),
|
||||
PinnedPaths = MergePinnedPaths(
|
||||
ancestor.PinnedPaths, local.PinnedPaths, remote.PinnedPaths, conflicts),
|
||||
PortForwards = MergePortForwards(
|
||||
ancestor.PortForwards, local.PortForwards, remote.PortForwards, conflicts),
|
||||
RelayEnabled = Field(
|
||||
nameof(HostSecret.RelayEnabled),
|
||||
ancestor.RelayEnabled,
|
||||
@@ -342,6 +344,46 @@ public static class HostSecretMerge
|
||||
return PinnedPathList.Create(merge.Merged.Keys);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges the forwards per forward, so two people each adding one both keep theirs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Keyed by id and ordered the way <see cref="MergePinnedPaths"/> is: existing forwards, then this
|
||||
/// machine's additions, then the server's.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Unlike a tag or a pinned path, a conflict is reachable here.</b> The value under a key is the whole
|
||||
/// rule, so two machines changing one forward differently is a genuine clash. The server's version wins,
|
||||
/// as every scalar's does, and the other is recorded in OpenSSH's notation so the user can read what was
|
||||
/// lost. An edit on one side beats a removal on the other, which is the map merge's standing rule.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private static PortForwardList MergePortForwards(
|
||||
PortForwardList ancestor,
|
||||
PortForwardList local,
|
||||
PortForwardList remote,
|
||||
List<HostFieldConflict> conflicts)
|
||||
{
|
||||
var merge = ThreeWayMerge.Map(
|
||||
ancestor.ToIdMap(),
|
||||
local.ToIdMap(),
|
||||
remote.ToIdMap(),
|
||||
EqualityComparer<Guid>.Default);
|
||||
|
||||
foreach (var conflict in merge.Conflicts)
|
||||
{
|
||||
conflicts.Add(new HostFieldConflict(
|
||||
$"{nameof(HostSecret.PortForwards)}[{conflict.Key}]",
|
||||
conflict.DiscardedSide,
|
||||
conflict.Kept?.Notation ?? "removed",
|
||||
conflict.Discarded?.Notation ?? "removed",
|
||||
conflict.DiscardedWasRemoval));
|
||||
}
|
||||
|
||||
return PortForwardList.Create(merge.Merged.Values);
|
||||
}
|
||||
|
||||
private static string FormatChain(JumpChain chain) =>
|
||||
chain.Count == 0 ? "(none)" : string.Join(" → ", chain);
|
||||
}
|
||||
Reference in new issue
Block a user