Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
mafintosh committed Mar 14, 2012
0 parents commit 3279b60
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 0 deletions.
34 changes: 34 additions & 0 deletions README.md
@@ -0,0 +1,34 @@
# DNSJack

A simple DNS proxy that lets you intercept domains and route them to whatever IP you decide.
It's easy to use:

``` js
var jack = require('dnsjack').createServer();

jack.route('www.google.com', '127.0.0.1'); // route all requests to www.google.com to localhost
jack.listen(); // it listens on the standard DNS port of 53 per default

// now all requests to google.com should be routed localhost
require('http').createServer(function(req, res) {
res.writeHead(200);
res.end('jack says hi!');
}).listen(80);
```

You probably need to run the above example with `sudo` as we need to listen to port `80` and `53`.
Now change your local DNS server to `127.0.0.1` and visit `http://www.google.com` in your browser.

DNSJack will forward all request that you don't route yourself to Google's DNS server or whatever DNS
you provide in `.createServer()`.

You can also use it to monitor your DNS resolutions which can be super useful for debugging:

``` js
var jack = require('dnsjack').createServer();

jack.on('resolve', function(domain) {
console.log('Someone is resolving', domain);
});
jack.listen();
```
10 changes: 10 additions & 0 deletions examples/google.js
@@ -0,0 +1,10 @@
var jack = require('dnsjack').createServer();

jack.route('www.google.com', '127.0.0.1'); // route all requests to www.google.com to localhost
jack.listen(); // it listens on the standard DNS port of 53 per default

// now all requests to google.com should be routed localhost
require('http').createServer(function(req, res) {
res.writeHead(200);
res.end('jack says hi!');
}).listen(80);
11 changes: 11 additions & 0 deletions examples/monitor.js
@@ -0,0 +1,11 @@
var router = require('./index');

var dns = router.createServer();

dns.route('example.com', '127.0.0.1');

dns.on('resolve', function(domain) {
console.log('wanna resolve ' + domain);
});

dns.listen();
247 changes: 247 additions & 0 deletions index.js
@@ -0,0 +1,247 @@
var common = require('common');
var dgram = require('dgram');
var net = require('net');
var dns = require('dns');

var bitSlice = function(b, offset, length) {
return (b >>> (7-(offset+length-1))) & ~(0xff << length);
};
var numify = function(ip) {
ip = ip.split('.').map(function(n) {
return parseInt(n, 10);
});

var result = 0;
var base = 1;

for (var i = ip.length-1; i >= 0; i--) {
result += ip[i]*base;
base *= 256;
}

return result;
};
var domainify = function(qname) {
var parts = [];

for (var i = 0; i < qname.length && qname[i];) {
var length = qname[i];
var offset = i+1;

parts.push(qname.slice(offset,offset+length).toString());

i = offset+length;
}

return parts.join('.');
};
var qnameify = function(domain) {
var qname = new Buffer(domain.length+2);
var offset = 0;

domain = domain.split('.');

for (var i = 0; i < domain.length; i++) {
qname[offset] = domain[i].length;
qname.write(domain[i], offset+1, domain[i].length, 'ascii');
offset += qname[offset]+1;
}

qname[qname.length-1] = 0;

return qname;
};

var parse = function(buf) {
var header = {};
var question = {};
var b = buf.slice(2,3).toString('binary', 0, 1).charCodeAt(0);

header.id = buf.slice(0,2);
header.qr = bitSlice(b,0,1);
header.opcode = bitSlice(b,1,4);
header.aa = bitSlice(b,5,1);
header.tc = bitSlice(b,6,1);
header.rd = bitSlice(b,7,1);

b = buf.slice(3,4).toString('binary', 0, 1).charCodeAt(0);

header.ra = bitSlice(b,0,1);
header.z = bitSlice(b,1,3);
header.rcode = bitSlice(b,4,4);

header.qdcount = buf.slice(4,6);
header.ancount = buf.slice(6,8);
header.nscount = buf.slice(8,10);
header.arcount = buf.slice(10, 12);

question.qname = buf.slice(12, buf.length-4);
question.qtype = buf.slice(buf.length-4, buf.length-2);
question.qclass = buf.slice(buf.length-2, buf.length);


return {header:header, question:question};
};

