fix(config_flow): always retry historically-confirmed DTLS ports (#192)
The liveness sweep's ICMP-based verdict isn't trustworthy on every network path -- a segregated-VLAN report showed it calling three closed ports live while missing the one port a concurrent nmap scan found genuinely open|filtered, which also happened to be one of our two historically confirmed DTLS ports. Rather than trust a "not live" verdict against that prior, always give PREFERRED_PROBE_PORTS a real handshake attempt even when the sweep excludes them, bounded to at most those two extra attempts. This is a stop-gap for the reported failure mode, not a full fix for the sweep's underlying unreliability -- left a comment on the issue with the diagnosis and flagged the sweep itself for a deeper redesign.
This commit is contained in:
@@ -194,7 +194,17 @@ def _find_live_ports(host: str, ports: list[int], timeout: float) -> list[int]:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return _order_candidates(live)
|
||||
# The sweep's ICMP-based verdict isn't reliable on every network path --
|
||||
# issue #192 captured a segregated-VLAN device where it called three
|
||||
# ports live that a concurrent nmap scan showed as closed, while the
|
||||
# port nmap found genuinely open|filtered (49154, one of our historically
|
||||
# confirmed ports) never showed up as live at all. Rather than trust a
|
||||
# wrong "not live" verdict on a port we already have strong prior
|
||||
# evidence for, always give the historically-confirmed ports a real
|
||||
# handshake attempt too. Bounded cost: at most len(PREFERRED_PROBE_PORTS)
|
||||
# extra handshakes, only when the sweep disagrees with the prior.
|
||||
rescued = [p for p in PREFERRED_PROBE_PORTS if p in ports and p not in live]
|
||||
return _order_candidates(live + rescued)
|
||||
|
||||
|
||||
def _is_placeholder_serial(serial: str) -> bool:
|
||||
|
||||
@@ -105,6 +105,38 @@ def test_find_live_ports_detects_silent_port(socket_enabled) -> None:
|
||||
assert result == [live_port]
|
||||
|
||||
|
||||
def test_find_live_ports_rescues_preferred_ports_the_sweep_missed(
|
||||
socket_enabled,
|
||||
) -> None:
|
||||
"""Issue #192: a segregated VLAN made the ICMP-based sweep call three
|
||||
closed ports live while missing the one port (49154, a historically
|
||||
confirmed DTLS port) that nmap showed as genuinely open|filtered. The
|
||||
sweep's verdict on a preferred port shouldn't be trusted blindly --
|
||||
it must always come back as a candidate even if the sweep marked it
|
||||
dead, so the config flow gets a real handshake attempt against it."""
|
||||
import socket
|
||||
|
||||
from custom_components.localthings.config_flow import _find_live_ports
|
||||
|
||||
# Bind and immediately close a socket on 49154 so loopback refuses
|
||||
# datagrams to it -- standing in for the sweep wrongly ruling out a
|
||||
# port we have strong prior evidence for.
|
||||
reserved = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
reserved.bind(('127.0.0.1', 49154))
|
||||
reserved.close()
|
||||
|
||||
live_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
live_sock.bind(('127.0.0.1', 0))
|
||||
live_port = live_sock.getsockname()[1]
|
||||
|
||||
try:
|
||||
result = _find_live_ports('127.0.0.1', [49154, live_port], 0.8)
|
||||
finally:
|
||||
live_sock.close()
|
||||
|
||||
assert set(result) == {49154, live_port}
|
||||
|
||||
|
||||
async def test_probe_uses_discovered_low_port(hass: HomeAssistant, monkeypatch) -> None:
|
||||
"""A device that only answers on 49153 — outside the historical
|
||||
49154/49155 pair — is found by the liveness sweep and its port is stored
|
||||
|
||||
Reference in New Issue
Block a user