]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
NAT64 support 722/head
authorDavid Lamparter <equinox@diac24.net>
Sat, 23 Jul 2022 18:26:20 +0000 (14:26 -0400)
committerDavid Lamparter <equinox@diac24.net>
Mon, 7 Nov 2022 11:37:50 +0000 (11:37 +0000)
This implements #721.  Includes documentation and some very basic tests.
Please refer to doc for further detail.

16 files changed:
daemon/cachedump.c
doc/Changelog
doc/README.DNS64
doc/example.conf.in
doc/unbound.conf.5.in
iterator/iter_utils.c
iterator/iter_utils.h
iterator/iterator.c
iterator/iterator.h
pythonmod/interface.i
testdata/iter_nat64.rpl [new file with mode: 0644]
testdata/iter_nat64_prefix.rpl [new file with mode: 0644]
testdata/iter_nat64_prefix48.rpl [new file with mode: 0644]
util/config_file.h
util/configlexer.lex
util/configparser.y

index baf8008ea80f47063533bcb7c06cf246569aec92..3c5f9767426003624bfc6e858154551cb69c01e7 100644 (file)
@@ -859,7 +859,8 @@ int print_deleg_lookup(RES* ssl, struct worker* worker, uint8_t* nm,
                /* go up? */
                if(iter_dp_is_useless(&qinfo, BIT_RD, dp,
                        (worker->env.cfg->do_ip4 && worker->back->num_ip4 != 0),
-                       (worker->env.cfg->do_ip6 && worker->back->num_ip6 != 0))) {
+                       (worker->env.cfg->do_ip6 && worker->back->num_ip6 != 0),
+                       worker->env.cfg->do_nat64)) {
                        print_dp_main(ssl, dp, msg);
                        print_dp_details(ssl, worker, dp);
                        if(!ssl_printf(ssl, "cache delegation was "
index 32e7ae8ae9f2f3a01c77df0df1cebb265a1e3b9d..2a8bf55305a89d27da974dda6c5f545b21561fad 100644 (file)
@@ -1,3 +1,6 @@
+5 November 2022: equinox (David Lamparter)
+       - Implemented NAT64 support
+
 21 October 2022: George
        - Merge #767 from jonathangray: consistently use IPv4/IPv6 in
          unbound.conf.5.
index 49446ac575d104fe8e73e46f669652696d674472..71e2310ed9aaf8f80371130f54eee42f95f101c5 100644 (file)
@@ -28,3 +28,23 @@ prefix. For example:
     ;; ANSWER SECTION:
     jazz-v4.viagenie.ca.        86400   IN      AAAA    64:ff9b::ce7b:1f02
 
+
+NAT64 support was added by David Lamparter in 2022; license(s) of the
+surrounding code apply.  Note that NAT64 is closely related but functionally
+orthogonal to DNS64;  it allows Unbound to send outgoing queries to IPv4-only
+servers over IPv6 through the configured NAT64 prefix.  This allows running
+an Unbound instance on an IPv6-only host without breaking every single domain
+that only has IPv4 servers.  Whether that Unbound instance also does DNS64 is
+an independent choice.
+
+To enable NAT64 in Unbound, add to unbound.conf's "server" section:
+
+    do-nat64: yes
+
+The NAT64 prefix defaults to the DNS64 prefix, which in turn defaults to the
+standard 64:FF9B::/96 prefix.  You can reconfigure it with:
+
+    nat64-prefix: 64:FF9B::/96
+
+To test NAT64 operation, pick a domain that only has IPv4 reachability for its
+nameservers and try resolving any names in that domain.
index c21246e4c37d4a1c16373eb57413960ca4bebabe..e45f497bb360d46e26453de1196785f7b4f71d85 100644 (file)
@@ -229,6 +229,16 @@ server:
        # Enable IPv6, "yes" or "no".
        # do-ip6: yes
 
+       # If running unbound on an IPv6-only host, domains that only have
+       # IPv4 servers would become unresolveable.  If NAT64 is available in
+       # the network, unbound can use NAT64 to reach these servers with
+       # the following option.  This is NOT needed for enabling DNS64 on a
+       # system that has IPv4 connectivity.
+       # do-nat64: no
+
+       # NAT64 prefix.  Defaults to using dns64-prefix value.
+       # nat64-prefix: 64:ff9b::0/96
+
        # Enable UDP, "yes" or "no".
        # do-udp: yes
 
index d829008a717b99ec1d8fc47ac13311b9c66c522f..2f02c2640e6b23fb5bddfca97aba61b7fdbf4a44 100644 (file)
@@ -2268,6 +2268,18 @@ List domain for which the AAAA records are ignored and the A record is
 used by dns64 processing instead.  Can be entered multiple times, list a
 new domain for which it applies, one per line.  Applies also to names
 underneath the name given.
+.SS "NAT64 Operation"
+.LP
+NAT64 operation allows using a NAT64 prefix for outbound requests to IPv4-only
+servers.  It is controlled by two options in the \fBserver:\fR section:
+.TP
+.B do\-nat64: \fI<yes or no>\fR
+Use NAT64 to reach IPv4-only servers.  Default no.
+.TP
+.B nat64\-prefix: \fI<IPv6 prefix>\fR
+Use a specific NAT64 prefix to reach IPv4-only servers.  Defaults to using
+the prefix configured in \fBdns64\-prefix\fR, which in turn defaults to
+64:ff9b::/96.  Must be /96 or shorter.
 .SS "DNSCrypt Options"
 .LP
 The
index 56b184a02fb893ec89536cf3a0ac809397782201..98fac8186a7e3f42d24cc7c4d230edcd7bbe1644 100644 (file)
 /** time when nameserver glue is said to be 'recent' */
 #define SUSPICION_RECENT_EXPIRY 86400
 
+/** if NAT64 is enabled and no NAT64 prefix is configured, first fall back to
+ * DNS64 prefix.  If that is not configured, fall back to this default value.
+ */
+static const char DEFAULT_NAT64_PREFIX[] = "64:ff9b::/96";
+
 /** fillup fetch policy array */
 static void
 fetch_fill(struct iter_env* ie, const char* str)
@@ -142,6 +147,7 @@ caps_white_apply_cfg(rbtree_type* ntree, struct config_file* cfg)
 int
 iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg)
 {
+       const char *nat64_prefix;
        int i;
        /* target fetch policy */
        if(!read_fetch_policy(iter_env, cfg->target_fetch_policy))
@@ -172,8 +178,34 @@ iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg)
                }
 
        }
