Add ENETUNREACH to non-retryable errors in XorTransport (#1668)
Some checks failed
Stale / stale (push) Has been cancelled
CI / Perform Lint Checks (3.13) (push) Has been cancelled
CodeQL Checks / Analyze (python) (push) Has been cancelled
CI / Python 3.11 on macos-latest (push) Has been cancelled
CI / Python 3.12 on macos-latest (push) Has been cancelled
CI / Python 3.13 on macos-latest (push) Has been cancelled
CI / Python 3.11 on ubuntu-latest (push) Has been cancelled
CI / Python 3.12 on ubuntu-latest (push) Has been cancelled
CI / Python 3.13 on ubuntu-latest (push) Has been cancelled
CI / Python 3.11 on windows-latest (push) Has been cancelled
CI / Python 3.12 on windows-latest (push) Has been cancelled
CI / Python 3.13 on windows-latest (push) Has been cancelled

`ENETUNREACH` is semantically equivalent to `EHOSTUNREACH` (which is
already in `_NO_RETRY_ERRORS`) — both indicate the destination cannot be
reached at the network layer. Retrying immediately will not resolve the
condition.

In containerized environments (e.g., Home Assistant running in Docker
with bridge networking), a single ICMP "Network unreachable" response
can temporarily poison the container's routing cache. The unnecessary
retries reinforce the poisoned cache entry, causing cascading failures
for other devices on the same subnet.
This commit is contained in:
wh1t3f1r3
2026-03-02 15:03:46 -06:00
committed by GitHub
parent be5ce08f45
commit 99fca940cf
2 changed files with 14 additions and 2 deletions

View File

@@ -29,7 +29,12 @@ from kasa.json import loads as json_loads
from .basetransport import BaseTransport
_LOGGER = logging.getLogger(__name__)
_NO_RETRY_ERRORS = {errno.EHOSTDOWN, errno.EHOSTUNREACH, errno.ECONNREFUSED}
_NO_RETRY_ERRORS = {
errno.EHOSTDOWN,
errno.EHOSTUNREACH,
errno.ENETUNREACH,
errno.ECONNREFUSED,
}
_UNSIGNED_INT_NETWORK_ORDER = struct.Struct(">I")

View File

@@ -807,10 +807,17 @@ async def test_transport_credentials_hash_from_config(mocker, transport_class):
[
(ConnectionRefusedError("dummy exception"), False),
(OSError(errno.EHOSTDOWN, os.strerror(errno.EHOSTDOWN)), False),
(OSError(errno.ENETUNREACH, os.strerror(errno.ENETUNREACH)), False),
(OSError(errno.ECONNRESET, os.strerror(errno.ECONNRESET)), True),
(Exception("dummy exception"), True),
],
ids=("ConnectionRefusedError", "OSErrorNoRetry", "OSErrorRetry", "Exception"),
ids=(
"ConnectionRefusedError",
"OSErrorHostDown",
"OSErrorNetUnreach",
"OSErrorRetry",
"Exception",
),
)
@pytest.mark.parametrize(
("protocol_class", "transport_class"),