As of version 3.1.7 of the PowerDNS Recursor, it is possible to modify resolving behaviour using simple scripts written in the Lua programming language.
![]() | This functionality is expected to change from version to version as additional scripting needs become apparant! |
These scripts can be used to quickly override dangerous domains, for load balancing or for legal or commercial purposes.
As of 3.1.7, queries can be intercepted in two places: before the resolving logic starts to work, plus after the resolving process failed to find a correct answer for a domain.
In order to load scripts, the PowerDNS Recursor must have Lua support built in. The packages distributed from the PowerDNS website have this language enabled, other distributions may differ. To compile with Lua support, use: LUA=1 make or LUA=1 gmake as the case may be. Paths to the Lua include files and binaries may be found near the top of the Makefile.
If lua support is available, a script can be configured either via the configuration file, or at runtime via the rec_control tool. Scripts can be reloaded or unloaded at runtime with no interruption in operations. If a new script contains syntax errors, the old script remains in force.
On the commandline, or in the configuration file, the setting lua-dns-script can be used to supply a full path to a 'lua' script.
At runtime, rec_control reload-lua-script can be used to either reload the script from its current location, or, when passed a new filename, load one from a new location. A failure to parse the new script will leave the old script in working order.
Finally, rec_control unload-lua-script can be used to remove the currently installed script, and revert to unmodified behaviour.
Once a script is loaded, PowerDNS looks for two functions: preresolve
and nxdomain
. Either or both of these
can be absent, in which case the corresponding functionality is disabled.
preresolve
is called before any DNS resolution is attempted, and if this function indicates it, it can supply a direct answer to the
DNS query, overriding the internet. This is useful to combat botnets, or to disable domains unacceptable to an organization for whatever reason.
nxdomain
is called after the DNS resolution process has run its course, but ended in an 'NXDOMAIN' situation, indicating that the domain
or the specific record does not exist. This can be used for various purposes.
Both functions are passed the IP address of the requestor, plus the name and type being requested. In return, these functions indicate if they have taken over the request, or want to let normal proceedings take their course. Beyond version 3.1.7, the IP address on which the question was received is inserted immediately after the IP address of the requestor - so in that case there are 4 parameters.
If a function has taken over a request, it should return an rcode (usually 0), and specify a table with records to be put in the answer section
of a packet. An interesting rcode is NXDOMAIN (3, or pdns.NXDOMAIN
), which specifies the non-existence of a domain.
Returning -1 and an empty table signifies that the function chose not to intervene.
A minimal sample script:
function nxdomain ( ip, domain, qtype ) print ("nxhandler called for: ", ip, domain, qtype) ret={} if qtype ~= pdns.A then return -1, ret end -- only A records if not string.find(domain, "^www%.") then return -1, ret end -- only things that start with www. if not matchnetmask(ip, "10.0.0.0/8", "192.168.0.0/16") then return -1, ret end -- only interfere with local queries ret[1]={qtype=pdns.A, content="127.1.2.3"} -- add IN A 127.1.2.3 ret[2]={qtype=pdns.A, content="127.3.2.1"} -- add IN A 127.3.2.1 return 0, ret -- return no error, plus records end
![]() | Please do NOT use the above sample script in production! Responsible NXDomain redirection requires more attention to detail. |
Note that the domain is passed to the Lua function terminated by a '.'. A more complete sample script is provided as powerdns-example-script.lua in the PowerDNS Recursor distribution.
The answer content format is (nearly) identical to the storage in the PowerDNS Authoritative Server database, or as in zone files. The exception is that, unlike in the database, there is no 'prio' field, which means that an MX record with priority 25 pointing to 'smtp.mailserver.com' would be encoded as '25 smtp.mailserver.com.'.
Useful return 'rcodes' include 0 for "no error" and pdns.NXDOMAIN
for "NXDOMAIN".
Fields that can be set in the return table include:
Content of the record, as specified above in 'zone file format'. No default, mandatory field.
Place of this record. Defaults to 1, indicating 'Answer' section. Can also be 2, for Authority of 3 for Additional. When using this rare feature, always emit records with 'Place' in ascending order. This field is usually not needed.
qname of the answer, the 'name' of the record. Defaults to the name of the query, which is almost always correct except when specifying additional records or rolling out a CNAME chain.
Currently the numerical qtype of the answer, defaulting to '1' which is an A record. Can be also be specified as
pdns.A
, or pdns.CNAME
etc.
Time to live of a record. Defaults to 3600. Be sure not to specify differing TTLs within answers with an identical qname. While this will be encoded in DNS, actual results may be undesired.
![]() | The result table must have indexes that start at 1! Otherwise the first or confusingly the last entry of the table will be ignored. A useful technique is to return data using: return 0, {{qtype=1, content="1.2.3.4"}, {qtype=1, content="4.3.2.1"}} as this will get the numbering right automatically. |
The function matchnetmask(ip, netmask1, netmask2..)
(or matchnetmask(ip, {netmask1, netmask2})
) is available to match incoming queries against
a number of netmasks. If any of these matches, the function returns true.
To log messages with the main PowerDNS Recursor process, use pdnslog(message)
. Available since 3.1.8.