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");
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
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")
517 function resolver:getsocket (servernum) -- - - - - - - - - - - - - getsocket
519 self.socket = self.socket or {}
520 self.socketset = self.socketset or {}
522 local sock = self.socket[servernum]
523 if sock then return sock end
526 if self.socket_wrapper then sock = self.socket_wrapper (sock) end
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
537 function resolver:socket_wrapper_set (func) -- - - - - - - socket_wrapper_set
538 self.socket_wrapper = func
542 function resolver:closeall () -- - - - - - - - - - - - - - - - - - closeall
543 for i,sock in ipairs (self.socket) do self.socket[i]:close () end
548 function resolver:remember (rr, type) -- - - - - - - - - - - - - - remember
550 -- print ('remember', type, rr.class, rr.type, rr.name)
554 local all = get (self.cache, rr.class, '*', rr.name)
555 -- print ('remember all', all)
556 if all then append (all, rr) end
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))
564 if type == 'MX' then self.unsorted[rrs] = true end
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)
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
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')
592 else self.cache = {} end
596 function resolver:query (qname, qtype, qclass) -- - - - - - - - - - -- query
598 qname, qtype, qclass = standardize (qname, qtype, qclass)
600 if not self.server then self:adddefaultnameservers () end
602 local question = encodeQuestion (qname, qtype, qclass)
603 local peek = self:peek (qname, qtype, qclass)
604 if peek then return peek end
606 local header, id = encodeHeader ()
607 -- print ('query id', id, qclass, qtype, qname)
608 local o = { packet = header..question,
611 retry = socket.gettime () + self.delays[1] }
612 self:getsocket (o.server):send (o.packet)
614 -- remember the query
615 self.active[id] = self.active[id] or {}
616 self.active[id][question] = o
618 -- remember which coroutine wants the answer
619 local co = coroutine.running ()
621 set (self.wanted, qclass, qtype, qname, co, true)
622 set (self.yielded, co, qclass, qtype, qname, true)
626 function resolver:receive (rset) -- - - - - - - - - - - - - - - - - receive
628 -- print 'receive' print (self.socket)
629 self.time = socket.gettime ()
630 rset = rset or self.socket
633 for i,sock in pairs (rset) do
635 if self.socketset[sock] then
636 local packet = sock:receive ()
639 response = self:decode (packet)
641 -- print 'received response'
642 -- self.print (response)
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
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
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)
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
663 set (self.wanted, q.class, q.type, q.name, nil)
670 function resolver:pulse () -- - - - - - - - - - - - - - - - - - - - - pulse
673 while self:receive () do end
674 if not next (self.active) then return nil end
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
681 o.server = o.server + 1
682 if o.server > #self.server then
684 o.delay = o.delay + 1
687 if o.delay > #self.delays then
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
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]
699 if next (self.active) then return true end
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)
712 -- print ---------------------------------------------------------------- print
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',
730 local function hint (p, s) -- - - - - - - - - - - - - - - - - - - - - - hint
731 return (hints[s] and hints[s][p[s]]) or '' end
734 function resolver.print (response) -- - - - - - - - - - - - - resolver.print
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) )
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)
748 local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 }
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))
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))
763 -- module api ------------------------------------------------------ module api
766 local function resolve (func, ...) -- - - - - - - - - - - - - - resolver_get
767 dns._resolver = dns._resolver or dns.resolver ()
768 return func (dns._resolver, ...)
772 function dns.resolver () -- - - - - - - - - - - - - - - - - - - - - resolver
774 -- this function seems to be redundant with resolver.new ()
776 local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, yielded = {} }
777 setmetatable (r, resolver)
778 setmetatable (r.cache, cache_metatable)
779 setmetatable (r.unsorted, { __mode = 'kv' })
784 function dns.lookup (...) -- - - - - - - - - - - - - - - - - - - - - lookup
785 return resolve (resolver.lookup, ...) end
788 function dns.purge (...) -- - - - - - - - - - - - - - - - - - - - - - purge
789 return resolve (resolver.purge, ...) end
791 function dns.peek (...) -- - - - - - - - - - - - - - - - - - - - - - - peek
792 return resolve (resolver.peek, ...) end
795 function dns.query (...) -- - - - - - - - - - - - - - - - - - - - - - query
796 return resolve (resolver.query, ...) end
799 function dns:socket_wrapper_set (...) -- - - - - - - - - socket_wrapper_set
800 return resolve (resolver.socket_wrapper_set, ...) end