fix(dns): enable rewrites the supervised bridge's unit itself - #483
Merged
Conversation
The last step anyone still had to run by hand:
moshcode dns service --proxy-probe <a real name> --write
`dns enable` could detect the proxy, install it, start it and trust its
root, and still leave every Moshpit name answering its origin — because
what a resolver answers with is fixed when it spawns, and the bridge was
already running. `startDaemon` saw a live pidfile, reported "already
running", and left it alone. Correct, and the reason proxy mode arrived a
reboot late. Stopping the bridge did not help either: the unit is
`Restart=always`, so systemd put the same ExecStart back within the second.
The only thing that changes a supervised bridge's mind is rewriting its
unit and restarting it, and `enable` could not do that. It escalates, and
the bridge is a *user* unit — so `systemctl --user` from there addressed
root's session, which has no bridge in it and never has. The unit that had
to change belongs to a session the escalated half of the command cannot
see.
So an escalated run now drops back to the invoking user and derives the
runtime directory their session bus lives in, rather than inheriting one
that points at root's. Same reason `servicePaths` now resolves the
operator's home instead of `homedir()`: escalated, that was /root, so the
unit was being written somewhere systemd would never look for it.
`installService` also restarts rather than only `enable --now`, which
starts a stopped unit and does nothing whatever to a running one — the
change would have landed on disk and nowhere else.
No name is invented and no handshake is attempted. `enable` knows the
proxy address because it started the proxy, which is why the probe that
needed a real registered name is not on this path at all.
Upstreams are read back off the unit's own ExecStart rather than
rediscovered. By this point the machine may already be routing every
lookup at the bridge from an earlier enable, so asking the resolver for
its upstreams can answer "this bridge" — a bridge whose upstream is itself
resolves nothing at all.
Four things fell out of it:
- `disable` killed the pid and printed "bridge stopped" on a machine where
systemd restarted it within the second. It now stops the unit through
systemd and leaves the file, since turning resolution off for an
afternoon should not delete a unit the operator may have written.
- The closing note told a supervised machine the bridge does not survive a
reboot. It does now, and advice that is wrong is how advice stops being
read.
- A rollback signalled the pid of a bridge it had not started and
announced it had removed it. Both untrue, and the second one louder.
- A restart that never answers is a failure. `systemctl restart` returns
as soon as a Type=simple unit forks, so it returns 0 for a bridge that
forked and died — and the next thing this run does is point every lookup
on the machine at that port.
doas publishes a user name and no uid, which would have addressed root's
session silently; the uid falls back to the owner of the operator's home.
Two stubs moved into the enable test harness. Left to their defaults they
reach the real machine — one starts an installed proxy, the other writes
/etc/systemd/resolved.conf.d and restarts systemd-resolved — so a test
that forgot them either edited the box running the suite or failed on
EACCES from somewhere that read as a bug in whatever it was testing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThnQwoieWt8VR6N7gtgnhp
ThreatCrush Security Scan1 finding(s) in the 4 file(s) this pull request changes. HIGH/CRITICAL: 1
90 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 7 | **MEDIUM**: 73 | **LOW**: 10Not introduced by this pull request. The full set is in the Security tab.
…and 70 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The last step anyone still had to run by hand:
dns enablecould detect the pinned-TLS proxy, install it, start it, watch it take 443 and put its root in the system trust store — and still leave every Moshpit name answering its origin. What a resolver answers with is fixed when it spawns, and the bridge was already running:startDaemonsaw a live pidfile, reported "already running", and left it alone. Correct, and the reason proxy mode arrived a reboot late.Stopping the bridge did not help either. The unit is
Restart=always, so systemd put the sameExecStartback within the second.Why enable could not do it
The only thing that changes a supervised bridge's mind is rewriting its unit and restarting it, and
enablewas the one command that could not. It escalates, and the bridge is a user unit — sosystemctl --userfrom there addressed root's session, which has no bridge in it and never has. The unit that had to change belongs to a session the escalated half of the command cannot see.An escalated run now drops back to the invoking user and derives the runtime directory their session bus lives in, rather than inheriting one that points at root's.
servicePathsresolves the operator's home for the same reason: escalated,homedir()was/root, so the unit was being written somewhere systemd would never look.installServicealso restarts rather than onlyenable --now, which starts a stopped unit and does nothing whatever to a running one. Without that the change lands on disk and nowhere else.No name is invented
The probe that needed a real registered name is not on this path.
enableknows the proxy address because it started the proxy — which is what v0.89.0 established — so there is nothing to look up and nothing to guess.Upstreams are read back off the unit's own
ExecStartrather than rediscovered. By this point the machine may already be routing every lookup at the bridge from an earlier enable, so asking the resolver for its upstreams can answer this bridge — and a bridge whose upstream is itself resolves nothing at all.Four things that fell out of it
disablesystemctl restartreturns as soon as aType=simpleunit forks, so it returns 0 for a bridge that forked and died, and the next thing this run does is point every lookup on the machine at that portdoaspublishes a user name and no uid, which would have addressed root's session silently. The uid falls back to the owner of the operator's home.Test harness
proxyWrapperandapplyWithmoved intonoSystem(). Left to their defaults they reach the real machine — one starts an installed proxy, the other writes/etc/systemd/resolved.conf.dand restartssystemd-resolved— so a test that forgot to stub them either edited the box running the suite or failed on EACCES from somewhere that read as a bug in whatever it was actually testing. Three of the new tests hit exactly that before the stubs moved.Checks
npm test— 2794 tests, 0 failures (up from 2782 on main; 24 new).Not verified on a live desktop: this needs an escalated
dns enableon a machine with the user unit installed, which is bonita, not the dev box. The dev box has nomoshcode-dns.serviceat all — its bridge is moshpit-proxy's, a different implementation — so running it here would prove nothing and overwrite nothing worth overwriting. The command form was checked against the real/run/user/1000(present, owned by uid 1000).🤖 Generated with Claude Code
https://claude.ai/code/session_01ThnQwoieWt8VR6N7gtgnhp