summaryrefslogtreecommitdiff
path: root/pythonmod/examples
diff options
context:
space:
mode:
Diffstat (limited to 'pythonmod/examples')
-rw-r--r--pythonmod/examples/edns.py194
-rw-r--r--pythonmod/examples/inplace_callbacks.py244
2 files changed, 438 insertions, 0 deletions
diff --git a/pythonmod/examples/edns.py b/pythonmod/examples/edns.py
new file mode 100644
index 000000000000..3fae1c652af8
--- /dev/null
+++ b/pythonmod/examples/edns.py
@@ -0,0 +1,194 @@
+# -*- coding: utf-8 -*-
+'''
+ edns.py: python module showcasing EDNS option functionality.
+
+ Copyright (c) 2016, NLnet Labs.
+
+ This software is open source.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the organization nor the names of its
+ contributors may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+'''
+#Try:
+# - dig @localhost nlnetlabs.nl +ednsopt=65001:c001
+# This query will always reach the modules stage as EDNS option 65001 is
+# registered to bypass the cache response stage. It will also be handled
+# as a unique query because of the no_aggregation flag. This means that
+# it will not be aggregated with other queries for the same qinfo.
+# For demonstration purposes when option 65001 with hexdata 'c001' is
+# sent from the client side this module will reply with the same code and
+# data 'deadbeef'.
+
+# Useful functions:
+# edns_opt_list_is_empty(edns_opt_list):
+# Check if the option list is empty.
+# Return True if empty, False otherwise.
+#
+# edns_opt_list_append(edns_opt_list, code, data_bytearray, region):
+# Append the EDNS option with code and data_bytearray to the given
+# edns_opt_list.
+# NOTE: data_bytearray MUST be a Python bytearray.
+# Return True on success, False on failure.
+#
+# edns_opt_list_remove(edns_opt_list, code):
+# Remove all occurences of the given EDNS option code from the
+# edns_opt_list.
+# Return True when at least one EDNS option was removed, False otherwise.
+#
+# register_edns_option(env, code, bypass_cache_stage=True,
+# no_aggregation=True):
+# Register EDNS option code as a known EDNS option.
+# bypass_cache_stage:
+# bypasses answering from cache and allows the query to reach the
+# modules for further EDNS handling.
+# no_aggregation:
+# makes every query with the said EDNS option code unique.
+# Return True on success, False on failure.
+#
+# Examples on how to use the functions are given in this file.
+
+
+def init_standard(id, env):
+ """New version of the init function.
+ The function's signature is the same as the C counterpart and allows for
+ extra functionality during init.
+ ..note:: This function is preferred by unbound over the old init function.
+ ..note:: The previously accesible configuration options can now be found in
+ env.cgf.
+ """
+ log_info("python: inited script {}".format(env.cfg.python_script))
+
+ # Register EDNS option 65001 as a known EDNS option.
+ if not register_edns_option(env, 65001, bypass_cache_stage=True,
+ no_aggregation=True):
+ return False
+
+ return True
+
+
+def init(id, cfg):
+ """Previous version init function.
+ ..note:: This function is still supported for backwards compatibility when
+ the init_standard function is missing. When init_standard is
+ present this function SHOULD be ommited to avoid confusion to the
+ reader.
+ """
+ return True
+
+
+def deinit(id): return True
+
+
+def inform_super(id, qstate, superqstate, qdata): return True
+
+
+def operate(id, event, qstate, qdata):
+ if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS):
+ # Detect if EDNS option code 56001 is present from the client side. If
+ # so turn on the flags for cache management.
+ if not edns_opt_list_is_empty(qstate.edns_opts_front_in):
+ log_info("python: searching for EDNS option code 65001 during NEW "
+ "or PASS event ")
+ for o in qstate.edns_opts_front_in_iter:
+ if o.code == 65001:
+ log_info("python: found EDNS option code 65001")
+ # Instruct other modules to not lookup for an
+ # answer in the cache.
+ qstate.no_cache_lookup = 1
+ log_info("python: enabled no_cache_lookup")
+
+ # Instruct other modules to not store the answer in
+ # the cache.
+ qstate.no_cache_store = 1
+ log_info("python: enabled no_cache_store")
+
+ #Pass on the query
+ qstate.ext_state[id] = MODULE_WAIT_MODULE
+ return True
+
+ elif event == MODULE_EVENT_MODDONE:
+ # If the client sent EDNS option code 65001 and data 'c001' reply
+ # with the same code and data 'deadbeef'.
+ if not edns_opt_list_is_empty(qstate.edns_opts_front_in):
+ log_info("python: searching for EDNS option code 65001 during "
+ "MODDONE")
+ for o in qstate.edns_opts_front_in_iter:
+ if o.code == 65001 and o.data == bytearray.fromhex("c001"):
+ b = bytearray.fromhex("deadbeef")
+ if not edns_opt_list_append(qstate.edns_opts_front_out,
+ o.code, b, qstate.region):
+ qstate.ext_state[id] = MODULE_ERROR
+ return False
+
+ # List every EDNS option in all lists.
+ # The available lists are:
+ # - qstate.edns_opts_front_in: EDNS options that came from the
+ # client side. SHOULD NOT be changed;
+ #
+ # - qstate.edns_opts_back_out: EDNS options that will be sent to the
+ # server side. Can be populated by
+ # EDNS literate modules;
+ #
+ # - qstate.edns_opts_back_in: EDNS options that came from the
+ # server side. SHOULD NOT be changed;
+ #
+ # - qstate.edns_opts_front_out: EDNS options that will be sent to the
+ # client side. Can be populated by
+ # EDNS literate modules;
+ #
+ # The lists' contents can be accessed in python by their _iter
+ # counterpart as an iterator.
+ if not edns_opt_list_is_empty(qstate.edns_opts_front_in):
+ log_info("python: EDNS options in edns_opts_front_in:")
+ for o in qstate.edns_opts_front_in_iter:
+ log_info("python: Code: {}, Data: '{}'".format(o.code,
+ "".join('{:02x}'.format(x) for x in o.data)))
+
+ if not edns_opt_list_is_empty(qstate.edns_opts_back_out):
+ log_info("python: EDNS options in edns_opts_back_out:")
+ for o in qstate.edns_opts_back_out_iter:
+ log_info("python: Code: {}, Data: '{}'".format(o.code,
+ "".join('{:02x}'.format(x) for x in o.data)))
+
+ if not edns_opt_list_is_empty(qstate.edns_opts_back_in):
+ log_info("python: EDNS options in edns_opts_back_in:")
+ for o in qstate.edns_opts_back_in_iter:
+ log_info("python: Code: {}, Data: '{}'".format(o.code,
+ "".join('{:02x}'.format(x) for x in o.data)))
+
+ if not edns_opt_list_is_empty(qstate.edns_opts_front_out):
+ log_info("python: EDNS options in edns_opts_front_out:")
+ for o in qstate.edns_opts_front_out_iter:
+ log_info("python: Code: {}, Data: '{}'".format(o.code,
+ "".join('{:02x}'.format(x) for x in o.data)))
+
+ qstate.ext_state[id] = MODULE_FINISHED
+ return True
+
+ log_err("pythonmod: Unknown event")
+ qstate.ext_state[id] = MODULE_ERROR
+ return True
diff --git a/pythonmod/examples/inplace_callbacks.py b/pythonmod/examples/inplace_callbacks.py
new file mode 100644
index 000000000000..e87614a12470
--- /dev/null
+++ b/pythonmod/examples/inplace_callbacks.py
@@ -0,0 +1,244 @@
+# -*- coding: utf-8 -*-
+'''
+ inplace_callbacks.py: python module showcasing inplace callback function
+ registration and functionality.
+
+ Copyright (c) 2016, NLnet Labs.
+
+ This software is open source.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the organization nor the names of its
+ contributors may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+'''
+#Try:
+# - dig @localhost nlnetlabs.nl +ednsopt=65002:
+# This query *could* be answered from cache. If so, unbound will reply
+# with the same EDNS option 65002, but with hexdata 'deadbeef' as data.
+#
+# - dig @localhost bogus.nlnetlabs.nl txt:
+# This query returns SERVFAIL as the txt record of bogus.nlnetlabs.nl is
+# intentionally bogus. The reply will contain an empty EDNS option
+# with option code 65003.
+# (unbound needs to be validating for this example to work)
+
+# Useful functions:
+# register_inplace_cb_reply(inplace_reply_callback, env):
+# Register the reply_callback function as an inplace callback function
+# when answering with a resolved query.
+# Return True on success, False on failure.
+#
+# register_inplace_cb_reply_cache(inplace_reply_cache_callback, env):
+# Register the reply_cache_callback function as an inplace callback
+# function when answering from cache.
+# Return True on success, False on failure.
+#
+# register_inplace_cb_reply_local(inplace_reply_local_callback, env):
+# Register the reply_local_callback function as an inplace callback
+# function when answering from local data or chaos reply.
+# Return True on success, False on failure.
+#
+# register_inplace_cb_reply_servfail(inplace_reply_servfail_callback, env):
+# Register the reply_servfail_callback function as an inplace callback
+# function when answering with servfail.
+# Return True on success, False on failure.
+#
+# Examples on how to use the functions are given in this file.
+
+
+def inplace_reply_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
+ region):
+ """Function that will be registered as an inplace callback function.
+ It will be called when answering with a resolved query.
+ :param qinfo: query_info struct;
+ :param qstate: module qstate. It contains the available opt_lists; It
+ SHOULD NOT be altered;
+ :param rep: reply_info struct;
+ :param rcode: return code for the query;
+ :param edns: edns_data to be sent to the client side. It SHOULD NOT be
+ altered;
+ :param opt_list_out: the list with the EDNS options that will be sent as a
+ reply. It can be populated with EDNS options;
+ :param region: region to allocate temporary data. Needs to be used when we
+ want to append a new option to opt_list_out.
+ :return: True on success, False on failure.
+ """
+ log_info("python: called back while replying.")
+ return True
+
+
+def inplace_cache_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
+ region):
+ """Function that will be registered as an inplace callback function.
+ It will be called when answering from the cache.
+ :param qinfo: query_info struct;
+ :param qstate: module qstate. None;
+ :param rep: reply_info struct;
+ :param rcode: return code for the query;
+ :param edns: edns_data sent from the client side. The list with the EDNS
+ options is accesible through edns.opt_list. It SHOULD NOT be
+ altered;
+ :param opt_list_out: the list with the EDNS options that will be sent as a
+ reply. It can be populated with EDNS options;
+ :param region: region to allocate temporary data. Needs to be used when we
+ want to append a new option to opt_list_out.
+ :return: True on success, False on failure.
+
+ For demostration purposes we want to see if EDNS option 65002 is present
+ and reply with a new value.
+ """
+ log_info("python: called back while answering from cache.")
+ # Inspect the incoming EDNS options.
+ if not edns_opt_list_is_empty(edns.opt_list):
+ log_info("python: available EDNS options:")
+ for o in edns.opt_list_iter:
+ log_info("python: Code: {}, Data: '{}'".format(o.code,
+ "".join('{:02x}'.format(x) for x in o.data)))
+ if o.code == 65002:
+ log_info("python: *found option code 65002*")
+
+ # add to opt_list
+ # Data MUST be represented in a bytearray.
+ b = bytearray.fromhex("deadbeef")
+ if edns_opt_list_append(opt_list_out, o.code, b, region):
+ log_info("python: *added new option code 65002*")
+ else:
+ log_info("python: *failed to add new option code 65002*")
+ return False
+ break
+
+ return True
+
+
+def inplace_local_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
+ region):
+ """Function that will be registered as an inplace callback function.
+ It will be called when answering from local data.
+ :param qinfo: query_info struct;
+ :param qstate: module qstate. None;
+ :param rep: reply_info struct;
+ :param rcode: return code for the query;
+ :param edns: edns_data sent from the client side. The list with the
+ EDNS options is accesible through edns.opt_list. It
+ SHOULD NOT be altered;
+ :param opt_list_out: the list with the EDNS options that will be sent as a
+ reply. It can be populated with EDNS options;
+ :param region: region to allocate temporary data. Needs to be used when we
+ want to append a new option to opt_list_out.
+ :return: True on success, False on failure.
+ """
+ log_info("python: called back while replying with local data or chaos"
+ " reply.")
+ return True
+
+
+def inplace_servfail_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
+ region):
+ """Function that will be registered as an inplace callback function.
+ It will be called when answering with SERVFAIL.
+ :param qinfo: query_info struct;
+ :param qstate: module qstate. If not None the relevant opt_lists are
+ available here;
+ :param rep: reply_info struct. None;
+ :param rcode: return code for the query. LDNS_RCODE_SERVFAIL;
+ :param edns: edns_data to be sent to the client side. If qstate is None
+ edns.opt_list contains the EDNS options sent from the client
+ side. It SHOULD NOT be altered;
+ :param opt_list_out: the list with the EDNS options that will be sent as a
+ reply. It can be populated with EDNS options;
+ :param region: region to allocate temporary data. Needs to be used when we
+ want to append a new option to opt_list_out.
+ :return: True on success, False on failure.
+
+ For demostration purposes we want to reply with an empty EDNS code '65003'.
+ """
+ log_info("python: called back while servfail.")
+ b = bytearray.fromhex("")
+ edns_opt_list_append(opt_list_out, 65003, b, region)
+ return True
+
+
+def init_standard(id, env):
+ """New version of the init function.
+ The function's signature is the same as the C counterpart and allows for
+ extra functionality during init.
+ ..note:: This function is preferred by unbound over the old init function.
+ ..note:: The previously accesible configuration options can now be found in
+ env.cgf.
+ """
+ log_info("python: inited script {}".format(env.cfg.python_script))
+
+ # Register the inplace_reply_callback function as an inplace callback
+ # function when answering a resolved query.
+ if not register_inplace_cb_reply(inplace_reply_callback, env):
+ return False
+
+ # Register the inplace_cache_callback function as an inplace callback
+ # function when answering from cache.
+ if not register_inplace_cb_reply_cache(inplace_cache_callback, env):
+ return False
+
+ # Register the inplace_local_callback function as an inplace callback
+ # function when answering from local data.
+ if not register_inplace_cb_reply_local(inplace_local_callback, env):
+ return False
+
+ # Register the inplace_servfail_callback function as an inplace callback
+ # function when answering with SERVFAIL.
+ if not register_inplace_cb_reply_servfail(inplace_servfail_callback, env):
+ return False
+
+ return True
+
+
+def init(id, cfg):
+ """Previous version init function.
+ ..note:: This function is still supported for backwards compatibility when
+ the init_standard function is missing. When init_standard is
+ present this function SHOULD be ommited to avoid confusion to the
+ reader.
+ """
+ return True
+
+
+def deinit(id): return True
+
+
+def inform_super(id, qstate, superqstate, qdata): return True
+
+
+def operate(id, event, qstate, qdata):
+ if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS):
+ qstate.ext_state[id] = MODULE_WAIT_MODULE
+ return True
+
+ elif event == MODULE_EVENT_MODDONE:
+ qstate.ext_state[id] = MODULE_FINISHED
+ return True
+
+ log_err("pythonmod: Unknown event")
+ qstate.ext_state[id] = MODULE_ERROR
+ return True