From: Vladimír Čunát Date: Thu, 28 Jun 2018 10:55:27 +0000 (+0200) Subject: modules/dns64: implement more properties from RFC X-Git-Tag: v2.4.0~5^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=eb937f408f73f6fb969ebd24d516db21f14a77a6;p=thirdparty%2Fknot-resolver.git modules/dns64: implement more properties from RFC - don't synthesize if +CD - bound synthesized TTL by SOA's TTL - set AD flag if synthesizing from secure NODATA and A. - review the RFC for properties that the module is missing --- diff --git a/NEWS b/NEWS index fbbabeff8..f3417dea0 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,7 @@ Bugfixes -------- - avoid turning off qname minimization in some cases, e.g. co.uk. (#339) - fix validation of explicit wildcard queries (#274) +- dns64 module: more properties from the RFC implemented (incl. bug #375) Improvements ------------ diff --git a/lib/rplan.h b/lib/rplan.h index ad452303e..21b0b0ba8 100644 --- a/lib/rplan.h +++ b/lib/rplan.h @@ -31,7 +31,8 @@ struct kr_qflags { bool NO_IPV6 : 1; /**< Disable IPv6 */ bool NO_IPV4 : 1; /**< Disable IPv4 */ bool TCP : 1; /**< Use TCP for this query. */ - bool RESOLVED : 1; /**< Query is resolved. */ + bool RESOLVED : 1; /**< Query is resolved. Note that kr_query gets + * RESOLVED before following a CNAME chain; see .CNAME. */ bool AWAIT_IPV4 : 1; /**< Query is waiting for A address. */ bool AWAIT_IPV6 : 1; /**< Query is waiting for AAAA address. */ bool AWAIT_CUT : 1; /**< Query is waiting for zone cut lookup */ diff --git a/modules/dns64/README.rst b/modules/dns64/README.rst index 6ee33d816..047443fdf 100644 --- a/modules/dns64/README.rst +++ b/modules/dns64/README.rst @@ -4,8 +4,11 @@ DNS64 ----- The module for :rfc:`6147` DNS64 AAAA-from-A record synthesis, it is used to enable client-server communication between an IPv6-only client and an IPv4-only server. See the well written `introduction`_ in the PowerDNS documentation. +If no address is passed (i.e. ``nil``), the well-known prefix ``64:ff9b::`` is used. .. warning:: The module currently won't work well with :ref:`policy.STUB `. + Also, the IPv6 passed in configuration is assumed to be ``/96``, and + PTR synthesis and "exclusion prefixes" aren't implemented. .. tip:: The A record sub-requests will be DNSSEC secured, but the synthetic AAAA records can't be. Make sure the last mile between stub and resolver is secure to avoid spoofing. @@ -20,6 +23,4 @@ Example configuration dns64.config('fe80::21b:aabb:0:0') - -.. _RPZ: https://dnsrpz.info/ .. _introduction: https://doc.powerdns.com/md/recursor/dns64 diff --git a/modules/dns64/dns64.lua b/modules/dns64/dns64.lua index 1c65aff04..fdc1a6a86 100644 --- a/modules/dns64/dns64.lua +++ b/modules/dns64/dns64.lua @@ -3,64 +3,101 @@ local ffi = require('ffi') local M = {} local addr_buf = ffi.new('char[16]') +--[[ +Missing parts of the RFC: + > The implementation SHOULD support mapping of separate IPv4 address + > 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 + + PTR queries aren't supported (MUST), sec. 5.3.1.2 +]] + -- Config function M.config (confstr) - if confstr == nil then return end - M.proxy = kres.str2ip(confstr) + M.proxy = kres.str2ip(confstr or '64:ff9b::') if M.proxy == nil then error('[dns64] "'..confstr..'" is not a valid address') end end -- Layers -M.layer = { - consume = function (state, req, pkt) - if state == kres.FAIL then return state end - pkt = kres.pkt_t(pkt) - req = kres.request_t(req) - local qry = req:current() - -- Observe only authoritative answers - if M.proxy == nil or not qry.flags.RESOLVED then - return state +M.layer = { } +function M.layer.consume(state, req, pkt) + if state == kres.FAIL then return state end + pkt = kres.pkt_t(pkt) + req = kres.request_t(req) + local qry = req:current() + -- Observe only final answers in IN class where request has no CD flag. + if M.proxy == nil or not qry.flags.RESOLVED + or pkt:qclass() ~= kres.class.IN or req.answer: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 + -- Start a *marked* corresponding A sub-query. + local extraFlags = kres.mk_qflags({}) + extraFlags.DNSSEC_WANT = qry.flags.DNSSEC_WANT + extraFlags.AWAIT_CUT = true + extraFlags.DNS64_MARK = true + req:push(pkt:qname(), kres.type.A, kres.class.IN, extraFlags, qry) + return state + end + + + -- Observe answer to the marked sub-query, and convert all A records in ANSWER + -- to corresponding AAAA records to be put into the request's answer. + if not qry.flags.DNS64_MARK then return state end + -- Find rank for the NODATA answer. + -- That will result into corresponding AD flag. See RFC 6147 5.5.2. + local neg_rank + if qry.parent.flags.DNSSEC_WANT and not qry.parent.flags.DNSSEC_INSECURE + then neg_rank = ffi.C.KR_RANK_SECURE + else neg_rank = ffi.C.KR_RANK_INSECURE + end + -- Find TTL bound from SOA, according to RFC 6147 5.1.7.4. + local max_ttl = 600 + for i = 1, tonumber(req.auth_selected.len) do + local entry = req.auth_selected.at[i - 1] + if entry.qry_uid == qry.parent.uid and entry.rr + and entry.rr.type == kres.type.SOA + and entry.rr.rclass == kres.class.IN then + max_ttl = entry.rr:ttl() end - -- Synthetic AAAA from marked A responses - local answer = pkt:section(kres.section.ANSWER) - if qry.flags.DNS64_MARK then -- Marked request - local section = ffi.C.knot_pkt_section(pkt, kres.section.ANSWER) - for i = 1, section.count do - local orig = ffi.C.knot_pkt_rr(section, i - 1) - if orig.type == kres.type.A then - -- Disable GC, as this object doesn't own either owner or RDATA, it's just a reference - local rrs = ffi.gc(kres.rrset(nil, kres.type.AAAA, orig.rclass), nil) - rrs._owner = ffi.cast('knot_dname_t *', orig:owner()) -- explicit cast needed here - for k = 1, orig.rrs.rr_count do - local rdata = orig:rdata( k - 1 ) - ffi.copy(addr_buf, M.proxy, 16) - ffi.copy(addr_buf + 12, rdata, 4) - ffi.C.knot_rrset_add_rdata(rrs, ffi.string(addr_buf, 16), 16, orig:ttl(), req.pool) - end - -- All referred memory is copied within the function, - -- so it doesn't matter that lua GCs our variables. - ffi.C.kr_ranked_rrarray_add( - req.answ_selected, - rrs, - ffi.C.KR_RANK_OMIT, - true, - qry.uid, - req.pool) - end - end - else -- Observe AAAA NODATA responses - 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 qry.parent == nil) then - local extraFlags = kres.mk_qflags({}) - extraFlags.DNSSEC_WANT = qry.flags.DNSSEC_WANT - extraFlags.AWAIT_CUT = true - extraFlags.DNS64_MARK = true - req:push(pkt:qname(), kres.type.A, kres.class.IN, extraFlags, qry) + end + -- Find the As and do the conversion itself. + for i = 1, tonumber(req.answ_selected.len) do + local orig = req.answ_selected.at[i - 1] + if orig.qry_uid == qry.uid and orig.rr.type == kres.type.A then + local rank = neg_rank + if orig.rank < rank then rank = orig.rank end + -- Disable GC, as this object doesn't own owner or RDATA, it's just a reference + local rrs = ffi.gc(kres.rrset(nil, kres.type.AAAA, orig.rr.rclass), nil) + rrs._owner = ffi.cast('knot_dname_t *', orig.rr:owner()) -- explicit cast needed here + for k = 1, orig.rr.rrs.rr_count do + local rdata = orig.rr:rdata( k - 1 ) + ffi.copy(addr_buf, M.proxy, 12) + ffi.copy(addr_buf + 12, rdata, 4) + local ttl = orig.rr:ttl() + if ttl > max_ttl then ttl = max_ttl end + ffi.C.knot_rrset_add_rdata(rrs, ffi.string(addr_buf, 16), 16, ttl, req.pool) end + ffi.C.kr_ranked_rrarray_add( + req.answ_selected, + rrs, + rank, + true, + qry.uid, + req.pool) end - return state end -} +end return M diff --git a/tests/deckard b/tests/deckard index 314baab23..41318bc5f 160000 --- a/tests/deckard +++ b/tests/deckard @@ -1 +1 @@ -Subproject commit 314baab234262eb61a3e6cfd03941be5f3c94c9f +Subproject commit 41318bc5feb3b62db0305e9124175d0f0a6b8fb2