-- This file is included with Prosody IM. It has modifications,
-- which are hereby placed in the public domain.
--- public domain 20080404 lua@ztact.com
-
-- todo: quick (default) header generation
-- todo: nxdomain, error handling
local socket = require "socket";
-local ztact = require "util.ztact";
+local timer = require "util.timer";
+
local _, windows = pcall(require, "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, unpack =
- ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack;
+local ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack, select, type=
+ ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack, 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 = 15;
-------------------------------------------------- module dns
module('dns')
local resolver = {};
resolver.__index = resolver;
+resolver.timeout = default_timeout;
local SRV_tostring;
+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 = SRV_tostring;
+};
local rr_metatable = {}; -- - - - - - - - - - - - - - - - - - - rr_metatable
function rr_metatable.__tostring(rr)
- local s0 = string.format('%2s %-5s %6i %-28s', rr.class, rr.type, rr.ttl, rr.name);
- local s1 = '';
- if rr.type == 'A' then
- s1 = ' '..rr.a;
- elseif rr.type == 'MX' then
- s1 = string.format(' %2i %s', rr.pref, rr.mx);
- elseif rr.type == 'CNAME' then
- s1 = ' '..rr.cname;
- elseif rr.type == 'LOC' then
- s1 = ' '..resolver.LOC_tostring(rr);
- elseif rr.type == 'NS' then
- s1 = ' '..rr.ns;
- elseif rr.type == 'SRV' then
- s1 = ' '..SRV_tostring(rr);
- elseif rr.type == 'TXT' then
- s1 = ' '..rr.txt;
- else
- s1 = ' <UNKNOWN RDATA TYPE>';
- end
- return s0..s1;
+ 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
function dns.random(...) -- - - - - - - - - - - - - - - - - - - dns.random
- math.randomseed(10000*socket.gettime());
+ math.randomseed(math.floor(10000*socket.gettime()));
dns.random = math.random;
return dns.random(...);
end
rr.srv.target = self:name();
end
+function resolver:PTR(rr)
+ rr.ptr = self:name();
+end
function SRV_tostring(rr) -- - - - - - - - - - - - - - - - - - SRV_tostring
local s = rr.srv;
function resolver:adddefaultnameservers() -- - - - - adddefaultnameservers
if is_windows then
- if windows then
+ if windows and windows.get_nameservers then
for _, server in ipairs(windows.get_nameservers()) do
self:addnameserver(server);
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") ;
+ 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
- local address = line:gsub("#.*$", ""):match('^%s*nameserver%s+(%d+%.%d+%.%d+%.%d+)%s*$');
- if address then self:addnameserver(address) end
+ line = line:gsub("#.*$", "")
+ :match('^%s*nameserver%s+(.*)%s*$');
+ if line then
+ line:gsub("%f[%d.](%d+%.%d+%.%d+%.%d+)%f[^%d.]", function (address)
+ self:addnameserver(address)
+ end);
+ end
end
end
if not self.server or #self.server == 0 then
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 = rr.type;
- local all = get(self.cache, rr.class, '*', rr.name);
+ 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, rr.class, type, rr.name) or
- set(self.cache, rr.class, type, rr.name, setmetatable({}, rrs_metatable));
+ local rrs = get(self.cache, qclass, type, qname) or
+ set(self.cache, qclass, type, qname, setmetatable({}, rrs_metatable));
append(rrs, rr);
if type == 'MX' then self.unsorted[rrs] = true; end
retry = socket.gettime() + self.delays[1]
};
- -- remember the query
+ -- remember the query
self.active[id] = self.active[id] or {};
self.active[id][question] = o;
- -- remember which coroutine wants the answer
+ -- remember which coroutine wants the answer
local co = coroutine.running();
if co then
set(self.wanted, qclass, qtype, qname, co, true);
--set(self.yielded, co, qclass, qtype, qname, true);
end
- self:getsocket (o.server):send (o.packet)
+ local conn = self:getsocket(o.server)
+ conn:send (o.packet)
+
+ if 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
+ if i < num_servers then
+ i = i + 1;
+ self:servfail(conn);
+ o.server = self.best_server;
+ conn = self:getsocket(o.server);
+ conn:send(o.packet);
+ return self.timeout;
+ else
+ -- Tried everything, failed
+ self:cancel(qclass, qtype, qname, co, true);
+ end
+ end
+ end)
+ end
end
function resolver:servfail(sock)
end
end
end
-
+
if num == self.best_server then
self.best_server = self.best_server + 1;
if self.best_server > #self.server then
end
end
+function resolver:settimeout(seconds)
+ self.timeout = seconds;
+end
+
function resolver:receive(rset) -- - - - - - - - - - - - - - - - - receive
--print('receive'); print(self.socket);
self.time = socket.gettime();
local packet = sock:receive();
if packet then
response = self:decode(packet);
- if response then
+ 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 i,section in pairs({ 'answer', 'authority', 'additional' }) do
- for j,rr in pairs(response[section]) do
+ for j,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];
- if queries[response.question.raw] then
- queries[response.question.raw] = nil;
- end
+ 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;
+ 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
end
-function resolver:feed(sock, packet)
+function resolver:feed(sock, packet, force)
--print('receive'); print(self.socket);
self.time = socket.gettime();
- local response = self:decode(packet);
- if response then
+ 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 i,section in pairs({ 'answer', 'authority', 'additional' }) do
- for j,rr in pairs(response[section]) do
- self:remember(rr, response.question[1].type);
- end
+ for j,rr in pairs(response.answer) do
+ self:remember(rr, response.question[1].type);
end
-- retire the query
local queries = self.active[response.header.id];
- if queries[response.question.raw] then
- queries[response.question.raw] = nil;
- end
+ 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
set(self.wanted, q.class, q.type, q.name, nil);
end
end
- end
+ end
return response;
end
-function resolver:cancel(data)
- local cos = get(self.wanted, unpack(data, 1, 3));
+function resolver:cancel(qclass, qtype, qname, co, call_handler)
+ local cos = get(self.wanted, qclass, qtype, qname);
if cos then
- cos[data[4]] = nil;
+ if call_handler then
+ coroutine.resume(co);
+ end
+ cos[co] = nil;
end
end
function resolver:lookup(qname, qtype, qclass) -- - - - - - - - - - lookup
self:query (qname, qtype, qclass)
- while self:pulse() do socket.select(self.socket, nil, 4); end
+ 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
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
-- module api ------------------------------------------------------ module api
-local function resolve(func, ...) -- - - - - - - - - - - - - - resolver_get
- return func(dns._resolver, ...);
-end
-
-
function dns.resolver () -- - - - - - - - - - - - - - - - - - - - - resolver
-- this function seems to be redundant with resolver.new ()
return r;
end
+local _resolver = dns.resolver();
+dns._resolver = _resolver;
function dns.lookup(...) -- - - - - - - - - - - - - - - - - - - - - lookup
- return resolve(resolver.lookup, ...);
+ return _resolver:lookup(...);
end
+function dns.tohostname(...)
+ return _resolver:tohostname(...);
+end
function dns.purge(...) -- - - - - - - - - - - - - - - - - - - - - - purge
- return resolve(resolver.purge, ...);
+ return _resolver:purge(...);
end
function dns.peek(...) -- - - - - - - - - - - - - - - - - - - - - - - peek
- return resolve(resolver.peek, ...);
+ return _resolver:peek(...);
end
-
function dns.query(...) -- - - - - - - - - - - - - - - - - - - - - - query
- return resolve(resolver.query, ...);
+ return _resolver:query(...);
end
-function dns.feed(...) -- - - - - - - - - - - - - - - - - - - - - - feed
- return resolve(resolver.feed, ...);
+function dns.feed(...) -- - - - - - - - - - - - - - - - - - - - - - - feed
+ return _resolver:feed(...);
end
-function dns.cancel(...) -- - - - - - - - - - - - - - - - - - - - - - cancel
- return resolve(resolver.cancel, ...);
+function dns.cancel(...) -- - - - - - - - - - - - - - - - - - - - - - cancel
+ return _resolver:cancel(...);
end
-function dns:socket_wrapper_set(...) -- - - - - - - - - socket_wrapper_set
- return resolve(resolver.socket_wrapper_set, ...);
+function dns.settimeout(...)
+ return _resolver:settimeout(...);
end
-dns._resolver = dns.resolver();
+function dns.socket_wrapper_set(...) -- - - - - - - - - socket_wrapper_set
+ return _resolver:socket_wrapper_set(...);
+end
return dns;