README.rst 11.7 KB
Newer Older
1 2 3
*********************
Knot Resolver library
*********************
4 5

Requirements
6
============
7 8 9

* libknot_ 2.0 (Knot DNS high-performance DNS library.)

10 11
For users
=========
12

Marek Vavruša's avatar
Marek Vavruša committed
13 14
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.
15

Marek Vavruša's avatar
Marek Vavruša committed
16
.. 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.
17

18
.. _lib-layers:
19

20 21
For developers
==============
22

23 24 25
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)
26 27
* interacting with the library user (i.e. asking caller for I/O, accepting queries)
* fetching assets needed by layers (i.e. zone cut)
28

29
This is the *driver*. The driver is not meant to know *"how"* the query resolves, but rather *"when"* to execute *"what"*.
30 31 32 33

.. image:: ../doc/resolution.png
   :align: center

34
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.
35

36
.. 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.
37 38 39 40 41 42 43 44 45 46

Layers can also change course of resolution, for example by appending additional queries.

.. code-block:: lua

	consume = function (state, req, answer)
		answer = kres.pkt_t(answer)
		if answer:qtype() == kres.type.NS then
			req = kres.request_t(req)
			local qry = req:push(answer:qname(), kres.type.SOA, kres.class.IN)
47
			qry.flags.AWAIT_CUT = true
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
		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)
		answer = kres.pkt_t(answer)
		if state == kres.YIELD then
			print('continuing yielded layer')
			return kres.DONE
		else
			if answer:qtype() == kres.type.NS then
				req = kres.request_t(req)
				local qry = req:push(answer:qname(), kres.type.SOA, kres.class.IN)
65
				qry.flags.AWAIT_CUT = true
66 67 68 69 70 71 72 73 74
				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.
75 76 77 78

Writing layers
==============

79 80 81
.. 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.
82

Marek Vavruša's avatar
Marek Vavruša committed
83
.. 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.
84 85 86

The library offers following services:

87 88 89
- :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.
90 91

A processing layer is going to be called by the query resolution driver for each query,
Marek Vavruša's avatar
Marek Vavruša committed
92 93
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.
94 95 96

.. code-block:: c

97
	int consume(kr_layer_t *ctx, knot_pkt_t *pkt)
98
	{
99 100
		struct kr_request *req = ctx->req;
		struct kr_query *qry = req->current_query;
101 102 103 104 105 106
	}

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

107
	int produce(kr_layer_t *ctx, knot_pkt_t *pkt)
108
	{
109 110
		struct kr_request *req = ctx->req;
		struct kr_query *qry = req->current_query;
111

112
		/* Query can be satisfied locally. */
113
		if (can_satisfy(qry)) {
114 115
			/* This flag makes the resolver move the query
			 * to the "resolved" list. */
116
			qry->flags.RESOLVED = true;
117
			return KR_STATE_DONE;
118 119 120 121 122 123
		}

		/* Pass-through. */
		return ctx->state;
	}

124
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.
125 126 127

.. code-block:: c

128
	int finish(kr_layer_t *ctx)
129
	{
130 131
		struct kr_request *req = ctx->req;
		struct kr_rplan *rplan = req->rplan;
132 133 134 135 136 137 138 139 140 141 142

		/* 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;
	}
143

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
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``.
* 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)
		pkt = kres.pkt_t(answer)
		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)
229
		-- Clear answer and write QUESTION
230
		pkt:recycle()
231 232
		pkt:question('\7blocked', kres.class.IN, kres.type.SOA)
		-- Start writing data
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
		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)
		req = kres.request_t(req)
		print(req.options)
		print(req.state)

		-- Print information about current query
		local current = req:current()
		print(kres.dname2str(current.owner))
255
		print(current.stype, current.sclass, current.id, current.flags)
256 257
	end

258
In layers that either begin or finalize, you can walk the list of resolved queries.
259 260 261

.. code-block:: lua

262
	local last = req:resolved()
263
	print(last.stype)
264 265 266 267 268 269

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
270
		local qry = req:push(pkt:qname(), kres.type.SOA, kres.class.IN)
271
		qry.flags.AWAIT_CUT = true
272

273 274 275 276
		-- Pop the query, this will erase it from resolution plan
		req:pop(qry)


277
.. _libknot:  https://gitlab.labs.nic.cz/knot/knot-dns/tree/master/src/libknot
Vladimír Čunát's avatar
Vladimír Čunát committed
278
.. _bindings: https://gitlab.labs.nic.cz/knot/knot-resolver/blob/master/daemon/lua/kres.lua
279

280

281 282
.. _significant-lua-changes:

283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
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.


316
.. |---| unicode:: U+02014 .. em dash