Skip to content

fix(dns): actually use the catch-all routing that #195 built - #200

Merged
ralyodio merged 1 commit into
mainfrom
dns-wire-catchall
Aug 1, 2026
Merged

fix(dns): actually use the catch-all routing that #195 built#200
ralyodio merged 1 commit into
mainfrom
dns-wire-catchall

Conversation

@ralyodio

@ralyodio ralyodio commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

#195 built the catch-all machinery and wired none of it in. I shipped v0.16.0 with release notes saying "routing stopped being a list". It hadn't.

What was actually shipped

dns enable still called resolvedConf(tlds) (line 813). dns start never passed upstreams. resolvedCatchAllConf, parseUpstreams and the whole forwarding path existed and nothing called them.

So a box that upgraded to v0.16.0 got the same 4,586-ending list, the same silent truncation at the resolver's cap, and the same curl: (6) Could not resolve host. Confirmed on a live box after upgrading — dns enable still printed "Routing covers the 4620 TLDs claimed right now… every TLD is listed".

Harmless, but useless. My error.

Wiring it is the easy half

The hard half: catch-all routing is only safe if the bridge can forward what isn't ours. Point every lookup at a bridge with no upstreams and the machine loses DNS entirely — far worse than a Moshpit name that doesn't resolve.

So it's conditional by construction, not by flag:

upstreams found routing written worst case
yes Domains=~. + forwarding Moshpit names fail
no per-ending list, as before Moshpit names fail

Neither branch can take the box's DNS with it.

  • discoverUpstreams reads /etc/resolv.conf before routing is switched — afterwards it may point at us, and the real servers are no longer discoverable from it.
  • Loopback entries are dropped, so the bridge can't forward to itself.
  • dns start passes the same upstreams and the claimed-ending set, and prints which upstreams it will use — or warns plainly when it has none.
  • dnsmasq follows the same rule, with no-resolv so it doesn't inherit upstreams pointing back here.

Tests

98 tests, 98 pass. The new ones assert the conditional directly — that upstreams: [] produces the per-ending list and never ~., on both backends. That's the assertion standing between this and taking someone's machine off the internet.

After merge

Needs a v0.16.1 release to reach anyone, since install.sh resolves releases/latest.

🤖 Generated with Claude Code

#195 added the catch-all config generators, upstream parsing and the
forwarding path, and wired none of them in. `dns enable` still called
resolvedConf(tlds) and `dns start` never passed upstreams, so v0.16.0
shipped the capability and none of the behaviour: a box that upgraded got
the same 4586-ending list, the same silent truncation at the resolver's
cap, and the same `curl: (6) Could not resolve host`. The release notes
said routing had stopped being a list. It had not.

Wiring it is the easy half. The hard half is that catch-all routing is
only safe when the bridge can forward what is not ours — point every
lookup at a bridge with no upstreams and the machine loses DNS entirely,
which is far worse than a Moshpit name that does not resolve.

So it is conditional by construction rather than by flag:

  - `discoverUpstreams` reads /etc/resolv.conf BEFORE routing is switched,
    because afterwards resolv.conf may point at us and the real servers
    are no longer discoverable from it
  - loopback entries are dropped, so the bridge cannot forward to itself
  - upstreams found  → `Domains=~.` and the bridge forwards
  - none found       → the per-ending list, exactly as before, which can
    only ever break Moshpit names
  - `dns start` passes the same upstreams and the claimed-ending set to
    the server, and says which upstreams it will use

The dnsmasq backend follows the same rule, with no-resolv so it does not
inherit upstreams that point back here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio
ralyodio merged commit 648fc58 into main Aug 1, 2026
3 checks passed
@ralyodio
ralyodio deleted the dns-wire-catchall branch August 1, 2026 14:13
@ralyodio ralyodio mentioned this pull request Aug 1, 2026
ralyodio added a commit that referenced this pull request Aug 1, 2026
v0.16.0 shipped the catch-all routing machinery and wired none of it in,
so upgrading to it changed nothing: `dns enable` still wrote every claimed
ending onto one `Domains=` line, systemd-resolved still accepted about
1090 of them and dropped the rest without an error a caller could see, and
`curl <name>` still could not resolve. The release notes said routing had
stopped being a list. It had not.

#200 wires it, and makes it conditional rather than assumed: the upstreams
are read from /etc/resolv.conf before routing is switched, and catch-all
is only written when there is somewhere to forward. With no upstreams the
per-ending list stays, because pointing every lookup at a bridge that
cannot forward takes the machine's DNS with it rather than just Moshpit
names.

#201 keeps the resolver up when its socket errors after bind.

The list is also getting worse on its own: 4593 endings this morning, 4882
by the afternoon, against a resolver cap near 1090.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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