var responseBuffer = function(query) {
var question = query.question;
var header = query.header;
var qname = question.qname;
var offset = 16+qname.length;
var length = offset;

for (var i = 0; i < query.rr.length; i++) {
length += query.rr[i].qname.length+14;
}

var buf = new Buffer(length);

header.id.copy(buf, 0, 0, 2);

buf[2] = 0x00 | header.qr << 7 | header.opcode << 3 | header.aa << 2 | header.tc << 1 | header.rd;
buf[3] = 0x00 | header.ra << 7 | header.z << 4 | header.rcode;

buf.writeUInt16BE(header.qdcount, 4);
buf.writeUInt16BE(header.ancount, 6);
buf.writeUInt16BE(header.nscount, 8);
buf.writeUInt16BE(header.arcount, 10);

qname.copy(buf, 12);

question.qtype.copy(buf, 12+qname.length, question.qtype, 2);
question.qclass.copy(buf, 12+qname.length+2, question.qclass, 2);

for (var i = 0; i < query.rr.length; i++) {
var rr = query.rr[i];

rr.qname.copy(buf, offset);

offset += rr.qname.length;

buf.writeUInt16BE(rr.qtype, offset);
buf.writeUInt16BE(rr.qclass, offset+2);
buf.writeUInt32BE(rr.ttl, offset+4);
buf.writeUInt16BE(rr.rdlength, offset+8);
buf.writeUInt32BE(rr.rdata, offset+10);

offset += 14;
}

return buf;
};

var response = function(query, to) {
var response = {};
var header = response.header = {};
var question = response.question = {};
var rrs = resolve(query.question.qname, to);

header.id = query.header.id;
header.ancount = rrs.length;

header.qr = 1;
header.opcode = 0;
header.aa = 0;
header.tc = 0;
header.rd = 1;
header.ra = 0;
header.z = 0;
header.rcode = 0;
header.qdcount = 1;
header.nscount = 0;
header.arcount = 0;

question.qname = query.question.qname;
question.qtype = query.question.qtype;
question.qclass = query.question.qclass;

response.rr = rrs;

return responseBuffer(response);
};

var resolve = function(qname, to) {
var r = {};

r.qname = qname;
r.qtype = 1;
r.qclass = 1;
r.ttl = 1;
r.rdlength = 4;
r.rdata = to;

return [r];
};


exports.createServer = function(proxy) {
proxy = proxy || '8.8.8.8';

var that = common.createEmitter();
var server = dgram.createSocket('udp4');
var routes = [];

server.on('message', function (message, rinfo) {
var query = parse(message);
var domain = domainify(query.question.qname);
var to;

var respond = function(buf) {
server.send(buf, 0, buf.length, rinfo.port, rinfo.address);
};

for (var i = 0; i < routes.length; i++) {
if (routes[i].from.test(domain)) {
to = routes[i].to;
break;
}
}
if (to) {
that.emit('route', domain, to);
respond(response(query, to));
return;
}

var fallback = dgram.createSocket('udp4');

that.emit('resolve', domain);

fallback.send(message, 0, message.length, 53, proxy);
fallback.on('message', function(response) {
respond(response);
fallback.close();
});
});

that.route = function(from, to) {
if (Array.isArray(from)) {
from.forEach(function(item) {
that.route(item, to);
});
return that;
}
if (!net.isIP(to)) {
dns.lookup(to, function(err, ip) {
if (err) {
return;
}
that.route(from, ip);
});
return that;
}

from = new RegExp('^'+from.replace(/\./g, '\\.').replace(/\*\\\./g, '(.+)\\.')+'$', 'i');
to = numify(to);

routes.push({from:from, to:to});

return that;
};
that.listen = function(port) {
server.bind(port || 53);

return that;
};

return that;
};

0 comments on commit 3279b60

Please sign in to comment.