Devices: fixes from review round 21

- Behind a jump host, a forward it couldn't open moves on to the next address;
  only a refused key stops (judged by ssh's words, not the step).
- Add a headset suggests an alias no Host in ~/.ssh/config already uses.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
saphidandClaude Opus 5.5 committed 2026-09-29 00:44:18 +10:00
1 parent 42b51afc5a
commit 8bd8d63546
2 files changed
+25 -3

No files matched your search

+11
View File
@@ -317,6 +317,15 @@ class Connecting(unittest.TestCase):
self.assertFalse(fl.Link.is_target('Authenticated to bastion ([1.2.3.4]:22) using "publickey".', opts, "frame")) self.assertFalse(fl.Link.is_target('Authenticated to bastion ([1.2.3.4]:22) using "publickey".', opts, "frame"))
self.assertTrue(fl.Link.is_target('Authenticated to 10.0.0.5 ([10.0.0.5]:22) using "publickey".', opts, "frame")) self.assertTrue(fl.Link.is_target('Authenticated to 10.0.0.5 ([10.0.0.5]:22) using "publickey".', opts, "frame"))
def test_a_forward_the_jump_host_couldnt_open_tries_the_next_address(self):
said = ["Authenticated to bastion ([1.2.3.4]:22) using \"publickey\".",
"channel 0: open failed: connect failed: Connection refused", "stdio forwarding failed"]
self.link.state["stages"] = [{"id": i, "state": "pending", "started": None, "ended": None, "detail": ""}
for i, _ in fl.STAGES]
self.assertEqual(self.link.failed("login", said, False, "frame"), "next")
self.assertEqual(self.link.failed("login", ["steamos@frame: Permission denied (publickey)."], False, "frame"),
"stop")
def test_probes_from_an_earlier_attempt_leave_the_new_rows_alone(self): def test_probes_from_an_earlier_attempt_leave_the_new_rows_alone(self):
self.link.state.update(attempt=2, probes=[{"host": "b", "state": "waiting"}]) self.link.state.update(attempt=2, probes=[{"host": "b", "state": "waiting"}])
self.link.probe_update(0, 1, state="answered", ip="10.0.0.2") self.link.probe_update(0, 1, state="answered", ip="10.0.0.2")
@@ -528,6 +537,8 @@ class Connecting(unittest.TestCase):
self.assertIn("frame-9", out["message"]) self.assertIn("frame-9", out["message"])
self.assertEqual(out["active"], d["id"]) self.assertEqual(out["active"], d["id"])
self.assertEqual(fl.next_alias(self.link), "frame") self.assertEqual(fl.next_alias(self.link), "frame")
(self.dir / "ssh" / "config").write_text("Host frame lab-*\n HostName 10.0.0.7\n") # someone's own `frame`
self.assertEqual(fl.next_alias(self.link), "frame-2")
@unittest.skipIf(os.name == "nt", "the stand-in ssh is a POSIX script") @unittest.skipIf(os.name == "nt", "the stand-in ssh is a POSIX script")
+14 -3
View File
@@ -914,7 +914,11 @@ class Link:
self.fail("identity", "This address answered as a different headset (its SSH identity doesn't match). " self.fail("identity", "This address answered as a different headset (its SSH identity doesn't match). "
"If SteamOS was reinstalled, use Forget Identity on the Devices tab.", text) "If SteamOS was reinstalled, use Forget Identity on the Devices tab.", text)
return "next" return "next"
if step == "login" or re.search(r"Permission denied", text): # A refused key is the same at every address: stop. (Judged by ssh's own words, not
# the step: a jump host's progress lines look like the headset's.) A forward that
# a jump host couldn't open is about this address only: try the next.
forward = re.search(r"open failed|forwarding failed|Connection refused|Connection closed|timed out", text)
if re.search(r"Permission denied", text) and not forward:
self.fail("login", self.explain(text) or "The headset didn't accept this computer's key.", text) self.fail("login", self.explain(text) or "The headset didn't accept this computer's key.", text)
return "stop" return "stop"
if step == "connected": if step == "connected":
@@ -1141,8 +1145,15 @@ def devices_action(link, body, open_setup, busy=lambda: 0):
def next_alias(link): def next_alias(link):
taken = {d["alias"] for d in link.reg.devices()} | {b["alias"] for b in frame_devices.parse_blocks( """A free alias for a new headset: not one Frame Control knows, nor any `Host` name
frame_devices.read_config())} already in ~/.ssh/config (Set Up Connection's block would shadow it)."""
text = frame_devices.read_config()
taken = {d["alias"] for d in link.reg.devices()} | {b["alias"] for b in frame_devices.parse_blocks(text)}
taken |= {a for a in (link.session_alias, link.override, link.active_device().get("alias")) if a}
for line in text.splitlines():
f = line.split()
if f and f[0].lower() == "host":
taken |= {name for name in f[1:] if not any(c in name for c in "*?!")}
if "frame" not in taken: if "frame" not in taken:
return "frame" return "frame"
n = 2 n = 2