This week I was approached by a man dressed in platypus pyjamas, he asked me: “These layers and modules you talk about, they’re cool. But can it be even better?”. After the initial costume distraction wore off, I pondered a bit and said: “Sure, let me just grab a cup of coffee”. The real story is that the layers are now much more interactive, and the documentation is improved.
What are layers
Callbacks you can execute on each new/completed query, and resolver sub-requests. For example – to log all AAAA answers from *.kiwi, or refuse all queries matching a blocklist. Here’s an example similar to the documentation.
local mod = {} mod.count = 0 mod.layer = { consume = function (state, req, pkt) pkt = kres.pkt_t(pkt) if pkt:qtype() == kres.type.AAAA and pkt:qname():find('\4kiwi') then mod.count = mod.count + 1 end return state end } return mod
Save it as example.lua and start kresd.
$ kresd -v > modules = { 'example' } > resolve('abcd.kiwi', kres.type.AAAA) ... > example.count 1
This is only passive observation of the resolver I/O. You can also inspect state of the queries. For example to answer question “which queries in this zone are answered from cache?” We can write something along the lines of:
req = kres.request_t(req) local query = req:current() if query.flags and kres.query.CACHED then mod.count = mod.count + 1 end
At this point, you should skim through the Lua API reference. The examples often reference to constants like kres.type.AAAA or accessors req:current(), the reference explains where to find what.
Chaining queries
The previous example has shown how to observe the resolution chain. Let’s see how we can change it. For example – for every NS record, I want its SOA as well. One way how to do that, is to simply push another query. Here’s a documentation reference.
Short version is that there is a driver behind resolution which does I/O, figures out when to ask what, sets correct flags, and so on. The added value is that layers don’t have to know about DNSSEC, caching, correct ordering etc.
if pkt:qtype() == kres.type.SOA then local next = req:push(pkt:qname(), kres.type.NS, kres.class.IN) next.flags = kres.query.AWAIT_CUT + kres.query.DNSSEC_WANT return kres.DONE else return state end
This piece of code fetches NS for each SOA query, after the query completes. You can check that the record was fetched in the finish layer afterwards. This is convenient if you want to simply chain queries.
Reordering queries
States signalize the outcome of layer pass-through, there are 3 interesting ones: DONE, FAIL and YIELD. A common idiom is to copy input state if you’re not interested in this packet.
consume = function (state, req, pkt) if state == kres.FAIL then return state end return kres.DONE end
The YIELD pauses current layer, starts solving whatever queries you pushed, and then resumes the paused layer. One example is DNSSEC, where you need a DNSKEY to verify signatures. Or to answer only NS records, for which a SOA exists.
if state == kres.YIELD then local last = req:resolved() if bit.band(last.flags, kres.query.RESOLVED) then return kres.DONE -- Fetched NS else return kres.FAIL -- Failed to fetch NS end else if pkt:qtype() == kres.type.SOA then local next = req:push(pkt:qname(), kres.type.NS, kres.class.IN) next.flags = kres.query.AWAIT_CUT + kres.query.DNSSEC_WANT next.parent = qry return kres.YIELD -- Resolve NS and resume end return state end
The first branch is executed when the paused layer resumes, there we can check whether the NS query finished successfuly. The second branch pushes the NS query, and then pauses.
Rewriting queries
I’m a bit reluctant to write this, as there is not any good use case that comes to my mind. But let’s say you want to sinkhole several domain names, e.g. if the QNAME matches a pattern, we rewrite it to something else. This can be done before driver starts issuing queries, in the begin layer.
begin = function(state, req) req = kres.request_t(req) local query = req:current() if query:name():find('\4kiwi') then req:pop(query) -- Pop old query, and replace with new one req:push('\7blocked', query.type, query.class, query.flags, 0) end return state end
Wrap up
That’s it. Here we are with the examples for three different layer usage patterns in a few lines of Lua code. The advantage here is that you can drop custom modules in the configuration directory, no need to patch or recompile the software. If you don’t mess up the Lua code, it’s going to run close to C code speed. When in trouble achieving that, check the Embedding LuaJIT in 30 minutes I wrote before.
Dobrý den,
chtěl bych moc poprosit o radu. Používáme knot resolver v našem projektu a dopisujeme si vlastní věci v jazyku Lua. Čerpal jsem i z tohoto článku, ale jedna věc se mi nedaří a to rozběhnout Rewriting queries. Zkoušel jsem to nejdřív podle našich potřeb, ale nedařilo se, takže jsem zkusil uvedený příklad a končím na stejné chybě
“error: /usr/local/lib/kdns_modules/kres.lua:462: missing declaration for symbol ‘kr_rplan_pop'” a jsme v koncích 🙁
Díky
Vláďa Kučera
Dobrý den,
prosím udělejte issue na gitlab.labs.nic.cz/knot/resolver/.
Díky,
Ondřej Surý