Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,45 @@ and Apache-2.0 carries an explicit patent grant where MIT is silent. Implement
the pin scheme however you like; the NOTICE covers naming.

Security policy and the trust model behind the local CA: [SECURITY.md](SECURITY.md).

## Resolving Moshpit names on a machine (`systemd/`)

`systemd/moshpit-dns.service` and `systemd/00-moshpit.conf` are the deployed
resolver setup, kept here because they were previously configured by hand on
each box and existed in no repository.

```sh
cp systemd/moshpit-dns.service /etc/systemd/system/
cp systemd/00-moshpit.conf /etc/systemd/resolved.conf.d/
systemctl daemon-reload && systemctl enable --now moshpit-dns
systemctl restart systemd-resolved
```

The bridge runs as this machine's **primary** resolver, with no list of Moshpit
endings anywhere. That is not a shortcut — a per-TLD list cannot be made to work:

- **It does not scale.** `moshcode dns install --write` emits every claimed
ending as a routing domain — 5,661 of them on one line. systemd-resolved caps
search domains near 1090 and drops the rest alphabetically, logging thousands
of `Failed to add search domain '~zoology': Argument list too long`. Endings
past the cut are configured on disk and absent from the resolver.
- **Curating the list does not fix it either.** A routing domain selects a
*scope*, and a scope tries its servers in order. With an upstream resolver in
the same scope, `.hacker` goes there first, comes back NXDOMAIN, and
systemd-resolved treats that as final — the bridge is never asked.

Sending every query to the bridge removes both problems. It answers Moshpit
names from the registry and forwards everything else upstream (`mode=clearnet`:
the ordinary internet owns any name it can answer; the registry is a backfill).

The drop-in is named `00-` so it is read first: systemd-resolved appends `DNS=`
in filename order and uses the first server, rotating only on *failure* — never
on NXDOMAIN. So any pre-existing resolvers stay listed as a genuine fallback for
if the bridge stops, rather than shadowing it.

**Do not set `MOSHPIT_DNS_CATCHALL=1` here.** It parks every unresolved name on
the registry's parking address, which on a machine's own resolver means
`github.com` resolves to a parking page. Correct startup logs say `mode=clearnet`.
If clearnet names start resolving to a `69.46.46.x` address, check for a stray
`moshcode dns start` bound to `127.0.0.1:5354` — a loopback bind beats the
service's `0.0.0.0` bind and wins every local query.
30 changes: 30 additions & 0 deletions systemd/00-moshpit.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# The Moshpit resolver, as this machine's primary DNS.
#
# Named 00- so it is read before DigitalOcean.conf: systemd-resolved appends
# DNS= across drop-ins in filename order, and it uses the first server for
# every query, rotating only on failure. So the bridge answers, and DO's
# resolvers stay listed as a fallback for if it ever stops.
#
# There is deliberately no Domains= line, and that is the whole point.
#
# The old approach listed every claimed Moshpit ending as a routing domain --
# 5,661 of them, ~60KB on one line. systemd-resolved caps search domains at
# about 1090 and drops the rest alphabetically, logging 8,199 lines of
# "Failed to add search domain '~zoology', ignoring: Argument list too long".
# Endings past the cut (~hacker and ~eggs among them) were configured on disk
# and absent from the resolver, so the bridge answered `dig` and the browser
# still got nothing. It also does not scale: every new ending needs another
# entry, and regenerating the file clobbers any hand-curated version.
#
# It cannot be fixed by curating the list either. A routing domain sends the
# query to a *scope*, and a scope tries its servers in order -- so with DO's
# resolvers in the same global scope, `.hacker` went to 67.207.67.3, came back
# NXDOMAIN, and resolved treated that as final. The bridge was never asked.
#
# Sending everything to the bridge removes all of it. It answers Moshpit names
# from the registry and forwards the rest upstream (mode=clearnet: the ordinary
# internet owns any name it can answer; the registry is only a backfill).
# Nothing to list, no cap to hit, nothing to regenerate.

[Resolve]
DNS=127.0.0.1:5354
51 changes: 51 additions & 0 deletions systemd/moshpit-dns.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
[Unit]
# The Moshpit resolver, running as this machine's primary DNS.
#
# Primary rather than a side-channel on purpose. systemd-resolved cannot do
# split-horizon within one scope: a routing domain sends the query to a scope,
# and the scope tries its servers in order, so an upstream that answers NXDOMAIN
# for `.hacker` ends the lookup before the bridge is ever asked. Listing every
# Moshpit ending in `Domains=` was the workaround, and it does not scale --
# systemd-resolved caps search domains at ~1090 and silently drops the rest.
#
# So the bridge answers everything and forwards what is not a Moshpit name.
# There is no domain list to maintain, nothing to regenerate, and no cap.
#
# MOSHPIT_DNS_CATCHALL is deliberately NOT set. It parks every unresolved name
# on the registry's parking address, which for a machine's own resolver means
# github.com resolves to a parking page. That mode belongs on the public parking
# responder, not here.
Description=Moshpit DNS bridge (primary resolver)
Documentation=https://github.com/profullstack/moshpit-proxy
After=network-online.target
Wants=network-online.target
Before=systemd-resolved.service

[Service]
Type=simple
User=anthony
Group=anthony
WorkingDirectory=/home/anthony/moshpit-dns
Environment=MOSHPIT_DNS_PORT=5354
Environment=MOSHPIT_DNS_HTTP_PORT=8053
Environment=MOSHPIT_DNS_LOG=queries
ExecStart=/home/anthony/.local/share/mise/installs/bun/latest/bin/bun run scripts/moshpit-dns.ts

# It is the resolver now. If it dies, name resolution for Moshpit names dies
# with it, so it comes back rather than staying dead like the orphaned process
# this replaced.
Restart=always
RestartSec=2

# Unprivileged: it binds 5354, not 53.
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/anthony/moshpit-dns
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

[Install]
WantedBy=multi-user.target
Loading