From 1a9a4e4ca163e9f200dfc04701abb9c8c128cc34 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 11 Feb 2026 16:01:30 +0100 Subject: [PATCH] - Fix #1403: Inconsistency between do-nat64 and do-not-query-address during retries. --- doc/Changelog | 4 + doc/unbound.conf.rst | 11 ++ iterator/iter_utils.c | 27 ++++- testdata/iter_nat64_donotq.rpl | 192 +++++++++++++++++++++++++++++++++ 4 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 testdata/iter_nat64_donotq.rpl diff --git a/doc/Changelog b/doc/Changelog index 0c7f35462..cd6848653 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +11 February 2026: Wouter + - Fix #1403: Inconsistency between do-nat64 and do-not-query-address + during retries. + 9 February 2026: Wouter - Merge #1401: Add a new build-time option for system TLS. The --enable-system-tls flag enables the diff --git a/doc/unbound.conf.rst b/doc/unbound.conf.rst index 885bd8deb..e53282ee6 100644 --- a/doc/unbound.conf.rst +++ b/doc/unbound.conf.rst @@ -4164,6 +4164,17 @@ servers. Use a specific NAT64 prefix to reach IPv4-only servers. The prefix length must be one of /32, /40, /48, /56, /64 or /96. + The NAT64 prefix is allowed by the + :ref:`do-not-query-address` option, + so that there is a clear outcome of addresses in both; the NAT64 prefix + is allowed. + The IPv4 address could be filtered by the + :ref:`do-not-query-address` option, + if needed. + Allowing the NAT64 prefix is useful when using do-not-query-address + for a cluster of machines that is IPv6-only and uses NAT64, but does + not have internet access. + Default: 64:ff9b::/96 (same as :ref:`dns64-prefix`) .. _unbound.conf.dnscrypt: diff --git a/iterator/iter_utils.c b/iterator/iter_utils.c index fb419f9ab..feb5b702b 100644 --- a/iterator/iter_utils.c +++ b/iterator/iter_utils.c @@ -308,9 +308,30 @@ iter_filter_unsuitable(struct iter_env* iter_env, struct module_env* env, if(a->bogus) return -1; /* address of server is bogus */ if(donotq_lookup(iter_env->donotq, &a->addr, a->addrlen)) { - log_addr(VERB_ALGO, "skip addr on the donotquery list", - &a->addr, a->addrlen); - return -1; /* server is on the donotquery list */ + if(iter_env->nat64.use_nat64 && + addr_is_ip6(&a->addr, a->addrlen) && + a->addrlen == iter_env->nat64.nat64_prefix_addrlen && + addr_in_common(&a->addr, 128, + &iter_env->nat64.nat64_prefix_addr, + iter_env->nat64.nat64_prefix_net, + iter_env->nat64.nat64_prefix_addrlen) == + iter_env->nat64.nat64_prefix_net) { + /* The NAT64 is enabled, and address is IPv6, it is + * in the NAT64 prefix. It is allowed. + * So that in an IPv6-only cluster without internet + * access, that makes the NAT64 translation continue + * to work. The NAT64 prefix is allowed. */ + /* Otherwise, after a timeout, the already NAT64 + * translated address would be treated differently, + * and that causes confusion. */ + log_addr(VERB_ALGO, "the addr is on the donotquery " + "list, but allowed because it is NAT64", + &a->addr, a->addrlen); + } else { + log_addr(VERB_ALGO, "skip addr on the donotquery list", + &a->addr, a->addrlen); + return -1; /* server is on the donotquery list */ + } } if(!iter_env->supports_ipv6 && addr_is_ip6(&a->addr, a->addrlen)) { return -1; /* there is no ip6 available */ diff --git a/testdata/iter_nat64_donotq.rpl b/testdata/iter_nat64_donotq.rpl new file mode 100644 index 000000000..079d52e68 --- /dev/null +++ b/testdata/iter_nat64_donotq.rpl @@ -0,0 +1,192 @@ +; config options +server: + do-nat64: yes + nat64-prefix: 2001:db8:1234::/96 + target-fetch-policy: "0 0 0 0 0" + + ; This is like a machine that is part of a cluster of hosts that + ; is IPv6-only, and uses NAT64. The cluster has no internet access. + do-not-query-address: ::0/0 + + qname-minimisation: no + +stub-zone: + name: "." + ; Pick an address in the NAT64 prefix, so it is allowed. + ; other addresses would not be allowed. Or without the bugfix, + ; allowed depending on state machine activation sequence. + stub-addr: 2001:db8:1234::1 +CONFIG_END + +SCENARIO_BEGIN Test NAT64 transport for v4-only with do-not-query-addresses. + +RANGE_BEGIN 0 100 + ADDRESS 2001:db8:1234::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:1234::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 20 + 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 SOA ns.v4only. host. 1 3600 300 48000 3600 +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 + +RANGE_BEGIN 50 100 + ADDRESS 2001:db8:1234::c000:0201 +; no AAAA +; The last resort lookup of the AAAA is blocked here, +; the last resort processing is not desired, it should resolve test2 +; straight away. +;ENTRY_BEGIN +;MATCH opcode qtype qname +;ADJUST copy_id +;REPLY AA QR NOERROR +;SECTION QUESTION +;ns.v4only. IN AAAA +;SECTION AUTHORITY +;v4only. IN SOA ns.v4only. host. 1 3600 300 48000 3600 +;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 +ns.v4only. IN A +SECTION ANSWER +ns.v4only. IN A 192.0.2.1 +SECTION AUTHORITY +v4only. IN NS ns.v4only. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY AA QR NOERROR +SECTION QUESTION +test2.v4only. IN A +SECTION ANSWER +test2.v4only. IN A 192.0.2.3 +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 + +; for a query where the upstream nameserver has a timeout. +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test2.v4only. IN A +ENTRY_END + +; Only the test2 query is there, and it has a timeout. +; The address is already NAT64 translated, so now that it is +; attempted again, it is looked up in dotnotq as the ipv6 address. +STEP 40 TIMEOUT + +STEP 50 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +test2.v4only. IN A +SECTION ANSWER +test2.v4only. IN A 192.0.2.3 +ENTRY_END + +SCENARIO_END