package keymgr import "ssh-access-tui/internal/model" // AllocatedForward is a model.Forward together with the local port it will // be bound to on the user's machine. type AllocatedForward struct { model.Forward LocalPort int } // localPortBase is where fallback local ports are handed out from once a // forward's natural remote port is already taken locally by another // forward. Chosen high enough to avoid colliding with well-known ports the // user's own machine might already be using. const localPortBase = 20000 // AllocateLocalPorts assigns every forward a local port, all of which the // user opens at once over a single ssh connection (one LocalForward line // per forward — see RenderClientConfig). This is the actual answer to // "avoid collisions when forwarding everything simultaneously": many hosts // commonly expose the same remote port (e.g. 22, or an app port like 7888 // repeated across ten hosts), but only one of them can bind to that same // port number locally. Each forward keeps its remote port as the local // port when that's still free (so the common case reads naturally, e.g. // remote 7888 -> local 7888), and falls back to the next free port from // localPortBase upward on collision. Assignment is a pure function of the // input order and set, so re-exporting an unchanged host list reproduces // the same local ports. func AllocateLocalPorts(forwards []model.Forward) []AllocatedForward { used := make(map[int]bool, len(forwards)) next := localPortBase out := make([]AllocatedForward, 0, len(forwards)) for _, f := range forwards { lp := f.Port if lp <= 0 || lp > 65535 || used[lp] { for used[next] { next++ } lp = next next++ } used[lp] = true out = append(out, AllocatedForward{Forward: f, LocalPort: lp}) } return out }