dns.js
Version:
Plain javascript DNS protocol client (minimal)
162 lines (128 loc) • 3.5 kB
JavaScript
var framer = require('./framer');
var parser = require('./parser');
var dgram = require('dgram');
function Client(options) {
var self = this;
options = options || {};
this._seq = (Math.random() * 0x10000) | 0;
this._reqs = {};
this.timeout = 30000;
this.send = options.send;
if (this.send) return;
this.socket = dgram.createSocket('udp4');
var dnsIP = options.dnsServer || Client.DEFAULT_DNS_SERVER;
this.send = function(data) {
var buf = new Buffer(data);
self.socket.send(buf, 0, buf.length, 53, dnsIP);
};
this.socket.on('message', function(b) {
self.feed(b);
});
}
exports.Client = Client;
Client.DEFAULT_DNS_SERVER = '8.8.8.8';
Client.prototype.seq = function seq() {
var seq = this._seq++;
// Wrap-up
if (this._seq > 0xffff)
this._seq = 0;
return seq;
};
/**
* Meant to be used instead of Node.JS dns.resolve method
*/
Client.prototype.resolve = function(host, rrtype, callback) {
if (typeof rrtype === 'function') {
callback = rrtype;
rrtype = undefined;
}
return this.query(rrtype || 'A', host, callback);
}
Client.prototype.resolve4 = function(host, callback) {
return this.resolve(host, 'A', callback);
}
Client.prototype.resolve6 = function(host, callback) {
return this.resolve(host, 'AAAA', callback);
}
Client.prototype.query = function query(type, host, cb) {
if (this._destroyed) throw new Error('Client has been destroyed');
var seq = this.seq();
var header = framer.header({
id: seq,
opcode: 'QUERY',
rcode: 'OK',
qdcount: 1,
rd: true,
ra: true
});
if (host[host.length - 1] !== '.')
host += '.';
var qd = framer.question(host.split(/\./g), type, 'IN');
this._addReq(seq, function(err, ans) {
if (err)
return cb(err);
ans = ans.an.filter(function(an) {
return an.type === type;
}).map(function(an) {
return an.rdata;
});
cb(null, ans);
});
this.send(header.concat(qd));
};
Client.prototype._addReq = function _addReq(seq, cb) {
if (this._reqs[seq])
throw new Error('More than 65535 active requests');
this._reqs[seq] = {
packets: [],
timer: setTimeout(function() {
cb(new Error('Timed out'), null);
}, this.timeout),
cb: cb
};
};
Client.prototype.feed = function feed(data) {
var packet = parser.feed(data);
var seq = packet.id;
// No request - ignore response
var req = this._reqs[seq];
if (!req)
return;
req.packets.push(packet);
// Truncated - collect all data
if (packet.tc)
return;
clearTimeout(req.timer);
req.timer = null;
var ans = {
qd: [],
an: [],
ns: [],
ar: []
};
for (var i = 0; i < req.packets.length; i++) {
ans.qd = ans.qd.concat(req.packets[i].qd);
ans.an = ans.an.concat(req.packets[i].an);
ans.ns = ans.ns.concat(req.packets[i].ns);
ans.ar = ans.ar.concat(req.packets[i].ar);
}
req.cb(null, ans, req.packets);
};
Client.prototype.destroy = function() {
if (this._destroyed) return;
this._destroyed = true;
if (this.socket) this.socket.close();
};
[ 'resolve', 'resolve4', 'resolve6' ].forEach(function(method) {
// the node.js dns module's methods that are currently supported
exports[method] = function() {
var client = new Client();
var args = [].slice.call(arguments);
var cb = args[args.length - 1];
args[args.length - 1] = function(err, result) {
client.destroy();
cb(err, result);
}
client[method].apply(client, args);
}
})