+
+       nat64_prefix = cfg->nat64_prefix;
+       if (!nat64_prefix)
+               nat64_prefix = cfg->dns64_prefix;
+       if (!nat64_prefix)
+               nat64_prefix = DEFAULT_NAT64_PREFIX;
+       if (!netblockstrtoaddr(nat64_prefix, 0, &iter_env->nat64_prefix_addr,
+                              &iter_env->nat64_prefix_addrlen,
+                              &iter_env->nat64_prefix_net)) {
+               log_err("cannot parse nat64-prefix netblock: %s", nat64_prefix);
+               return 0;
+       }
+       if (!addr_is_ip6(&iter_env->nat64_prefix_addr,
+                        iter_env->nat64_prefix_addrlen)) {
+               log_err("nat64_prefix is not IPv6: %s", cfg->nat64_prefix);
+               return 0;
+       }
+       if (iter_env->nat64_prefix_net != 32 && iter_env->nat64_prefix_net != 40 &&
+           iter_env->nat64_prefix_net != 48 && iter_env->nat64_prefix_net != 56 &&
+           iter_env->nat64_prefix_net != 64 && iter_env->nat64_prefix_net != 96 ) {
+               log_err("dns64-prefix length it not 32, 40, 48, 56, 64 or 96: %s",
+                       nat64_prefix);
+               return 0;
+       }
+
        iter_env->supports_ipv6 = cfg->do_ip6;
        iter_env->supports_ipv4 = cfg->do_ip4;
+       iter_env->use_nat64 = cfg->do_nat64;
        iter_env->outbound_msg_retry = cfg->outbound_msg_retry;
        return 1;
 }
