net.server_select: Set select() timeout to 3600 by default.
[prosody.git] / net / dns.lua
index a1bdb7d799b92963f700e33bf46ab58a0d74edfd..c5f219a8f0a5b07c7e9831ae07a0018331afd0e7 100644 (file)
 -- reference: http://tools.ietf.org/html/rfc1876 (LOC)
 
 
-require 'socket'
-local ztact = require 'util.ztact'
-local require = require
-local os = os;
+local socket = require "socket";
+local ztact = require "util.ztact";
+local timer = require "util.timer";
 
-local coroutine, io, math, socket, string, table =
-      coroutine, io, math, socket, string, table
+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
+      ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack;
 
-local get, set = ztact.get, ztact.set
+local get, set = ztact.get, ztact.set;
 
+local default_timeout = 15;
 
 -------------------------------------------------- module dns
-module ('dns')
+module('dns')
 local dns = _M;
 
 
@@ -39,884 +42,966 @@ local dns = _M;
 local append = table.insert
 
 
-local function highbyte (i)    -- - - - - - - - - - - - - - - - - - -  highbyte
-  return (i-(i%0x100))/0x100
-  end
+local function highbyte(i)    -- - - - - - - - - - - - - - - - - - -  highbyte
+       return (i-(i%0x100))/0x100;
+end
 
 
 local function augment (t)    -- - - - - - - - - - - - - - - - - - - -  augment
-  local a = {}
-  for i,s in pairs (t) do  a[i] = s  a[s] = s  a[string.lower (s)] = s  end
-  return a
-  end
+       local a = {};
+       for i,s in pairs(t) do
+               a[i] = s;
+               a[s] = s;
+               a[string.lower(s)] = s;
+       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
+       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 = {
-  'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR', 'NULL', 'WKS',
-  'PTR', 'HINFO', 'MINFO', 'MX', 'TXT',
-  [ 28] = 'AAAA', [ 29] = 'LOC',   [ 33] = 'SRV',
-  [252] = 'AXFR', [253] = 'MAILB', [254] = 'MAILA', [255] = '*' }
-
-
-dns.classes = { 'IN', 'CS', 'CH', 'HS', [255] = '*' }
-
+       'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR', 'NULL', 'WKS',
+       'PTR', 'HINFO', 'MINFO', 'MX', 'TXT',
+       [ 28] = 'AAAA', [ 29] = 'LOC',   [ 33] = 'SRV',
+       [252] = 'AXFR', [253] = 'MAILB', [254] = 'MAILA', [255] = '*' };
 
-dns.type      = augment (dns.types)
-dns.class     = augment (dns.classes)
-dns.typecode  = encode  (dns.types)
-dns.classcode = encode  (dns.classes)
 
+dns.classes = { 'IN', 'CS', 'CH', 'HS', [255] = '*' };
 
 
-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
+dns.type      = augment (dns.types);
+dns.class     = augment (dns.classes);
+dns.typecode  = encode  (dns.types);
+dns.classcode = encode  (dns.classes);
 
 
-local function prune (rrs, time, soft)    -- - - - - - - - - - - - - - -  prune
 
-  time = time or socket.gettime ()
-  for i,rr in pairs (rrs) do
+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
 
-    if rr.tod then
-      -- rr.tod = rr.tod - 50    -- accelerated decripitude
-      rr.ttl = math.floor (rr.tod - time)
-      if rr.ttl <= 0 then
-        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[i] = nil
-      end  end  end
+local function prune(rrs, time, soft)    -- - - - - - - - - - - - - - -  prune
+       time = time or socket.gettime();
+       for i,rr in pairs(rrs) do
+               if rr.tod then
+                       -- rr.tod = rr.tod - 50    -- accelerated decripitude
+                       rr.ttl = math.floor(rr.tod - time);
+                       if rr.ttl <= 0 then
+                               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[i] = nil;
+               end
+       end
+end
 
 
 -- metatables & co. ------------------------------------------ metatables & co.
 
 
