xref: /netbsd-src/external/bsd/unbound/dist/pythonmod/examples/edns.py (revision 76c7fc5f6b13ed0b1508e6b313e88e59977ed78e)
1# -*- coding: utf-8 -*-
2'''
3 edns.py: python module showcasing EDNS option functionality.
4
5 Copyright (c) 2016, NLnet Labs.
6
7 This software is open source.
8
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions
11 are met:
12
13    * Redistributions of source code must retain the above copyright notice,
14      this list of conditions and the following disclaimer.
15
16    * Redistributions in binary form must reproduce the above copyright notice,
17      this list of conditions and the following disclaimer in the documentation
18      and/or other materials provided with the distribution.
19
20    * Neither the name of the organization nor the names of its
21      contributors may be used to endorse or promote products derived from this
22      software without specific prior written permission.
23
24 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
26 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
28 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 POSSIBILITY OF SUCH DAMAGE.
35'''
36#Try:
37# - dig @localhost nlnetlabs.nl +ednsopt=65001:c001
38#       This query will always reach the modules stage as EDNS option 65001 is
39#       registered to bypass the cache response stage. It will also be handled
40#       as a unique query because of the no_aggregation flag. This means that
41#       it will not be aggregated with other queries for the same qinfo.
42#       For demonstration purposes when option 65001 with hexdata 'c001' is
43#       sent from the client side this module will reply with the same code and
44#       data 'deadbeef'.
45
46# Useful functions:
47#   edns_opt_list_is_empty(edns_opt_list):
48#       Check if the option list is empty.
49#       Return True if empty, False otherwise.
50#
51#   edns_opt_list_append(edns_opt_list, code, data_bytearray, region):
52#       Append the EDNS option with code and data_bytearray to the given
53#           edns_opt_list.
54#       NOTE: data_bytearray MUST be a Python bytearray.
55#       Return True on success, False on failure.
56#
57#   edns_opt_list_remove(edns_opt_list, code):
58#       Remove all occurences of the given EDNS option code from the
59#           edns_opt_list.
60#       Return True when at least one EDNS option was removed, False otherwise.
61#
62#   register_edns_option(env, code, bypass_cache_stage=True,
63#                        no_aggregation=True):
64#       Register EDNS option code as a known EDNS option.
65#       bypass_cache_stage:
66#           bypasses answering from cache and allows the query to reach the
67#           modules for further EDNS handling.
68#       no_aggregation:
69#           makes every query with the said EDNS option code unique.
70#       Return True on success, False on failure.
71#
72# Examples on how to use the functions are given in this file.
73
74
75def init_standard(id, env):
76    """New version of the init function.
77    The function's signature is the same as the C counterpart and allows for
78    extra functionality during init.
79    ..note:: This function is preferred by unbound over the old init function.
80    ..note:: The previously accessible configuration options can now be found in
81             env.cgf.
82    """
83    log_info("python: inited script {}".format(env.cfg.python_script))
84
85    # Register EDNS option 65001 as a known EDNS option.
86    if not register_edns_option(env, 65001, bypass_cache_stage=True,
87                                no_aggregation=True):
88        return False
89
90    return True
91
92
93def init(id, cfg):
94    """Previous version init function.
95    ..note:: This function is still supported for backwards compatibility when
96             the init_standard function is missing. When init_standard is
97             present this function SHOULD be omitted to avoid confusion to the
98             reader.
99    """
100    return True
101
102
103def deinit(id): return True
104
105
106def inform_super(id, qstate, superqstate, qdata): return True
107
108
109def operate(id, event, qstate, qdata):
110    if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS):
111        # Detect if EDNS option code 56001 is present from the client side. If
112        # so turn on the flags for cache management.
113        if not edns_opt_list_is_empty(qstate.edns_opts_front_in):
114            log_info("python: searching for EDNS option code 65001 during NEW "
115                     "or PASS event ")
116            for o in qstate.edns_opts_front_in_iter:
117                if o.code == 65001:
118                    log_info("python: found EDNS option code 65001")
119                    # Instruct other modules to not lookup for an
120                    # answer in the cache.
121                    qstate.no_cache_lookup = 1
122                    log_info("python: enabled no_cache_lookup")
123
124                    # Instruct other modules to not store the answer in
125                    # the cache.
126                    qstate.no_cache_store = 1
127                    log_info("python: enabled no_cache_store")
128
129        #Pass on the query
130        qstate.ext_state[id] = MODULE_WAIT_MODULE
131        return True
132
133    elif event == MODULE_EVENT_MODDONE:
134        # If the client sent EDNS option code 65001 and data 'c001' reply
135        # with the same code and data 'deadbeef'.
136        if not edns_opt_list_is_empty(qstate.edns_opts_front_in):
137            log_info("python: searching for EDNS option code 65001 during "
138                     "MODDONE")
139            for o in qstate.edns_opts_front_in_iter:
140                if o.code == 65001 and o.data == bytearray.fromhex("c001"):
141                    b = bytearray.fromhex("deadbeef")
142                    if not edns_opt_list_append(qstate.edns_opts_front_out,
143                                           o.code, b, qstate.region):
144                        qstate.ext_state[id] = MODULE_ERROR
145                        return False
146
147        # List every EDNS option in all lists.
148        # The available lists are:
149        #   - qstate.edns_opts_front_in:  EDNS options that came from the
150        #                                 client side. SHOULD NOT be changed;
151        #
152        #   - qstate.edns_opts_back_out:  EDNS options that will be sent to the
153        #                                 server side. Can be populated by
154        #                                 EDNS literate modules;
155        #
156        #   - qstate.edns_opts_back_in:   EDNS options that came from the
157        #                                 server side. SHOULD NOT be changed;
158        #
159        #   - qstate.edns_opts_front_out: EDNS options that will be sent to the
160        #                                 client side. Can be populated by
161        #                                 EDNS literate modules;
162        #
163        # The lists' contents can be accessed in python by their _iter
164        # counterpart as an iterator.
165        if not edns_opt_list_is_empty(qstate.edns_opts_front_in):
166            log_info("python: EDNS options in edns_opts_front_in:")
167            for o in qstate.edns_opts_front_in_iter:
168                log_info("python:    Code: {}, Data: '{}'".format(o.code,
169                                "".join('{:02x}'.format(x) for x in o.data)))
170
171        if not edns_opt_list_is_empty(qstate.edns_opts_back_out):
172            log_info("python: EDNS options in edns_opts_back_out:")
173            for o in qstate.edns_opts_back_out_iter:
174                log_info("python:    Code: {}, Data: '{}'".format(o.code,
175                                "".join('{:02x}'.format(x) for x in o.data)))
176
177        if not edns_opt_list_is_empty(qstate.edns_opts_back_in):
178            log_info("python: EDNS options in edns_opts_back_in:")
179            for o in qstate.edns_opts_back_in_iter:
180                log_info("python:    Code: {}, Data: '{}'".format(o.code,
181                                "".join('{:02x}'.format(x) for x in o.data)))
182
183        if not edns_opt_list_is_empty(qstate.edns_opts_front_out):
184            log_info("python: EDNS options in edns_opts_front_out:")
185            for o in qstate.edns_opts_front_out_iter:
186                log_info("python:    Code: {}, Data: '{}'".format(o.code,
187                                "".join('{:02x}'.format(x) for x in o.data)))
188
189        qstate.ext_state[id] = MODULE_FINISHED
190        return True
191
192    log_err("pythonmod: Unknown event")
193    qstate.ext_state[id] = MODULE_ERROR
194    return True
195