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
|