2 -- This file is included with Prosody IM. It has modifications,
3 -- which are hereby placed in the public domain.
5 -- public domain 20080404 lua@ztact.com
8 -- todo: quick (default) header generation
9 -- todo: nxdomain, error handling
10 -- todo: cache results of encodeName
13 -- reference: http://tools.ietf.org/html/rfc1035
14 -- reference: http://tools.ietf.org/html/rfc1876 (LOC)
18 local ztact = require 'util.ztact'
21 local coroutine, io, math, socket, string, table =
22 coroutine, io, math, socket, string, table
24 local ipairs, next, pairs, print, setmetatable, tostring, assert, error =
25 ipairs, next, pairs, print, setmetatable, tostring, assert, error
27 local get, set = ztact.get, ztact.set
30 -------------------------------------------------- module dns
35 -- dns type & class codes ------------------------------ dns type & class codes
38 local append = table.insert
41 local function highbyte (i) -- - - - - - - - - - - - - - - - - - - highbyte
42 return (i-(i%0x100))/0x100
46 local function augment (t) -- - - - - - - - - - - - - - - - - - - - augment
48 for i,s in pairs (t) do a[i] = s a[s] = s a[string.lower (s)] = s end
53 local function encode (t) -- - - - - - - - - - - - - - - - - - - - - encode
55 for i,s in pairs (t) do
56 local word = string.char (highbyte (i), i %0x100)
59 code[string.lower (s)] = word
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] = '*' }
72 dns.classes = { 'IN', 'CS', 'CH', 'HS', [255] = '*' }
75 dns.type = augment (dns.types)
76 dns.class = augment (dns.classes)
77 dns.typecode = encode (dns.types)
78 dns.classcode = encode (dns.classes)
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']
89 local function prune (rrs, time, soft) -- - - - - - - - - - - - - - - prune
91 time = time or socket.gettime ()
92 for i,rr in pairs (rrs) do
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
99 elseif soft == 'soft' then -- What is this? I forget!
105 -- metatables & co. ------------------------------------------ metatables & co.
109 resolver.__index = resolver
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 )
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
133 local rrs_metatable = {} -- - - - - - - - - - - - - - - - - - rrs_metatable
134 function rrs_metatable.__tostring (rrs)
136 for i,rr in pairs (rrs) do append (t, tostring (rr)..'\n') end
137 return table.concat (t)
141 local cache_metatable = {} -- - - - - - - - - - - - - - - - cache_metatable
142 function cache_metatable.__tostring (cache)
143 local time = socket.gettime ()
145 for class,types in pairs (cache) do
146 for type,names in pairs (types) do
147 for name,rrs in pairs (names) do
149 append (t, tostring (rrs)) end end end
150 return table.concat (t)
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' })
163 -- packet layer -------------------------------------------------- packet layer
166 function dns.random (...) -- - - - - - - - - - - - - - - - - - - dns.random
167 math.randomseed (10000*socket.gettime ())
168 dns.random = math.random
169 return dns.random (...)
173 local function encodeHeader (o) -- - - - - - - - - - - - - - - encodeHeader
177 o.id = o.id or -- 16b (random) id
178 dns.random (0, 0xffff)
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
185 -- 2 server status request
187 o.qr = o.qr or 0 -- 1b 0 query, 1 response
189 o.rcode = o.rcode or 0 -- 4b 0 no error
196 o.z = o.z or 0 -- 3b 0 resvered
197 o.ra = o.ra or 0 -- 1b 1 recursion available
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
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 )
218 local function encodeName (name) -- - - - - - - - - - - - - - - - encodeName
220 for part in string.gmatch (name, '[^.]+') do
221 append (t, string.char (string.len (part)))
224 append (t, string.char (0))
225 return table.concat (t)
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;
237 function resolver:byte (len) -- - - - - - - - - - - - - - - - - - - - - byte
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)
248 function resolver:word () -- - - - - - - - - - - - - - - - - - - - - - word
249 local b1, b2 = self:byte (2)
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
261 function resolver:sub (len) -- - - - - - - - - - - - - - - - - - - - - - sub
263 local s = string.sub (self.packet, self.offset, self.offset + len - 1)
264 self.offset = self.offset + len
269 function resolver:header (force) -- - - - - - - - - - - - - - - - - - header
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
275 local h = { id = id }
277 local b1, b2 = self:byte (2)
289 h.qdcount = self:word ()
290 h.ancount = self:word ()
291 h.nscount = self:word ()
292 h.arcount = self:word ()
294 for k,v in pairs (h) do h[k] = v-v%1 end
300 function resolver:name () -- - - - - - - - - - - - - - - - - - - - - - name
301 local remember, pointers = nil, 0
302 local len = self:byte ()
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)..'.')
316 self.offset = remember or self.offset
317 return table.concat (n)
321 function resolver:question () -- - - - - - - - - - - - - - - - - - question
323 q.name = self:name ()
324 q.type = dns.type[self:word ()]
325 q.class = dns.type[self:word ()]
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)
336 function resolver:CNAME (rr) -- - - - - - - - - - - - - - - - - - - - CNAME
337 rr.cname = self:name ()
341 function resolver:MX (rr) -- - - - - - - - - - - - - - - - - - - - - - - MX
342 rr.pref = self:word ()
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))
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 ()
367 local function LOC_tostring_degrees (f, pos, neg) -- - - - - - - - - - - - -
369 if f < 0 then pos = neg f = -f end
375 return string.format ('%3d %2d %2.3f %s', deg, min, msec/1000, pos)
379 function resolver.LOC_tostring (rr) -- - - - - - - - - - - - - LOC_tostring
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]))
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,
396 rr.loc.horiz_pre / 100,
397 rr.loc.vert_pre / 100 ) )
399 return table.concat (t)
403 function resolver:NS (rr) -- - - - - - - - - - - - - - - - - - - - - - - NS
408 function resolver:SOA (rr) -- - - - - - - - - - - - - - - - - - - - - - SOA
412 function resolver:SRV (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 ()
421 function SRV_tostring (rr) -- - - - - - - - - - - - - - - - - - SRV_tostring
423 return string.format ( '%5d %5d %5d %s',
424 s.priority, s.weight, s.port, s.target )
428 function resolver:TXT (rr) -- - - - - - - - - - - - - - - - - - - - - - TXT
429 rr.txt = self:sub (rr.rdlength)
433 function resolver:rr () -- - - - - - - - - - - - - - - - - - - - - - - - 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 ()
442 if rr.ttl == 0 then -- pass
443 else rr.tod = self.time + rr.ttl end
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)
454 function resolver:rrs (count) -- - - - - - - - - - - - - - - - - - - - - rrs
456 for i = 1,count do append (rrs, self:rr ()) end
461 function resolver:decode (packet, force) -- - - - - - - - - - - - - - decode
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 }
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)
475 if not self.active[response.header.id] or
476 not self.active[response.header.id][response.question.raw] then
479 response.answer = self:rrs (response.header.ancount)
480 response.authority = self:rrs (response.header.nscount)
481 response.additional = self:rrs (response.header.arcount)
487 -- socket layer -------------------------------------------------- socket layer
490 resolver.delays = { 1, 3, 11, 45 }
493 function resolver:addnameserver (address) -- - - - - - - - - - addnameserver
494 self.server = self.server or {}
495 append (self.server, address)
499 function resolver:setnameserver (address) -- - - - - - - - - - setnameserver
501 self:addnameserver (address)
505 function resolver:adddefaultnameservers () -- - - - - adddefaultnameservers
506 local resolv_conf = io.open("/etc/resolv.conf");
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
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")
519 function resolver:getsocket (servernum) -- - - - - - - - - - - - - getsocket
521 self.socket = self.socket or {}
522 self.socketset = self.socketset or {}
524 local sock = self.socket[servernum]
525 if sock then return sock end
528 if self.socket_wrapper then sock = self.socket_wrapper (sock) end
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
539 function resolver:socket_wrapper_set (func) -- - - - - - - socket_wrapper_set
540 self.socket_wrapper = func
544 function resolver:closeall () -- - - - - - - - - - - - - - - - - - closeall
545 for i,sock in ipairs (self.socket) do self.socket[i]:close () end
550 function resolver:remember (rr, type) -- - - - - - - - - - - - - - remember
552 -- print ('remember', type, rr.class, rr.type, rr.name)
556 local all = get (self.cache, rr.class, '*', rr.name)
557 -- print ('remember all', all)
558 if all then append (all, rr) end
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))
566 if type == 'MX' then self.unsorted[rrs] = true end
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)
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
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')
594 else self.cache = {} end
598 function resolver:query (qname, qtype, qclass) -- - - - - - - - - - -- query
600 qname, qtype, qclass = standardize (qname, qtype, qclass)
602 if not self.server then self:adddefaultnameservers () end
604 local question = encodeQuestion (qname, qtype, qclass)
605 local peek = self:peek (qname, qtype, qclass)
606 if peek then return peek end
608 local header, id = encodeHeader ()
609 -- print ('query id', id, qclass, qtype, qname)
610 local o = { packet = header..question,
613 retry = socket.gettime () + self.delays[1] }
614 self:getsocket (o.server):send (o.packet)
616 -- remember the query
617 self.active[id] = self.active[id] or {}
618 self.active[id][question] = o
620 -- remember which coroutine wants the answer
621 local co = coroutine.running ()
623 set (self.wanted, qclass, qtype, qname, co, true)
624 set (self.yielded, co, qclass, qtype, qname, true)
628 function resolver:receive (rset) -- - - - - - - - - - - - - - - - - receive
630 -- print 'receive' print (self.socket)
631 self.time = socket.gettime ()
632 rset = rset or self.socket
635 for i,sock in pairs (rset) do
637 if self.socketset[sock] then
638 local packet = sock:receive ()
641 response = self:decode (packet)
643 -- print 'received response'
644 -- self.print (response)
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
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
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)
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
665 set (self.wanted, q.class, q.type, q.name, nil)
672 function resolver:pulse () -- - - - - - - - - - - - - - - - - - - - - pulse
675 while self:receive () do end
676 if not next (self.active) then return nil end
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
683 o.server = o.server + 1
684 if o.server > #self.server then
686 o.delay = o.delay + 1
689 if o.delay > #self.delays then
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
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]
701 if next (self.active) then return true end
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)
714 -- print ---------------------------------------------------------------- print
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',
732 local function hint (p, s) -- - - - - - - - - - - - - - - - - - - - - - hint
733 return (hints[s] and hints[s][p[s]]) or '' end
736 function resolver.print (response) -- - - - - - - - - - - - - resolver.print
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) )
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)
750 local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 }
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))
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))
765 -- module api ------------------------------------------------------ module api
768 local function resolve (func, ...) -- - - - - - - - - - - - - - resolver_get
769 dns._resolver = dns._resolver or dns.resolver ()
770 return func (dns._resolver, ...)
774 function dns.resolver () -- - - - - - - - - - - - - - - - - - - - - resolver
776 -- this function seems to be redundant with resolver.new ()
778 local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, yielded = {} }
779 setmetatable (r, resolver)
780 setmetatable (r.cache, cache_metatable)
781 setmetatable (r.unsorted, { __mode = 'kv' })
786 function dns.lookup (...) -- - - - - - - - - - - - - - - - - - - - - lookup
787 return resolve (resolver.lookup, ...) end
790 function dns.purge (...) -- - - - - - - - - - - - - - - - - - - - - - purge
791 return resolve (resolver.purge, ...) end
793 function dns.peek (...) -- - - - - - - - - - - - - - - - - - - - - - - peek
794 return resolve (resolver.peek, ...) end
797 function dns.query (...) -- - - - - - - - - - - - - - - - - - - - - - query
798 return resolve (resolver.query, ...) end
801 function dns:socket_wrapper_set (...) -- - - - - - - - - socket_wrapper_set
802 return resolve (resolver.socket_wrapper_set, ...) end