From 7660280c2a83c074c2b6548c34175c95cd84462b Mon Sep 17 00:00:00 2001 From: Matthew Zipkin Date: Fri, 5 Feb 2021 15:16:54 -0500 Subject: [PATCH] node: accept --no-dns and --no-rs configuration option to disable NS and RS --- CHANGELOG.md | 9 +++ lib/node/fullnode.js | 57 +++++++++++-------- lib/node/spvnode.js | 57 +++++++++++-------- test/dns-test.js | 129 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+), 44 deletions(-) create mode 100644 test/dns-test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index d8672972c..acf733e6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,15 @@ - Root server DNSSEC has been fixed. It is only authoritative over DS and TXT records, and only returns TXT if no NS (referral) is present in the zone. +### Node changes + +- `FullNode` and `SPVNode` accept configuration parameter `--no-dns` (or `no-dns: true` in +`hsd.conf`) which launches the node without either DNS server (the root authoritative +server and the recursive resolver). This avoids some port collisions with other HNS resolvers +like hnsd running locally, and generally separates and reduces security concerns around +running unneeded servers when a node is just used for transactions and blocks. +`--no-rs` is also accepted to disable the recursive DNS resolver (but keep the root server). + ### Wallet API changes - Adds new wallet HTTP endpoint `/wallet/:id/auction` based on `POST /wallet/:id/bid`. diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js index 73d585fde..44bc098b0 100644 --- a/lib/node/fullnode.js +++ b/lib/node/fullnode.js @@ -147,24 +147,28 @@ class FullNode extends Node { cors: this.config.bool('cors') }); - this.ns = new RootServer({ - logger: this.logger, - key: this.identityKey, - host: this.config.str('ns-host'), - port: this.config.uint('ns-port', this.network.nsPort), - lookup: key => this.chain.db.tree.get(key), - publicHost: this.config.str('public-host') - }); - - this.rs = new RecursiveServer({ - logger: this.logger, - key: this.identityKey, - host: this.config.str('rs-host'), - port: this.config.uint('rs-port', this.network.rsPort), - stubHost: this.ns.host, - stubPort: this.ns.port, - noUnbound: this.config.bool('rs-no-unbound') - }); + if (!this.config.bool('no-dns')) { + this.ns = new RootServer({ + logger: this.logger, + key: this.identityKey, + host: this.config.str('ns-host'), + port: this.config.uint('ns-port', this.network.nsPort), + lookup: key => this.chain.db.tree.get(key), + publicHost: this.config.str('public-host') + }); + + if (!this.config.bool('no-rs')) { + this.rs = new RecursiveServer({ + logger: this.logger, + key: this.identityKey, + host: this.config.str('rs-host'), + port: this.config.uint('rs-port', this.network.rsPort), + stubHost: this.ns.host, + stubPort: this.ns.port, + noUnbound: this.config.bool('rs-no-unbound') + }); + } + } this.init(); } @@ -259,8 +263,13 @@ class FullNode extends Node { await this.openPlugins(); await this.http.open(); - await this.ns.open(); - await this.rs.open(); + + if (this.ns) + await this.ns.open(); + + if (this.rs) + await this.rs.open(); + await this.handleOpen(); if (this.has('walletdb')) { @@ -286,8 +295,12 @@ class FullNode extends Node { await this.handlePreclose(); await this.http.close(); - await this.rs.close(); - await this.ns.close(); + + if (this.rs) + await this.rs.close(); + + if (this.ns) + await this.ns.close(); await this.closePlugins(); diff --git a/lib/node/spvnode.js b/lib/node/spvnode.js index d3c86013b..823b8fb7a 100644 --- a/lib/node/spvnode.js +++ b/lib/node/spvnode.js @@ -93,24 +93,28 @@ class SPVNode extends Node { cors: this.config.bool('cors') }); - this.ns = new RootServer({ - logger: this.logger, - key: this.identityKey, - host: this.config.str('ns-host'), - port: this.config.uint('ns-port', this.network.nsPort), - lookup: key => this.pool.resolve(key), - publicHost: this.config.str('public-host') - }); - - this.rs = new RecursiveServer({ - logger: this.logger, - key: this.identityKey, - host: this.config.str('rs-host'), - port: this.config.uint('rs-port', this.network.rsPort), - stubHost: this.ns.host, - stubPort: this.ns.port, - noUnbound: this.config.bool('rs-no-unbound') - }); + if (!this.config.bool('no-dns')) { + this.ns = new RootServer({ + logger: this.logger, + key: this.identityKey, + host: this.config.str('ns-host'), + port: this.config.uint('ns-port', this.network.nsPort), + lookup: key => this.pool.resolve(key), + publicHost: this.config.str('public-host') + }); + + if (!this.config.bool('no-rs')) { + this.rs = new RecursiveServer({ + logger: this.logger, + key: this.identityKey, + host: this.config.str('rs-host'), + port: this.config.uint('rs-port', this.network.rsPort), + stubHost: this.ns.host, + stubPort: this.ns.port, + noUnbound: this.config.bool('rs-no-unbound') + }); + } + } this.init(); } @@ -172,8 +176,13 @@ class SPVNode extends Node { await this.openPlugins(); await this.http.open(); - await this.ns.open(); - await this.rs.open(); + + if (this.ns) + await this.ns.open(); + + if (this.rs) + await this.rs.open(); + await this.handleOpen(); this.logger.info('Node is loaded.'); @@ -190,8 +199,12 @@ class SPVNode extends Node { await this.handlePreclose(); await this.http.close(); - await this.rs.close(); - await this.ns.close(); + + if (this.rs) + await this.rs.close(); + + if (this.ns) + await this.ns.close(); await this.closePlugins(); diff --git a/test/dns-test.js b/test/dns-test.js new file mode 100644 index 000000000..71aaef994 --- /dev/null +++ b/test/dns-test.js @@ -0,0 +1,129 @@ +/* eslint-env mocha */ +/* eslint prefer-arrow-callback: "off" */ + +'use strict'; + +const assert = require('bsert'); +const Network = require('../lib/protocol/network'); +const FullNode = require('../lib/node/fullnode'); +const SPVNode = require('../lib/node/spvnode'); +const network = Network.get('regtest'); + +const {Resolver} = require('dns').promises; + +const rootResolver = new Resolver({timeout: 1000}); +const recursiveResolver = new Resolver({timeout: 1000}); +rootResolver.setServers([`127.0.0.1:${network.nsPort}`]); +recursiveResolver.setServers([`127.0.0.1:${network.rsPort}`]); + +describe('Server Configuration', function() { + describe('Full Node', function() { + let node; + + afterEach(async () => { + await node.close(); + }); + + it('should open full node with both DNS servers', async () => { + node = new FullNode({ + memory: true, + network: network.type + }); + + await node.open(); + const res1 = await rootResolver.resolveSoa('.'); + assert(res1); + const res2 = await recursiveResolver.resolveSoa('.'); + assert(res2); + }); + + it('should open full node with neither DNS server', async () => { + node = new FullNode({ + memory: true, + network: network.type, + noDns: true + }); + + await node.open(); + await assert.rejects( + rootResolver.resolveSoa('.'), + {message: 'querySoa ECONNREFUSED .'} + ); + await assert.rejects( + recursiveResolver.resolveSoa('.'), + {message: 'querySoa ECONNREFUSED .'} + ); + }); + + it('should open full node only with root name server', async () => { + node = new FullNode({ + memory: true, + network: network.type, + noRs: true + }); + + await node.open(); + const res1 = await rootResolver.resolveSoa('.'); + assert(res1); + await assert.rejects( + recursiveResolver.resolveSoa('.'), + {message: 'querySoa ECONNREFUSED .'} + ); + }); + }); + + describe('SPV Node', function() { + let node; + + afterEach(async () => { + await node.close(); + }); + + it('should open SPV node with both DNS servers', async () => { + node = new SPVNode({ + memory: true, + network: network.type + }); + + await node.open(); + const res1 = await rootResolver.resolveSoa('.'); + assert(res1); + const res2 = await recursiveResolver.resolveSoa('.'); + assert(res2); + }); + + it('should open SPV node with neither DNS server', async () => { + node = new SPVNode({ + memory: true, + network: network.type, + noDns: true + }); + + await node.open(); + await assert.rejects( + rootResolver.resolveSoa('.'), + {message: 'querySoa ECONNREFUSED .'} + ); + await assert.rejects( + recursiveResolver.resolveSoa('.'), + {message: 'querySoa ECONNREFUSED .'} + ); + }); + + it('should open SPV node only with root name server', async () => { + node = new SPVNode({ + memory: true, + network: network.type, + noRs: true + }); + + await node.open(); + const res1 = await rootResolver.resolveSoa('.'); + assert(res1); + await assert.rejects( + recursiveResolver.resolveSoa('.'), + {message: 'querySoa ECONNREFUSED .'} + ); + }); + }); +});