-local resolver = {}
-resolver.__index = resolver
-
-
-local 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
-  end
+local resolver = {};
+resolver.__index = resolver;
+
+resolver.timeout = default_timeout;
+
+local 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;
+end
 
 
-local rrs_metatable = {}    -- - - - - - - - - - - - - - - - - -  rrs_metatable
-function rrs_metatable.__tostring (rrs)
-  local t = {}
-  for i,rr in pairs (rrs) do  append (t, tostring (rr)..'\n')  end
-  return table.concat (t)
-  end
+local rrs_metatable = {};    -- - - - - - - - - - - - - - - - - -  rrs_metatable
+function rrs_metatable.__tostring(rrs)
+       local t = {};
+       for i,rr in pairs(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
+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
 
 
-function resolver:new ()    -- - - - - - - - - - - - - - - - - - - - - resolver
-  local r = { active = {}, cache = {}, unsorted = {} }
-  setmetatable (r, resolver)
-  setmetatable (r.cache, cache_metatable)
-  setmetatable (r.unsorted, { __mode = 'kv' })
-  return r
-  end
+function resolver:new()    -- - - - - - - - - - - - - - - - - - - - - resolver
+       local r = { active = {}, cache = {}, unsorted = {} };
+       setmetatable(r, resolver);
+       setmetatable(r.cache, cache_metatable);
+       setmetatable(r.unsorted, { __mode = 'kv' });
+       return r;
+end
 
 
 -- packet layer -------------------------------------------------- packet layer
 
 
-function dns.random (...)    -- - - - - - - - - - - - - - - - - - -  dns.random
-  math.randomseed (10000*socket.gettime ())
-  dns.random = math.random
-  return dns.random (...)
-  end
-
-
-local function encodeHeader (o)    -- - - - - - - - - - - - - - -  encodeHeader
+function dns.random(...)    -- - - - - - - - - - - - - - - - - - -  dns.random
+       math.randomseed(math.floor(10000*socket.gettime()));
+       dns.random = math.random;
+       return dns.random(...);
+end
 
-  o = o or {}
 
-  o.id = o.id or               -- 16b  (random) id
-    dns.random (0, 0xffff)
+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
+       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.qr = o.qr or 0;               --  1b  0 query, 1 response
 
-  o.rcode = o.rcode or 0       --  4b  0 no error
+       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 resvered
-  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
+       o.z = o.z  or 0;                --  3b  0 resvered
+       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
+       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: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
+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
 
-  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 = {}
-  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
+       local h = { id = id };
 
+       local b1, b2 = self:byte(2);
 
-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
+       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;
 
-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
+       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
 
-function resolver:CNAME (rr)    -- - - - - - - - - - - - - - - - - - - -  CNAME
-  rr.cname = self:name ()
-  end
+       return h;
+end
 
 
-function resolver:MX (rr)    -- - - - - - - - - - - - - - - - - - - - - - -  MX
-  rr.pref = self:word ()
-  rr.mx   = self:name ()
-  end
+function resolver:name()    -- - - - - - - - - - - - - - - - - - - - - -  name
+       local remember, pointers = nil, 0;
+       local len = self:byte();
+       local n = {};
+       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: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: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: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
+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
 
 
-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:CNAME(rr)    -- - - - - - - - - - - - - - - - - - - -  CNAME
+       rr.cname = self:name();
+end
 
-function resolver:SOA (rr)    -- - - - - - - - - - - - - - - - - - - - - -  SOA
-  end
 
+function resolver:MX(rr)    -- - - - - - - - - - - - - - - - - - - - - - -  MX
+       rr.pref = self:word();
+       rr.mx   = self:name();
+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: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 SRV_tostring (rr)    -- - - - - - - - - - - - - - - - - - SRV_tostring
-  local s = rr.srv
-  return string.format ( '%5d %5d %5d %s',
-                         s.priority, s.weight, s.port, s.target )
-  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
 
-function resolver:TXT (rr)    -- - - - - - - - - - - - - - - - - - - - - -  TXT
-  rr.txt = self:sub (rr.rdlength)
-  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: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 ()
 
-  if rr.ttl <= 0 then rr.tod = self.time;
-  else  rr.tod = self.time + rr.ttl  end
+function resolver.LOC_tostring(rr)    -- - - - - - - - - - - - -  LOC_tostring
+       local t = {};
 
-  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
+       --[[
+       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:rrs (count)    -- - - - - - - - - - - - - - - - - - - - - rrs
-  local rrs = {}
-  for i = 1,count do  append (rrs, self:rr ())  end
-  return rrs
-  end
+function resolver:NS(rr)    -- - - - - - - - - - - - - - - - - - - - - - -  NS
+       rr.ns = self:name();
+end
 
 
-function resolver:decode (packet, force)    -- - - - - - - - - - - - - - decode
+function resolver:SOA(rr)    -- - - - - - - - - - - - - - - - - - - - - -  SOA
+end
 
-  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 i = 1,response.header.qdcount do
-    append (response.question, self:question ())  end
-  response.question.raw = string.sub (self.packet, offset, self.offset - 1)
+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
 
-  if not force then
-    if not self.active[response.header.id] or
-       not self.active[response.header.id][response.question.raw] then
-      return nil  end  end
+function resolver:PTR(rr)
+       rr.ptr = self:name();
+end
 
-  response.answer     = self:rrs (response.header.ancount)
-  response.authority  = self:rrs (response.header.nscount)
-  response.additional = self:rrs (response.header.arcount)
+function SRV_tostring(rr)    -- - - - - - - - - - - - - - - - - - SRV_tostring
+       local s = rr.srv;
+       return string.format( '%5d %5d %5d %s', s.priority, s.weight, s.port, s.target );
+end
 
-  return response
-  end
 
+function resolver:TXT(rr)    -- - - - - - - - - - - - - - - - - - - - - -  TXT
+       rr.txt = self:sub (rr.rdlength);
+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();
+
+       if rr.ttl <= 0 then
+               rr.tod = self.time + 30;
+       else
+               rr.tod = self.time + rr.ttl;
+       end
+
+       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
 
--- socket layer -------------------------------------------------- socket layer
 
+function resolver:rrs (count)    -- - - - - - - - - - - - - - - - - - - - - rrs
+       local rrs = {};
+       for i = 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 i = 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
+                       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);
 
-resolver.delays = { 1, 3  }
+       return response;
+end
 
 
-function resolver:addnameserver (address)    -- - - - - - - - - - addnameserver
-  self.server = self.server or {}
-  append (self.server, address)
-  end
+-- socket layer -------------------------------------------------- socket layer
 
 
-function resolver:setnameserver (address)    -- - - - - - - - - - setnameserver
-  self.server = {}
-  self:addnameserver (address)
-  end
+resolver.delays = { 1, 3 };
 
 
-function resolver:adddefaultnameservers ()    -- - - - -  adddefaultnameservers
-  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
-         end
-  elseif os.getenv("WINDIR") then
-       self:addnameserver ("208.67.222.222")
-       self:addnameserver ("208.67.220.220")   
-  end
-  if not self.server or #self.server == 0 then
-       self:addnameserver("127.0.0.1");
-  end
+function resolver:addnameserver(address)    -- - - - - - - - - - addnameserver
+       self.server = self.server or {};
+       append(self.server, address);
 end
 
 
-function resolver:getsocket (servernum)    -- - - - - - - - - - - - - getsocket
+function resolver:setnameserver(address)    -- - - - - - - - - - setnameserver
+       self.server = {};
+       self:addnameserver(address);
+end
 
-  self.socket = self.socket or {}
-  self.socketset = self.socketset or {}
 
-  local sock = self.socket[servernum]
-  if sock then  return sock  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+(.*)%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
+                       -- TODO log warning about no nameservers, adding localhost as the default nameserver
+                       self:addnameserver("127.0.0.1");
+               end
+       end
+end
 
-  sock = socket.udp ()
-  if self.socket_wrapper then  sock = self.socket_wrapper (sock, self)  end
-  sock:settimeout (0)
-  -- todo: attempt to use a random port, fallback to 0
-  sock:setsockname ('*', 0)
-  sock:setpeername (self.server[servernum], 53)
-  self.socket[servernum] = sock
-  self.socketset[sock] = servernum
-  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
+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
+
+       sock = socket.udp();
+       if self.socket_wrapper then sock = self.socket_wrapper(sock, self); end
+       sock:settimeout(0);
+       -- todo: attempt to use a random port, fallback to 0
+       sock:setsockname('*', 0);
+       sock:setpeername(self.server[servernum], 53);
+       self.socket[servernum] = sock;
+       self.socketset[sock] = servernum;
+       return sock;
 end
 
-function resolver:socket_wrapper_set (func)  -- - - - - - - socket_wrapper_set
-  self.socket_wrapper = func
-  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
+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: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)
+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)
-    --print ('remember all', all)
-    if all then  append (all, rr)  end
-    end
+       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, rr.class, type, rr.name) or
-    set (self.cache, rr.class, type, rr.name, setmetatable ({}, rrs_metatable))
-  append (rrs, rr)
+       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));
+       append(rrs, rr);
 
