mirror of
https://github.com/bjc/prosody.git
synced 2025-04-01 20:27:39 +03:00
1236 lines
36 KiB
Lua
1236 lines
36 KiB
Lua
-- Prosody IM
|
|
-- This file is included with Prosody IM. It has modifications,
|
|
-- which are hereby placed in the public domain.
|
|
|
|
|
|
-- todo: quick (default) header generation
|
|
-- todo: nxdomain, error handling
|
|
-- todo: cache results of encodeName
|
|
|
|
|
|
-- reference: https://www.rfc-editor.org/rfc/rfc1035.html
|
|
-- reference: https://www.rfc-editor.org/rfc/rfc1876.html (LOC)
|
|
|
|
|
|
local socket = require "socket";
|
|
local have_timer, timer = pcall(require, "prosody.util.timer");
|
|
local new_ip = require "prosody.util.ip".new_ip;
|
|
local have_util_net, util_net = pcall(require, "prosody.util.net");
|
|
|
|
local log = require "prosody.util.logger".init("dns");
|
|
|
|
local _, windows = pcall(require, "prosody.util.windows");
|
|
local is_windows = (_ and windows) or os.getenv("WINDIR");
|
|
|
|
local coroutine, io, math, string, table =
|
|
coroutine, io, math, string, table;
|
|
|
|
local ipairs, next, pairs, print, setmetatable, tostring, assert, error, select, type =
|
|
ipairs, next, pairs, print, setmetatable, tostring, assert, error, select, type;
|
|
|
|
local ztact = { -- public domain 20080404 lua@ztact.com
|
|
get = function(parent, ...)
|
|
local len = select('#', ...);
|
|
for i=1,len do
|
|
parent = parent[select(i, ...)];
|
|
if parent == nil then break; end
|
|
end
|
|
return parent;
|
|
end;
|
|
set = function(parent, ...)
|
|
local len = select('#', ...);
|
|
local key, value = select(len-1, ...);
|
|
local cutpoint, cutkey;
|
|
|
|
for i=1,len-2 do
|
|
local key = select (i, ...)
|
|
local child = parent[key]
|
|
|
|
if value == nil then
|
|
if child == nil then
|
|
return;
|
|
elseif next(child, next(child)) then
|
|
cutpoint = nil; cutkey = nil;
|
|
elseif cutpoint == nil then
|
|
cutpoint = parent; cutkey = key;
|
|
end
|
|
elseif child == nil then
|
|
child = {};
|
|
parent[key] = child;
|
|
end
|
|
parent = child
|
|
end
|
|
|
|
if value == nil and cutpoint then
|
|
cutpoint[cutkey] = nil;
|
|
else
|
|
parent[key] = value;
|
|
return value;
|
|
end
|
|
end;
|
|
};
|
|
local get, set = ztact.get, ztact.set;
|
|
|
|
local default_timeout = 5;
|
|
local default_jitter = 1;
|
|
local default_retry_jitter = 2;
|
|
|
|
-------------------------------------------------- module dns
|
|
local _ENV = nil;
|
|
-- luacheck: std none
|
|
local dns = {};
|
|
|
|
|
|
-- dns type & class codes ------------------------------ dns type & class codes
|
|
|
|
|
|
local append = table.insert
|
|
|
|
|
|
local function highbyte(i) -- - - - - - - - - - - - - - - - - - - highbyte
|
|
return (i-(i%0x100))/0x100;
|
|
end
|
|
|
|
|
|
local function augment (t, prefix) -- - - - - - - - - - - - - - - - - augment
|
|
local a = {};
|
|
for i,s in pairs(t) do
|
|
a[i] = s;
|
|
a[s] = s;
|
|
a[string.lower(s)] = s;
|
|
end
|
|
setmetatable(a, {
|
|
__index = function (_, i)
|
|
if type(i) == "number" then
|
|
return ("%s%d"):format(prefix, i);
|
|
elseif type(i) == "string" then
|
|
return i:upper();
|
|
end
|
|
end;
|
|
})
|
|
return a;
|
|
end
|
|
|
|
|
|
local function encode (t) -- - - - - - - - - - - - - - - - - - - - - encode
|
|
local code = {};
|
|
for i,s in pairs(t) do
|
|
local word = string.char(highbyte(i), i%0x100);
|
|
code[i] = word;
|
|
code[s] = word;
|
|
code[string.lower(s)] = word;
|
|
end
|
|
return code;
|
|
end
|
|
|
|
|
|
dns.types = {
|
|
[1] = "A", -- a host address,[RFC1035],,
|
|
[2] = "NS", -- an authoritative name server,[RFC1035],,
|
|
[3] = "MD", -- a mail destination (OBSOLETE - use MX),[RFC1035],,
|
|
[4] = "MF", -- a mail forwarder (OBSOLETE - use MX),[RFC1035],,
|
|
[5] = "CNAME", -- the canonical name for an alias,[RFC1035],,
|
|
[6] = "SOA", -- marks the start of a zone of authority,[RFC1035],,
|
|
[7] = "MB", -- a mailbox domain name (EXPERIMENTAL),[RFC1035],,
|
|
[8] = "MG", -- a mail group member (EXPERIMENTAL),[RFC1035],,
|
|
[9] = "MR", -- a mail rename domain name (EXPERIMENTAL),[RFC1035],,
|
|
[10] = "NULL", -- a null RR (EXPERIMENTAL),[RFC1035],,
|
|
[11] = "WKS", -- a well known service description,[RFC1035],,
|
|
[12] = "PTR", -- a domain name pointer,[RFC1035],,
|
|
[13] = "HINFO", -- host information,[RFC1035],,
|
|
[14] = "MINFO", -- mailbox or mail list information,[RFC1035],,
|
|
[15] = "MX", -- mail exchange,[RFC1035],,
|
|
[16] = "TXT", -- text strings,[RFC1035],,
|
|
[17] = "RP", -- for Responsible Person,[RFC1183],,
|
|
[18] = "AFSDB", -- for AFS Data Base location,[RFC1183][RFC5864],,
|
|
[19] = "X25", -- for X.25 PSDN address,[RFC1183],,
|
|
[20] = "ISDN", -- for ISDN address,[RFC1183],,
|
|
[21] = "RT", -- for Route Through,[RFC1183],,
|
|
[22] = "NSAP", -- "for NSAP address, NSAP style A record",[RFC1706],,
|
|
[23] = "NSAP-PTR", -- "for domain name pointer, NSAP style",[RFC1348][RFC1637][RFC1706],,
|
|
[24] = "SIG", -- for security signature,[RFC4034][RFC3755][RFC2535][RFC2536][RFC2537][RFC2931][RFC3110][RFC3008],,
|
|
[25] = "KEY", -- for security key,[RFC4034][RFC3755][RFC2535][RFC2536][RFC2537][RFC2539][RFC3008][RFC3110],,
|
|
[26] = "PX", -- X.400 mail mapping information,[RFC2163],,
|
|
[27] = "GPOS", -- Geographical Position,[RFC1712],,
|
|
[28] = "AAAA", -- IP6 Address,[RFC3596],,
|
|
[29] = "LOC", -- Location Information,[RFC1876],,
|
|
[30] = "NXT", -- Next Domain (OBSOLETE),[RFC3755][RFC2535],,
|
|
[31] = "EID", -- Endpoint Identifier,[Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt],,1995-06
|
|
[32] = "NIMLOC", -- Nimrod Locator,[1][Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt],,1995-06
|
|
[33] = "SRV", -- Server Selection,[1][RFC2782],,
|
|
[34] = "ATMA", -- ATM Address,"[ ATM Forum Technical Committee, ""ATM Name System, V2.0"", Doc ID: AF-DANS-0152.000, July 2000. Available from and held in escrow by IANA.]",,
|
|
[35] = "NAPTR", -- Naming Authority Pointer,[RFC2915][RFC2168][RFC3403],,
|
|
[36] = "KX", -- Key Exchanger,[RFC2230],,
|
|
[37] = "CERT", -- CERT,[RFC4398],,
|
|
[38] = "A6", -- A6 (OBSOLETE - use AAAA),[RFC3226][RFC2874][RFC6563],,
|
|
[39] = "DNAME", -- DNAME,[RFC6672],,
|
|
[40] = "SINK", -- SINK,[Donald_E_Eastlake][http://tools.ietf.org/html/draft-eastlake-kitchen-sink],,1997-11
|
|
[41] = "OPT", -- OPT,[RFC6891][RFC3225],,
|
|
[42] = "APL", -- APL,[RFC3123],,
|
|
[43] = "DS", -- Delegation Signer,[RFC4034][RFC3658],,
|
|
[44] = "SSHFP", -- SSH Key Fingerprint,[RFC4255],,
|
|
[45] = "IPSECKEY", -- IPSECKEY,[RFC4025],,
|
|
[46] = "RRSIG", -- RRSIG,[RFC4034][RFC3755],,
|
|
[47] = "NSEC", -- NSEC,[RFC4034][RFC3755],,
|
|
[48] = "DNSKEY", -- DNSKEY,[RFC4034][RFC3755],,
|
|
[49] = "DHCID", -- DHCID,[RFC4701],,
|
|
[50] = "NSEC3", -- NSEC3,[RFC5155],,
|
|
[51] = "NSEC3PARAM", -- NSEC3PARAM,[RFC5155],,
|
|
[52] = "TLSA", -- TLSA,[RFC6698],,
|
|
[53] = "SMIMEA", -- S/MIME cert association,[RFC8162],SMIMEA/smimea-completed-template,2015-12-01
|
|
-- [54] = "Unassigned", -- ,,,
|
|
[55] = "HIP", -- Host Identity Protocol,[RFC8005],,
|
|
[56] = "NINFO", -- NINFO,[Jim_Reid],NINFO/ninfo-completed-template,2008-01-21
|
|
[57] = "RKEY", -- RKEY,[Jim_Reid],RKEY/rkey-completed-template,2008-01-21
|
|
[58] = "TALINK", -- Trust Anchor LINK,[Wouter_Wijngaards],TALINK/talink-completed-template,2010-02-17
|
|
[59] = "CDS", -- Child DS,[RFC7344],CDS/cds-completed-template,2011-06-06
|
|
[60] = "CDNSKEY", -- DNSKEY(s) the Child wants reflected in DS,[RFC7344],,2014-06-16
|
|
[61] = "OPENPGPKEY", -- OpenPGP Key,[RFC7929],OPENPGPKEY/openpgpkey-completed-template,2014-08-12
|
|
[62] = "CSYNC", -- Child-To-Parent Synchronization,[RFC7477],,2015-01-27
|
|
-- [63 .. 98] = "Unassigned", -- ,,,
|
|
[99] = "SPF", -- ,[RFC7208],,
|
|
[100] = "UINFO", -- ,[IANA-Reserved],,
|
|
[101] = "UID", -- ,[IANA-Reserved],,
|
|
[102] = "GID", -- ,[IANA-Reserved],,
|
|
[103] = "UNSPEC", -- ,[IANA-Reserved],,
|
|
[104] = "NID", -- ,[RFC6742],ILNP/nid-completed-template,
|
|
[105] = "L32", -- ,[RFC6742],ILNP/l32-completed-template,
|
|
[106] = "L64", -- ,[RFC6742],ILNP/l64-completed-template,
|
|
[107] = "LP", -- ,[RFC6742],ILNP/lp-completed-template,
|
|
[108] = "EUI48", -- an EUI-48 address,[RFC7043],EUI48/eui48-completed-template,2013-03-27
|
|
[109] = "EUI64", -- an EUI-64 address,[RFC7043],EUI64/eui64-completed-template,2013-03-27
|
|
-- [110 .. 248] = "Unassigned", -- ,,,
|
|
[249] = "TKEY", -- Transaction Key,[RFC2930],,
|
|
[250] = "TSIG", -- Transaction Signature,[RFC2845],,
|
|
[251] = "IXFR", -- incremental transfer,[RFC1995],,
|
|
[252] = "AXFR", -- transfer of an entire zone,[RFC1035][RFC5936],,
|
|
[253] = "MAILB", -- "mailbox-related RRs (MB, MG or MR)",[RFC1035],,
|
|
[254] = "MAILA", -- mail agent RRs (OBSOLETE - see MX),[RFC1035],,
|
|
[255] = "*", -- A request for all records the server/cache has available,[RFC1035][RFC6895],,
|
|
[256] = "URI", -- URI,[RFC7553],URI/uri-completed-template,2011-02-22
|
|
[257] = "CAA", -- Certification Authority Restriction,[RFC6844],CAA/caa-completed-template,2011-04-07
|
|
[258] = "AVC", -- Application Visibility and Control,[Wolfgang_Riedel],AVC/avc-completed-template,2016-02-26
|
|
[259] = "DOA", -- Digital Object Architecture,[draft-durand-doa-over-dns],DOA/doa-completed-template,2017-08-30
|
|
-- [260 .. 32767] = "Unassigned", -- ,,,
|
|
[32768] = "TA", -- DNSSEC Trust Authorities,"[Sam_Weiler][http://cameo.library.cmu.edu/][ Deploying DNSSEC Without a Signed Root. Technical Report 1999-19, Information Networking Institute, Carnegie Mellon University, April 2004.]",,2005-12-13
|
|
[32769] = "DLV", -- DNSSEC Lookaside Validation,[RFC4431],,
|
|
-- [32770 .. 65279] = "Unassigned", -- ,,,
|
|
-- [65280 .. 65534] = "Private use", -- ,,,
|
|
-- [65535] = "Reserved", -- ,,,
|
|
}
|
|
|
|
dns.classes = { 'IN', 'CS', 'CH', 'HS', [255] = '*' };
|
|
|
|
|
|
dns.type = augment (dns.types, "TYPE");
|
|
dns.class = augment (dns.classes, "CLASS");
|
|
dns.typecode = encode (dns.types);
|
|
dns.classcode = encode (dns.classes);
|
|
|
|
|
|
|
|
local function standardize(qname, qtype, qclass) -- - - - - - - standardize
|
|
if string.byte(qname, -1) ~= 0x2E then qname = qname..'.'; end
|
|
qname = string.lower(qname);
|
|
return qname, dns.type[qtype or 'A'], dns.class[qclass or 'IN'];
|
|
end
|
|
|
|
|
|
local function prune(rrs, time, soft) -- - - - - - - - - - - - - - - prune
|
|
time = time or socket.gettime();
|
|
for i,rr in ipairs(rrs) do
|
|
if rr.tod then
|
|
if rr.tod < time then
|
|
rrs[rr[rr.type:lower()]] = nil;
|
|
table.remove(rrs, i);
|
|
return prune(rrs, time, soft); -- Re-iterate
|
|
end
|
|
elseif soft == 'soft' then -- What is this? I forget!
|
|
assert(rr.ttl == 0);
|
|
rrs[rr[rr.type:lower()]] = nil;
|
|
table.remove(rrs, i);
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- metatables & co. ------------------------------------------ metatables & co.
|
|
|
|
|
|
local resolver = {};
|
|
resolver.__index = resolver;
|
|
|
|
resolver.timeout = default_timeout;
|
|
|
|
local function default_rr_tostring(rr)
|
|
local rr_val = rr.type and rr[rr.type:lower()];
|
|
if type(rr_val) ~= "string" then
|
|
return "<UNKNOWN RDATA TYPE>";
|
|
end
|
|
return rr_val;
|
|
end
|
|
|
|
local special_tostrings = {
|
|
LOC = resolver.LOC_tostring;
|
|
MX = function (rr)
|
|
return string.format('%2i %s', rr.pref, rr.mx);
|
|
end;
|
|
SRV = function (rr)
|
|
local s = rr.srv;
|
|
return string.format('%5d %5d %5d %s', s.priority, s.weight, s.port, s.target);
|
|
end;
|
|
};
|
|
|
|
local rr_metatable = {}; -- - - - - - - - - - - - - - - - - - - rr_metatable
|
|
function rr_metatable.__tostring(rr)
|
|
local rr_string = (special_tostrings[rr.type] or default_rr_tostring)(rr);
|
|
return string.format('%2s %-5s %6i %-28s %s', rr.class, rr.type, rr.ttl, rr.name, rr_string);
|
|
end
|
|
|
|
|
|
local rrs_metatable = {}; -- - - - - - - - - - - - - - - - - - rrs_metatable
|
|
function rrs_metatable.__tostring(rrs)
|
|
local t = {};
|
|
for _, rr in ipairs(rrs) do
|
|
append(t, tostring(rr)..'\n');
|
|
end
|
|
return table.concat(t);
|
|
end
|
|
|
|
|
|
local cache_metatable = {}; -- - - - - - - - - - - - - - - - cache_metatable
|
|
function cache_metatable.__tostring(cache)
|
|
local time = socket.gettime();
|
|
local t = {};
|
|
for class,types in pairs(cache) do
|
|
for type,names in pairs(types) do
|
|
for name,rrs in pairs(names) do
|
|
prune(rrs, time);
|
|
append(t, tostring(rrs));
|
|
end
|
|
end
|
|
end
|
|
return table.concat(t);
|
|
end
|
|
|
|
|
|
-- packet layer -------------------------------------------------- packet layer
|
|
|
|
|
|
function dns.random(...) -- - - - - - - - - - - - - - - - - - - dns.random
|
|
math.randomseed(math.floor(10000*socket.gettime()) % 0x80000000);
|
|
dns.random = math.random;
|
|
return dns.random(...);
|
|
end
|
|
|
|
|
|
local function encodeHeader(o) -- - - - - - - - - - - - - - - encodeHeader
|
|
o = o or {};
|
|
o.id = o.id or dns.random(0, 0xffff); -- 16b (random) id
|
|
|
|
o.rd = o.rd or 1; -- 1b 1 recursion desired
|
|
o.tc = o.tc or 0; -- 1b 1 truncated response
|
|
o.aa = o.aa or 0; -- 1b 1 authoritative response
|
|
o.opcode = o.opcode or 0; -- 4b 0 query
|
|
-- 1 inverse query
|
|
-- 2 server status request
|
|
-- 3-15 reserved
|
|
o.qr = o.qr or 0; -- 1b 0 query, 1 response
|
|
|
|
o.rcode = o.rcode or 0; -- 4b 0 no error
|
|
-- 1 format error
|
|
-- 2 server failure
|
|
-- 3 name error
|
|
-- 4 not implemented
|
|
-- 5 refused
|
|
-- 6-15 reserved
|
|
o.z = o.z or 0; -- 3b 0 reserved
|
|
o.ra = o.ra or 0; -- 1b 1 recursion available
|
|
|
|
o.qdcount = o.qdcount or 1; -- 16b number of question RRs
|
|
o.ancount = o.ancount or 0; -- 16b number of answers RRs
|
|
o.nscount = o.nscount or 0; -- 16b number of nameservers RRs
|
|
o.arcount = o.arcount or 0; -- 16b number of additional RRs
|
|
|
|
-- string.char() rounds, so prevent roundup with -0.4999
|
|
local header = string.char(
|
|
highbyte(o.id), o.id %0x100,
|
|
o.rd + 2*o.tc + 4*o.aa + 8*o.opcode + 128*o.qr,
|
|
o.rcode + 16*o.z + 128*o.ra,
|
|
highbyte(o.qdcount), o.qdcount %0x100,
|
|
highbyte(o.ancount), o.ancount %0x100,
|
|
highbyte(o.nscount), o.nscount %0x100,
|
|
highbyte(o.arcount), o.arcount %0x100
|
|
);
|
|
|
|
return header, o.id;
|
|
end
|
|
|
|
|
|
local function encodeName(name) -- - - - - - - - - - - - - - - - encodeName
|
|
local t = {};
|
|
for part in string.gmatch(name, '[^.]+') do
|
|
append(t, string.char(string.len(part)));
|
|
append(t, part);
|
|
end
|
|
append(t, string.char(0));
|
|
return table.concat(t);
|
|
end
|
|
|
|
|
|
local function encodeQuestion(qname, qtype, qclass) -- - - - encodeQuestion
|
|
qname = encodeName(qname);
|
|
qtype = dns.typecode[qtype or 'a'];
|
|
qclass = dns.classcode[qclass or 'in'];
|
|
return qname..qtype..qclass;
|
|
end
|
|
|
|
|
|
function resolver:byte(len) -- - - - - - - - - - - - - - - - - - - - - byte
|
|
len = len or 1;
|
|
local offset = self.offset;
|
|
local last = offset + len - 1;
|
|
if last > #self.packet then
|
|
error(string.format('out of bounds: %i>%i', last, #self.packet));
|
|
end
|
|
self.offset = offset + len;
|
|
return string.byte(self.packet, offset, last);
|
|
end
|
|
|
|
|
|
function resolver:word() -- - - - - - - - - - - - - - - - - - - - - - word
|
|
local b1, b2 = self:byte(2);
|
|
return 0x100*b1 + b2;
|
|
end
|
|
|
|
|
|
function resolver:dword () -- - - - - - - - - - - - - - - - - - - - - dword
|
|
local b1, b2, b3, b4 = self:byte(4);
|
|
--print('dword', b1, b2, b3, b4);
|
|
return 0x1000000*b1 + 0x10000*b2 + 0x100*b3 + b4;
|
|
end
|
|
|
|
|
|
function resolver:sub(len) -- - - - - - - - - - - - - - - - - - - - - - sub
|
|
len = len or 1;
|
|
local s = string.sub(self.packet, self.offset, self.offset + len - 1);
|
|
self.offset = self.offset + len;
|
|
return s;
|
|
end
|
|
|
|
|
|
function resolver:header(force) -- - - - - - - - - - - - - - - - - - header
|
|
local id = self:word();
|
|
--print(string.format(':header id %x', id));
|
|
if not self.active[id] and not force then return nil; end
|
|
|
|
local h = { id = id };
|
|
|
|
local b1, b2 = self:byte(2);
|
|
|
|
h.rd = b1 %2;
|
|
h.tc = b1 /2%2;
|
|
h.aa = b1 /4%2;
|
|
h.opcode = b1 /8%16;
|
|
h.qr = b1 /128;
|
|
|
|
h.rcode = b2 %16;
|
|
h.z = b2 /16%8;
|
|
h.ra = b2 /128;
|
|
|
|
h.qdcount = self:word();
|
|
h.ancount = self:word();
|
|
h.nscount = self:word();
|
|
h.arcount = self:word();
|
|
|
|
for k,v in pairs(h) do h[k] = v-v%1; end
|
|
|
|
return h;
|
|
end
|
|
|
|
|
|
function resolver:name() -- - - - - - - - - - - - - - - - - - - - - - name
|
|
local remember, pointers = nil, 0;
|
|
local len = self:byte();
|
|
local n = {};
|
|
if len == 0 then return "." end -- Root label
|
|
while len > 0 do
|
|
if len >= 0xc0 then -- name is "compressed"
|
|
pointers = pointers + 1;
|
|
if pointers >= 20 then error('dns error: 20 pointers'); end;
|
|
local offset = ((len-0xc0)*0x100) + self:byte();
|
|
remember = remember or self.offset;
|
|
self.offset = offset + 1; -- +1 for lua
|
|
else -- name is not compressed
|
|
append(n, self:sub(len)..'.');
|
|
end
|
|
len = self:byte();
|
|
end
|
|
self.offset = remember or self.offset;
|
|
return table.concat(n);
|
|
end
|
|
|
|
|
|
function resolver:question() -- - - - - - - - - - - - - - - - - - question
|
|
local q = {};
|
|
q.name = self:name();
|
|
q.type = dns.type[self:word()];
|
|
q.class = dns.class[self:word()];
|
|
return q;
|
|
end
|
|
|
|
|
|
function resolver:A(rr) -- - - - - - - - - - - - - - - - - - - - - - - - A
|
|
local b1, b2, b3, b4 = self:byte(4);
|
|
rr.a = string.format('%i.%i.%i.%i', b1, b2, b3, b4);
|
|
end
|
|
|
|
if have_util_net and util_net.ntop then
|
|
function resolver:A(rr)
|
|
rr.a = util_net.ntop(self:sub(4));
|
|
end
|
|
end
|
|
|
|
function resolver:AAAA(rr)
|
|
local addr = {};
|
|
for _ = 1, rr.rdlength, 2 do
|
|
local b1, b2 = self:byte(2);
|
|
table.insert(addr, ("%02x%02x"):format(b1, b2));
|
|
end
|
|
addr = table.concat(addr, ":"):gsub("%f[%x]0+(%x)","%1");
|
|
local zeros = {};
|
|
for item in addr:gmatch(":[0:]+:[0:]+:") do
|
|
table.insert(zeros, item)
|
|
end
|
|
if #zeros == 0 then
|
|
rr.aaaa = addr;
|
|
return
|
|
elseif #zeros > 1 then
|
|
table.sort(zeros, function(a, b) return #a > #b end);
|
|
end
|
|
rr.aaaa = addr:gsub(zeros[1], "::", 1):gsub("^0::", "::"):gsub("::0$", "::");
|
|
end
|
|
|
|
if have_util_net and util_net.ntop then
|
|
function resolver:AAAA(rr)
|
|
rr.aaaa = util_net.ntop(self:sub(16));
|
|
end
|
|
end
|
|
|
|
function resolver:CNAME(rr) -- - - - - - - - - - - - - - - - - - - - CNAME
|
|
rr.cname = self:name();
|
|
end
|
|
|
|
|
|
function resolver:MX(rr) -- - - - - - - - - - - - - - - - - - - - - - - MX
|
|
rr.pref = self:word();
|
|
rr.mx = self:name();
|
|
end
|
|
|
|
|
|
function resolver:LOC_nibble_power() -- - - - - - - - - - LOC_nibble_power
|
|
local b = self:byte();
|
|
--print('nibbles', ((b-(b%0x10))/0x10), (b%0x10));
|
|
return ((b-(b%0x10))/0x10) * (10^(b%0x10));
|
|
end
|
|
|
|
|
|
function resolver:LOC(rr) -- - - - - - - - - - - - - - - - - - - - - - LOC
|
|
rr.version = self:byte();
|
|
if rr.version == 0 then
|
|
rr.loc = rr.loc or {};
|
|
rr.loc.size = self:LOC_nibble_power();
|
|
rr.loc.horiz_pre = self:LOC_nibble_power();
|
|
rr.loc.vert_pre = self:LOC_nibble_power();
|
|
rr.loc.latitude = self:dword();
|
|
rr.loc.longitude = self:dword();
|
|
rr.loc.altitude = self:dword();
|
|
end
|
|
end
|
|
|
|
|
|
local function LOC_tostring_degrees(f, pos, neg) -- - - - - - - - - - - - -
|
|
f = f - 0x80000000;
|
|
if f < 0 then pos = neg; f = -f; end
|
|
local deg, min, msec;
|
|
msec = f%60000;
|
|
f = (f-msec)/60000;
|
|
min = f%60;
|
|
deg = (f-min)/60;
|
|
return string.format('%3d %2d %2.3f %s', deg, min, msec/1000, pos);
|
|
end
|
|
|
|
|
|
function resolver.LOC_tostring(rr) -- - - - - - - - - - - - - LOC_tostring
|
|
local t = {};
|
|
|
|
--[[
|
|
for k,name in pairs { 'size', 'horiz_pre', 'vert_pre', 'latitude', 'longitude', 'altitude' } do
|
|
append(t, string.format('%4s%-10s: %12.0f\n', '', name, rr.loc[name]));
|
|
end
|
|
--]]
|
|
|
|
append(t, string.format(
|
|
'%s %s %.2fm %.2fm %.2fm %.2fm',
|
|
LOC_tostring_degrees (rr.loc.latitude, 'N', 'S'),
|
|
LOC_tostring_degrees (rr.loc.longitude, 'E', 'W'),
|
|
(rr.loc.altitude - 10000000) / 100,
|
|
rr.loc.size / 100,
|
|
rr.loc.horiz_pre / 100,
|
|
rr.loc.vert_pre / 100
|
|
));
|
|
|
|
return table.concat(t);
|
|
end
|
|
|
|
|
|
function resolver:NS(rr) -- - - - - - - - - - - - - - - - - - - - - - - NS
|
|
rr.ns = self:name();
|
|
end
|
|
|
|
|
|
function resolver:SOA(rr) -- - - - - - - - - - - - - - - - - - - - - - SOA
|
|
end
|
|
|
|
|
|
function resolver:SRV(rr) -- - - - - - - - - - - - - - - - - - - - - - SRV
|
|
rr.srv = {};
|
|
rr.srv.priority = self:word();
|
|
rr.srv.weight = self:word();
|
|
rr.srv.port = self:word();
|
|
rr.srv.target = self:name();
|
|
end
|
|
|
|
function resolver:PTR(rr)
|
|
rr.ptr = self:name();
|
|
end
|
|
|
|
function resolver:TXT(rr) -- - - - - - - - - - - - - - - - - - - - - - TXT
|
|
rr.txt = self:sub (self:byte());
|
|
end
|
|
|
|
|
|
function resolver:rr() -- - - - - - - - - - - - - - - - - - - - - - - - rr
|
|
local rr = {};
|
|
setmetatable(rr, rr_metatable);
|
|
rr.name = self:name(self);
|
|
rr.type = dns.type[self:word()] or rr.type;
|
|
rr.class = dns.class[self:word()] or rr.class;
|
|
rr.ttl = 0x10000*self:word() + self:word();
|
|
rr.rdlength = self:word();
|
|
|
|
rr.tod = self.time + math.max(rr.ttl, 1);
|
|
|
|
local remember = self.offset;
|
|
local rr_parser = self[dns.type[rr.type]];
|
|
if rr_parser then rr_parser(self, rr); end
|
|
self.offset = remember;
|
|
rr.rdata = self:sub(rr.rdlength);
|
|
return rr;
|
|
end
|
|
|
|
|
|
function resolver:rrs (count) -- - - - - - - - - - - - - - - - - - - - - rrs
|
|
local rrs = {};
|
|
for _ = 1, count do append(rrs, self:rr()); end
|
|
return rrs;
|
|
end
|
|
|
|
|
|
function resolver:decode(packet, force) -- - - - - - - - - - - - - - decode
|
|
self.packet, self.offset = packet, 1;
|
|
local header = self:header(force);
|
|
if not header then return nil; end
|
|
local response = { header = header };
|
|
|
|
response.question = {};
|
|
local offset = self.offset;
|
|
for _ = 1, response.header.qdcount do
|
|
append(response.question, self:question());
|
|
end
|
|
response.question.raw = string.sub(self.packet, offset, self.offset - 1);
|
|
|
|
if not force then
|
|
if not self.active[response.header.id] or not self.active[response.header.id][response.question.raw] then
|
|
self.active[response.header.id] = nil;
|
|
return nil;
|
|
end
|
|
end
|
|
|
|
response.answer = self:rrs(response.header.ancount);
|
|
response.authority = self:rrs(response.header.nscount);
|
|
response.additional = self:rrs(response.header.arcount);
|
|
|
|
return response;
|
|
end
|
|
|
|
|
|
-- socket layer -------------------------------------------------- socket layer
|
|
|
|
|
|
resolver.delays = { 1, 2, 3, 5 };
|
|
|
|
resolver.jitter = have_timer and default_jitter or nil;
|
|
resolver.retry_jitter = have_timer and default_retry_jitter or nil;
|
|
|
|
function resolver:addnameserver(address) -- - - - - - - - - - addnameserver
|
|
self.server = self.server or {};
|
|
append(self.server, address);
|
|
end
|
|
|
|
|
|
function resolver:setnameserver(address) -- - - - - - - - - - setnameserver
|
|
self.server = {};
|
|
self:addnameserver(address);
|
|
end
|
|
|
|
|
|
function resolver:adddefaultnameservers() -- - - - - adddefaultnameservers
|
|
if is_windows then
|
|
if windows and windows.get_nameservers then
|
|
for _, server in ipairs(windows.get_nameservers()) do
|
|
self:addnameserver(server);
|
|
end
|
|
end
|
|
if not self.server or #self.server == 0 then
|
|
-- TODO log warning about no nameservers, adding opendns servers as fallback
|
|
self:addnameserver("208.67.222.222");
|
|
self:addnameserver("208.67.220.220");
|
|
end
|
|
else -- posix
|
|
local resolv_conf = io.open("/etc/resolv.conf");
|
|
if resolv_conf then
|
|
for line in resolv_conf:lines() do
|
|
line = line:gsub("#.*$", "")
|
|
:match('^%s*nameserver%s+([%x:%.]*%%?%S*)%s*$');
|
|
if line then
|
|
local ip = new_ip(line);
|
|
if ip then
|
|
self:addnameserver(ip.addr);
|
|
end
|
|
end
|
|
end
|
|
resolv_conf:close();
|
|
end
|
|
if not self.server or #self.server == 0 then
|
|
-- TODO log warning about no nameservers, adding localhost as the default nameserver
|
|
self:addnameserver("127.0.0.1");
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function resolver:getsocket(servernum) -- - - - - - - - - - - - - getsocket
|
|
self.socket = self.socket or {};
|
|
self.socketset = self.socketset or {};
|
|
|
|
local sock = self.socket[servernum];
|
|
if sock then return sock; end
|
|
|
|
local ok, err;
|
|
local peer = self.server[servernum];
|
|
if peer:find(":") then
|
|
sock, err = socket.udp6();
|
|
else
|
|
sock, err = (socket.udp4 or socket.udp)();
|
|
end
|
|
if sock and self.socket_wrapper then sock, err = self.socket_wrapper(sock, self); end
|
|
if not sock then
|
|
return nil, err;
|
|
end
|
|
sock:settimeout(0);
|
|
-- todo: attempt to use a random port, fallback to 0
|
|
self.socket[servernum] = sock;
|
|
self.socketset[sock] = servernum;
|
|
-- set{sock,peer}name can fail, eg because of local routing table
|
|
-- if so, try the next server
|
|
ok, err = sock:setsockname('*', 0);
|
|
if not ok then return self:servfail(sock, err); end
|
|
ok, err = sock:setpeername(peer, 53);
|
|
if not ok then return self:servfail(sock, err); end
|
|
return sock;
|
|
end
|
|
|
|
function resolver:voidsocket(sock)
|
|
if self.socket[sock] then
|
|
self.socketset[self.socket[sock]] = nil;
|
|
self.socket[sock] = nil;
|
|
elseif self.socketset[sock] then
|
|
self.socket[self.socketset[sock]] = nil;
|
|
self.socketset[sock] = nil;
|
|
end
|
|
sock:close();
|
|
end
|
|
|
|
function resolver:socket_wrapper_set(func) -- - - - - - - socket_wrapper_set
|
|
self.socket_wrapper = func;
|
|
end
|
|
|
|
|
|
function resolver:closeall () -- - - - - - - - - - - - - - - - - - closeall
|
|
for i,sock in ipairs(self.socket) do
|
|
self.socket[i] = nil;
|
|
self.socketset[sock] = nil;
|
|
sock:close();
|
|
end
|
|
end
|
|
|
|
|
|
function resolver:remember(rr, type) -- - - - - - - - - - - - - - remember
|
|
--print ('remember', type, rr.class, rr.type, rr.name)
|
|
local qname, qtype, qclass = standardize(rr.name, rr.type, rr.class);
|
|
|
|
if type ~= '*' then
|
|
type = qtype;
|
|
local all = get(self.cache, qclass, '*', qname);
|
|
--print('remember all', all);
|
|
if all then append(all, rr); end
|
|
end
|
|
|
|
self.cache = self.cache or setmetatable({}, cache_metatable);
|
|
local rrs = get(self.cache, qclass, type, qname) or
|
|
set(self.cache, qclass, type, qname, setmetatable({}, rrs_metatable));
|
|
if rr[qtype:lower()] and not rrs[rr[qtype:lower()]] then
|
|
rrs[rr[qtype:lower()]] = true;
|
|
append(rrs, rr);
|
|
end
|
|
|
|
if type == 'MX' then self.unsorted[rrs] = true; end
|
|
end
|
|
|
|
|
|
local function comp_mx(a, b) -- - - - - - - - - - - - - - - - - - - comp_mx
|
|
return (a.pref == b.pref) and (a.mx < b.mx) or (a.pref < b.pref);
|
|
end
|
|
|
|
|
|
function resolver:peek (qname, qtype, qclass, n) -- - - - - - - - - - - - peek
|
|
qname, qtype, qclass = standardize(qname, qtype, qclass);
|
|
local rrs = get(self.cache, qclass, qtype, qname);
|
|
if not rrs then
|
|
if n then if n <= 0 then return end else n = 3 end
|
|
rrs = get(self.cache, qclass, "CNAME", qname);
|
|
if not (rrs and rrs[1]) then return end
|
|
return self:peek(rrs[1].cname, qtype, qclass, n - 1);
|
|
end
|
|
if prune(rrs, socket.gettime()) and qtype == '*' or not next(rrs) then
|
|
set(self.cache, qclass, qtype, qname, nil);
|
|
return nil;
|
|
end
|
|
if self.unsorted[rrs] then table.sort (rrs, comp_mx); self.unsorted[rrs] = nil; end
|
|
return rrs;
|
|
end
|
|
|
|
|
|
function resolver:purge(soft) -- - - - - - - - - - - - - - - - - - - purge
|
|
if soft == 'soft' then
|
|
self.time = socket.gettime();
|
|
for class,types in pairs(self.cache or {}) do
|
|
for type,names in pairs(types) do
|
|
for name,rrs in pairs(names) do
|
|
prune(rrs, self.time, 'soft')
|
|
end
|
|
end
|
|
end
|
|
else self.cache = setmetatable({}, cache_metatable); end
|
|
end
|
|
|
|
|
|
function resolver:query(qname, qtype, qclass) -- - - - - - - - - - -- query
|
|
qname, qtype, qclass = standardize(qname, qtype, qclass)
|
|
|
|
local co = coroutine.running();
|
|
local q = get(self.wanted, qclass, qtype, qname);
|
|
if co and q then
|
|
-- We are already waiting for a reply to an identical query.
|
|
set(self.wanted, qclass, qtype, qname, co, true);
|
|
return true;
|
|
end
|
|
|
|
if not self.server then self:adddefaultnameservers(); end
|
|
|
|
local question = encodeQuestion(qname, qtype, qclass);
|
|
local peek = self:peek (qname, qtype, qclass);
|
|
if peek then return peek; end
|
|
|
|
local header, id = encodeHeader();
|
|
--print ('query id', id, qclass, qtype, qname)
|
|
local o = {
|
|
packet = header..question,
|
|
server = self.best_server,
|
|
delay = 1,
|
|
retry = socket.gettime() + self.delays[1];
|
|
qclass = qclass;
|
|
qtype = qtype;
|
|
qname = qname;
|
|
};
|
|
|
|
-- remember the query
|
|
self.active[id] = self.active[id] or {};
|
|
self.active[id][question] = o;
|
|
|
|
local conn, err = self:getsocket(o.server)
|
|
if not conn then
|
|
return nil, err;
|
|
end
|
|
if self.jitter then
|
|
timer.add_task(math.random()*self.jitter, function ()
|
|
conn:send(o.packet);
|
|
end);
|
|
else
|
|
conn:send(o.packet);
|
|
end
|
|
|
|
-- remember which coroutine wants the answer
|
|
if co then
|
|
set(self.wanted, qclass, qtype, qname, co, true);
|
|
end
|
|
|
|
if have_timer and self.timeout then
|
|
local num_servers = #self.server;
|
|
local i = 1;
|
|
timer.add_task(self.timeout, function ()
|
|
if get(self.wanted, qclass, qtype, qname, co) then
|
|
log("debug", "DNS request timeout %d/%d", i, num_servers)
|
|
i = i + 1;
|
|
self:servfail(self.socket[o.server]);
|
|
-- end
|
|
end
|
|
-- Still outstanding? (i.e. retried)
|
|
if get(self.wanted, qclass, qtype, qname, co) then
|
|
return self.timeout; -- Then wait
|
|
end
|
|
end)
|
|
end
|
|
return true;
|
|
end
|
|
|
|
function resolver:servfail(sock, err)
|
|
-- Resend all queries for this server
|
|
|
|
local num = self.socketset[sock]
|
|
|
|
-- Socket is dead now
|
|
sock = self:voidsocket(sock);
|
|
|
|
-- Find all requests to the down server, and retry on the next server
|
|
self.time = socket.gettime();
|
|
log("debug", "servfail %d (of %d)", num, #self.server);
|
|
for id,queries in pairs(self.active) do
|
|
for question,o in pairs(queries) do
|
|
if o.server == num then -- This request was to the broken server
|
|
o.server = o.server + 1 -- Use next server
|
|
if o.server > #self.server then
|
|
o.server = 1;
|
|
end
|
|
|
|
o.retries = (o.retries or 0) + 1;
|
|
local retried;
|
|
if o.retries < #self.server then
|
|
sock, err = self:getsocket(o.server);
|
|
if sock then
|
|
retried = true;
|
|
if self.retry_jitter then
|
|
local delay = self.delays[((o.retries-1)%#self.delays)+1] + (math.random()*self.retry_jitter);
|
|
log("debug", "retry %d in %0.2fs", o.retries, delay);
|
|
timer.add_task(delay, function ()
|
|
sock:send(o.packet);
|
|
end);
|
|
else
|
|
log("debug", "retry %d (immediate)", o.retries);
|
|
sock:send(o.packet);
|
|
end
|
|
end
|
|
end
|
|
if not retried then
|
|
log("debug", 'tried all servers, giving up');
|
|
self:cancel(o.qclass, o.qtype, o.qname);
|
|
queries[question] = nil;
|
|
end
|
|
end
|
|
end
|
|
if next(queries) == nil then
|
|
self.active[id] = nil;
|
|
end
|
|
end
|
|
|
|
if num == self.best_server then
|
|
self.best_server = self.best_server + 1;
|
|
if self.best_server > #self.server then
|
|
-- Exhausted all servers, try first again
|
|
self.best_server = 1;
|
|
end
|
|
end
|
|
return sock, err;
|
|
end
|
|
|
|
function resolver:settimeout(seconds)
|
|
self.timeout = seconds;
|
|
end
|
|
|
|
function resolver:receive(rset) -- - - - - - - - - - - - - - - - - receive
|
|
--print('receive'); print(self.socket);
|
|
self.time = socket.gettime();
|
|
rset = rset or self.socket;
|
|
|
|
local response;
|
|
for _, sock in pairs(rset) do
|
|
|
|
if self.socketset[sock] then
|
|
local packet = sock:receive();
|
|
if packet then
|
|
response = self:decode(packet);
|
|
if response and self.active[response.header.id]
|
|
and self.active[response.header.id][response.question.raw] then
|
|
--print('received response');
|
|
--self.print(response);
|
|
|
|
for _, rr in pairs(response.answer) do
|
|
if rr.name:sub(-#response.question[1].name, -1) == response.question[1].name then
|
|
self:remember(rr, response.question[1].type)
|
|
end
|
|
end
|
|
|
|
-- retire the query
|
|
local queries = self.active[response.header.id];
|
|
queries[response.question.raw] = nil;
|
|
|
|
if not next(queries) then self.active[response.header.id] = nil; end
|
|
if not next(self.active) then self:closeall(); end
|
|
|
|
-- was the query on the wanted list?
|
|
local q = response.question[1];
|
|
local cos = get(self.wanted, q.class, q.type, q.name);
|
|
if cos then
|
|
for co in pairs(cos) do
|
|
if coroutine.status(co) == "suspended" then coroutine.resume(co); end
|
|
end
|
|
set(self.wanted, q.class, q.type, q.name, nil);
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
return response;
|
|
end
|
|
|
|
|
|
function resolver:feed(sock, packet, force)
|
|
--print('receive'); print(self.socket);
|
|
self.time = socket.gettime();
|
|
|
|
local response = self:decode(packet, force);
|
|
if response and self.active[response.header.id]
|
|
and self.active[response.header.id][response.question.raw] then
|
|
--print('received response');
|
|
--self.print(response);
|
|
|
|
for _, rr in pairs(response.answer) do
|
|
self:remember(rr, rr.type);
|
|
end
|
|
|
|
for _, rr in pairs(response.additional) do
|
|
self:remember(rr, rr.type);
|
|
end
|
|
|
|
-- retire the query
|
|
local queries = self.active[response.header.id];
|
|
queries[response.question.raw] = nil;
|
|
if not next(queries) then self.active[response.header.id] = nil; end
|
|
if not next(self.active) then self:closeall(); end
|
|
|
|
-- was the query on the wanted list?
|
|
local q = response.question[1];
|
|
if q then
|
|
local cos = get(self.wanted, q.class, q.type, q.name);
|
|
if cos then
|
|
for co in pairs(cos) do
|
|
if coroutine.status(co) == "suspended" then coroutine.resume(co); end
|
|
end
|
|
set(self.wanted, q.class, q.type, q.name, nil);
|
|
end
|
|
end
|
|
end
|
|
|
|
return response;
|
|
end
|
|
|
|
function resolver:cancel(qclass, qtype, qname)
|
|
local cos = get(self.wanted, qclass, qtype, qname);
|
|
if cos then
|
|
for co in pairs(cos) do
|
|
if coroutine.status(co) == "suspended" then coroutine.resume(co); end
|
|
end
|
|
set(self.wanted, qclass, qtype, qname, nil);
|
|
end
|
|
end
|
|
|
|
function resolver:pulse() -- - - - - - - - - - - - - - - - - - - - - pulse
|
|
--print(':pulse');
|
|
while self:receive() do end
|
|
if not next(self.active) then return nil; end
|
|
|
|
self.time = socket.gettime();
|
|
for id,queries in pairs(self.active) do
|
|
for question,o in pairs(queries) do
|
|
if self.time >= o.retry then
|
|
|
|
o.server = o.server + 1;
|
|
if o.server > #self.server then
|
|
o.server = 1;
|
|
o.delay = o.delay + 1;
|
|
end
|
|
|
|
if o.delay > #self.delays then
|
|
--print('timeout');
|
|
queries[question] = nil;
|
|
if not next(queries) then self.active[id] = nil; end
|
|
if not next(self.active) then return nil; end
|
|
else
|
|
--print('retry', o.server, o.delay);
|
|
local _a = self.socket[o.server];
|
|
if _a then _a:send(o.packet); end
|
|
o.retry = self.time + self.delays[o.delay];
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if next(self.active) then return true; end
|
|
return nil;
|
|
end
|
|
|
|
|
|
function resolver:lookup(qname, qtype, qclass) -- - - - - - - - - - lookup
|
|
self:query (qname, qtype, qclass)
|
|
while self:pulse() do
|
|
local recvt = {}
|
|
for i, s in ipairs(self.socket) do
|
|
recvt[i] = s
|
|
end
|
|
socket.select(recvt, nil, 4)
|
|
end
|
|
--print(self.cache);
|
|
return self:peek(qname, qtype, qclass);
|
|
end
|
|
|
|
function resolver:lookupex(handler, qname, qtype, qclass) -- - - - - - - - - - lookup
|
|
return self:peek(qname, qtype, qclass) or self:query(qname, qtype, qclass);
|
|
end
|
|
|
|
function resolver:tohostname(ip)
|
|
return dns.lookup(ip:gsub("(%d+)%.(%d+)%.(%d+)%.(%d+)", "%4.%3.%2.%1.in-addr.arpa."), "PTR");
|
|
end
|
|
|
|
--print ---------------------------------------------------------------- print
|
|
|
|
|
|
local hints = { -- - - - - - - - - - - - - - - - - - - - - - - - - - - hints
|
|
qr = { [0]='query', 'response' },
|
|
opcode = { [0]='query', 'inverse query', 'server status request' },
|
|
aa = { [0]='non-authoritative', 'authoritative' },
|
|
tc = { [0]='complete', 'truncated' },
|
|
rd = { [0]='recursion not desired', 'recursion desired' },
|
|
ra = { [0]='recursion not available', 'recursion available' },
|
|
z = { [0]='(reserved)' },
|
|
rcode = { [0]='no error', 'format error', 'server failure', 'name error', 'not implemented' },
|
|
|
|
type = dns.type,
|
|
class = dns.class
|
|
};
|
|
|
|
|
|
local function hint(p, s) -- - - - - - - - - - - - - - - - - - - - - - hint
|
|
return (hints[s] and hints[s][p[s]]) or '';
|
|
end
|
|
|
|
|
|
function resolver.print(response) -- - - - - - - - - - - - - resolver.print
|
|
for _, s in pairs { 'id', 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z',
|
|
'rcode', 'qdcount', 'ancount', 'nscount', 'arcount' } do
|
|
print( string.format('%-30s', 'header.'..s), response.header[s], hint(response.header, s) );
|
|
end
|
|
|
|
for i,question in ipairs(response.question) do
|
|
print(string.format ('question[%i].name ', i), question.name);
|
|
print(string.format ('question[%i].type ', i), question.type);
|
|
print(string.format ('question[%i].class ', i), question.class);
|
|
end
|
|
|
|
local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 };
|
|
local tmp;
|
|
for _, s in pairs({'answer', 'authority', 'additional'}) do
|
|
for i,rr in pairs(response[s]) do
|
|
for _, t in pairs({ 'name', 'type', 'class', 'ttl', 'rdlength' }) do
|
|
tmp = string.format('%s[%i].%s', s, i, t);
|
|
print(string.format('%-30s', tmp), rr[t], hint(rr, t));
|
|
end
|
|
for j,t in pairs(rr) do
|
|
if not common[j] then
|
|
tmp = string.format('%s[%i].%s', s, i, j);
|
|
print(string.format('%-30s %s', tostring(tmp), tostring(t)));
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- module api ------------------------------------------------------ module api
|
|
|
|
|
|
function dns.resolver () -- - - - - - - - - - - - - - - - - - - - - resolver
|
|
local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, best_server = 1 };
|
|
setmetatable (r, resolver);
|
|
setmetatable (r.cache, cache_metatable);
|
|
setmetatable (r.unsorted, { __mode = 'kv' });
|
|
return r;
|
|
end
|
|
|
|
local _resolver = dns.resolver();
|
|
dns._resolver = _resolver;
|
|
_resolver.jitter, _resolver.retry_jitter = false, false;
|
|
|
|
function dns.lookup(...) -- - - - - - - - - - - - - - - - - - - - - lookup
|
|
return _resolver:lookup(...);
|
|
end
|
|
|
|
function dns.tohostname(...)
|
|
return _resolver:tohostname(...);
|
|
end
|
|
|
|
function dns.purge(...) -- - - - - - - - - - - - - - - - - - - - - - purge
|
|
return _resolver:purge(...);
|
|
end
|
|
|
|
function dns.peek(...) -- - - - - - - - - - - - - - - - - - - - - - - peek
|
|
return _resolver:peek(...);
|
|
end
|
|
|
|
function dns.query(...) -- - - - - - - - - - - - - - - - - - - - - - query
|
|
return _resolver:query(...);
|
|
end
|
|
|
|
function dns.feed(...) -- - - - - - - - - - - - - - - - - - - - - - - feed
|
|
return _resolver:feed(...);
|
|
end
|
|
|
|
function dns.cancel(...) -- - - - - - - - - - - - - - - - - - - - - - cancel
|
|
return _resolver:cancel(...);
|
|
end
|
|
|
|
function dns.settimeout(...)
|
|
return _resolver:settimeout(...);
|
|
end
|
|
|
|
function dns.cache()
|
|
return _resolver.cache;
|
|
end
|
|
|
|
function dns.socket_wrapper_set(...) -- - - - - - - - - socket_wrapper_set
|
|
return _resolver:socket_wrapper_set(...);
|
|
end
|
|
|
|
return dns;
|