xref: /netbsd-src/external/bsd/unbound/dist/pythonmod/examples/inplace_callbacks.py (revision 181254a7b1bdde6873432bffef2d2decc4b5c22f)
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(es) of the client(s) 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                     It is None when the callback happens in the mesh
95                     states(modules).
96
97    :return: True on success, False on failure.
98
99    """
100    log_info("python: called back while replying.")
101    return True
102
103
104def inplace_cache_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
105                           region, **kwargs):
106    """
107    Function that will be registered as an inplace callback function.
108    It will be called when answering from the cache.
109
110    :param qinfo: query_info struct;
111    :param qstate: module qstate. None;
112    :param rep: reply_info struct;
113    :param rcode: return code for the query;
114    :param edns: edns_data sent from the client side. The list with the EDNS
115                 options is accessible through edns.opt_list. It SHOULD NOT be
116                 altered;
117    :param opt_list_out: the list with the EDNS options that will be sent as a
118                         reply. It can be populated with EDNS options;
119    :param region: region to allocate temporary data. Needs to be used when we
120                   want to append a new option to opt_list_out.
121    :param **kwargs: Dictionary that may contain parameters added in a future
122                     release. Current parameters:
123        ``repinfo``: Reply information for a communication point (comm_reply).
124                     It is None when the callback happens in the mesh
125                     states(modules).
126
127    :return: True on success, False on failure.
128
129    For demonstration purposes we want to see if EDNS option 65002 is present
130    and reply with a new value.
131
132    """
133    log_info("python: called back while answering from cache.")
134    # Inspect the incoming EDNS options.
135    if not edns_opt_list_is_empty(edns.opt_list):
136        log_info("python: available EDNS options:")
137        for o in edns.opt_list_iter:
138            log_info("python:    Code: {}, Data: '{}'".format(o.code,
139                "".join('{:02x}'.format(x) for x in o.data)))
140            if o.code == 65002:
141                log_info("python: *found option code 65002*")
142
143                # add to opt_list
144                # Data MUST be represented in a bytearray.
145                b = bytearray.fromhex("deadbeef")
146                if edns_opt_list_append(opt_list_out, o.code, b, region):
147                    log_info("python: *added new option code 65002*")
148                else:
149                    log_info("python: *failed to add new option code 65002*")
150                    return False
151                break
152
153    return True
154
155
156def inplace_local_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
157                           region, **kwargs):
158    """
159    Function that will be registered as an inplace callback function.
160    It will be called when answering from local data.
161
162    :param qinfo: query_info struct;
163    :param qstate: module qstate. None;
164    :param rep: reply_info struct;
165    :param rcode: return code for the query;
166    :param edns: edns_data sent from the client side. The list with the
167                 EDNS options is accessible through edns.opt_list. It
168                 SHOULD NOT be altered;
169    :param opt_list_out: the list with the EDNS options that will be sent as a
170                         reply. It can be populated with EDNS options;
171    :param region: region to allocate temporary data. Needs to be used when we
172                   want to append a new option to opt_list_out.
173    :param **kwargs: Dictionary that may contain parameters added in a future
174                     release. Current parameters:
175        ``repinfo``: Reply information for a communication point (comm_reply).
176                     It is None when the callback happens in the mesh
177                     states(modules).
178
179    :return: True on success, False on failure.
180
181    """
182    log_info("python: called back while replying with local data or chaos"
183             " reply.")
184    return True
185
186
187def inplace_servfail_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
188                              region, **kwargs):
189    """
190    Function that will be registered as an inplace callback function.
191    It will be called when answering with SERVFAIL.
192
193    :param qinfo: query_info struct;
194    :param qstate: module qstate. If not None the relevant opt_lists are
195                   available here;
196    :param rep: reply_info struct. None;
197    :param rcode: return code for the query. LDNS_RCODE_SERVFAIL;
198    :param edns: edns_data to be sent to the client side. If qstate is None
199                 edns.opt_list contains the EDNS options sent from the client
200                 side. It SHOULD NOT be altered;
201    :param opt_list_out: the list with the EDNS options that will be sent as a
202                         reply. It can be populated with EDNS options;
203    :param region: region to allocate temporary data. Needs to be used when we
204                   want to append a new option to opt_list_out.
205    :param **kwargs: Dictionary that may contain parameters added in a future
206                     release. Current parameters:
207        ``repinfo``: Reply information for a communication point (comm_reply).
208                     It is None when the callback happens in the mesh
209                     states(modules).
210
211    :return: True on success, False on failure.
212
213    For demonstration purposes we want to reply with an empty EDNS code '65003'
214    and log the IP address(es) of the client(s).
215
216    """
217    log_info("python: called back while servfail.")
218    # Append the example ENDS option
219    b = bytearray.fromhex("")
220    edns_opt_list_append(opt_list_out, 65003, b, region)
221
222    # Log the client(s) IP address(es)
223    comm_reply = kwargs['repinfo']
224    if comm_reply:
225        # If it is not None this callback was called before the query reached
226        # the mesh states(modules). There is only one client associated with
227        # this query.
228        addr = comm_reply.addr
229        port = comm_reply.port
230        addr_family = comm_reply.family
231        log_info("python: Client IP: {}({}), port: {}"
232                 "".format(addr, addr_family, port))
233    else:
234        # If it is not None this callback was called while the query is in the
235        # mesh states(modules). In this case they may be multiple clients
236        # waiting for this query.
237        # The following code is the same as with the resip.py example.
238        rl = qstate.mesh_info.reply_list
239        while (rl):
240            if rl.query_reply:
241                q = rl.query_reply
242                log_info("python: Client IP: {}({}), port: {}"
243                         "".format(q.addr, q.family, q.port))
244            rl = rl.next
245
246
247    return True
248
249
250def inplace_query_callback(qinfo, flags, qstate, addr, zone, region, **kwargs):
251    """
252    Function that will be registered as an inplace callback function.
253    It will be called before sending a query to a backend server.
254
255    :param qinfo: query_info struct;
256    :param flags: flags of the query;
257    :param qstate: module qstate. opt_lists are available here;
258    :param addr: struct sockaddr_storage. Address of the backend server;
259    :param zone: zone name in binary;
260    :param region: region to allocate temporary data. Needs to be used when we
261                   want to append a new option to opt_lists.
262    :param **kwargs: Dictionary that may contain parameters added in a future
263                     release.
264    """
265    log_info("python: outgoing query to {}@{}".format(addr.addr, addr.port))
266    return True
267
268
269def init_standard(id, env):
270    """
271    New version of the init function.
272
273    The function's signature is the same as the C counterpart and allows for
274    extra functionality during init.
275
276    ..note:: This function is preferred by unbound over the old init function.
277    ..note:: The previously accessible configuration options can now be found in
278             env.cfg.
279
280    """
281    log_info("python: inited script {}".format(env.cfg.python_script))
282
283    # Register the inplace_reply_callback function as an inplace callback
284    # function when answering a resolved query.
285    if not register_inplace_cb_reply(inplace_reply_callback, env, id):
286        return False
287
288    # Register the inplace_cache_callback function as an inplace callback
289    # function when answering from cache.
290    if not register_inplace_cb_reply_cache(inplace_cache_callback, env, id):
291        return False
292
293    # Register the inplace_local_callback function as an inplace callback
294    # function when answering from local data.
295    if not register_inplace_cb_reply_local(inplace_local_callback, env, id):
296        return False
297
298    # Register the inplace_servfail_callback function as an inplace callback
299    # function when answering with SERVFAIL.
300    if not register_inplace_cb_reply_servfail(inplace_servfail_callback, env, id):
301        return False
302
303    # Register the inplace_query_callback function as an inplace callback
304    # before sending a query to a backend server.
305    if not register_inplace_cb_query(inplace_query_callback, env, id):
306        return False
307
308    return True
309
310
311def init(id, cfg):
312    """
313    Previous version of the init function.
314
315    ..note:: This function is still supported for backwards compatibility when
316             the init_standard function is missing. When init_standard is
317             present this function SHOULD be omitted to avoid confusion to the
318             reader.
319
320    """
321    return True
322
323
324def deinit(id): return True
325
326
327def inform_super(id, qstate, superqstate, qdata): return True
328
329
330def operate(id, event, qstate, qdata):
331    if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS):
332        qstate.ext_state[id] = MODULE_WAIT_MODULE
333        return True
334
335    elif event == MODULE_EVENT_MODDONE:
336        qstate.ext_state[id] = MODULE_FINISHED
337        return True
338
339    log_err("pythonmod: Unknown event")
340    qstate.ext_state[id] = MODULE_ERROR
341    return True
342