@@ -238,7 +270,8 @@ iter_filter_unsuitable(struct iter_env* iter_env, struct module_env* env,
        if(!iter_env->supports_ipv6 && addr_is_ip6(&a->addr, a->addrlen)) {
                return -1; /* there is no ip6 available */
        }
-       if(!iter_env->supports_ipv4 && !addr_is_ip6(&a->addr, a->addrlen)) {
+       if(!iter_env->supports_ipv4 && !iter_env->use_nat64 &&
+          !addr_is_ip6(&a->addr, a->addrlen)) {
                return -1; /* there is no ip4 available */
        }
        /* check lameness - need zone , class info */
@@ -745,10 +778,14 @@ iter_mark_pside_cycle_targets(struct module_qstate* qstate, struct delegpt* dp)
 
 int
 iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags,
-       struct delegpt* dp, int supports_ipv4, int supports_ipv6)
+       struct delegpt* dp, int supports_ipv4, int supports_ipv6, int use_nat64)
 {
        struct delegpt_ns* ns;
        struct delegpt_addr* a;
+
+       if (supports_ipv6 && use_nat64)
+               supports_ipv4 = 1;
+
        /* check:
         *      o RD qflag is on.
         *      o no addresses are provided.
index 850be96a6e1678e7ce46665b63fb7a8cc29b7beb..48ea8316be69fa66eabd4e1f81935bd556cc1bae 100644 (file)
@@ -192,7 +192,8 @@ void iter_mark_pside_cycle_targets(struct module_qstate* qstate,
  * @return true if dp is useless.
  */
 int iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags, 
-       struct delegpt* dp, int supports_ipv4, int supports_ipv6);
+       struct delegpt* dp, int supports_ipv4, int supports_ipv6,
+       int use_nat64);
 
 /**
  * See if qname has DNSSEC needs.  This is true if there is a trust anchor above
index 9c8d256d36132021088c7d7deac38058dd186ae3..8f4a8e04aec6d795c8d39960fe2859926be10ed8 100644 (file)
@@ -255,7 +255,7 @@ error_supers(struct module_qstate* qstate, int id, struct module_qstate* super)
                                log_err("out of memory adding missing");
                }
                delegpt_mark_neg(dpns, qstate->qinfo.qtype);
-               if((dpns->got4 == 2 || !ie->supports_ipv4) &&
+               if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->use_nat64)) &&
                        (dpns->got6 == 2 || !ie->supports_ipv6)) {
                        dpns->resolved = 1; /* mark as failed */
                        target_count_increase_nx(super_iq, 1);
