130 lines
4.3 KiB
Lua
130 lines
4.3 KiB
Lua
-- 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
|