From c3934acb303244822f13728d0ee70e99b18ef736 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 14 Dec 2017 20:26:28 -0700 Subject: [PATCH] add tcp support --- README.md | 2 + bin/digd.js | 103 +++++++++++++++++++++++----------------------------- lib/tcpd.js | 70 +++++++++++++++++++++++++++++++++++ lib/udpd.js | 58 +++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+), 58 deletions(-) create mode 100644 lib/tcpd.js create mode 100644 lib/udpd.js diff --git a/README.md b/README.md index 7c816a7..3a916f4 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,8 @@ Options +time= Sets the timeout for a query in seconds. +norecurse Set `ra` flag to 0. Do not perform recursion. +aaonly Set `aa` flag to 1. Do not respond with non-authoritative responses. ++notcp Disable TCP server (default in v1.2) ++tcp Enable TCP server (default in v1.3) --debug verbose output ``` diff --git a/bin/digd.js b/bin/digd.js index 8955f31..21f0bfb 100755 --- a/bin/digd.js +++ b/bin/digd.js @@ -5,9 +5,7 @@ var cli = require('cli'); var pkg = require('../package.json'); var dig = require('dig.js/dns-request'); -var dgram = require('dgram'); var dnsjs = require('dns-suite'); -var crypto = require('crypto'); var common = require('dig.js/common'); var defaultNameservers = require('dns').getServers(); var hexdump; @@ -49,8 +47,32 @@ cli.main(function (args, cli) { cli.norecurse = true; return; } + if (arg === '+notcp') { + if (cli.notcp) { + console.error("'+notcp' was specified more than once"); + process.exit(1); + return; + } + cli.notcp = true; + return; + } + if (arg === '+tcp') { + if (cli.tcp) { + console.error("'+tcp' was specified more than once"); + process.exit(1); + return; + } + cli.tcp = true; + return; + } }); + if (!cli.tcp) { + if (!cli.notcp) { + console.info("[WARNING] Set '+notcp' to disable tcp connections. The default behavior changes to +tcp in v1.3"); + } + } + if (cli.mdns) { if (!cli.type) { cli.type = cli.t = 'PTR'; @@ -73,32 +95,10 @@ cli.main(function (args, cli) { } } - var handlers = {}; - var server = dgram.createSocket({ - type: cli.udp6 ? 'udp6' : 'udp4' - , reuseAddr: true - }); - server.bind({ - port: cli.port - , address: cli.address - }); - - handlers.onError = function (err) { - if ('EACCES' === err.code) { - console.error(""); - console.error("EACCES: Couldn't bind to port. You probably need to use sudo, authbind, or setcap."); - console.error(""); - process.exit(123); - return; - } - console.error("error:", err.stack); - server.close(); - }; - - handlers.onMessage = function (nb, rinfo) { - console.log('[DEBUG] got a message'); - - var queryAb = nb.buffer.slice(nb.byteOffset, nb.byteOffset + nb.byteLength); + var dnsd = {}; + dnsd.onMessage = function (nb, cb) { + var byteOffset = nb._dnsByteOffset || nb.byteOffset; + var queryAb = nb.buffer.slice(byteOffset, byteOffset + nb.byteLength); var query; var count; @@ -215,12 +215,11 @@ cli.main(function (args, cli) { console.error("Could not write empty DNS response"); console.error(e); console.error(emptyResp); + cb(e, null, '[DEV] response sent (empty)'); return; } - server.send(newAb, rinfo.port, rinfo.address, function () { - console.log('[DEV] response sent (empty)', rinfo.port, rinfo.address); - }); + cb(null, newAb, '[DEV] response sent (empty)'); } function sendResponse(newPacket) { @@ -232,12 +231,11 @@ cli.main(function (args, cli) { console.error("Could not write DNS response from local"); console.error(e); console.error(newPacket); + cb(e, null, '[DEV] response sent (local query)'); return; } - server.send(newAb, rinfo.port, rinfo.address, function () { - console.log('[DEV] response sent (local query)', rinfo.port, rinfo.address); - }); + cb(null, newAb, '[DEV] response sent (local query)'); } function recurse() { @@ -290,12 +288,11 @@ cli.main(function (args, cli) { } catch(e) { console.error("Could not write DNS response"); console.error(newResponse); + cb(e, null, '[DEV] response sent'); return; } - server.send(newAb, rinfo.port, rinfo.address, function () { - console.log('[DEV] response sent', rinfo.port, rinfo.address); - }); + cb(null, newAb, '[DEV] response sent'); } } @@ -335,7 +332,8 @@ cli.main(function (args, cli) { console.log('request sent to', res.nameserver); } */ - console.log('[DEV] query sent (recurse)', rinfo.port, rinfo.address); + //console.log('[DEV] query sent (recurse)', rinfo.port, rinfo.address); + //dnsd.onSent('[DEV] query sent (recurse)'); } , onTimeout: function (res) { console.log(";; [" + q.name + "] connection timed out; no servers could be reached"); @@ -393,32 +391,24 @@ cli.main(function (args, cli) { return; } require('../lib/digd.js').query(engine, query, respondWithResults); - }; - handlers.onListening = function () { - /*jshint validthis:true*/ - var server = this; + cli.defaultNameservers = defaultNameservers; + require('../lib/udpd.js').create(cli, dnsd).on('listening', function () { cli.chosenNameserver = cli.nameserver; var index; if (!cli.chosenNameserver) { - index = crypto.randomBytes(2).readUInt16BE(0) % defaultNameservers.length; - cli.chosenNameserver = defaultNameservers[index]; + index = require('crypto').randomBytes(2).readUInt16BE(0) % cli.defaultNameservers.length; + cli.chosenNameserver = cli.defaultNameservers[index]; if (cli.debug) { - console.log('index, defaultNameservers', index, defaultNameservers); + console.log('index, defaultNameservers', index, cli.defaultNameservers); } } - - if (cli.mdns || '224.0.0.251' === cli.nameserver) { - server.setBroadcast(true); - server.addMembership(cli.nameserver); - } - - console.log(''); - console.log('Bound and Listening:'); - console.log(server.address().address + '#' + server.address().port + ' (' + server.type + ')'); - }; + }); + if (cli.tcp /* TODO v1.3 !cli.notcp */) { + require('../lib/tcpd.js').create(cli, dnsd); + } console.log(''); if (!cli.nocmd) { @@ -426,7 +416,4 @@ cli.main(function (args, cli) { console.log(';; global options: +cmd'); } - server.on('error', handlers.onError); - server.on('message', handlers.onMessage); - server.on('listening', handlers.onListening); }); diff --git a/lib/tcpd.js b/lib/tcpd.js new file mode 100644 index 0000000..8b6d608 --- /dev/null +++ b/lib/tcpd.js @@ -0,0 +1,70 @@ +'use strict'; + +module.exports.create = function (cli, dnsd) { + function runTcp() { + var tcpServer = require('net').createServer({ }, function (c) { + c.on('error', function (err) { + console.warn("TCP Connection Error:"); + console.warn(err); + }); + c.on('data', function (nb) { + //console.log('TCP data.length:', nb.length); + //console.log(nb.toString('hex')); + + // DNS packets include a 2-byte length header + var count = nb.length; + var length = nb[0] << 8; + length = length | nb[1]; + count -= 2; + // TODO slice? + nb._dnsByteOffset = nb.byteOffset + 2; + + if (length !== count) { + console.error("Handling TCP packets > 512 bytes not implemented."); + c.end(); + return; + } + + // TODO pad two bytes for lengths + dnsd.onMessage(nb, function (err, newAb, dbgmsg) { + var lenbuf = Buffer.from([ newAb.length >> 8, newAb.length & 255 ]); + // TODO XXX generate legit error packet + if (err) { console.error("Error", err); c.end(); return; } + console.log('TCP ' + dbgmsg); + + c.write(lenbuf); + c.end(newAb); + }); + }); + c.on('end', function () { + console.log('TCP client disconnected from server'); + }); + }); + + tcpServer.on('error', function (err) { + if ('EADDRINUSE' === err.code) { + console.error("Port '" + cli.port + "' is already in use."); + tcpServer.close(); + process.exit(0); + } + if ('EACCES' === err.code) { + console.error("Could not bind on port '" + cli.port + "': EACCESS (you probably need root permissions)"); + tcpServer.close(); + process.exit(0); + } + console.error("TCP Server Error:"); + console.error(err); + tcpServer.close(function () { + setTimeout(runTcp, 1000); + }); + }); + + tcpServer.listen(cli.port, function () { + console.log('TCP Server bound'); + }); + + return tcpServer; + } + + return runTcp(); +}; diff --git a/lib/udpd.js b/lib/udpd.js new file mode 100644 index 0000000..fbdf317 --- /dev/null +++ b/lib/udpd.js @@ -0,0 +1,58 @@ +'use strict'; + +module.exports.create = function (cli, dnsd) { + var server = require('dgram').createSocket({ + type: cli.udp6 ? 'udp6' : 'udp4' + , reuseAddr: true + }); + server.bind({ + port: cli.port + , address: cli.address + }); + + var handlers = {}; + handlers.onError = function (err) { + if ('EACCES' === err.code) { + console.error(""); + console.error("EACCES: Couldn't bind to port. You probably need to use sudo, authbind, or setcap."); + console.error(""); + process.exit(123); + return; + } + console.error("error:", err.stack); + server.close(); + }; + + handlers.onMessage = function (nb, rinfo) { + //console.log('[DEBUG] got a UDP message', nb.length); + //console.log(nb.toString('hex')); + + dnsd.onMessage(nb, function (err, newAb, dbgmsg) { + // TODO send legit error message + if (err) { server.send(Buffer.from([0x00])); return; } + server.send(newAb, rinfo.port, rinfo.address, function () { + console.log(dbgmsg, rinfo.port, rinfo.address); + }); + }); + }; + + handlers.onListening = function () { + /*jshint validthis:true*/ + var server = this; + + if (cli.mdns || '224.0.0.251' === cli.nameserver) { + server.setBroadcast(true); + server.addMembership(cli.nameserver); + } + + console.log(''); + console.log('Bound and Listening:'); + console.log(server.address().address + '#' + server.address().port + ' (' + server.type + ')'); + }; + + server.on('error', handlers.onError); + server.on('message', handlers.onMessage); + server.on('listening', handlers.onListening); + + return server; +};