diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:26:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:26:00 +0000 |
commit | 830407e88f9d40d954356c3754f2647f91d5c06a (patch) | |
tree | d6a0ece6feea91f3c656166dbaa884ef8a29740e /lib/README.rst | |
parent | Initial commit. (diff) | |
download | knot-resolver-upstream.tar.xz knot-resolver-upstream.zip |
Adding upstream version 5.6.0.upstream/5.6.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/README.rst')
-rw-r--r-- | lib/README.rst | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/lib/README.rst b/lib/README.rst new file mode 100644 index 0000000..1c8cf7b --- /dev/null +++ b/lib/README.rst @@ -0,0 +1,313 @@ +.. SPDX-License-Identifier: GPL-3.0-or-later + +********************* +Knot Resolver library +********************* + +Requirements +============ + +* libknot_ 2.0 (Knot DNS high-performance DNS library.) + +For users +========= + +The library as described provides basic services for name resolution, which should cover the usage, +examples are in the :ref:`resolve API <lib_api_rplan>` documentation. + +.. tip:: If you're migrating from ``getaddrinfo()``, see *"synchronous"* API, but the library offers iterative API as well to plug it into your event loop for example. + +.. _lib-layers: + +For developers +============== + +The resolution process starts with the functions in :ref:`resolve.c <lib_api_rplan>`, they are responsible for: + +* reacting to state machine state (i.e. calling consume layers if we have an answer ready) +* interacting with the library user (i.e. asking caller for I/O, accepting queries) +* fetching assets needed by layers (i.e. zone cut) + +This is the *driver*. The driver is not meant to know *"how"* the query resolves, but rather *"when"* to execute *"what"*. + +.. image:: ../doc/resolution.png + :align: center + +On the other side are *layers*. They are responsible for dissecting the packets and informing the driver about the results. For example, a *produce* layer generates query, a *consume* layer validates answer. + +.. tip:: Layers are executed asynchronously by the driver. If you need some asset beforehand, you can signalize the driver using returning state or current query flags. For example, setting a flag ``AWAIT_CUT`` forces driver to fetch zone cut information before the packet is consumed; setting a ``RESOLVED`` flag makes it pop a query after the current set of layers is finished; returning ``FAIL`` state makes it fail current query. + +Layers can also change course of resolution, for example by appending additional queries. + +.. code-block:: lua + + consume = function (state, req, answer) + if answer:qtype() == kres.type.NS then + local qry = req:push(answer:qname(), kres.type.SOA, kres.class.IN) + qry.flags.AWAIT_CUT = true + end + return state + end + +This **doesn't** block currently processed query, and the newly created sub-request will start as soon as driver finishes processing current. In some cases you might need to issue sub-request and process it **before** continuing with the current, i.e. validator may need a DNSKEY before it can validate signatures. In this case, layers can yield and resume afterwards. + +.. code-block:: lua + + consume = function (state, req, answer) + if state == kres.YIELD then + print('continuing yielded layer') + return kres.DONE + else + if answer:qtype() == kres.type.NS then + local qry = req:push(answer:qname(), kres.type.SOA, kres.class.IN) + qry.flags.AWAIT_CUT = true + print('planned SOA query, yielding') + return kres.YIELD + end + return state + end + end + +The ``YIELD`` state is a bit special. When a layer returns it, it interrupts current walk through the layers. When the layer receives it, +it means that it yielded before and now it is resumed. This is useful in a situation where you need a sub-request to determine whether current answer is valid or not. + +Writing layers +============== + +.. warning:: FIXME: this dev-docs section is outdated! Better see comments in files instead, for now. + +The resolver :ref:`library <lib_index>` leverages the processing API from the libknot to separate packet processing code into layers. + +.. note:: This is only crash-course in the library internals, see the resolver :ref:`library <lib_index>` documentation for the complete overview of the services. + +The library offers following services: + +- :ref:`Cache <lib_api_cache>` - MVCC cache interface for retrieving/storing resource records. +- :ref:`Resolution plan <lib_api_rplan>` - Query resolution plan, a list of partial queries (with hierarchy) sent in order to satisfy original query. This contains information about the queries, nameserver choice, timing information, answer and its class. +- :ref:`Nameservers <lib_api_nameservers>` - Reputation database of nameservers, this serves as an aid for nameserver choice. + +A processing layer is going to be called by the query resolution driver for each query, +so you're going to work with :ref:`struct kr_request <lib_api_rplan>` as your per-query context. +This structure contains pointers to resolution context, resolution plan and also the final answer. + +.. code-block:: c + + int consume(kr_layer_t *ctx, knot_pkt_t *pkt) + { + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + } + +This is only passive processing of the incoming answer. If you want to change the course of resolution, say satisfy a query from a local cache before the library issues a query to the nameserver, you can use states (see the :ref:`Static hints <mod-hints>` for example). + +.. code-block:: c + + int produce(kr_layer_t *ctx, knot_pkt_t *pkt) + { + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + + /* Query can be satisfied locally. */ + if (can_satisfy(qry)) { + /* This flag makes the resolver move the query + * to the "resolved" list. */ + qry->flags.RESOLVED = true; + return KR_STATE_DONE; + } + + /* Pass-through. */ + return ctx->state; + } + +It is possible to not only act during the query resolution, but also to view the complete resolution plan afterwards. This is useful for analysis-type tasks, or *"per answer"* hooks. + +.. code-block:: c + + int finish(kr_layer_t *ctx) + { + struct kr_request *req = ctx->req; + struct kr_rplan *rplan = req->rplan; + + /* Print the query sequence with start time. */ + char qname_str[KNOT_DNAME_MAXLEN]; + struct kr_query *qry = NULL + WALK_LIST(qry, rplan->resolved) { + knot_dname_to_str(qname_str, qry->sname, sizeof(qname_str)); + printf("%s at %u\n", qname_str, qry->timestamp); + } + + return ctx->state; + } + +APIs in Lua +=========== + +The APIs in Lua world try to mirror the C APIs using LuaJIT FFI, with several differences and enhancements. +There is not comprehensive guide on the API yet, but you can have a look at the bindings_ file. + +Elementary types and constants +------------------------------ + +* States are directly in ``kres`` table, e.g. ``kres.YIELD, kres.CONSUME, kres.PRODUCE, kres.DONE, kres.FAIL``. +* DNS classes are in ``kres.class`` table, e.g. ``kres.class.IN`` for Internet class. +* DNS types are in ``kres.type`` table, e.g. ``kres.type.AAAA`` for AAAA type. +* DNS rcodes types are in ``kres.rcode`` table, e.g. ``kres.rcode.NOERROR``. +* Extended DNS error codes are in ``kres.extended_error`` table, e.g. ``kres.extended_error.BLOCKED``. +* Packet sections (QUESTION, ANSWER, AUTHORITY, ADDITIONAL) are in the ``kres.section`` table. + +Working with domain names +------------------------- + +The internal API usually works with domain names in label format, you can convert between text and wire freely. + +.. code-block:: lua + + local dname = kres.str2dname('business.se') + local strname = kres.dname2str(dname) + +Working with resource records +----------------------------- + +Resource records are stored as tables. + +.. code-block:: lua + + local rr = { owner = kres.str2dname('owner'), + ttl = 0, + class = kres.class.IN, + type = kres.type.CNAME, + rdata = kres.str2dname('someplace') } + print(kres.rr2str(rr)) + +RRSets in packet can be accessed using FFI, you can easily fetch single records. + +.. code-block:: lua + + local rrset = { ... } + local rr = rrset:get(0) -- Return first RR + print(kres.dname2str(rr:owner())) + print(rr:ttl()) + print(kres.rr2str(rr)) + +Working with packets +-------------------- + +Packet is the data structure that you're going to see in layers very often. They consists of a header, and four sections: QUESTION, ANSWER, AUTHORITY, ADDITIONAL. The first section is special, as it contains the query name, type, and class; the rest of the sections contain RRSets. + +First you need to convert it to a type known to FFI and check basic properties. Let's start with a snippet of a *consume* layer. + +.. code-block:: lua + + consume = function (state, req, pkt) + print('rcode:', pkt:rcode()) + print('query:', kres.dname2str(pkt:qname()), pkt:qclass(), pkt:qtype()) + if pkt:rcode() ~= kres.rcode.NOERROR then + print('error response') + end + end + +You can enumerate records in the sections. + +.. code-block:: lua + + local records = pkt:section(kres.section.ANSWER) + for i = 1, #records do + local rr = records[i] + if rr.type == kres.type.AAAA then + print(kres.rr2str(rr)) + end + end + +During *produce* or *begin*, you might want to want to write to packet. Keep in mind that you have to write packet sections in sequence, +e.g. you can't write to ANSWER after writing AUTHORITY, it's like stages where you can't go back. + +.. code-block:: lua + + pkt:rcode(kres.rcode.NXDOMAIN) + -- Clear answer and write QUESTION + pkt:recycle() + pkt:question('\7blocked', kres.class.IN, kres.type.SOA) + -- Start writing data + pkt:begin(kres.section.ANSWER) + -- Nothing in answer + pkt:begin(kres.section.AUTHORITY) + local soa = { owner = '\7blocked', ttl = 900, class = kres.class.IN, type = kres.type.SOA, rdata = '...' } + pkt:put(soa.owner, soa.ttl, soa.class, soa.type, soa.rdata) + +Working with requests +--------------------- + +The request holds information about currently processed query, enabled options, cache, and other extra data. +You primarily need to retrieve currently processed query. + +.. code-block:: lua + + consume = function (state, req, pkt) + print(req.options) + print(req.state) + + -- Print information about current query + local current = req:current() + print(kres.dname2str(current.owner)) + print(current.stype, current.sclass, current.id, current.flags) + end + +In layers that either begin or finalize, you can walk the list of resolved queries. + +.. code-block:: lua + + local last = req:resolved() + print(last.stype) + +As described in the layers, you can not only retrieve information about current query, but also push new ones or pop old ones. + +.. code-block:: lua + + -- Push new query + local qry = req:push(pkt:qname(), kres.type.SOA, kres.class.IN) + qry.flags.AWAIT_CUT = true + + -- Pop the query, this will erase it from resolution plan + req:pop(qry) + + +.. _libknot: https://gitlab.nic.cz/knot/knot-dns/tree/master/src/libknot +.. _bindings: https://gitlab.nic.cz/knot/knot-resolver/blob/master/daemon/lua/kres.lua + + +.. _significant-lua-changes: + +Significant Lua API changes +--------------------------- + +Incompatible changes since 3.0.0 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the main ``kres.*`` lua binding, there was only change in struct knot_rrset_t: + +- constructor now accepts TTL as additional parameter (defaulting to zero) +- add_rdata() doesn't accept TTL anymore (and will throw an error if passed) + +In case you used knot_* functions and structures bound to lua: + +- knot_dname_is_sub(a, b): knot_dname_in_bailiwick(a, b) > 0 +- knot_rdata_rdlen(): knot_rdataset_at().len +- knot_rdata_data(): knot_rdataset_at().data +- knot_rdata_array_size(): offsetof(struct knot_data_t, data) + knot_rdataset_at().len +- struct knot_rdataset: field names were renamed to .count and .rdata +- some functions got inlined from headers, but you can use their kr_* clones: + kr_rrsig_sig_inception(), kr_rrsig_sig_expiration(), kr_rrsig_type_covered(). + Note that these functions now accept knot_rdata_t* instead of a pair + knot_rdataset_t* and size_t - you can use knot_rdataset_at() for that. + +- knot_rrset_add_rdata() doesn't take TTL parameter anymore +- knot_rrset_init_empty() was inlined, but in lua you can use the constructor +- knot_rrset_ttl() was inlined, but in lua you can use :ttl() method instead + +- knot_pkt_qname(), _qtype(), _qclass(), _rr(), _section() were inlined, + but in lua you can use methods instead, e.g. myPacket:qname() +- knot_pkt_free() takes knot_pkt_t* instead of knot_pkt_t**, but from lua + you probably didn't want to use that; constructor ensures garbage collection. + + +.. |---| unicode:: U+02014 .. em dash |