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
12 changes: 11 additions & 1 deletion lib/dns/policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,17 @@ export function moshpitCandidate(qname: string): { name: string; label: string;
const tld = parts[parts.length - 1];
const label = parts[parts.length - 2];
if (NEVER_MOSHPIT.has(tld)) return null;
if (/^\d+$/.test(tld)) return null;
// A name whose every label is a number is address-shaped, and reading
// `10.0.0.1` or `192.168` as a name in this namespace would let anyone who
// registered `.1` intercept traffic meant for a machine. A *numeric ending*
// under a real label is a different thing: `.2600` is a registered ending,
// and `alt.2600` can only ever be a name. Rejecting the whole class here
// meant the registry sold endings the resolver then refused to resolve.
//
// The registry still decides whether the name exists. This is only the shape
// filter, and it should exclude what can never be a name rather than what
// merely looks unusual.
if (parts.every((part) => /^\d+$/.test(part))) return null;
if (tld.length < 2) return null;
if (!LABEL.test(tld) || !LABEL.test(label)) return null;
// Underscore-prefixed service labels (`_dmarc`, `_acme-challenge`) are legal
Expand Down
24 changes: 23 additions & 1 deletion tests/dns-policy.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,33 @@ test("names the legacy root already owns are never sent to the registry", () =>
});

test("things that only look like names are not names", () => {
for (const name of ["", "localhost", "203.0.113.7", "eggs", "scrambled.e", "-bad.eggs", "scrambled.123"]) {
for (const name of ["", "localhost", "203.0.113.7", "eggs", "scrambled.e", "-bad.eggs"]) {
assert.equal(moshpitCandidate(name), null, `${name} should not be a Moshpit candidate`);
}
});

test("a numeric ending is a real ending", () => {
// `.2600` is registered, and the resolver used to refuse every name under
// it — the registry sold endings that could not resolve. A numeric ending
// under a real label can only ever be a name.
const candidate = moshpitCandidate("alt.2600");
assert.equal(candidate?.name, "alt.2600");
assert.equal(candidate?.tld, "2600");
assert.equal(candidate?.label, "alt");
assert.equal(moshpitCandidate("www.alt.2600")?.name, "alt.2600", "subdomains of it still work");
});

test("an address is still never read as a name", () => {
// This is what the old blanket rule was protecting, and it has to keep
// holding: if `10.0.0.1` were a candidate, whoever registered `.1` could
// intercept traffic meant for a machine.
for (const address of ["10.0.0.1", "203.0.113.7", "192.168", "1.2.3.4", "8.8.8.8", "12.34"]) {
assert.equal(moshpitCandidate(address), null, `${address} is an address, not a name`);
}
// Colons are IPv6, rejected earlier and for the same reason.
assert.equal(moshpitCandidate("2604:a880:400:d1:0:4:c3fe:1"), null);
});

test("clearnet mode forwards first and lets the registry backfill", () => {
const plan = planQuery({ question: question("scrambled.eggs"), rd: true, mode: "clearnet" });
assert.equal(plan.action, "forward-first");
Expand Down
Loading