Files
felhom-agent/internal/felhomsshd/claim.go
T

40 lines
1.5 KiB
Go

package felhomsshd
import "fmt"
// Candidates is the ordered OOB-port candidate list (spike §2). First free wins; NEVER :22 or a
// random port. Package-var (not const) so tests can shrink it.
var Candidates = []int{8822, 2222, 8022, 62222}
// ErrPortsExhausted is returned when every candidate is busy — a LOUD failure [SF-4/trap 6], never a
// silent fallback to :22 or a random high port.
var ErrPortsExhausted = fmt.Errorf("felhomsshd: all candidate OOB ports are busy — refusing to fall back to :22 or a random port")
// portProbe reports whether a TCP port is free (nothing listening AND a real bind succeeds). Injected
// for tests; production impl = probeFree (ss + net.Listen).
type portProbe func(port int) bool
// claimPort returns the OOB port, mirroring the spike's shell algorithm:
// - if a persisted port exists AND is still free → keep it (idempotent, no thrash),
// - else the FIRST free candidate → persist + return,
// - else ErrPortsExhausted (LOUD).
//
// persist writes PortFile; readPersisted reads it. isFree is the probe. All injected for tests.
func claimPort(candidates []int, isFree portProbe, readPersisted func() (int, bool), persist func(int) error) (int, error) {
if cur, ok := readPersisted(); ok && cur != 22 && isFree(cur) {
return cur, nil
}
for _, p := range candidates {
if p == 22 {
continue // defensive: never :22
}
if isFree(p) {
if err := persist(p); err != nil {
return 0, fmt.Errorf("felhomsshd: persist claimed port %d: %w", p, err)
}
return p, nil
}
}
return 0, ErrPortsExhausted
}