-  if type == 'MX' then  self.unsorted[rrs] = true  end
-  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
+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)    -- - - - - - - - - - - -  peek
-  qname, qtype, qclass = standardize (qname, qtype, qclass)
-  local rrs = get (self.cache, qclass, qtype, qname)
-  if not rrs then  return nil  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)  end
-  return rrs
-  end
-
+       qname, qtype, qclass = standardize(qname, qtype, qclass);
+       local rrs = get(self.cache, qclass, qtype, qname);
+       if not rrs then return nil; 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); 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 = {}  end
-  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 = {}; end
+end
 
-function resolver:query (qname, qtype, qclass)    -- - - - - - - - - - -- query
 
-  qname, qtype, qclass = standardize (qname, qtype, qclass)
+function resolver:query(qname, qtype, qclass)    -- - - - - - - - - - -- query
+       qname, qtype, qclass = standardize(qname, qtype, qclass)
 
-  if not self.server then self:adddefaultnameservers ()  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 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] }
+       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]
+       };
 
-  -- remember the query
-  self.active[id] = self.active[id] or {}
-  self.active[id][question] = o
+       -- remember the query
+       self.active[id] = self.active[id] or {};
+       self.active[id][question] = o;
 
-  -- 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)
+       -- 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
 
+       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)
-  -- Resend all queries for this server
-  
-  local num = self.socketset[sock]
-  
-  -- Socket is dead now
-  self:voidsocket(sock);
-  
-  -- Find all requests to the down server, and retry on the next server
-  self.time = socket.gettime ()
-  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;
-        if o.retries >= #self.server then
-          --print ('timeout')
-          queries[question] = nil
-        else
-          local _a = self:getsocket(o.server);
-          if _a then _a:send (o.packet) end
-        end
-      end
-    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
-end
-
-function resolver:receive (rset)    -- - - - - - - - - - - - - - - - -  receive
-
-  --print 'receive'  print (self.socket)
-  self.time = socket.gettime ()
-  rset = rset or self.socket
-
-  local response
-  for i,sock in pairs (rset) do
-
-    if self.socketset[sock] then
-    local packet = sock:receive ()
-    if packet then
-
-    response = self:decode (packet)
-    if response 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  end
-
-    -- retire the query
-    local queries = self.active[response.header.id]
-    if queries[response.question.raw] then
-      queries[response.question.raw] = nil  end
-    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 cos = get (self.wanted, q.class, q.type, q.name)
-    if cos then
-      for co in pairs (cos) do
-        set (self.yielded, co, q.class, q.type, q.name, nil)
-       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)
-  --print 'receive'  print (self.socket)
-  self.time = socket.gettime ()
-
-  local response = self:decode (packet)
-  if response 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
-    end
-
-    -- retire the query
-    local queries = self.active[response.header.id]
-    if queries[response.question.raw] then
-      queries[response.question.raw] = nil
-    end
-    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
-          set (self.yielded, co, q.class, q.type, q.name, nil)
-          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(data)
-       local cos = get (self.wanted, unpack(data, 1, 3))
-       if cos then
-               cos[data[4]] = nil;
+       -- Resend all queries for this server
+
+       local num = self.socketset[sock]
+
+       -- Socket is dead now
+       self:voidsocket(sock);
+
+       -- Find all requests to the down server, and retry on the next server
+       self.time = socket.gettime();
+       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;
+                               if o.retries >= #self.server then
+                                       --print('timeout');
+                                       queries[question] = nil;
+                               else
+                                       local _a = self:getsocket(o.server);
+                                       if _a then _a:send(o.packet); end
+                               end
+                       end
+               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
 end
 
-function resolver:pulse ()    -- - - - - - - - - - - - - - - - - - - - -  pulse
+function resolver:settimeout(seconds)
+       self.timeout = seconds;
+end
 
-  --print ':pulse'
-  while self:receive() do end
-  if not next (self.active) then  return nil  end
+function resolver:receive(rset)    -- - - - - - - - - - - - - - - - -  receive
+       --print('receive');  print(self.socket);
+       self.time = socket.gettime();
+       rset = rset or self.socket;
+
+       local response;
+       for i,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 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];
+                                       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
+                                                       set(self.yielded, co, q.class, q.type, q.name, nil);
+                                                       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
 
-  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
+       return response;
+end
 
-        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
+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 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];
+               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
+                                       set(self.yielded, co, q.class, q.type, q.name, nil);
+                                       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, co, call_handler)
+       local cos = get(self.wanted, qclass, qtype, qname);
+       if cos then
+               if call_handler then
+                       coroutine.resume(co);
+               end
+               cos[co] = 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
+       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  socket.select (self.socket, nil, 4)  end
-  --print (self.cache)
-  return self:peek (qname, qtype, qclass)
-  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:lookupex(handler, qname, qtype, qclass)    -- - - - - - - - - -  lookup
+       return self:peek(qname, qtype, qclass) or self:query(qname, qtype, qclass);
+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,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,s in pairs {'answer', 'authority', 'additional'} do
-    for i,rr in pairs (response[s]) do
-      for j,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
+       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
 
 
--- module api ------------------------------------------------------ module api
+function resolver.print(response)    -- - - - - - - - - - - - - resolver.print
+       for s,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 function resolve (func, ...)    -- - - - - - - - - - - - - - resolver_get
-  dns._resolver = dns._resolver or dns.resolver ()
-  return func (dns._resolver, ...)
-  end
+       local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 };
+       local tmp;
+       for s,s in pairs({'answer', 'authority', 'additional'}) do
+               for i,rr in pairs(response[s]) do
+                       for j,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
 
 
-function dns.resolver ()    -- - - - - - - - - - - - - - - - - - - - - resolver
-
-  -- this function seems to be redundant with resolver.new ()
+-- module api ------------------------------------------------------ module api
 
