xref: /netbsd-src/external/bsd/unbound/dist/pythonmod/examples/inplace_callbacks.py (revision 53b02e147d4ed531c0d2a5ca9b3e8026ba3e99b5)
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#Try:
38# - dig @localhost nlnetlabs.nl +ednsopt=65002:
39#       This query *could* be answered from cache. If so, unbound will reply
40#       with the same EDNS option 65002, but with hexdata 'deadbeef' as data.
41#
42# - dig @localhost bogus.nlnetlabs.nl txt:
43#       This query returns SERVFAIL as the txt record of bogus.nlnetlabs.nl is
44#       intentionally bogus. The reply will contain an empty EDNS option
45#       with option code 65003.
46#       Unbound will also log the source address of the client that made
47#       the request.
48#       (unbound needs to be validating for this example to work)
49
50# Useful functions:
51#   register_inplace_cb_reply(inplace_reply_callback, env, id):
52#       Register the reply_callback function as an inplace callback function
53#       when answering with a resolved query.
54#       Return True on success, False on failure.
55#
56#   register_inplace_cb_reply_cache(inplace_reply_cache_callback, env, id):
57#       Register the reply_cache_callback function as an inplace callback
58#       function when answering from cache.
59#       Return True on success, False on failure.
60#
61#   register_inplace_cb_reply_local(inplace_reply_local_callback, env, id):
62#       Register the reply_local_callback function as an inplace callback
63#       function when answering from local data or chaos reply.
64#       Return True on success, False on failure.
65#
66#   register_inplace_cb_reply_servfail(inplace_reply_servfail_callback, env, id):
67#       Register the reply_servfail_callback function as an inplace callback
68#       function when answering with servfail.
69#       Return True on success, False on failure.
70#
71# Examples on how to use the functions are given in this file.
72
73
74def inplace_reply_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
75                           region, **kwargs):
76    """
77    Function that will be registered as an inplace callback function.
78    It will be called when answering with a resolved query.
79
80    :param qinfo: query_info struct;
81    :param qstate: module qstate. It contains the available opt_lists; It
82                   SHOULD NOT be altered;
83    :param rep: reply_info struct;
84    :param rcode: return code for the query;
85    :param edns: edns_data to be sent to the client side. It SHOULD NOT be
86                 altered;
87    :param opt_list_out: the list with the EDNS options that will be sent as a
88                         reply. It can be populated with EDNS options;
89    :param region: region to allocate temporary data. Needs to be used when we
90                   want to append a new option to opt_list_out.
91    :param **kwargs: Dictionary that may contain parameters added in a future
92                     release. Current parameters:
93        ``repinfo``: Reply information for a communication point (comm_reply).
94
95    :return: True on success, False on failure.
96
97    """
98    log_info("python: called back while replying.")
99    return True
100
101
102def inplace_cache_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
103                           region, **kwargs):
104    """
105    Function that will be registered as an inplace callback function.
106    It will be called when answering from the cache.
107
108    :param qinfo: query_info struct;
109    :param qstate: module qstate. None;
110    :param rep: reply_info struct;
111    :param rcode: return code for the query;
112    :param edns: edns_data sent from the client side. The list with the EDNS
113                 options is accessible through edns.opt_list. It SHOULD NOT be
114                 altered;
115    :param opt_list_out: the list with the EDNS options that will be sent as a
116                         reply. It can be populated with EDNS options;
117    :param region: region to allocate temporary data. Needs to be used when we
118                   want to append a new option to opt_list_out.
119    :param **kwargs: Dictionary that may contain parameters added in a future
120                     release. Current parameters:
121        ``repinfo``: Reply information for a communication point (comm_reply).
122
123    :return: True on success, False on failure.
124
125    For demonstration purposes we want to see if EDNS option 65002 is present
126    and reply with a new value.
127
128    """
129    log_info("python: called back while answering from cache.")
130    # Inspect the incoming EDNS options.
131    if not edns_opt_list_is_empty(edns.opt_list):
132        log_info("python: available EDNS options:")
133        for o in edns.opt_list_iter:
134            log_info("python:    Code: {}, Data: '{}'".format(o.code,
135                "".join('{:02x}'.format(x) for x in o.data)))
136            if o.code == 65002:
137                log_info("python: *found option code 65002*")
138
139                # add to opt_list
140                # Data MUST be represented in a bytearray.
141                b = bytearray.fromhex("deadbeef")
142                if edns_opt_list_append(opt_list_out, o.code, b, region):
143                    log_info("python: *added new option code 65002*")
144                else:
145                    log_info("python: *failed to add new option code 65002*")
146                    return False
147                break
148
149    return True
150
151
152def inplace_local_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
153                           region, **kwargs):
154    """
155    Function that will be registered as an inplace callback function.
156    It will be called when answering from local data.
157
158    :param qinfo: query_info struct;
159    :param qstate: module qstate. None;
160    :param rep: reply_info struct;
161    :param rcode: return code for the query;
162    :param edns: edns_data sent from the client side. The list with the
163                 EDNS options is accessible through edns.opt_list. It
164                 SHOULD NOT be altered;
165    :param opt_list_out: the list with the EDNS options that will be sent as a
166                         reply. It can be populated with EDNS options;
167    :param region: region to allocate temporary data. Needs to be used when we
168                   want to append a new option to opt_list_out.
169    :param **kwargs: Dictionary that may contain parameters added in a future
170                     release. Current parameters:
171        ``repinfo``: Reply information for a communication point (comm_reply).
172
173    :return: True on success, False on failure.
174
175    """
176    log_info("python: called back while replying with local data or chaos"
177             " reply.")
178    return True
179
180
181def inplace_servfail_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
182                              region, **kwargs):
183    """
184    Function that will be registered as an inplace callback function.
185    It will be called when answering with SERVFAIL.
186
187    :param qinfo: query_info struct;
188    :param qstate: module qstate. If not None the relevant opt_lists are
189                   available here;
190    :param rep: reply_info struct. None;
191    :param rcode: return code for the query. LDNS_RCODE_SERVFAIL;
192    :param edns: edns_data to be sent to the client side. If qstate is None
193                 edns.opt_list contains the EDNS options sent from the client
194                 side. It SHOULD NOT be altered;
195    :param opt_list_out: the list with the EDNS options that will be sent as a
196                         reply. It can be populated with EDNS options;
197    :param region: region to allocate temporary data. Needs to be used when we
198                   want to append a new option to opt_list_out.
199    :param **kwargs: Dictionary that may contain parameters added in a future
200                     release. Current parameters:
201        ``repinfo``: Reply information for a communication point (comm_reply).
202
203    :return: True on success, False on failure.
204
205    For demonstration purposes we want to reply with an empty EDNS code '65003'
206    and log the IP address of the client.
207
208    """
209    log_info("python: called back while servfail.")
210    # Append the example ENDS option
211    b = bytearray.fromhex("")
212    edns_opt_list_append(opt_list_out, 65003, b, region)
213
214    # Log the client's IP address
215    comm_reply = kwargs['repinfo']
216    if comm_reply:
217        addr = comm_reply.addr
218        port = comm_reply.port
219        addr_family = comm_reply.family
220        log_info("python: Client IP: {}({}), port: {}"
221                 "".format(addr, addr_family, port))
222
223    return True
224
225
226def inplace_query_callback(qinfo, flags, qstate, addr, zone, region, **kwargs):
227    """
228    Function that will be registered as an inplace callback function.
229    It will be called before sending a query to a backend server.
230
231    :param qinfo: query_info struct;
232    :param flags: flags of the query;
233    :param qstate: module qstate. opt_lists are available here;
234    :param addr: struct sockaddr_storage. Address of the backend server;
235    :param zone: zone name in binary;
236    :param region: region to allocate temporary data. Needs to be used when we
237                   want to append a new option to opt_lists.
238    :param **kwargs: Dictionary that may contain parameters added in a future
239                     release.
240    """
241    log_info("python: outgoing query to {}@{}".format(addr.addr, addr.port))
242    return True
243
244
245def init_standard(id, env):
246    """
247    New version of the init function.
248
249    The function's signature is the same as the C counterpart and allows for
250    extra functionality during init.
251
252    ..note:: This function is preferred by unbound over the old init function.
253    ..note:: The previously accessible configuration options can now be found in
254             env.cfg.
255
256    """
257    log_info("python: inited script {}".format(env.cfg.python_script))
258
259    # Register the inplace_reply_callback function as an inplace callback
260    # function when answering a resolved query.
261    if not register_inplace_cb_reply(inplace_reply_callback, env, id):
262        return False
263
264    # Register the inplace_cache_callback function as an inplace callback
265    # function when answering from cache.
266    if not register_inplace_cb_reply_cache(inplace_cache_callback, env, id):
267        return False
268
269    # Register the inplace_local_callback function as an inplace callback
270    # function when answering from local data.
271    if not register_inplace_cb_reply_local(inplace_local_callback, env, id):
272        return False
273
274    # Register the inplace_servfail_callback function as an inplace callback
275    # function when answering with SERVFAIL.
276    if not register_inplace_cb_reply_servfail(inplace_servfail_callback, env, id):
277        return False
278
279    # Register the inplace_query_callback function as an inplace callback
280    # before sending a query to a backend server.
281    if not register_inplace_cb_query(inplace_query_callback, env, id):
282        return False
283
284    return True
285
286
287def init(id, cfg):
288    """
289    Previous version of the init function.
290
291    ..note:: This function is still supported for backwards compatibility when
292             the init_standard function is missing. When init_standard is
293             present this function SHOULD be omitted to avoid confusion to the
294             reader.
295
296    """
297    return True
298
299
300def deinit(id): return True
301
302
303def inform_super(id, qstate, superqstate, qdata): return True
304
305
306def operate(id, event, qstate, qdata):
307    if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS):
308        qstate.ext_state[id] = MODULE_WAIT_MODULE
309        return True
310
311    elif event == MODULE_EVENT_MODDONE:
312        qstate.ext_state[id] = MODULE_FINISHED
313        return True
314
315    log_err("pythonmod: Unknown event")
316    qstate.ext_state[id] = MODULE_ERROR
317    return True
318