Skip to content

feat(dns): --upstream, so a supervised bridge can bootstrap - #460

Merged
ralyodio merged 1 commit into
mainfrom
feat/dns-upstream-flag
Aug 30, 2026
Merged

feat(dns): --upstream, so a supervised bridge can bootstrap#460
ralyodio merged 1 commit into
mainfrom
feat/dns-upstream-flag

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Fixes the bug that v0.78.0 exposed. Diagnosed on a live Kubuntu desktop, not from the source.

The bridge could not bootstrap through itself

discoverUpstreams reads the machine's resolv.conf and drops loopback. Correct — until the machine's resolver is this bridge. Then the only nameserver on file is the bridge, discovery rightly refuses to return it, and the daemon comes up with nowhere to forward.

The failure is total and hides its own cause:

pit.moshcode.sh → NXDOMAIN
api.morgen.so → NXDOMAIN
mail-ads.google.com → NXDOMAIN

Every clearnet name NXDOMAINs — including pit.moshcode.sh, so the bridge never learns which endings are Moshpit and answers for nothing either. The machine has no DNS at all and cannot look up why, because the lookup goes through the bridge.

dns enable never hit this, which is why it stayed hidden: it runs before the routing exists, so discovery still sees real servers. A bridge supervised by the unit added in #458 hits it every boot, because the resolved drop-in is a file and is already in place when the service starts. Shipping dns service turned a latent bug into one that fires on every restart.

So upstreams become sayable

moshcode dns start --port 5354 --upstream 1.1.1.1
moshcode dns start --port 5354 --upstream 1.1.1.1,1.0.0.1
moshcode dns start --port 5354 --upstream 9.9.9.9#5353

Repeatable and comma-separated both work. address#port matches resolv.conf and dnsmasq rather than inventing a third spelling. Anything that isn't an address is reported, not dropped — silently discarding it leaves exactly the forwards-nowhere bridge this flag exists to prevent. Named upstreams override discovery outright.

The half that actually fixes reboots

dns service now resolves the upstreams while a working resolver is still there to be asked, and bakes them into the unit:

ExecStart=/…/node /…/moshcode.mjs dns start --port 5354 --registry https://pit.moshcode.sh --upstream 67.207.67.3,67.207.67.2

At boot the daemon no longer has to guess. When discovery is empty at generation time, the command says so and prints the flag to pass instead of writing a unit that will fail silently. dns start explains the empty case too, since the symptom looks nothing like the cause.

Verification

Live, on a real bridge, on a spare port so the box's own resolver was never touched:

  • --upstream 1.1.1.1,1.0.0.1 logs forwarding non-Moshpit lookups to 1.1.1.1, 1.0.0.1 (--upstream)
  • loads all 18224 endings
  • forwards example.com → real A records
  • resolves alt.260067.205.189.229
  • generated units carry the flag; units with nothing to record omit it rather than writing an empty one

Parser exercised across single/comma/repeated/#port/IPv6/invalid/missing-value/duplicate/absent.

9 new tests. Full suite: 2716 tests, 0 failures, 334 skipped (the usual apps/pwa guards).

🤖 Generated with Claude Code

https://claude.ai/code/session_01ThnQwoieWt8VR6N7gtgnhp

`discoverUpstreams` reads the machine's resolv.conf and drops loopback. That is
right until the machine's resolver IS this bridge: then the only nameserver on
file is the bridge itself, discovery correctly refuses to return it, and the
daemon comes up with nowhere to forward.

The result is total and self-concealing. Every clearnet name answers NXDOMAIN,
including `pit.moshcode.sh` — so the bridge never learns which endings are
Moshpit and answers for nothing either. The machine has no DNS at all and
cannot look up the reason, because the lookup goes through the bridge.

`dns enable` never hit this, which is why it went unnoticed: it runs before the
routing exists, so discovery still sees the real servers. A bridge supervised by
the unit added in v0.78.0 hits it every boot, because the resolved drop-in is a
file and is already in place when the service starts. Shipping `dns service`
therefore turned a latent bug into one that fires on every restart.

Observed on a Kubuntu desktop: the bridge up and listening, resolved correctly
routed to it, and a journal consisting entirely of

    pit.moshcode.sh → NXDOMAIN
    api.morgen.so → NXDOMAIN
    mail-ads.google.com → NXDOMAIN

from the bridge that was supposed to answer them.

So the upstreams become sayable rather than only discoverable:

    moshcode dns start --port 5354 --upstream 1.1.1.1
    moshcode dns start --port 5354 --upstream 1.1.1.1,1.0.0.1
    moshcode dns start --port 5354 --upstream 9.9.9.9#5353

Repeatable and comma-separated both work, `address#port` matches resolv.conf and
dnsmasq rather than inventing a third spelling, and anything that is not an
address is reported rather than dropped — silently discarding it leaves exactly
the bridge-that-forwards-nowhere this flag exists to prevent.

Named upstreams override discovery outright. Someone who says where to forward
has almost always said it because discovery got it wrong.

The half that actually fixes the reboot is `dns service`: it now resolves the
upstreams while a working resolver is still there to be asked, and bakes them
into the unit's ExecStart. At boot the daemon no longer has to guess. When
discovery comes up empty at generation time, the command says so and prints the
flag to pass instead of writing a unit that will fail silently.

`dns start` also explains the empty case now, since the symptom (every name
NXDOMAIN) looks nothing like the cause.

Verified live: a bridge started with `--upstream 1.1.1.1,1.0.0.1` logs
`forwarding non-Moshpit lookups to 1.1.1.1, 1.0.0.1 (--upstream)`, loads all
18224 endings, forwards example.com, and resolves alt.2600. Generated units
carry the flag; units with nothing to record omit it rather than writing an
empty one. Suite: 2716 tests, 0 failures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThnQwoieWt8VR6N7gtgnhp
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

4 finding(s) in the 5 file(s) this pull request changes.

HIGH/CRITICAL: 1 | MEDIUM: 3

Severity Rule Location
HIGH tls-verification-disabled src/dns.mjs:766
MEDIUM sql-string-concatenation src/cli-schema.mjs:162
MEDIUM sql-string-concatenation src/cli-schema.mjs:504
MEDIUM sql-string-concatenation src/cli-schema.mjs:630
84 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 4 | **MEDIUM**: 70 | **LOW**: 10

Not introduced by this pull request. The full set is in the Security tab.

Severity Rule Location
HIGH js-ssrf-outbound-request apps/pwa/public/sw.js:45
HIGH tls-verification-disabled apps/pwa/src/lib/moshpit-gateway.mjs:299
HIGH sh-remote-script-execution install.sh:79
HIGH sh-remote-script-execution install.sh:83
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:138
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:152
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:178
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:365
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:369
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:414
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:663
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:859
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:861
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:920
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:966
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1036
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1139
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1162
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1184
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1383

…and 64 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 96c03ba into main Aug 30, 2026
6 checks passed
@ralyodio ralyodio mentioned this pull request Aug 30, 2026
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