ea1d5d969864ba4a8654205885bdd033b3c31ef2
[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 resolv_conf then
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
510   else -- FIXME correct for windows, using opendns nameservers for now
511         self:addnameserver ("208.67.222.222")
512         self:addnameserver ("208.67.220.220")
513   end
514 end
515
516
517 function resolver:getsocket (servernum)    -- - - - - - - - - - - - - getsocket
518
519   self.socket = self.socket or {}
520   self.socketset = self.socketset or {}
521
522   local sock = self.socket[servernum]
523   if sock then  return sock  end
524
525   sock = socket.udp ()
526   if self.socket_wrapper then  sock = self.socket_wrapper (sock)  end
527   sock:settimeout (0)
528   -- todo: attempt to use a random port, fallback to 0
529   sock:setsockname ('*', 0)
530   sock:setpeername (self.server[servernum], 53)
531   self.socket[servernum] = sock
532   self.socketset[sock] = sock
533   return sock
534   end
535
536
537 function resolver:socket_wrapper_set (func)  -- - - - - - - socket_wrapper_set
538   self.socket_wrapper = func
539   end
540
541
542 function resolver:closeall ()    -- - - - - - - - - - - - - - - - - -  closeall
543   for i,sock in ipairs (self.socket) do  self.socket[i]:close ()  end
544   self.socket = {}
545   end
546
547
548 function resolver:remember (rr, type)    -- - - - - - - - - - - - - -  remember
549
550   -- print ('remember', type, rr.class, rr.type, rr.name)
551
552   if type ~= '*' then
553     type = rr.type
554     local all = get (self.cache, rr.class, '*', rr.name)
555     -- print ('remember all', all)
556     if all then  append (all, rr)  end
557     end
558
559   self.cache = self.cache or setmetatable ({}, cache_metatable)
560   local rrs = get (self.cache, rr.class, type, rr.name) or
561     set (self.cache, rr.class, type, rr.name, setmetatable ({}, rrs_metatable))
562   append (rrs, rr)
563
564   if type == 'MX' then  self.unsorted[rrs] = true  end
565   end
566
567
568 local function comp_mx (a, b)    -- - - - - - - - - - - - - - - - - - - comp_mx
569   return (a.pref == b.pref) and (a.mx < b.mx) or (a.pref < b.pref)
570   end
571
572
573 function resolver:peek (qname, qtype, qclass)    -- - - - - - - - - - - -  peek
574   qname, qtype, qclass = standardize (qname, qtype, qclass)
575   local rrs = get (self.cache, qclass, qtype, qname)
576   if not rrs then  return nil  end
577   if prune (rrs, socket.gettime ()) and qtype == '*' or not next (rrs) then
578     set (self.cache, qclass, qtype, qname, nil)  return nil  end
579   if self.unsorted[rrs] then  table.sort (rrs, comp_mx)  end
580   return rrs
581   end
582
583
584 function resolver:purge (soft)    -- - - - - - - - - - - - - - - - - - -  purge
585   if soft == 'soft' then
586     self.time = socket.gettime ()
587     for class,types in pairs (self.cache or {}) do
588       for type,names in pairs (types) do
589         for name,rrs in pairs (names) do
590           prune (rrs, self.time, 'soft')
591           end  end  end
592   else  self.cache = {}  end
593   end
594
595
596 function resolver:query (qname, qtype, qclass)    -- - - - - - - - - - -- query
597
598   qname, qtype, qclass = standardize (qname, qtype, qclass)
599
600   if not self.server then  self:adddefaultnameservers ()  end
601
602   local question = encodeQuestion (qname, qtype, qclass)
603   local peek = self:peek (qname, qtype, qclass)
604   if peek then  return peek  end
605
606   local header, id = encodeHeader ()
607   -- print ('query  id', id, qclass, qtype, qname)
608   local o = { packet = header..question,
609               server = 1,
610               delay  = 1,
611               retry  = socket.gettime () + self.delays[1] }
612   self:getsocket (o.server):send (o.packet)
613
614   -- remember the query
615   self.active[id] = self.active[id] or {}
616   self.active[id][question] = o
617
618   -- remember which coroutine wants the answer
619   local co = coroutine.running ()
620   if co then
621     set (self.wanted, qclass, qtype, qname, co, true)
622     set (self.yielded, co, qclass, qtype, qname, true)
623     end  end
624
625
626 function resolver:receive (rset)    -- - - - - - - - - - - - - - - - -  receive
627
628   -- print 'receive'  print (self.socket)
629   self.time = socket.gettime ()
630   rset = rset or self.socket
631
632   local response
633   for i,sock in pairs (rset) do
634
635     if self.socketset[sock] then
636     local packet = sock:receive ()
637     if packet then
638
639     response = self:decode (packet)
640     if response then
641     -- print 'received response'
642     -- self.print (response)
643
644     for i,section in pairs { 'answer', 'authority', 'additional' } do
645       for j,rr in pairs (response[section]) do
646         self:remember (rr, response.question[1].type)  end  end
647
648     -- retire the query
649     local queries = self.active[response.header.id]
650     if queries[response.question.raw] then
651       queries[response.question.raw] = nil  end
652     if not next (queries) then  self.active[response.header.id] = nil  end
653     if not next (self.active) then  self:closeall ()  end
654
655     -- was the query on the wanted list?
656     local q = response.question
657     local cos = get (self.wanted, q.class, q.type, q.name)
658     if cos then
659       for co in pairs (cos) do
660         set (self.yielded, co, q.class, q.type, q.name, nil)
661         if not self.yielded[co] then  coroutine.resume (co)  end
662         end
663       set (self.wanted, q.class, q.type, q.name, nil)
664       end  end  end  end  end
665
666   return response
667   end
668
669
670 function resolver:pulse ()    -- - - - - - - - - - - - - - - - - - - - -  pulse
671
672   -- print ':pulse'
673   while self:receive () do end
674   if not next (self.active) then  return nil  end
675
676   self.time = socket.gettime ()
677   for id,queries in pairs (self.active) do
678     for question,o in pairs (queries) do
679       if self.time >= o.retry then
680
681         o.server = o.server + 1
682         if o.server > #self.server then
683           o.server = 1
684           o.delay = o.delay + 1
685           end
686
687         if o.delay > #self.delays then
688           print ('timeout')
689           queries[question] = nil
690           if not next (queries) then  self.active[id] = nil  end
691           if not next (self.active) then  return nil  end
692         else
693           -- print ('retry', o.server, o.delay)
694           local _a = self.socket[o.server];
695           if _a then _a:send (o.packet) end
696           o.retry = self.time + self.delays[o.delay]
697           end  end  end  end
698
699   if next (self.active) then  return true  end
700   return nil
701   end
702
703
704 function resolver:lookup (qname, qtype, qclass)    -- - - - - - - - - -  lookup
705   self:query (qname, qtype, qclass)
706   while self:pulse () do  socket.select (self.socket, nil, 4)  end
707   -- print (self.cache)
708   return self:peek (qname, qtype, qclass)
709   end
710
711
712 -- print ---------------------------------------------------------------- print
713
714
715 local hints = {    -- - - - - - - - - - - - - - - - - - - - - - - - - - - hints
716   qr = { [0]='query', 'response' },
717   opcode = { [0]='query', 'inverse query', 'server status request' },
718   aa = { [0]='non-authoritative', 'authoritative' },
719   tc = { [0]='complete', 'truncated' },
720   rd = { [0]='recursion not desired', 'recursion desired' },
721   ra = { [0]='recursion not available', 'recursion available' },
722   z  = { [0]='(reserved)' },
723   rcode = { [0]='no error', 'format error', 'server failure', 'name error',
724             'not implemented' },
725
726   type = dns.type,
727   class = dns.class, }
728
729
730 local function hint (p, s)    -- - - - - - - - - - - - - - - - - - - - - - hint
731   return (hints[s] and hints[s][p[s]]) or ''  end
732
733
734 function resolver.print (response)    -- - - - - - - - - - - - - resolver.print
735
736   for s,s in pairs { 'id', 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z',
737                      'rcode', 'qdcount', 'ancount', 'nscount', 'arcount' } do
738     print ( string.format ('%-30s', 'header.'..s),
739             response.header[s], hint (response.header, s) )
740     end
741
742   for i,question in ipairs (response.question) do
743     print (string.format ('question[%i].name         ', i), question.name)
744     print (string.format ('question[%i].type         ', i), question.type)
745     print (string.format ('question[%i].class        ', i), question.class)
746     end
747
748   local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 }
749   local tmp
750   for s,s in pairs {'answer', 'authority', 'additional'} do
751     for i,rr in pairs (response[s]) do
752       for j,t in pairs { 'name', 'type', 'class', 'ttl', 'rdlength' } do
753         tmp = string.format ('%s[%i].%s', s, i, t)
754         print (string.format ('%-30s', tmp), rr[t], hint (rr, t))
755         end
756       for j,t in pairs (rr) do
757         if not common[j] then
758           tmp = string.format ('%s[%i].%s', s, i, j)
759           print (string.format ('%-30s  %s', tmp, t))
760           end  end  end  end  end
761
762
763 -- module api ------------------------------------------------------ module api
764
765
766 local function resolve (func, ...)    -- - - - - - - - - - - - - - resolver_get
767   dns._resolver = dns._resolver or dns.resolver ()
768   return func (dns._resolver, ...)
769   end
770
771
772 function dns.resolver ()    -- - - - - - - - - - - - - - - - - - - - - resolver
773
774   -- this function seems to be redundant with resolver.new ()
775
776   local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, yielded = {} }
777   setmetatable (r, resolver)
778   setmetatable (r.cache, cache_metatable)
779   setmetatable (r.unsorted, { __mode = 'kv' })
780   return r
781   end
782
783
784 function dns.lookup (...)    -- - - - - - - - - - - - - - - - - - - - -  lookup
785   return resolve (resolver.lookup, ...)  end
786
787
788 function dns.purge (...)    -- - - - - - - - - - - - - - - - - - - - - -  purge
789   return resolve (resolver.purge, ...)  end
790
791 function dns.peek (...)    -- - - - - - - - - - - - - - - - - - - - - - -  peek
792   return resolve (resolver.peek, ...)  end
793
794
795 function dns.query (...)    -- - - - - - - - - - - - - - - - - - - - - -  query
796   return resolve (resolver.query, ...)  end
797
798
799 function dns:socket_wrapper_set (...)    -- - - - - - - - -  socket_wrapper_set
800   return resolve (resolver.socket_wrapper_set, ...)  end
801
802
803 return dns