--- /dev/null
+# Scripting The Recursor
+As of version 3.1.7 of the PowerDNS Recursor, it is possible to modify
+resolving behaviour using simple scripts written in the
+[Lua](http://www.lua.org) programming language. This page documents the Recursor 4.x and beyond
+version of the scripting API.
+
+These scripts can be used to quickly override dangerous domains, fix things
+that are wrong, for load balancing or for legal or commercial purposes.
+
+Lua is extremely fast and lightweight, easily supporting hundreds of
+thousands of queries per second. The Lua language is explained very
+well in the excellent book [Programming in Lua](http://www.amazon.com/exec/obidos/ASIN/859037985X/lua-pilindex-20).
+
+For extra performance, a Just In Time compiled version of Lua called LuaJIT is supported.
+
+Queries can be intercepted in many places:
+
+* before any packet parsing begins (`ipfilter`)
+* before the resolving logic starts to work (`preresolve`)
+* after the resolving process failed to find a correct answer for a domain (`nodata`, `nxdomain`)
+* after the whole process is done and an answer is ready for the client (`postresolve`)
+* before an outgoing query is made to an authoritative server (`preoutquery`)
+
+## Configuring Lua scripts
+
+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`, or passed
+to `configure`.
+
+**note**: Only one script can be loaded at the same time. If you load a different
+script, the current one will be replaced!
+
+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 command line, 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 file name, load one
+from a new location. A failure to parse the new script will leave the old
+script in working order.
+
+**Note**: It is also possible to precompile scripts using `luac`, and have
+PowerDNS load the result. This means that switching scripts is faster, and
+also that you'll be informed about syntax errors at compile time.
+
+Finally, `rec_control unload-lua-script` can be used to remove the currently
+installed script, and revert to unmodified behaviour.
+
+
+## Writing Lua PowerDNS Recursor scripts for version 4.x
+**Note**: This describes the Lua scripts as supported by 4.x. They are very different than the ones from 3.x, but
+tend to be faster and more correct. For 3.x, see XXX.
+
+In the 4.x API, addresses and DNS Names are not passed as strings but as native objects. This allows for
+easy checking against netmasks and domain sets. It also means that to print such names, the ":tostring"
+method must be used.
+
+Once a script is loaded, PowerDNS looks for several functions, as detailed below. All of these functions are optional.
+
+### `function ipfilter ( remoteip, localip )`
+This hook gets queried immediately after consulting the packet cache, but before
+parsing the DNS packet. If this hook returns something else than false, the packet is dropped.
+However, because this check is after the packet cache, the IP address might still receive answers
+that require no packet parsing.
+
+With this hook, undesired traffic can be dropped rapidly before using precious CPU cycles
+for parsing.
+
+`remoteip` is the IP(v6) address of the requestor, `localip` is the address on which the query arrived.
+
+As an example, to filter all queries coming from 1.2.3.0/24:
+
+```
+badips = newNMG()
+badips:addMask("1.2.3.0/24")
+
+function ipfilter(loc, rem)
+ return badips:match(rem)
+end
+```
+
+### The DNSQuestion object
+The following functions all receive a DNSQuestion object, which contains details about
+the current state of the question. This state can be modified from the various hooks. If
+a function returns 'true', it will indicate that it handled a query. If it returns false,
+the Recursor will continue processing unchanged (with one minor exception).
+
+The DNSQuestion object contains at least the following fields:
+
+* qname - DNS native version of the name this query is for
+* qtype - type this query is for, can be compared against pdns.A, pdns.AAAA etc
+* rcode - current DNS Result Code, which can be overridden, including to several magical values
+* remoteaddr - address of the requestor
+* localaddr - address this query was received on
+* variable - a boolean which, if sets, indicates the recursor should not packet cache this answer. Honored even when returning 'false'!
+
+It also supports the following methods:
+
+* `addAnswer(type, content, [ttl, name])`: add an answer to the record of `type` with `content`. Optionally supply TTL and the name of
+ the answer too, which defaults to the name of the question
+* `getRecords()`: get a table of DNS Records in this DNS Question (or answer by now)
+* `setRecords(records)`: after your edits, update the answers of this question
+
+### `function preresolve ( dq )`
+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.
+
+The rcode can be set to pdns.DROP to drop the query. Other statuses are normal DNS
+return codes, like no error, NXDOMDAIN etc.
+
+### `function postresolve (dq)`
+
+is called right before returning a response to a client (and, unless
+`variable` is set, to the packet cache too). It allows inspection
+and modification of almost any detail in the return packet.
+
+### `function nxdomain ( dq )`
+
+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. Works entirely like postresolve, but saves a trip through Lua for
+answers which are not NXDOMAIN.
+
+### `function nodata ( dq )`
+is just like `nxdomain`, except it gets called when a domain exists, but the
+requested type does not. This is where one would implement DNS64.
+
+### `function preoutquery (dq)`
+This hook is not called in response to a client packet, but fires when the Recursor
+wants to talk to an authoritative server. When this hook sets the special result code -3,
+the whole DNS client query causing this outquery gets dropped.
+
+However, this function can also return records like the preresolve query above.
+
+## Semantics
+
+All these functions are passed the IP address of the requester. Most also
+get passed 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.
+
+If a function has taken over a request, it should set 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 false signifies that the
+function chose not to intervene.
+
+The `ipfilter` and `preoutquery` hooks are different, in that `ipfilter` can only return a true of false value, and
+that `preoutquery` can also set rcode -3 to signify that the whole query should be terminated.
+
+A minimal sample script:
+
+```
+function nxdomain(dq)
+ print("Intercepting NXDOMAIN for: ",dq.qname:toString())
+ if dq.qtype == pdns.A
+ then
+ dq.rcode=0 -- make it a normal answer
+ dq:addAnswer(pdns.A, "192.168.1.1")
+ return true
+ end
+ return false
+end
+```
+
+**Warning**: Please do NOT use the above sample script in production!
+Responsible NXDomain redirection requires more attention to detail.
+
+Useful 'rcodes' include 0 for "no error", `pdns.NXDOMAIN` for
+"NXDOMAIN", `pdns.DROP` to drop the question from further processing (since
+3.6, and such a drop is accounted in the 'policy-drops' metric).
+
+## Helpful functions
+
+IP addresses are passed to Lua in native format. They can be matched against netmasks objects like this:
+```
+nmg = newNMG()
+nmg:addMask("127.0.0.0/8")
+nmg:addMask({"213.244.168.0/24", "130.161.0.0/16"})
+
+if nmg:match(dq.remote) then
+ print("Intercepting query from ", dq.remote)
+end
+```
+
+To log messages with the main PowerDNS Recursor process, use
+`pdnslog(message)`. Available since version 3.2. pdnslog can also write
+out to a syslog loglevel if specified. Use `pdnslog(message,
+pdns.loglevels.LEVEL)` with the correct pdns.loglevels entry. Entries are
+listed in the following table:
+
+| | |
+|:--||:--|
+|All|pdns.loglevels.All|
+|Alert|pdns.loglevels.Alert|
+|Critical|pdns.loglevels.Critical|
+|Error|pdns.loglevels.Error|
+|Warning|pdns.loglevels.Warning|
+|Notice|pdns.loglevels.Notice|
+|Info|pdns.loglevels.Info|
+|Debug|pdns.loglevels.Debug|
+|None|pdns.loglevels.None|
+
+`pdnslog(message)` will write out to Info by default.
+
+`getregisteredname('www.powerdns.com')` returns `powerdns.com.`, based on Mozilla's
+Public Suffix List. In general it will tell you the 'registered domain' for a given
+name.
+
+To get fake AAAA records for DNS64 usage, set dq.followupFunction to "getFakeAAAARecords",
+dq.followupPrefix to (say) "fe80::21b:77ff:0:0" and dq.followupName to the name you want to
+synthesise an IPv6 address for.
+
+## CNAME chain resolution
+It may be useful to return a CNAME record for Lua, and then have the
+PowerDNS Recursor continue resolving that CNAME. This can be achieved by
+setting dq.followupFunction to "followCNAMERecords" and followupDomain to
+"www.powerdns.com". PowerDNS will do the rest.
+
+
+## Some sample scripts
+### Dropping all traffic from botnet-infected users
+Frequently, DoS attacks are performed where specific IP addresses are attacked,
+often by queries coming in from open resolvers. These queries then lead to a lot of
+queries to 'authoritative servers' which actually often aren't nameservers at all, but
+just targets of attack.
+
+The following script will add a requestor's IP address to a blocking set if they've
+sent a query that caused PowerDNS to attempt to talk to a certain subnet.
+
+This specific script is, as of January 2015, useful to prevent traffic to ezdns.it related
+traffic from creating CPU load. This script requires PowerDNS Recursor 3.7 or later.
+
+```
+lethalgroup=newNMG()
+lethalgroup:addMask("192.121.121.0/24") -- touch these nameservers and you die
+
+function preoutquery(dq)
+-- print("pdns wants to ask "..remoteip:tostring().." about "..domain.." "..qtype.." on behalf of requestor "..getlocaladdress())
+ if(lethalgroup:match(dq.remoteaddr))
+ then
+-- print("We matched the group "..lethalgroup:tostring().."!", "killing query dead & adding requestor "..getlocaladdress().." to block list")
+ dq.rcode = -3 -- "kill"
+ return true
+ end
+ return false
+end
+
+
+```