1 # -*- coding: utf-8 -*-
3 inplace_callbacks.py: python module showcasing inplace callback function
4 registration and functionality.
6 Copyright (c) 2016, NLnet Labs.
8 This software is open source.
10 Redistribution and use in source and binary forms, with or without
11 modification, are permitted provided that the following conditions
14 * Redistributions of source code must retain the above copyright notice,
15 this list of conditions and the following disclaimer.
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.
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.
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.
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.
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
48 # (unbound needs to be validating for this example to work)
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.
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.
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.
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.
71 # Examples on how to use the functions are given in this file.
74 def inplace_reply_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
77 Function that will be registered as an inplace callback function.
78 It will be called when answering with a resolved query.
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
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
97 :return: True on success, False on failure.
100 log_info("python: called back while replying.")
104 def inplace_cache_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
107 Function that will be registered as an inplace callback function.
108 It will be called when answering from the cache.
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
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
127 :return: True on success, False on failure.
129 For demonstration purposes we want to see if EDNS option 65002 is present
130 and reply with a new value.
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)))
141 log_info("python: *found option code 65002*")
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*")
149 log_info("python: *failed to add new option code 65002*")
156 def inplace_local_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
159 Function that will be registered as an inplace callback function.
160 It will be called when answering from local data.
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
179 :return: True on success, False on failure.
182 log_info("python: called back while replying with local data or chaos"
187 def inplace_servfail_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
190 Function that will be registered as an inplace callback function.
191 It will be called when answering with SERVFAIL.
193 :param qinfo: query_info struct;
194 :param qstate: module qstate. If not None the relevant opt_lists are
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
211 :return: True on success, False on failure.
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).
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)
222 # Log the client(s) IP address(es)
223 comm_reply = kwargs['repinfo']
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
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))
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
242 log_info("python: Client IP: {}({}), port: {}"
243 "".format(q.addr, q.family, q.port))
250 def inplace_query_callback(qinfo, flags, qstate, addr, zone, region, **kwargs):
252 Function that will be registered as an inplace callback function.
253 It will be called before sending a query to a backend server.
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
265 log_info("python: outgoing query to {}@{}".format(addr.addr, addr.port))
269 def init_standard(id, env):
271 New version of the init function.
273 The function's signature is the same as the C counterpart and allows for
274 extra functionality during init.
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
281 log_info("python: inited script {}".format(env.cfg.python_script))
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):
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):
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):
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):
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):
313 Previous version of the init function.
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
324 def deinit(id): return True
327 def inform_super(id, qstate, superqstate, qdata): return True
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
335 elif event == MODULE_EVENT_MODDONE:
336 qstate.ext_state[id] = MODULE_FINISHED
339 log_err("pythonmod: Unknown event")
340 qstate.ext_state[id] = MODULE_ERROR