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'
19 local require = require
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, unpack =
25 ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack
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.class[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)
630 function resolver:receive (rset) -- - - - - - - - - - - - - - - - - receive
632 --print 'receive' print (self.socket)
633 self.time = socket.gettime ()
634 rset = rset or self.socket
637 for i,sock in pairs (rset) do
639 if self.socketset[sock] then
640 local packet = sock:receive ()
643 response = self:decode (packet)
645 --print 'received response'
646 --self.print (response)
648 for i,section in pairs { 'answer', 'authority', 'additional' } do
649 for j,rr in pairs (response[section]) do
650 self:remember (rr, response.question[1].type) end end
653 local queries = self.active[response.header.id]
654 if queries[response.question.raw] then
655 queries[response.question.raw] = nil end
656 if not next (queries) then self.active[response.header.id] = nil end
657 if not next (self.active) then self:closeall () end
659 -- was the query on the wanted list?
660 local q = response.question
661 local cos = get (self.wanted, q.class, q.type, q.name)
663 for co in pairs (cos) do
664 set (self.yielded, co, q.class, q.type, q.name, nil)
665 if coroutine.status(co) == "suspended" then coroutine.resume (co) end
667 set (self.wanted, q.class, q.type, q.name, nil)
674 function resolver:feed(sock, packet)
675 --print 'receive' print (self.socket)
676 self.time = socket.gettime ()
678 local response = self:decode (packet)
680 --print 'received response'
681 --self.print (response)
683 for i,section in pairs { 'answer', 'authority', 'additional' } do
684 for j,rr in pairs (response[section]) do
685 self:remember (rr, response.question[1].type)
690 local queries = self.active[response.header.id]
691 if queries[response.question.raw] then
692 queries[response.question.raw] = nil
694 if not next (queries) then self.active[response.header.id] = nil end
695 if not next (self.active) then self:closeall () end
697 -- was the query on the wanted list?
698 local q = response.question[1]
700 local cos = get (self.wanted, q.class, q.type, q.name)
702 for co in pairs (cos) do
703 set (self.yielded, co, q.class, q.type, q.name, nil)
704 if coroutine.status(co) == "suspended" then coroutine.resume (co) end
706 set (self.wanted, q.class, q.type, q.name, nil)
714 function resolver:cancel(data)
715 local cos = get (self.wanted, unpack(data, 1, 3))
721 function resolver:pulse () -- - - - - - - - - - - - - - - - - - - - - pulse
724 while self:receive() do end
725 if not next (self.active) then return nil end
727 self.time = socket.gettime ()
728 for id,queries in pairs (self.active) do
729 for question,o in pairs (queries) do
730 if self.time >= o.retry then
732 o.server = o.server + 1
733 if o.server > #self.server then
735 o.delay = o.delay + 1
738 if o.delay > #self.delays then
740 queries[question] = nil
741 if not next (queries) then self.active[id] = nil end
742 if not next (self.active) then return nil end
744 --print ('retry', o.server, o.delay)
745 local _a = self.socket[o.server];
746 if _a then _a:send (o.packet) end
747 o.retry = self.time + self.delays[o.delay]
750 if next (self.active) then return true end
755 function resolver:lookup (qname, qtype, qclass) -- - - - - - - - - - lookup
756 self:query (qname, qtype, qclass)
757 while self:pulse () do socket.select (self.socket, nil, 4) end
759 return self:peek (qname, qtype, qclass)
762 function resolver:lookupex (handler, qname, qtype, qclass) -- - - - - - - - - - lookup
763 return self:peek (qname, qtype, qclass) or self:query (qname, qtype, qclass)
767 --print ---------------------------------------------------------------- print
770 local hints = { -- - - - - - - - - - - - - - - - - - - - - - - - - - - hints
771 qr = { [0]='query', 'response' },
772 opcode = { [0]='query', 'inverse query', 'server status request' },
773 aa = { [0]='non-authoritative', 'authoritative' },
774 tc = { [0]='complete', 'truncated' },
775 rd = { [0]='recursion not desired', 'recursion desired' },
776 ra = { [0]='recursion not available', 'recursion available' },
777 z = { [0]='(reserved)' },
778 rcode = { [0]='no error', 'format error', 'server failure', 'name error',
785 local function hint (p, s) -- - - - - - - - - - - - - - - - - - - - - - hint
786 return (hints[s] and hints[s][p[s]]) or '' end
789 function resolver.print (response) -- - - - - - - - - - - - - resolver.print
791 for s,s in pairs { 'id', 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z',
792 'rcode', 'qdcount', 'ancount', 'nscount', 'arcount' } do
793 print ( string.format ('%-30s', 'header.'..s),
794 response.header[s], hint (response.header, s) )
797 for i,question in ipairs (response.question) do
798 print (string.format ('question[%i].name ', i), question.name)
799 print (string.format ('question[%i].type ', i), question.type)
800 print (string.format ('question[%i].class ', i), question.class)
803 local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 }
805 for s,s in pairs {'answer', 'authority', 'additional'} do
806 for i,rr in pairs (response[s]) do
807 for j,t in pairs { 'name', 'type', 'class', 'ttl', 'rdlength' } do
808 tmp = string.format ('%s[%i].%s', s, i, t)
809 print (string.format ('%-30s', tmp), rr[t], hint (rr, t))
811 for j,t in pairs (rr) do
812 if not common[j] then
813 tmp = string.format ('%s[%i].%s', s, i, j)
814 print (string.format ('%-30s %s', tostring(tmp), tostring(t)))
818 -- module api ------------------------------------------------------ module api
821 local function resolve (func, ...) -- - - - - - - - - - - - - - resolver_get
822 dns._resolver = dns._resolver or dns.resolver ()
823 return func (dns._resolver, ...)
827 function dns.resolver () -- - - - - - - - - - - - - - - - - - - - - resolver
829 -- this function seems to be redundant with resolver.new ()
831 local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, yielded = {} }
832 setmetatable (r, resolver)
833 setmetatable (r.cache, cache_metatable)
834 setmetatable (r.unsorted, { __mode = 'kv' })
839 function dns.lookup (...) -- - - - - - - - - - - - - - - - - - - - - lookup
840 return resolve (resolver.lookup, ...) end
843 function dns.purge (...) -- - - - - - - - - - - - - - - - - - - - - - purge
844 return resolve (resolver.purge, ...) end
846 function dns.peek (...) -- - - - - - - - - - - - - - - - - - - - - - - peek
847 return resolve (resolver.peek, ...) end
850 function dns.query (...) -- - - - - - - - - - - - - - - - - - - - - - query
851 return resolve (resolver.query, ...) end
853 function dns.feed (...) -- - - - - - - - - - - - - - - - - - - - - - feed
854 return resolve (resolver.feed, ...) end
856 function dns.cancel(...) -- - - - - - - - - - - - - - - - - - - - - - cancel
857 return resolve(resolver.cancel, ...) end
859 function dns:socket_wrapper_set (...) -- - - - - - - - - socket_wrapper_set
860 return resolve (resolver.socket_wrapper_set, ...) end