moshcode 0.78.0 → 0.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli-schema.mjs +2 -1
- package/src/dns-service.mjs +8 -0
- package/src/dns.mjs +96 -5
- package/src/iana-tlds.mjs +1461 -0
package/package.json
CHANGED
package/src/cli-schema.mjs
CHANGED
|
@@ -1007,7 +1007,7 @@ export const DNS_VERBS = [
|
|
|
1007
1007
|
{ name: "disable", description: "undo enable" },
|
|
1008
1008
|
{ name: "status", description: "what is running, what is routed, does it work" },
|
|
1009
1009
|
{ name: "refresh", description: "re-apply routing for endings claimed since" },
|
|
1010
|
-
{ name: "start", description: "run the bridge in the foreground" },
|
|
1010
|
+
{ name: "start", description: "run the bridge in the foreground (--upstream IP to say where to forward)" },
|
|
1011
1011
|
{ name: "install", description: "print the resolver config without applying it" },
|
|
1012
1012
|
{
|
|
1013
1013
|
name: "service",
|
|
@@ -1017,6 +1017,7 @@ export const DNS_VERBS = [
|
|
|
1017
1017
|
["moshcode dns service --write", "install and start it as this user; needs no root"],
|
|
1018
1018
|
["moshcode dns service --system", "a system unit instead — place it with sudo tee"],
|
|
1019
1019
|
["moshcode dns service --remove", "stop it and take the unit away"],
|
|
1020
|
+
["moshcode dns service --upstream IP", "where to forward, when discovery cannot see past the bridge"],
|
|
1020
1021
|
],
|
|
1021
1022
|
},
|
|
1022
1023
|
{ name: "tlds", description: "list the endings claimed in the Pit" },
|
package/src/dns-service.mjs
CHANGED
|
@@ -66,12 +66,20 @@ export function serviceUnit({
|
|
|
66
66
|
entry,
|
|
67
67
|
port,
|
|
68
68
|
registryBase = null,
|
|
69
|
+
upstreams = [],
|
|
69
70
|
user = process.env.USER || process.env.LOGNAME,
|
|
70
71
|
} = {}) {
|
|
71
72
|
if (!entry) throw new Error("serviceUnit needs the entry script to run");
|
|
72
73
|
|
|
73
74
|
const args = [entry, "dns", "start", "--port", String(port)];
|
|
74
75
|
if (registryBase) args.push("--registry", registryBase);
|
|
76
|
+
// The reason this unit exists at all is that the bridge now starts at boot —
|
|
77
|
+
// and at boot the resolved drop-in is already in place, so the only
|
|
78
|
+
// nameserver discovery can find is this bridge. It refuses loopback, comes up
|
|
79
|
+
// with nowhere to forward, and NXDOMAINs every clearnet name including the
|
|
80
|
+
// registry. Recorded here, while a working resolver is still around to be
|
|
81
|
+
// asked, rather than rediscovered at boot when it cannot be.
|
|
82
|
+
if (upstreams.length) args.push("--upstream", upstreams.join(","));
|
|
75
83
|
const exec = [execPath, ...args].map((part) => (/\s/.test(part) ? JSON.stringify(part) : part)).join(" ");
|
|
76
84
|
|
|
77
85
|
const lines = [
|
package/src/dns.mjs
CHANGED
|
@@ -1163,7 +1163,23 @@ export function forwardQuery(msg, upstream, { timeoutMs = 3000 } = {}) {
|
|
|
1163
1163
|
export function isOurs(name, tldSet) {
|
|
1164
1164
|
if (!(tldSet instanceof Set) || tldSet.size === 0) return false;
|
|
1165
1165
|
const parsed = parseRegistryName(name);
|
|
1166
|
-
|
|
1166
|
+
if (!parsed) return false;
|
|
1167
|
+
// A real top-level domain is never ours, whatever the registry says it sold.
|
|
1168
|
+
//
|
|
1169
|
+
// Answering for one does not add a name, it removes the internet: every
|
|
1170
|
+
// lookup under that TLD stops being forwarded and starts being answered from
|
|
1171
|
+
// a namespace that has never heard of it. `.sh` was claimed for $2 and took
|
|
1172
|
+
// `pit.moshcode.sh` — the registry every bridge fetches its endings from —
|
|
1173
|
+
// off the air for anyone running a bridge, which is a resolver that cannot
|
|
1174
|
+
// resolve the thing it needs in order to resolve. It also blackholed the real
|
|
1175
|
+
// Saint Helena ccTLD on those machines, quietly, with a parking IP.
|
|
1176
|
+
//
|
|
1177
|
+
// Checked here rather than trusted from the registry because this is the half
|
|
1178
|
+
// that protects a person who is already running a bridge, today, against
|
|
1179
|
+
// endings that were sold before anyone noticed. The registry refusing to sell
|
|
1180
|
+
// new ones is the other half and it fixes nothing already on disk.
|
|
1181
|
+
if (isRealTld(parsed.tld)) return false;
|
|
1182
|
+
return tldSet.has(parsed.tld);
|
|
1167
1183
|
}
|
|
1168
1184
|
|
|
1169
1185
|
export function createServer(options = {}) {
|
|
@@ -1629,6 +1645,48 @@ export function dnsmasqCatchAllConf({ host = DEFAULT_HOST, port = DEFAULT_PORT }
|
|
|
1629
1645
|
* wrote 127.0.0.53 into resolv.conf is the thing sending us the query, and
|
|
1630
1646
|
* forwarding back to it is a loop that ends in a timeout rather than an answer.
|
|
1631
1647
|
*/
|
|
1648
|
+
/**
|
|
1649
|
+
* Upstreams named on the command line, which override discovery entirely.
|
|
1650
|
+
*
|
|
1651
|
+
* Discovery reads the machine's resolv.conf and drops loopback, which is right
|
|
1652
|
+
* until the machine's resolver is this bridge. Then the only nameserver on file
|
|
1653
|
+
* IS the bridge, discovery correctly refuses to return it, and the daemon comes
|
|
1654
|
+
* up with nowhere to forward — every clearnet name NXDOMAIN, including the
|
|
1655
|
+
* registry it needs in order to know which endings are Moshpit at all. The box
|
|
1656
|
+
* loses DNS entirely and the bridge cannot bootstrap out of it, because the
|
|
1657
|
+
* lookup that would fix it goes through the bridge.
|
|
1658
|
+
*
|
|
1659
|
+
* `dns enable` never hit this: it runs before the routing exists. A supervised
|
|
1660
|
+
* bridge starting at boot hits it every time, because the drop-in is a file and
|
|
1661
|
+
* is already in place. So the upstreams have to be sayable rather than only
|
|
1662
|
+
* discoverable, and `dns service` bakes the ones it found into the unit.
|
|
1663
|
+
*
|
|
1664
|
+
* Repeatable and comma-separated both work. `address#port` matches resolv.conf
|
|
1665
|
+
* and dnsmasq rather than inventing a third spelling.
|
|
1666
|
+
*/
|
|
1667
|
+
export function upstreamsFromArgs(args = []) {
|
|
1668
|
+
const servers = [];
|
|
1669
|
+
const invalid = [];
|
|
1670
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1671
|
+
if (args[i] !== "--upstream") continue;
|
|
1672
|
+
const value = args[i + 1];
|
|
1673
|
+
// A bare trailing `--upstream`, or one followed by the next flag, is a
|
|
1674
|
+
// typo rather than a request for no upstreams. Reported, not ignored.
|
|
1675
|
+
if (value === undefined || value.startsWith("--")) {
|
|
1676
|
+
invalid.push("(missing value)");
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
for (const part of value.split(",")) {
|
|
1680
|
+
const server = part.trim();
|
|
1681
|
+
if (!server) continue;
|
|
1682
|
+
const [address] = server.split("#");
|
|
1683
|
+
if (!isIP(address)) invalid.push(server);
|
|
1684
|
+
else if (!servers.includes(server)) servers.push(server);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
return { servers, invalid };
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1632
1690
|
export function parseUpstreams(resolvConf) {
|
|
1633
1691
|
const out = [];
|
|
1634
1692
|
for (const line of String(resolvConf ?? "").split("\n")) {
|
|
@@ -2278,6 +2336,7 @@ import { applyTrust, applyUntrust, createAutoTrust, trustName, verifyStockTls }
|
|
|
2278
2336
|
import { readFile, writeFile } from "node:fs/promises";
|
|
2279
2337
|
import { existsSync } from "node:fs";
|
|
2280
2338
|
import { fileURLToPath } from "node:url";
|
|
2339
|
+
import { isRealTld } from "./iana-tlds.mjs";
|
|
2281
2340
|
import { installService, removeService, serviceUnit, servicePaths, UNIT_NAME } from "./dns-service.mjs";
|
|
2282
2341
|
import {
|
|
2283
2342
|
applyPlan, daemonStatus, describePlan, detectPlatform, disablePlan, enablePlan,
|
|
@@ -2307,6 +2366,9 @@ const USAGE = `moshcode dns — resolve Moshpit names on this machine
|
|
|
2307
2366
|
look a name up; --open opens a parked name in the Pit
|
|
2308
2367
|
--json prints one stable document for scripts
|
|
2309
2368
|
moshcode dns start [--port N] run the resolver in the foreground
|
|
2369
|
+
--upstream IP[,IP] where to forward clearnet
|
|
2370
|
+
lookups; overrides resolv.conf discovery, which
|
|
2371
|
+
finds nothing once this machine is routed here
|
|
2310
2372
|
also serves parked names over HTTP so \`curl <name>\`
|
|
2311
2373
|
lands on the Pit; --parking-port N, --no-parking-http
|
|
2312
2374
|
--no-filter runs it with blocklists off
|
|
@@ -2514,7 +2576,12 @@ export async function dnsCommand(args = [], out = console.log, deps = {}) {
|
|
|
2514
2576
|
if (!park) out("! parking host did not resolve — unpointed names will return NXDOMAIN");
|
|
2515
2577
|
// Without these the bridge answers only for endings it is authoritative
|
|
2516
2578
|
// for, which is correct for per-ending routing and fatal for catch-all.
|
|
2517
|
-
|
|
2579
|
+
// Named upstreams win outright. Discovery is a fallback for the ordinary
|
|
2580
|
+
// case, not a second opinion: someone who says where to forward has almost
|
|
2581
|
+
// always said it because discovery got it wrong.
|
|
2582
|
+
const named = upstreamsFromArgs(rest);
|
|
2583
|
+
for (const bad of named.invalid) out(`! ignoring --upstream ${bad} — not an IP address`);
|
|
2584
|
+
const upstreams = named.servers.length ? named.servers : await discoverUpstreams();
|
|
2518
2585
|
// Swallowing this was the quietest way to turn the namespace off. An empty
|
|
2519
2586
|
// ending set makes isOurs() say no to every name, so with upstreams present
|
|
2520
2587
|
// the bridge forwards the whole of Moshpit to the clearnet, which denies it
|
|
@@ -2528,8 +2595,17 @@ export async function dnsCommand(args = [], out = console.log, deps = {}) {
|
|
|
2528
2595
|
(err) => ({ error: err?.message || String(err) }),
|
|
2529
2596
|
);
|
|
2530
2597
|
const tldSet = new Set(tlds.found || []);
|
|
2531
|
-
if (upstreams.length)
|
|
2532
|
-
|
|
2598
|
+
if (upstreams.length) {
|
|
2599
|
+
out(`forwarding non-Moshpit lookups to ${upstreams.join(", ")}${named.servers.length ? " (--upstream)" : ""}`);
|
|
2600
|
+
} else {
|
|
2601
|
+
out("! no upstreams found in /etc/resolv.conf — this bridge can only answer Moshpit names");
|
|
2602
|
+
// The specific way this happens is worth naming, because the symptom
|
|
2603
|
+
// (every name NXDOMAIN) looks nothing like the cause and the machine
|
|
2604
|
+
// cannot look the cause up.
|
|
2605
|
+
out(" if this machine's resolver is already routed here, discovery has only");
|
|
2606
|
+
out(" the bridge to find and correctly refuses it — say where to forward:");
|
|
2607
|
+
out(` moshcode dns start --port ${port} --upstream 1.1.1.1`);
|
|
2608
|
+
}
|
|
2533
2609
|
if (tldSet.size) out(`answering for ${tldSet.size} endings`);
|
|
2534
2610
|
else {
|
|
2535
2611
|
out(`! could not read the ending list from ${registryBase}${tlds.error ? ` — ${tlds.error}` : ""}`);
|
|
@@ -2707,7 +2783,22 @@ export async function dnsCommand(args = [], out = console.log, deps = {}) {
|
|
|
2707
2783
|
return 0;
|
|
2708
2784
|
}
|
|
2709
2785
|
|
|
2710
|
-
|
|
2786
|
+
// Asked now, not at boot. This command runs while the machine still has a
|
|
2787
|
+
// resolver that answers; the service it writes will not.
|
|
2788
|
+
const upstreams = upstreamsFromArgs(rest).servers.length
|
|
2789
|
+
? upstreamsFromArgs(rest).servers
|
|
2790
|
+
: await discoverUpstreams();
|
|
2791
|
+
const unit = serviceUnit({ system, entry: cliEntry(), port, registryBase, upstreams });
|
|
2792
|
+
|
|
2793
|
+
if (!upstreams.length) {
|
|
2794
|
+
out("! no upstream nameservers found, and none given");
|
|
2795
|
+
out(" a bridge with nowhere to forward answers NXDOMAIN for every clearnet");
|
|
2796
|
+
out(" name — including the registry it needs to know which endings exist.");
|
|
2797
|
+
out(" If this machine is already routed here, discovery can only see the");
|
|
2798
|
+
out(" bridge and correctly refuses it. Say where to forward:");
|
|
2799
|
+
out(` moshcode dns service --upstream 1.1.1.1${rest.includes("--write") ? " --write" : ""}`);
|
|
2800
|
+
out("");
|
|
2801
|
+
}
|
|
2711
2802
|
|
|
2712
2803
|
if (rest.includes("--write")) {
|
|
2713
2804
|
const result = await installService(unit, { system });
|