From: Vladimír Čunát Date: Tue, 10 Aug 2021 17:42:28 +0000 (+0200) Subject: modules/dns64: implement "exclusion prefixes" X-Git-Tag: v5.4.2~8^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=df71f15aeed95c0fa63578ecc55ba401469ff821;p=thirdparty%2Fknot-resolver.git modules/dns64: implement "exclusion prefixes" The RFC says we MUST do it, though this implementation is lazy and avoids a SHOULD in the RFC. --- diff --git a/NEWS b/NEWS index c64950e01..761d46a8f 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ Improvements ------------ - dns64 module: also map the reverse (PTR) subtree (#478, !1201) - dns64 module: allow disabling based on client address (#368, !1201) +- dns64 module: allow configuring AAAA subnets not allowed in answer (!1201) Knot Resolver 5.4.1 (2021-08-19) diff --git a/modules/dns64/dns64.lua b/modules/dns64/dns64.lua index af8926f8f..4b3403354 100644 --- a/modules/dns64/dns64.lua +++ b/modules/dns64/dns64.lua @@ -1,7 +1,8 @@ -- SPDX-License-Identifier: GPL-3.0-or-later -- Module interface local ffi = require('ffi') -local M = {} +local C = ffi.C +local M = { layer = { } } local addr_buf = ffi.new('char[16]') --[[ @@ -10,8 +11,6 @@ Missing parts of the RFC: > ranges to separate IPv6 prefixes for AAAA record synthesis. This > allows handling of special use IPv4 addresses [RFC5735]. - Also the exclusion prefixes are not implemented, sec. 5.1.4 (MUST). - TODO: support different prefix lengths, defaulting to /96 if not specified https://tools.ietf.org/html/rfc6052#section-2.2 ]] @@ -35,10 +34,53 @@ function M.config(conf) :gsub('.', '%1.') .. 'ip6.arpa.' ) + + -- RFC 6147.5.1.4 + M.exclude_subnets = {} + if conf.exclude_subnets ~= nil and type(conf.exclude_subnets) ~= 'table' then + error('[dns64] .exclude_subnets is not a table') + end + for _, subnet_cfg in ipairs(conf.exclude_subnets or { '::ffff/96' }) do + local subnet = {} + subnet.prefix = ffi.new('char[16]') + subnet.bitlen = C.kr_straddr_subnet(subnet.prefix, tostring(subnet_cfg)) + if subnet.bitlen < 0 or not string.find(subnet_cfg, ':', 1, true) then + error(string.format('[dns64] failed to parse IPv6 subnet: %q', subnet_cfg)) + end + table.insert(M.exclude_subnets, subnet) + end +end + +-- Filter the AAAA records from the last ANSWER, return iff it's NODATA afterwards. +-- Currently the implementation is lazy and kills it all if any AAAA is excluded. +local function do_exclude_prefixes(qry) + local rrsel = qry.request.answ_selected + for i = 0, tonumber(rrsel.len) - 1 do + local rr_e = rrsel.at[i] -- struct ranked_rr_array_entry + if rr_e.qry_uid ~= qry.uid or rr_e.rr.type ~= kres.type.AAAA or not rr_e.to_wire + then goto next_rrset end + -- Found answer AAAA RRset + for _, subnet in ipairs(M.exclude_subnets) do + for j = 0, rr_e.rr:rdcount() - 1 do + local rd = rr_e.rr:rdata_pt(j) + if rd.len == 16 and C.kr_bitcmp(subnet.prefix, rd.data, subnet.bitlen) == 0 then + -- We can't use this RR. TODO: and we're lazy, + -- so we kill the whole RRset instead of filtering. + rr_e.to_wire = false + return true + end + end + end + -- We can use the answer -> return false + -- We use a nonsensical if to fool the parser; is return adjacent to a label forbidden? + if true then return false end + + ::next_rrset:: + end + -- No RRset found, it was probably NODATA. + return true end --- Layers -M.layer = { } function M.layer.consume(state, req, pkt) if state == kres.FAIL then return state end local qry = req:current() @@ -47,13 +89,11 @@ function M.layer.consume(state, req, pkt) or pkt:qclass() ~= kres.class.IN or req.qsource.packet:cd() then return state end - -- Synthetic AAAA from marked A responses - local answer = pkt:section(kres.section.ANSWER) -- Observe final AAAA NODATA responses to the current SNAME. - local is_nodata = pkt:rcode() == kres.rcode.NOERROR and #answer == 0 - if pkt:qtype() == kres.type.AAAA and is_nodata and pkt:qname() == qry:name() - and qry.flags.RESOLVED and not qry.flags.CNAME and qry.parent == nil then + if pkt:qtype() == kres.type.AAAA and pkt:qname() == qry:name() + and qry.flags.RESOLVED and not qry.flags.CNAME and qry.parent == nil + and pkt:rcode() == kres.rcode.NOERROR and do_exclude_prefixes(qry) then -- Start a *marked* corresponding A sub-query. local extraFlags = kres.mk_qflags({}) extraFlags.DNSSEC_WANT = qry.flags.DNSSEC_WANT