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