# 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.
+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.
-These scripts can be used to quickly override dangerous domains, fix things that are wrong, for load balancing or for legal or commercial purposes.
+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.
+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).
-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.
+Queries can be intercepted in many places:
+
+* before any packet parsing begins (`ipfilter`, since 3.7)
+* 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`, since 3.7)
## 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`.
-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.
+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`.
+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.
+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.
+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.
+**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.
+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
Once a script is loaded, PowerDNS looks for several functions, as detailed below. All of these functions are optional.
-### `preresolve ( remoteip, domain, qtype )`
-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.
-
-### `postresolve ( remoteip, domain, qtype, records, origrcode )`
-is called right before returning a response to a client (and, unless `setvariable()` is called, to the packet cache too). It allows inspection and modification of almost any detail in the return packet. Available since version 3.4.
-
-### `function nxdomain ( remoteip, domain, qtype )`
-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.
-
-### `function nodata ( remoteip, domain, qtype, records )`
-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. Available since version 3.4.
-
### `function ipfilter ( remoteip )`
This hook gets queried immediately after consulting the packet cache, but before
parsing the DNS packet. If this hook returns something else than -1, the packet is dropped.
**Note**: `remoteip` is passed as an `iputils.ca` type (for which see below).
+### `preresolve ( remoteip, domain, qtype )`
+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.
+
+### `postresolve ( remoteip, domain, qtype, records, origrcode )`
+is called right before returning a response to a client (and, unless `setvariable()` is called, to the packet cache too). It allows inspection and modification of almost any detail in the return packet. Available since version 3.4.
+
+### `function nxdomain ( remoteip, domain, qtype )`
+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.
+
+### `function nodata ( remoteip, domain, qtype, records )`
+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. Available since version 3.4.
+
### `function preoutquery ( remoteip, domain, qtype )`
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 returns the special result code -3,
end
```
-**Warning**: Please do NOT use the above sample script in production! Responsible NXDomain redirection requires more attention to detail.
+**Warning**: 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.
+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.example.net' would be encoded as '25 smtp.example.net.'.
+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.example.net' would be
+encoded as '25 smtp.example.net.'.
-Useful return '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).
+Useful return '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).
Fields that can be set in the return table include:
**Warning**: Only the IN class (1) is fully supported!
-**Warning**: 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="192.0.2.4"}, {qtype=1, content="4.3.2.1"}}` as this will get the numbering right automatically.
+**Warning**: 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="192.0.2.4"}, {qtype=1, content="4.3.2.1"}}` as this will get the
+numbering right automatically.
+
+## Helpful functions
-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 match, the function returns true.
+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 match, the function returns true.
-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:
+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|
-|NTLog|pdns.loglevels.NTLog|
|Alert|pdns.loglevels.Alert|
|Critical|pdns.loglevels.Critical|
|Error|pdns.loglevels.Error|
`pdnslog(message)` will write out to Info by default.
To retrieve the IP address on which a query was received, use `getlocaladdress()`. Available since version 3.2.
+In `preoutquery`, `getlocaladdress()` returns the address of the client that caused the outgoing query.
-To indicate that an answer should not be cached in the packet cache, use `setvariable()`. Available since version 3.3.
+To indicate that an answer should not be cached in the packet cache, use
+`setvariable()`. Available since version 3.3.
-To get fake AAAA records for DNS64 usage, use `return "getFakeAAAARecords", domain, "fe80::21b:77ff:0:0"`. Available since version 3.4.
+To get fake AAAA records for DNS64 usage, use `return "getFakeAAAARecords",
+domain, "fe80::21b:77ff:0:0"`. Available since version 3.4.
## IP Address and netmask processing
(Available in PowerDNS Recursor versions released after 3.6.2)
```
## 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 returning: "followCNAMERecords", 0, {{qtype=pdns.CNAME, content="www.powerdns.com"}}. This indicates an rcode of 0 and the records to put in the record. But the first string instruct PowerDNS to complete the CNAME chain. Available since 3.6.
+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
+returning: "followCNAMERecords", 0, {{qtype=pdns.CNAME,
+content="www.powerdns.com"}}. This indicates an rcode of 0 and the records
+to put in the record. But the first string instructs PowerDNS to complete
+the CNAME chain. Available since 3.6.
+
+
+## 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=iputils.newnmgroup()
+lethalgroup:add("192.121.121.0/24") -- touch these nameservers and you die
+
+blockset=iputils.newipset() -- which client IP addresses we block
+
+function preoutquery(remoteip, domain, qtype)
+-- print("pdns wants to ask "..remoteip:tostring().." about "..domain.." "..qtype.." on behalf of requestor "..getlocaladdress())
+ if(lethalgroup:match(remoteip))
+ then
+-- print("We matched the group "..lethalgroup:tostring().."!", "killing query dead & adding requestor "..getlocaladdress().." to block list")
+ blockset[iputils.newca(getlocaladdress())]=1
+ return -3,{} -- -3 means 'kill'
+ end
+ return -1,{} -- -1 means 'no opinion'
+end
+
+
+local delcount=0
+
+function ipfilter(remoteip)
+ delcount=delcount+1
+
+ if((delcount % 10000)==0)
+ then
+-- print("Clearing blockset!")
+ blockset=iputils.newipset() -- clear it
+ end
+
+ if(blockset[remoteip] ~= nil) then
+ return 1 -- block!
+ end
+ return -1 -- no opinion
+end
+```
+
+Every 10000 queries, the block set is emptied.