1Inplace callbacks
2=================
3
4This example shows how to register and use inplace callback functions. These
5functions are going to be called just before unbound replies back to a client.
6They can perform certain actions without interrupting unbound's execution flow
7(e.g. add/remove EDNS options, manipulate the reply).
8
9Two different scenarios will be shown:
10
11- If answering from cache and the client used EDNS option code ``65002`` we
12  will answer with the same code but with data ``0xdeadbeef``;
13- When answering with a SERVFAIL we also add an empty EDNS option code
14  ``65003``.
15
16
17Key parts
18~~~~~~~~~
19
20This example relies on the following functionalities:
21
22
23Registering inplace callback functions
24--------------------------------------
25
26There are four types of inplace callback functions:
27
28- `inplace callback reply functions`_
29- `inplace callback reply_cache functions`_
30- `inplace callback reply_local functions`_
31- `inplace callback reply_servfail functions`_
32
33
34Inplace callback reply functions
35................................
36
37Called when answering with a *resolved* query.
38
39The callback function's prototype is the following:
40
41.. code-block:: python
42
43    def inplace_reply_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
44                               region, **kwargs):
45        """
46        Function that will be registered as an inplace callback function.
47        It will be called when answering with a resolved query.
48
49        :param qinfo: query_info struct;
50        :param qstate: module qstate. It contains the available opt_lists; It
51                       SHOULD NOT be altered;
52        :param rep: reply_info struct;
53        :param rcode: return code for the query;
54        :param edns: edns_data to be sent to the client side. It SHOULD NOT be
55                     altered;
56        :param opt_list_out: the list with the EDNS options that will be sent as a
57                             reply. It can be populated with EDNS options;
58        :param region: region to allocate temporary data. Needs to be used when we
59                       want to append a new option to opt_list_out.
60        :param **kwargs: Dictionary that may contain parameters added in a future
61                         release. Current parameters:
62            ``repinfo``: Reply information for a communication point (comm_reply).
63
64        :return: True on success, False on failure.
65
66        """
67
68.. note:: The function's name is irrelevant.
69
70We can register such function as:
71
72.. code-block:: python
73
74    if not register_inplace_cb_reply(inplace_reply_callback, env, id):
75        log_info("python: Could not register inplace callback function.")
76
77
78Inplace callback reply_cache functions
79......................................
80
81Called when answering *from cache*.
82
83The callback function's prototype is the following:
84
85.. code-block:: python
86
87    def inplace_cache_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
88                               region, **kwargs):
89        """
90        Function that will be registered as an inplace callback function.
91        It will be called when answering from the cache.
92
93        :param qinfo: query_info struct;
94        :param qstate: module qstate. None;
95        :param rep: reply_info struct;
96        :param rcode: return code for the query;
97        :param edns: edns_data sent from the client side. The list with the EDNS
98                     options is accessible through edns.opt_list. It SHOULD NOT be
99                     altered;
100        :param opt_list_out: the list with the EDNS options that will be sent as a
101                             reply. It can be populated with EDNS options;
102        :param region: region to allocate temporary data. Needs to be used when we
103                       want to append a new option to opt_list_out.
104        :param **kwargs: Dictionary that may contain parameters added in a future
105                         release. Current parameters:
106            ``repinfo``: Reply information for a communication point (comm_reply).
107
108        :return: True on success, False on failure.
109
110        For demonstration purposes we want to see if EDNS option 65002 is present
111        and reply with a new value.
112
113        """
114
115.. note:: The function's name is irrelevant.
116
117We can register such function as:
118
119.. code-block:: python
120
121    if not register_inplace_cb_reply_cache(inplace_cache_callback, env, id):
122        log_info("python: Could not register inplace callback function.")
123
124
125Inplace callback reply_local functions
126......................................
127
128Called when answering with *local data* or a *Chaos(CH) reply*.
129
130The callback function's prototype is the following:
131
132.. code-block:: python
133
134    def inplace_local_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
135                               region, **kwargs):
136        """
137        Function that will be registered as an inplace callback function.
138        It will be called when answering from local data.
139
140        :param qinfo: query_info struct;
141        :param qstate: module qstate. None;
142        :param rep: reply_info struct;
143        :param rcode: return code for the query;
144        :param edns: edns_data sent from the client side. The list with the
145                     EDNS options is accessible through edns.opt_list. It
146                     SHOULD NOT be altered;
147        :param opt_list_out: the list with the EDNS options that will be sent as a
148                             reply. It can be populated with EDNS options;
149        :param region: region to allocate temporary data. Needs to be used when we
150                       want to append a new option to opt_list_out.
151        :param **kwargs: Dictionary that may contain parameters added in a future
152                         release. Current parameters:
153            ``repinfo``: Reply information for a communication point (comm_reply).
154
155        :return: True on success, False on failure.
156
157        """
158
159.. note:: The function's name is irrelevant.
160
161We can register such function as:
162
163.. code-block:: python
164
165    if not register_inplace_cb_reply_local(inplace_local_callback, env, id):
166        log_info("python: Could not register inplace callback function.")
167
168
169Inplace callback reply_servfail functions
170.........................................
171
172Called when answering with *SERVFAIL*.
173
174The callback function's prototype is the following:
175
176.. code-block:: python
177
178    def inplace_servfail_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
179                                  region, **kwargs):
180        """
181        Function that will be registered as an inplace callback function.
182        It will be called when answering with SERVFAIL.
183
184        :param qinfo: query_info struct;
185        :param qstate: module qstate. If not None the relevant opt_lists are
186                       available here;
187        :param rep: reply_info struct. None;
188        :param rcode: return code for the query. LDNS_RCODE_SERVFAIL;
189        :param edns: edns_data to be sent to the client side. If qstate is None
190                     edns.opt_list contains the EDNS options sent from the client
191                     side. It SHOULD NOT be altered;
192        :param opt_list_out: the list with the EDNS options that will be sent as a
193                             reply. It can be populated with EDNS options;
194        :param region: region to allocate temporary data. Needs to be used when we
195                       want to append a new option to opt_list_out.
196        :param **kwargs: Dictionary that may contain parameters added in a future
197                         release. Current parameters:
198            ``repinfo``: Reply information for a communication point (comm_reply).
199
200        :return: True on success, False on failure.
201
202        For demonstration purposes we want to reply with an empty EDNS code '65003'
203        and log the IP address(es) of the client(s).
204
205        """
206
207.. note:: The function's name is irrelevant.
208
209We can register such function as:
210
211.. code-block:: python
212
213    if not register_inplace_cb_reply_servfail(inplace_servfail_callback, env, id):
214        log_info("python: Could not register inplace callback function.")
215
216
217Testing
218~~~~~~~
219
220Run the Unbound server: ::
221
222    root@localhost$ unbound -dv -c ./test-inplace_callbacks.conf
223
224In case you use your own configuration file, don't forget to enable the Python
225module::
226
227    module-config: "validator python iterator"
228
229and use a valid script path ::
230
231    python-script: "./examples/inplace_callbacks.py"
232
233On the first query for the nlnetlabs.nl A record we get no EDNS option back:
234
235::
236
237    root@localhost$ dig @localhost nlnetlabs.nl +ednsopt=65002
238
239    ; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost nlnetlabs.nl +ednsopt=65002
240    ; (1 server found)
241    ;; global options: +cmd
242    ;; Got answer:
243    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48057
244    ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 3
245
246    ;; OPT PSEUDOSECTION:
247    ; EDNS: version: 0, flags:; udp: 4096
248    ;; QUESTION SECTION:
249    ;nlnetlabs.nl.                  IN      A
250
251    ;; ANSWER SECTION:
252    nlnetlabs.nl.           10200   IN      A       185.49.140.10
253
254    ;; AUTHORITY SECTION:
255    nlnetlabs.nl.           10200   IN      NS      ns.nlnetlabs.nl.
256    nlnetlabs.nl.           10200   IN      NS      sec2.authdns.ripe.net.
257    nlnetlabs.nl.           10200   IN      NS      anyns.pch.net.
258    nlnetlabs.nl.           10200   IN      NS      ns-ext1.sidn.nl.
259
260    ;; ADDITIONAL SECTION:
261    ns.nlnetlabs.nl.        10200   IN      A       185.49.140.60
262    ns.nlnetlabs.nl.        10200   IN      AAAA    2a04:b900::8:0:0:60
263
264    ;; Query time: 813 msec
265    ;; SERVER: 127.0.0.1#53(127.0.0.1)
266    ;; WHEN: Mon Dec 05 16:15:32 CET 2016
267    ;; MSG SIZE  rcvd: 204
268
269When we issue the same query again we get a cached response and the expected
270``65002: 0xdeadbeef`` EDNS option:
271
272::
273
274    root@localhost$ dig @localhost nlnetlabs.nl +ednsopt=65002
275
276    ; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost nlnetlabs.nl +ednsopt=65002
277    ; (1 server found)
278    ;; global options: +cmd
279    ;; Got answer:
280    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26489
281    ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 3
282
283    ;; OPT PSEUDOSECTION:
284    ; EDNS: version: 0, flags:; udp: 4096
285    ; OPT=65002: de ad be ef ("....")
286    ;; QUESTION SECTION:
287    ;nlnetlabs.nl.                  IN      A
288
289    ;; ANSWER SECTION:
290    nlnetlabs.nl.           10197   IN      A       185.49.140.10
291
292    ;; AUTHORITY SECTION:
293    nlnetlabs.nl.           10197   IN      NS      ns.nlnetlabs.nl.
294    nlnetlabs.nl.           10197   IN      NS      sec2.authdns.ripe.net.
295    nlnetlabs.nl.           10197   IN      NS      anyns.pch.net.
296    nlnetlabs.nl.           10197   IN      NS      ns-ext1.sidn.nl.
297
298    ;; ADDITIONAL SECTION:
299    ns.nlnetlabs.nl.        10197   IN      AAAA    2a04:b900::8:0:0:60
300    ns.nlnetlabs.nl.        10197   IN      A       185.49.140.60
301
302    ;; Query time: 0 msec
303    ;; SERVER: 127.0.0.1#53(127.0.0.1)
304    ;; WHEN: Mon Dec 05 16:50:04 CET 2016
305    ;; MSG SIZE  rcvd: 212
306
307By issuing a query for a bogus domain unbound replies with SERVFAIL and an
308empty EDNS option code ``65003``. *For this example to work unbound needs to be
309validating*:
310
311::
312
313    root@localhost$ dig @localhost bogus.nlnetlabs.nl txt
314
315    ; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost bogus.nlnetlabs.nl txt
316    ; (1 server found)
317    ;; global options: +cmd
318    ;; Got answer:
319    ;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 19865
320    ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
321
322    ;; OPT PSEUDOSECTION:
323    ; EDNS: version: 0, flags:; udp: 4096
324    ; OPT=65003
325    ;; QUESTION SECTION:
326    ;bogus.nlnetlabs.nl.            IN      TXT
327
328    ;; Query time: 11 msec
329    ;; SERVER: 127.0.0.1#53(127.0.0.1)
330    ;; WHEN: Mon Dec 05 17:06:01 CET 2016
331    ;; MSG SIZE  rcvd: 51
332
333
334Complete source code
335~~~~~~~~~~~~~~~~~~~~
336.. literalinclude:: ../../examples/inplace_callbacks.py
337    :language: python
338