]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
priming: implement priming queries as module
authorVítězslav Kříž <vitezslav.kriz@nic.cz>
Wed, 1 Nov 2017 17:26:54 +0000 (18:26 +0100)
committerPetr Špaček <petr.spacek@nic.cz>
Tue, 28 Nov 2017 14:22:07 +0000 (15:22 +0100)
daemon/lua/kres-gen.lua
daemon/lua/kres-gen.sh
doc/modules.rst
modules/modules.mk
modules/priming/README.rst [new file with mode: 0644]
modules/priming/priming.lua [new file with mode: 0644]
modules/priming/priming.mk [new file with mode: 0644]

index 7d41beb38f1855ead93bf9a97b4b2d325d1dd8a3..8410f21bff306f12efd6133f4a08fff7413621a4 100644 (file)
@@ -210,6 +210,7 @@ int knot_dname_size(const knot_dname_t *);
 char *knot_dname_to_str(char *, const knot_dname_t *, size_t);
 uint16_t knot_rdata_rdlen(const knot_rdata_t *);
 uint8_t *knot_rdata_data(const knot_rdata_t *);
+size_t knot_rdata_array_size(uint16_t);
 knot_rdata_t *knot_rdataset_at(const knot_rdataset_t *, size_t);
 int knot_rrset_add_rdata(knot_rrset_t *, const uint8_t *, const uint16_t, const uint32_t, knot_mm_t *);
 void knot_rrset_init_empty(knot_rrset_t *);
@@ -244,6 +245,8 @@ struct sockaddr *kr_straddr_socket(const char *, int);
 int kr_ranked_rrarray_add(ranked_rr_array_t *, const knot_rrset_t *, uint8_t, _Bool, uint32_t, knot_mm_t *);
 void kr_qflags_set(struct kr_qflags *, struct kr_qflags);
 void kr_qflags_clear(struct kr_qflags *, struct kr_qflags);
+int kr_zonecut_add(struct kr_zonecut *, const knot_dname_t *, const knot_rdata_t *);
+void kr_zonecut_set(struct kr_zonecut *, const knot_dname_t *);
 knot_rrset_t *kr_ta_get(map_t *, const knot_dname_t *);
 int kr_ta_add(map_t *, const knot_dname_t *, uint16_t, uint32_t, const uint8_t *, uint16_t);
 int kr_ta_del(map_t *, const knot_dname_t *);
index 43ca87f5e0b50d57d8976e4d78f1d69ad0ded872..865cd1ce6c706c6804db2b9e570659387e07f2d9 100755 (executable)
@@ -89,6 +89,7 @@ printf "\tchar _stub[];\n};\n"
 # Resource records
        knot_rdata_rdlen
        knot_rdata_data
+       knot_rdata_array_size
        knot_rdataset_at
        knot_rrset_add_rdata
        knot_rrset_init_empty
@@ -132,6 +133,8 @@ EOF
        kr_ranked_rrarray_add
        kr_qflags_set
        kr_qflags_clear
+       kr_zonecut_add
+       kr_zonecut_set
 # Trust anchors
        kr_ta_get
        kr_ta_add
index 4b463fff9f70ea23f04343abacade54aaabf947f..d3c5eec1c63d727b788b9fe111a3e89af106fac5 100644 (file)
@@ -26,3 +26,4 @@ Knot DNS Resolver modules
 .. include:: ../modules/workarounds/README.rst
 .. include:: ../modules/dnstap/README.rst
 .. include:: ../modules/ta_signal_query/README.rst
+.. include:: ../modules/priming/README.rst
index 157648df669ad75ad5fe7fad4aff5a92de6b2c33..6734b6c3fd0e7184628f2796d5054e13775da37e 100644 (file)
@@ -33,7 +33,8 @@ modules_TARGETS += ketcd \
                    daf \
                    workarounds \
                    version \
-                   ta_signal_query
+                   ta_signal_query \
+                   priming
 endif
 
 # Make C module
diff --git a/modules/priming/README.rst b/modules/priming/README.rst
new file mode 100644 (file)
index 0000000..3a1d3e7
--- /dev/null
@@ -0,0 +1,17 @@
+.. _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 and it is not recommended to disable it.
+For debugging purposes you may disable the module by appending
+`modules.unload('priming')` to your configuration.
diff --git a/modules/priming/priming.lua b/modules/priming/priming.lua
new file mode 100644 (file)
index 0000000..6f05d7f
--- /dev/null
@@ -0,0 +1,128 @@
+-- Module interface
+local ffi = require('ffi')
+local knot = ffi.load(libknot_SONAME)
+
+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, addresses in pairs(nsset) do
+               for _, rdata_addr in pairs(addresses) do
+                       ffi.C.kr_zonecut_add(roothints, dname, rdata_addr)
+               end
+       end
+end
+
+-- Count A and AAAA addresses in nsset
+local function count_addresses(nsset)
+       local count = 0
+       for _, addresses in pairs(nsset) do
+               count = count + #addresses
+       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)
+       pkt = kres.pkt_t(pkt)
+       -- req = kres.request_t(req)
+       if pkt:rcode() ~= kres.rcode.NOERROR then
+               warn("[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 rr = section[i]
+                       if rr.type == kres.type.A or rr.type == kres.type.AAAA then
+                               for k = 0, rr.rrs.rr_count-1 do
+                                       local rdata = knot.knot_rdataset_at(rr.rrs, k)
+                                       rdata = ffi.string(rdata, knot.knot_rdata_array_size(knot.knot_rdata_rdlen(rdata)))
+                                       table.insert(internal.nsset[rr:owner()], rdata)
+                               end
+                       end
+               end
+       end
+       internal.to_resolve = internal.to_resolve - 1
+       if internal.to_resolve == 0 then
+               if count_addresses(internal.nsset) == 0 then
+                       warn("[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)
+                       if verbose() then
+                               log("[priming] triggered priming query, next in %d seconds", internal.min_ttl)
+                       end
+                       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)
+       pkt = kres.pkt_t(pkt)
+       -- req = kres.request_t(req)
+       if pkt:rcode() ~= kres.rcode.NOERROR then
+               warn("[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.rr_count
+                       for k = 0, rr.rrs.rr_count-1 do
+                               local nsname_text = rr:tostring(k)
+                               local nsname_wire = rr:rdata(k)
+                               internal.nsset[nsname_wire] = {}
+                               resolve(nsname_text, kres.type.A, kres.class.IN, 0, address_callback)
+                               resolve(nsname_text, kres.type.AAAA, kres.class.IN, 0, address_callback)
+                       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
diff --git a/modules/priming/priming.mk b/modules/priming/priming.mk
new file mode 100644 (file)
index 0000000..b5043b1
--- /dev/null
@@ -0,0 +1,2 @@
+priming_SOURCES := priming.lua
+$(call make_lua_module,priming)