diff options
Diffstat (limited to 'modules/priming')
-rw-r--r-- | modules/priming/.packaging/test.config | 4 | ||||
-rw-r--r-- | modules/priming/README.rst | 18 | ||||
-rw-r--r-- | modules/priming/priming.lua | 130 |
3 files changed, 152 insertions, 0 deletions
diff --git a/modules/priming/.packaging/test.config b/modules/priming/.packaging/test.config new file mode 100644 index 0000000..63239f0 --- /dev/null +++ b/modules/priming/.packaging/test.config @@ -0,0 +1,4 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('priming') +assert(priming) +quit() diff --git a/modules/priming/README.rst b/modules/priming/README.rst new file mode 100644 index 0000000..b74bad4 --- /dev/null +++ b/modules/priming/README.rst @@ -0,0 +1,18 @@ +.. SPDX-License-Identifier: GPL-3.0-or-later + +.. _mod-priming: + +Priming module +============== + +The module for Initializing a DNS Resolver with Priming Queries implemented +according to :rfc:`8109`. Purpose of the module is to keep up-to-date list of +root DNS servers and associated IP addresses. + +Result of successful priming query replaces root hints distributed with +the resolver software. Unlike other DNS resolvers, Knot Resolver caches +result of priming query on disk and keeps the data between restarts until +TTL expires. + +This module is enabled by default; you may disable it by adding +``modules.unload('priming')`` to your configuration. diff --git a/modules/priming/priming.lua b/modules/priming/priming.lua new file mode 100644 index 0000000..624a9df --- /dev/null +++ b/modules/priming/priming.lua @@ -0,0 +1,130 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +-- Module interface +local ffi = require('ffi') + +local priming = {} +priming.retry_time = 10 * sec -- retry time when priming fail + +-- internal state variables and functions +local internal = {} +internal.nsset = {} -- set of resolved nameservers +internal.min_ttl = 0 -- minimal TTL of NS records +internal.to_resolve = 0 -- number of pending queries to A or AAAA +internal.prime = {} -- function triggering priming query +internal.event = nil -- stores event id + +-- Copy hints from nsset table to resolver engine +-- These addresses replace root hints loaded by default from file. +-- They are stored outside cache and cache flush will not affect them. +local function publish_hints(nsset) + local roothints = kres.context().root_hints + -- reset zone cut and clear address list + ffi.C.kr_zonecut_set(roothints, kres.str2dname(".")) + for dname, addrsets in pairs(nsset) do + for i = 0, addrsets:rdcount() - 1 do + local rdpt = addrsets:rdata_pt(i) + ffi.C.kr_zonecut_add(roothints, dname, rdpt.data, rdpt.len) + end + end +end + +-- Count A and AAAA addresses in nsset +local function count_addresses(nsset) + local count = 0 + for _, addrset in pairs(nsset) do + count = count + addrset:rdcount() + end + return count +end + +-- Callback for response from A or AAAA query for root nameservers +-- address is added to table internal.nsset. +-- When all response is processed internal.nsset is published in resolver engine +-- luacheck: no unused args +local function address_callback(pkt, req) + if pkt == nil or pkt:rcode() ~= kres.rcode.NOERROR then + pkt = req.qsource.packet + log_info(ffi.C.LOG_GRP_PRIMING, "cannot resolve address '%s', type: %d", kres.dname2str(pkt:qname()), pkt:qtype()) + else + local section = pkt:rrsets(kres.section.ANSWER) + for i = 1, #section do + local rrset_new = section[i] + if rrset_new.type == kres.type.A or rrset_new.type == kres.type.AAAA then + local owner = rrset_new:owner() + local rrset_comb = internal.nsset[owner] + if rrset_comb == nil then + rrset_comb = kres.rrset(nil, rrset_new.type) + internal.nsset[owner] = rrset_comb + end + assert(ffi.istype(kres.rrset, rrset_new)) + rrset_comb:merge_rdata(rrset_new) + end + end + end + internal.to_resolve = internal.to_resolve - 1 + if internal.to_resolve == 0 then + if count_addresses(internal.nsset) == 0 then + log_info(ffi.C.LOG_GRP_PRIMING, "cannot resolve any root server address, \ + next priming query in %d seconds", priming.retry_time / sec) + internal.event = event.after(priming.retry_time, internal.prime) + else + publish_hints(internal.nsset) + log_info(ffi.C.LOG_GRP_PRIMING, "triggered priming query, next in %d seconds", internal.min_ttl) + internal.event = event.after(internal.min_ttl * sec, internal.prime) + end + end +end + +-- Callback for priming query ('.' NS) +-- For every NS record creates two separate queries for A and AAAA. +-- These new queries should be resolved from cache. +-- luacheck: no unused args +local function priming_callback(pkt, req) + if pkt == nil or pkt:rcode() ~= kres.rcode.NOERROR then + log_info(ffi.C.LOG_GRP_PRIMING, "cannot resolve '.' NS, next priming query in %d seconds", priming.retry_time / sec) + internal.event = event.after(priming.retry_time, internal.prime) + return nil + end + local section = pkt:rrsets(kres.section.ANSWER) + for i = 1, #section do + local rr = section[i] + if rr.type == kres.type.NS then + internal.min_ttl = math.min(internal.min_ttl, rr:ttl()) + internal.to_resolve = internal.to_resolve + 2 * rr.rrs.count + for k = 0, rr.rrs.count-1 do + local nsname_text = rr:tostring(k) + if net.ipv4 then + resolve(nsname_text, kres.type.A, kres.class.IN, 0, address_callback) + end + if net.ipv6 then + resolve(nsname_text, kres.type.AAAA, kres.class.IN, 0, address_callback) + end + end + end + end +end + +-- trigger priming query +function internal.prime() + internal.min_ttl = math.max(1, cache.max_ttl()) -- sanity check for disabled cache + internal.nsset = {} + internal.to_resolve = 0 + resolve(".", kres.type.NS, kres.class.IN, 0, priming_callback) +end + +function priming.init() + if internal.event then + error("Priming module is already loaded.") + else + internal.event = event.after(0 , internal.prime) + end +end + +function priming.deinit() + if internal.event then + event.cancel(internal.event) + internal.event = nil + end +end + +return priming |