xref: /netbsd-src/external/bsd/unbound/dist/pythonmod/examples/edns.py (revision 91f7d55fb697b5e0475da4718fa34c3a3ebeac85)
10cd9f4ecSchristos# -*- coding: utf-8 -*-
20cd9f4ecSchristos'''
30cd9f4ecSchristos edns.py: python module showcasing EDNS option functionality.
40cd9f4ecSchristos
50cd9f4ecSchristos Copyright (c) 2016, NLnet Labs.
60cd9f4ecSchristos
70cd9f4ecSchristos This software is open source.
80cd9f4ecSchristos
90cd9f4ecSchristos Redistribution and use in source and binary forms, with or without
100cd9f4ecSchristos modification, are permitted provided that the following conditions
110cd9f4ecSchristos are met:
120cd9f4ecSchristos
130cd9f4ecSchristos    * Redistributions of source code must retain the above copyright notice,
140cd9f4ecSchristos      this list of conditions and the following disclaimer.
150cd9f4ecSchristos
160cd9f4ecSchristos    * Redistributions in binary form must reproduce the above copyright notice,
170cd9f4ecSchristos      this list of conditions and the following disclaimer in the documentation
180cd9f4ecSchristos      and/or other materials provided with the distribution.
190cd9f4ecSchristos
200cd9f4ecSchristos    * Neither the name of the organization nor the names of its
210cd9f4ecSchristos      contributors may be used to endorse or promote products derived from this
220cd9f4ecSchristos      software without specific prior written permission.
230cd9f4ecSchristos
240cd9f4ecSchristos THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
250cd9f4ecSchristos "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
260cd9f4ecSchristos TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
270cd9f4ecSchristos PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
280cd9f4ecSchristos LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
290cd9f4ecSchristos CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
300cd9f4ecSchristos SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
310cd9f4ecSchristos INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
320cd9f4ecSchristos CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
330cd9f4ecSchristos ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
340cd9f4ecSchristos POSSIBILITY OF SUCH DAMAGE.
350cd9f4ecSchristos'''
360cd9f4ecSchristos#Try:
370cd9f4ecSchristos# - dig @localhost nlnetlabs.nl +ednsopt=65001:c001
380cd9f4ecSchristos#       This query will always reach the modules stage as EDNS option 65001 is
390cd9f4ecSchristos#       registered to bypass the cache response stage. It will also be handled
400cd9f4ecSchristos#       as a unique query because of the no_aggregation flag. This means that
410cd9f4ecSchristos#       it will not be aggregated with other queries for the same qinfo.
420cd9f4ecSchristos#       For demonstration purposes when option 65001 with hexdata 'c001' is
430cd9f4ecSchristos#       sent from the client side this module will reply with the same code and
440cd9f4ecSchristos#       data 'deadbeef'.
450cd9f4ecSchristos
460cd9f4ecSchristos# Useful functions:
470cd9f4ecSchristos#   edns_opt_list_is_empty(edns_opt_list):
480cd9f4ecSchristos#       Check if the option list is empty.
490cd9f4ecSchristos#       Return True if empty, False otherwise.
500cd9f4ecSchristos#
510cd9f4ecSchristos#   edns_opt_list_append(edns_opt_list, code, data_bytearray, region):
520cd9f4ecSchristos#       Append the EDNS option with code and data_bytearray to the given
530cd9f4ecSchristos#           edns_opt_list.
540cd9f4ecSchristos#       NOTE: data_bytearray MUST be a Python bytearray.
550cd9f4ecSchristos#       Return True on success, False on failure.
560cd9f4ecSchristos#
570cd9f4ecSchristos#   edns_opt_list_remove(edns_opt_list, code):
587a540f2bSchristos#       Remove all occurrences of the given EDNS option code from the
590cd9f4ecSchristos#           edns_opt_list.
600cd9f4ecSchristos#       Return True when at least one EDNS option was removed, False otherwise.
610cd9f4ecSchristos#
620cd9f4ecSchristos#   register_edns_option(env, code, bypass_cache_stage=True,
630cd9f4ecSchristos#                        no_aggregation=True):
640cd9f4ecSchristos#       Register EDNS option code as a known EDNS option.
650cd9f4ecSchristos#       bypass_cache_stage:
660cd9f4ecSchristos#           bypasses answering from cache and allows the query to reach the
670cd9f4ecSchristos#           modules for further EDNS handling.
680cd9f4ecSchristos#       no_aggregation:
690cd9f4ecSchristos#           makes every query with the said EDNS option code unique.
700cd9f4ecSchristos#       Return True on success, False on failure.
710cd9f4ecSchristos#
720cd9f4ecSchristos# Examples on how to use the functions are given in this file.
730cd9f4ecSchristos
740cd9f4ecSchristos
750cd9f4ecSchristosdef init_standard(id, env):
760cd9f4ecSchristos    """New version of the init function.
770cd9f4ecSchristos    The function's signature is the same as the C counterpart and allows for
780cd9f4ecSchristos    extra functionality during init.
790cd9f4ecSchristos    ..note:: This function is preferred by unbound over the old init function.
800cd9f4ecSchristos    ..note:: The previously accessible configuration options can now be found in
8101049ae6Schristos             env.cfg.
820cd9f4ecSchristos    """
83*91f7d55fSchristos    log_info("python: inited script {}".format(mod_env['script']))
840cd9f4ecSchristos
850cd9f4ecSchristos    # Register EDNS option 65001 as a known EDNS option.
860cd9f4ecSchristos    if not register_edns_option(env, 65001, bypass_cache_stage=True,
870cd9f4ecSchristos                                no_aggregation=True):
880cd9f4ecSchristos        return False
890cd9f4ecSchristos
900cd9f4ecSchristos    return True
910cd9f4ecSchristos
920cd9f4ecSchristos
930cd9f4ecSchristosdef init(id, cfg):
940cd9f4ecSchristos    """Previous version init function.
950cd9f4ecSchristos    ..note:: This function is still supported for backwards compatibility when
960cd9f4ecSchristos             the init_standard function is missing. When init_standard is
970cd9f4ecSchristos             present this function SHOULD be omitted to avoid confusion to the
980cd9f4ecSchristos             reader.
990cd9f4ecSchristos    """
1000cd9f4ecSchristos    return True
1010cd9f4ecSchristos
1020cd9f4ecSchristos
1030cd9f4ecSchristosdef deinit(id): return True
1040cd9f4ecSchristos
1050cd9f4ecSchristos
1060cd9f4ecSchristosdef inform_super(id, qstate, superqstate, qdata): return True
1070cd9f4ecSchristos
1080cd9f4ecSchristos
1090cd9f4ecSchristosdef operate(id, event, qstate, qdata):
1100cd9f4ecSchristos    if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS):
1110cd9f4ecSchristos        # Detect if EDNS option code 56001 is present from the client side. If
1120cd9f4ecSchristos        # so turn on the flags for cache management.
1130cd9f4ecSchristos        if not edns_opt_list_is_empty(qstate.edns_opts_front_in):
1140cd9f4ecSchristos            log_info("python: searching for EDNS option code 65001 during NEW "
1150cd9f4ecSchristos                     "or PASS event ")
1160cd9f4ecSchristos            for o in qstate.edns_opts_front_in_iter:
1170cd9f4ecSchristos                if o.code == 65001:
1180cd9f4ecSchristos                    log_info("python: found EDNS option code 65001")
1190cd9f4ecSchristos                    # Instruct other modules to not lookup for an
1200cd9f4ecSchristos                    # answer in the cache.
1210cd9f4ecSchristos                    qstate.no_cache_lookup = 1
1220cd9f4ecSchristos                    log_info("python: enabled no_cache_lookup")
1230cd9f4ecSchristos
1240cd9f4ecSchristos                    # Instruct other modules to not store the answer in
1250cd9f4ecSchristos                    # the cache.
1260cd9f4ecSchristos                    qstate.no_cache_store = 1
1270cd9f4ecSchristos                    log_info("python: enabled no_cache_store")
1280cd9f4ecSchristos
1290cd9f4ecSchristos        #Pass on the query
1300cd9f4ecSchristos        qstate.ext_state[id] = MODULE_WAIT_MODULE
1310cd9f4ecSchristos        return True
1320cd9f4ecSchristos
1330cd9f4ecSchristos    elif event == MODULE_EVENT_MODDONE:
1340cd9f4ecSchristos        # If the client sent EDNS option code 65001 and data 'c001' reply
1350cd9f4ecSchristos        # with the same code and data 'deadbeef'.
1360cd9f4ecSchristos        if not edns_opt_list_is_empty(qstate.edns_opts_front_in):
1370cd9f4ecSchristos            log_info("python: searching for EDNS option code 65001 during "
1380cd9f4ecSchristos                     "MODDONE")
1390cd9f4ecSchristos            for o in qstate.edns_opts_front_in_iter:
1400cd9f4ecSchristos                if o.code == 65001 and o.data == bytearray.fromhex("c001"):
1410cd9f4ecSchristos                    b = bytearray.fromhex("deadbeef")
1420cd9f4ecSchristos                    if not edns_opt_list_append(qstate.edns_opts_front_out,
1430cd9f4ecSchristos                                           o.code, b, qstate.region):
1440cd9f4ecSchristos                        qstate.ext_state[id] = MODULE_ERROR
1450cd9f4ecSchristos                        return False
1460cd9f4ecSchristos
1470cd9f4ecSchristos        # List every EDNS option in all lists.
1480cd9f4ecSchristos        # The available lists are:
1490cd9f4ecSchristos        #   - qstate.edns_opts_front_in:  EDNS options that came from the
1500cd9f4ecSchristos        #                                 client side. SHOULD NOT be changed;
1510cd9f4ecSchristos        #
1520cd9f4ecSchristos        #   - qstate.edns_opts_back_out:  EDNS options that will be sent to the
1530cd9f4ecSchristos        #                                 server side. Can be populated by
1540cd9f4ecSchristos        #                                 EDNS literate modules;
1550cd9f4ecSchristos        #
1560cd9f4ecSchristos        #   - qstate.edns_opts_back_in:   EDNS options that came from the
1570cd9f4ecSchristos        #                                 server side. SHOULD NOT be changed;
1580cd9f4ecSchristos        #
1590cd9f4ecSchristos        #   - qstate.edns_opts_front_out: EDNS options that will be sent to the
1600cd9f4ecSchristos        #                                 client side. Can be populated by
1610cd9f4ecSchristos        #                                 EDNS literate modules;
1620cd9f4ecSchristos        #
1630cd9f4ecSchristos        # The lists' contents can be accessed in python by their _iter
1640cd9f4ecSchristos        # counterpart as an iterator.
1650cd9f4ecSchristos        if not edns_opt_list_is_empty(qstate.edns_opts_front_in):
1660cd9f4ecSchristos            log_info("python: EDNS options in edns_opts_front_in:")
1670cd9f4ecSchristos            for o in qstate.edns_opts_front_in_iter:
1680cd9f4ecSchristos                log_info("python:    Code: {}, Data: '{}'".format(o.code,
1690cd9f4ecSchristos                                "".join('{:02x}'.format(x) for x in o.data)))
1700cd9f4ecSchristos
1710cd9f4ecSchristos        if not edns_opt_list_is_empty(qstate.edns_opts_back_out):
1720cd9f4ecSchristos            log_info("python: EDNS options in edns_opts_back_out:")
1730cd9f4ecSchristos            for o in qstate.edns_opts_back_out_iter:
1740cd9f4ecSchristos                log_info("python:    Code: {}, Data: '{}'".format(o.code,
1750cd9f4ecSchristos                                "".join('{:02x}'.format(x) for x in o.data)))
1760cd9f4ecSchristos
1770cd9f4ecSchristos        if not edns_opt_list_is_empty(qstate.edns_opts_back_in):
1780cd9f4ecSchristos            log_info("python: EDNS options in edns_opts_back_in:")
1790cd9f4ecSchristos            for o in qstate.edns_opts_back_in_iter:
1800cd9f4ecSchristos                log_info("python:    Code: {}, Data: '{}'".format(o.code,
1810cd9f4ecSchristos                                "".join('{:02x}'.format(x) for x in o.data)))
1820cd9f4ecSchristos
1830cd9f4ecSchristos        if not edns_opt_list_is_empty(qstate.edns_opts_front_out):
1840cd9f4ecSchristos            log_info("python: EDNS options in edns_opts_front_out:")
1850cd9f4ecSchristos            for o in qstate.edns_opts_front_out_iter:
1860cd9f4ecSchristos                log_info("python:    Code: {}, Data: '{}'".format(o.code,
1870cd9f4ecSchristos                                "".join('{:02x}'.format(x) for x in o.data)))
1880cd9f4ecSchristos
1890cd9f4ecSchristos        qstate.ext_state[id] = MODULE_FINISHED
1900cd9f4ecSchristos        return True
1910cd9f4ecSchristos
1920cd9f4ecSchristos    log_err("pythonmod: Unknown event")
1930cd9f4ecSchristos    qstate.ext_state[id] = MODULE_ERROR
1940cd9f4ecSchristos    return True
195