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