aboutsummaryrefslogtreecommitdiffstats
path: root/main/lua-dns
diff options
context:
space:
mode:
authorNatanael Copa <ncopa@alpinelinux.org>2013-02-07 19:13:33 +0000
committerNatanael Copa <ncopa@alpinelinux.org>2013-02-07 19:50:17 +0000
commita4eda4ac0ff674d5500be1f46311826df319cd6b (patch)
tree2389e71f83b21d72f48d40d60c5c670194139817 /main/lua-dns
parent50e41491b5d8d5f2f4cf643a252dcf134208ef95 (diff)
downloadaports-a4eda4ac0ff674d5500be1f46311826df319cd6b.tar.bz2
aports-a4eda4ac0ff674d5500be1f46311826df319cd6b.tar.xz
main/lua-dns: rebuild due to missing origin
Diffstat (limited to 'main/lua-dns')
-rw-r--r--main/lua-dns/APKBUILD11
-rw-r--r--main/lua-dns/dns.lua795
-rw-r--r--main/lua-dns/ztact.lua364
3 files changed, 1166 insertions, 4 deletions
diff --git a/main/lua-dns/APKBUILD b/main/lua-dns/APKBUILD
index 23f7d0497d..35cdc8d447 100644
--- a/main/lua-dns/APKBUILD
+++ b/main/lua-dns/APKBUILD
@@ -1,7 +1,7 @@
# Maintainer: Natanael Copa <ncopa@alpinelinux.org>
pkgname=lua-dns
pkgver=20080404
-pkgrel=1
+pkgrel=2
pkgdesc="a DNS client library written in Lua using LuaSocket"
url="http://ztact.com/software/dns.lua"
arch="noarch"
@@ -10,9 +10,8 @@ depends="lua lua-socket"
makedepends=
install=
subpackages=
-source="http://ztact.com/files/src/ztact-lua/dns.lua
- http://ztact.com/files/src/ztact-lua/ztact.lua
- "
+# Was found here: http://ztact.com/files/src/ztact-lua/
+source="dns.lua ztact.lua"
prepare() {
:
@@ -29,3 +28,7 @@ package() {
md5sums="8a085a4cf7accf2f6e798c507709bc78 dns.lua
a83eb072dece91c5db043f5884ee00b0 ztact.lua"
+sha256sums="7dab246c0f68f028eca8f636f2000dd394ce7b4f725d74c65bbf8228954a11ac dns.lua
+4476af73b3361b59f8f322abe4f28e1f50a0868d617459c50d4d34009e83ed6a ztact.lua"
+sha512sums="d0d72b1ab652b55d573f8c2087709e5ca59525b9429579bca89121209b6f19a4db1e3a63681841cc23c892b5936ad460335dfc275c673ac6728c2c7b71f1faab dns.lua
+d0d59a58deea4552924f5ed11ca9ab404ed0b9f3c14b6834bd76c3a0f714b4b427e31682bce9880cdd110ec3b519e84839ff111af4df6f70c052fa2408e081bc ztact.lua"
diff --git a/main/lua-dns/dns.lua b/main/lua-dns/dns.lua
new file mode 100644
index 0000000000..31e07f5b49
--- /dev/null
+++ b/main/lua-dns/dns.lua
@@ -0,0 +1,795 @@
+
+
+-- public domain 20080404 lua@ztact.com
+
+
+-- todo: quick (default) header generation
+-- todo: nxdomain, error handling
+-- todo: cache results of encodeName
+
+
+-- reference: http://tools.ietf.org/html/rfc1035
+-- reference: http://tools.ietf.org/html/rfc1876 (LOC)
+
+
+require 'socket'
+require 'ztact'
+
+
+local coroutine, io, math, socket, string, table =
+ coroutine, io, math, socket, string, table
+
+local ipairs, next, pairs, print, setmetatable, tostring =
+ ipairs, next, pairs, print, setmetatable, tostring
+
+local get, set = ztact.get, ztact.set
+
+
+local dns = _G -------------------------------------------------- module dns
+module ((...) or 'dns')
+dns = dns[(...) or 'dns']
+
+
+-- dns type & class codes ------------------------------ dns type & class codes
+
+
+local append = table.insert
+
+
+local function highbyte (i) -- - - - - - - - - - - - - - - - - - - highbyte
+ return (i-(i%0x100))/0x100
+ end
+
+
+local function augment (t) -- - - - - - - - - - - - - - - - - - - - augment
+ local a = {}
+ for i,s in pairs (t) do a[i] = s a[s] = s a[string.lower (s)] = s end
+ return a
+ end
+
+
+local function encode (t) -- - - - - - - - - - - - - - - - - - - - - encode
+ local code = {}
+ for i,s in pairs (t) do
+ local word = string.char (highbyte (i), i %0x100)
+ code[i] = word
+ code[s] = word
+ code[string.lower (s)] = word
+ end
+ return code
+ end
+
+
+dns.types = {
+ 'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR', 'NULL', 'WKS',
+ 'PTR', 'HINFO', 'MINFO', 'MX', 'TXT',
+ [ 28] = 'AAAA', [ 29] = 'LOC', [ 33] = 'SRV',
+ [252] = 'AXFR', [253] = 'MAILB', [254] = 'MAILA', [255] = '*' }
+
+
+dns.classes = { 'IN', 'CS', 'CH', 'HS', [255] = '*' }
+
+
+dns.type = augment (dns.types)
+dns.class = augment (dns.classes)
+dns.typecode = encode (dns.types)
+dns.classcode = encode (dns.classes)
+
+
+
+local function standardize (qname, qtype, qclass) -- - - - - - - standardize
+ if string.byte (qname, -1) ~= 0x2E then qname = qname..'.' end
+ qname = string.lower (qname)
+ return qname, dns.type[qtype or 'A'], dns.class[qclass or 'IN']
+ end
+
+
+local function prune (rrs, time, soft) -- - - - - - - - - - - - - - - prune
+
+ time = time or socket.gettime ()
+ for i,rr in pairs (rrs) do
+
+ if rr.tod then
+ -- rr.tod = rr.tod - 50 -- accelerated decripitude
+ rr.ttl = math.floor (rr.tod - time)
+ if rr.ttl <= 0 then rrs[i] = nil end
+
+ elseif soft == 'soft' then -- What is this? I forget!
+ assert (rr.ttl == 0)
+ rrs[i] = nil
+ end end end
+
+
+-- metatables & co. ------------------------------------------ metatables & co.
+
+
+local resolver = {}
+resolver.__index = resolver
+
+
+local SRV_tostring
+
+
+local rr_metatable = {} -- - - - - - - - - - - - - - - - - - - rr_metatable
+function rr_metatable.__tostring (rr)
+ local s0 = string.format (
+ '%2s %-5s %6i %-28s', rr.class, rr.type, rr.ttl, rr.name )
+ local s1 = ''
+ if rr.type == 'A' then s1 = ' '..rr.a
+ elseif rr.type == 'MX' then
+ s1 = string.format (' %2i %s', rr.pref, rr.mx)
+ elseif rr.type == 'CNAME' then s1 = ' '..rr.cname
+ elseif rr.type == 'LOC' then s1 = ' '..resolver.LOC_tostring (rr)
+ elseif rr.type == 'NS' then s1 = ' '..rr.ns
+ elseif rr.type == 'SRV' then s1 = ' '..SRV_tostring (rr)
+ elseif rr.type == 'TXT' then s1 = ' '..rr.txt
+ else s1 = ' <UNKNOWN RDATA TYPE>' end
+ return s0..s1
+ end
+
+
+local rrs_metatable = {} -- - - - - - - - - - - - - - - - - - rrs_metatable
+function rrs_metatable.__tostring (rrs)
+ t = {}
+ for i,rr in pairs (rrs) do append (t, tostring (rr)..'\n') end
+ return table.concat (t)
+ end
+
+
+local cache_metatable = {} -- - - - - - - - - - - - - - - - cache_metatable
+function cache_metatable.__tostring (cache)
+ local time = socket.gettime ()
+ local t = {}
+ for class,types in pairs (cache) do
+ for type,names in pairs (types) do
+ for name,rrs in pairs (names) do
+ prune (rrs, time)
+ append (t, tostring (rrs)) end end end
+ return table.concat (t)
+ end
+
+
+function resolver:new () -- - - - - - - - - - - - - - - - - - - - - resolver
+ local r = { active = {}, cache = {}, unsorted = {} }
+ setmetatable (r, resolver)
+ setmetatable (r.cache, cache_metatable)
+ setmetatable (r.unsorted, { __mode = 'kv' })
+ return r
+ end
+
+
+-- packet layer -------------------------------------------------- packet layer
+
+
+function dns.random (...) -- - - - - - - - - - - - - - - - - - - dns.random
+ math.randomseed (10000*socket.gettime ())
+ dns.random = math.random
+ return dns.random (...)
+ end
+
+
+local function encodeHeader (o) -- - - - - - - - - - - - - - - encodeHeader
+
+ o = o or {}
+
+ o.id = o.id or -- 16b (random) id
+ dns.random (0, 0xffff)
+
+ o.rd = o.rd or 1 -- 1b 1 recursion desired
+ o.tc = o.tc or 0 -- 1b 1 truncated response
+ o.aa = o.aa or 0 -- 1b 1 authoritative response
+ o.opcode = o.opcode or 0 -- 4b 0 query
+ -- 1 inverse query
+ -- 2 server status request
+ -- 3-15 reserved
+ o.qr = o.qr or 0 -- 1b 0 query, 1 response
+
+ o.rcode = o.rcode or 0 -- 4b 0 no error
+ -- 1 format error
+ -- 2 server failure
+ -- 3 name error
+ -- 4 not implemented
+ -- 5 refused
+ -- 6-15 reserved
+ o.z = o.z or 0 -- 3b 0 resvered
+ o.ra = o.ra or 0 -- 1b 1 recursion available
+
+ o.qdcount = o.qdcount or 1 -- 16b number of question RRs
+ o.ancount = o.ancount or 0 -- 16b number of answers RRs
+ o.nscount = o.nscount or 0 -- 16b number of nameservers RRs
+ o.arcount = o.arcount or 0 -- 16b number of additional RRs
+
+ -- string.char() rounds, so prevent roundup with -0.4999
+ local header = string.char (
+ highbyte (o.id), o.id %0x100,
+ o.rd + 2*o.tc + 4*o.aa + 8*o.opcode + 128*o.qr,
+ o.rcode + 16*o.z + 128*o.ra,
+ highbyte (o.qdcount), o.qdcount %0x100,
+ highbyte (o.ancount), o.ancount %0x100,
+ highbyte (o.nscount), o.nscount %0x100,
+ highbyte (o.arcount), o.arcount %0x100 )
+
+ return header, o.id
+ end
+
+
+local function encodeName (name) -- - - - - - - - - - - - - - - - encodeName
+ local t = {}
+ for part in string.gmatch (name, '[^.]+') do
+ append (t, string.char (string.len (part)))
+ append (t, part)
+ end
+ append (t, string.char (0))
+ return table.concat (t)
+ end
+
+
+local function encodeQuestion (qname, qtype, qclass) -- - - - encodeQuestion
+ qname = encodeName (qname)
+ qtype = dns.typecode[qtype or 'a']
+ qclass = dns.classcode[qclass or 'in']
+ return qname..qtype..qclass;
+ end
+
+
+function resolver:byte (len) -- - - - - - - - - - - - - - - - - - - - - byte
+ len = len or 1
+ local offset = self.offset
+ local last = offset + len - 1
+ if last > #self.packet then
+ error (string.format ('out of bounds: %i>%i', last, #self.packet)) end
+ self.offset = offset + len
+ return string.byte (self.packet, offset, last)
+ end
+
+
+function resolver:word () -- - - - - - - - - - - - - - - - - - - - - - word
+ local b1, b2 = self:byte (2)
+ return 0x100*b1 + b2
+ end
+
+
+function resolver:dword () -- - - - - - - - - - - - - - - - - - - - - dword
+ local b1, b2, b3, b4 = self:byte (4)
+ -- print ('dword', b1, b2, b3, b4)
+ return 0x1000000*b1 + 0x10000*b2 + 0x100*b3 + b4
+ end
+
+
+function resolver:sub (len) -- - - - - - - - - - - - - - - - - - - - - - sub
+ len = len or 1
+ local s = string.sub (self.packet, self.offset, self.offset + len - 1)
+ self.offset = self.offset + len
+ return s
+ end
+
+
+function resolver:header (force) -- - - - - - - - - - - - - - - - - - header
+
+ local id = self:word ()
+ -- print (string.format (':header id %x', id))
+ if not self.active[id] and not force then return nil end
+
+ local h = { id = id }
+
+ local b1, b2 = self:byte (2)
+
+ h.rd = b1 %2
+ h.tc = b1 /2%2
+ h.aa = b1 /4%2
+ h.opcode = b1 /8%16
+ h.qr = b1 /128
+
+ h.rcode = b2 %16
+ h.z = b2 /16%8
+ h.ra = b2 /128
+
+ h.qdcount = self:word ()
+ h.ancount = self:word ()
+ h.nscount = self:word ()
+ h.arcount = self:word ()
+
+ for k,v in pairs (h) do h[k] = v-v%1 end
+
+ return h
+ end
+
+
+function resolver:name () -- - - - - - - - - - - - - - - - - - - - - - name
+ local remember, pointers = nil, 0
+ local len = self:byte ()
+ local n = {}
+ while len > 0 do
+ if len >= 0xc0 then -- name is "compressed"
+ pointers = pointers + 1
+ if pointers >= 20 then error ('dns error: 20 pointers') end
+ local offset = ((len-0xc0)*0x100) + self:byte ()
+ remember = remember or self.offset
+ self.offset = offset + 1 -- +1 for lua
+ else -- name is not compressed
+ append (n, self:sub (len)..'.')
+ end
+ len = self:byte ()
+ end
+ self.offset = remember or self.offset
+ return table.concat (n)
+ end
+
+
+function resolver:question () -- - - - - - - - - - - - - - - - - - question
+ local q = {}
+ q.name = self:name ()
+ q.type = dns.type[self:word ()]
+ q.class = dns.type[self:word ()]
+ return q
+ end
+
+
+function resolver:A (rr) -- - - - - - - - - - - - - - - - - - - - - - - - A
+ local b1, b2, b3, b4 = self:byte (4)
+ rr.a = string.format ('%i.%i.%i.%i', b1, b2, b3, b4)
+ end
+
+
+function resolver:CNAME (rr) -- - - - - - - - - - - - - - - - - - - - CNAME
+ rr.cname = self:name ()
+ end
+
+
+function resolver:MX (rr) -- - - - - - - - - - - - - - - - - - - - - - - MX
+ rr.pref = self:word ()
+ rr.mx = self:name ()
+ end
+
+
+function resolver:LOC_nibble_power () -- - - - - - - - - - LOC_nibble_power
+ local b = self:byte ()
+ -- print ('nibbles', ((b-(b%0x10))/0x10), (b%0x10))
+ return ((b-(b%0x10))/0x10) * (10^(b%0x10))
+ end
+
+
+function resolver:LOC (rr) -- - - - - - - - - - - - - - - - - - - - - - LOC
+ rr.version = self:byte ()
+ if rr.version == 0 then
+ rr.loc = rr.loc or {}
+ rr.loc.size = self:LOC_nibble_power ()
+ rr.loc.horiz_pre = self:LOC_nibble_power ()
+ rr.loc.vert_pre = self:LOC_nibble_power ()
+ rr.loc.latitude = self:dword ()
+ rr.loc.longitude = self:dword ()
+ rr.loc.altitude = self:dword ()
+ end end
+
+
+local function LOC_tostring_degrees (f, pos, neg) -- - - - - - - - - - - - -
+ f = f - 0x80000000
+ if f < 0 then pos = neg f = -f end
+ local deg, min, msec
+ msec = f%60000
+ f = (f-msec)/60000
+ min = f%60
+ deg = (f-min)/60
+ return string.format ('%3d %2d %2.3f %s', deg, min, msec/1000, pos)
+ end
+
+
+function resolver.LOC_tostring (rr) -- - - - - - - - - - - - - LOC_tostring
+
+ local t = {}
+
+ --[[
+ for k,name in pairs { 'size', 'horiz_pre', 'vert_pre',
+ 'latitude', 'longitude', 'altitude' } do
+ append (t, string.format ('%4s%-10s: %12.0f\n', '', name, rr.loc[name]))
+ end
+ --]]
+
+ append ( t, string.format (
+ '%s %s %.2fm %.2fm %.2fm %.2fm',
+ LOC_tostring_degrees (rr.loc.latitude, 'N', 'S'),
+ LOC_tostring_degrees (rr.loc.longitude, 'E', 'W'),
+ (rr.loc.altitude - 10000000) / 100,
+ rr.loc.size / 100,
+ rr.loc.horiz_pre / 100,
+ rr.loc.vert_pre / 100 ) )
+
+ return table.concat (t)
+ end
+
+
+function resolver:NS (rr) -- - - - - - - - - - - - - - - - - - - - - - - NS
+ rr.ns = self:name ()
+ end
+
+
+function resolver:SOA (rr) -- - - - - - - - - - - - - - - - - - - - - - SOA
+ end
+
+
+function resolver:SRV (rr) -- - - - - - - - - - - - - - - - - - - - - - SRV
+ rr.srv = {}
+ rr.srv.priority = self:word ()
+ rr.srv.weight = self:word ()
+ rr.srv.port = self:word ()
+ rr.srv.target = self:name ()
+ end
+
+
+function SRV_tostring (rr) -- - - - - - - - - - - - - - - - - - SRV_tostring
+ local s = rr.srv
+ return string.format ( '%5d %5d %5d %s',
+ s.priority, s.weight, s.port, s.target )
+ end
+
+
+function resolver:TXT (rr) -- - - - - - - - - - - - - - - - - - - - - - TXT
+ rr.txt = self:sub (rr.rdlength)
+ end
+
+
+function resolver:rr () -- - - - - - - - - - - - - - - - - - - - - - - - rr
+ local rr = {}
+ setmetatable (rr, rr_metatable)
+ rr.name = self:name (self)
+ rr.type = dns.type[self:word ()] or rr.type
+ rr.class = dns.class[self:word ()] or rr.class
+ rr.ttl = 0x10000*self:word () + self:word ()
+ rr.rdlength = self:word ()
+
+ if rr.ttl == 0 then -- pass
+ else rr.tod = self.time + rr.ttl end
+
+ local remember = self.offset
+ local rr_parser = self[dns.type[rr.type]]
+ if rr_parser then rr_parser (self, rr) end
+ self.offset = remember
+ rr.rdata = self:sub (rr.rdlength)
+ return rr
+ end
+
+
+function resolver:rrs (count) -- - - - - - - - - - - - - - - - - - - - - rrs
+ local rrs = {}
+ for i = 1,count do append (rrs, self:rr ()) end
+ return rrs
+ end
+
+
+function resolver:decode (packet, force) -- - - - - - - - - - - - - - decode
+
+ self.packet, self.offset = packet, 1
+ local header = self:header (force)
+ if not header then return nil end
+ local response = { header = header }
+
+ response.question = {}
+ local offset = self.offset
+ for i = 1,response.header.qdcount do
+ append (response.question, self:question ()) end
+ response.question.raw = string.sub (self.packet, offset, self.offset - 1)
+
+ if not force then
+ if not self.active[response.header.id] or
+ not self.active[response.header.id][response.question.raw] then
+ return nil end end
+
+ response.answer = self:rrs (response.header.ancount)
+ response.authority = self:rrs (response.header.nscount)
+ response.additional = self:rrs (response.header.arcount)
+
+ return response
+ end
+
+
+-- socket layer -------------------------------------------------- socket layer
+
+
+resolver.delays = { 1, 3, 11, 45 }
+
+
+function resolver:addnameserver (address) -- - - - - - - - - - addnameserver
+ self.server = self.server or {}
+ append (self.server, address)
+ end
+
+
+function resolver:setnameserver (address) -- - - - - - - - - - setnameserver
+ self.server = {}
+ self:addnameserver (address)
+ end
+
+
+function resolver:adddefaultnameservers () -- - - - - adddefaultnameservers
+ for line in io.lines ('/etc/resolv.conf') do
+ address = string.match (line, 'nameserver%s+(%d+%.%d+%.%d+%.%d+)')
+ if address then self:addnameserver (address) end
+ end end
+
+
+function resolver:getsocket (servernum) -- - - - - - - - - - - - - getsocket
+
+ self.socket = self.socket or {}
+ self.socketset = self.socketset or {}
+
+ local sock = self.socket[servernum]
+ if sock then return sock end
+
+ sock = socket.udp ()
+ if self.socket_wrapper then sock = self.socket_wrapper (sock) end
+ sock:settimeout (0)
+ -- todo: attempt to use a random port, fallback to 0
+ sock:setsockname ('*', 0)
+ sock:setpeername (self.server[servernum], 53)
+ self.socket[servernum] = sock
+ self.socketset[sock] = sock
+ return sock
+ end
+
+
+function resolver:socket_wrapper_set (func) -- - - - - - - socket_wrapper_set
+ self.socket_wrapper = func
+ end
+
+
+function resolver:closeall () -- - - - - - - - - - - - - - - - - - closeall
+ for i,sock in ipairs (self.socket) do self.socket[i]:close () end
+ self.socket = {}
+ end
+
+
+function resolver:remember (rr, type) -- - - - - - - - - - - - - - remember
+
+ -- print ('remember', type, rr.class, rr.type, rr.name)
+
+ if type ~= '*' then
+ type = rr.type
+ local all = get (self.cache, rr.class, '*', rr.name)
+ -- print ('remember all', all)
+ if all then append (all, rr) end
+ end
+
+ self.cache = self.cache or setmetatable ({}, cache_metatable)
+ local rrs = get (self.cache, rr.class, type, rr.name) or
+ set (self.cache, rr.class, type, rr.name, setmetatable ({}, rrs_metatable))
+ append (rrs, rr)
+
+ if type == 'MX' then self.unsorted[rrs] = true end
+ end
+
+
+local function comp_mx (a, b) -- - - - - - - - - - - - - - - - - - - comp_mx
+ return (a.pref == b.pref) and (a.mx < b.mx) or (a.pref < b.pref)
+ end
+
+
+function resolver:peek (qname, qtype, qclass) -- - - - - - - - - - - - peek
+ qname, qtype, qclass = standardize (qname, qtype, qclass)
+ local rrs = get (self.cache, qclass, qtype, qname)
+ if not rrs then return nil end
+ if prune (rrs, socket.gettime ()) and qtype == '*' or not next (rrs) then
+ set (self.cache, qclass, qtype, qname, nil) return nil end
+ if self.unsorted[rrs] then table.sort (rrs, comp_mx) end
+ return rrs
+ end
+
+
+function resolver:purge (soft) -- - - - - - - - - - - - - - - - - - - purge
+ if soft == 'soft' then
+ self.time = socket.gettime ()
+ for class,types in pairs (self.cache or {}) do
+ for type,names in pairs (types) do
+ for name,rrs in pairs (names) do
+ prune (rrs, time, 'soft')
+ end end end
+ else self.cache = {} end
+ end
+
+
+function resolver:query (qname, qtype, qclass) -- - - - - - - - - - -- query
+
+ qname, qtype, qclass = standardize (qname, qtype, qclass)
+
+ if not self.server then self:adddefaultnameservers () end
+
+ local question = question or encodeQuestion (qname, qtype, qclass)
+ local peek = self:peek (qname, qtype, qclass)
+ if peek then return peek end
+
+ local header, id = encodeHeader ()
+ -- print ('query id', id, qclass, qtype, qname)
+ local o = { packet = header..question,
+ server = 1,
+ delay = 1,
+ retry = socket.gettime () + self.delays[1] }
+ self:getsocket (o.server):send (o.packet)
+
+ -- remember the query
+ self.active[id] = self.active[id] or {}
+ self.active[id][question] = o
+
+ -- remember which coroutine wants the answer
+ local co = coroutine.running ()
+ if co then
+ set (self.wanted, qclass, qtype, qname, co, true)
+ set (self.yielded, co, qclass, qtype, qname, true)
+ end end
+
+
+function resolver:receive (rset) -- - - - - - - - - - - - - - - - - receive
+
+ -- print 'receive' print (self.socket)
+ self.time = socket.gettime ()
+ rset = rset or self.socket
+
+ local response
+ for i,sock in pairs (rset) do
+
+ if self.socketset[sock] then
+ local packet = sock:receive ()
+ if packet then
+
+ response = self:decode (packet)
+ if response then
+ -- print 'received response'
+ -- self.print (response)
+
+ for i,section in pairs { 'answer', 'authority', 'additional' } do
+ for j,rr in pairs (response[section]) do
+ self:remember (rr, response.question[1].type) end end
+
+ -- retire the query
+ local queries = self.active[response.header.id]
+ if queries[response.question.raw] then
+ queries[response.question.raw] = nil end
+ if not next (queries) then self.active[response.header.id] = nil end
+ if not next (self.active) then self:closeall () end
+
+ -- was the query on the wanted list?
+ local q = response.question
+ local cos = get (self.wanted, q.class, q.type, q.name)
+ if cos then
+ for co in pairs (cos) do
+ set (self.yielded, co, q.class, q.type, q.name, nil)
+ if not self.yielded[co] then coroutine.resume (co) end
+ end
+ set (self.wanted, q.class, q.type, q.name, nil)
+ end end end end end
+
+ return response
+ end
+
+
+function resolver:pulse () -- - - - - - - - - - - - - - - - - - - - - pulse
+
+ -- print ':pulse'
+ while self:receive () do end
+ if not next (self.active) then return nil end
+
+ self.time = socket.gettime ()
+ for id,queries in pairs (self.active) do
+ for question,o in pairs (queries) do
+ if self.time >= o.retry then
+
+ o.server = o.server + 1
+ if o.server > #self.server then
+ o.server = 1
+ o.delay = o.delay + 1
+ end
+
+ if o.delay > #self.delays then
+ print ('timeout')
+ queries[question] = nil
+ if not next (queries) then self.active[id] = nil end
+ if not next (self.active) then return nil end
+ else
+ -- print ('retry', o.server, o.delay)
+ self.socket[o.server]:send (o.packet)
+ o.retry = self.time + self.delays[o.delay]
+ end end end end
+
+ if next (self.active) then return true end
+ return nil
+ end
+
+
+function resolver:lookup (qname, qtype, qclass) -- - - - - - - - - - lookup
+ self:query (qname, qtype, qclass)
+ while self:pulse () do socket.select (self.socket, nil, 4) end
+ -- print (self.cache)
+ return self:peek (qname, qtype, qclass)
+ end
+
+
+-- print ---------------------------------------------------------------- print
+
+
+local hints = { -- - - - - - - - - - - - - - - - - - - - - - - - - - - hints
+ qr = { [0]='query', 'response' },
+ opcode = { [0]='query', 'inverse query', 'server status request' },
+ aa = { [0]='non-authoritative', 'authoritative' },
+ tc = { [0]='complete', 'truncated' },
+ rd = { [0]='recursion not desired', 'recursion desired' },
+ ra = { [0]='recursion not available', 'recursion available' },
+ z = { [0]='(reserved)' },
+ rcode = { [0]='no error', 'format error', 'server failure', 'name error',
+ 'not implemented' },
+
+ type = dns.type,
+ class = dns.class, }
+
+
+local function hint (p, s) -- - - - - - - - - - - - - - - - - - - - - - hint
+ return (hints[s] and hints[s][p[s]]) or '' end
+
+
+function resolver.print (response) -- - - - - - - - - - - - - resolver.print
+
+ for s,s in pairs { 'id', 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z',
+ 'rcode', 'qdcount', 'ancount', 'nscount', 'arcount' } do
+ print ( string.format ('%-30s', 'header.'..s),
+ response.header[s], hint (response.header, s) )
+ end
+
+ for i,question in ipairs (response.question) do
+ print (string.format ('question[%i].name ', i), question.name)
+ print (string.format ('question[%i].type ', i), question.type)
+ print (string.format ('question[%i].class ', i), question.class)
+ end
+
+ local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 }
+ local tmp
+ for s,s in pairs {'answer', 'authority', 'additional'} do
+ for i,rr in pairs (response[s]) do
+ for j,t in pairs { 'name', 'type', 'class', 'ttl', 'rdlength' } do
+ tmp = string.format ('%s[%i].%s', s, i, t)
+ print (string.format ('%-30s', tmp), rr[t], hint (rr, t))
+ end
+ for j,t in pairs (rr) do
+ if not common[j] then
+ tmp = string.format ('%s[%i].%s', s, i, j)
+ print (string.format ('%-30s %s', tmp, t))
+ end end end end end
+
+
+-- module api ------------------------------------------------------ module api
+
+
+local function resolve (func, ...) -- - - - - - - - - - - - - - resolver_get
+ dns._resolver = dns._resolver or dns.resolver ()
+ return func (dns._resolver, ...)
+ end
+
+
+function dns.resolver () -- - - - - - - - - - - - - - - - - - - - - resolver
+
+ -- this function seems to be redundant with resolver.new ()
+
+ r = { active = {}, cache = {}, unsorted = {}, wanted = {}, yielded = {} }
+ setmetatable (r, resolver)
+ setmetatable (r.cache, cache_metatable)
+ setmetatable (r.unsorted, { __mode = 'kv' })
+ return r
+ end
+
+
+function dns.lookup (...) -- - - - - - - - - - - - - - - - - - - - - lookup
+ return resolve (resolver.lookup, ...) end
+
+
+function dns.purge (...) -- - - - - - - - - - - - - - - - - - - - - - purge
+ return resolve (resolver.purge, ...) end
+
+function dns.peek (...) -- - - - - - - - - - - - - - - - - - - - - - - peek
+ return resolve (resolver.peek, ...) end
+
+
+function dns.query (...) -- - - - - - - - - - - - - - - - - - - - - - query
+ return resolve (resolver.query, ...) end
+
+
+function dns:socket_wrapper_set (...) -- - - - - - - - - socket_wrapper_set
+ return resolve (resolver.socket_wrapper_set, ...) end
+
+
+return dns
diff --git a/main/lua-dns/ztact.lua b/main/lua-dns/ztact.lua
new file mode 100644
index 0000000000..15bcffad6c
--- /dev/null
+++ b/main/lua-dns/ztact.lua
@@ -0,0 +1,364 @@
+
+
+-- public domain 20080410 lua@ztact.com
+
+
+pcall (require, 'lfs') -- lfs may not be installed/necessary.
+pcall (require, 'pozix') -- pozix may not be installed/necessary.
+
+
+local getfenv, ipairs, next, pairs, pcall, require, select, tostring, type =
+ getfenv, ipairs, next, pairs, pcall, require, select, tostring, type
+local unpack, xpcall =
+ unpack, xpcall
+
+local io, lfs, os, string, table, pozix = io, lfs, os, string, table, pozix
+
+local assert, print = assert, print
+
+local error = error
+
+
+module ((...) or 'ztact') ------------------------------------- module ztact
+
+
+-- dir -------------------------------------------------------------------- dir
+
+
+function dir (path) -- - - - - - - - - - - - - - - - - - - - - - - - - - dir
+ local it = lfs.dir (path)
+ return function ()
+ repeat
+ local dir = it ()
+ if dir ~= '.' and dir ~= '..' then return dir end
+ until not dir
+ end end
+
+
+function is_file (path) -- - - - - - - - - - - - - - - - - - is_file (path)
+ local mode = lfs.attributes (path, 'mode')
+ return mode == 'file' and path
+ end
+
+
+-- network byte ordering -------------------------------- network byte ordering
+
+
+function htons (word) -- - - - - - - - - - - - - - - - - - - - - - - - htons
+ return (word-word%0x100)/0x100, word%0x100
+ end
+
+
+-- pcall2 -------------------------------------------------------------- pcall2
+
+
+getfenv ().pcall = pcall -- store the original pcall as ztact.pcall
+
+
+local argc, argv, errorhandler, pcall2_f
+
+
+local function _pcall2 () -- - - - - - - - - - - - - - - - - - - - - _pcall2
+ local tmpv = argv
+ argv = nil
+ return pcall2_f (unpack (tmpv, 1, argc))
+ end
+
+
+function seterrorhandler (func) -- - - - - - - - - - - - - - seterrorhandler
+ errorhandler = func
+ end
+
+
+function pcall2 (f, ...) -- - - - - - - - - - - - - - - - - - - - - - pcall2
+
+ pcall2_f = f
+ argc = select ('#', ...)
+ argv = { ... }
+
+ if not errorhandler then
+ local debug = require ('debug')
+ errorhandler = debug.traceback
+ end
+
+ return xpcall (_pcall2, errorhandler)
+ end
+
+
+function append (t, ...) -- - - - - - - - - - - - - - - - - - - - - - append
+ local insert = table.insert
+ for i,v in ipairs {...} do
+ insert (t, v)
+ end end
+
+
+function print_r (d, indent) -- - - - - - - - - - - - - - - - - - - print_r
+ local rep = string.rep (' ', indent or 0)
+ if type (d) == 'table' then
+ for k,v in pairs (d) do
+ if type (v) == 'table' then
+ io.write (rep, k, '\n')
+ print_r (v, (indent or 0) + 1)
+ else io.write (rep, k, ' = ', tostring (v), '\n') end
+ end
+ else io.write (d, '\n') end
+ end
+
+
+function tohex (s) -- - - - - - - - - - - - - - - - - - - - - - - - - tohex
+ return string.format (string.rep ('%02x ', #s), string.byte (s, 1, #s))
+ end
+
+
+function tostring_r (d, indent, tab0) -- - - - - - - - - - - - - tostring_r
+
+ tab1 = tab0 or {}
+ local rep = string.rep (' ', indent or 0)
+ if type (d) == 'table' then
+ for k,v in pairs (d) do
+ if type (v) == 'table' then
+ append (tab1, rep, k, '\n')
+ tostring_r (v, (indent or 0) + 1, tab1)
+ else append (tab1, rep, k, ' = ', tostring (v), '\n') end
+ end
+ else append (tab1, d, '\n') end
+
+ if not tab0 then return table.concat (tab1) end
+ end
+
+
+-- queue manipulation -------------------------------------- queue manipulation
+
+
+-- Possible queue states. 1 (i.e. queue.p[1]) is head of queue.
+--
+-- 1..2
+-- 3..4 1..2
+-- 3..4 1..2 5..6
+-- 1..2 5..6
+-- 1..2
+
+
+local function print_queue (queue, ...) -- - - - - - - - - - - - print_queue
+ for i=1,10 do io.write ((queue[i] or '.')..' ') end
+ io.write ('\t')
+ for i=1,6 do io.write ((queue.p[i] or '.')..' ') end
+ print (...)
+ end
+
+
+function dequeue (queue) -- - - - - - - - - - - - - - - - - - - - - dequeue
+
+ local p = queue.p
+ if not p and queue[1] then queue.p = { 1, #queue } p = queue.p end
+
+ if not p[1] then return nil end
+
+ local element = queue[p[1]]
+ queue[p[1]] = nil
+
+ if p[1] < p[2] then p[1] = p[1] + 1
+
+ elseif p[4] then p[1], p[2], p[3], p[4] = p[3], p[4], nil, nil
+
+ elseif p[5] then p[1], p[2], p[5], p[6] = p[5], p[6], nil, nil
+
+ else p[1], p[2] = nil, nil end
+
+ print_queue (queue, ' de '..element)
+ return element
+ end
+
+
+function enqueue (queue, element) -- - - - - - - - - - - - - - - - - enqueue
+
+ local p = queue.p
+ if not p then queue.p = {} p = queue.p end
+
+ if p[5] then -- p3..p4 p1..p2 p5..p6
+ p[6] = p[6]+1
+ queue[p[6]] = element
+
+ elseif p[3] then -- p3..p4 p1..p2
+
+ if p[4]+1 < p[1] then
+ p[4] = p[4] + 1
+ queue[p[4]] = element
+
+ else
+ p[5] = p[2]+1
+ p[6], queue[p[5]] = p[5], element
+ end
+
+ elseif p[1] then -- p1..p2
+ if p[1] == 1 then
+ p[2] = p[2] + 1
+ queue[p[2]] = element
+
+ else
+ p[3], p[4], queue[1] = 1, 1, element
+ end
+
+ else -- empty queue
+ p[1], p[2], queue[1] = 1, 1, element
+ end
+
+ print_queue (queue, ' '..element)
+ end
+
+
+local function test_queue ()
+ t = {}
+ enqueue (t, 1)
+ enqueue (t, 2)
+ enqueue (t, 3)
+ enqueue (t, 4)
+ enqueue (t, 5)
+ dequeue (t)
+ dequeue (t)
+ enqueue (t, 6)
+ enqueue (t, 7)
+ enqueue (t, 8)
+ enqueue (t, 9)
+ dequeue (t)
+ dequeue (t)
+ dequeue (t)
+ dequeue (t)
+ enqueue (t, 'a')
+ dequeue (t)
+ enqueue (t, 'b')
+ enqueue (t, 'c')
+ dequeue (t)
+ dequeue (t)
+ dequeue (t)
+ dequeue (t)
+ dequeue (t)
+ enqueue (t, 'd')
+ dequeue (t)
+ dequeue (t)
+ dequeue (t)
+ end
+
+
+-- test_queue ()
+
+
+function queue_len (queue)
+ end
+
+
+function queue_peek (queue)
+ end
+
+
+-- tree manipulation ---------------------------------------- tree manipulation
+
+
+function set (parent, ...) --- - - - - - - - - - - - - - - - - - - - - - set
+
+ -- print ('set', ...)
+
+ local len = select ('#', ...)
+ local key, value = select (len-1, ...)
+ local cutpoint, cutkey
+
+ for i=1,len-2 do
+
+ local key = select (i, ...)
+ local child = parent[key]
+
+ if value == nil then
+ if child == nil then return
+ elseif next (child, next (child)) then cutpoint = nil cutkey = nil
+ elseif cutpoint == nil then cutpoint = parent cutkey = key end
+
+ elseif child == nil then child = {} parent[key] = child end
+
+ parent = child
+ end
+
+ if value == nil and cutpoint then cutpoint[cutkey] = nil
+ else parent[key] = value return value end
+ end
+
+
+function get (parent, ...) --- - - - - - - - - - - - - - - - - - - - - - get
+ local len = select ('#', ...)
+ for i=1,len do
+ parent = parent[select (i, ...)]
+ if parent == nil then break end
+ end
+ return parent
+ end
+
+
+-- misc ------------------------------------------------------------------ misc
+
+
+function find (path, ...) --------------------------------------------- find
+
+ local dirs, operators = { path }, {...}
+ for operator in ivalues (operators) do
+ if not operator (path) then break end end
+
+ while next (dirs) do
+ local parent = table.remove (dirs)
+ for child in assert (pozix.opendir (parent)) do
+ if child and child ~= '.' and child ~= '..' then
+ local path = parent..'/'..child
+ if pozix.stat (path, 'is_dir') then table.insert (dirs, path) end
+ for operator in ivalues (operators) do
+ if not operator (path) then break end end
+ end end end end
+
+
+function ivalues (t) ----------------------------------------------- ivalues
+ local i = 0
+ return function () if t[i+1] then i = i + 1 return t[i] end end
+ end
+
+
+function lson_encode (mixed, f, indent, indents) --------------- lson_encode
+
+
+ local capture
+ if not f then
+ capture = {}
+ f = function (s) append (capture, s) end
+ end
+
+ indent = indent or 0
+ indents = indents or {}
+ indents[indent] = indents[indent] or string.rep (' ', 2*indent)
+
+ local type = type (mixed)
+
+ if type == 'number' then f (mixed)
+
+ else if type == 'string' then f (string.format ('%q', mixed))
+
+ else if type == 'table' then
+ f ('{')
+ for k,v in pairs (mixed) do
+ f ('\n')
+ f (indents[indent])
+ f ('[') f (lson_encode (k)) f ('] = ')
+ lson_encode (v, f, indent+1, indents)
+ f (',')
+ end
+ f (' }')
+ end end end
+
+ if capture then return table.concat (capture) end
+ end
+
+
+function timestamp (time) ---------------------------------------- timestamp
+ return os.date ('%Y%m%d.%H%M%S', time)
+ end
+
+
+function values (t) ------------------------------------------------- values
+ local k, v
+ return function () k, v = next (t, k) return v end
+ end