xref: /netbsd-src/external/bsd/unbound/dist/pythonmod/examples/inplace_callbacks.py (revision 91f7d55fb697b5e0475da4718fa34c3a3ebeac85)
1# -*- coding: utf-8 -*-
2'''
3 inplace_callbacks.py: python module showcasing inplace callback function
4                       registration and functionality.
5
6 Copyright (c) 2016, NLnet Labs.
7
8 This software is open source.
9
10 Redistribution and use in source and binary forms, with or without
11 modification, are permitted provided that the following conditions
12 are met:
13
14    * Redistributions of source code must retain the above copyright notice,
15      this list of conditions and the following disclaimer.
16
17    * Redistributions in binary form must reproduce the above copyright notice,
18      this list of conditions and the following disclaimer in the documentation
19      and/or other materials provided with the distribution.
20
21    * Neither the name of the organization nor the names of its
22      contributors may be used to endorse or promote products derived from this
23      software without specific prior written permission.
24
25 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
29 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 POSSIBILITY OF SUCH DAMAGE.
36'''
37
38import os
39
40#Try:
41# - dig @localhost nlnetlabs.nl +ednsopt=65002:
42#       This query *could* be answered from cache. If so, unbound will reply
43#       with the same EDNS option 65002, but with hexdata 'deadbeef' as data.
44#
45# - dig @localhost bogus.nlnetlabs.nl txt:
46#       This query returns SERVFAIL as the txt record of bogus.nlnetlabs.nl is
47#       intentionally bogus. The reply will contain an empty EDNS option
48#       with option code 65003.
49#       Unbound will also log the source address of the client that made
50#       the request.
51#       (unbound needs to be validating for this example to work)
52
53# Useful functions:
54#   register_inplace_cb_reply(inplace_reply_callback, env, id):
55#       Register the reply_callback function as an inplace callback function
56#       when answering with a resolved query.
57#       Return True on success, False on failure.
58#
59#   register_inplace_cb_reply_cache(inplace_reply_cache_callback, env, id):
60#       Register the reply_cache_callback function as an inplace callback
61#       function when answering from cache.
62#       Return True on success, False on failure.
63#
64#   register_inplace_cb_reply_local(inplace_reply_local_callback, env, id):
65#       Register the reply_local_callback function as an inplace callback
66#       function when answering from local data or chaos reply.
67#       Return True on success, False on failure.
68#
69#   register_inplace_cb_reply_servfail(inplace_reply_servfail_callback, env, id):
70#       Register the reply_servfail_callback function as an inplace callback
71#       function when answering with servfail.
72#       Return True on success, False on failure.
73#
74# Examples on how to use the functions are given in this file.
75
76
77def inplace_reply_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
78                           region, **kwargs):
79    """
80    Function that will be registered as an inplace callback function.
81    It will be called when answering with a resolved query.
82
83    :param qinfo: query_info struct;
84    :param qstate: module qstate. It contains the available opt_lists; It
85                   SHOULD NOT be altered;
86    :param rep: reply_info struct;
87    :param rcode: return code for the query;
88    :param edns: edns_data to be sent to the client side. It SHOULD NOT be
89                 altered;
90    :param opt_list_out: the list with the EDNS options that will be sent as a
91                         reply. It can be populated with EDNS options;
92    :param region: region to allocate temporary data. Needs to be used when we
93                   want to append a new option to opt_list_out.
94    :param **kwargs: Dictionary that may contain parameters added in a future
95                     release. Current parameters:
96        ``repinfo``: Reply information for a communication point (comm_reply).
97
98    :return: True on success, False on failure.
99
100    """
101    log_info("python: called back while replying.")
102    return True
103
104
105def inplace_cache_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
106                           region, **kwargs):
107    """
108    Function that will be registered as an inplace callback function.
109    It will be called when answering from the cache.
110
111    :param qinfo: query_info struct;
112    :param qstate: module qstate. None;
113    :param rep: reply_info struct;
114    :param rcode: return code for the query;
115    :param edns: edns_data sent from the client side. The list with the EDNS
116                 options is accessible through edns.opt_list. It SHOULD NOT be
117                 altered;
118    :param opt_list_out: the list with the EDNS options that will be sent as a
119                         reply. It can be populated with EDNS options;
120    :param region: region to allocate temporary data. Needs to be used when we
121                   want to append a new option to opt_list_out.
122    :param **kwargs: Dictionary that may contain parameters added in a future
123                     release. Current parameters:
124        ``repinfo``: Reply information for a communication point (comm_reply).
125
126    :return: True on success, False on failure.
127
128    For demonstration purposes we want to see if EDNS option 65002 is present
129    and reply with a new value.
130
131    """
132    log_info("python: called back while answering from cache.")
133    # Inspect the incoming EDNS options.
134    if not edns_opt_list_is_empty(edns.opt_list):
135        log_info("python: available EDNS options:")
136        for o in edns.opt_list_iter:
137            log_info("python:    Code: {}, Data: '{}'".format(o.code,
138                "".join('{:02x}'.format(x) for x in o.data)))
139            if o.code == 65002:
140                log_info("python: *found option code 65002*")
141
142                # add to opt_list
143                # Data MUST be represented in a bytearray.
144                b = bytearray.fromhex("deadbeef")
145                if edns_opt_list_append(opt_list_out, o.code, b, region):
146                    log_info("python: *added new option code 65002*")
147                else:
148                    log_info("python: *failed to add new option code 65002*")
149                    return False
150                break
151
152    return True
153
154
155def inplace_local_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
156                           region, **kwargs):
157    """
158    Function that will be registered as an inplace callback function.
159    It will be called when answering from local data.
160
161    :param qinfo: query_info struct;
162    :param qstate: module qstate. None;
163    :param rep: reply_info struct;
164    :param rcode: return code for the query;
165    :param edns: edns_data sent from the client side. The list with the
166                 EDNS options is accessible through edns.opt_list. It
167                 SHOULD NOT be altered;
168    :param opt_list_out: the list with the EDNS options that will be sent as a
169                         reply. It can be populated with EDNS options;
170    :param region: region to allocate temporary data. Needs to be used when we
171                   want to append a new option to opt_list_out.
172    :param **kwargs: Dictionary that may contain parameters added in a future
173                     release. Current parameters:
174        ``repinfo``: Reply information for a communication point (comm_reply).
175
176    :return: True on success, False on failure.
177
178    """
179    log_info("python: called back while replying with local data or chaos"
180             " reply.")
181    return True
182
183
184def inplace_servfail_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
185                              region, **kwargs):
186    """
187    Function that will be registered as an inplace callback function.
188    It will be called when answering with SERVFAIL.
189
190    :param qinfo: query_info struct;
191    :param qstate: module qstate. If not None the relevant opt_lists are
192                   available here;
193    :param rep: reply_info struct. None;
194    :param rcode: return code for the query. LDNS_RCODE_SERVFAIL;
195    :param edns: edns_data to be sent to the client side. If qstate is None
196                 edns.opt_list contains the EDNS options sent from the client
197                 side. It SHOULD NOT be altered;
198    :param opt_list_out: the list with the EDNS options that will be sent as a
199                         reply. It can be populated with EDNS options;
200    :param region: region to allocate temporary data. Needs to be used when we
201                   want to append a new option to opt_list_out.
202    :param **kwargs: Dictionary that may contain parameters added in a future
203                     release. Current parameters:
204        ``repinfo``: Reply information for a communication point (comm_reply).
205
206    :return: True on success, False on failure.
207
208    For demonstration purposes we want to reply with an empty EDNS code '65003'
209    and log the IP address of the client.
210
211    """
212    log_info("python: called back while servfail.")
213    # Append the example EDNS option
214    b = bytearray.fromhex("")
215    edns_opt_list_append(opt_list_out, 65003, b, region)
216
217    # Log the client's IP address
218    comm_reply = kwargs['repinfo']
219    if comm_reply:
220        addr = comm_reply.addr
221        port = comm_reply.port
222        addr_family = comm_reply.family
223        log_info("python: Client IP: {}({}), port: {}"
224                 "".format(addr, addr_family, port))
225
226    return True
227
228
229def inplace_query_callback(qinfo, flags, qstate, addr, zone, region, **kwargs):
230    """
231    Function that will be registered as an inplace callback function.
232    It will be called before sending a query to a backend server.
233
234    :param qinfo: query_info struct;
235    :param flags: flags of the query;
236    :param qstate: module qstate. opt_lists are available here;
237    :param addr: struct sockaddr_storage. Address of the backend server;
238    :param zone: zone name in binary;
239    :param region: region to allocate temporary data. Needs to be used when we
240                   want to append a new option to opt_lists.
241    :param **kwargs: Dictionary that may contain parameters added in a future
242                     release.
243    """
244    log_info("python: outgoing query to {}@{}".format(addr.addr, addr.port))
245    return True
246
247
248def inplace_query_response_callback(qstate, response, **kwargs):
249    """
250    Function that will be registered as an inplace callback function.
251    It will be called after receiving a reply from a backend server.
252
253    :param qstate: module qstate. opt_lists are available here;
254    :param response: struct dns_msg. The reply received from the backend server;
255    :param **kwargs: Dictionary that may contain parameters added in a future
256                     release.
257    """
258    log_dns_msg(
259        "python: incoming reply from {}{}".format(qstate.reply.addr, os.linesep),
260        response.qinfo, response.rep
261    )
262    return True
263
264
265def inplace_edns_back_parsed_call(qstate, **kwargs):
266    """
267    Function that will be registered as an inplace callback function.
268    It will be called after EDNS is parsed on a reply from a backend server..
269
270    :param qstate: module qstate. opt_lists are available here;
271    :param **kwargs: Dictionary that may contain parameters added in a future
272                     release.
273    """
274    log_info("python: edns parsed")
275    return True
276
277
278def init_standard(id, env):
279    """
280    New version of the init function.
281
282    The function's signature is the same as the C counterpart and allows for
283    extra functionality during init.
284
285    ..note:: This function is preferred by unbound over the old init function.
286    ..note:: The previously accessible configuration options can now be found in
287             env.cfg.
288
289    """
290    log_info("python: inited script {}".format(mod_env['script']))
291
292    # Register the inplace_reply_callback function as an inplace callback
293    # function when answering a resolved query.
294    if not register_inplace_cb_reply(inplace_reply_callback, env, id):
295        return False
296
297    # Register the inplace_cache_callback function as an inplace callback
298    # function when answering from cache.
299    if not register_inplace_cb_reply_cache(inplace_cache_callback, env, id):
300        return False
301
302    # Register the inplace_local_callback function as an inplace callback
303    # function when answering from local data.
304    if not register_inplace_cb_reply_local(inplace_local_callback, env, id):
305        return False
306
307    # Register the inplace_servfail_callback function as an inplace callback
308    # function when answering with SERVFAIL.
309    if not register_inplace_cb_reply_servfail(inplace_servfail_callback, env, id):
310        return False
311
312    # Register the inplace_query_callback function as an inplace callback
313    # before sending a query to a backend server.
314    if not register_inplace_cb_query(inplace_query_callback, env, id):
315        return False
316
317    # Register the inplace_edns_back_parsed_call function as an inplace callback
318    # for when a reply is received from a backend server.
319    if not register_inplace_cb_query_response(inplace_query_response_callback, env, id):
320        return False
321
322    # Register the inplace_edns_back_parsed_call function as an inplace callback
323    # for when EDNS is parsed on a reply from a backend server.
324    if not register_inplace_cb_edns_back_parsed_call(inplace_edns_back_parsed_call, env, id):
325        return False
326
327    return True
328
329
330def init(id, cfg):
331    """
332    Previous version of the init function.
333
334    ..note:: This function is still supported for backwards compatibility when
335             the init_standard function is missing. When init_standard is
336             present this function SHOULD be omitted to avoid confusion to the
337             reader.
338
339    """
340    return True
341
342
343def deinit(id): return True
344
345
346def inform_super(id, qstate, superqstate, qdata): return True
347
348
349def operate(id, event, qstate, qdata):
350    if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS):
351        qstate.ext_state[id] = MODULE_WAIT_MODULE
352        return True
353
354    elif event == MODULE_EVENT_MODDONE:
355        qstate.ext_state[id] = MODULE_FINISHED
356        return True
357
358    log_err("pythonmod: Unknown event")
359    qstate.ext_state[id] = MODULE_ERROR
360    return True
361