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:
Marc Billow
2026-07-29 12:53:56 +00:00
parent 7fe9e2a975
commit e8df8f7b0c
2 changed files with 43 additions and 1 deletions
+11 -1
View File
@@ -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:
+32
View File
@@ -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