]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/recursordist/docs/lua-scripting/hooks.rst
Merge pull request #8946 from omoerbeek/rec-buildbot-test
[thirdparty/pdns.git] / pdns / recursordist / docs / lua-scripting / hooks.rst
CommitLineData
223bb49e
PL
1Intercepting queries with Lua
2=============================
3To get a quick start, we have supplied a sample script that showcases all functionality described below.
49f9972c 4Please find it `here <https://github.com/PowerDNS/pdns/blob/master/pdns/recursordist/contrib/powerdns-example-script.lua>`_.
223bb49e
PL
5
6Queries can be intercepted in many places:
7
8- before any packet parsing begins (:func:`ipfilter`)
9- before any filtering policy have been applied (:func:`prerpz`)
10- before the resolving logic starts to work (:func:`preresolve`)
11- after the resolving process failed to find a correct answer for a domain (:func:`nodata`, :func:`nxdomain`)
12- after the whole process is done and an answer is ready for the client (:func:`postresolve`)
13- before an outgoing query is made to an authoritative server (:func:`preoutquery`)
14
15Writing Lua PowerDNS Recursor scripts
16-------------------------------------
17Addresses and DNS Names are not passed as strings but as native objects.
18This allows for easy checking against `Netmasks <scripting-netmasks>`_ and `domain sets <scripting-dnsname>`_.
19It also means that to print such names, the ``:toString`` method must be used (or even ``:toStringWithPort`` for addresses).
20
21Once a script is loaded, PowerDNS looks for several `functions <scripting-hooks>`_ in the loaded script.
22All of these functions are optional.
23
24If a function returns true, it will indicate that it handled a query.
25If it returns false, the Recursor will continue processing unchanged (with one minor exception).
26
27Interception Functions
28----------------------
29
30.. function:: ipfilter(remoteip, localip, dh) -> bool
31
32 This hook gets queried immediately after consulting the packet cache, but before parsing the DNS packet.
33 If this hook returns something else than false, the packet is dropped.
34 However, because this check is after the packet cache, the IP address might still receive answers that require no packet parsing.
35
36 With this hook, undesired traffic can be dropped rapidly before using precious CPU cycles for parsing.
37 As an example, to filter all queries coming from 1.2.3.0/24, or with the
38 AD bit set:
39
40 .. code-block:: Lua
41
42 badips = newNMG()
43 badips:addMask("1.2.3.0/24")
44
45 function ipfilter(rem, loc, dh)
46 return badips:match(rem) or dh:getAD()
47 end
48
49 This hook does not get the full :class:`DNSQuestion` object, since filling out the fields would require packet parsing, which is what we are trying to prevent with this function.
50
51 :param ComboAddress remoteip: The IP(v6) address of the requestor
52 :param ComboAddress localip: The address on which the query arrived.
53 :param DNSHeader dh: The DNS Header of the query.
54
55
d14a6965
RG
56.. function:: gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp, proxyprotocolvalues) -> int
57 gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp) -> int
ebee0255 58 gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions) -> int
223bb49e
PL
59
60 .. versionchanged:: 4.1.0
61
62 The ``tcp`` parameter was added.
63
f7965474 64 .. versionchanged:: 4.4.0
d14a6965
RG
65
66 The ``proxyprotocolvalues`` parameter was added.
67
223bb49e
PL
68 The ``gettag`` function is invoked when the Recursor attempts to discover in which packetcache an answer is available.
69
70 This function must return an integer, which is the tag number of the packetcache.
71 In addition to this integer, this function can return a table of policy tags.
72 The resulting tag number can be accessed via :attr:`dq.tag <DNSQuestion.tag>` in the :func:`preresolve` hook, and the policy tags via :meth:`dq:getPolicyTags() <DNSQuestion:getPolicyTags>` in every hook.
73
74 .. versionadded:: 4.1.0
75
adc95453 76 It can also return a table whose keys and values are strings to fill the :attr:`DNSQuestion.data` table, as well as a ``requestorId`` value to fill the :attr:`DNSQuestion.requestorId` field and a ``deviceId`` value to fill the :attr:`DNSQuestion.deviceId` field.
d14a6965 77
cee8bfbd 78 .. versionadded:: 4.3.0
0a6a45c8
CHB
79
80 Along the ``deviceId`` value that can be returned, it was addded a ``deviceName`` field to fill the :attr:`DNSQuestion.deviceName` field.
223bb49e
PL
81
82 The tagged packetcache can e.g. be used to answer queries from cache that have e.g. been filtered for certain IPs (this logic should be implemented in :func:`gettag`).
83 This ensure that queries are answered quickly compared to setting :attr:`dq.variable <DNSQuestion.variable>` to true.
84 In the latter case, repeated queries will pass through the entire Lua script.
85
86 :param ComboAddress remote: The sender's IP address
ebee0255
PD
87 :param Netmask ednssubnet: The EDNS Client subnet that was extracted from the packet
88 :param ComboAddress localip: The IP address the query was received on
223bb49e
PL
89 :param DNSName qname: The domain name the query is for
90 :param int qtype: The query type of the query
91 :param ednsoptions: A table whose keys are EDNS option codes and values are :class:`EDNSOptionView` objects. This table is empty unless the :ref:`setting-gettag-needs-edns-options` option is set.
92 :param bool tcp: Added in 4.1.0, a boolean indicating whether the query was received over UDP (false) or TCP (true).
d14a6965 93 :param proxyprotocolvalues: Added in 4.4.0, a table of :class:`ProxyProtocolValue` objects representing the Type-Length Values received via the Proxy Protocol, if any.
223bb49e
PL
94
95.. function:: prerpz(dq)
96
97 This hook is called before any filtering policy have been applied, making it possible to completely disable filtering by setting :attr:`dq.wantsRPZ <DNSQuestion.wantsRPZ>` to false.
98 Using the :meth:`dq:discardPolicy() <DNSQuestion:discardPolicy>` function, it is also possible to selectively disable one or more filtering policy, for example RPZ zones, based on the content of the ``dq`` object.
99
100 As an example, to disable the "malware" policy for example.com queries:
101
102 .. code-block:: Lua
103
104 function prerpz(dq)
105 -- disable the RPZ policy named 'malware' for example.com
106 if dq.qname:equal('example.com') then
107 dq:discardPolicy('malware')
108 end
109 return false
110 end
111
112 :param DNSQuestion dq: The DNS question to handle
113
114.. function:: preresolve(dq)
115
116 This function 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.
117 This is useful to combat botnets, or to disable domains unacceptable to an organization for whatever reason.
118
119 :param DNSQuestion dq: The DNS question to handle
120
121.. function:: postresolve(dq)
122
123 is called right before returning a response to a client (and, unless :attr:`dq.variable <DNSQuestion.variable>` is set, to the packet cache too).
124 It allows inspection and modification of almost any detail in the return packet.
125
126 :param DNSQuestion dq: The DNS question to handle
127
128.. function:: nxdomain(dq)
129
130 is called after the DNS resolution process has run its course, but ended in an 'NXDOMAIN' situation, indicating that the domain does not exist.
131 Works entirely like :func:`postresolve`, but saves a trip through Lua for answers which are not NXDOMAIN.
132
133 :param DNSQuestion dq: The DNS question to handle
134
135.. function:: nodata(dq)
136
137 is just like :func:`nxdomain`, except it gets called when a domain exists, but the requested type does not.
138 This is where one would implement :doc:`DNS64 <../dns64>`.
139
140 :param DNSQuestion dq: The DNS question to handle
141
142.. function:: preoutquery(dq)
143
144 This hook is not called in response to a client packet, but fires when the Recursor wants to talk to an authoritative server.
145 When this hook sets the special result code -3, the whole DNS client query causing this outquery gets dropped.
146
147 However, this function can also return records like :func:`preresolve`.
148
149 :param DNSQuestion dq: The DNS question to handle
150
151Semantics
152^^^^^^^^^
153The functions must return ``true`` if they have taken over the query and wish that the nameserver should not proceed with its regular query-processing.
154When a function returns ``false``, the nameserver will process the query normally until a new function is called.
155
156If 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.
157An interesting rcode is NXDOMAIN (3, or ``pdns.NXDOMAIN``), which specifies the non-existence of a domain.
158
159The :func:`ipfilter` and :func:`preoutquery` hooks are different, in that :func:`ipfilter` can only return a true of false value, and that :func:`preoutquery` can also set rcode -3 to signify that the whole query should be terminated.
160
161A minimal sample script:
162
163.. code-block:: Lua
164
165 function nxdomain(dq)
166 print("Intercepting NXDOMAIN for: ",dq.qname:toString())
167 if dq.qtype == pdns.A
168 then
169 dq.rcode=0 -- make it a normal answer
170 dq:addAnswer(pdns.A, "192.168.1.1")
171 return true
172 end
173 return false
174 end
175
176**Warning**: Please do NOT use the above sample script in production!
177Responsible NXDomain redirection requires more attention to detail.
178
179Useful 'rcodes' include 0 for "no error", ``pdns.NXDOMAIN`` for "NXDOMAIN", ``pdns.DROP`` to drop the question from further processing.
180Such a drop is accounted in the 'policy-drops' metric.
181
182DNS64
183-----
184
185The ``getFakeAAAARecords`` and ``getFakePTRRecords`` followupFunctions
cf3e4fab 186can be used to implement DNS64. See :doc:`../dns64` for more information.
223bb49e
PL
187
188To get fake AAAA records for DNS64 usage, set dq.followupFunction to
189``getFakeAAAARecords``, dq.followupPrefix to e.g. "64:ff9b::" and
190dq.followupName to the name you want to synthesize an IPv6 address for.
191
192For fake reverse (PTR) records, set dq.followupFunction to
193``getFakePTRRecords`` and set dq.followupName to the name to look up and
194dq.followupPrefix to the same prefix as used with
195``getFakeAAAARecords``.
196
197Follow up actions
198-----------------
199When modifying queries, it might be needed that the Recursor does some extra work after the function returns.
200The :attr:`dq.followupFunction <DNSQuestion.followupFunction>` can be set in this case.
201
202.. _cnamechainresolution:
203
204CNAME chain resolution
205^^^^^^^^^^^^^^^^^^^^^^
206It may be useful to return a CNAME record for Lua, and then have the PowerDNS Recursor continue resolving that CNAME.
207This can be achieved by setting dq.followupFunction to ``followCNAMERecords`` and dq.followupDomain to "www.powerdns.com".
208PowerDNS will do the rest.
209
210.. _udpqueryresponse:
211
212UDP Query Response
213^^^^^^^^^^^^^^^^^^
214The ``udpQueryResponse`` :attr:`dq.followupFunction <DNSQuestion.followupFunction>` allows you to query a simple key-value store over UDP asynchronously.
215
216Several dq variables can be set:
217
218- :attr:`dq.udpQueryDest <DNSQuestion.udpQueryDest>`: destination IP address to send the UDP packet to
219- :attr:`dq.udpQuery <DNSQuestion.udpQuery>`: The content of the UDP payload
220- :attr:`dq.udpCallback <DNSQuestion.udpCallback>`: The name of the callback function that is called when an answer is received
221
222The callback function must accept the ``dq`` object and can find the response to the UDP query in :attr:`dq.udpAnswer <DNSQuestion.udpAnswer>`.
223
224In this callback function, :attr:`dq.followupFunction <DNSQuestion.followupFunction>` can be set again to any of the available functions for further processing.
225
226This example script queries a simple key/value store over UDP to decide on whether or not to filter a query:
227
228.. literalinclude:: ../../contrib/kv-example-script.lua
229 :language: Lua
230
231Example Script
232--------------
233
234.. literalinclude:: ../../contrib/powerdns-example-script.lua
235 :language: Lua
236
237Dropping all traffic from botnet-infected users
238^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
239Frequently, DoS attacks are performed where specific IP addresses are attacked, often by queries coming in from open resolvers.
240These queries then lead to a lot of queries to 'authoritative servers' which actually often aren't nameservers at all, but just targets of attack.
241
242The 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.
243
244This specific script is, as of January 2015, useful to prevent traffic to ezdns.it related traffic from creating CPU load.
245This script requires PowerDNS Recursor 4.x or later.
246
247.. code-block:: Lua
248
249 lethalgroup=newNMG()
250 lethalgroup:addMask("192.121.121.0/24") -- touch these nameservers and you die
251
252 function preoutquery(dq)
253 print("pdns wants to ask "..dq.remoteaddr:toString().." about "..dq.qname:toString().." "..dq.qtype.." on behalf of requestor "..dq.localaddr:toString())
254 if(lethalgroup:match(dq.remoteaddr))
255 then
256 print("We matched the group "..lethalgroup:tostring().."!", "killing query dead & adding requestor "..dq.localaddr:toString().." to block list")
257 dq.rcode = -3 -- "kill"
258 return true
259 end
260 return false
261 end
262
263.. _modifyingpolicydecisions:
264
265Modifying Policy Decisions
266--------------------------
267The PowerDNS Recursor has a :doc:`policy engine based on Response Policy Zones (RPZ) <../lua-config/rpz>`.
268Starting with version 4.0.1 of the recursor, it is possible to alter this decision inside the Lua hooks.
269
270If the decision is modified in a Lua hook, ``false`` should be returned, as the query is not actually handled by Lua so the decision is picked up by the Recursor.
271The result of the policy decision is checked after :func:`preresolve` and :func:`postresolve`.
272
273For example, if a decision is set to ``pdns.policykinds.NODATA`` by the policy engine and is unchanged in :func:`preresolve`, the query is replied to with a NODATA response immediately after :func:`preresolve`.
274
275Example script
276^^^^^^^^^^^^^^
277
278.. code-block:: Lua
279
280 -- Dont ever block my own domain and IPs
281 myDomain = newDN("example.com")
282
283 myNetblock = newNMG()
e94b05b0 284 myNetblock:addMasks({"192.0.2.0/24"})
223bb49e
PL
285
286 function preresolve(dq)
287 if dq.qname:isPartOf(myDomain) and dq.appliedPolicy.policyKind ~= pdns.policykinds.NoAction then
288 pdnslog("Not blocking our own domain!")
289 dq.appliedPolicy.policyKind = pdns.policykinds.NoAction
290 end
e94b05b0 291 return false
223bb49e
PL
292 end
293
294 function postresolve(dq)
295 if dq.appliedPolicy.policyKind ~= pdns.policykinds.NoAction then
296 local records = dq:getRecords()
297 for k,v in pairs(records) do
298 if v.type == pdns.A then
299 local blockedIP = newCA(v:getContent())
300 if myNetblock:match(blockedIP) then
301 pdnslog("Not blocking our IP space")
302 dq.appliedPolicy.policyKind = pdns.policykinds.NoAction
303 end
304 end
305 end
306 end
e94b05b0 307 return false
223bb49e
PL
308 end
309
4368d62f
PL
310.. _snmp:
311
223bb49e
PL
312SNMP Traps
313----------
314
315PowerDNS Recursor, when compiled with SNMP support, has the ability to
316act as a SNMP agent to provide SNMP statistics and to be able to send
317traps from Lua.
318
319For example, to send a custom SNMP trap containing the qname from the
320``preresolve`` hook:
321
322.. code-block:: Lua
323
324 function preresolve(dq)
325 sendCustomSNMPTrap('Trap from preresolve, qname is '..dq.qname:toString())
326 return false
327 end
4368d62f 328
a2f87dd1
CHB
329.. _hooks-maintenance-callback:
330
331Maintenance callback
332--------------------
2a9a7388
PD
333Starting with version 4.2.0 of the recursor, it is possible to define a `maintenance()` callback function that will be called periodically.
334This function expects no argument and doesn't return any value.
a2f87dd1
CHB
335
336.. code-block:: Lua
337
338 function maintenance()
339 -- This would be called every second
340 -- Perform here your maintenance
341 end
342
343The interval can be configured through the :ref:`setting-maintenance-interval` setting.