@@ -1571,7 +1571,8 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
                 * same server reply) if useless-checked.
                 */
                if(iter_dp_is_useless(&qstate->qinfo, qstate->query_flags, 
-                       iq->dp, ie->supports_ipv4, ie->supports_ipv6)) {
+                       iq->dp, ie->supports_ipv4, ie->supports_ipv6,
+                       ie->use_nat64)) {
                        struct delegpt* retdp = NULL;
                        if(!can_have_last_resort(qstate->env, iq->dp->name, iq->dp->namelen, iq->qchase.qclass, &retdp)) {
                                if(retdp) {
@@ -2085,14 +2086,14 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq,
                /* if this nameserver is at a delegation point, but that
                 * delegation point is a stub and we cannot go higher, skip*/
                if( ((ie->supports_ipv6 && !ns->done_pside6) ||
-                   (ie->supports_ipv4 && !ns->done_pside4)) &&
+                   ((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4)) &&
                    !can_have_last_resort(qstate->env, ns->name, ns->namelen,
                        iq->qchase.qclass, NULL)) {
                        log_nametypeclass(VERB_ALGO, "cannot pside lookup ns "
                                "because it is also a stub/forward,",
                                ns->name, LDNS_RR_TYPE_NS, iq->qchase.qclass);
                        if(ie->supports_ipv6) ns->done_pside6 = 1;
-                       if(ie->supports_ipv4) ns->done_pside4 = 1;
+                       if(ie->supports_ipv4 || ie->use_nat64) ns->done_pside4 = 1;
                        continue;
                }
                /* query for parent-side A and AAAA for nameservers */
@@ -2117,7 +2118,7 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq,
                                return 0;
                        }
                }
-               if(ie->supports_ipv4 && !ns->done_pside4) {
+               if((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4) {
                        /* Send the A request. */
                        if(!generate_parentside_target_query(qstate, iq, id, 
                                ns->name, ns->namelen, 
@@ -2384,7 +2385,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
        }
        if(!ie->supports_ipv6)
                delegpt_no_ipv6(iq->dp);
-       if(!ie->supports_ipv4)
+       if(!ie->supports_ipv4 && !ie->use_nat64)
                delegpt_no_ipv4(iq->dp);
        delegpt_log(VERB_ALGO, iq->dp);
 
@@ -2811,6 +2812,41 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
                        iq->dnssec_expected?"expected": "not expected",
                        iq->dnssec_lame_query?" but lame_query anyway": "");
        }
+
+       struct sockaddr_storage real_addr = target->addr;
+       socklen_t real_addrlen = target->addrlen;
+
+       if (ie->use_nat64 && real_addr.ss_family == AF_INET) {
+               struct sockaddr_in *sin = (struct sockaddr_in *)&target->addr;
+               struct sockaddr_in6 *sin6;
+               int plen = ie->nat64_prefix_net;
+               uint8_t *v4_byte;
+
+               real_addr = ie->nat64_prefix_addr;
+               real_addrlen = ie->nat64_prefix_addrlen;
+
+               sin6 = (struct sockaddr_in6 *)&real_addr;
+               sin6->sin6_flowinfo = 0;
+               sin6->sin6_port = sin->sin_port;
+
+               /* config validation enforces these prefix lengths too */
+               log_assert(plen == 32 || plen == 40 || plen == 48 || plen == 56
+                          || plen == 64 || plen == 96);
+               plen = plen / 8;
+
+               v4_byte = (uint8_t *)&sin->sin_addr.s_addr;
+               for (int i = 0; i < 4; i++) {
+                       if (plen == 8)
+                               /* bits 64...72 are MBZ */
+                               sin6->sin6_addr.s6_addr[plen++] = 0;
+
+                       sin6->sin6_addr.s6_addr[plen++] = *v4_byte++;
+               }
+
+               log_name_addr(VERB_QUERY, "applied NAT64:", iq->dp->name,
+                       &real_addr, real_addrlen);
+       }
+
        fptr_ok(fptr_whitelist_modenv_send_query(qstate->env->send_query));
        outq = (*qstate->env->send_query)(&iq->qinfo_out,
                iq->chase_flags | (iq->chase_to_rd?BIT_RD:0),
@@ -2821,7 +2857,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
                !qstate->blacklist&&(!iter_qname_indicates_dnssec(qstate->env,
                &iq->qinfo_out)||target->attempts==1)?0:BIT_CD),
                iq->dnssec_expected, iq->caps_fallback || is_caps_whitelisted(
-               ie, iq), sq_check_ratelimit, &target->addr, target->addrlen,
+               ie, iq), sq_check_ratelimit, &real_addr, real_addrlen,
                iq->dp->name, iq->dp->namelen,
                (iq->dp->tcp_upstream || qstate->env->cfg->tcp_upstream),
                (iq->dp->ssl_upstream || qstate->env->cfg->ssl_upstream),
@@ -3564,7 +3600,7 @@ processTargetResponse(struct module_qstate* qstate, int id,
        } else {
                verbose(VERB_ALGO, "iterator TargetResponse failed");
                delegpt_mark_neg(dpns, qstate->qinfo.qtype);
-               if((dpns->got4 == 2 || !ie->supports_ipv4) &&
+               if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->use_nat64)) &&
                        (dpns->got6 == 2 || !ie->supports_ipv6)) {
                        dpns->resolved = 1; /* fail the target */
                        /* do not count cached answers */
index 18d3270a0c159c9b0a0c47be57f0c48b28b6340a..3f078cfe58b7be6abdbca99c6a57d48859ceab07 100644 (file)
@@ -117,6 +117,18 @@ struct iter_env {
        /** A flag to indicate whether or not we have an IPv4 route */
        int supports_ipv4;
 
+       /** A flag to locally apply NAT64 to make IPv4 addrs into IPv6 */
+       int use_nat64;
+
+       /** NAT64 prefix address, cf. dns64_env->prefix_addr */
+       struct sockaddr_storage nat64_prefix_addr;
+
+       /** sizeof(sockaddr_in6) */
+       socklen_t nat64_prefix_addrlen;
+
+       /** CIDR mask length of NAT64 prefix */
+       int nat64_prefix_net;
+
        /** A set of inetaddrs that should never be queried. */
        struct iter_donotq* donotq;
 
index df8514b4793b44c277212ade21bb2f59ad58622a..17bf0227398730b7e2e862cc1b7559722ab22278 100644 (file)
@@ -1378,7 +1378,7 @@ struct delegpt* dns_cache_find_delegation(struct module_env* env,
         struct regional* region, struct dns_msg** msg, uint32_t timenow,
         int noexpiredabove, uint8_t* expiretop, size_t expiretoplen);
 int iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags,
-        struct delegpt* dp, int supports_ipv4, int supports_ipv6);
+        struct delegpt* dp, int supports_ipv4, int supports_ipv6, int use_nat64);
 struct iter_hints_stub* hints_lookup_stub(struct iter_hints* hints,
         uint8_t* qname, uint16_t qclass, struct delegpt* dp);
 
@@ -1409,7 +1409,8 @@ struct delegpt* find_delegation(struct module_qstate* qstate, char *nm, size_t n
         if(!dp)
             return NULL;
         if(iter_dp_is_useless(&qinfo, BIT_RD, dp,
-                qstate->env->cfg->do_ip4, qstate->env->cfg->do_ip6)) {
+                qstate->env->cfg->do_ip4, qstate->env->cfg->do_ip6,
+                qstate->env->cfg->do_nat64)) {
             if (dname_is_root((uint8_t*)nm))
                 return NULL;
             nm = (char*)dp->name;
diff --git a/testdata/iter_nat64.rpl b/testdata/iter_nat64.rpl
new file mode 100644 (file)
index 0000000..dde0a25
--- /dev/null
@@ -0,0 +1,117 @@
+; config options
+server:
+       do-nat64: yes
+       target-fetch-policy: "0 0 0 0 0"
+
+stub-zone:
+       name: "."
+       stub-addr: 2001:db8::1
+CONFIG_END
+
+SCENARIO_BEGIN Test NAT64 transport for a v4-only server.
+
+RANGE_BEGIN 0 100
+       ADDRESS 2001:db8::1
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS        FAKE.ROOT.
+SECTION ADDITIONAL
+FAKE.ROOT.     IN      AAAA    2001:db8::1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+v4only. IN NS
+SECTION AUTHORITY
+v4only.        IN NS   ns.v4only.
+SECTION ADDITIONAL
+ns.v4only.     IN      A       192.0.2.1
+ENTRY_END
+
+RANGE_END
+
+; replies from NS over "NAT64"
+
+RANGE_BEGIN 0 100
+       ADDRESS 64:ff9b::c000:0201
+
+; A over NAT64
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY AA QR NOERROR
+SECTION QUESTION
+ns.v4only. IN A
+SECTION ANSWER
+ns.v4only.     IN      A       192.0.2.1
+SECTION AUTHORITY
+v4only.                IN      NS      ns.v4only.
+ENTRY_END
+
+; no AAAA
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY AA QR NOERROR
+SECTION QUESTION
+ns.v4only.     IN      AAAA
+SECTION AUTHORITY
+v4only.                IN      NS      ns.v4only.
+SECTION ADDITIONAL
+ns.v4only.     IN      A       192.0.2.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY AA QR NOERROR
+SECTION QUESTION
+v4only.                IN      NS
+SECTION ANSWER
+v4only.                IN      NS      ns.v4only.
+SECTION ADDITIONAL
+ns.v4only.     IN      A       192.0.2.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY AA QR NOERROR
+SECTION QUESTION
+test.v4only.   IN      A
+SECTION ANSWER
+test.v4only.   IN      A       192.0.2.2
+SECTION AUTHORITY
+v4only.                IN      NS      ns.v4only.
+SECTION ADDITIONAL
+ns.v4only.     IN      A       192.0.2.1
+ENTRY_END
+
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test.v4only. IN A
+ENTRY_END
+
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+test.v4only.   IN      A
+SECTION ANSWER
+test.v4only.   IN      A       192.0.2.2
+ENTRY_END
+
+SCENARIO_END
diff --git a/testdata/iter_nat64_prefix.rpl b/testdata/iter_nat64_prefix.rpl
new file mode 100644 (file)
index 0000000..afc317e
--- /dev/null
@@ -0,0 +1,118 @@
+; config options
+server:
+       do-nat64: yes
+       nat64-prefix: 2001:db8:1234::/96
+       target-fetch-policy: "0 0 0 0 0"
+
+stub-zone:
+       name: "."
+       stub-addr: 2001:db8::1
+CONFIG_END
+
+SCENARIO_BEGIN Test NAT64 transport for a v4-only server, custom NAT64 prefix.
+
+RANGE_BEGIN 0 100
+       ADDRESS 2001:db8::1
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS        FAKE.ROOT.
+SECTION ADDITIONAL
+FAKE.ROOT.     IN      AAAA    2001:db8::1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+v4only. IN NS
+SECTION AUTHORITY
+v4only.        IN NS   ns.v4only.
+SECTION ADDITIONAL
+ns.v4only.     IN      A       192.0.2.1
+ENTRY_END
+
+RANGE_END
+
+; replies from NS over "NAT64"
+
+RANGE_BEGIN 0 100
+       ADDRESS 2001:db8:1234::c000:0201
+
+; A over NAT64
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY AA QR NOERROR
+SECTION QUESTION
+ns.v4only. IN A
+SECTION ANSWER
+ns.v4only.     IN      A       192.0.2.1
+SECTION AUTHORITY
+v4only.                IN      NS      ns.v4only.
+ENTRY_END
+
+; no AAAA
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY AA QR NOERROR
+SECTION QUESTION
+ns.v4only.     IN      AAAA
+SECTION AUTHORITY
+v4only.                IN      NS      ns.v4only.
+SECTION ADDITIONAL
+ns.v4only.     IN      A       192.0.2.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY AA QR NOERROR
+SECTION QUESTION
+v4only.                IN      NS
+SECTION ANSWER
+v4only.                IN      NS      ns.v4only.
+SECTION ADDITIONAL
+ns.v4only.     IN      A       192.0.2.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY AA QR NOERROR
+SECTION QUESTION
+test.v4only.   IN      A
+SECTION ANSWER
+test.v4only.   IN      A       192.0.2.2
+SECTION AUTHORITY
+v4only.                IN      NS      ns.v4only.
+SECTION ADDITIONAL
+ns.v4only.     IN      A       192.0.2.1
+ENTRY_END
+
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test.v4only. IN A
+ENTRY_END
+
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+test.v4only.   IN      A
+SECTION ANSWER
+test.v4only.   IN      A       192.0.2.2
+ENTRY_END
+
+SCENARIO_END
diff --git a/testdata/iter_nat64_prefix48.rpl b/testdata/iter_nat64_prefix48.rpl
new file mode 100644 (file)
index 0000000..e7c32e8
--- /dev/null
@@ -0,0 +1,118 @@
+; config options
+server:
+       do-nat64: yes
+       nat64-prefix: 2001:db8:2345::/48
+       target-fetch-policy: "0 0 0 0 0"
+
+stub-zone:
+       name: "."
+       stub-addr: 2001:db8::1
+CONFIG_END
+
+SCENARIO_BEGIN Test NAT64 transport, this time with /48 NAT64 prefix.
+
+RANGE_BEGIN 0 100
+       ADDRESS 2001:db8::1
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS        FAKE.ROOT.
+SECTION ADDITIONAL
+FAKE.ROOT.     IN      AAAA    2001:db8::1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+v4only. IN NS
+SECTION AUTHORITY
+v4only.        IN NS   ns.v4only.
+SECTION ADDITIONAL
+ns.v4only.     IN      A       192.0.2.1
+ENTRY_END
+
+RANGE_END
+
+; replies from NS over "NAT64"
+
+RANGE_BEGIN 0 100
+       ADDRESS 2001:db8:2345:c000:0002:0100::
+
+; A over NAT64
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY AA QR NOERROR
+SECTION QUESTION
+ns.v4only. IN A
+SECTION ANSWER
+ns.v4only.     IN      A       192.0.2.1
+SECTION AUTHORITY
+v4only.                IN      NS      ns.v4only.
+ENTRY_END
+
+; no AAAA
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY AA QR NOERROR
+SECTION QUESTION
+ns.v4only.     IN      AAAA
+SECTION AUTHORITY
+v4only.                IN      NS      ns.v4only.
+SECTION ADDITIONAL
+ns.v4only.     IN      A       192.0.2.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY AA QR NOERROR
+SECTION QUESTION
+v4only.                IN      NS
+SECTION ANSWER
+v4only.                IN      NS      ns.v4only.
+SECTION ADDITIONAL
+ns.v4only.     IN      A       192.0.2.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY AA QR NOERROR
+SECTION QUESTION
+test.v4only.   IN      A
+SECTION ANSWER
+test.v4only.   IN      A       192.0.2.2
+SECTION AUTHORITY
+v4only.                IN      NS      ns.v4only.
+SECTION ADDITIONAL
+ns.v4only.     IN      A       192.0.2.1
+ENTRY_END
+
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test.v4only. IN A
+ENTRY_END
+
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+test.v4only.   IN      A
+SECTION ANSWER
+test.v4only.   IN      A       192.0.2.2
+ENTRY_END
+
+SCENARIO_END
index b1406913a8c1271fa21e30cf575af8e4abdebc9b..7f24ddc108120734ad4cb7b8aa724ad2b9925c5f 100644 (file)
@@ -86,6 +86,8 @@ struct config_file {
        int do_ip4;
        /** do ip6 query support. */
        int do_ip6;
+       /** do nat64 on queries */
+       int do_nat64;
        /** prefer ip4 upstream queries. */
        int prefer_ip4;
        /** prefer ip6 upstream queries. */
@@ -533,6 +535,9 @@ struct config_file {
        /** ignore AAAAs for these domain names and use A record anyway */
        struct config_strlist* dns64_ignore_aaaa;
 
+       /* NAT64 prefix; if unset defaults to dns64_prefix */
+       char* nat64_prefix;
+
        /** true to enable dnstap support */
        int dnstap;
        /** using bidirectional frame streams if true */
index 09e314b21156fec3fc251c54a2fc3d20417fb9bf..e71ffa7ca698150abf70f8f44854e202d508834d 100644 (file)
@@ -227,6 +227,7 @@ outgoing-num-tcp{COLON}             { YDVAR(1, VAR_OUTGOING_NUM_TCP) }
 incoming-num-tcp{COLON}                { YDVAR(1, VAR_INCOMING_NUM_TCP) }
 do-ip4{COLON}                  { YDVAR(1, VAR_DO_IP4) }
 do-ip6{COLON}                  { YDVAR(1, VAR_DO_IP6) }
+do-nat64{COLON}                        { YDVAR(1, VAR_DO_NAT64) }
 prefer-ip4{COLON}              { YDVAR(1, VAR_PREFER_IP4) }
 prefer-ip6{COLON}              { YDVAR(1, VAR_PREFER_IP6) }
 do-udp{COLON}                  { YDVAR(1, VAR_DO_UDP) }
@@ -461,6 +462,7 @@ max-udp-size{COLON}         { YDVAR(1, VAR_MAX_UDP_SIZE) }
 dns64-prefix{COLON}            { YDVAR(1, VAR_DNS64_PREFIX) }
 dns64-synthall{COLON}          { YDVAR(1, VAR_DNS64_SYNTHALL) }
 dns64-ignore-aaaa{COLON}       { YDVAR(1, VAR_DNS64_IGNORE_AAAA) }
+nat64-prefix{COLON}            { YDVAR(1, VAR_NAT64_PREFIX) }
 define-tag{COLON}              { YDVAR(1, VAR_DEFINE_TAG) }
 local-zone-tag{COLON}          { YDVAR(2, VAR_LOCAL_ZONE_TAG) }
 access-control-tag{COLON}      { YDVAR(2, VAR_ACCESS_CONTROL_TAG) }
index 3ecdad2ad254b5bc0d9ef10f31df5e9906984158..91f8af0a7fbbde25e9cc78a40216ca1d61292fa5 100644 (file)
@@ -73,7 +73,7 @@ extern struct config_parser_state* cfg_parser;
 %token VAR_FORCE_TOPLEVEL
 %token VAR_SERVER VAR_VERBOSITY VAR_NUM_THREADS VAR_PORT
 %token VAR_OUTGOING_RANGE VAR_INTERFACE VAR_PREFER_IP4
-%token VAR_DO_IP4 VAR_DO_IP6 VAR_PREFER_IP6 VAR_DO_UDP VAR_DO_TCP
+%token VAR_DO_IP4 VAR_DO_IP6 VAR_DO_NAT64 VAR_PREFER_IP6 VAR_DO_UDP VAR_DO_TCP
 %token VAR_TCP_MSS VAR_OUTGOING_TCP_MSS VAR_TCP_IDLE_TIMEOUT
 %token VAR_EDNS_TCP_KEEPALIVE VAR_EDNS_TCP_KEEPALIVE_TIMEOUT
 %token VAR_CHROOT VAR_USERNAME VAR_DIRECTORY VAR_LOGFILE VAR_PIDFILE
@@ -123,6 +123,7 @@ extern struct config_parser_state* cfg_parser;
 %token VAR_UNBLOCK_LAN_ZONES VAR_INSECURE_LAN_ZONES
 %token VAR_INFRA_CACHE_MIN_RTT VAR_INFRA_CACHE_MAX_RTT VAR_INFRA_KEEP_PROBING
 %token VAR_DNS64_PREFIX VAR_DNS64_SYNTHALL VAR_DNS64_IGNORE_AAAA
+%token VAR_NAT64_PREFIX
 %token VAR_DNSTAP VAR_DNSTAP_ENABLE VAR_DNSTAP_SOCKET_PATH VAR_DNSTAP_IP
 %token VAR_DNSTAP_TLS VAR_DNSTAP_TLS_SERVER_NAME VAR_DNSTAP_TLS_CERT_BUNDLE
 %token VAR_DNSTAP_TLS_CLIENT_KEY_FILE VAR_DNSTAP_TLS_CLIENT_CERT_FILE
@@ -222,8 +223,8 @@ contents_server: contents_server content_server
        | ;
 content_server: server_num_threads | server_verbosity | server_port |
        server_outgoing_range | server_do_ip4 |
-       server_do_ip6 | server_prefer_ip4 | server_prefer_ip6 |
-       server_do_udp | server_do_tcp |
+       server_do_ip6 | server_do_nat64 | server_prefer_ip4 |
+       server_prefer_ip6 | server_do_udp | server_do_tcp |
        server_tcp_mss | server_outgoing_tcp_mss | server_tcp_idle_timeout |
        server_tcp_keepalive | server_tcp_keepalive_timeout |
        server_interface | server_chroot | server_username |
@@ -273,6 +274,7 @@ content_server: server_num_threads | server_verbosity | server_port |
        server_so_reuseport | server_delay_close | server_udp_connect |
        server_unblock_lan_zones | server_insecure_lan_zones |
        server_dns64_prefix | server_dns64_synthall | server_dns64_ignore_aaaa |
+       server_nat64_prefix |
        server_infra_cache_min_rtt | server_infra_cache_max_rtt | server_harden_algo_downgrade |
        server_ip_transparent | server_ip_ratelimit | server_ratelimit |
        server_ip_dscp | server_infra_keep_probing |
@@ -840,6 +842,15 @@ server_do_ip6: VAR_DO_IP6 STRING_ARG
                free($2);
        }
        ;
+server_do_nat64: VAR_DO_NAT64 STRING_ARG
+       {
+               OUTYY(("P(server_do_nat64:%s)\n", $2));
+               if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+                       yyerror("expected yes or no.");
+               else cfg_parser->cfg->do_nat64 = (strcmp($2, "yes")==0);
+               free($2);
+       }
+       ;
 server_do_udp: VAR_DO_UDP STRING_ARG
        {
                OUTYY(("P(server_do_udp:%s)\n", $2));
@@ -2323,6 +2334,13 @@ server_dns64_ignore_aaaa: VAR_DNS64_IGNORE_AAAA STRING_ARG
                        fatal_exit("out of memory adding dns64-ignore-aaaa");
        }
        ;
+server_nat64_prefix: VAR_NAT64_PREFIX STRING_ARG
+       {
+               OUTYY(("P(nat64_prefix:%s)\n", $2));
+               free(cfg_parser->cfg->nat64_prefix);
+               cfg_parser->cfg->nat64_prefix = $2;
+       }
+       ;
 server_define_tag: VAR_DEFINE_TAG STRING_ARG
        {
                char* p, *s = $2;