Skip to content

Lua Examples (Recursor)

Josh Soref edited this page Aug 29, 2021 · 12 revisions

Warning - these are all outdated and will probably not work with 4.x

Please consult the documentation at https://doc.powerdns.com/recursor/lua-scripting/index.html before deploying any of those Lua Scripts and be warned that should you do any blocking operations in these Scripts your queries will also block indefinitely and thus any scripting errors may render your recursive nameservice broken.

Always test your Lua script before deploying it to your live recursors.

Additional examples are part of the PowerDNS sources:

Feel free to add your own Snipplets - this is a wiki! ;)

time.example.com

Wraps your nameservers local time in a TXT record.

function preresolve ( remoteip, domain, qtype )
        if domain == "the.time."
        then
                d=os.date("\"%c\"")
                ret={
                        {qtype=pdns.TXT, ttl=1, place="1", content=d},
                }
                if qtype == pdns.TXT
                then
                        return 0, ret
                else
                        return -1, {}
                end
        end
	return -1, {}
end
function nxdomain ( remoteip, domain, qtype )
	return -1, {}
end

NXDOMAIN 'redirect'

Note: this should probably be rewritten as a postresolve script after the release of Recursor 3.4.

This will return an A record if a query for IN A or ANY www.powerdns.org would otherwise give an NXDOMAIN response.

ranges={
        "127.0.0.0/24",         -- localnet for debugging
}
hostnames={
        "nothere%.example%.com", -- domain we want to be redirect if it triggers an NXDOMAIN response
}
function preresolve ( remoteip, domain, qtype )
	return -1, {}
end
function nxdomain ( remoteip, domain, qtype )
	if matchnetmask(remoteip, ranges) -- this is a c function that the recursor exports to the Lua script
	then
		domain=string.lower(domain)
		for i,v in pairs(hostnames)
		do
			if string.find(domain, "^" .. v .. "%.$")
			then
				if qtype == pdns.A or qtype == pdns.ANY
				then
					return 0, {
						{qtype=pdns.A, content="1.2.3.4", ttl=3600, place="1"},
					}
				else
					return 0, {}
				end
			end
		end
	end
	return -1, {}
end

WhatsMyIP

Turns your Recursor into a poor man's STUN server.

function preresolve (...)
   ip = arg[1]
   if #arg == 3 then
      domain = arg[2]
      qtype = arg[3]
   else
      destination = arg[2]
      domain = arg[3]
      qtype = arg[4]
   end

   if qtype == pdns.TXT and string.lower( domain ) == "whatsmyip." then
      setvariable()
      return 0, {{qtype=qtype, ttl=10, place="1", content='"'..ip..'"'}}
   end

   return -1, {}
end

function nxdomain (...)
   ip = arg[1]
   if #arg == 3 then
      domain = arg[2]
      qtype = arg[3]
   else
      destination = arg[2]
      domain = arg[3]
      qtype = arg[4]
   end

   return -1, {}
end

Furnish private IP address inside LAN

Note: this should probably be rewritten as a postresolve script after the release of Recursor 3.4.

Here we use "preresolve" to return a private IP address in the 192.168.x.x range for a host when inside a LAN. For example, the DynDNS-managed hostname "mymachine.homelinux.org" should yield the public IP address of the LAN gateway when queried on the Internet (where the query goes to the DynDNS nameservers), but 192.168.0.10 when queried on the LAN (where the query should go to locally configured pdns-recursor). For fun and Lua training, we also translate the numeric query type to its cleartext representation when logging.

-- Provide a function to translate the query type to to a cleartext string.
-- If the query type is unknown, its numeric value is returned as string.
-- The query types have been copy/pasted from pdns/qtype.hh with regex replacements
-- and sorting.

do 
   local qtt = {
      [1]     = "A",
      [38]    = "A6",
      [28]    = "AAAA",
      [65400] = "ADDR",
      [18]    = "AFSDB",
      [65401] = "ALIAS",
      [255]   = "ANY",
      [252]   = "AXFR",
      [257]   = "CAA",
      [60]    = "CDNSKEY",
      [59]    = "CDS",
      [37]    = "CERT",
      [5]     = "CNAME",
      [49]    = "DHCID",
      [32769] = "DLV",
      [39]    = "DNAME",
      [48]    = "DNSKEY",
      [43]    = "DS",
      [108]   = "EUI48",
      [109]   = "EUI64",
      [13]    = "HINFO",
      [45]    = "IPSECKEY",
      [251]   = "IXFR",
      [25]    = "KEY",
      [36]    = "KX",
      [29]    = "LOC",
      [254]   = "MAILA",
      [253]   = "MAILB",
      [14]    = "MINFO",
      [9]     = "MR",
      [15]    = "MX",
      [35]    = "NAPTR",
      [2]     = "NS",
      [47]    = "NSEC",
      [50]    = "NSEC3",
      [51]    = "NSEC3PARAM",
      [61]    = "OPENPGPKEY",
      [41]    = "OPT",
      [12]    = "PTR",
      [57]    = "RKEY",
      [17]    = "RP",
      [46]    = "RRSIG",
      [24]    = "SIG",
      [6]     = "SOA",
      [99]    = "SPF",
      [33]    = "SRV",
      [44]    = "SSHFP",
      [249]   = "TKEY",
      [52]    = "TLSA",
      [250]   = "TSIG",
      [16]    = "TXT",
      [256]   = "URI",
      [11]    = "WKS"  }

   function translateQtype ( qtype ) 
      local str = qtt[qtype]
      if str then
         return str
      else 
         return tostring(qtype)
      end
   end

   -- example: print ( translateQtype( 161 ) ) --> "161"
   --          print ( translateQtype( 249 ) ) --> "TKEY"

end

-- "preresolve" is called by pdns-resolver. 
-- The parameter list is the old style one (prior to version. 3.1.7)
-- for newer versions, use preresolve ( requestorip, acceptorip, domain, qtype )
-- Look in the system logfile for the logging text.

function preresolve ( requestorip, domain, qtype )

        pdnslog ("preresolve() called by " .. tostring(requestorip) .. " for domain=" .. tostring(domain) .. ", type=" .. translateQtype(qtype) )

        if domain == "mymachine.homelinux.org." and qtype == pdns.A
        then
            return 0,  { {qtype=pdns.A, content="192.168.0.10"} }
        else
            return -1, {}
        end
end

Custom Response for a specific NXDOMAIN query

Sometimes, we need to create a custom dns response for a Non-Existent Domain

domainame = "test.example.org"
response  = "192.168.1.10"

function nxdomain(dq)
    if dq.qname:equal(domainame) then
        dq.rcode=0 -- make it a normal answer
        dq:addAnswer(pdns.A, response)
        dq.variable = true -- disable packet cache
        return true
    end
    return false
end