Skip to content

feat(remote-access): reach targets by their mDNS .local name - #15

Merged
reubenmiller merged 1 commit into
mainfrom
feat/remote-access-mdns-targets
Oct 7, 2026
Merged

reubenmiller merged 1 commit into
mainfrom
feat/remote-access-mdns-targets

Conversation

@reubenmiller

Copy link
Copy Markdown
Contributor

Summary

A remote-access endpoint can now name its target as <host>.local (for example rpi5-d83add9f145a.local:22). The client resolves it on every session, so the endpoint keeps working when the host's DHCP lease moves it. Two endpoints on our own boards still point at 192.168.68.72, which is the Pi's old address. The open and close events name both the host and the address it resolved to: tunnel to rpi5-d83add9f145a.local (192.168.68.84):22 opened.

Why the client sends its own mDNS query. Zephyr 4.4.2's resolver cannot look up .local names on our boards, for two reasons (details in tedge-zephyr/docs/zephyr-mdns-resolver-bugs.md, written for an upstream report):

  1. When DNS comes from DHCP, the resolver has no mDNS server. That is also the SNMP trap manager's -11 error. Setting DNS_SERVER_IP_ADDRESSES=y adds one, but it puts mDNS in front and every ordinary lookup then goes to it, so the device can't connect.
  2. When the application runs the mDNS responder, the resolver never reads the answers. A packet capture on the Pi shows its answer arriving and being ignored.

So .local targets use a one-shot legacy-unicast query (RFC 6762 §6.7) that the client sends and reads itself. Every other name still goes through Zephyr's resolver.

Changes

  • CONFIG_TEDGE_REMOTE_ACCESS_MDNS (default y). With it off, a .local target fails with "mDNS is not built in".
  • Retries:
    • A lookup that times out or hits a transient error is retried. An authoritative "no such name" is not.
    • Options: RESOLVE_ATTEMPTS (3), RESOLVE_TIMEOUT_MS (5000) and RESOLVE_RETRY_DELAY_MS (500).
    • A build-time check keeps the worst case under 30 s. Each failed attempt is logged as a warning, and the failure reason gives the number of attempts.
  • Any host on the network can answer an mDNS query, so a .local answer must be on the device's own subnets under every target policy.
  • The event text grows from 144 to 200 bytes so the longest host, address and byte counts fit.
  • The OpenSpec change is archived and the tedge-remote-access spec is updated. There is a new README section, "Remote-access targets".

Size. mDNS costs about 1.3 KB of flash and no static RAM. A lookup holds one UDP socket and a 512 B stack buffer while it runs. The whole change against the old baseline:

  • C6 full: +2.4 KB and +176 B RAM.
  • S3 DevKitC and QT Py full: about +220 B and +168 B RAM. Their images hide most of the code growth in flash alignment.

release/size-baseline.json is rebuilt for the nine full builds; only those entries changed. Other builds don't compile any of the changed code.

Testing

  • Unit tests (native_sim) pass, including 9 remote-access tests: .local detection, retry decisions, the policy and subnet rule, the query builder and answer parser (including malformed and looping input), and a check that the longest close event fits.
  • The Kconfig checks pass (29 of 29), along with check-independence.py and check-secrets.py.
  • On the S3 DevKitC (full, through Cumulocity eu-latest), against the rpi5 (systemd-resolved):
    • SSH to rpi5-d83add9f145a.local:22 works in 3–4 s. Both events show the name and the address.
    • A name that never answers fails after about 16 s with "did not answer over mDNS after 3 attempts", and the seat is freed.
    • With the first lookup blocked on the Pi (iptables), the second lookup answers and the tunnel is up after 7 s. With only the third answering, it is up after 12.5 s and SSH completes. That third-attempt test stalled once in two runs (the Pi closed it after 120 s); the cause is unknown.
    • With the option off, the request fails at once with "mDNS is not built in".
  • On the C6 (full, flashed from the rpi5): SSH to the Pi by .local name works in 3 s.

🤖 Generated with Claude Code

A remote-access endpoint can name its target as "<host>.local". The
client resolves it on every session, so the endpoint keeps working when
the host's DHCP lease moves it. Open and close events name the host and
the address it resolved to.

Zephyr 4.4.2's resolver can't do this on our boards. With DNS from
DHCP it has no mDNS server (the SNMP trap manager's -11), and when the
application runs the mDNS responder it never reads the answers. The
client therefore sends its own one-shot legacy-unicast query (RFC 6762
6.7) and reads the reply itself; other names still use Zephyr's
resolver. Both bugs are written up for upstream in
tedge-zephyr/docs/zephyr-mdns-resolver-bugs.md.

- CONFIG_TEDGE_REMOTE_ACCESS_MDNS (default y) turns it on; off, a
  .local target fails with "mDNS is not built in".
- A lookup that times out or hits a transient error is retried:
  RESOLVE_ATTEMPTS (3), RESOLVE_TIMEOUT_MS (5000) and
  RESOLVE_RETRY_DELAY_MS (500). The worst case is checked at build time
  to stay under 30 s.
- A .local answer must be on the device's own subnets under every target
  policy, because any host on the link can answer an mDNS query.
- The event text grows to 200 bytes so the longest host, address and
  byte counts fit.

Costs about 1.3 KB of flash and no static RAM for mDNS. The nine full
builds' size baselines are updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@reubenmiller
reubenmiller merged commit d7b8968 into main Oct 7, 2026
23 checks passed
@reubenmiller
reubenmiller deleted the feat/remote-access-mdns-targets branch October 7, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant