1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 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
|