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 /* 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: * Example usage:
* *
* js.do_callbacks = true; * js.do_callbacks = true;
...@@ -11,20 +11,209 @@ ...@@ -11,20 +11,209 @@
* exit(0); * exit(0);
* } * }
* *
* dns = new DNS();
* dns.resolve('example.com', handle); * 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'); require('sockdefs.js', 'SOCK_DGRAM');
function DNS(servers) { function DNS(servers) {
if (servers === undefined)
servers = system.name_servers;
var nextid = 0; var nextid = 0;
var outstanding = {}; var synchronous = false;
if (this.synchronous === undefined) {
Object.defineProperty(this, 'synchronous', {get: function() {
return synchronous;
}});
}
this.outstanding = {};
this.sockets = []; 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 resp;
var id; var id;
var offset = 0; var offset = 0;
...@@ -39,11 +228,11 @@ function DNS(servers) { ...@@ -39,11 +228,11 @@ function DNS(servers) {
var rdlen; var rdlen;
var tmp; var tmp;
string_to_int16 = function(str) { function string_to_int16(str) {
return ((ascii(str[0])<<8) | (ascii(str[1]))); 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]))); return ((ascii(str[0])<<24) | (ascii(str[1]) << 16) | (ascii(str[1]) << 8) | (ascii(str[1])));
} }
...@@ -213,10 +402,10 @@ function DNS(servers) { ...@@ -213,10 +402,10 @@ function DNS(servers) {
} }
} }
resp = this.recv(10000); resp = sock.recv(10000);
id = string_to_int16(resp); id = string_to_int16(resp);
if (this.outstanding[id] === undefined) if (this.outstanding[id] === undefined)
return false; return null;
q = this.outstanding[id]; q = this.outstanding[id];
delete this.outstanding[id]; delete this.outstanding[id];
...@@ -235,7 +424,8 @@ function DNS(servers) { ...@@ -235,7 +424,8 @@ function DNS(servers) {
nameservers = string_to_int16(resp.substr(8, 2)); nameservers = string_to_int16(resp.substr(8, 2));
arecords = string_to_int16(resp.substr(10, 2)); arecords = string_to_int16(resp.substr(10, 2));
if (answers === 0) if (answers === 0)
return false; return null;
if (q.timeoutid !== undefined)
js.clearTimeout(q.timeoutid); js.clearTimeout(q.timeoutid);
offset = 12; offset = 12;
for (i = 0; i < queries; i++) { for (i = 0; i < queries; i++) {
...@@ -304,106 +494,57 @@ function DNS(servers) { ...@@ -304,106 +494,57 @@ function DNS(servers) {
ret.additional.push(rdata); ret.additional.push(rdata);
} }
q.callback.call(q.thisObj, ret); return {'q':q, 'ret':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;
} }
}
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) { DNS.prototype.query = function(queries, /* queryStr, type, class, */callback, thisObj, recursive, timeout, failures, failed) {
var id; var query;
var namebits = {};
function int16_to_string(id) { if (callback === undefined)
return ascii((id & 0xff00) >> 8) + ascii(id & 0xff); 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() { queries.forEach(function(q) {
this.failed++; 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.sockets.forEach(function(sock) {
this.callback.call(this); sock.write(query.buf);
else });
this.obj.query(this.query, this.callback, this.thisObj, this.recursive, this.timeout, this.failures, this.failed);
} }
}, this);
return undefined;
};
queries.forEach(function(query) { DNS_blocking.prototype.query = function(queries, callback, thisObj, recursive, timeout, failures, failed) {
if (query.str === undefined) var query;
query.str = 'example.com'; var ret;
if (query.type === undefined) var socket_array = [];
query.type = 'AAAA'; var end;
if (DNS.types[query.type] !== undefined) var to;
query.type = DNS.types[query.type]; var resp;
query.type = parseInt(query.type, 10); var ready;
if (isNaN(query.type)) { var done = false;
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 (callback === undefined) if (thisObj === undefined)
return; thisObj = this;
if (recursive === undefined) if (recursive === undefined)
recursive = true; recursive = true;
if (timeout === undefined) if (timeout === undefined)
...@@ -413,62 +554,46 @@ DNS.prototype.query = function(queries, /* queryStr, type, class, */callback, th ...@@ -413,62 +554,46 @@ DNS.prototype.query = function(queries, /* queryStr, type, class, */callback, th
if (failed === undefined) if (failed === undefined)
failed = 0; failed = 0;
var query = ''; queries.forEach(function(q) {
query = this.generate_query([q], recursive);
// Header if (query) {
id = increment_id(); this.outstanding[query.id].callback = callback;
query = int16_to_string(id); this.outstanding[query.id].thisObj = thisObj;
query += ascii(recursive ? 1 : 0); this.outstanding[query.id].timeout = timeout;
query += ascii(0); this.outstanding[query.id].failures = failures;
query += int16_to_string(queries.length); // Questions this.outstanding[query.id].failed = failed;
query += int16_to_string(0); // Answers
query += int16_to_string(0); // Name Servers
query += int16_to_string(0); // Additional Records
// Queries this.sockets.forEach(function(sock) {
queries.forEach(function(oneQuery) { sock.write(query.buf);
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];
}
}); });
// 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);
this.sockets[0].outstanding[id] = {'callback':callback, 'query':queries, 'recursive':recursive, 'timeout':timeout, 'thisObj':thisObj, 'id':id, 'obj':this, 'failed':failed}; ret = [];
this.sockets[0].outstanding[id].timeoutid = js.setTimeout(handle_timeout, timeout, this.sockets[0].outstanding[id]);
this.sockets.forEach(function(sock) { end = system.timer + timeout / 1000;
sock.write(query); 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) DNS.prototype.resolve = function(host, callback, thisObj)
{ {
...@@ -484,40 +609,59 @@ DNS.prototype.resolve = function(host, callback, thisObj) ...@@ -484,40 +609,59 @@ DNS.prototype.resolve = function(host, callback, thisObj)
if (host === undefined) if (host === undefined)
throw new Error('No host specified'); throw new Error('No host specified');
if (this.synchronous && callback === undefined)
throw new Error('No callback specified');
if (thisObj === undefined) if (thisObj === undefined)
thisObj = this; thisObj = this;
ctx.callback = callback; ctx.callback = callback;
ctx.thisObj = thisObj; ctx.thisObj = thisObj;
ctx.final = js.addEventListener(ctx.unique_id+'.final', function() { ctx.final_callback = function() {
var ret = []; this.ret = [];
this.AAAA.addrs.forEach(function(addr) { this.AAAA.addrs.forEach(function(addr) {
ret.push(addr); this.ret.push(addr);
}); }, this);
this.A.addrs.forEach(function(addr) { 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.final);
js.removeEventListener(this.respA); js.removeEventListener(this.respA);
js.removeEventListener(this.respAAAA); 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; this.A.done = true;
if (this.AAAA.done) if (this.AAAA.done) {
if (this.final !== undefined)
js.dispatchEvent(this.unique_id + '.final', this); 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; this.AAAA.done = true;
if (this.A.done) if (this.A.done) {
if (this.final !== undefined)
js.dispatchEvent(ctx.unique_id + '.final', this); 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) { function handle_response(resp) {
var rectype; var rectype;
switch(resp.queries[0].type) { switch(resp.queries[0].type) {
case DNS.types.A: case DNS.types.A:
rectype = 'A'; rectype = 'A';
...@@ -536,12 +680,15 @@ DNS.prototype.resolve = function(host, callback, thisObj) ...@@ -536,12 +680,15 @@ DNS.prototype.resolve = function(host, callback, thisObj)
return; return;
this[rectype].addrs.push(ans.rdata); this[rectype].addrs.push(ans.rdata);
}, this); }, this);
if (ctx.callback !== undefined)
js.dispatchEvent(this.unique_id + '.resp'+rectype, this); 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:'AAAA'},{query:host, type:'A'}], handle_response, ctx);
this.query([{query:host, type:'A'}], handle_response, ctx); return ctx.ret;
} };
DNS.prototype.resolveTypeClass = function(host, type, class, callback, thisObj) DNS.prototype.resolveTypeClass = function(host, type, class, callback, thisObj)
{ {
...@@ -549,6 +696,7 @@ 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 final;
var respA; var respA;
var respAAAA; var respAAAA;
var ret;
this.sockets.forEach(function(sock) { this.sockets.forEach(function(sock) {
ctx.unique_id += '.'+sock.local_port; ctx.unique_id += '.'+sock.local_port;
...@@ -563,7 +711,7 @@ DNS.prototype.resolveTypeClass = function(host, type, class, callback, thisObj) ...@@ -563,7 +711,7 @@ DNS.prototype.resolveTypeClass = function(host, type, class, callback, thisObj)
if (class === undefined) if (class === undefined)
throw new Error('No class specified'); throw new Error('No class specified');
if (callback === undefined) if (this.synchronous && callback === undefined)
throw new Error('No callback specified'); throw new Error('No callback specified');
ctx.callback = callback; ctx.callback = callback;
...@@ -572,19 +720,23 @@ DNS.prototype.resolveTypeClass = function(host, type, class, callback, thisObj) ...@@ -572,19 +720,23 @@ DNS.prototype.resolveTypeClass = function(host, type, class, callback, thisObj)
ctx.thisObj = thisObj; ctx.thisObj = thisObj;
function handle_response(resp) { function handle_response(resp) {
ret = []; if (this.ret === undefined)
this.ret = [];
if (resp !== undefined && resp.answers !== undefined) { if (resp !== undefined && resp.answers !== undefined) {
resp.answers.forEach(function(ans) { resp.answers.forEach(function(ans) {
if (resp.queries[0].type != ans.type || resp.queries[0].class != ans.class) if (resp.queries[0].type != ans.type || resp.queries[0].class != ans.class)
return; 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); this.query([{query:host, type:type, class:class}], handle_response, ctx);
} if (callback === undefined)
return ctx.ret;
};
DNS.prototype.reverse = function(ip, callback, thisObj) DNS.prototype.reverse = function(ip, callback, thisObj)
{ {
...@@ -596,6 +748,9 @@ DNS.prototype.reverse = function(ip, callback, thisObj) ...@@ -596,6 +748,9 @@ DNS.prototype.reverse = function(ip, callback, thisObj)
if (ip === undefined) if (ip === undefined)
throw new Error('No IP specified'); throw new Error('No IP specified');
if (this.synchronous && callback === undefined)
throw new Error('No callback specified');
if (thisObj === undefined) if (thisObj === undefined)
thisObj = this; thisObj = this;
...@@ -641,8 +796,8 @@ DNS.prototype.reverse = function(ip, callback, thisObj) ...@@ -641,8 +796,8 @@ DNS.prototype.reverse = function(ip, callback, thisObj)
qstr += 'ip6.arpa'; 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) DNS.prototype.resolveMX = function(host, callback, thisObj)
{ {
...@@ -655,6 +810,9 @@ DNS.prototype.resolveMX = function(host, callback, thisObj) ...@@ -655,6 +810,9 @@ DNS.prototype.resolveMX = function(host, callback, thisObj)
if (host === undefined) if (host === undefined)
throw new Error('No host specified'); throw new Error('No host specified');
if (this.synchronous && callback === undefined)
throw new Error('No callback specified');
if (thisObj === undefined) if (thisObj === undefined)
thisObj = this; thisObj = this;
ctx.thisObj = thisObj; ctx.thisObj = thisObj;
...@@ -667,8 +825,12 @@ DNS.prototype.resolveMX = function(host, callback, thisObj) ...@@ -667,8 +825,12 @@ DNS.prototype.resolveMX = function(host, callback, thisObj)
resp.forEach(function(r) { resp.forEach(function(r) {
ret.push(r.exchange); ret.push(r.exchange);
}); });
if (this.callback === undefined)
this.ret = ret;
else
this.callback.call(this.thisObj, ret); this.callback.call(this.thisObj, ret);
} }
this.resolveTypeClass(host, 'MX', 'IN', handler, ctx); 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