diff --git a/tests/test_link.py b/tests/test_link.py index 116b46b..3513f34 100644 --- a/tests/test_link.py +++ b/tests/test_link.py @@ -382,7 +382,7 @@ class Connecting(unittest.TestCase): self.assertEqual(rows["::1"]["ssh"], "wrong") self.assertEqual(rows["nothing.invalid"]["state"], "unresolved") self.assertEqual(self.routes, []) - self.assertTrue(all("ControlPath=none" in c for c in self.calls())) + self.assertTrue(all("ControlPath=none" in c for c in self.calls() if "-G" not in c)) def test_switching_to_a_headset_that_never_answers_stops_using_the_last_one(self): self.device("localhost") @@ -483,6 +483,16 @@ class Connecting(unittest.TestCase): self.assertIsNotNone(proc.poll()) self.assertIsNone(self.link.master) + def test_test_now_goes_through_a_jump_host(self): + d = self.device("10.99.99.99") + self.pin(d["id"]) + self.hosts({"10.99.99.99": "ok"}) + with mock.patch.object(fl, "ssh_g", return_value=("frame-t", 22, "steamos", True)): + self.link.test(d["id"]) + row = self.link.snapshot()["tests"][d["id"]]["rows"][0] + self.assertEqual(row["ssh"], "ok", row) + self.assertIn("jump host", row["detail"]) + def test_devices_api_checks_everything(self): d = self.device("localhost") bad = [{"action": "address-add", "id": d["id"], "host": "-oProxyCommand=touch /tmp/x"}, diff --git a/ui/frame_link.py b/ui/frame_link.py index f13c86a..c4fc46c 100644 --- a/ui/frame_link.py +++ b/ui/frame_link.py @@ -926,12 +926,18 @@ class Link: put() net = self.state["network"] or {} + proxied = ssh_g(device["alias"])[3] + def one(i, a): - res = probe(a["host"], device["port"], update=lambda **f: (rows[i].update(f), put())) + if proxied: # through a jump host: a direct probe says nothing, ssh itself is the test + res = {"state": "answered", "detail": "Through a jump host", "ip": None, "rtt_ms": None} + else: + res = probe(a["host"], device["port"], update=lambda **f: (rows[i].update(f), put())) rows[i].update({k: res.get(k) for k in ("state", "detail", "ip", "rtt_ms")}) put() if res["state"] != "answered": return + lead = f"Answered in {res['rtt_ms']:g} ms" if res.get("rtt_ms") is not None else "Through the jump host" rows[i]["ssh"] = "checking" put() argv = [*self.mux_base[:3], "-o", "ControlPath=none", "-o", "ConnectTimeout=8", @@ -942,10 +948,10 @@ class Link: errors="replace", timeout=20) err = r.stderr.strip() if r.returncode == 0: - rows[i].update(ssh="ok", detail=f"Answered in {res['rtt_ms']:g} ms · SSH works") + rows[i].update(ssh="ok", detail=f"{lead} · SSH works") self.reg.record_success(device_id, a["host"], net.get("id"), res["rtt_ms"]) elif UNKNOWN.search(err): - rows[i].update(ssh="unpinned", detail=f"Answered in {res['rtt_ms']:g} ms · identity not saved yet") + rows[i].update(ssh="unpinned", detail=f"{lead} · identity not saved yet") elif CHANGED.search(err): rows[i].update(ssh="wrong", detail="Answered as a different headset") elif DENIED.search(err): diff --git a/ui/index.html b/ui/index.html index 5492f8a..06a6486 100644 --- a/ui/index.html +++ b/ui/index.html @@ -2676,7 +2676,6 @@ $("devSel").onchange = e => useDevice(e.target.value); async function useDevice(id) { const d = dv.list.find(x => x.id === id); if (!d || d.active) return; - devGen++; // from here on, answers about the last headset are dropped const res = await act(`Switch to ${d.name}`, () => api("/api/devices", { action: "use", id })); if (!res) { // refused (an install is running): show the headset still in use const current = dv.list.find(x => x.active);