Merge pull request #135 from QuiteYellow/fix/stale-dtls-session-fixed-source-port

Bind a fixed DTLS source port per device to evict stale sessions
This commit is contained in:
Marc Billow authored and GitHub committed 2026-07-27 18:40:36 -05:00
commit c19800ac00
4 files changed
+71 -5

No files matched your search

+34 -2
View File
@@ -11,9 +11,12 @@ from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import issue_registry as ir
from custom_components.localthings.const import (
CONF_BYPASS_REMOTE_CONTROL, CONF_HOST, DOMAIN, SUMMARY_INTERVAL_S,
CONF_BYPASS_REMOTE_CONTROL, CONF_HOST, DOMAIN, DTLS_LOCAL_PORT_BASE,
SUMMARY_INTERVAL_S,
)
from custom_components.localthings.coordinator import (
LocalThingsCoordinator, _local_source_port,
)
from custom_components.localthings.coordinator import LocalThingsCoordinator
from custom_components.localthings.registry.capabilities.common import (
remote_control_enabled,
remote_control_required_for_write,
@@ -1174,3 +1177,32 @@ async def test_send_command_operational_still_blocked_when_without_sc(
assert exc_info.value.translation_domain == DOMAIN
assert exc_info.value.translation_key == 'remote_control_disabled'
assert posted is False
def test_local_source_port_stable_and_in_range() -> None:
"""Same host always maps to the same port (stability is the whole point:
a reconnect must reuse the 5-tuple), and it stays in the documented window."""
for host in ('192.168.1.217', '10.0.0.42', 'fridge.local'):
port = _local_source_port(host)
assert port == _local_source_port(host)
assert DTLS_LOCAL_PORT_BASE <= port <= DTLS_LOCAL_PORT_BASE + 0xFF
def test_local_source_port_unique_per_host_on_a_24() -> None:
"""Distinct last octets -> distinct ports, so two devices on the same HA
host never share a source port (the lib's socket is unconnected, so a
shared port would cross-deliver datagrams)."""
ports = {_local_source_port(f'192.168.1.{n}') for n in range(1, 255)}
assert len(ports) == 254
def test_local_source_port_ipv4_uses_last_octet() -> None:
"""The IPv4 fast path is the last octet, not a hash."""
assert _local_source_port('192.168.1.217') == DTLS_LOCAL_PORT_BASE + 217
def test_local_source_port_non_ipv4_falls_back_to_hash() -> None:
"""A non-IPv4 host still yields a deterministic in-range port."""
port = _local_source_port('some-hostname')
assert port == _local_source_port('some-hostname')
assert DTLS_LOCAL_PORT_BASE <= port <= DTLS_LOCAL_PORT_BASE + 0xFF