Skip to content

send_packet no longer rediscovers routes on APS_NO_ACK; a stale next hop black-holes devices until restart (since #260) #288

Description

@TS-hub

Summary

The coordinator's routing table can hold a next hop that still MAC-ACKs but no
longer forwards. Every unicast to that destination then fails with APS_NO_ACK.
Z-Stack does not report NWK_NO_ROUTE in this case. Since #260, send_packet
only rediscovers a route on NWK_NO_ROUTE (#273) and after MAC_TRANSACTION_EXPIRED,
so the route is never repaired. Downlink to the device stays dead until a
restart clears the table. Sleepy end devices eventually go unavailable.

One ZDO.ExtRouteDisc fixes it within 2 s. I tested this 7 times with a sniffer
and it worked every time.

Before #260, APS_NO_ACK was in REQUEST_ROUTING_ERRORS (856bf6a, 2020). The
retry chain then tried the last good route (reversed SrcRtgInd relays), then
route discovery, then IEEE addressing. #260 doesn't mention why this was removed.

Environment

  • zigpy-znp 1.1.1, zigpy 2.3.0, ZHA 2.3.1 (HA 2026.10.3 for the first case, 2026.10.4 for the rest; both ship the same ZHA)
  • CC2674P10 (SMLIGHT SLZB-Ultima3), Z-Stack 2.7.1, build 20260310
  • About 70 devices. Most routers are IKEA TRADFRI GU10 bulbs (EmberZNet), plus TRETAKT plugs, one Hue bulb and one TRADFRI repeater
  • A second adapter sniffed channel 15 continuously; the captures were decrypted with the network key

One case in detail (SNZB-02DR2, 0x12B2, 2026-10-25)

  1. Until 10:15:44 the path coordinator → 0xE732 (GU10) → 0xA857 → parent 0xDFD9 worked.
  2. From 10:31:21 every coordinator frame for 0x12B2 still went to 0xE732. Between 09:57 and 11:15 that was 418 frames. 0xE732 MAC-ACKed 404 of them, forwarded none, and sent no Network Status. It stayed active the whole time (200–290 frames per 10 min).
  3. None of these frames were source-routed. Yet the coordinator kept receiving valid route records for 0x12B2 via other routers. Uplink worked, so HA still showed the device as available.
  4. zigpy logged APS_NO_ACK at 10:43:56 and 11:12:58. It logged no NWK_NO_ROUTE and started no discovery. The device retried its Time cluster read about 40 times per 10 min (normally 1–8), because no reply reached it.
  5. At 11:15:21 I called app._discover_route(0x12B2). A route reply came back via 0xAAAC within 0.1 s. From 11:15:22.6 the coordinator sent via 0xAAAC: frames were delivered and APS-ACKed, the Time retries stopped, and no further APS_NO_ACK appeared.

All cases

Date Device Stale next hop Frames MAC-ACKed / forwarded New next hop
10-25 11:15 SNZB-02DR2 0x12B2 0xE732 (GU10) 404 / 0 0xAAAC
10-26 03:44 SNZB-02D 0x7BFC 0x08DA not counted 0x07CB, still in use 1.5 days later
10-27 15:08 SNZB-02D 0xEE3F 0xE799 (GU10) not counted 0x1670
10-27 15:14 SNZB-02D 0x65C9 0x6892 (TRADFRI driver) not counted 0xAAAC
10-27 23:14 SNZB-02DR2 0x12B2 0xE799 (GU10) 276 / 0 0x07CB
10-29 14:31 SNZB-02D 0x65C9 0xB2E9 111 / 0 0x9986
10-30 00:31 SNZB-02D 0xC32C 0x6892 (TRADFRI driver) 69 of 70 / 0 0x07CB

Each fix took one discovery. HA last restarted on 10-25 at 15:52 (update to 2026.10.4), so only the first case was affected by a restart. Before I had a probe, only HA restarts cleared this: the Z-Stack soft reset empties the routing table.

Why next hops go stale (partly inferred)

The TRADFRI GU10 firmware (0x23093631, EmberZNet) has a 10-entry route table.
When it is full, the oldest entry at max age is overwritten whatever its status,
so a route in use can be evicted. The bulb drops a route only when it receives a
Network Status. I couldn't determine from the firmware whether it sends a route
error when it can't forward, and none appeared on air. From the coordinator,
the only symptom is APS_NO_ACK.

Related

Proposal

Treat APS_NO_ACK like NWK_NO_ROUTE in send_packet: discover the route and retry once.

if (
    e.response.Status in (t.Status.NWK_NO_ROUTE, t.Status.APS_NO_ACK)
    and device is not None
    and retry_attempt < 1
):
    await self._discover_route(device.nwk)
    continue

_discover_route is already deduplicated per NWK (@combine_concurrent_calls)
and sends a single unicast RREQ. For comparison, zigbee-herdsman's Z-Stack adapter
doesn't retry on APS_NO_ACK either, but when an expected ZCL response times out
it calls discoverRoute() and retries once. That covers requests but not replies
like the Check-in Response above.

Alternative, as before #260: retry once over the reversed relays of the last
SrcRtgInd. In these captures those relays were working paths.

I can share decrypted packet summaries (TSV) for each case, or test a patch.

Related: #289

Activity

  1. zigpy-review-bot commented on Oct 30, 2026

    @zigpy-review-bot
    Collaborator

    Thanks for the detailed captures. I opened #289 with the proposed change: send_packet now treats APS_NO_ACK like NWK_NO_ROUTE for a unicast to a known device, runs _discover_route and retries once, and a second failure is raised as a DeliveryError as before. Broadcasts and multicasts are unaffected.

    Replies like the Poll Control Check-in Response go through the same send_packet, so they are covered too.

    Two trade-offs are noted in the PR body. A device that is really unreachable now costs one extra route discovery and one extra send per send_packet call, on top of zigpy's own request retries. And APS_NO_ACK can also mean only the APS ack was lost, so the retry may repeat a non-idempotent command; that matches the behaviour before #260.

    The full test suite passes with zigpy 1.5.0. I could not test on hardware, so it would help if the reporter could try the patch against the stale-next-hop case. Note that the suite currently fails on dev with zigpy 2.4.1 installed (unrelated to this change).

  2. removed their assignment
    on Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    regressionWorked in an earlier release

    Type

    Fields

    Priority

    None yet

    Size

    None yet

    Area

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions