diff --git a/lib/dns/server.js b/lib/dns/server.js index 3c995039d4..a9f96614b6 100644 --- a/lib/dns/server.js +++ b/lib/dns/server.js @@ -239,7 +239,7 @@ class RootServer extends DNSServer { throw new Error('Tree not available.'); if (!this.chain.safeEntry) - throw new Error('Chain is not safe for name resolution.'); + throw new NotReadyError('Chain is not safe for name resolution.'); const {treeRoot} = this.chain.safeEntry; const hash = rules.hashName(name); @@ -389,7 +389,7 @@ class RootServer extends DNSServer { // (These requests are most // likely bad anyways) if (!rules.verifyName(tld)) - throw new Error('Invalid name.'); + throw new BlockedError('Invalid name.'); // Ask the urkel tree for the name data. const data = !this.blacklist.has(tld) @@ -544,7 +544,13 @@ class RootServer extends DNSServer { } catch (e) { this.logger.error(e); res = new Message(); - res.code = codes.REFUSED; + + if (e.type === 'EDError') { + res.code = codes.REFUSED; + res.edns.setEDE(e.infoCode, e.message); + } else { + res.code = codes.SERVFAIL; + } } return res; @@ -807,10 +813,51 @@ class RecursiveServer extends DNSServer { } } +/** + * Extended DNS Error + * see https://www.ietf.org/rfc/rfc8914.html + * @extends Error + * @property {Number} infoCode + * @property {String} extraText + */ + +class EDError extends Error { + /** + * Create an error. + * @constructor + * @param {Number} infoCode + * @param {String} extraText + */ + + constructor(infoCode, msg) { + super(); + this.type = 'EDError'; + this.infoCode = infoCode; + this.message = msg; + + if (Error.captureStackTrace) + Error.captureStackTrace(this, EDError); + } +} + /* * Helpers */ +class NotReadyError extends EDError { + constructor(msg) { + // https://www.rfc-editor.org/rfc/rfc8914#name-extended-dns-error-code-14- + super(14, msg); + } +} + +class BlockedError extends EDError { + constructor(msg) { + // https://www.rfc-editor.org/rfc/rfc8914#name-extended-dns-error-code-15- + super(15, msg); + } +} + function toKey(name, type) { const labels = util.split(name); const label = util.from(name, labels, -1); diff --git a/test/ns-test.js b/test/ns-test.js index 20a7e03e01..7a5c4185ca 100644 --- a/test/ns-test.js +++ b/test/ns-test.js @@ -38,6 +38,19 @@ describe('RootServer', function() { await ns.close(); }); + it('should send SERVFAIL', async () => { + const name = 'macaroni.'; + const type = wire.types.A; + const req = { + question: [{name, type}] + }; + + // Looking up real name without Urkel Tree + assert(!ns.lookup); + const res = await ns.resolve(req); + assert(res.code === wire.codes.SERVFAIL); + }); + it('should resolve . NS as SYNTH4', async () => { // Default assert.strictEqual(ns.publicHost, '127.0.0.1'); @@ -329,12 +342,7 @@ describe('RootServer Plugins', function() { describe('RootServer DNSSEC', function () { const ns = new RootServer({ - chain: { - safeEntry: { - treeRoot: Buffer.alloc(32), - time: Date.now() / 1000 - } - }, + chain: {}, port: 25349, // regtest lookup: (hash) => { assert(hash instanceof Buffer); @@ -410,10 +418,51 @@ describe('RootServer DNSSEC', function () { } }); + it('should refuse to resolve without safe entry', async () => { + const qname = 'pizza.'; + const res1 = await resolve(qname, wire.types.NS); + assert.strictEqual(res1.code, wire.codes.REFUSED); + + assert(res1.edns); + assert.strictEqual(res1.edns.options.length, 1); + assert.deepStrictEqual( + res1.edns.options[0].getJSON(), + { + code: 'EDE', + option: { + infoCode: 14, // "Not Ready" + extraText: 'Chain is not safe for name resolution.' + } + } + ); + + // Confirm imaginary blocks + ns.chain.safeEntry = { + treeRoot: Buffer.alloc(32), + time: Date.now() / 1000 + }; + + const res2 = await resolve(qname, wire.types.NS); + assert.strictEqual(res2.code, wire.codes.NXDOMAIN); + }); + it('should refuse invalid names', async () => { const qname = 'example\\000'; const res = await resolve(qname, wire.types.NS); assert.strictEqual(res.code, wire.codes.REFUSED); + + assert(res.edns); + assert.strictEqual(res.edns.options.length, 1); + assert.deepStrictEqual( + res.edns.options[0].getJSON(), + { + code: 'EDE', + option: { + infoCode: 15, // "Blocked" + extraText: 'Invalid name.' + } + } + ); }); it('should prove NXDOMAIN', async () => {