Automated merge with http://waqas.ath.cx:8000/
[prosody.git] / net / dns.lua
1 -- Prosody IM v0.2
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
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 =
25       ipairs, next, pairs, print, setmetatable, tostring, assert, error
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.type[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  end
626
627
628 function resolver:receive (rset)    -- - - - - - - - - - - - - - - - -  receive
629
630   -- print 'receive'  print (self.socket)
631   self.time = socket.gettime ()
632   rset = rset or self.socket
633
634   local response
635   for i,sock in pairs (rset) do
636
637     if self.socketset[sock] then
638     local packet = sock:receive ()
639     if packet then
640
641     response = self:decode (packet)
642     if response then
643     -- print 'received response'
644     -- self.print (response)
645
646     for i,section in pairs { 'answer', 'authority', 'additional' } do
647       for j,rr in pairs (response[section]) do
648         self:remember (rr, response.question[1].type)  end  end
649
650     -- retire the query
651     local queries = self.active[response.header.id]
652     if queries[response.question.raw] then
653       queries[response.question.raw] = nil  end
654     if not next (queries) then  self.active[response.header.id] = nil  end
655     if not next (self.active) then  self:closeall ()  end
656
657     -- was the query on the wanted list?
658     local q = response.question
659     local cos = get (self.wanted, q.class, q.type, q.name)
660     if cos then
661       for co in pairs (cos) do
662         set (self.yielded, co, q.class, q.type, q.name, nil)
663         if not self.yielded[co] then  coroutine.resume (co)  end
664         end
665       set (self.wanted, q.class, q.type, q.name, nil)
666       end  end  end  end  end
667
668   return response
669   end
670
671
672 function resolver:pulse ()    -- - - - - - - - - - - - - - - - - - - - -  pulse
673
674   -- print ':pulse'
675   while self:receive () do end
676   if not next (self.active) then  return nil  end
677
678   self.time = socket.gettime ()
679   for id,queries in pairs (self.active) do
680     for question,o in pairs (queries) do
681       if self.time >= o.retry then
682
683         o.server = o.server + 1
684         if o.server > #self.server then
685           o.server = 1
686           o.delay = o.delay + 1
687           end
688
689         if o.delay > #self.delays then
690           print ('timeout')
691           queries[question] = nil
692           if not next (queries) then  self.active[id] = nil  end
693           if not next (self.active) then  return nil  end
694         else
695           -- print ('retry', o.server, o.delay)
696           local _a = self.socket[o.server];
697           if _a then _a:send (o.packet) end
698           o.retry = self.time + self.delays[o.delay]
699           end  end  end  end
700
701   if next (self.active) then  return true  end
702   return nil
703   end
704
705
706 function resolver:lookup (qname, qtype, qclass)    -- - - - - - - - - -  lookup
707   self:query (qname, qtype, qclass)
708   while self:pulse () do  socket.select (self.socket, nil, 4)  end
709   -- print (self.cache)
710   return self:peek (qname, qtype, qclass)
711   end
712
713
714 -- print ---------------------------------------------------------------- print
715
716
717 local hints = {    -- - - - - - - - - - - - - - - - - - - - - - - - - - - hints
718   qr = { [0]='query', 'response' },
719   opcode = { [0]='query', 'inverse query', 'server status request' },
720   aa = { [0]='non-authoritative', 'authoritative' },
721   tc = { [0]='complete', 'truncated' },
722   rd = { [0]='recursion not desired', 'recursion desired' },
723   ra = { [0]='recursion not available', 'recursion available' },
724   z  = { [0]='(reserved)' },
725   rcode = { [0]='no error', 'format error', 'server failure', 'name error',
726             'not implemented' },
727
728   type = dns.type,
729   class = dns.class, }
730
731
732 local function hint (p, s)    -- - - - - - - - - - - - - - - - - - - - - - hint
733   return (hints[s] and hints[s][p[s]]) or ''  end
734
735
736 function resolver.print (response)    -- - - - - - - - - - - - - resolver.print
737
738   for s,s in pairs { 'id', 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z',
739                      'rcode', 'qdcount', 'ancount', 'nscount', 'arcount' } do
740     print ( string.format ('%-30s', 'header.'..s),
741             response.header[s], hint (response.header, s) )
742     end
743
744   for i,question in ipairs (response.question) do
745     print (string.format ('question[%i].name         ', i), question.name)
746     print (string.format ('question[%i].type         ', i), question.type)
747     print (string.format ('question[%i].class        ', i), question.class)
748     end
749
750   local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 }
751   local tmp
752   for s,s in pairs {'answer', 'authority', 'additional'} do
753     for i,rr in pairs (response[s]) do
754       for j,t in pairs { 'name', 'type', 'class', 'ttl', 'rdlength' } do
755         tmp = string.format ('%s[%i].%s', s, i, t)
756         print (string.format ('%-30s', tmp), rr[t], hint (rr, t))
757         end
758       for j,t in pairs (rr) do
759         if not common[j] then
760           tmp = string.format ('%s[%i].%s', s, i, j)
761           print (string.format ('%-30s  %s', tmp, t))
762           end  end  end  end  end
763
764
765 -- module api ------------------------------------------------------ module api
766
767
768 local function resolve (func, ...)    -- - - - - - - - - - - - - - resolver_get
769   dns._resolver = dns._resolver or dns.resolver ()
770   return func (dns._resolver, ...)
771   end
772
773
774 function dns.resolver ()    -- - - - - - - - - - - - - - - - - - - - - resolver
775
776   -- this function seems to be redundant with resolver.new ()
777
778   local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, yielded = {} }
779   setmetatable (r, resolver)
780   setmetatable (r.cache, cache_metatable)
781   setmetatable (r.unsorted, { __mode = 'kv' })
782   return r
783   end
784
785
786 function dns.lookup (...)    -- - - - - - - - - - - - - - - - - - - - -  lookup
787   return resolve (resolver.lookup, ...)  end
788
789
790 function dns.purge (...)    -- - - - - - - - - - - - - - - - - - - - - -  purge
791   return resolve (resolver.purge, ...)  end
792
793 function dns.peek (...)    -- - - - - - - - - - - - - - - - - - - - - - -  peek
794   return resolve (resolver.peek, ...)  end
795
796
797 function dns.query (...)    -- - - - - - - - - - - - - - - - - - - - - -  query
798   return resolve (resolver.query, ...)  end
799
800
801 function dns:socket_wrapper_set (...)    -- - - - - - - - -  socket_wrapper_set
802   return resolve (resolver.socket_wrapper_set, ...)  end
803
804
805 return dns