UNPKG

hsd

Version:
290 lines (229 loc) 5.45 kB
/*! * seeder.js - dns seed server for hsd * Copyright (c) 2020, Christopher Jeffrey (MIT License). * https://github.com/handshake-org/hsd */ 'use strict'; const assert = require('bsert'); const IP = require('binet'); const bns = require('bns'); const HostList = require('../net/hostlist'); const { DNSServer, wire, util } = bns; const { Message, Record, ARecord, AAAARecord, NSRecord, SOARecord, types, codes } = wire; /** * Seeder */ class Seeder extends DNSServer { constructor(options) { assert(options != null); super({ inet6: false, tcp: false }); this.ra = false; this.edns = true; this.dnssec = false; this.hosts = new HostList({ network: options.network, prefix: options.prefix, filename: options.filename, memory: false }); this.zone = '.'; this.ns = '.'; this.ip = null; this.host = '127.0.0.1'; this.port = 53; this.lastRefresh = 0; this.res4 = new Message(); this.res6 = new Message(); this.initOptions(options); } initOptions(options) { assert(options != null); this.parseOptions(options); if (options.zone != null) this.zone = util.fqdn(options.zone); if (options.ns != null) this.ns = util.fqdn(options.ns); if (options.ip != null) this.ip = IP.normalize(options.ip); if (options.host != null) this.host = IP.normalize(options.host); if (options.port != null) { assert((options.port & 0xffff) === options.port); assert(options.port !== 0); this.port = options.port; } return this; } async resolve(req, rinfo) { const [qs] = req.question; const {name, type} = qs; if (!util.isSubdomain(this.zone, name)) throw createError(codes.NOTZONE); if (!util.equal(name, this.zone)) return this.createEmpty(codes.NXDOMAIN); await this.refresh(); switch (type) { case types.ANY: case types.A: return this.res4.clone(); case types.AAAA: return this.res6.clone(); case types.NS: return this.createNS(); case types.SOA: return this.createSOA(); } return this.createEmpty(codes.SUCCESS); } async refresh() { if (Date.now() > this.lastRefresh + 10 * 60 * 1000) { this.hosts.reset(); try { await this.hosts.loadFile(); } catch (e) { return; } this.lastRefresh = Date.now(); this.res4 = this.createA(types.A); this.res6 = this.createA(types.AAAA); } } createA(type) { const res = new Message(); const items = []; for (const entry of this.hosts.map.values()) { const {addr} = entry; if (this.hosts.isStale(entry)) continue; if (!entry.lastSuccess) continue; if (addr.port !== this.hosts.network.port) continue; if (addr.hasKey()) continue; if (!addr.isValid()) continue; items.push(entry); } items.sort((a, b) => { return b.lastSuccess - a.lastSuccess; }); for (const entry of items) { const {addr} = entry; switch (type) { case types.A: if (!addr.isIPv4()) continue; res.answer.push(createA(this.zone, addr.host)); break; case types.AAAA: if (!addr.isIPv6()) continue; res.answer.push(createAAAA(this.zone, addr.host)); break; } if (res.answer.length === 50) break; } if (res.answer.length === 0) return this.createEmpty(codes.SUCCESS); return res; } createNS() { const res = new Message(); res.answer.push(createNS(this.zone, this.ns)); if (this.ip) { const rr = IP.isIPv6String(this.ip) ? createAAAA(this.ns, this.ip) : createA(this.ns, this.ip); res.additional.push(rr); } return res; } createSOA() { const res = new Message(); res.answer.push(createSOA(this.zone, this.ns, this.zone)); return res; } createEmpty(code) { const res = new Message(); res.code = code; res.authority.push(createSOA(this.zone, this.ns, this.zone)); return res; } async open() { return super.open(this.port, this.host); } } /* * Helpers */ function createSOA(name, ns, mbox) { const rr = new Record(); const rd = new SOARecord(); rr.name = name; rr.type = types.SOA; rr.ttl = 86400; rr.data = rd; rd.ns = ns; rd.mbox = mbox; rd.serial = Math.floor(Date.now() / 1000); rd.refresh = 604800; rd.retry = 86400; rd.expire = 2592000; rd.minttl = 604800; return rr; } function createNS(name, ns) { const rr = new Record(); const rd = new NSRecord(); rr.name = name; rr.type = types.NS; rr.ttl = 40000; rr.data = rd; rd.ns = ns; return rr; } function createA(name, address) { const rr = new Record(); const rd = new ARecord(); rr.name = name; rr.type = types.A; rr.ttl = 3600; rr.data = rd; rd.address = address; return rr; } function createAAAA(name, address) { const rr = new Record(); const rd = new AAAARecord(); rr.name = name; rr.type = types.AAAA; rr.ttl = 3600; rr.data = rd; rd.address = address; return rr; } function createError(code) { const err = new Error('Invalid request.'); err.type = 'DNSError'; err.errno = code; return err; } /* * Expose */ module.exports = Seeder;