Skip to content
Snippets Groups Projects
Commit 48a21fc6 authored by Deucе's avatar Deucе :ok_hand_tone4:
Browse files

Add DNS_blocking sub-class for non-event driven lookups

The DNS_blocking class supports the same API as the DNS class, but
does not use events, so any method will complete before returning.
As such, the callback argument is optional.
parent e25619bc
No related branches found
No related tags found
1 merge request!463MRC mods by Codefenix (2024-10-20)
/* DNS protocol library
* Uses events, so the calling script *MUST* set js.do_callbacks
* The DNS class Uses events, so the calling script *MUST* set js.do_callbacks
* Example usage:
*
* js.do_callbacks = true;
......@@ -11,20 +11,209 @@
* exit(0);
* }
*
* dns = new DNS();
* dns.resolve('example.com', handle);
*
* The DNS_blocking does not use events, so callback arguments are optional:
*
* load('dns.js');
* dns = new DNS_blocking();
* log(LOG_ERROR, dns.resolve('example.com').toSource());
*
*/
require('sockdefs.js', 'SOCK_DGRAM');
function DNS(servers) {
if (servers === undefined)
servers = system.name_servers;
var nextid = 0;
var outstanding = {};
var synchronous = false;
if (this.synchronous === undefined) {
Object.defineProperty(this, 'synchronous', {get: function() {
return synchronous;
}});
}
this.outstanding = {};
this.sockets = [];
handle_response = function() {
function handle_response() {
var resp = this.dnsObject.handle_response(this);
if (resp)
resp.q.callback.call(resp.q.thisObj, resp.ret);
}
if (servers === undefined)
servers = system.name_servers;
servers.forEach(function(server) {
var sock = new Socket(SOCK_DGRAM, "dns", server.indexOf(':') >= 0);
sock.bind();
sock.connect(server, 53);
sock.cbID = sock.on('read', handle_response);
sock.dnsObject = this;
this.sockets.push(sock);
}, this);
if (this.sockets.length < 1)
throw('Unable to create any sockets');
this.increment_id = function() {
var ret = nextid;
do {
nextid++;
if (nextid > 65535)
nextid = 0;
} while (this.outstanding[nextid] !== undefined);
return ret;
};
}
function DNS_blocking(servers) {
var synchronous = false;
Object.defineProperty(this, 'synchronous', {get: function() {
return synchronous;
}});
DNS.call(this, servers);
// Disable callbacks
this.sockets.forEach(function(sock) {
sock.clearOn('read', sock.cbID);
});
}
DNS_blocking.prototype = Object.create(DNS.prototype);
/*
* Class properties and methods
*/
DNS.types = {
'A':1,
'NS':2,
'MD':3,
'MF':4,
'CNAME':5,
'SOA':6,
'MB':7,
'MG':8,
'MR':9,
'NULL':10,
'WKS':11,
'PTR':12,
'HINFO':13,
'MINFO':14,
'MX':15,
'TXT':16,
'AAAA':28,
'SRV':33,
'NAPTR':35,
'URI':256
};
DNS.classes = {
'IN':1,
'CS':2,
'CH':3,
'HS':4
};
DNS.prototype.handle_timeout = function(os) {
os.failed++;
delete this.outstanding[os.id];
if (os.failed > os.failures)
os.callback.call(os);
else
os.obj.query(os.query, os.callback, os.thisObj, os.recursive,
os.timeout, os.failures, os.failed);
};
DNS.prototype.generate_query = function(queries, recursive) {
var id;
var namebits = {};
var query = {};
function int16_to_string(id) {
return ascii((id & 0xff00) >> 8) + ascii(id & 0xff);
}
queries.forEach(function(query) {
if (query.str === undefined)
query.str = 'example.com';
if (query.type === undefined)
query.type = 'AAAA';
if (DNS.types[query.type] !== undefined)
query.type = DNS.types[query.type];
query.type = parseInt(query.type, 10);
if (isNaN(query.type)) {
throw new Error('Invalid type');
}
if (query.class === undefined)
query.class = 'IN';
if (DNS.classes[query.class] !== undefined)
query.class = DNS.classes[query.class];
query.class = parseInt(query.class, 10);
if (isNaN(query.class)) {
throw new Error('Invalid class');
}
});
if (recursive === undefined)
recursive = true;
// Header
query.id = this.increment_id();
query.buf = int16_to_string(query.id);
query.buf += ascii(recursive ? 1 : 0);
query.buf += ascii(0);
query.buf += int16_to_string(queries.length); // Questions
query.buf += int16_to_string(0); // Answers
query.buf += int16_to_string(0); // Name Servers
query.buf += int16_to_string(0); // Additional Records
// Queries
queries.forEach(function(oneQuery) {
var thisname = '';
var fields = oneQuery.query.split(/\./).reverse();
var matched;
fields.forEach(function(field) {
if (field.length > 63)
throw new Error('invalid field in query "'+field+'" (longer than 63 characters)');
thisname = ascii(field.length) + field + thisname;
if (namebits[thisname] !== undefined && namebits[thisname] > 0) {
matched = {'offset':namebits[thisname], 'length':thisname.length};
}
else {
namebits[thisname] = 0 - thisname.length;
}
});
// Now fix up negative namebits...
Object.keys(namebits).forEach(function(name) {
if (namebits[name] < 0) {
namebits[name] = query.buf.length + thisname.length + namebits[name];
}
});
// And use the match
if (matched !== undefined) {
thisname = thisname.substr(0, thisname.length - matched.length);
thisname = thisname + ascii(0xc0 | ((matched.offset & 0x3f) >> 8)) + ascii(matched.offset & 0xff);
}
else
thisname += ascii(0);
query.buf += thisname;
query.buf += int16_to_string(oneQuery.type);
query.buf += int16_to_string(oneQuery.class);
});
this.outstanding[query.id] = {'query':queries, 'recursive':recursive, 'id':query.id, 'obj':this};
return query;
};
DNS.prototype.handle_response = function(sock) {
var resp;
var id;
var offset = 0;
......@@ -39,11 +228,11 @@ function DNS(servers) {
var rdlen;
var tmp;
string_to_int16 = function(str) {
function string_to_int16(str) {
return ((ascii(str[0])<<8) | (ascii(str[1])));
}
string_to_int32 = function(str) {
function string_to_int32(str) {
return ((ascii(str[0])<<24) | (ascii(str[1]) << 16) | (ascii(str[1]) << 8) | (ascii(str[1])));
}
......@@ -213,10 +402,10 @@ function DNS(servers) {
}
}
resp = this.recv(10000);
resp = sock.recv(10000);
id = string_to_int16(resp);
if (this.outstanding[id] === undefined)
return false;
return null;
q = this.outstanding[id];
delete this.outstanding[id];
......@@ -235,7 +424,8 @@ function DNS(servers) {
nameservers = string_to_int16(resp.substr(8, 2));
arecords = string_to_int16(resp.substr(10, 2));
if (answers === 0)
return false;
return null;
if (q.timeoutid !== undefined)
js.clearTimeout(q.timeoutid);
offset = 12;
for (i = 0; i < queries; i++) {
......@@ -304,106 +494,57 @@ function DNS(servers) {
ret.additional.push(rdata);
}
q.callback.call(q.thisObj, ret);
return true;
}
servers.forEach(function(server) {
var sock = new Socket(SOCK_DGRAM, "dns", server.indexOf(':') >= 0);
sock.bind();
sock.connect(server, 53);
sock.on('read', handle_response);
sock.outstanding = outstanding;
this.sockets.push(sock);
}, this);
if (this.sockets.length < 1)
throw('Unable to create any sockets');
increment_id = function() {
var ret = nextid;
do {
nextid++;
if (nextid > 65535)
nextid = 0;
} while (outstanding[nextid] !== undefined);
return ret;
return {'q':q, 'ret':ret};
}
}
DNS.types = {
'A':1,
'NS':2,
'MD':3,
'MF':4,
'CNAME':5,
'SOA':6,
'MB':7,
'MG':8,
'MR':9,
'NULL':10,
'WKS':11,
'PTR':12,
'HINFO':13,
'MINFO':14,
'MX':15,
'TXT':16,
'AAAA':28,
'SRV':33,
'NAPTR':35,
'URI':256
};
DNS.classes = {
'IN':1,
'CS':2,
'CH':3,
'HS':4
};
DNS.prototype.query = function(queries, /* queryStr, type, class, */callback, thisObj, recursive, timeout, failures, failed) {
var id;
var namebits = {};
var query;
function int16_to_string(id) {
return ascii((id & 0xff00) >> 8) + ascii(id & 0xff);
}
if (callback === undefined)
throw new Error('callback is required');
if (thisObj === undefined)
thisObj = this;
if (recursive === undefined)
recursive = true;
if (timeout === undefined)
timeout = 1000;
if (failures === undefined)
failures = 1;
if (failed === undefined)
failed = 0;
function handle_timeout() {
this.failed++;
queries.forEach(function(q) {
query = this.generate_query([q], recursive);
delete outstanding[this.id];
if (query) {
this.outstanding[query.id].callback = callback;
this.outstanding[query.id].thisObj = thisObj;
this.outstanding[query.id].timeout = timeout;
this.outstanding[query.id].failures = failures;
this.outstanding[query.id].failed = failed;
this.outstanding[query.id].timeoutid = js.setTimeout(this.handle_timeout, timeout,
this.outstanding[query.id]);
if (this.failed > 2)
this.callback.call(this);
else
this.obj.query(this.query, this.callback, this.thisObj, this.recursive, this.timeout, this.failures, this.failed);
this.sockets.forEach(function(sock) {
sock.write(query.buf);
});
}
}, this);
return undefined;
};
queries.forEach(function(query) {
if (query.str === undefined)
query.str = 'example.com';
if (query.type === undefined)
query.type = 'AAAA';
if (DNS.types[query.type] !== undefined)
query.type = DNS.types[query.type];
query.type = parseInt(query.type, 10);
if (isNaN(query.type)) {
throw new Error('Invalid type');
}
if (query.class === undefined)
query.class = 'IN';
if (DNS.classes[query.class] !== undefined)
query.class = DNS.classes[query.class];
query.class = parseInt(query.class, 10);
if (isNaN(query.class)) {
throw new Error('Invalid class');
}
});
DNS_blocking.prototype.query = function(queries, callback, thisObj, recursive, timeout, failures, failed) {
var query;
var ret;
var socket_array = [];
var end;
var to;
var resp;
var ready;
var done = false;
if (callback === undefined)
return;
if (thisObj === undefined)
thisObj = this;
if (recursive === undefined)
recursive = true;
if (timeout === undefined)
......@@ -413,62 +554,46 @@ DNS.prototype.query = function(queries, /* queryStr, type, class, */callback, th
if (failed === undefined)
failed = 0;
var query = '';
queries.forEach(function(q) {
query = this.generate_query([q], recursive);
// Header
id = increment_id();
query = int16_to_string(id);
query += ascii(recursive ? 1 : 0);
query += ascii(0);
query += int16_to_string(queries.length); // Questions
query += int16_to_string(0); // Answers
query += int16_to_string(0); // Name Servers
query += int16_to_string(0); // Additional Records
if (query) {
this.outstanding[query.id].callback = callback;
this.outstanding[query.id].thisObj = thisObj;
this.outstanding[query.id].timeout = timeout;
this.outstanding[query.id].failures = failures;
this.outstanding[query.id].failed = failed;
// Queries
queries.forEach(function(oneQuery) {
var thisname = '';
var fields = oneQuery.query.split(/\./).reverse();
var matched;
fields.forEach(function(field) {
if (field.length > 63)
throw new Error('invalid field in query "'+field+'" (longer than 63 characters)');
thisname = ascii(field.length) + field + thisname;
if (namebits[thisname] !== undefined && namebits[thisname] > 0) {
matched = {'offset':namebits[thisname], 'length':thisname.length};
}
else {
namebits[thisname] = 0 - thisname.length;
}
});
// Now fix up negative namebits...
Object.keys(namebits).forEach(function(name) {
if (namebits[name] < 0) {
namebits[name] = query.length + thisname.length + namebits[name];
}
this.sockets.forEach(function(sock) {
sock.write(query.buf);
});
// And use the match
if (matched !== undefined) {
thisname = thisname.substr(0, thisname.length - matched.length);
thisname = thisname + ascii(0xc0 | ((matched.offset & 0x3f) >> 8)) + ascii(matched.offset & 0xff);
}
else
thisname += ascii(0);
query += thisname;
query += int16_to_string(oneQuery.type);
query += int16_to_string(oneQuery.class);
}, this);
this.sockets[0].outstanding[id] = {'callback':callback, 'query':queries, 'recursive':recursive, 'timeout':timeout, 'thisObj':thisObj, 'id':id, 'obj':this, 'failed':failed};
this.sockets[0].outstanding[id].timeoutid = js.setTimeout(handle_timeout, timeout, this.sockets[0].outstanding[id]);
ret = [];
this.sockets.forEach(function(sock) {
sock.write(query);
});
end = system.timer + timeout / 1000;
do {
to = end - system.timer;
if (to < 0)
to = 0;
ready = socket_select(this.sockets, to);
if (ready === null)
break;
ready.forEach(function(sid) {
resp = this.handle_response(this.sockets[sid]);
if (resp !== null) {
ret.push(resp.ret);
if (callback !== undefined) {
callback.call(thisObj, resp.ret);
}
}
}, this);
} while (!done && system.timer < end && ret.length < queries.length);
if (callback === undefined)
return ret;
};
DNS.prototype.resolve = function(host, callback, thisObj)
{
......@@ -484,40 +609,59 @@ DNS.prototype.resolve = function(host, callback, thisObj)
if (host === undefined)
throw new Error('No host specified');
if (this.synchronous && callback === undefined)
throw new Error('No callback specified');
if (thisObj === undefined)
thisObj = this;
ctx.callback = callback;
ctx.thisObj = thisObj;
ctx.final = js.addEventListener(ctx.unique_id+'.final', function() {
var ret = [];
ctx.final_callback = function() {
this.ret = [];
this.AAAA.addrs.forEach(function(addr) {
ret.push(addr);
});
this.ret.push(addr);
}, this);
this.A.addrs.forEach(function(addr) {
ret.push(addr);
});
this.ret.push(addr);
}, this);
if (this.callback !== undefined) {
js.removeEventListener(this.final);
js.removeEventListener(this.respA);
js.removeEventListener(this.respAAAA);
this.callback.call(this.thisObj, ret);
});
this.callback.call(this.thisObj, this.ret);
}
};
if (callback !== undefined)
ctx.final = js.addEventListener(ctx.unique_id+'.final', ctx.final_callback);
ctx.respA = js.addEventListener(ctx.unique_id+'.respA', function() {
ctx.respA_callback = function() {
this.A.done = true;
if (this.AAAA.done)
if (this.AAAA.done) {
if (this.final !== undefined)
js.dispatchEvent(this.unique_id + '.final', this);
});
else
this.final_callback(this);
}
};
if (callback !== undefined)
ctx.respA = js.addEventListener(ctx.unique_id+'.respA', ctx.respA_callback);
ctx.respAAAA = js.addEventListener(ctx.unique_id+'.respAAAA', function() {
ctx.respAAAA_callback = function() {
this.AAAA.done = true;
if (this.A.done)
if (this.A.done) {
if (this.final !== undefined)
js.dispatchEvent(ctx.unique_id + '.final', this);
});
else
this.final_callback(this);
}
}
if (callback !== undefined)
ctx.respAAAA = js.addEventListener(ctx.unique_id+'.respAAAA', ctx.respAAAA_callback);
function handle_response(resp) {
var rectype;
switch(resp.queries[0].type) {
case DNS.types.A:
rectype = 'A';
......@@ -536,12 +680,15 @@ DNS.prototype.resolve = function(host, callback, thisObj)
return;
this[rectype].addrs.push(ans.rdata);
}, this);
if (ctx.callback !== undefined)
js.dispatchEvent(this.unique_id + '.resp'+rectype, this);
else
this['resp'+rectype+'_callback']();
}
this.query([{query:host, type:'AAAA'}], handle_response, ctx);
this.query([{query:host, type:'A'}], handle_response, ctx);
}
this.query([{query:host, type:'AAAA'},{query:host, type:'A'}], handle_response, ctx);
return ctx.ret;
};
DNS.prototype.resolveTypeClass = function(host, type, class, callback, thisObj)
{
......@@ -549,6 +696,7 @@ DNS.prototype.resolveTypeClass = function(host, type, class, callback, thisObj)
var final;
var respA;
var respAAAA;
var ret;
this.sockets.forEach(function(sock) {
ctx.unique_id += '.'+sock.local_port;
......@@ -563,7 +711,7 @@ DNS.prototype.resolveTypeClass = function(host, type, class, callback, thisObj)
if (class === undefined)
throw new Error('No class specified');
if (callback === undefined)
if (this.synchronous && callback === undefined)
throw new Error('No callback specified');
ctx.callback = callback;
......@@ -572,19 +720,23 @@ DNS.prototype.resolveTypeClass = function(host, type, class, callback, thisObj)
ctx.thisObj = thisObj;
function handle_response(resp) {
ret = [];
if (this.ret === undefined)
this.ret = [];
if (resp !== undefined && resp.answers !== undefined) {
resp.answers.forEach(function(ans) {
if (resp.queries[0].type != ans.type || resp.queries[0].class != ans.class)
return;
ret.push(ans.rdata);
});
this.ret.push(ans.rdata);
}, this);
}
this.callback.call(this.thisObj, ret);
if (this.callback !== undefined)
this.callback.call(this.thisObj, this.ret);
}
this.query([{query:host, type:type, class:class}], handle_response, ctx);
}
if (callback === undefined)
return ctx.ret;
};
DNS.prototype.reverse = function(ip, callback, thisObj)
{
......@@ -596,6 +748,9 @@ DNS.prototype.reverse = function(ip, callback, thisObj)
if (ip === undefined)
throw new Error('No IP specified');
if (this.synchronous && callback === undefined)
throw new Error('No callback specified');
if (thisObj === undefined)
thisObj = this;
......@@ -641,8 +796,8 @@ DNS.prototype.reverse = function(ip, callback, thisObj)
qstr += 'ip6.arpa';
}
this.resolveTypeClass(qstr, 'PTR', 'IN', callback, thisObj);
}
return this.resolveTypeClass(qstr, 'PTR', 'IN', callback, thisObj);
};
DNS.prototype.resolveMX = function(host, callback, thisObj)
{
......@@ -655,6 +810,9 @@ DNS.prototype.resolveMX = function(host, callback, thisObj)
if (host === undefined)
throw new Error('No host specified');
if (this.synchronous && callback === undefined)
throw new Error('No callback specified');
if (thisObj === undefined)
thisObj = this;
ctx.thisObj = thisObj;
......@@ -667,8 +825,12 @@ DNS.prototype.resolveMX = function(host, callback, thisObj)
resp.forEach(function(r) {
ret.push(r.exchange);
});
if (this.callback === undefined)
this.ret = ret;
else
this.callback.call(this.thisObj, ret);
}
this.resolveTypeClass(host, 'MX', 'IN', handler, ctx);
}
return ctx.ret;
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment