ejabberdsql2prosody: Allow for multiple INSERTs to the same table
[prosody.git] / net / dns.lua
1 -- Prosody IM
2 -- This file is included with Prosody IM. It has modifications,
3 -- which are hereby placed in the public domain.
4
5 -- public domain 20080404 lua@ztact.com
6
7
8 -- todo: quick (default) header generation
9 -- todo: nxdomain, error handling
10 -- todo: cache results of encodeName
11
12
13 -- reference: http://tools.ietf.org/html/rfc1035
14 -- reference: http://tools.ietf.org/html/rfc1876 (LOC)
15
16
17 require 'socket'
18 local ztact = require 'util.ztact'
19 local require = require
20
21 local coroutine, io, math, socket, string, table =
22       coroutine, io, math, socket, string, table
23
24 local ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack =
25       ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack
26
27 local get, set = ztact.get, ztact.set
28
29
30 -------------------------------------------------- module dns
31 module ('dns')
32 local dns = _M;
33
34
35 -- dns type & class codes ------------------------------ dns type & class codes
36
37
38 local append = table.insert
39
40
41 local function highbyte (i)    -- - - - - - - - - - - - - - - - - - -  highbyte
42   return (i-(i%0x100))/0x100
43   end
44
45
46 local function augment (t)    -- - - - - - - - - - - - - - - - - - - -  augment
47   local a = {}
48   for i,s in pairs (t) do  a[i] = s  a[s] = s  a[string.lower (s)] = s  end
49   return a
50   end
51
52
53 local function encode (t)    -- - - - - - - - - - - - - - - - - - - - -  encode
54   local code = {}
55   for i,s in pairs (t) do
56     local word = string.char (highbyte (i), i %0x100)
57     code[i] = word
58     code[s] = word
59     code[string.lower (s)] = word
60     end
61   return code
62   end
63
64
65 dns.types = {
66   'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR', 'NULL', 'WKS',
67   'PTR', 'HINFO', 'MINFO', 'MX', 'TXT',
68   [ 28] = 'AAAA', [ 29] = 'LOC',   [ 33] = 'SRV',
69   [252] = 'AXFR', [253] = 'MAILB', [254] = 'MAILA', [255] = '*' }
70
71
72 dns.classes = { 'IN', 'CS', 'CH', 'HS', [255] = '*' }
73
74
75 dns.type      = augment (dns.types)
76 dns.class     = augment (dns.classes)
77 dns.typecode  = encode  (dns.types)
78 dns.classcode = encode  (dns.classes)
79
80
81
82 local function standardize (qname, qtype, qclass)    -- - - - - - - standardize
83   if string.byte (qname, -1) ~= 0x2E then  qname = qname..'.'  end
84   qname = string.lower (qname)
85   return qname, dns.type[qtype or 'A'], dns.class[qclass or 'IN']
86   end
87
88
89 local function prune (rrs, time, soft)    -- - - - - - - - - - - - - - -  prune
90
91   time = time or socket.gettime ()
92   for i,rr in pairs (rrs) do
93
94     if rr.tod then
95       -- rr.tod = rr.tod - 50    -- accelerated decripitude
96       rr.ttl = math.floor (rr.tod - time)
97       if rr.ttl <= 0 then  rrs[i] = nil  end
98
99     elseif soft == 'soft' then    -- What is this?  I forget!
100       assert (rr.ttl == 0)
101       rrs[i] = nil
102       end  end  end
103
104
105 -- metatables & co. ------------------------------------------ metatables & co.
106
107
108 local resolver = {}
109 resolver.__index = resolver
110
111
112 local SRV_tostring
113
114
115 local rr_metatable = {}    -- - - - - - - - - - - - - - - - - - -  rr_metatable
116 function rr_metatable.__tostring (rr)
117   local s0 = string.format (
118     '%2s %-5s %6i %-28s', rr.class, rr.type, rr.ttl, rr.name )
119   local s1 = ''
120   if rr.type == 'A' then  s1 = ' '..rr.a
121   elseif rr.type == 'MX' then
122     s1 = string.format (' %2i %s', rr.pref, rr.mx)
123   elseif rr.type == 'CNAME' then  s1 = ' '..rr.cname
124   elseif rr.type == 'LOC'   then  s1 = ' '..resolver.LOC_tostring (rr)
125   elseif rr.type == 'NS'    then  s1 = ' '..rr.ns
126   elseif rr.type == 'SRV'   then  s1 = ' '..SRV_tostring (rr)
127   elseif rr.type == 'TXT'   then  s1 = ' '..rr.txt
128   else  s1 = ' <UNKNOWN RDATA TYPE>'  end
129   return s0..s1
130   end
131
132
133 local rrs_metatable = {}    -- - - - - - - - - - - - - - - - - -  rrs_metatable
134 function rrs_metatable.__tostring (rrs)
135   local t = {}
136   for i,rr in pairs (rrs) do  append (t, tostring (rr)..'\n')  end
137   return table.concat (t)
138   end
139
140
141 local cache_metatable = {}    -- - - - - - - - - - - - - - - -  cache_metatable
142 function cache_metatable.__tostring (cache)
143   local time = socket.gettime ()
144   local t = {}
145   for class,types in pairs (cache) do
146     for type,names in pairs (types) do
147       for name,rrs in pairs (names) do
148         prune (rrs, time)
149         append (t, tostring (rrs))  end  end  end
150   return table.concat (t)
151   end
152
153
154 function resolver:new ()    -- - - - - - - - - - - - - - - - - - - - - resolver
155   local r = { active = {}, cache = {}, unsorted = {} }
156   setmetatable (r, resolver)
157   setmetatable (r.cache, cache_metatable)
158   setmetatable (r.unsorted, { __mode = 'kv' })
159   return r
160   end
161
162
163 -- packet layer -------------------------------------------------- packet layer
164
165
166 function dns.random (...)    -- - - - - - - - - - - - - - - - - - -  dns.random
167   math.randomseed (10000*socket.gettime ())
168   dns.random = math.random
169   return dns.random (...)
170   end
171
172
173 local function encodeHeader (o)    -- - - - - - - - - - - - - - -  encodeHeader
174
175   o = o or {}
176
177   o.id = o.id or                -- 16b  (random) id
178     dns.random (0, 0xffff)
179
180   o.rd = o.rd or 1              --  1b  1 recursion desired
181   o.tc = o.tc or 0              --  1b  1 truncated response
182   o.aa = o.aa or 0              --  1b  1 authoritative response
183   o.opcode = o.opcode or 0      --  4b  0 query
184                                 --      1 inverse query
185                                 --      2 server status request
186                                 --      3-15 reserved
187   o.qr = o.qr or 0              --  1b  0 query, 1 response
188
189   o.rcode = o.rcode or 0        --  4b  0 no error
190                                 --      1 format error
191                                 --      2 server failure
192                                 --      3 name error
193                                 --      4 not implemented
194                                 --      5 refused
195                                 --      6-15 reserved
196   o.z  = o.z  or 0              --  3b  0 resvered
197   o.ra = o.ra or 0              --  1b  1 recursion available
198
199   o.qdcount = o.qdcount or 1    -- 16b  number of question RRs
200   o.ancount = o.ancount or 0    -- 16b  number of answers RRs
201   o.nscount = o.nscount or 0    -- 16b  number of nameservers RRs
202   o.arcount = o.arcount or 0    -- 16b  number of additional RRs
203
204   -- string.char() rounds, so prevent roundup with -0.4999
205   local header = string.char (
206     highbyte (o.id),  o.id %0x100,
207     o.rd + 2*o.tc + 4*o.aa + 8*o.opcode + 128*o.qr,
208     o.rcode + 16*o.z + 128*o.ra,
209     highbyte (o.qdcount),  o.qdcount %0x100,
210     highbyte (o.ancount),  o.ancount %0x100,
211     highbyte (o.nscount),  o.nscount %0x100,
212     highbyte (o.arcount),  o.arcount %0x100 )
213
214   return header, o.id
215   end
216
217
218 local function encodeName (name)    -- - - - - - - - - - - - - - - - encodeName
219   local t = {}
220   for part in string.gmatch (name, '[^.]+') do
221     append (t, string.char (string.len (part)))
222     append (t, part)
223     end
224   append (t, string.char (0))
225   return table.concat (t)
226   end
227
228
229 local function encodeQuestion (qname, qtype, qclass)    -- - - - encodeQuestion
230   qname  = encodeName (qname)
231   qtype  = dns.typecode[qtype or 'a']
232   qclass = dns.classcode[qclass or 'in']
233   return qname..qtype..qclass;
234   end
235
236
237 function resolver:byte (len)    -- - - - - - - - - - - - - - - - - - - - - byte
238   len = len or 1
239   local offset = self.offset
240   local last = offset + len - 1
241   if last > #self.packet then
242     error (string.format ('out of bounds: %i>%i', last, #self.packet))  end
243   self.offset = offset + len
244   return string.byte (self.packet, offset, last)
245   end
246
247
248 function resolver:word ()    -- - - - - - - - - - - - - - - - - - - - - -  word
249   local b1, b2 = self:byte (2)
250   return 0x100*b1 + b2
251   end
252
253
254 function resolver:dword ()    -- - - - - - - - - - - - - - - - - - - - -  dword
255   local b1, b2, b3, b4 = self:byte (4)
256   --print ('dword', b1, b2, b3, b4)
257   return 0x1000000*b1 + 0x10000*b2 + 0x100*b3 + b4
258   end
259
260
261 function resolver:sub (len)    -- - - - - - - - - - - - - - - - - - - - - - sub
262   len = len or 1
263   local s = string.sub (self.packet, self.offset, self.offset + len - 1)
264   self.offset = self.offset + len
265   return s
266   end
267
268
269 function resolver:header (force)    -- - - - - - - - - - - - - - - - - - header
270
271   local id = self:word ()
272   --print (string.format (':header  id  %x', id))
273   if not self.active[id] and not force then  return nil  end
274
275   local h = { id = id }
276
277   local b1, b2 = self:byte (2)
278
279   h.rd      = b1 %2
280   h.tc      = b1 /2%2
281   h.aa      = b1 /4%2
282   h.opcode  = b1 /8%16
283   h.qr      = b1 /128
284
285   h.rcode   = b2 %16
286   h.z       = b2 /16%8
287   h.ra      = b2 /128
288
289   h.qdcount = self:word ()
290   h.ancount = self:word ()
291   h.nscount = self:word ()
292   h.arcount = self:word ()
293
294   for k,v in pairs (h) do  h[k] = v-v%1  end
295
296   return h
297   end
298
299
300 function resolver:name ()    -- - - - - - - - - - - - - - - - - - - - - -  name
301   local remember, pointers = nil, 0
302   local len = self:byte ()
303   local n = {}
304   while len > 0 do
305     if len >= 0xc0 then    -- name is "compressed"
306       pointers = pointers + 1
307       if pointers >= 20 then  error ('dns error: 20 pointers')  end
308       local offset = ((len-0xc0)*0x100) + self:byte ()
309       remember = remember or self.offset
310       self.offset = offset + 1    -- +1 for lua
311     else    -- name is not compressed
312       append (n, self:sub (len)..'.')
313       end
314     len = self:byte ()
315     end
316   self.offset = remember or self.offset
317   return table.concat (n)
318   end
319
320
321 function resolver:question ()    -- - - - - - - - - - - - - - - - - -  question
322   local q = {}
323   q.name  = self:name ()
324   q.type  = dns.type[self:word ()]
325   q.class = dns.class[self:word ()]
326   return q
327   end
328
329
330 function resolver:A (rr)    -- - - - - - - - - - - - - - - - - - - - - - - -  A
331   local b1, b2, b3, b4 = self:byte (4)
332   rr.a = string.format ('%i.%i.%i.%i', b1, b2, b3, b4)
333   end
334
335
336 function resolver:CNAME (rr)    -- - - - - - - - - - - - - - - - - - - -  CNAME
337   rr.cname = self:name ()
338   end
339
340
341 function resolver:MX (rr)    -- - - - - - - - - - - - - - - - - - - - - - -  MX
342   rr.pref = self:word ()
343   rr.mx   = self:name ()
344   end
345
346
347 function resolver:LOC_nibble_power ()    -- - - - - - - - - -  LOC_nibble_power
348   local b = self:byte ()
349   --print ('nibbles', ((b-(b%0x10))/0x10), (b%0x10))
350   return ((b-(b%0x10))/0x10) * (10^(b%0x10))
351   end
352
353
354 function resolver:LOC (rr)    -- - - - - - - - - - - - - - - - - - - - - -  LOC
355   rr.version = self:byte ()
356   if rr.version == 0 then
357     rr.loc           = rr.loc or {}
358     rr.loc.size      = self:LOC_nibble_power ()
359     rr.loc.horiz_pre = self:LOC_nibble_power ()
360     rr.loc.vert_pre  = self:LOC_nibble_power ()
361     rr.loc.latitude  = self:dword ()
362     rr.loc.longitude = self:dword ()
363     rr.loc.altitude  = self:dword ()
364     end  end
365
366
367 local function LOC_tostring_degrees (f, pos, neg)    -- - - - - - - - - - - - -
368   f = f - 0x80000000
369   if f < 0 then  pos = neg  f = -f  end
370   local deg, min, msec
371   msec = f%60000
372   f    = (f-msec)/60000
373   min  = f%60
374   deg = (f-min)/60
375   return string.format ('%3d %2d %2.3f %s', deg, min, msec/1000, pos)
376   end
377
378
379 function resolver.LOC_tostring (rr)    -- - - - - - - - - - - - -  LOC_tostring
380
381   local t = {}
382
383   --[[
384   for k,name in pairs { 'size', 'horiz_pre', 'vert_pre',
385                         'latitude', 'longitude', 'altitude' } do
386     append (t, string.format ('%4s%-10s: %12.0f\n', '', name, rr.loc[name]))
387     end
388   --]]
389
390   append ( t, string.format (
391     '%s    %s    %.2fm %.2fm %.2fm %.2fm',
392     LOC_tostring_degrees (rr.loc.latitude, 'N', 'S'),
393     LOC_tostring_degrees (rr.loc.longitude, 'E', 'W'),
394     (rr.loc.altitude - 10000000) / 100,
395     rr.loc.size / 100,
396     rr.loc.horiz_pre / 100,
397     rr.loc.vert_pre / 100 ) )
398
399   return table.concat (t)
400   end
401
402
403 function resolver:NS (rr)    -- - - - - - - - - - - - - - - - - - - - - - -  NS
404   rr.ns = self:name ()
405   end
406
407
408 function resolver:SOA (rr)    -- - - - - - - - - - - - - - - - - - - - - -  SOA
409   end
410
411
412 function resolver:SRV (rr)    -- - - - - - - - - - - - - - - - - - - - - -  SRV
413   rr.srv = {}
414   rr.srv.priority = self:word ()
415   rr.srv.weight   = self:word ()
416   rr.srv.port     = self:word ()
417   rr.srv.target   = self:name ()
418   end
419
420
421 function SRV_tostring (rr)    -- - - - - - - - - - - - - - - - - - SRV_tostring
422   local s = rr.srv
423   return string.format ( '%5d %5d %5d %s',
424                          s.priority, s.weight, s.port, s.target )
425   end
426
427
428 function resolver:TXT (rr)    -- - - - - - - - - - - - - - - - - - - - - -  TXT
429   rr.txt = self:sub (rr.rdlength)
430   end
431
432
433 function resolver:rr ()    -- - - - - - - - - - - - - - - - - - - - - - - -  rr
434   local rr = {}
435   setmetatable (rr, rr_metatable)
436   rr.name     = self:name (self)
437   rr.type     = dns.type[self:word ()] or rr.type
438   rr.class    = dns.class[self:word ()] or rr.class
439   rr.ttl      = 0x10000*self:word () + self:word ()
440   rr.rdlength = self:word ()
441
442   if rr.ttl == 0 then  -- pass
443   else  rr.tod = self.time + rr.ttl  end
444
445   local remember = self.offset
446   local rr_parser = self[dns.type[rr.type]]
447   if rr_parser then  rr_parser (self, rr)  end
448   self.offset = remember
449   rr.rdata = self:sub (rr.rdlength)
450   return rr
451   end
452
453
454 function resolver:rrs (count)    -- - - - - - - - - - - - - - - - - - - - - rrs
455   local rrs = {}
456   for i = 1,count do  append (rrs, self:rr ())  end
457   return rrs
458   end
459
460
461 function resolver:decode (packet, force)    -- - - - - - - - - - - - - - decode
462
463   self.packet, self.offset = packet, 1
464   local header = self:header (force)
465   if not header then  return nil  end
466   local response = { header = header }
467
468   response.question = {}
469   local offset = self.offset
470   for i = 1,response.header.qdcount do
471     append (response.question, self:question ())  end
472   response.question.raw = string.sub (self.packet, offset, self.offset - 1)
473
474   if not force then
475     if not self.active[response.header.id] or
476        not self.active[response.header.id][response.question.raw] then
477       return nil  end  end
478
479   response.answer     = self:rrs (response.header.ancount)
480   response.authority  = self:rrs (response.header.nscount)
481   response.additional = self:rrs (response.header.arcount)
482
483   return response
484   end
485
486
487 -- socket layer -------------------------------------------------- socket layer
488
489
490 resolver.delays = { 1, 3, 11, 45 }
491
492
493 function resolver:addnameserver (address)    -- - - - - - - - - - addnameserver
494   self.server = self.server or {}
495   append (self.server, address)
496   end
497
498
499 function resolver:setnameserver (address)    -- - - - - - - - - - setnameserver
500   self.server = {}
501   self:addnameserver (address)
502   end
503
504
505 function resolver:adddefaultnameservers ()    -- - - - -  adddefaultnameservers
506   local resolv_conf = io.open("/etc/resolv.conf");
507   if resolv_conf then
508           for line in resolv_conf:lines() do
509                 local address = string.match (line, 'nameserver%s+(%d+%.%d+%.%d+%.%d+)')
510                 if address then  self:addnameserver (address)  end
511           end
512   else -- FIXME correct for windows, using opendns nameservers for now
513         self:addnameserver ("208.67.222.222")
514         self:addnameserver ("208.67.220.220")
515   end
516 end
517
518
519 function resolver:getsocket (servernum)    -- - - - - - - - - - - - - getsocket
520
521   self.socket = self.socket or {}
522   self.socketset = self.socketset or {}
523
524   local sock = self.socket[servernum]
525   if sock then  return sock  end
526
527   sock = socket.udp ()
528   if self.socket_wrapper then  sock = self.socket_wrapper (sock)  end
529   sock:settimeout (0)
530   -- todo: attempt to use a random port, fallback to 0
531   sock:setsockname ('*', 0)
532   sock:setpeername (self.server[servernum], 53)
533   self.socket[servernum] = sock
534   self.socketset[sock] = sock
535   return sock
536   end
537
538
539 function resolver:socket_wrapper_set (func)  -- - - - - - - socket_wrapper_set
540   self.socket_wrapper = func
541   end
542
543
544 function resolver:closeall ()    -- - - - - - - - - - - - - - - - - -  closeall
545   for i,sock in ipairs (self.socket) do  self.socket[i]:close ()  end
546   self.socket = {}
547   end
548
549
550 function resolver:remember (rr, type)    -- - - - - - - - - - - - - -  remember
551
552   --print ('remember', type, rr.class, rr.type, rr.name)
553
554   if type ~= '*' then
555     type = rr.type
556     local all = get (self.cache, rr.class, '*', rr.name)
557     --print ('remember all', all)
558     if all then  append (all, rr)  end
559     end
560
561   self.cache = self.cache or setmetatable ({}, cache_metatable)
562   local rrs = get (self.cache, rr.class, type, rr.name) or
563     set (self.cache, rr.class, type, rr.name, setmetatable ({}, rrs_metatable))
564   append (rrs, rr)
565
566   if type == 'MX' then  self.unsorted[rrs] = true  end
567   end
568
569
570 local function comp_mx (a, b)    -- - - - - - - - - - - - - - - - - - - comp_mx
571   return (a.pref == b.pref) and (a.mx < b.mx) or (a.pref < b.pref)
572   end
573
574
575 function resolver:peek (qname, qtype, qclass)    -- - - - - - - - - - - -  peek
576   qname, qtype, qclass = standardize (qname, qtype, qclass)
577   local rrs = get (self.cache, qclass, qtype, qname)
578   if not rrs then  return nil  end
579   if prune (rrs, socket.gettime ()) and qtype == '*' or not next (rrs) then
580     set (self.cache, qclass, qtype, qname, nil)  return nil  end
581   if self.unsorted[rrs] then  table.sort (rrs, comp_mx)  end
582   return rrs
583   end
584
585
586 function resolver:purge (soft)    -- - - - - - - - - - - - - - - - - - -  purge
587   if soft == 'soft' then
588     self.time = socket.gettime ()
589     for class,types in pairs (self.cache or {}) do
590       for type,names in pairs (types) do
591         for name,rrs in pairs (names) do
592           prune (rrs, self.time, 'soft')
593           end  end  end
594   else  self.cache = {}  end
595   end
596
597
598 function resolver:query (qname, qtype, qclass)    -- - - - - - - - - - -- query
599
600   qname, qtype, qclass = standardize (qname, qtype, qclass)
601
602   if not self.server then self:adddefaultnameservers ()  end
603
604   local question = encodeQuestion (qname, qtype, qclass)
605   local peek = self:peek (qname, qtype, qclass)
606   if peek then  return peek  end
607
608   local header, id = encodeHeader ()
609   --print ('query  id', id, qclass, qtype, qname)
610   local o = { packet = header..question,
611               server = 1,
612               delay  = 1,
613               retry  = socket.gettime () + self.delays[1] }
614   self:getsocket (o.server):send (o.packet)
615
616   -- remember the query
617   self.active[id] = self.active[id] or {}
618   self.active[id][question] = o
619
620   -- remember which coroutine wants the answer
621   local co = coroutine.running ()
622   if co then
623     set (self.wanted, qclass, qtype, qname, co, true)
624     --set (self.yielded, co, qclass, qtype, qname, true)
625   end
626 end
627
628
629
630 function resolver:receive (rset)    -- - - - - - - - - - - - - - - - -  receive
631
632   --print 'receive'  print (self.socket)
633   self.time = socket.gettime ()
634   rset = rset or self.socket
635
636   local response
637   for i,sock in pairs (rset) do
638
639     if self.socketset[sock] then
640     local packet = sock:receive ()
641     if packet then
642
643     response = self:decode (packet)
644     if response then
645     --print 'received response'
646     --self.print (response)
647
648     for i,section in pairs { 'answer', 'authority', 'additional' } do
649       for j,rr in pairs (response[section]) do
650         self:remember (rr, response.question[1].type)  end  end
651
652     -- retire the query
653     local queries = self.active[response.header.id]
654     if queries[response.question.raw] then
655       queries[response.question.raw] = nil  end
656     if not next (queries) then  self.active[response.header.id] = nil  end
657     if not next (self.active) then  self:closeall ()  end
658
659     -- was the query on the wanted list?
660     local q = response.question
661     local cos = get (self.wanted, q.class, q.type, q.name)
662     if cos then
663       for co in pairs (cos) do
664         set (self.yielded, co, q.class, q.type, q.name, nil)
665         if coroutine.status(co) == "suspended" then  coroutine.resume (co)  end
666         end
667       set (self.wanted, q.class, q.type, q.name, nil)
668       end  end  end  end  end
669
670   return response
671   end
672
673
674 function resolver:feed(sock, packet)
675   --print 'receive'  print (self.socket)
676   self.time = socket.gettime ()
677
678   local response = self:decode (packet)
679   if response then
680     --print 'received response'
681     --self.print (response)
682
683     for i,section in pairs { 'answer', 'authority', 'additional' } do
684       for j,rr in pairs (response[section]) do
685         self:remember (rr, response.question[1].type)
686       end
687     end
688
689     -- retire the query
690     local queries = self.active[response.header.id]
691     if queries[response.question.raw] then
692       queries[response.question.raw] = nil
693     end
694     if not next (queries) then  self.active[response.header.id] = nil  end
695     if not next (self.active) then  self:closeall ()  end
696
697     -- was the query on the wanted list?
698     local q = response.question[1]
699     if q then
700       local cos = get (self.wanted, q.class, q.type, q.name)
701       if cos then
702         for co in pairs (cos) do
703           set (self.yielded, co, q.class, q.type, q.name, nil)
704           if coroutine.status(co) == "suspended" then coroutine.resume (co)  end
705         end
706         set (self.wanted, q.class, q.type, q.name, nil)
707       end
708     end
709   end 
710
711   return response
712 end
713
714 function resolver:cancel(data)
715         local cos = get (self.wanted, unpack(data, 1, 3))
716         if cos then
717                 cos[data[4]] = nil;
718         end
719 end
720
721 function resolver:pulse ()    -- - - - - - - - - - - - - - - - - - - - -  pulse
722
723   --print ':pulse'
724   while self:receive() do end
725   if not next (self.active) then  return nil  end
726
727   self.time = socket.gettime ()
728   for id,queries in pairs (self.active) do
729     for question,o in pairs (queries) do
730       if self.time >= o.retry then
731
732         o.server = o.server + 1
733         if o.server > #self.server then
734           o.server = 1
735           o.delay = o.delay + 1
736           end
737
738         if o.delay > #self.delays then
739           --print ('timeout')
740           queries[question] = nil
741           if not next (queries) then  self.active[id] = nil  end
742           if not next (self.active) then  return nil  end
743         else
744           --print ('retry', o.server, o.delay)
745           local _a = self.socket[o.server];
746           if _a then _a:send (o.packet) end
747           o.retry = self.time + self.delays[o.delay]
748           end  end  end  end
749
750   if next (self.active) then  return true  end
751   return nil
752   end
753
754
755 function resolver:lookup (qname, qtype, qclass)    -- - - - - - - - - -  lookup
756   self:query (qname, qtype, qclass)
757   while self:pulse () do  socket.select (self.socket, nil, 4)  end
758   --print (self.cache)
759   return self:peek (qname, qtype, qclass)
760   end
761
762 function resolver:lookupex (handler, qname, qtype, qclass)    -- - - - - - - - - -  lookup
763   return self:peek (qname, qtype, qclass) or self:query (qname, qtype, qclass)
764   end
765
766
767 --print ---------------------------------------------------------------- print
768
769
770 local hints = {    -- - - - - - - - - - - - - - - - - - - - - - - - - - - hints
771   qr = { [0]='query', 'response' },
772   opcode = { [0]='query', 'inverse query', 'server status request' },
773   aa = { [0]='non-authoritative', 'authoritative' },
774   tc = { [0]='complete', 'truncated' },
775   rd = { [0]='recursion not desired', 'recursion desired' },
776   ra = { [0]='recursion not available', 'recursion available' },
777   z  = { [0]='(reserved)' },
778   rcode = { [0]='no error', 'format error', 'server failure', 'name error',
779             'not implemented' },
780
781   type = dns.type,
782   class = dns.class, }
783
784
785 local function hint (p, s)    -- - - - - - - - - - - - - - - - - - - - - - hint
786   return (hints[s] and hints[s][p[s]]) or ''  end
787
788
789 function resolver.print (response)    -- - - - - - - - - - - - - resolver.print
790
791   for s,s in pairs { 'id', 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z',
792                      'rcode', 'qdcount', 'ancount', 'nscount', 'arcount' } do
793     print ( string.format ('%-30s', 'header.'..s),
794             response.header[s], hint (response.header, s) )
795     end
796
797   for i,question in ipairs (response.question) do
798     print (string.format ('question[%i].name         ', i), question.name)
799     print (string.format ('question[%i].type         ', i), question.type)
800     print (string.format ('question[%i].class        ', i), question.class)
801     end
802
803   local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 }
804   local tmp
805   for s,s in pairs {'answer', 'authority', 'additional'} do
806     for i,rr in pairs (response[s]) do
807       for j,t in pairs { 'name', 'type', 'class', 'ttl', 'rdlength' } do
808         tmp = string.format ('%s[%i].%s', s, i, t)
809         print (string.format ('%-30s', tmp), rr[t], hint (rr, t))
810         end
811       for j,t in pairs (rr) do
812         if not common[j] then
813           tmp = string.format ('%s[%i].%s', s, i, j)
814           print (string.format ('%-30s  %s', tostring(tmp), tostring(t)))
815           end  end  end  end  end
816
817
818 -- module api ------------------------------------------------------ module api
819
820
821 local function resolve (func, ...)    -- - - - - - - - - - - - - - resolver_get
822   dns._resolver = dns._resolver or dns.resolver ()
823   return func (dns._resolver, ...)
824   end
825
826
827 function dns.resolver ()    -- - - - - - - - - - - - - - - - - - - - - resolver
828
829   -- this function seems to be redundant with resolver.new ()
830
831   local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, yielded = {} }
832   setmetatable (r, resolver)
833   setmetatable (r.cache, cache_metatable)
834   setmetatable (r.unsorted, { __mode = 'kv' })
835   return r
836   end
837
838
839 function dns.lookup (...)    -- - - - - - - - - - - - - - - - - - - - -  lookup
840   return resolve (resolver.lookup, ...)  end
841
842
843 function dns.purge (...)    -- - - - - - - - - - - - - - - - - - - - - -  purge
844   return resolve (resolver.purge, ...)  end
845
846 function dns.peek (...)    -- - - - - - - - - - - - - - - - - - - - - - -  peek
847   return resolve (resolver.peek, ...)  end
848
849
850 function dns.query (...)    -- - - - - - - - - - - - - - - - - - - - - -  query
851   return resolve (resolver.query, ...)  end
852
853 function dns.feed (...)    -- - - - - - - - - - - - - - - - - - - - - -  feed
854   return resolve (resolver.feed, ...)  end
855
856 function dns.cancel(...)   -- - - - - - - - - - - - - - - - - - - - - -  cancel
857   return resolve(resolver.cancel, ...) end
858
859 function dns:socket_wrapper_set (...)    -- - - - - - - - -  socket_wrapper_set
860   return resolve (resolver.socket_wrapper_set, ...)  end
861
862
863 return dns