fix(dns): EPERM means the bridge is alive, not stale - #456
Merged
Conversation
`isAlive` asked `process.kill(pid, 0)` and caught every failure as "dead".
That call fails two ways which mean opposite things: ESRCH for a pid that is
gone, and EPERM for one that is there but belongs to another user.
The second is the case this tool manufactures. `dns enable` escalates, so the
bridge it starts is root's while every later `status` asking after it is not.
A live root-owned bridge therefore reported as a stale pidfile for the rest of
its life, and each caller drew a worse conclusion from it than the last:
- `status` printed "bridge NOT running - stale pidfile for <pid>" and
advised `dns enable` as the fix
- `stopDaemon` deleted the pidfile and reported it had cleared a stale
entry, while the daemon it named kept running
- `startDaemon` saw nothing running and started a second bridge on
127.0.0.1 underneath the working one on 0.0.0.0. The kernel delivers to
the more specific socket, so the advice shadowed the bridge it was meant
to rescue and took the machine's resolver down
Only ESRCH is dead now. EPERM is alive and someone else's, which the caller is
told rather than having it papered over: `stop` returns "Operation not
permitted" instead of silently orphaning the daemon, and `start` short-circuits
on `alreadyRunning` instead of shadowing it.
`isAlive` takes an injectable `kill` so both branches are testable without
privileges, matching how every other system call here is reached.
Observed as `dns status` reporting a stale pidfile on a Kubuntu desktop whose
bridge had been up the entire time. The port probe in `bridgePresence` was
already authoritative and is untouched; this fixes the half that reads the
pidfile.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThnQwoieWt8VR6N7gtgnhp
ThreatCrush Security Scan0 finding(s) in the 2 file(s) this pull request changes. Nothing in the files this pull request changes. 88 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 5 | **MEDIUM**: 73 | **LOW**: 10Not introduced by this pull request. The full set is in the Security tab.
…and 68 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.
isAliveaskedprocess.kill(pid, 0)and caught every failure as "dead":That call fails two ways which mean opposite things — ESRCH for a pid that is gone, and EPERM for one that is there but belongs to another user.
The second is the case this tool manufactures.
dns enableescalates, so the bridge it starts is root's while every laterstatusasking after it is not. A live root-owned bridge read as a stale pidfile for the rest of its life, and each caller drew a worse conclusion from it than the last:statusbridge NOT running — stale pidfile for <pid>, then adviseddns enableas the fixstopDaemonstartDaemon127.0.0.1under the working one on0.0.0.0The last one is the outage. The kernel delivers to the more specific socket, so the advice shadowed the bridge it was meant to rescue — the failure
bridgePresencealready documents, reached by believing our own liveness check.The change
Only ESRCH is dead. EPERM is alive and someone else's, and the caller is told rather than having it papered over —
stopreturnsOperation not permittedinstead of silently orphaning the daemon, andstartshort-circuits onalreadyRunninginstead of shadowing it.isAlivegains an injectablekillso both branches are testable without privileges, matching how every other system call here is reached.Scope
The port probe in
bridgePresencewas already authoritative and is untouched — it is whystatusstill correctly said nothing was answering on 5354. This fixes the half that reads the pidfile.Not addressed here: the bridge still is not a systemd unit, so it does not survive a reboot on desktops. That is the separate gap
docs/hosting-a-moshpit-name.mdpoints atdeploy/moshcode-dns.servicefor, which is not shipped in the package.Tests
Four added to
test/dns-daemon-verify.test.mjs. Three fail without the fix; the ESRCH one is the control and passes either way.isAlive(1)holds whoever runs the suite: unprivileged it is EPERM, as root it simply succeeds.Full suite: 2698 tests, 0 failures, 334 skipped (the usual
apps/pwadependency guards).Observed as
dns statusreporting a stale pidfile on a Kubuntu desktop whose bridge had been up the entire time.🤖 Generated with Claude Code
https://claude.ai/code/session_01ThnQwoieWt8VR6N7gtgnhp