]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - pythonmod/examples/inplace_callbacks.py
Vendor import of Unbound 1.9.0.
[FreeBSD/FreeBSD.git] / pythonmod / examples / inplace_callbacks.py
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
74 def 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
104 def 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
156 def 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
187 def 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
250 def 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
269 def 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.cgf.
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
311 def 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
324 def deinit(id): return True
325
326
327 def inform_super(id, qstate, superqstate, qdata): return True
328
329
330 def 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