Comparison

net/dns.lua @ 337:4a1dd1c2c219

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