-  local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, yielded = {}, 
-              best_server = 1 }
-  setmetatable (r, resolver)
-  setmetatable (r.cache, cache_metatable)
-  setmetatable (r.unsorted, { __mode = 'kv' })
-  return r
-  end
 
+function dns.resolver ()    -- - - - - - - - - - - - - - - - - - - - - resolver
+       -- this function seems to be redundant with resolver.new ()
 
-function dns.lookup (...)    -- - - - - - - - - - - - - - - - - - - - -  lookup
-  return resolve (resolver.lookup, ...)  end
+       local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, yielded = {}, 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;
 
-function dns.purge (...)    -- - - - - - - - - - - - - - - - - - - - - -  purge
-  return resolve (resolver.purge, ...)  end
+function dns.lookup(...)    -- - - - - - - - - - - - - - - - - - - - -  lookup
+       return _resolver:lookup(...);
+end
 
-function dns.peek (...)    -- - - - - - - - - - - - - - - - - - - - - - -  peek
-  return resolve (resolver.peek, ...)  end
+function dns.purge(...)    -- - - - - - - - - - - - - - - - - - - - - -  purge
+       return _resolver:purge(...);
+end
 
+function dns.peek(...)    -- - - - - - - - - - - - - - - - - - - - - - -  peek
+       return _resolver:peek(...);
+end
 
-function dns.query (...)    -- - - - - - - - - - - - - - - - - - - - - -  query
-  return resolve (resolver.query, ...)  end
+function dns.query(...)    -- - - - - - - - - - - - - - - - - - - - - -  query
+       return _resolver:query(...);
+end
 
-function dns.feed (...)    -- - - - - - - - - - - - - - - - - - - - - -  feed
-  return resolve (resolver.feed, ...)  end
+function dns.feed(...)    -- - - - - - - - - - - - - - - - - - - - - - -  feed
+       return _resolver:feed(...);
+end
 
-function dns.cancel(...)   -- - - - - - - - - - - - - - - - - - - - - -  cancel
-  return resolve(resolver.cancel, ...) end
+function dns.cancel(...)  -- - - - - - - - - - - - - - - - - - - - - -  cancel
+       return _resolver:cancel(...);
+end
 
-function dns:socket_wrapper_set (...)    -- - - - - - - - -  socket_wrapper_set
-  return resolve (resolver.socket_wrapper_set, ...)  end
+function dns.settimeout(...)
+       return _resolver:settimeout(...);
+end
 
+function dns.socket_wrapper_set(...)    -- - - - - - - - -  socket_wrapper_set
+       return _resolver:socket_wrapper_set(...);
+end
 
-return dns
+return dns;