diff --git a/src/dns.mjs b/src/dns.mjs index 323508c..54d3f8b 100644 --- a/src/dns.mjs +++ b/src/dns.mjs @@ -2231,6 +2231,17 @@ export async function dnsCommand(args = [], out = console.log, deps = {}) { if (proxyIndex >= 0) { const given = rest[proxyIndex + 1]; const host = given && !given.startsWith("-") ? given : null; + // A host name passes the reachability probe (connect resolves it) but a + // DNS answer can only carry an address — isIP would leave both families + // null, so the mode would announce success and then NODATA every live + // name. That is the very outage the gate below exists to refuse, so it is + // refused here for the same reason rather than warned about. + if (host && !isIP(host)) { + out(`! --proxy needs an IP address, not a host name like "${host}"`); + out(" a name here answers every live Moshpit name with nothing, which reads"); + out(" as a total outage — pass the proxy's address (127.0.0.1 or ::1) instead."); + return 1; + } const candidates = host ? [host] : ["127.0.0.1", "::1"]; const reachable = []; for (const candidate of candidates) { diff --git a/test/dns-proxy-mode.test.mjs b/test/dns-proxy-mode.test.mjs index ad3a4a1..9e07553 100644 --- a/test/dns-proxy-mode.test.mjs +++ b/test/dns-proxy-mode.test.mjs @@ -189,3 +189,28 @@ test("an explicit --proxy host is the only one probed", async () => { await held.release(); } }); + +test("--proxy with a host name refuses instead of NODATA'ing every live name", async () => { + // A name like `localhost` passes the reachability probe (connect resolves it) + // but cannot go in an A/AAAA answer, so the mode would announce success and + // then answer every live name with nothing — the outage the gate exists for. + // Hold the resolver port so that even without the fix `start` cannot bind and + // sit on the loop — the assertion is about the refusal, not the bind. + const held = await holdUdp(); + try { + const lines = []; + let probed = false; + const code = await dnsCommand(["start", "--proxy", "localhost", "--port", String(held.port)], (l) => lines.push(l), { + tlds: async () => ["eggs"], + proxyReachableImpl: async () => { probed = true; return true; }, + }); + + assert.equal(code, 1); + assert.equal(probed, false, "a host name is rejected before anything is probed"); + const text = lines.join("\n"); + assert.match(text, /needs an IP address/); + assert.doesNotMatch(text, /proxying every live name/, "it must not claim to have started proxying"); + } finally { + await held.release(); + } +});