diff options
Diffstat (limited to 'pythonmod')
-rw-r--r-- | pythonmod/doc/conf.py | 5 | ||||
-rw-r--r-- | pythonmod/doc/examples/example1.rst | 14 | ||||
-rw-r--r-- | pythonmod/doc/examples/example2.rst | 45 | ||||
-rw-r--r-- | pythonmod/doc/examples/example4.rst | 180 | ||||
-rw-r--r-- | pythonmod/doc/examples/example5.rst | 191 | ||||
-rw-r--r-- | pythonmod/doc/examples/example6.rst | 299 | ||||
-rw-r--r-- | pythonmod/doc/examples/index.rst | 17 | ||||
-rw-r--r-- | pythonmod/doc/install.rst | 44 | ||||
-rw-r--r-- | pythonmod/doc/modules/functions.rst | 139 | ||||
-rw-r--r-- | pythonmod/doc/modules/struct.rst | 187 | ||||
-rw-r--r-- | pythonmod/examples/edns.py | 194 | ||||
-rw-r--r-- | pythonmod/examples/inplace_callbacks.py | 244 | ||||
-rw-r--r-- | pythonmod/interface.i | 427 | ||||
-rw-r--r-- | pythonmod/pythonmod.c | 42 | ||||
-rw-r--r-- | pythonmod/pythonmod.h | 12 | ||||
-rw-r--r-- | pythonmod/test-edns.conf | 17 | ||||
-rw-r--r-- | pythonmod/test-inplace_callbacks.py | 17 |
17 files changed, 1826 insertions, 248 deletions
diff --git a/pythonmod/doc/conf.py b/pythonmod/doc/conf.py index bc7a5aba68d5..7fcfe2d0508c 100644 --- a/pythonmod/doc/conf.py +++ b/pythonmod/doc/conf.py @@ -80,10 +80,13 @@ pygments_style = 'sphinx' # Options for HTML output # ----------------------- +# The theme that the html output should use. +html_theme = "classic" + # The style sheet to use for HTML and HTML Help pages. A file of that name # must exist either in Sphinx' static/ path, or in one of the custom paths # given in html_static_path. -html_style = 'default.css' +#html_style = 'default.css' # The name for this set of Sphinx documents. If None, it defaults to # "<project> v<release> documentation". diff --git a/pythonmod/doc/examples/example1.rst b/pythonmod/doc/examples/example1.rst index b49e64409255..ccd76da5a77e 100644 --- a/pythonmod/doc/examples/example1.rst +++ b/pythonmod/doc/examples/example1.rst @@ -1,10 +1,12 @@ .. _log_handler: Packet logger -========================= +============= This example shows how to log and print details about query and response. -As soon as the ``iterator`` has finished (event is :data:`module_event_moddone`), ``qstate.return_msg`` contains response packet or ``None``. +As soon as the ``iterator`` has finished (event is +:data:`module_event_moddone`), ``qstate.return_msg`` contains response packet +or ``None``. This packet will be send to a client that asked for it. Complete source code @@ -14,14 +16,16 @@ Complete source code :language: python Testing ------------------- +------- Run the unbound server: ``root@localhost>unbound -dv -c ./test-log.conf`` -In case you use own configuration file, don't forget to enable python module: ``module-config: "validator python iterator"`` and use valid script path: ``python-script: "./examples/log.py"``. +In case you use own configuration file, don't forget to enable python module: +``module-config: "validator python iterator"`` and use valid script path: +``python-script: "./examples/log.py"``. -Example of output:: +Example of output:: [1231790168] unbound[7941:0] info: response for <f.gtld-servers.NET. AAAA IN> [1231790168] unbound[7941:0] info: reply from <gtld-servers.NET.> 192.5.6.31#53 diff --git a/pythonmod/doc/examples/example2.rst b/pythonmod/doc/examples/example2.rst index f00fcc239609..4ba9239a003f 100644 --- a/pythonmod/doc/examples/example2.rst +++ b/pythonmod/doc/examples/example2.rst @@ -1,12 +1,14 @@ Response generation -===================== +=================== This example shows how to handle queries and generate response packet. .. note:: - If the python module is the first module and validator module is enabled (``module-config: "python validator iterator"``), - a return_msg security flag has to be set at least to 2. Leaving security flag untouched causes that the - response will be refused by unbound worker as unbound will consider it as non-valid response. + If the python module is the first module and validator module is enabled + (``module-config: "python validator iterator"``), a return_msg security flag + has to be set at least to 2. Leaving security flag untouched causes that the + response will be refused by unbound worker as unbound will consider it as + non-valid response. Complete source code -------------------- @@ -27,20 +29,21 @@ Query for a A record ending with .localdomain Dig produces the following output:: - ;; global options: printcmd - ;; Got answer: - ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48426 - ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 - - ;; QUESTION SECTION: - ;test.xxx.localdomain. IN A - - ;; ANSWER SECTION: - test.xxx.localdomain. 10 IN A 127.0.0.1 - - ;; Query time: 2 msec - ;; SERVER: 127.0.0.1#53(127.0.0.1) - ;; WHEN: Mon Jan 01 12:46:02 2009 - ;; MSG SIZE rcvd: 54 - -As we handle (override) in python module only queries ending with "localdomain.", the unboud can still resolve host names. + ;; global options: printcmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48426 + ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + + ;; QUESTION SECTION: + ;test.xxx.localdomain. IN A + + ;; ANSWER SECTION: + test.xxx.localdomain. 10 IN A 127.0.0.1 + + ;; Query time: 2 msec + ;; SERVER: 127.0.0.1#53(127.0.0.1) + ;; WHEN: Mon Jan 01 12:46:02 2009 + ;; MSG SIZE rcvd: 54 + +As we handle (override) in the python module only queries ending with +``localdomain.``, unboud can still resolve host names. diff --git a/pythonmod/doc/examples/example4.rst b/pythonmod/doc/examples/example4.rst index b665351e84ec..3382109903aa 100644 --- a/pythonmod/doc/examples/example4.rst +++ b/pythonmod/doc/examples/example4.rst @@ -1,15 +1,19 @@ DNS-based language dictionary -=============================== +============================= This example shows how to create a simple language dictionary based on **DNS** -service within 15 minutes. The translation will be performed using TXT resource records. +service within 15 minutes. The translation will be performed using TXT resource +records. Key parts ------------ +--------- Initialization -~~~~~~~~~~~~~~~~~~~~~~~ -On **init()** module loads dictionary from a text file containing records in ``word [tab] translation`` format. +~~~~~~~~~~~~~~ + +On **init()** module loads dictionary from a text file containing records in +``word [tab] translation`` format. + :: def init(id, cfg): @@ -20,11 +24,14 @@ On **init()** module loads dictionary from a text file containing records in ``w The suitable file can be found at http://slovnik.zcu.cz DNS query and word lookup -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~ -Let's define the following format od DNS queries: ``word1[.]word2[.] ... wordN[.]{en,cs}[._dict_.cz.]``. +Let's define the following format od DNS queries: +``word1[.]word2[.] ... wordN[.]{en,cs}[._dict_.cz.]``. Word lookup is done by simple ``dict`` lookup from broken DNS request. -Query name is divided into a list of labels. This list is accessible as qname_list attribute. +Query name is divided into a list of labels. This list is accessible as +``qname_list`` attribute. + :: aword = ' '.join(qstate.qinfo.qname_list[0:-4]) #skip last four labels @@ -37,35 +44,40 @@ Query name is divided into a list of labels. This list is accessible as qname_li if (adict == "cs") and (aword in cz_dict): words = cz_dict[aword] # CS -> EN -In the first step, we get a string in the form: ``word1[space]word2[space]...word[space]``. -In the second assignment, fourth label from the end is obtained. This label should contains *"cs"* or *"en"*. -This label determines the direction of translation. - +In the first step, we get a string in the form: +``word1[space]word2[space]...word[space]``. +In the second assignment, fourth label from the end is obtained. This label +should contains *"cs"* or *"en"*. This label determines the direction of +translation. Forming of a DNS reply -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~ DNS reply is formed only on valid match and added as TXT answer. + :: - msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_TXT, RR_CLASS_IN, PKT_AA) + msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_TXT, RR_CLASS_IN, PKT_AA) - for w in words: - msg.answer.append("%s 300 IN TXT \"%s\"" % (qstate.qinfo.qname_str, w.replace("\"", "\\\""))) + for w in words: + msg.answer.append("%s 300 IN TXT \"%s\"" % (qstate.qinfo.qname_str, w.replace("\"", "\\\""))) - if not msg.set_return_msg(qstate): - qstate.ext_state[id] = MODULE_ERROR - return True + if not msg.set_return_msg(qstate): + qstate.ext_state[id] = MODULE_ERROR + return True - qstate.return_rcode = RCODE_NOERROR - qstate.ext_state[id] = MODULE_FINISHED - return True + qstate.return_rcode = RCODE_NOERROR + qstate.ext_state[id] = MODULE_FINISHED + return True -In the first step, a :class:`DNSMessage` instance is created for a given query *(type TXT)*. +In the first step, a :class:`DNSMessage` instance is created for a given query +*(type TXT)*. The fourth argument specifies the flags *(authoritative answer)*. -In the second step, we append TXT records containing the translation *(on the right side of RR)*. +In the second step, we append TXT records containing the translation *(on the +right side of RR)*. Then, the response is finished and ``qstate.return_msg`` contains new response. -If no error, the module sets :attr:`module_qstate.return_rcode` and :attr:`module_qstate.ext_state`. +If no error, the module sets :attr:`module_qstate.return_rcode` and +:attr:`module_qstate.ext_state`. **Steps:** @@ -82,80 +94,82 @@ Run the Unbound server: In case you use own configuration file, don't forget to enable Python module:: - module-config: "validator python iterator" + module-config: "validator python iterator" and use valid script path:: - python-script: "./examples/dict.py" + python-script: "./examples/dict.py" The translation from english word *"a bar fly"* to Czech can be done by doing: ``>>>dig TXT @127.0.0.1 a.bar.fly.en._dict_.cz`` -:: +:: + + ; (1 server found) + ;; global options: printcmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48691 + ;; flags: aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + + ;; QUESTION SECTION: + ;a.bar.fly.en._dict_.cz. IN TXT + + ;; ANSWER SECTION: + a.bar.fly.en._dict_.cz. 300 IN TXT "barov\253 povale\232" + + ;; Query time: 5 msec + ;; SERVER: 127.0.0.1#53(127.0.0.1) + ;; WHEN: Mon Jan 01 17:44:18 2009 + ;; MSG SIZE rcvd: 67 - ; (1 server found) - ;; global options: printcmd - ;; Got answer: - ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48691 - ;; flags: aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 - - ;; QUESTION SECTION: - ;a.bar.fly.en._dict_.cz. IN TXT - - ;; ANSWER SECTION: - a.bar.fly.en._dict_.cz. 300 IN TXT "barov\253 povale\232" - - ;; Query time: 5 msec - ;; SERVER: 127.0.0.1#53(127.0.0.1) - ;; WHEN: Mon Jan 01 17:44:18 2009 - ;; MSG SIZE rcvd: 67 - ``>>>dig TXT @127.0.0.1 nic.cs._dict_.cz`` + :: - ; <<>> DiG 9.5.0-P2 <<>> TXT @127.0.0.1 nic.cs._dict_.cz - ; (1 server found) - ;; global options: printcmd - ;; Got answer: - ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58710 - ;; flags: aa rd ra; QUERY: 1, ANSWER: 6, AUTHORITY: 0, ADDITIONAL: 0 - - ;; QUESTION SECTION: - ;nic.cs._dict_.cz. IN TXT - - ;; ANSWER SECTION: - nic.cs._dict_.cz. 300 IN TXT "aught" - nic.cs._dict_.cz. 300 IN TXT "naught" - nic.cs._dict_.cz. 300 IN TXT "nihil" - nic.cs._dict_.cz. 300 IN TXT "nix" - nic.cs._dict_.cz. 300 IN TXT "nothing" - nic.cs._dict_.cz. 300 IN TXT "zilch" - - ;; Query time: 0 msec - ;; SERVER: 127.0.0.1#53(127.0.0.1) - ;; WHEN: Mon Jan 01 17:45:39 2009 - ;; MSG SIZE rcvd: 143 - -Proof that the unbound still works as resolver. + ; <<>> DiG 9.5.0-P2 <<>> TXT @127.0.0.1 nic.cs._dict_.cz + ; (1 server found) + ;; global options: printcmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58710 + ;; flags: aa rd ra; QUERY: 1, ANSWER: 6, AUTHORITY: 0, ADDITIONAL: 0 + + ;; QUESTION SECTION: + ;nic.cs._dict_.cz. IN TXT + + ;; ANSWER SECTION: + nic.cs._dict_.cz. 300 IN TXT "aught" + nic.cs._dict_.cz. 300 IN TXT "naught" + nic.cs._dict_.cz. 300 IN TXT "nihil" + nic.cs._dict_.cz. 300 IN TXT "nix" + nic.cs._dict_.cz. 300 IN TXT "nothing" + nic.cs._dict_.cz. 300 IN TXT "zilch" + + ;; Query time: 0 msec + ;; SERVER: 127.0.0.1#53(127.0.0.1) + ;; WHEN: Mon Jan 01 17:45:39 2009 + ;; MSG SIZE rcvd: 143 + + Proof that the unbound still works as resolver. ``>>>dig A @127.0.0.1 www.nic.cz`` + :: - ; (1 server found) - ;; global options: printcmd - ;; Got answer: - ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19996 - ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 3, ADDITIONAL: 5 - - ;; QUESTION SECTION: - ;www.nic.cz. IN A - - ;; ANSWER SECTION: - www.nic.cz. 1662 IN A 217.31.205.50 - - ;; AUTHORITY SECTION: - ... + ; (1 server found) + ;; global options: printcmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19996 + ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 3, ADDITIONAL: 5 + + ;; QUESTION SECTION: + ;www.nic.cz. IN A + + ;; ANSWER SECTION: + www.nic.cz. 1662 IN A 217.31.205.50 + + ;; AUTHORITY SECTION: + ... Complete source code -------------------- diff --git a/pythonmod/doc/examples/example5.rst b/pythonmod/doc/examples/example5.rst new file mode 100644 index 000000000000..058fc331e4ba --- /dev/null +++ b/pythonmod/doc/examples/example5.rst @@ -0,0 +1,191 @@ +EDNS options +============ + +This example shows how to interact with EDNS options. + +When quering unbound with the EDNS option ``65001`` and data ``0xc001`` we +expect an answer with the same EDNS option code and data ``0xdeadbeef``. + + +Key parts +~~~~~~~~~ + +This example relies on the following functionalities: + + +Registering EDNS options +------------------------ + +By registering EDNS options we can tune unbound's behavior when encountering a +query with a known EDNS option. The two available options are: + +- ``bypass_cache_stage``: If set to ``True`` unbound will not try to answer + from cache. Instead execution is passed to the modules +- ``no_aggregation``: If set to ``True`` unbound will consider this query + unique and will not aggregate it with similar queries + +Both values default to ``False``. + +.. code-block:: python + + if not register_edns_option(env, 65001, bypass_cache_stage=True, + no_aggregation=True): + log_info("python: Could not register EDNS option {}".format(65001)) + + +EDNS option lists +----------------- + +EDNS option lists can be found in the :class:`module_qstate` class. There are +four available lists in total: + +- :class:`module_qstate.edns_opts_front_in`: options that came from the client + side. **Should not** be changed +- :class:`module_qstate.edns_opts_back_out`: options that will be sent to the + server side. Can be populated by edns literate modules +- :class:`module_qstate.edns_opts_back_in`: options that came from the server + side. **Should not** be changed +- :class:`module_qstate.edns_opts_front_out`: options that will be sent to the + client side. Can be populated by edns literate modules + +Each list element has the following members: + +- ``code``: the EDNS option code; +- ``data``: the EDNS option data. + + +Reading an EDNS option list +........................... + +The lists' contents can be accessed in python by their ``_iter`` counterpart as +an iterator: + +.. code-block:: python + + if not edns_opt_list_is_empty(qstate.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))) + + +Writing to an EDNS option list +.............................. + +By appending to an EDNS option list we can add new EDNS options. The new +element is going to be allocated in :class:`module_qstate.region`. The data +**must** be represented with a python ``bytearray``: + +.. code-block:: python + + b = bytearray.fromhex("deadbeef") + if not edns_opt_list_append(qstate.edns_opts_front_out, + o.code, b, qstate.region): + log_info("python: Could not append EDNS option {}".format(o.code)) + +We can also remove an EDNS option code from an EDNS option list. + +.. code-block:: python + + if not edns_opt_list_remove(edns_opt_list, code): + log_info("python: Option code {} was not found in the " + "list.".format(code)) + +.. note:: All occurences of the EDNS option code will be removed from the list: + + +Controlling other modules' cache behavior +----------------------------------------- + +During the modules' operation, some modules may interact with the cache +(e.g., iterator). This behavior can be controlled by using the following +:class:`module_qstate` flags: + +- :class:`module_qstate.no_cache_lookup`: Modules *operating after* this module + will not lookup the cache for an answer +- :class:`module_qstate.no_cache_store`: Modules *operating after* this module + will not store the response in the cache + +Both values default to ``0``. + +.. code-block:: python + + 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") + + +Testing +~~~~~~~ + +Run the Unbound server: :: + + root@localhost$ unbound -dv -c ./test-edns.conf + +In case you use your own configuration file, don't forget to enable the Python +module:: + + module-config: "validator python iterator" + +and use a valid script path:: + + python-script: "./examples/edns.py" + +Quering with EDNS option ``65001:0xc001``: + +:: + + root@localhost$ dig @localhost nlnetlabs.nl +ednsopt=65001:c001 + + ; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost nlnetlabs.nl +ednsopt=65001:c001 + ; (1 server found) + ;; global options: +cmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33450 + ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 3 + + ;; OPT PSEUDOSECTION: + ; EDNS: version: 0, flags:; udp: 4096 + ; OPT=65001: de ad be ef ("....") + ;; QUESTION SECTION: + ;nlnetlabs.nl. IN A + + ;; ANSWER SECTION: + nlnetlabs.nl. 10200 IN A 185.49.140.10 + + ;; AUTHORITY SECTION: + nlnetlabs.nl. 10200 IN NS anyns.pch.net. + nlnetlabs.nl. 10200 IN NS ns.nlnetlabs.nl. + nlnetlabs.nl. 10200 IN NS ns-ext1.sidn.nl. + nlnetlabs.nl. 10200 IN NS sec2.authdns.ripe.net. + + ;; ADDITIONAL SECTION: + ns.nlnetlabs.nl. 10200 IN AAAA 2a04:b900::8:0:0:60 + ns.nlnetlabs.nl. 10200 IN A 185.49.140.60 + + ;; Query time: 10 msec + ;; SERVER: 127.0.0.1#53(127.0.0.1) + ;; WHEN: Mon Dec 05 14:50:56 CET 2016 + ;; MSG SIZE rcvd: 212 + + +Complete source code +~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: ../../examples/edns.py + :language: python diff --git a/pythonmod/doc/examples/example6.rst b/pythonmod/doc/examples/example6.rst new file mode 100644 index 000000000000..ce89aab99f40 --- /dev/null +++ b/pythonmod/doc/examples/example6.rst @@ -0,0 +1,299 @@ +Inplace callbacks +================= + +This example shows how to register and use inplace callback functions. These +functions are going to be called just before unbound replies back to a client. +They can perform certain actions without interrupting unbound's execution flow +(e.g. add/remove EDNS options, manipulate the reply). + +Two different scenarios will be shown: + +- If answering from cache and the client used EDNS option code ``65002`` we + will answer with the same code but with data ``0xdeadbeef``; +- When answering with a SERVFAIL we also add an empty EDNS option code + ``65003``. + + +Key parts +~~~~~~~~~ + +This example relies on the following functionalities: + + +Registering inplace callback functions +-------------------------------------- + +There are four types of inplace callback functions: + +- `inplace callback reply functions`_ +- `inplace callback reply_cache functions`_ +- `inplace callback reply_local functions`_ +- `inplace callback reply_servfail functions`_ + + +Inplace callback reply functions +................................ + +Called when answering with a *resolved* query. + +The callback function's prototype is the following: + +.. code-block:: python + + 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. + """ + +.. note:: The function's name is irrelevant. + +We can register such function as: + +.. code-block:: python + + if not register_inplace_cb_reply(inplace_reply_callback, env): + log_info("python: Could not register inplace callback function.") + + +Inplace callback reply_cache functions +...................................... + +Called when answering *from cache*. + +The callback function's prototype is the following: + +.. code-block:: python + + 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. + """ + +.. note:: The function's name is irrelevant. + +We can register such function as: + +.. code-block:: python + + if not register_inplace_cb_reply_cache(inplace_cache_callback, env): + log_info("python: Could not register inplace callback function.") + + +Inplace callback reply_local functions +...................................... + +Called when answering with *local data* or a *Chaos(CH) reply*. + +The callback function's prototype is the following: + +.. code-block:: python + + 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. + """ + +.. note:: The function's name is irrelevant. + +We can register such function as: + +.. code-block:: python + + if not register_inplace_cb_reply_local(inplace_local_callback, env): + log_info("python: Could not register inplace callback function.") + + +Inplace callback reply_servfail functions +......................................... + +Called when answering with *SERVFAIL*. + +The callback function's prototype is the following: + +.. code-block:: python + + 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. + """ + +.. note:: The function's name is irrelevant. + +We can register such function as: + +.. code-block:: python + + if not register_inplace_cb_reply_servfail(inplace_servfail_callback, env): + log_info("python: Could not register inplace callback function.") + + +Testing +~~~~~~~ + +Run the Unbound server: :: + + root@localhost$ unbound -dv -c ./test-inplace_callbacks.conf + +In case you use your own configuration file, don't forget to enable the Python +module:: + + module-config: "validator python iterator" + +and use a valid script path :: + + python-script: "./examples/inplace_callbacks.py" + +On the first query for the nlnetlabs.nl A record we get no EDNS option back: + +:: + + root@localhost$ dig @localhost nlnetlabs.nl +ednsopt=65002 + + ; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost nlnetlabs.nl +ednsopt=65002 + ; (1 server found) + ;; global options: +cmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48057 + ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 3 + + ;; OPT PSEUDOSECTION: + ; EDNS: version: 0, flags:; udp: 4096 + ;; QUESTION SECTION: + ;nlnetlabs.nl. IN A + + ;; ANSWER SECTION: + nlnetlabs.nl. 10200 IN A 185.49.140.10 + + ;; AUTHORITY SECTION: + nlnetlabs.nl. 10200 IN NS ns.nlnetlabs.nl. + nlnetlabs.nl. 10200 IN NS sec2.authdns.ripe.net. + nlnetlabs.nl. 10200 IN NS anyns.pch.net. + nlnetlabs.nl. 10200 IN NS ns-ext1.sidn.nl. + + ;; ADDITIONAL SECTION: + ns.nlnetlabs.nl. 10200 IN A 185.49.140.60 + ns.nlnetlabs.nl. 10200 IN AAAA 2a04:b900::8:0:0:60 + + ;; Query time: 813 msec + ;; SERVER: 127.0.0.1#53(127.0.0.1) + ;; WHEN: Mon Dec 05 16:15:32 CET 2016 + ;; MSG SIZE rcvd: 204 + +When we issue the same query again we get a cached response and the expected +``65002: 0xdeadbeef`` EDNS option: + +:: + + root@localhost$ dig @localhost nlnetlabs.nl +ednsopt=65002 + + ; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost nlnetlabs.nl +ednsopt=65002 + ; (1 server found) + ;; global options: +cmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26489 + ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 3 + + ;; OPT PSEUDOSECTION: + ; EDNS: version: 0, flags:; udp: 4096 + ; OPT=65002: de ad be ef ("....") + ;; QUESTION SECTION: + ;nlnetlabs.nl. IN A + + ;; ANSWER SECTION: + nlnetlabs.nl. 10197 IN A 185.49.140.10 + + ;; AUTHORITY SECTION: + nlnetlabs.nl. 10197 IN NS ns.nlnetlabs.nl. + nlnetlabs.nl. 10197 IN NS sec2.authdns.ripe.net. + nlnetlabs.nl. 10197 IN NS anyns.pch.net. + nlnetlabs.nl. 10197 IN NS ns-ext1.sidn.nl. + + ;; ADDITIONAL SECTION: + ns.nlnetlabs.nl. 10197 IN AAAA 2a04:b900::8:0:0:60 + ns.nlnetlabs.nl. 10197 IN A 185.49.140.60 + + ;; Query time: 0 msec + ;; SERVER: 127.0.0.1#53(127.0.0.1) + ;; WHEN: Mon Dec 05 16:50:04 CET 2016 + ;; MSG SIZE rcvd: 212 + +By issuing a query for a bogus domain unbound replies with SERVFAIL and an +empty EDNS option code ``65003``. *For this example to work unbound needs to be +validating*: + +:: + + root@localhost$ dig @localhost bogus.nlnetlabs.nl txt + + ; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost bogus.nlnetlabs.nl txt + ; (1 server found) + ;; global options: +cmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 19865 + ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1 + + ;; OPT PSEUDOSECTION: + ; EDNS: version: 0, flags:; udp: 4096 + ; OPT=65003 + ;; QUESTION SECTION: + ;bogus.nlnetlabs.nl. IN TXT + + ;; Query time: 11 msec + ;; SERVER: 127.0.0.1#53(127.0.0.1) + ;; WHEN: Mon Dec 05 17:06:01 CET 2016 + ;; MSG SIZE rcvd: 51 + + +Complete source code +~~~~~~~~~~~~~~~~~~~~ +.. literalinclude:: ../../examples/inplace_callbacks.py + :language: python diff --git a/pythonmod/doc/examples/index.rst b/pythonmod/doc/examples/index.rst index 6c50225810ef..93d9b8e1e133 100644 --- a/pythonmod/doc/examples/index.rst +++ b/pythonmod/doc/examples/index.rst @@ -1,15 +1,16 @@ .. _Tutorials: -============================== -Tutorials -============================== +Examples +======== -Here you can find several tutorials which clarify the usage and capabilities of Unbound scriptable interface. +Here you can find several tutorials which clarify the usage and capabilities of +the Unbound scriptable interface. -`Tutorials` +Tutorials +--------- .. toctree:: - :maxdepth: 2 - :glob: + :maxdepth: 2 + :glob: - example* + example* diff --git a/pythonmod/doc/install.rst b/pythonmod/doc/install.rst index 991e2b4becf9..b8d0b9fa60d1 100644 --- a/pythonmod/doc/install.rst +++ b/pythonmod/doc/install.rst @@ -1,39 +1,44 @@ Installation -=================================== +============ -**Prerequisites** +Prerequisites +------------- Python 2.4 or higher, SWIG 1.3 or higher, GNU make -**Download** +Download +-------- You can download the source codes `here`_. The latest release is 1.1.1, Jan 15, 2009. .. _here: unbound-1.1.1-py.tar.gz -**Compiling** +Compiling +--------- After downloading, you can compile the Unbound library by doing:: - > tar -xzf unbound-1.1.1-py.tar.gz - > cd unbound-1.1.1 - > ./configure --with-pythonmodule - > make + > tar -xzf unbound-1.1.1-py.tar.gz + > cd unbound-1.1.1 + > ./configure --with-pythonmodule + > make You need GNU make to compile sources. SWIG and Python devel libraries to compile extension module. -**Testing** +Testing +------- If the compilation is successful, you can test the extension module by:: - > cd pythonmod - > make sudo # or "make test" or "make suexec" + > cd pythonmod + > make sudo # or "make test" or "make suexec" -This will start unbound server with language dictionary service (see :ref:`Tutorials`). +This will start unbound server with language dictionary service +(see :ref:`Tutorials`). In order to test this service, type:: - + > dig TXT @127.0.0.1 aught.en._dict_.cz Dig should print this message (czech equivalent of aught):: @@ -44,16 +49,17 @@ Dig should print this message (czech equivalent of aught):: ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30085 ;; flags: aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 - + ;; QUESTION SECTION: - ;aught.en._dict_.cz. IN TXT - + ;aught.en._dict_.cz. IN TXT + ;; ANSWER SECTION: - aught.en._dict_.cz. 300 IN TXT "nic" - + aught.en._dict_.cz. 300 IN TXT "nic" + ;; Query time: 11 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Thu Jan 10 16:45:58 2009 ;; MSG SIZE rcvd: 52 -The ``pythonmod/examples`` directory contains simple applications written in Python. +The ``pythonmod/examples`` directory contains simple applications written in +Python. diff --git a/pythonmod/doc/modules/functions.rst b/pythonmod/doc/modules/functions.rst index 45a469fec04e..627d44922477 100644 --- a/pythonmod/doc/modules/functions.rst +++ b/pythonmod/doc/modules/functions.rst @@ -7,25 +7,26 @@ Network .. function:: ntohs(netshort) This subroutine converts values between the host and network byte order. - Specifically, **ntohs()** converts 16-bit quantities from network byte order to host byte order. - + Specifically, **ntohs()** converts 16-bit quantities from network byte order + to host byte order. + :param netshort: 16-bit short addr :rtype: converted addr - - + + Cache ----- .. function:: storeQueryInCache(qstate, qinfo, msgrep, is_referral) Store pending query in local cache. - + :param qstate: :class:`module_qstate` :param qinfo: :class:`query_info` :param msgrep: :class:`reply_info` :param is_referal: integer :rtype: boolean - + .. function:: invalidateQueryInCache(qstate, qinfo) Invalidate record in local cache. @@ -34,6 +35,111 @@ Cache :param qinfo: :class:`query_info` +EDNS options +------------ + +.. function:: register_edns_option(env, code, bypass_cache_stage=False, no_aggregation=False) + + Register EDNS option code. + + :param env: :class:`module_env` + :param code: option code(integer) + :param bypass_cache_stage: whether to bypass the cache response stage + :param no_aggregation: whether this query should be unique + :return: ``1`` if successful, ``0`` otherwise + :rtype: integer + +.. function:: edns_opt_list_find(list, code) + + Find the EDNS option code in the EDNS option list. + + :param list: linked list of :class:`edns_option` + :param code: option code (integer) + :return: the edns option if found or None + :rtype: :class:`edns_option` or None + +.. function:: edns_opt_list_remove(list, code); + + Remove an ENDS option code from the list. + .. note:: All :class:`edns_option` with the code will be removed + + :param list: linked list of :class:`edns_option` + :param code: option code (integer) + :return: ``1`` if at least one :class:`edns_option` was removed, ``0`` otherwise + :rtype: integer + +.. function:: edns_opt_list_append(list, code, data, region) + + Append given EDNS option code with data to the list. + + :param list: linked list of :class:`edns_option` + :param code: option code (integer) + :param data: EDNS data. **Must** be a :class:`bytearray` + :param region: :class:`regional` + +.. function:: edns_opt_list_is_empty(list) + + Check if an EDNS option list is empty. + + :param list: linked list of :class:`edns_option` + :return: ``1`` if list is empty, ``0`` otherwise + :rtype: integer + + +Inplace callbacks +----------------- + +.. function:: inplace_cb_reply(qinfo, qstate, rep, rcode, edns, opt_list_out, region) + + Function prototype for callback functions used in + `register_inplace_cb_reply`_, `register_inplace_cb_reply_cache`_, + `register_inplace_cb_reply_local` and `register_inplace_cb_reply_servfail`. + + :param qinfo: :class:`query_info` + :param qstate: :class:`module_qstate` + :param rep: :class:`reply_info` + :param rcode: return code (integer), check ``RCODE_`` constants. + :param edns: :class:`edns_data` + :param opt_list_out: :class:`edns_option`. EDNS option list to append options to. + :param region: :class:`regional` + +.. function:: register_inplace_cb_reply(py_cb, env) + + Register py_cb as an inplace reply callback function. + + :param py_cb: Python function that follows `inplace_cb_reply`_'s prototype. **Must** be callable. + :param env: :class:`module_env` + :return: True on success, False otherwise + :rtype: boolean + +.. function:: register_inplace_cb_reply_cache(py_cb, env) + + Register py_cb as an inplace reply_cache callback function. + + :param py_cb: Python function that follows `inplace_cb_reply`_'s prototype. **Must** be callable. + :param env: :class:`module_env` + :return: True on success, False otherwise + :rtype: boolean + +.. function:: register_inplace_cb_reply_local(py_cb, env) + + Register py_cb as an inplace reply_local callback function. + + :param py_cb: Python function that follows `inplace_cb_reply`_'s prototype. **Must** be callable. + :param env: :class:`module_env` + :return: True on success, False otherwise + :rtype: boolean + +.. function:: register_inplace_cb_reply_servfail(py_cb, env) + + Register py_cb as an inplace reply_servfail callback function. + + :param py_cb: Python function that follows `inplace_cb_reply`_'s prototype. **Must** be callable. + :param env: :class:`module_env` + :return: True on success, False otherwise + :rtype: boolean + + Logging ------- @@ -71,50 +177,51 @@ Logging :param msg: string desc to accompany the hexdump. :param data: data to dump in hex format. :param length: length of data. - + .. function:: log_dns_msg(str, qinfo, reply) Log DNS message. - + :param str: string message :param qinfo: :class:`query_info` :param reply: :class:`reply_info` - + .. function:: log_query_info(verbosity_value, str, qinf) Log query information. - + :param verbosity_value: see constants :param str: string message :param qinf: :class:`query_info` - + .. function:: regional_log_stats(r) Log regional statistics. - + :param r: :class:`regional` + Debugging --------- .. function:: strextstate(module_ext_state) Debug utility, module external qstate to string. - + :param module_ext_state: the state value. :rtype: descriptive string. .. function:: strmodulevent(module_event) Debug utility, module event to string. - + :param module_event: the module event value. :rtype: descriptive string. - + .. function:: ldns_rr_type2str(atype) Convert RR type to string. - + .. function:: ldns_rr_class2str(aclass) Convert RR class to string. diff --git a/pythonmod/doc/modules/struct.rst b/pythonmod/doc/modules/struct.rst index 669f36d91ea2..3af5d8a48c01 100644 --- a/pythonmod/doc/modules/struct.rst +++ b/pythonmod/doc/modules/struct.rst @@ -6,55 +6,94 @@ module_qstate .. class:: module_qstate - Module state, per query. - - This class provides these data attributes: - - .. attribute:: qinfo - - (:class:`query_info`) Informations about query being answered. Name, RR type, RR class. - - .. attribute:: query_flags - - (uint16) Flags for query. See QF_BIT\_ predefined constants. - - .. attribute:: is_priming - - If this is a (stub or root) priming query (with hints). - - .. attribute:: reply - - comm_reply contains server replies. - - .. attribute:: return_msg - - (:class:`dns_msg`) The reply message, with message for client and calling module (read-only attribute). - Note that if you want to create of modify return_msg you should use :class:`DNSMessage`. - - .. attribute:: return_rcode - - The rcode, in case of error, instead of a reply message. Determines whether the return_msg contains reply. - - .. attribute:: region - - Region for this query. Cleared when query process finishes. - - .. attribute:: curmod - - Which module is executing. - - .. attribute:: ext_state[] - - Module states. - - .. attribute:: env - - Environment for this query. - - .. attribute:: mesh_info - - Mesh related information for this query. + Module state, per query. + + This class provides these data attributes: + + .. attribute:: qinfo + + (:class:`query_info`) Informations about query being answered. Name, RR type, RR class. + + .. attribute:: query_flags + + (uint16) Flags for query. See QF_BIT\_ predefined constants. + + .. attribute:: is_priming + + If this is a (stub or root) priming query (with hints). + + .. attribute:: reply + + comm_reply contains server replies. + + .. attribute:: return_msg + + (:class:`dns_msg`) The reply message, with message for client and calling module (read-only attribute). + Note that if you want to create of modify return_msg you should use :class:`DNSMessage`. + + .. attribute:: return_rcode + + The rcode, in case of error, instead of a reply message. Determines whether the return_msg contains reply. + + .. attribute:: region + + Region for this query. Cleared when query process finishes. + + .. attribute:: curmod + + Which module is executing. + + .. attribute:: ext_state[] + + Module states. + + .. attribute:: env + + Environment for this query. + .. attribute:: mesh_info + + Mesh related information for this query. + + .. attribute:: edns_opts_front_in + + Incoming EDNS options from the front end. + + .. attribute:: edns_opts_front_in_iter + + Iterator for `edns_opts_front_in`. + + .. attribute:: edns_opts_back_out + + Outgoing EDNS options to the back end. + + .. attribute:: edns_opts_back_out_iter + + Iterator for `edns_opts_back_out`. + + .. attribute:: edns_opts_back_in + + Incoming EDNS options from the back end. + + .. attribute:: edns_opts_back_in_iter + + Iterator for `ends_opts_back_in`. + + .. attribute:: edns_opts_front_out + + Outgoing EDNS options to the front end. + + .. attribute:: edns_opts_front_out_iter + + Iterator for `edns_opts_front_out`. + + .. attribute:: no_cache_lookup + + Flag to indicate whether modules should answer from the cache. + + .. attribute:: no_cache_store + + Flag to indicate whether modules should store answer in the cache. query_info ---------------- @@ -94,7 +133,57 @@ query_info .. attribute:: qclass_str The ``qclass`` in display presentation format (string). - + +edns_data +--------- + +.. class:: edns_data + + This class represents the EDNS information parsed/encoded from/to a packet. It provides these data attributes: + + .. attribute:: edns_present + + If EDNS OPT record is present. + + .. attribute:: ext_rcode + + Extended RCODE. + + .. attribute:: edns_version + + The EDNS version number. + + .. attribute:: bits + + The EDNS bits field from ttl (host order): Z. + + .. attribute:: udp_size + + UDP reassembly size. + + .. attribute:: opt_list + + The EDNS option list. + + .. attribute:: opt_list_iter + + Iterator for `opt_list`. + +edns_option +----------- + +.. class:: edns_option + + This class represents an EDNS option (code, data) found in EDNS option lists. It provides these data attributes: + + .. attribute:: code + + The EDNS option code. + + .. attribute:: data + + The EDNS option data. + reply_info -------------------- 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 diff --git a/pythonmod/interface.i b/pythonmod/interface.i index 4b20c6ec135c..89d138486606 100644 --- a/pythonmod/interface.i +++ b/pythonmod/interface.i @@ -1,7 +1,6 @@ /* * interface.i: unbound python module */ - %module unboundmodule %{ /** @@ -34,10 +33,10 @@ #include "sldns/pkthdr.h" %} -%include "stdint.i" // uint_16_t can be known type now +%include "stdint.i" /* uint_16_t can be known type now */ %inline %{ - //converts [len][data][len][data][0] string to a List of labels (PyBytes) + /* converts [len][data][len][data][0] string to a List of labels (PyBytes) */ PyObject* GetNameAsLabelList(const char* name, int len) { PyObject* list; int cnt=0, i; @@ -202,13 +201,16 @@ struct packed_rrset_key { char* dname; size_t dname_len; uint32_t flags; - uint16_t type; //rrset type in network format - uint16_t rrset_class; //rrset class in network format + uint16_t type; /* rrset type in network format */ + uint16_t rrset_class; /* rrset class in network format */ %mutable; }; -//This subroutine converts values between the host and network byte order. -//Specifically, ntohs() converts 16-bit quantities from network byte order to host byte order. +/** + * This subroutine converts values between the host and network byte order. + * Specifically, ntohs() converts 16-bit quantities from network byte order to + * host byte order. + */ uint16_t ntohs(uint16_t netshort); %inline %{ @@ -269,17 +271,24 @@ struct lruhash_entry { %ignore packed_rrset_data::rr_data; struct packed_rrset_data { - uint32_t ttl; //TTL (in seconds like time()) + /* TTL (in seconds like time()) */ + uint32_t ttl; - size_t count; //number of rrs - size_t rrsig_count; //number of rrsigs + /* number of rrs */ + size_t count; + /* number of rrsigs */ + size_t rrsig_count; enum rrset_trust trust; enum sec_status security; - size_t* rr_len; //length of every rr's rdata - uint32_t *rr_ttl; //ttl of every rr - uint8_t** rr_data; //array of pointers to every rr's rdata; The rr_data[i] rdata is stored in uncompressed wireformat. + /* length of every rr's rdata */ + size_t* rr_len; + /* ttl of every rr */ + uint32_t *rr_ttl; + /* array of pointers to every rr's rdata. The rr_data[i] rdata is stored in + * uncompressed wireformat. */ + uint8_t** rr_data; }; %pythoncode %{ @@ -359,10 +368,10 @@ struct reply_info { size_t an_numrrsets; size_t ns_numrrsets; size_t ar_numrrsets; - size_t rrset_count; // an_numrrsets + ns_numrrsets + ar_numrrsets + size_t rrset_count; /* an_numrrsets + ns_numrrsets + ar_numrrsets */ struct ub_packed_rrset_key** rrsets; - struct rrset_ref ref[1]; //? + struct rrset_ref ref[1]; /* ? */ }; struct rrset_ref { @@ -396,11 +405,11 @@ struct dns_msg { struct rrset_ref* _rrset_ref_get(struct reply_info* r, int idx) { if ((r != NULL) && (idx >= 0) && ((size_t)idx < r->rrset_count)) { -//printf("_rrset_ref_get: %lX key:%lX\n", r->ref + idx, r->ref[idx].key); +/* printf("_rrset_ref_get: %lX key:%lX\n", r->ref + idx, r->ref[idx].key); */ return &(r->ref[idx]); -// return &(r->ref[idx]); +/* return &(r->ref[idx]); */ } -//printf("_rrset_ref_get: NULL\n"); +/* printf("_rrset_ref_get: NULL\n"); */ return NULL; } %} @@ -479,30 +488,166 @@ struct comm_reply { if _newclass:family = _swig_property(_family_get) %} } + +/* ************************************************************************************ * + Structure edns_option + * ************************************************************************************ */ +/* Rename the members to follow the python convention of marking them as + * private. Access to the opt_code and opt_data members is given by the later + * python defined code and data members respectively. */ +%rename(_next) edns_option::next; +%rename(_opt_code) edns_option::opt_code; +%rename(_opt_len) edns_option::opt_len; +%rename(_opt_data) edns_option::opt_data; +struct edns_option { + struct edns_option* next; + uint16_t opt_code; + size_t opt_len; + uint8_t* opt_data; +}; + +%inline %{ + PyObject* _edns_option_opt_code_get(struct edns_option* option) { + uint16_t opt_code = option->opt_code; + return PyInt_FromLong(opt_code); + } + + PyObject* _edns_option_opt_data_get(struct edns_option* option) { + return PyByteArray_FromStringAndSize((uint8_t*)option->opt_data, + option->opt_len); + } +%} +%extend edns_option { + %pythoncode %{ + def _opt_code_get(self): return _edns_option_opt_code_get(self) + __swig_getmethods__["code"] = _opt_code_get + if _newclass: opt_code = _swig_property(_opt_code_get) + + def _opt_data_get(self): return _edns_option_opt_data_get(self) + __swig_getmethods__["data"] = _opt_data_get + if _newclass: opt_data = _swig_property(_opt_data_get) + %} +} + /* ************************************************************************************ * + Structure edns_data + * ************************************************************************************ */ +/* This is ignored because we will pass a double pointer of this to Python + * with custom getmethods. This is done to bypass Swig's behavior to pass NULL + * pointers as None. */ +%ignore edns_data::opt_list; +struct edns_data { + int edns_present; + uint8_t ext_rcode; + uint8_t edns_version; + uint16_t bits; + uint16_t udp_size; + struct edns_option* opt_list; +}; +%inline %{ + struct edns_option** _edns_data_opt_list_get(struct edns_data* edns) { + return &edns->opt_list; + } +%} +%extend edns_data { + %pythoncode %{ + def _opt_list_iter(self): return EdnsOptsListIter(self.opt_list) + __swig_getmethods__["opt_list_iter"] = _opt_list_iter + if _newclass:opt_list_iter = _swig_property(_opt_list_iter) + def _opt_list(self): return _edns_data_opt_list_get(self) + __swig_getmethods__["opt_list"] = _opt_list + if _newclass:opt_list = _swig_property(_opt_list) + %} +} + +/* ************************************************************************************ * + Structure module_env + * ************************************************************************************ */ +struct module_env { + struct config_file* cfg; + struct slabhash* msg_cache; + struct rrset_cache* rrset_cache; + struct infra_cache* infra_cache; + struct key_cache* key_cache; + + /* --- services --- */ + struct outbound_entry* (*send_query)(struct query_info* qinfo, + uint16_t flags, int dnssec, int want_dnssec, int nocaps, + struct sockaddr_storage* addr, socklen_t addrlen, + uint8_t* zone, size_t zonelen, int ssl_upstream, + struct module_qstate* q); + void (*detach_subs)(struct module_qstate* qstate); + int (*attach_sub)(struct module_qstate* qstate, + struct query_info* qinfo, uint16_t qflags, int prime, + int valrec, struct module_qstate** newq); + void (*kill_sub)(struct module_qstate* newq); + int (*detect_cycle)(struct module_qstate* qstate, + struct query_info* qinfo, uint16_t flags, int prime, + int valrec); + + struct regional* scratch; + struct sldns_buffer* scratch_buffer; + struct worker* worker; + struct mesh_area* mesh; + struct alloc_cache* alloc; + struct ub_randstate* rnd; + time_t* now; + struct timeval* now_tv; + int need_to_validate; + struct val_anchors* anchors; + struct val_neg_cache* neg_cache; + struct comm_timer* probe_timer; + struct iter_forwards* fwds; + struct iter_hints* hints; + void* modinfo[MAX_MODULE]; + + void* inplace_cb_lists[inplace_cb_types_total]; + struct edns_known_option* edns_known_options; + size_t edns_known_options_num; +}; + + +/* ************************************************************************************ * Structure module_qstate * ************************************************************************************ */ %ignore module_qstate::ext_state; %ignore module_qstate::minfo; +/* These are ignored because we will pass a double pointer of them to Python + * with custom getmethods. This is done to bypass Swig's behavior to pass NULL + * pointers as None. */ +%ignore module_qstate::edns_opts_front_in; +%ignore module_qstate::edns_opts_back_out; +%ignore module_qstate::edns_opts_back_in; +%ignore module_qstate::edns_opts_front_out; + /* Query state */ struct module_qstate { struct query_info qinfo; - uint16_t query_flags; //See QF_BIT_xx constants - int is_priming; + uint16_t query_flags; /* See QF_BIT_xx constants */ + int is_priming; + int is_valrec; struct comm_reply* reply; struct dns_msg* return_msg; - int return_rcode; + int return_rcode; struct regional* region; /* unwrapped */ - int curmod; + int curmod; - enum module_ext_state ext_state[MAX_MODULE]; - void* minfo[MAX_MODULE]; + enum module_ext_state ext_state[MAX_MODULE]; + void* minfo[MAX_MODULE]; + time_t prefetch_leeway; struct module_env* env; /* unwrapped */ struct mesh_state* mesh_info; + + struct edns_option* edns_opts_front_in; + struct edns_option* edns_opts_back_out; + struct edns_option* edns_opts_back_in; + struct edns_option* edns_opts_front_out; + int no_cache_lookup; + int no_cache_store; }; %constant int MODULE_COUNT = MAX_MODULE; @@ -540,6 +685,25 @@ struct module_qstate { def __getitem__(self, index): return _unboundmodule._ext_state_get(self.obj, index) def __setitem__(self, index, value): _unboundmodule._ext_state_set(self.obj, index, value) def __len__(self): return _unboundmodule.MODULE_COUNT + + class EdnsOptsListIter: + def __init__(self, obj): + self._current = obj + self._temp = None + def __iter__(self): return self + def __next__(self): + """Python 3 compatibility""" + return self._get_next() + def next(self): + """Python 2 compatibility""" + return self._get_next() + def _get_next(self): + if not edns_opt_list_is_empty(self._current): + self._temp = self._current + self._current = _p_p_edns_option_get_next(self._current) + return _dereference_edns_option(self._temp) + else: + raise StopIteration %} %inline %{ @@ -549,12 +713,42 @@ struct module_qstate { } return 0; } - + void _ext_state_set(struct module_qstate* q, int idx, enum module_ext_state state) { if ((q != NULL) && (idx >= 0) && (idx < MAX_MODULE)) { q->ext_state[idx] = state; } } + + int edns_opt_list_is_empty(struct edns_option** opt) { + if (!opt || !(*opt)) return 1; + return 0; + } + + struct edns_option* _dereference_edns_option(struct edns_option** opt) { + if (!opt) return NULL; + return *opt; + } + + struct edns_option** _p_p_edns_option_get_next(struct edns_option** opt) { + return &(*opt)->next; + } + + struct edns_option** _edns_opts_front_in_get(struct module_qstate* q) { + return &q->edns_opts_front_in; + } + + struct edns_option** _edns_opts_back_out_get(struct module_qstate* q) { + return &q->edns_opts_back_out; + } + + struct edns_option** _edns_opts_back_in_get(struct module_qstate* q) { + return &q->edns_opts_back_in; + } + + struct edns_option** _edns_opts_front_out_get(struct module_qstate* q) { + return &q->edns_opts_front_out; + } %} %extend module_qstate { @@ -566,6 +760,32 @@ struct module_qstate { def __ext_state_get(self): return ExtState(self) __swig_getmethods__["ext_state"] = __ext_state_get if _newclass:ext_state = _swig_property(__ext_state_get)#, __ext_state_set) + + def _edns_opts_front_in_iter(self): return EdnsOptsListIter(self.edns_opts_front_in) + __swig_getmethods__["edns_opts_front_in_iter"] = _edns_opts_front_in_iter + if _newclass:edns_opts_front_in_iter = _swig_property(_edns_opts_front_in_iter) + def _edns_opts_back_out_iter(self): return EdnsOptsListIter(self.edns_opts_back_out) + __swig_getmethods__["edns_opts_back_out_iter"] = _edns_opts_back_out_iter + if _newclass:edns_opts_back_out_iter = _swig_property(_edns_opts_back_out_iter) + def _edns_opts_back_in_iter(self): return EdnsOptsListIter(self.edns_opts_back_in) + __swig_getmethods__["edns_opts_back_in_iter"] = _edns_opts_back_in_iter + if _newclass:edns_opts_back_in_iter = _swig_property(_edns_opts_back_in_iter) + def _edns_opts_front_out_iter(self): return EdnsOptsListIter(self.edns_opts_front_out) + __swig_getmethods__["edns_opts_front_out_iter"] = _edns_opts_front_out_iter + if _newclass:edns_opts_front_out_iter = _swig_property(_edns_opts_front_out_iter) + + def _edns_opts_front_in(self): return _edns_opts_front_in_get(self) + __swig_getmethods__["edns_opts_front_in"] = _edns_opts_front_in + if _newclass:edns_opts_front_in = _swig_property(_edns_opts_front_in) + def _edns_opts_back_out(self): return _edns_opts_back_out_get(self) + __swig_getmethods__["edns_opts_back_out"] = _edns_opts_back_out + if _newclass:edns_opts_back_out = _swig_property(_edns_opts_back_out) + def _edns_opts_back_in(self): return _edns_opts_back_in_get(self) + __swig_getmethods__["edns_opts_back_in"] = _edns_opts_back_in + if _newclass:edns_opts_back_in = _swig_property(_edns_opts_back_in) + def _edns_opts_front_out(self): return _edns_opts_front_out_get(self) + __swig_getmethods__["edns_opts_front_out"] = _edns_opts_front_out + if _newclass:edns_opts_front_out = _swig_property(_edns_opts_front_out) %} } @@ -1037,8 +1257,9 @@ struct delegpt* find_delegation(struct module_qstate* qstate, char *nm, size_t n /* ************************************************************************************ * Functions * ************************************************************************************ */ - -// Various debuging functions +/****************************** + * Various debuging functions * + ******************************/ void verbose(enum verbosity_value level, const char* format, ...); void log_info(const char* format, ...); void log_err(const char* format, ...); @@ -1048,24 +1269,166 @@ void log_dns_msg(const char* str, struct query_info* qinfo, struct reply_info* r void log_query_info(enum verbosity_value v, const char* str, struct query_info* qinf); void regional_log_stats(struct regional *r); -// Free allocated memory from marked sources returning corresponding types +/*************************************************************************** + * Free allocated memory from marked sources returning corresponding types * + ***************************************************************************/ %typemap(newfree, noblock = 1) char * { free($1); } -// Mark as source returning newly allocated memory +/*************************************************** + * Mark as source returning newly allocated memory * + ***************************************************/ %newobject sldns_wire2str_type; %newobject sldns_wire2str_class; -// LDNS functions +/****************** + * LDNS functions * + ******************/ char *sldns_wire2str_type(const uint16_t atype); char *sldns_wire2str_class(const uint16_t aclass); -// Functions from pythonmod_utils +/********************************** + * Functions from pythonmod_utils * + **********************************/ int storeQueryInCache(struct module_qstate* qstate, struct query_info* qinfo, struct reply_info* msgrep, int is_referral); void invalidateQueryInCache(struct module_qstate* qstate, struct query_info* qinfo); -// Module conversion functions +/******************************* + * Module conversion functions * + *******************************/ const char* strextstate(enum module_ext_state s); const char* strmodulevent(enum module_ev e); +/************************** + * Edns related functions * + **************************/ +struct edns_option* edns_opt_list_find(struct edns_option* list, uint16_t code); +int edns_register_option(uint16_t opt_code, int bypass_cache_stage, + int no_aggregation, struct module_env* env); + +%pythoncode %{ + def register_edns_option(env, code, bypass_cache_stage=False, + no_aggregation=False): + """Wrapper function to provide keyword attributes.""" + return edns_register_option(code, bypass_cache_stage, + no_aggregation, env) +%} + +/****************************** + * Callback related functions * + ******************************/ +/* typemap to check if argument is callable */ +%typemap(in) PyObject *py_cb { + if (!PyCallable_Check($input)) { + SWIG_exception_fail(SWIG_TypeError, "Need a callable object!"); + return NULL; + } + $1 = $input; +} +/* typemap to get content/size from a bytearray */ +%typemap(in) (size_t len, uint8_t* py_bytearray_data) { + if (!PyByteArray_CheckExact($input)) { + SWIG_exception_fail(SWIG_TypeError, "Expected bytearray!"); + return NULL; + } + $2 = PyByteArray_AsString($input); + $1 = PyByteArray_Size($input); +} + +int edns_opt_list_remove(struct edns_option** list, uint16_t code); +int edns_opt_list_append(struct edns_option** list, uint16_t code, size_t len, + uint8_t* py_bytearray_data, struct regional* region); + +%{ + /* This function is called by unbound in order to call the python + * callback function. */ + int python_inplace_cb_reply_generic(struct query_info* qinfo, + struct module_qstate* qstate, struct reply_info* rep, int rcode, + struct edns_data* edns, struct edns_option** opt_list_out, + struct regional* region, void* python_callback) + { + PyObject *func, *py_edns, *py_qstate, *py_opt_list_out, *py_qinfo; + PyObject *py_rep, *py_region; + PyObject *result; + int res = 0; + + func = (PyObject *) python_callback; + PyGILState_STATE gstate = PyGILState_Ensure(); + py_edns = SWIG_NewPointerObj((void*) edns, SWIGTYPE_p_edns_data, 0); + py_qstate = SWIG_NewPointerObj((void*) qstate, + SWIGTYPE_p_module_qstate, 0); + py_opt_list_out = SWIG_NewPointerObj((void*) opt_list_out, + SWIGTYPE_p_p_edns_option, 0); + py_qinfo = SWIG_NewPointerObj((void*) qinfo, SWIGTYPE_p_query_info, 0); + py_rep = SWIG_NewPointerObj((void*) rep, SWIGTYPE_p_reply_info, 0); + py_region = SWIG_NewPointerObj((void*) region, SWIGTYPE_p_regional, 0); + result = PyObject_CallFunction(func, "OOOiOOO", py_qinfo, py_qstate, + py_rep, rcode, py_edns, py_opt_list_out, py_region); + Py_XDECREF(py_edns); + Py_XDECREF(py_qstate); + Py_XDECREF(py_opt_list_out); + Py_XDECREF(py_qinfo); + Py_XDECREF(py_rep); + Py_XDECREF(py_region); + if (result) { + res = PyInt_AsLong(result); + } + Py_XDECREF(result); + PyGILState_Release(gstate); + return res; + } + + /* Swig implementations for Python */ + static int register_inplace_cb_reply(PyObject* py_cb, + struct module_env* env) + { + int ret = inplace_cb_reply_register( + python_inplace_cb_reply_generic, (void*) py_cb, env); + if (ret) Py_INCREF(py_cb); + return ret; + } + static int register_inplace_cb_reply_cache(PyObject* py_cb, + struct module_env* env) + { + int ret = inplace_cb_reply_cache_register( + python_inplace_cb_reply_generic, (void*) py_cb, env); + if (ret) Py_INCREF(py_cb); + return ret; + } + static int register_inplace_cb_reply_local(PyObject* py_cb, + struct module_env* env) + { + int ret = inplace_cb_reply_local_register( + python_inplace_cb_reply_generic, (void*) py_cb, env); + if (ret) Py_INCREF(py_cb); + return ret; + } + static int register_inplace_cb_reply_servfail(PyObject* py_cb, + struct module_env* env) + { + int ret = inplace_cb_reply_servfail_register( + python_inplace_cb_reply_generic, (void*) py_cb, env); + if (ret) Py_INCREF(py_cb); + return ret; + } +%} +/* C declarations */ +int inplace_cb_reply_register( + inplace_cb_reply_func_t* cb, void* cb_arg, struct module_env* env); +int inplace_cb_reply_cache_register( + inplace_cb_reply_func_t* cb, void* cb_arg, struct module_env* env); +int inplace_cb_reply_local_register( + inplace_cb_reply_func_t* cb, void* cb_arg, struct module_env* env); +int inplace_cb_reply_servfail_register( + inplace_cb_reply_func_t* cb, void* cb_arg, struct module_env* env); + +/* Swig declarations */ +static int register_inplace_cb_reply(PyObject* py_cb, + struct module_env* env); +static int register_inplace_cb_reply_cache(PyObject* py_cb, + struct module_env* env); +static int register_inplace_cb_reply_local(PyObject* py_cb, + struct module_env* env); +static int register_inplace_cb_reply_servfail(PyObject* py_cb, + struct module_env* env); diff --git a/pythonmod/pythonmod.c b/pythonmod/pythonmod.c index 6c5e6392df4b..92e09dcacfaf 100644 --- a/pythonmod/pythonmod.c +++ b/pythonmod/pythonmod.c @@ -112,8 +112,10 @@ int pythonmod_init(struct module_env* env, int id) { /* Initialize module */ FILE* script_py = NULL; - PyObject* py_cfg, *res; + PyObject* py_init_arg, *res; PyGILState_STATE gil; + int init_standard = 1; + struct pythonmod_env* pe = (struct pythonmod_env*)calloc(1, sizeof(struct pythonmod_env)); if (!pe) { @@ -156,10 +158,10 @@ int pythonmod_init(struct module_env* env, int id) PyRun_SimpleString("import sys \n"); PyRun_SimpleString("sys.path.append('.') \n"); if(env->cfg->directory && env->cfg->directory[0]) { - char wdir[1524]; - snprintf(wdir, sizeof(wdir), "sys.path.append('%s') \n", - env->cfg->directory); - PyRun_SimpleString(wdir); + char wdir[1524]; + snprintf(wdir, sizeof(wdir), "sys.path.append('%s') \n", + env->cfg->directory); + PyRun_SimpleString(wdir); } PyRun_SimpleString("sys.path.append('"RUN_DIR"') \n"); PyRun_SimpleString("sys.path.append('"SHARE_DIR"') \n"); @@ -198,11 +200,15 @@ int pythonmod_init(struct module_env* env, int id) fclose(script_py); - if ((pe->func_init = PyDict_GetItemString(pe->dict, "init")) == NULL) + if ((pe->func_init = PyDict_GetItemString(pe->dict, "init_standard")) == NULL) { - log_err("pythonmod: function init is missing in %s", pe->fname); - PyGILState_Release(gil); - return 0; + init_standard = 0; + if ((pe->func_init = PyDict_GetItemString(pe->dict, "init")) == NULL) + { + log_err("pythonmod: function init is missing in %s", pe->fname); + PyGILState_Release(gil); + return 0; + } } if ((pe->func_deinit = PyDict_GetItemString(pe->dict, "deinit")) == NULL) { @@ -223,16 +229,28 @@ int pythonmod_init(struct module_env* env, int id) return 0; } - py_cfg = SWIG_NewPointerObj((void*) env->cfg, SWIGTYPE_p_config_file, 0); - res = PyObject_CallFunction(pe->func_init, "iO", id, py_cfg); + if (init_standard) + { + py_init_arg = SWIG_NewPointerObj((void*) env, SWIGTYPE_p_module_env, 0); + } + else + { + py_init_arg = SWIG_NewPointerObj((void*) env->cfg, + SWIGTYPE_p_config_file, 0); + } + res = PyObject_CallFunction(pe->func_init, "iO", id, py_init_arg); if (PyErr_Occurred()) { log_err("pythonmod: Exception occurred in function init"); PyErr_Print(); + Py_XDECREF(res); + Py_XDECREF(py_init_arg); + PyGILState_Release(gil); + return 0; } Py_XDECREF(res); - Py_XDECREF(py_cfg); + Py_XDECREF(py_init_arg); PyGILState_Release(gil); return 1; diff --git a/pythonmod/pythonmod.h b/pythonmod/pythonmod.h index b108cf9234c1..2386882de9c2 100644 --- a/pythonmod/pythonmod.h +++ b/pythonmod/pythonmod.h @@ -55,14 +55,22 @@ int pythonmod_init(struct module_env* env, int id); void pythonmod_deinit(struct module_env* env, int id); /** python module operate on a query */ -void pythonmod_operate(struct module_qstate* qstate, enum module_ev event, int id, struct outbound_entry* outbound); +void pythonmod_operate(struct module_qstate* qstate, enum module_ev event, + int id, struct outbound_entry* outbound); /** python module */ -void pythonmod_inform_super(struct module_qstate* qstate, int id, struct module_qstate* super); +void pythonmod_inform_super(struct module_qstate* qstate, int id, + struct module_qstate* super); /** python module cleanup query state */ void pythonmod_clear(struct module_qstate* qstate, int id); /** python module alloc size routine */ size_t pythonmod_get_mem(struct module_env* env, int id); + +/** Declared here for fptr_wlist access. The definition is in interface.i. */ +int python_inplace_cb_reply_generic(struct query_info* qinfo, + struct module_qstate* qstate, struct reply_info* rep, int rcode, + struct edns_data* edns, struct edns_option** opt_list_out, + struct regional* region, void* python_callback); #endif /* PYTHONMOD_H */ diff --git a/pythonmod/test-edns.conf b/pythonmod/test-edns.conf new file mode 100644 index 000000000000..440947f01e34 --- /dev/null +++ b/pythonmod/test-edns.conf @@ -0,0 +1,17 @@ +# Example configuration file for edns.py +server: + verbosity: 1 + interface: 0.0.0.0 + do-daemonize: no + access-control: 0.0.0.0/0 allow + chroot: "" + username: "" + directory: "" + logfile: "" + pidfile: "unbound.pid" + module-config: "validator python iterator" + +# Python config section +python: + # Script file to load + python-script: "./examples/edns.py" diff --git a/pythonmod/test-inplace_callbacks.py b/pythonmod/test-inplace_callbacks.py new file mode 100644 index 000000000000..d7081faa616e --- /dev/null +++ b/pythonmod/test-inplace_callbacks.py @@ -0,0 +1,17 @@ +# Example configuration file for edns.py +server: + verbosity: 1 + interface: 0.0.0.0 + do-daemonize: no + access-control: 0.0.0.0/0 allow + chroot: "" + username: "" + directory: "" + logfile: "" + pidfile: "unbound.pid" + module-config: "validator python iterator" + +# Python config section +python: + # Script file to load + python-script: "./examples/inplace_callbacks.py" |