2017-03-31 06:08:15 +00:00
'use strict' ;
var cli = require ( 'cli' ) ;
2017-09-25 20:43:21 +00:00
var dig = require ( '../dns-request' ) ;
2017-09-16 00:43:02 +00:00
var dgram = require ( 'dgram' ) ;
var dnsjs = require ( 'dns-suite' ) ;
2017-09-25 20:43:21 +00:00
var hexdump = require ( 'hexdump.js' ) . hexdump ;
2017-03-31 06:08:15 +00:00
var crypto = require ( 'crypto' ) ;
2017-09-16 00:43:02 +00:00
var common = require ( '../common' ) ;
2017-09-20 18:29:43 +00:00
var defaultNameservers = require ( 'dns' ) . getServers ( ) ;
2017-09-16 00:43:02 +00:00
2017-03-31 06:08:15 +00:00
cli . parse ( {
// 'b': [ false, 'set source IP address (defaults to 0.0.0.0)', 'string' ]
2017-09-16 00:43:02 +00:00
'class' : [ 'c' , 'class (defaults to IN)' , 'string' , 'IN' ]
, 'debug' : [ false , 'more verbose output' , 'boolean' , false ]
2017-03-31 06:08:15 +00:00
//, 'insecure': [ false, 'turn off RaNDOm cAPS required for securing queries']
//, 'ipv4': [ '4', 'use ipv4 exclusively (defaults to false)', 'boolean', false ]
//, 'ipv6': [ '6', 'use ipv6 exclusively (defaults to false)', 'boolean', false ]
//, 'json': [ false, 'output results as json', 'string' ]
//, 'lint': [ false, 'attack (in the metaphorical sense) a nameserver with all sorts of queries to test for correct responses', 'string', false ]
, 'mdns' : [ false , "Alias for setting defaults to -p 5353 @224.0.0.251 -t PTR -q _services._dns-sd._udp.local and waiting for multiple responses" , 'boolean' , false ]
2017-09-16 00:43:02 +00:00
, 'timeout' : [ false , "How long, in milliseconds, to wait for a response. Alias of +time=" , 'int' , false ]
2017-03-31 06:08:15 +00:00
, 'output' : [ 'o' , 'output prefix to use for writing query and response(s) to disk' , 'file' ]
2017-09-16 00:43:02 +00:00
, 'address' : [ false , 'ip address(es) to listen on (defaults to 0.0.0.0,::0)' , 'string' ]
2017-03-31 06:08:15 +00:00
, 'port' : [ 'p' , 'port (defaults to 53 for dns and 5353 for mdns)' , 'int' ]
2017-09-20 18:29:43 +00:00
, 'nameserver' : [ false , 'the nameserver(s) to use for recursive lookups (defaults to ' + defaultNameservers . join ( ',' ) + ')' , 'string' ]
2017-03-31 06:08:15 +00:00
//, 'serve': [ 's', 'path to json file with array of responses to issue for given queries', 'string' ]
, 'type' : [ 't' , 'type (defaults to ANY for dns and PTR for mdns)' , 'string' ]
, 'query' : [ 'q' , 'a superfluous explicit option to set the query as a command line flag' ]
} ) ;
2017-09-16 00:43:02 +00:00
cli . main ( function ( args , cli ) {
args . forEach ( function ( arg ) {
if ( arg === '+norecurse' ) {
if ( cli . norecurse ) {
console . error ( "'+norecurse' was specified more than once" ) ;
process . exit ( 1 ) ;
return ;
}
cli . norecurse = true ;
return ;
}
2017-03-31 06:08:15 +00:00
} ) ;
2017-09-16 00:43:02 +00:00
if ( cli . mdns ) {
if ( ! cli . type ) {
cli . type = cli . t = 'PTR' ;
}
if ( ! cli . port ) {
cli . port = cli . p = 5353 ;
}
if ( ! cli . nameserver ) {
cli . nameserver = '224.0.0.251' ;
}
if ( ! cli . query ) {
cli . query = '_services._dns-sd._udp.local' ;
}
if ( ! ( 'timeout' in cli ) ) {
cli . timeout = 3000 ;
}
} else {
if ( ! cli . port ) {
cli . port = cli . p = 53 ;
}
2017-03-31 06:08:15 +00:00
}
var handlers = { } ;
var server = dgram . createSocket ( {
2017-09-16 00:43:02 +00:00
type : cli . udp6 ? 'udp6' : 'udp4'
//, reuseAddr: true
} ) ;
server . bind ( {
port : cli . port
, address : cli . address
2017-03-31 06:08:15 +00:00
} ) ;
handlers . onError = function ( err ) {
console . error ( "error:" , err . stack ) ;
server . close ( ) ;
} ;
2017-09-25 20:43:21 +00:00
handlers . onMessage = function ( nb , rinfo ) {
2017-09-16 00:43:02 +00:00
var queryAb = nb . buffer . slice ( nb . byteOffset , nb . byteOffset + nb . byteLength ) ;
var query = dnsjs . DNSPacket . parse ( queryAb ) ;
2017-09-25 20:43:21 +00:00
var newQuery ;
var count ;
2017-03-31 06:08:15 +00:00
2017-09-16 00:43:02 +00:00
if ( cli . debug ) {
console . log ( '' ) ;
console . log ( 'DNS Question:' ) ;
console . log ( '' ) ;
console . log ( query ) ;
console . log ( '' ) ;
console . log ( hexdump ( queryAb ) ) ;
2017-03-31 06:08:15 +00:00
console . log ( '' ) ;
}
2017-09-25 20:43:21 +00:00
dig . logQuestion ( query ) ;
/ *
2017-09-16 00:43:02 +00:00
console . log ( ';; Got question:' ) ;
2017-03-31 06:08:15 +00:00
console . log ( ';; ->>HEADER<<-' ) ;
2017-09-16 00:43:02 +00:00
console . log ( JSON . stringify ( query . header ) ) ;
2017-03-31 06:08:15 +00:00
console . log ( '' ) ;
console . log ( ';; QUESTION SECTION:' ) ;
2017-09-16 00:43:02 +00:00
query . question . forEach ( function ( q ) {
2017-03-31 06:08:15 +00:00
console . log ( ';' + q . name + '.' , ' ' , q . className , q . typeName ) ;
} ) ;
2017-09-25 20:43:21 +00:00
* /
2017-09-16 00:43:02 +00:00
2017-03-31 06:08:15 +00:00
function print ( q ) {
2017-09-16 00:43:02 +00:00
var printer = common . printers [ q . typeName ] || common . printers . ANY ;
2017-03-31 06:08:15 +00:00
printer ( q ) ;
}
2017-09-16 00:43:02 +00:00
if ( query . answer . length ) {
console . error ( '[ERROR] Query contains an answer section:' ) ;
2017-03-31 06:08:15 +00:00
console . log ( ';; ANSWER SECTION:' ) ;
2017-09-16 00:43:02 +00:00
query . answer . forEach ( print ) ;
2017-03-31 06:08:15 +00:00
}
2017-09-16 00:43:02 +00:00
if ( query . authority . length ) {
2017-03-31 06:08:15 +00:00
console . log ( '' ) ;
2017-09-16 00:43:02 +00:00
console . error ( '[ERROR] Query contains an authority section:' ) ;
2017-03-31 06:08:15 +00:00
console . log ( ';; AUTHORITY SECTION:' ) ;
2017-09-16 00:43:02 +00:00
query . authority . forEach ( print ) ;
2017-03-31 06:08:15 +00:00
}
2017-09-16 00:43:02 +00:00
if ( query . additional . length ) {
2017-03-31 06:08:15 +00:00
console . log ( '' ) ;
2017-09-16 00:43:02 +00:00
console . error ( '[ERROR] Query contains an additional section:' ) ;
2017-03-31 06:08:15 +00:00
console . log ( ';; ADDITIONAL SECTION:' ) ;
2017-09-16 00:43:02 +00:00
query . additional . forEach ( print ) ;
2017-03-31 06:08:15 +00:00
}
console . log ( '' ) ;
console . log ( ';; MSG SIZE rcvd: ' + nb . byteLength ) ;
console . log ( '' ) ;
2017-09-16 00:43:02 +00:00
if ( cli . output ) {
2017-03-31 06:08:15 +00:00
console . log ( '' ) ;
2017-09-16 00:43:02 +00:00
common . writeQuery ( cli , query , queryAb ) ;
//common.writeResponse(opts, query, nb, packet);
2017-03-31 06:08:15 +00:00
}
2017-09-20 18:29:43 +00:00
2017-09-25 20:43:21 +00:00
function sendEmptyResponse ( query ) {
var newQuery = {
header : {
id : query . header . id // require('crypto').randomBytes(2).readUInt16BE(0)
, qr : 1
, opcode : 0
, aa : 0 // TODO maybe
, tc : 0
, rd : query . header . rd
, ra : cli . norecurse ? 0 : 1
, rcode : 0 // no error
}
, question : [ ]
, answer : [ ]
, authority : [ ]
, additional : [ ]
} ;
2017-09-20 18:29:43 +00:00
query . question . forEach ( function ( q ) {
2017-09-25 20:43:21 +00:00
newQuery . question . push ( {
name : q . name
, type : q . type
, typeName : q . typeName
, class : q . class
, className : q . className
2017-09-20 18:29:43 +00:00
} ) ;
} ) ;
2017-09-25 20:43:21 +00:00
server . send ( dnsjs . DNSPacket . write ( newQuery ) , rinfo . port , rinfo . address , function ( ) {
console . log ( '[DEV] response sent' ) ;
} ) ;
}
2017-09-25 21:12:58 +00:00
function recurse ( ) {
if ( ! query . header . rd ) {
console . log ( "[Could not answer. Sent empty response.]" ) ;
sendEmptyResponse ( query ) ;
return ;
}
2017-09-25 20:43:21 +00:00
if ( cli . norecurse ) {
console . log ( "[Could not answer. Sent empty response.]" ) ;
sendEmptyResponse ( query ) ;
return ;
2017-09-25 21:12:58 +00:00
}
2017-09-25 20:43:21 +00:00
2017-09-25 21:12:58 +00:00
// ANY, A, AAAA, CNAME, MX, NAPTR, NS, PTR, SOA, SRV, TXT
newQuery = {
header : {
id : query . header . id // require('crypto').randomBytes(2).readUInt16BE(0)
, qr : 0
, opcode : 0
, aa : query . header . aa ? 1 : 0 // NA? not sure what this would do
, tc : 0 // NA
, rd : 1
, ra : 0 // NA
, rcode : 0 // NA
}
, question : [ ]
, answer : [ ]
, authority : [ ]
, additional : [ ]
} ;
query . question . forEach ( function ( q ) {
newQuery . question . push ( {
name : q . name
, type : q . type
, typeName : q . typeName
, class : q . class
, className : q . className
} ) ;
function updateCount ( ) {
count -= 1 ;
if ( ! count ) {
server . send ( dnsjs . DNSPacket . write ( newQuery ) , rinfo . port , rinfo . address , function ( ) {
console . log ( '[DEV] response sent' ) ;
} ) ;
2017-09-25 20:43:21 +00:00
}
2017-09-25 21:12:58 +00:00
}
2017-09-25 20:43:21 +00:00
2017-09-25 21:12:58 +00:00
var opts = {
onError : function ( ) {
updateCount ( ) ;
}
, onMessage : function ( packet ) {
2017-09-25 20:43:21 +00:00
2017-09-25 21:12:58 +00:00
( packet . answer || [ ] ) . forEach ( function ( a ) {
// TODO copy each relevant property
console . log ( 'ans' , a ) ;
newQuery . answer . push ( a ) ;
} ) ;
( packet . authority || [ ] ) . forEach ( function ( a ) {
// TODO copy each relevant property
console . log ( 'auth' , a ) ;
newQuery . authority . push ( a ) ;
} ) ;
( packet . additional || [ ] ) . forEach ( function ( a ) {
// TODO copy each relevant property
console . log ( 'add' , a ) ;
newQuery . additional . push ( a ) ;
} ) ;
2017-09-25 20:43:21 +00:00
2017-09-25 21:12:58 +00:00
updateCount ( ) ;
2017-09-25 20:43:21 +00:00
2017-09-25 21:12:58 +00:00
}
, onListening : function ( ) { }
, onSent : function ( res ) {
if ( cli . debug ) {
2017-09-25 20:43:21 +00:00
console . log ( '' ) ;
2017-09-25 21:12:58 +00:00
console . log ( 'request sent to' , res . nameserver ) ;
2017-09-25 20:43:21 +00:00
}
2017-09-25 21:12:58 +00:00
}
, onTimeout : function ( res ) {
console . log ( ";; [" + q . name + "] connection timed out; no servers could be reached" ) ;
console . log ( ";; [timed out after " + res . timeout + "ms and 1 tries]" ) ;
}
, onClose : function ( ) {
console . log ( '' ) ;
}
, mdns : cli . mdns
, nameserver : cli . nameserver
, port : cli . port
, timeout : cli . timeout
} ;
2017-09-25 20:43:21 +00:00
2017-09-25 21:12:58 +00:00
//dig.resolve(queryAb, opts);
dig . resolveJson ( query , opts ) ;
2017-09-25 20:43:21 +00:00
2017-09-25 21:12:58 +00:00
console . log ( ';' + q . name + '.' , ' ' , q . className , q . typeName ) ;
} ) ;
}
count = query . question . length ;
if ( ! count ) {
sendEmptyResponse ( query ) ;
return ;
2017-09-20 18:29:43 +00:00
}
2017-09-25 21:12:58 +00:00
// TODO get local answer first, if available
recurse ( ) ;
2017-03-31 06:08:15 +00:00
} ;
2017-09-25 20:43:21 +00:00
2017-03-31 06:08:15 +00:00
handlers . onListening = function ( ) {
/*jshint validthis:true*/
var server = this ;
2017-09-16 00:43:02 +00:00
var nameserver = cli . nameserver ;
2017-03-31 06:08:15 +00:00
var index ;
if ( ! nameserver ) {
2017-09-20 18:29:43 +00:00
index = crypto . randomBytes ( 2 ) . readUInt16BE ( 0 ) % defaultNameservers . length ;
nameserver = defaultNameservers [ index ] ;
2017-09-16 00:43:02 +00:00
if ( cli . debug ) {
2017-09-20 18:29:43 +00:00
console . log ( index , defaultNameservers ) ;
2017-03-31 06:08:15 +00:00
}
}
2017-09-16 00:43:02 +00:00
if ( cli . mdns || '224.0.0.251' === cli . nameserver ) {
2017-03-31 06:08:15 +00:00
server . setBroadcast ( true ) ;
2017-09-16 00:43:02 +00:00
server . addMembership ( cli . nameserver ) ;
2017-03-31 06:08:15 +00:00
}
2017-09-16 00:43:02 +00:00
console . log ( '' ) ;
console . log ( 'Bound and Listening:' ) ;
console . log ( server . address ( ) . address + '#' + server . address ( ) . port ) ;
2017-03-31 06:08:15 +00:00
} ;
2017-09-16 00:43:02 +00:00
console . log ( '' ) ;
if ( ! cli . nocmd ) {
2017-09-25 21:12:58 +00:00
console . log ( '; <<>> digd.js ' + 'v0.0.0' + ' <<>> ' + process . argv . slice ( 2 ) ) ;
2017-09-16 00:43:02 +00:00
console . log ( ';; global options: +cmd' ) ;
}
2017-03-31 06:08:15 +00:00
server . on ( 'error' , handlers . onError ) ;
server . on ( 'message' , handlers . onMessage ) ;
server . on ( 'listening' , handlers . onListening ) ;
} ) ;