3 -- public domain 20080404 lua@ztact.com
6 -- todo: quick (default) header generation
7 -- todo: nxdomain, error handling
8 -- todo: cache results of encodeName
11 -- reference: http://tools.ietf.org/html/rfc1035
12 -- reference: http://tools.ietf.org/html/rfc1876 (LOC)
16 local ztact = require 'util.ztact'
19 local coroutine, io, math, socket, string, table =
20 coroutine, io, math, socket, string, table
22 local ipairs, next, pairs, print, setmetatable, tostring, assert, error =
23 ipairs, next, pairs, print, setmetatable, tostring, assert, error
25 local get, set = ztact.get, ztact.set
28 -------------------------------------------------- module dns
33 -- dns type & class codes ------------------------------ dns type & class codes
36 local append = table.insert
39 local function highbyte (i) -- - - - - - - - - - - - - - - - - - - highbyte
40 return (i-(i%0x100))/0x100
44 local function augment (t) -- - - - - - - - - - - - - - - - - - - - augment
46 for i,s in pairs (t) do a[i] = s a[s] = s a[string.lower (s)] = s end
51 local function encode (t) -- - - - - - - - - - - - - - - - - - - - - encode
53 for i,s in pairs (t) do
54 local word = string.char (highbyte (i), i %0x100)
57 code[string.lower (s)] = word
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] = '*' }
70 dns.classes = { 'IN', 'CS', 'CH', 'HS', [255] = '*' }
73 dns.type = augment (dns.types)
74 dns.class = augment (dns.classes)
75 dns.typecode = encode (dns.types)
76 dns.classcode = encode (dns.classes)
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']
87 local function prune (rrs, time, soft) -- - - - - - - - - - - - - - - prune
89 time = time or socket.gettime ()
90 for i,rr in pairs (rrs) do
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
97 elseif soft == 'soft' then -- What is this? I forget!
103 -- metatables & co. ------------------------------------------ metatables & co.
107 resolver.__index = resolver
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 )
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
131 local rrs_metatable = {} -- - - - - - - - - - - - - - - - - - rrs_metatable
132 function rrs_metatable.__tostring (rrs)
134 for i,rr in pairs (rrs) do append (t, tostring (rr)..'\n') end
135 return table.concat (t)
139 local cache_metatable = {} -- - - - - - - - - - - - - - - - cache_metatable
140 function cache_metatable.__tostring (cache)
141 local time = socket.gettime ()
143 for class,types in pairs (cache) do
144 for type,names in pairs (types) do
145 for name,rrs in pairs (names) do
147 append (t, tostring (rrs)) end end end
148 return table.concat (t)
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' })
161 -- packet layer -------------------------------------------------- packet layer
164 function dns.random (...) -- - - - - - - - - - - - - - - - - - - dns.random
165 math.randomseed (10000*socket.gettime ())
166 dns.random = math.random
167 return dns.random (...)
171 local function encodeHeader (o) -- - - - - - - - - - - - - - - encodeHeader
175 o.id = o.id or -- 16b (random) id
176 dns.random (0, 0xffff)
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
183 -- 2 server status request
185 o.qr = o.qr or 0 -- 1b 0 query, 1 response
187 o.rcode = o.rcode or 0 -- 4b 0 no error
194 o.z = o.z or 0 -- 3b 0 resvered
195 o.ra = o.ra or 0 -- 1b 1 recursion available
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
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 )
216 local function encodeName (name) -- - - - - - - - - - - - - - - - encodeName
218 for part in string.gmatch (name, '[^.]+') do
219 append (t, string.char (string.len (part)))
222 append (t, string.char (0))
223 return table.concat (t)
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;
235 function resolver:byte (len) -- - - - - - - - - - - - - - - - - - - - - byte
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)
246 function resolver:word () -- - - - - - - - - - - - - - - - - - - - - - word
247 local b1, b2 = self:byte (2)
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
259 function resolver:sub (len) -- - - - - - - - - - - - - - - - - - - - - - sub
261 local s = string.sub (self.packet, self.offset, self.offset + len - 1)
262 self.offset = self.offset + len
267 function resolver:header (force) -- - - - - - - - - - - - - - - - - - header
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
273 local h = { id = id }
275 local b1, b2 = self:byte (2)
287 h.qdcount = self:word ()
288 h.ancount = self:word ()
289 h.nscount = self:word ()
290 h.arcount = self:word ()
292 for k,v in pairs (h) do h[k] = v-v%1 end
298 function resolver:name () -- - - - - - - - - - - - - - - - - - - - - - name
299 local remember, pointers = nil, 0
300 local len = self:byte ()
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)..'.')
314 self.offset = remember or self.offset
315 return table.concat (n)
319 function resolver:question () -- - - - - - - - - - - - - - - - - - question
321 q.name = self:name ()
322 q.type = dns.type[self:word ()]
323 q.class = dns.type[self:word ()]
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)
334 function resolver:CNAME (rr) -- - - - - - - - - - - - - - - - - - - - CNAME
335 rr.cname = self:name ()
339 function resolver:MX (rr) -- - - - - - - - - - - - - - - - - - - - - - - MX
340 rr.pref = self:word ()
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))
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 ()
365 local function LOC_tostring_degrees (f, pos, neg) -- - - - - - - - - - - - -
367 if f < 0 then pos = neg f = -f end
373 return string.format ('%3d %2d %2.3f %s', deg, min, msec/1000, pos)
377 function resolver.LOC_tostring (rr) -- - - - - - - - - - - - - LOC_tostring
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]))
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,
394 rr.loc.horiz_pre / 100,
395 rr.loc.vert_pre / 100 ) )
397 return table.concat (t)
401 function resolver:NS (rr) -- - - - - - - - - - - - - - - - - - - - - - - NS
406 function resolver:SOA (rr) -- - - - - - - - - - - - - - - - - - - - - - SOA
410 function resolver:SRV (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 ()
419 function SRV_tostring (rr) -- - - - - - - - - - - - - - - - - - SRV_tostring
421 return string.format ( '%5d %5d %5d %s',
422 s.priority, s.weight, s.port, s.target )
426 function resolver:TXT (rr) -- - - - - - - - - - - - - - - - - - - - - - TXT
427 rr.txt = self:sub (rr.rdlength)
431 function resolver:rr () -- - - - - - - - - - - - - - - - - - - - - - - - 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 ()
440 if rr.ttl == 0 then -- pass
441 else rr.tod = self.time + rr.ttl end
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)
452 function resolver:rrs (count) -- - - - - - - - - - - - - - - - - - - - - rrs
454 for i = 1,count do append (rrs, self:rr ()) end
459 function resolver:decode (packet, force) -- - - - - - - - - - - - - - decode
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 }
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)
473 if not self.active[response.header.id] or
474 not self.active[response.header.id][response.question.raw] then
477 response.answer = self:rrs (response.header.ancount)
478 response.authority = self:rrs (response.header.nscount)
479 response.additional = self:rrs (response.header.arcount)
485 -- socket layer -------------------------------------------------- socket layer
488 resolver.delays = { 1, 3, 11, 45 }
491 function resolver:addnameserver (address) -- - - - - - - - - - addnameserver
492 self.server = self.server or {}
493 append (self.server, address)
497 function resolver:setnameserver (address) -- - - - - - - - - - setnameserver
499 self:addnameserver (address)
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
512 function resolver:getsocket (servernum) -- - - - - - - - - - - - - getsocket
514 self.socket = self.socket or {}
515 self.socketset = self.socketset or {}
517 local sock = self.socket[servernum]
518 if sock then return sock end
521 if self.socket_wrapper then sock = self.socket_wrapper (sock) end
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
532 function resolver:socket_wrapper_set (func) -- - - - - - - socket_wrapper_set
533 self.socket_wrapper = func
537 function resolver:closeall () -- - - - - - - - - - - - - - - - - - closeall
538 for i,sock in ipairs (self.socket) do self.socket[i]:close () end
543 function resolver:remember (rr, type) -- - - - - - - - - - - - - - remember
545 -- print ('remember', type, rr.class, rr.type, rr.name)
549 local all = get (self.cache, rr.class, '*', rr.name)
550 -- print ('remember all', all)
551 if all then append (all, rr) end
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))
559 if type == 'MX' then self.unsorted[rrs] = true end
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)
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
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')
587 else self.cache = {} end
591 function resolver:query (qname, qtype, qclass) -- - - - - - - - - - -- query
593 qname, qtype, qclass = standardize (qname, qtype, qclass)
595 if not self.server then self:adddefaultnameservers () end
597 local question = encodeQuestion (qname, qtype, qclass)
598 local peek = self:peek (qname, qtype, qclass)
599 if peek then return peek end
601 local header, id = encodeHeader ()
602 -- print ('query id', id, qclass, qtype, qname)
603 local o = { packet = header..question,
606 retry = socket.gettime () + self.delays[1] }
607 self:getsocket (o.server):send (o.packet)
609 -- remember the query
610 self.active[id] = self.active[id] or {}
611 self.active[id][question] = o
613 -- remember which coroutine wants the answer
614 local co = coroutine.running ()
616 set (self.wanted, qclass, qtype, qname, co, true)
617 set (self.yielded, co, qclass, qtype, qname, true)
621 function resolver:receive (rset) -- - - - - - - - - - - - - - - - - receive
623 -- print 'receive' print (self.socket)
624 self.time = socket.gettime ()
625 rset = rset or self.socket
628 for i,sock in pairs (rset) do
630 if self.socketset[sock] then
631 local packet = sock:receive ()
634 response = self:decode (packet)
636 -- print 'received response'
637 -- self.print (response)
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
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
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)
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
658 set (self.wanted, q.class, q.type, q.name, nil)
665 function resolver:pulse () -- - - - - - - - - - - - - - - - - - - - - pulse
668 while self:receive () do end
669 if not next (self.active) then return nil end
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
676 o.server = o.server + 1
677 if o.server > #self.server then
679 o.delay = o.delay + 1
682 if o.delay > #self.delays then
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
688 -- print ('retry', o.server, o.delay)
689 self.socket[o.server]:send (o.packet)
690 o.retry = self.time + self.delays[o.delay]
693 if next (self.active) then return true end
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)
706 -- print ---------------------------------------------------------------- print
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',
724 local function hint (p, s) -- - - - - - - - - - - - - - - - - - - - - - hint
725 return (hints[s] and hints[s][p[s]]) or '' end
728 function resolver.print (response) -- - - - - - - - - - - - - resolver.print
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) )
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)
742 local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 }
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))
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))
757 -- module api ------------------------------------------------------ module api
760 local function resolve (func, ...) -- - - - - - - - - - - - - - resolver_get
761 dns._resolver = dns._resolver or dns.resolver ()
762 return func (dns._resolver, ...)
766 function dns.resolver () -- - - - - - - - - - - - - - - - - - - - - resolver
768 -- this function seems to be redundant with resolver.new ()
770 local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, yielded = {} }
771 setmetatable (r, resolver)
772 setmetatable (r.cache, cache_metatable)
773 setmetatable (r.unsorted, { __mode = 'kv' })
778 function dns.lookup (...) -- - - - - - - - - - - - - - - - - - - - - lookup
779 return resolve (resolver.lookup, ...) end
782 function dns.purge (...) -- - - - - - - - - - - - - - - - - - - - - - purge
783 return resolve (resolver.purge, ...) end
785 function dns.peek (...) -- - - - - - - - - - - - - - - - - - - - - - - peek
786 return resolve (resolver.peek, ...) end
789 function dns.query (...) -- - - - - - - - - - - - - - - - - - - - - - query
790 return resolve (resolver.query, ...) end
793 function dns:socket_wrapper_set (...) -- - - - - - - - - socket_wrapper_set
794 return resolve (resolver.socket_wrapper_set, ...) end