Fallback read the peer list by exact key, so a partner whose tailnet name differs by one character rendered as UNREACHABLE

This commit is contained in:
Gmer4Lfe
2026-08-22 00:34:33 -04:00
parent 46239012b4
commit 5924aae1c0
2 changed files with 28 additions and 14 deletions
+4 -2
View File
@@ -167,8 +167,10 @@ function vv_fb_all(): array {
$nodes = [];
foreach ($hosts as $slot => $hostname) {
$isMe = ($slot === $currentHost || $currentHost === 'unknown');
$tsLabel = strtolower($hostname);
$ts = $tsPeers[$tsLabel] ?? ['online' => null, 'active' => false, 'ip' => null];
// Exact-key only, until now — see vv_pt_peer_lookup(). This mesh's conf name and tailnet
// name differ by one character, so HOST2 missed every lookup and the page rendered a
// partner that was up the whole time as state=UNREACHABLE.
$ts = vv_pt_peer_lookup($tsPeers, $hostname);
$ip = $ts['ip'] ?? null;
// State
+24 -12
View File
@@ -187,6 +187,29 @@ function vv_pt_ts_peers(): array {
return $peers;
}
// Resolve one host's peer record out of vv_pt_ts_peers().
//
// The tailnet name and the OS hostname are not the same string, and nothing keeps them in step:
// this mesh has master.conf saying "unRAID-Jayred36" while the tailnet device is
// "unraid-jayred365". An exact-key lookup finds nothing, and every consumer that did one showed
// a live partner as dark — the Partnership cards until that was fixed inline, and the Fallback
// page for as long as it has existed, where the miss became state=UNREACHABLE.
//
// Exact key first, then a single unambiguous prefix match in either direction. One candidate or
// none — server1 must never resolve to server10 because it happens to share a prefix, and this
// is never similarity scoring. Same rule as vv_resolve_tailscale_ip(), which applies it to
// `tailscale status` text rather than to the parsed peer array.
function vv_pt_peer_lookup(array $tsPeers, string $hostname): array {
$label = strtolower($hostname);
if (isset($tsPeers[$label])) return $tsPeers[$label];
$cand = [];
foreach ($tsPeers as $peerName => $peer) {
if (str_starts_with($peerName, $label) || str_starts_with($label, $peerName)) $cand[] = $peer;
}
return count($cand) === 1 ? $cand[0] : ['online' => null, 'active' => false, 'ip' => null];
}
// ── SSH helper — run a single command on a remote host ────────────────────────
// Multiplexed, because this page makes several of these per render — a state read per partner,
@@ -457,18 +480,7 @@ function vv_pt_nodes(): array {
//
// Same rule as vv_resolve_tailscale_ip(), and the same refusal: one candidate or none.
// server1 must never resolve to server10 because it happens to share a prefix.
$tsLabel = strtolower($hostname);
$ts = $tsPeers[$tsLabel] ?? null;
if ($ts === null) {
$cand = [];
foreach ($tsPeers as $peerName => $peer) {
if (str_starts_with($peerName, $tsLabel) || str_starts_with($tsLabel, $peerName)) {
$cand[] = $peer;
}
}
if (count($cand) === 1) $ts = $cand[0];
}
if ($ts === null) $ts = ['online' => null, 'active' => false, 'ip' => null];
$ts = vv_pt_peer_lookup($tsPeers, $hostname);
// Fallback state
$fbState = 'UNKNOWN';