Resolve overlay members by name

`--dns` serves a zone for the network's members, built from signed state,
so a member that is switched off still resolves — its claim outlived the
session. IPv4 only, as agreed: the IPv6 overlay address derives from a
key that travels in live announcements, so it cannot be answered for an
absent member, and answering for some and not others depending on who is
online is worse than not answering.

On Linux the agent tells systemd-resolved where to ask, over D-Bus.
SetLinkDNSEx carries a port, which is why the server needs neither port
53 nor CAP_NET_BIND_SERVICE; the suffix goes in as a routing domain and
the link's default route is cleared, so this never becomes the resolver
for anything else. The setting is keyed to the overlay interface, which
goes with the agent, so it cleans itself up.

That step needs permission CAP_NET_ADMIN does not give — resolved asks
polkit, and polkit decides by user, not by capability — so it is
reported as its own kind of failure with its own remedy. The server runs
regardless and status prints the exact dig line: the automatic part is
what is missing, not the feature.

The zone name is the user's to choose. One shadowing a real public
domain is reported and then used, because that is a decision; the
warning knows the IANA list, says something different about `.local`
where the clash is with mDNS, and stays quiet for names reserved for
private use.

Two bugs found by running it, both in the supervisor and neither
reachable from a unit test, so tests/dns_service.rs drives the real
binary. It bound to the allocated overlay address without checking that
address was on an interface — with --no-tun it never is — and left the
feature silently dead; it now tries the overlay first and falls back to
loopback. And it compared the address it got against the address it
wanted, which never matched when the preferred one could not be bound,
so it tore the listener down every two seconds; it now compares what it
tried.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
tsunagi
2026-09-21 17:05:24 +01:00
co-authored by Claude Opus 5
parent 240e471c88
commit 75b37fdda3
14 changed files with 1553 additions and 13 deletions
+39
View File
@@ -149,6 +149,45 @@ Because the address is allocated at run time rather than derived, it is not
known until the agent has started and agreed with its peers. The agent then
assigns it to the interface itself.
## Names
`--dns` serves a local DNS zone for the network's members, so they can be
reached by name instead of by address:
```bash
tsunagi up --network lab --secret "$SECRET" --wireguard --dns
dig @10.13.37.69 -p 5354 music.lab
```
Names come from signed state, which is the point: **a member that is
switched off still resolves**, because its claim outlived the session. IPv4
only — the IPv6 overlay address derives from a key that travels in live
announcements, so it cannot be answered for a member that is away, and
answering for some members and not others depending on who is online is
worse than not answering.
The zone is the network name unless `--dns-zone` says otherwise. It is
yours to choose, so a name that shadows a real public domain is reported and
then used: `--dns-zone ru` warns that every public `.ru` name becomes
unreachable from this host, and then does it. `.internal` is reserved for
exactly this and is never mentioned.
On Linux the agent tells systemd-resolved to send questions for that suffix
here, over D-Bus, scoped to the overlay interface and as a *routing* domain
so it never becomes the resolver for anything else. resolved drops the whole
setting when the interface goes, and the interface goes with the agent.
That last step needs permission that `CAP_NET_ADMIN` does not give:
systemd-resolved asks polkit, and polkit decides by user. Running as a
system service is enough; so is a polkit rule granting this user the
`org.freedesktop.resolve1.set-*` actions. **Without it the server still
runs** — `tsunagi status` prints where it is listening and the exact `dig`
line — so the automatic part is missing, not the feature.
The server is authoritative for its zone and nothing else. No recursion, no
forwarding, no cache: pointing a resolver at it can never make it a route to
the outside.
## Privileges
On Linux the agent **manages its own overlay interface**. It creates the TUN