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