From bb58dc0aa801cb370d495859e302f41041831d8e Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Sun, 18 Sep 2011 19:22:12 -0500 Subject: [PATCH 01/14] Refactor dnsserver.js into a reusable library --- dnsserver.js | 434 ----------------------------------------------- lib/dnsserver.js | 327 +++++++++++++++++++++++++++++++++++ 2 files changed, 327 insertions(+), 434 deletions(-) delete mode 100644 dnsserver.js create mode 100644 lib/dnsserver.js diff --git a/dnsserver.js b/dnsserver.js deleted file mode 100644 index 9b82ead..0000000 --- a/dnsserver.js +++ /dev/null @@ -1,434 +0,0 @@ -// Copyright (c) 2010 Tom Hughes-Croucher -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -var sys = require('sys'), - Buffer = require('buffer').Buffer, - dgram = require('dgram'); - -host = 'localhost'; -port = 9999; - -// slices a single byte into bits -// assuming only single bytes -var sliceBits = function(b, off, len) { - var s = 7 - (off + len - 1); - - b = b >>> s; - return b & ~(0xff << len); -}; - -var server = dgram.createSocket('udp4'); - -server.on('message', function (msg, rinfo) { - - //split up the message into the dns request header info and the query - var q = processRequest(msg); - - buf = createResponse(q); - server.send(buf, 0, buf.length, rinfo.port, rinfo.address, function (err, sent) { - - }); -}); - -//takes a buffer as a request -var processRequest = function(req) { - //see rfc1035 for more details - //http://tools.ietf.org/html/rfc1035#section-4.1.1 - - var query = {}; - query.header = {}; - //TODO write code to break questions up into an array - query.question = {}; - - var tmpSlice; - var tmpByte; - - //transaction id - // 2 bytes - query.header.id = req.slice(0,2); - - //slice out a byte for the next section to dice into binary. - tmpSlice = req.slice(2,3); - //convert the binary buf into a string and then pull the char code - //for the byte - tmpByte = tmpSlice.toString('binary', 0, 1).charCodeAt(0); - - //qr - // 1 bit - query.header.qr = sliceBits(tmpByte, 0,1); - //opcode - // 0 = standard, 1 = inverse, 2 = server status, 3-15 reserved - // 4 bits - query.header.opcode = sliceBits(tmpByte, 1,4); - //authorative answer - // 1 bit - query.header.aa = sliceBits(tmpByte, 5,1); - //truncated - // 1 bit - query.header.tc = sliceBits(tmpByte, 6,1); - //recursion desired - // 1 bit - query.header.rd = sliceBits(tmpByte, 7,1); - - //slice out a byte to dice into binary - tmpSlice = req.slice(3,4); - //convert the binary buf into a string and then pull the char code - //for the byte - tmpByte = tmpSlice.toString('binary', 0, 1).charCodeAt(0); - - //recursion available - // 1 bit - query.header.ra = sliceBits(tmpByte, 0,1); - - //reserved 3 bits - // rfc says always 0 - query.header.z = sliceBits(tmpByte, 1,3); - - //response code - // 0 = no error, 1 = format error, 2 = server failure - // 3 = name error, 4 = not implemented, 5 = refused - // 6-15 reserved - // 4 bits - query.header.rcode = sliceBits(tmpByte, 4,4); - - //question count - // 2 bytes - query.header.qdcount = req.slice(4,6); - //answer count - // 2 bytes - query.header.ancount = req.slice(6,8); - //ns count - // 2 bytes - query.header.nscount = req.slice(8,10); - //addition resources count - // 2 bytes - query.header.arcount = req.slice(10, 12); - - //assuming one question - //qname is the sequence of domain labels - //qname length is not fixed however it is 4 - //octets from the end of the buffer - query.question.qname = req.slice(12, req.length - 4); - //qtype - query.question.qtype = req.slice(req.length - 4, req.length - 2); - //qclass - query.question.qclass = req.slice(req.length - 2, req.length); - - return query; -}; - -var createResponse = function(query) { - - /* - * Step 1: find record associated with query - */ - var results = findRecords(query.question.qname, 1); - - /* - * Step 2: construct response object - */ - - var response = {}; - response.header = {}; - - //1 byte - response.header.id = query.header.id; //same as query id - - //combined 1 byte - response.header.qr = 1; //this is a response - response.header.opcode = 0; //standard for now TODO: add other types 4-bit! - response.header.aa = 0; //authority... TODO this should be modal - response.header.tc = 0; //truncation - response.header.rd = 1; //recursion asked for - - //combined 1 byte - response.header.ra = 0; //no rescursion here TODO - response.header.z = 0; // spec says this MUST always be 0. 3bit - response.header.rcode = 0; //TODO add error codes 4 bit. - - //1 byte - response.header.qdcount = 1; //1 question - //1 byte - response.header.ancount = results.length; //number of rrs returned from query - //1 byte - response.header.nscount = 0; - //1 byte - response.header.arcount = 0; - - response.question = {}; - response.question.qname = query.question.qname; - response.question.qtype = query.question.qtype; - response.question.qclass = query.question.qclass; - - response.rr = results; - - /* - * Step 3 render response into output buffer - */ - var buf = buildResponseBuffer(response); - - /* - * Step 4 return buffer - */ - return buf; -}; - -var domainToQname = function(domain) { - var tokens = domain.split("."); - len = domain.length + 2; - var qname = new Buffer(len); - var offset = 0; - for(var i=0; i> shift) & 255; - - buf[i] = insert; - } - - return buf; -}; - -var findRecords = function(qname, qtype, qclass) { - - //assuming we are always going to get internet - //request but adding basic qclass support - //for completeness - //TODO replace throws with error responses - if (qclass === undefined || qclass === 1) { - qclass = 'in'; - } else { - throw new Error('Only internet class records supported'); - } - - switch(qtype) { - case 1: - qtype = 'a'; //a host address - break; - case 2: - qtype = 'ns'; //an authoritative name server - break; - case 3: - qtype = 'md'; //a mail destination (Obsolete - use MX) - break; - case 4: - qtype = 'mf'; //a mail forwarder (Obsolete - use MX) - break; - case 5: - qtype = 'cname'; //the canonical name for an alias - break; - case 6: - qtype = 'soa'; //marks the start of a zone of authority - break; - case 7: - qtype = 'mb'; //a mailbox domain name (EXPERIMENTAL) - break; - case 8: - qtype = 'mg'; //a mail group member (EXPERIMENTAL) - break; - case 9: - qtype = 'mr'; //a mail rename domain name (EXPERIMENTAL) - break; - case 10: - qtype = 'null'; //a null RR (EXPERIMENTAL) - break; - case 11: - qtype = 'wks'; //a well known service description - break; - case 12: - qtype = 'ptr'; //a domain name pointer - break; - case 13: - qtype = 'hinfo'; //host information - break; - case 14: - qtype = 'minfo'; //mailbox or mail list information - break; - case 15: - qtype = 'mx'; //mail exchange - break; - case 16: - qtype = 'txt'; //text strings - break; - case 255: - qtype = '*'; //select all types - break; - default: - throw new Error('No valid type specified'); - break; - } - - var domain = qnameToDomain(qname); - - //TODO add support for wildcard - if (qtype === '*') { - throw new Error('Wildcard not support'); - } else { - var rr = records[domain][qclass][qtype]; - } - - - - return rr; -}; - -var qnameToDomain = function(qname) { - - var domain= ''; - for(var i=0;i>> s; + return b & ~(0xff << len); +} + +//takes a buffer as a request +function processRequest(req) { + //see rfc1035 for more details + //http://tools.ietf.org/html/rfc1035#section-4.1.1 + + var query = {}; + query.header = {}; + //TODO write code to break questions up into an array + query.question = {}; + + var tmpSlice; + var tmpByte; + + //transaction id + // 2 bytes + query.header.id = req.slice(0,2); + + //slice out a byte for the next section to dice into binary. + tmpSlice = req.slice(2,3); + //convert the binary buf into a string and then pull the char code + //for the byte + tmpByte = tmpSlice.toString('binary', 0, 1).charCodeAt(0); + + //qr + // 1 bit + query.header.qr = sliceBits(tmpByte, 0,1); + //opcode + // 0 = standard, 1 = inverse, 2 = server status, 3-15 reserved + // 4 bits + query.header.opcode = sliceBits(tmpByte, 1,4); + //authorative answer + // 1 bit + query.header.aa = sliceBits(tmpByte, 5,1); + //truncated + // 1 bit + query.header.tc = sliceBits(tmpByte, 6,1); + //recursion desired + // 1 bit + query.header.rd = sliceBits(tmpByte, 7,1); + + //slice out a byte to dice into binary + tmpSlice = req.slice(3,4); + //convert the binary buf into a string and then pull the char code + //for the byte + tmpByte = tmpSlice.toString('binary', 0, 1).charCodeAt(0); + + //recursion available + // 1 bit + query.header.ra = sliceBits(tmpByte, 0,1); + + //reserved 3 bits + // rfc says always 0 + query.header.z = sliceBits(tmpByte, 1,3); + + //response code + // 0 = no error, 1 = format error, 2 = server failure + // 3 = name error, 4 = not implemented, 5 = refused + // 6-15 reserved + // 4 bits + query.header.rcode = sliceBits(tmpByte, 4,4); + + //question count + // 2 bytes + query.header.qdcount = req.slice(4,6); + //answer count + // 2 bytes + query.header.ancount = req.slice(6,8); + //ns count + // 2 bytes + query.header.nscount = req.slice(8,10); + //addition resources count + // 2 bytes + query.header.arcount = req.slice(10, 12); + + //assuming one question + //qname is the sequence of domain labels + //qname length is not fixed however it is 4 + //octets from the end of the buffer + query.question.qname = req.slice(12, req.length - 4); + //qtype + query.question.qtype = req.slice(req.length - 4, req.length - 2); + //qclass + query.question.qclass = req.slice(req.length - 2, req.length); + + query.question.domain = qnameToDomain(query.question.qname); + + return query; +} + +function Response(socket, rinfo, query) { + this.socket = socket; + this.rinfo = rinfo; + this.header = {}; + + //1 byte + this.header.id = query.header.id; //same as query id + + //combined 1 byte + this.header.qr = 1; //this is a response + this.header.opcode = 0; //standard for now TODO: add other types 4-bit! + this.header.aa = 0; //authority... TODO this should be modal + this.header.tc = 0; //truncation + this.header.rd = 1; //recursion asked for + + //combined 1 byte + this.header.ra = 0; //no rescursion here TODO + this.header.z = 0; // spec says this MUST always be 0. 3bit + this.header.rcode = 0; //TODO add error codes 4 bit. + + //1 byte + this.header.qdcount = 1; //1 question + //1 byte + this.header.ancount = 0; //number of rrs returned from query + //1 byte + this.header.nscount = 0; + //1 byte + this.header.arcount = 0; + + this.question = {}; + this.question.qname = query.question.qname; + this.question.qtype = query.question.qtype; + this.question.qclass = query.question.qclass; + + this.rr = []; +} + +Response.prototype.addRR = function(domain, qtype, qclass, ttl, rdlength, rdata) { + var r = {}, address; + r.qname = domainToQname(domain); + r.qtype = qtype; + r.qclass = qclass; + r.ttl = ttl; + + if (address = inet_aton(rdlength)) { + r.rdlength = 4; + r.rdata = address; + } else { + r.rdlength = rdlength; + r.rdata = rdata; + } + + this.rr.push(r); + this.header.ancount++; +} + +Response.prototype.send = function(callback) { + var buffer = this.toBuffer(); + this.socket.send(buffer, 0, buffer.length, this.rinfo.port, this.rinfo.address, callback || function() {}); +} + +Response.prototype.toBuffer = function() { + //calculate len in octets + //NB not calculating rr this is done later + //headers(12) + qname(qname + 2 + 2) + //e.g. 16 + 2 * qname; + //qnames are Buffers so length is already in octs + var qnameLen = this.question.qname.length; + var len = 16 + qnameLen; + var buf = getZeroBuf(len); + + this.header.id.copy(buf, 0, 0, 2); + + buf[2] = 0x00 | this.header.qr << 7 | this.header.opcode << 3 | this.header.aa << 2 | this.header.tc << 1 | this.header.rd; + + + buf[3] = 0x00 | this.header.ra << 7 | this.header.z << 4 | this.header.rcode; + + numToBuffer(buf, 4, this.header.qdcount, 2); + + numToBuffer(buf, 6, this.header.ancount, 2); + numToBuffer(buf, 8, this.header.nscount, 2); + numToBuffer(buf, 10, this.header.arcount, 2); + + //end header + + this.question.qname.copy(buf, 12, 0, qnameLen); + this.question.qtype.copy(buf, 12+qnameLen, 0, 2); + this.question.qclass.copy(buf, 12+qnameLen+2, 0, 2); + + var rrStart = 12+qnameLen+4; + + for (var i = 0; i < this.rr.length; i++) { + //TODO figure out if this is actually cheaper than just iterating + //over the rr section up front and counting before creating buf + // + //create a new buffer to hold the request plus the rr + //len of each response is 14 bytes of stuff + qname len + var tmpBuf = getZeroBuf(buf.length + this.rr[i].qname.length + 14); + + buf.copy(tmpBuf, 0, 0, buf.length); + + this.rr[i].qname.copy(tmpBuf, rrStart, 0, this.rr[i].qname.length); + numToBuffer(tmpBuf, rrStart+this.rr[i].qname.length, this.rr[i].qtype, 2); + numToBuffer(tmpBuf, rrStart+this.rr[i].qname.length+2, this.rr[i].qclass, 2); + + numToBuffer(tmpBuf, rrStart+this.rr[i].qname.length+4, this.rr[i].ttl, 4); + numToBuffer(tmpBuf, rrStart+this.rr[i].qname.length+8, this.rr[i].rdlength, 2); + numToBuffer(tmpBuf, rrStart+this.rr[i].qname.length+10, this.rr[i].rdata, this.rr[i].rdlength); // rdlength indicates rdata length + + rrStart = rrStart + this.rr[i].qname.length + 14; + + buf = tmpBuf; + } + + //TODO compression + + return buf; +} + +function inet_aton(address) { + var parts = address.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/); + return parts ? parts[1] * 16777216 + parts[2] * 65536 + parts[3] * 256 + parts[4] * 1 : false; +} + +function domainToQname(domain) { + var tokens = domain.split("."); + var len = domain.length + 2; + var qname = new Buffer(len); + var offset = 0; + for (var i = 0; i < tokens.length; i++) { + qname[offset] = tokens[i].length; + offset++; + for (var j = 0; j < tokens[i].length; j++) { + qname[offset] = tokens[i].charCodeAt(j); + offset++; + } + } + qname[offset] = 0; + + return qname; +} + +function getZeroBuf(len) { + var buf = new Buffer(len); + for (var i = 0; i < buf.length; i++) buf[i] = 0; + return buf; +} + +//take a number and make sure it's written to the buffer as +//the correct length of bytes with leading 0 padding where necessary +// takes buffer, offset, number, length in bytes to insert +function numToBuffer(buf, offset, num, len, debug) { + if (typeof num != 'number') { + throw new Error('Num must be a number'); + } + + for (var i = offset; i < offset + len; i++) { + var shift = 8*((len - 1) - (i - offset)); + var insert = (num >> shift) & 255; + buf[i] = insert; + } + + return buf; +} + +function qnameToDomain(qname) { + var domain= ''; + for (var i = 0; i < qname.length; i++) { + if (qname[i] == 0) { + //last char chop trailing . + domain = domain.substring(0, domain.length - 1); + break; + } + + var tmpBuf = qname.slice(i+1, i+qname[i]+1); + domain += tmpBuf.toString('binary', 0, tmpBuf.length); + domain += '.'; + + i = i + qname[i]; + } + + return domain; +} From 7e4c147559c89e9bf8fbcb23ad7cf19f9b0b92cf Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Sun, 18 Sep 2011 19:22:20 -0500 Subject: [PATCH 02/14] Add an example --- examples/server.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100755 examples/server.js diff --git a/examples/server.js b/examples/server.js new file mode 100755 index 0000000..3d129d9 --- /dev/null +++ b/examples/server.js @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +var dnsserver = require('../lib/dnsserver'); + +var server = dnsserver.createServer(); +server.bind(8000, '127.0.0.1'); + +server.addListener('request', function(req, res) { + console.log("req = ", req); + + if (req.question.domain == 'tomhughescroucher.com') { + res.addRR(req.question.domain, 1, 1, 3600, '184.106.231.91') + } else { + res.header.rcode = 3; // NXDOMAIN + } + + res.send(); +}); + +server.addListener('error', function(e) { + throw e; +}); From 80cf223f42425c88ec0e8b924bbb1483335f7037 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Sun, 18 Sep 2011 19:22:26 -0500 Subject: [PATCH 03/14] Add package.json --- package.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..6d82d62 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "author": "Tom Hughes-Croucher ", + "name": "dnsserver", + "description": "A DNS server for Node.js", + "version": "0.1.0", + "homepage": "https://github.com/sh1mmer/dnsserver.js", + "repository": { + "type": "git", + "url": "git://github.com/sh1mmer/dnsserver.js.git" + }, + "main": "lib/dnsserver.js", + "engines": { + "node": "~>v0.4" + }, + "dependencies": {}, + "devDependencies": {} +} From e063c211586c920997d3f0a9e058fc0a655c5c8a Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Sun, 18 Sep 2011 20:15:31 -0500 Subject: [PATCH 04/14] Rename question.domain to question.name and add parsed question.type and question.class attributes --- examples/server.js | 6 ++++-- lib/dnsserver.js | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/server.js b/examples/server.js index 3d129d9..c04d28b 100755 --- a/examples/server.js +++ b/examples/server.js @@ -7,9 +7,11 @@ server.bind(8000, '127.0.0.1'); server.addListener('request', function(req, res) { console.log("req = ", req); + var question = req.question; - if (req.question.domain == 'tomhughescroucher.com') { - res.addRR(req.question.domain, 1, 1, 3600, '184.106.231.91') + if (question.type == 1 && question.class == 1 && question.name == 'tomhughescroucher.com') { + // IN A query + res.addRR(question.name, 1, 1, 3600, '184.106.231.91'); } else { res.header.rcode = 3; // NXDOMAIN } diff --git a/lib/dnsserver.js b/lib/dnsserver.js index 2a1ce29..f705136 100644 --- a/lib/dnsserver.js +++ b/lib/dnsserver.js @@ -136,7 +136,9 @@ function processRequest(req) { //qclass query.question.qclass = req.slice(req.length - 2, req.length); - query.question.domain = qnameToDomain(query.question.qname); + query.question.name = qnameToDomain(query.question.qname); + query.question.type = query.question.qtype[0] * 256 + query.question.qtype[1]; + query.question.class = query.question.qclass[0] * 256 + query.question.qclass[1]; return query; } From 4edf3f8abb940ca79d751f5637135c8095c318f0 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Sun, 18 Sep 2011 20:16:16 -0500 Subject: [PATCH 05/14] Prefer on to addListener --- examples/server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/server.js b/examples/server.js index c04d28b..d28d342 100755 --- a/examples/server.js +++ b/examples/server.js @@ -5,7 +5,7 @@ var dnsserver = require('../lib/dnsserver'); var server = dnsserver.createServer(); server.bind(8000, '127.0.0.1'); -server.addListener('request', function(req, res) { +server.on('request', function(req, res) { console.log("req = ", req); var question = req.question; @@ -19,6 +19,6 @@ server.addListener('request', function(req, res) { res.send(); }); -server.addListener('error', function(e) { +server.on('error', function(e) { throw e; }); From 2ba767ce1dc4223cb34261a6c4b481914d4fbb3f Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Wed, 9 May 2012 14:55:02 -0500 Subject: [PATCH 06/14] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d82d62..3bdb752 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "main": "lib/dnsserver.js", "engines": { - "node": "~>v0.4" + "node": "~>0.4" }, "dependencies": {}, "devDependencies": {} From 6bf424b8b54546c2cd13bf5e7ae29c834720f2dc Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Wed, 9 May 2012 14:56:59 -0500 Subject: [PATCH 07/14] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3bdb752..bceb46a 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "main": "lib/dnsserver.js", "engines": { - "node": "~>0.4" + "node": ">=0.4" }, "dependencies": {}, "devDependencies": {} From 0a7504fb736f55c5ef7ce8747eb7e4bc564f4d4d Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Wed, 9 May 2012 16:05:26 -0500 Subject: [PATCH 08/14] sys is unused --- lib/dnsserver.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/dnsserver.js b/lib/dnsserver.js index f705136..effd46e 100644 --- a/lib/dnsserver.js +++ b/lib/dnsserver.js @@ -19,8 +19,7 @@ // THE SOFTWARE. // -var sys = require('sys'), - util = require('util'), +var util = require('util'), Buffer = require('buffer').Buffer, dgram = require('dgram'); From d73178695cd6573066a6600808a95d0093dd0850 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Wed, 9 May 2012 23:20:57 -0500 Subject: [PATCH 09/14] Support arbitrary rdata buffers in addRR --- lib/dnsserver.js | 58 ++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/lib/dnsserver.js b/lib/dnsserver.js index effd46e..ffff66f 100644 --- a/lib/dnsserver.js +++ b/lib/dnsserver.js @@ -179,18 +179,18 @@ function Response(socket, rinfo, query) { this.rr = []; } -Response.prototype.addRR = function(domain, qtype, qclass, ttl, rdlength, rdata) { +Response.prototype.addRR = function(domain, qtype, qclass, ttl, rdata) { var r = {}, address; r.qname = domainToQname(domain); r.qtype = qtype; r.qclass = qclass; r.ttl = ttl; - if (address = inet_aton(rdlength)) { + if (address = inet_aton(rdata)) { r.rdlength = 4; r.rdata = address; } else { - r.rdlength = rdlength; + r.rdlength = rdata.length; r.rdata = rdata; } @@ -220,11 +220,11 @@ Response.prototype.toBuffer = function() { buf[3] = 0x00 | this.header.ra << 7 | this.header.z << 4 | this.header.rcode; - numToBuffer(buf, 4, this.header.qdcount, 2); + accumulate(buf, 4, this.header.qdcount, 2); - numToBuffer(buf, 6, this.header.ancount, 2); - numToBuffer(buf, 8, this.header.nscount, 2); - numToBuffer(buf, 10, this.header.arcount, 2); + accumulate(buf, 6, this.header.ancount, 2); + accumulate(buf, 8, this.header.nscount, 2); + accumulate(buf, 10, this.header.arcount, 2); //end header @@ -240,19 +240,20 @@ Response.prototype.toBuffer = function() { // //create a new buffer to hold the request plus the rr //len of each response is 14 bytes of stuff + qname len - var tmpBuf = getZeroBuf(buf.length + this.rr[i].qname.length + 14); + var size = 10 + this.rr[i].rdlength; + var tmpBuf = getZeroBuf(buf.length + this.rr[i].qname.length + size); buf.copy(tmpBuf, 0, 0, buf.length); this.rr[i].qname.copy(tmpBuf, rrStart, 0, this.rr[i].qname.length); - numToBuffer(tmpBuf, rrStart+this.rr[i].qname.length, this.rr[i].qtype, 2); - numToBuffer(tmpBuf, rrStart+this.rr[i].qname.length+2, this.rr[i].qclass, 2); + accumulate(tmpBuf, rrStart+this.rr[i].qname.length, this.rr[i].qtype, 2); + accumulate(tmpBuf, rrStart+this.rr[i].qname.length+2, this.rr[i].qclass, 2); - numToBuffer(tmpBuf, rrStart+this.rr[i].qname.length+4, this.rr[i].ttl, 4); - numToBuffer(tmpBuf, rrStart+this.rr[i].qname.length+8, this.rr[i].rdlength, 2); - numToBuffer(tmpBuf, rrStart+this.rr[i].qname.length+10, this.rr[i].rdata, this.rr[i].rdlength); // rdlength indicates rdata length + accumulate(tmpBuf, rrStart+this.rr[i].qname.length+4, this.rr[i].ttl, 4); + accumulate(tmpBuf, rrStart+this.rr[i].qname.length+8, this.rr[i].rdlength, 2); + accumulate(tmpBuf, rrStart+this.rr[i].qname.length+10, this.rr[i].rdata, this.rr[i].rdlength); // rdlength indicates rdata length - rrStart = rrStart + this.rr[i].qname.length + 14; + rrStart = rrStart + this.rr[i].qname.length + size; buf = tmpBuf; } @@ -263,7 +264,7 @@ Response.prototype.toBuffer = function() { } function inet_aton(address) { - var parts = address.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/); + var parts = address.toString().match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/); return parts ? parts[1] * 16777216 + parts[2] * 65536 + parts[3] * 256 + parts[4] * 1 : false; } @@ -291,23 +292,32 @@ function getZeroBuf(len) { return buf; } -//take a number and make sure it's written to the buffer as -//the correct length of bytes with leading 0 padding where necessary -// takes buffer, offset, number, length in bytes to insert -function numToBuffer(buf, offset, num, len, debug) { - if (typeof num != 'number') { - throw new Error('Num must be a number'); - } - +function pack(buf, offset, value, len) { for (var i = offset; i < offset + len; i++) { var shift = 8*((len - 1) - (i - offset)); - var insert = (num >> shift) & 255; + var insert = (value >> shift) & 255; buf[i] = insert; } return buf; } +//take a value and make sure it's written to the buffer as +//the correct length of bytes with 0 padding where necessary +// takes buffer, offset, number, length in bytes to insert +function accumulate(buf, offset, value, len) { + if (value instanceof Buffer) { + value.copy(buf, offset, 0, len); + return buf; + + } else if (typeof value == 'number') { + return pack(buf, offset, value, len); + + } else { + throw new Error('value must be a buffer or number'); + } +} + function qnameToDomain(qname) { var domain= ''; for (var i = 0; i < qname.length; i++) { From f2e422fef4d184e8abd047efe349d6607bbe3343 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Wed, 9 May 2012 23:34:32 -0500 Subject: [PATCH 10/14] Add helper for creating SOA rdata buffers --- lib/dnsserver.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lib/dnsserver.js b/lib/dnsserver.js index ffff66f..f825661 100644 --- a/lib/dnsserver.js +++ b/lib/dnsserver.js @@ -336,3 +336,39 @@ function qnameToDomain(qname) { return domain; } + +exports.createSOA = function(mname, rname, serial, refresh, retry, expire, minimum, length) { + var buffer, offset; + buffer = new Buffer(mname.length * 2 + 1 + rname.length * 2 + 1 + 5 * 4); + buffer.fill(0); + offset = 0; + + function writeLabel(label) { + buffer.writeInt8(label.length, offset); + offset += 1; + buffer.write(label, offset); + offset += label.length; + } + + function writeName(name) { + name.split(".").forEach(function(label) { + writeLabel(label); + }); + offset += 1; + } + + function writeInt(int) { + buffer.writeUInt32BE(int, offset); + offset += 4; + } + + writeName(mname); + writeName(rname); + writeInt(serial); + writeInt(refresh); + writeInt(retry); + writeInt(expire); + writeInt(minimum); + + return buffer.slice(0, offset); +} From 08d27252ff6ceb1fa6f102a8a0899a753196fc76 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Thu, 10 May 2012 09:08:58 -0500 Subject: [PATCH 11/14] addRR can send authoritative responses --- lib/dnsserver.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/dnsserver.js b/lib/dnsserver.js index f825661..a0da365 100644 --- a/lib/dnsserver.js +++ b/lib/dnsserver.js @@ -179,7 +179,7 @@ function Response(socket, rinfo, query) { this.rr = []; } -Response.prototype.addRR = function(domain, qtype, qclass, ttl, rdata) { +Response.prototype.addRR = function(domain, qtype, qclass, ttl, rdata, authoritative) { var r = {}, address; r.qname = domainToQname(domain); r.qtype = qtype; @@ -195,7 +195,13 @@ Response.prototype.addRR = function(domain, qtype, qclass, ttl, rdata) { } this.rr.push(r); - this.header.ancount++; + + if (authoritative) { + this.header.aa = 1; + this.header.nscount++; + } else { + this.header.ancount++; + } } Response.prototype.send = function(callback) { From 74eeb52866b356345572fcec5aa45b3180469984 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Thu, 10 May 2012 12:02:02 -0500 Subject: [PATCH 12/14] Extract helper for creating name buffers --- lib/dnsserver.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/dnsserver.js b/lib/dnsserver.js index a0da365..1c4aebf 100644 --- a/lib/dnsserver.js +++ b/lib/dnsserver.js @@ -343,24 +343,32 @@ function qnameToDomain(qname) { return domain; } -exports.createSOA = function(mname, rname, serial, refresh, retry, expire, minimum, length) { - var buffer, offset; - buffer = new Buffer(mname.length * 2 + 1 + rname.length * 2 + 1 + 5 * 4); +exports.createName = function(name) { + var labels = name.split("."), length = labels.length; + var buffer = new Buffer(name.length + 2), offset = 0; buffer.fill(0); - offset = 0; - function writeLabel(label) { + for (var i = 0; i < length; i++) { + var label = labels[i]; buffer.writeInt8(label.length, offset); offset += 1; buffer.write(label, offset); offset += label.length; } + return buffer; +} + +exports.createSOA = function(mname, rname, serial, refresh, retry, expire, minimum, length) { + var buffer, offset; + buffer = new Buffer(mname.length * 2 + 1 + rname.length * 2 + 1 + 5 * 4); + buffer.fill(0); + offset = 0; + function writeName(name) { - name.split(".").forEach(function(label) { - writeLabel(label); - }); - offset += 1; + var nameBuffer = exports.createName(name); + nameBuffer.copy(buffer, offset); + offset += nameBuffer.length; } function writeInt(int) { From 583432ac267a849fe94a6d63ff7765ae73ed4dfc Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Mon, 14 May 2012 12:12:16 -0500 Subject: [PATCH 13/14] Process requests with additional records after the first question --- lib/dnsserver.js | 92 ++++++++++++++++++------------------------------ 1 file changed, 35 insertions(+), 57 deletions(-) diff --git a/lib/dnsserver.js b/lib/dnsserver.js index 1c4aebf..8d93bc9 100644 --- a/lib/dnsserver.js +++ b/lib/dnsserver.js @@ -52,7 +52,7 @@ function sliceBits(b, off, len) { } //takes a buffer as a request -function processRequest(req) { +var processRequest = exports.processRequest = function(req) { //see rfc1035 for more details //http://tools.ietf.org/html/rfc1035#section-4.1.1 @@ -114,30 +114,21 @@ function processRequest(req) { //question count // 2 bytes - query.header.qdcount = req.slice(4,6); + query.header.qdcount = req.readUInt16BE(4); //answer count // 2 bytes - query.header.ancount = req.slice(6,8); + query.header.ancount = req.readUInt16BE(6); //ns count // 2 bytes - query.header.nscount = req.slice(8,10); + query.header.nscount = req.readUInt16BE(8); //addition resources count // 2 bytes - query.header.arcount = req.slice(10, 12); - - //assuming one question - //qname is the sequence of domain labels - //qname length is not fixed however it is 4 - //octets from the end of the buffer - query.question.qname = req.slice(12, req.length - 4); - //qtype - query.question.qtype = req.slice(req.length - 4, req.length - 2); - //qclass - query.question.qclass = req.slice(req.length - 2, req.length); - - query.question.name = qnameToDomain(query.question.qname); - query.question.type = query.question.qtype[0] * 256 + query.question.qtype[1]; - query.question.class = query.question.qclass[0] * 256 + query.question.qclass[1]; + query.header.arcount = req.readUInt16BE(10); + + // read only the first question + if (query.header.qdcount > 0) { + query.question = readName(req, 12); + } return query; } @@ -181,7 +172,7 @@ function Response(socket, rinfo, query) { Response.prototype.addRR = function(domain, qtype, qclass, ttl, rdata, authoritative) { var r = {}, address; - r.qname = domainToQname(domain); + r.qname = createName(domain); r.qtype = qtype; r.qclass = qclass; r.ttl = ttl; @@ -274,28 +265,10 @@ function inet_aton(address) { return parts ? parts[1] * 16777216 + parts[2] * 65536 + parts[3] * 256 + parts[4] * 1 : false; } -function domainToQname(domain) { - var tokens = domain.split("."); - var len = domain.length + 2; - var qname = new Buffer(len); - var offset = 0; - for (var i = 0; i < tokens.length; i++) { - qname[offset] = tokens[i].length; - offset++; - for (var j = 0; j < tokens[i].length; j++) { - qname[offset] = tokens[i].charCodeAt(j); - offset++; - } - } - qname[offset] = 0; - - return qname; -} - function getZeroBuf(len) { - var buf = new Buffer(len); - for (var i = 0; i < buf.length; i++) buf[i] = 0; - return buf; + var buffer = new Buffer(len); + buffer.fill(0); + return buffer; } function pack(buf, offset, value, len) { @@ -324,26 +297,31 @@ function accumulate(buf, offset, value, len) { } } -function qnameToDomain(qname) { - var domain= ''; - for (var i = 0; i < qname.length; i++) { - if (qname[i] == 0) { - //last char chop trailing . - domain = domain.substring(0, domain.length - 1); - break; - } +function readName(buffer, start) { + var offset = start, question = {}, labels = [], length; - var tmpBuf = qname.slice(i+1, i+qname[i]+1); - domain += tmpBuf.toString('binary', 0, tmpBuf.length); - domain += '.'; + do { + length = buffer.readUInt8(offset); + offset += 1; + if (length == 0) break; - i = i + qname[i]; - } + labels.push(buffer.slice(offset, offset + length).toString()); + offset += length; + + } while (offset < buffer.length); + + question.qname = buffer.slice(start, offset); + question.qtype = buffer.slice(offset, offset + 2); + question.qclass = buffer.slice(offset + 2, offset + 4); + + question.name = labels.join("."); + question.type = question.qtype.readUInt16BE(0); + question.class = question.qclass.readUInt16BE(0); - return domain; + return question; } -exports.createName = function(name) { +var createName = exports.createName = function(name) { var labels = name.split("."), length = labels.length; var buffer = new Buffer(name.length + 2), offset = 0; buffer.fill(0); @@ -366,7 +344,7 @@ exports.createSOA = function(mname, rname, serial, refresh, retry, expire, minim offset = 0; function writeName(name) { - var nameBuffer = exports.createName(name); + var nameBuffer = createName(name); nameBuffer.copy(buffer, offset); offset += nameBuffer.length; } From 4f2c713b2eb32f4dee7d487d21c1a1caab359928 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Mon, 14 May 2012 12:29:03 -0500 Subject: [PATCH 14/14] Swallow exceptions during request processing --- lib/dnsserver.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/dnsserver.js b/lib/dnsserver.js index 8d93bc9..dcb1cd5 100644 --- a/lib/dnsserver.js +++ b/lib/dnsserver.js @@ -29,10 +29,13 @@ function Server() { var self = this; this.on('message', function(msg, rinfo) { - //split up the message into the dns request header info and the query - var request = processRequest(msg); - var response = new Response(self, rinfo, request); - this.emit('request', request, response); + try { + //split up the message into the dns request header info and the query + var request = processRequest(msg); + var response = new Response(self, rinfo, request); + this.emit('request', request, response); + } catch (err) { + } }); }