]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
modules/dns64: implement more properties from RFC
authorVladimír Čunát <vladimir.cunat@nic.cz>
Thu, 28 Jun 2018 10:55:27 +0000 (12:55 +0200)
committerPetr Špaček <petr.spacek@nic.cz>
Mon, 2 Jul 2018 13:28:40 +0000 (15:28 +0200)
- 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

NEWS
lib/rplan.h
modules/dns64/README.rst
modules/dns64/dns64.lua
tests/deckard

diff --git a/NEWS b/NEWS
index fbbabeff81343602e9d8c07431e1a9c1e6dec8b1..f3417dea0b038b7efae06b8c9cb0336553876287 100644 (file)
--- 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
 ------------
index ad452303e65886fd016f1ea18033a213a673e9f6..21b0b0ba80ba191d7d87b59bcd2f42ba8598ea29 100644 (file)
@@ -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 */
index 6ee33d8167e9dc82a7387040841b2ced6daf4e1f..047443fdfa6abd150c440799b1e6e8db59f2647b 100644 (file)
@@ -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 <mod-policy>`.
+   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
index 1c65aff049344e8afcb9b4f11322f9e6920ef371..fdc1a6a862ffac75ae3f9999a45ae82dc0ccc2a2 100644 (file)
@@ -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
index 314baab234262eb61a3e6cfd03941be5f3c94c9f..41318bc5feb3b62db0305e9124175d0f0a6b8fb2 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 314baab234262eb61a3e6cfd03941be5f3c94c9f
+Subproject commit 41318bc5feb3b62db0305e9124175d0f0a6b8fb2