Commit 7195921147 for bind

commit 7195921147a9a07183c015d8a4c740363ef89bcc
Author: OndÅ™ej Surý <ondrej@isc.org>
Date:   Thu Aug 13 15:13:49 2026 +0200

    Honor minimal-any over TCP

    TCP transport does not change the semantics of the minimal-any option.
    Apply response minimization regardless of transport and retain coverage
    for both enabled and disabled configurations.

diff --git a/bin/tests/system/additional/ns1/named2.conf.j2 b/bin/tests/system/additional/ns1/named2.conf.j2
index 06a6d045e1..520d6e60cb 100644
--- a/bin/tests/system/additional/ns1/named2.conf.j2
+++ b/bin/tests/system/additional/ns1/named2.conf.j2
@@ -3,6 +3,7 @@ options {
 	recursion no;
 	dnssec-validation no;
 	notify no;
+	minimal-any no;
 	minimal-responses no;
 };

diff --git a/bin/tests/system/additional/tests.sh b/bin/tests/system/additional/tests.sh
index e434474ab1..aa199ebd7a 100644
--- a/bin/tests/system/additional/tests.sh
+++ b/bin/tests/system/additional/tests.sh
@@ -303,7 +303,7 @@ n=$((n + 1))
 echo_i "testing with 'minimal-any yes;' over TCP ($n)"
 ret=0
 $DIG $DIGOPTS -t ANY +tcp www.rt.example @10.53.0.1 >dig.out.$n || ret=1
-grep "ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1" dig.out.$n >/dev/null || ret=1
+grep "ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1" dig.out.$n >/dev/null || ret=1
 if [ $ret -eq 1 ]; then
   echo_i "failed"
   status=$((status + 1))
diff --git a/bin/tests/system/filters/common.py b/bin/tests/system/filters/common.py
index cade025568..0e94d65d69 100644
--- a/bin/tests/system/filters/common.py
+++ b/bin/tests/system/filters/common.py
@@ -52,27 +52,6 @@ def check_filtertype_only(dest, source, qname, ftype, expected, adflag):
             ), f"expected A {expected} in ANSWER: {res}"


-def check_any(dest, source, qname, expected4, expected6, do):
-    qname = dns.name.from_text(qname)
-    msg = isctest.query.create(qname, "any", dnssec=do)
-    res = isctest.query.tcp(msg, dest, source=source)
-    isctest.check.noerror(res)
-    a_record = res.get_rrset(res.answer, qname, rdataclass.IN, rdatatype.A)
-    if expected4:
-        assert (
-            a_record and a_record[0].address == expected4
-        ), f"expected A {expected4} in ANSWER: {res}"
-    else:
-        assert not a_record
-    aaaa_record = res.get_rrset(res.answer, qname, rdataclass.IN, rdatatype.AAAA)
-    if expected6:
-        assert (
-            aaaa_record and aaaa_record[0].address == expected6
-        ), f"expected AAAA {expected6} in ANSWER: {res}"
-    else:
-        assert not aaaa_record
-
-
 def check_nodata(dest, source, qname, qtype, do, adflag):
     msg = isctest.query.create(qname, qtype, dnssec=do)
     res = isctest.query.tcp(msg, dest, source=source)
@@ -155,43 +134,6 @@ def check_filter(addr, altaddr, ftype, break_dnssec, recursive):
     expected = "1.0.0.6" if ftype == "a" else "2001:db8::6"
     check_filtertype_only(addr, altaddr, "dual.unsigned", ftype, expected, False)

-    isctest.log.debug(
-        f"check that A/AAAA (and not {qtype}) is returned if both AAAA and A exist, signed, qtype=ANY, DO=0"
-    )
-    expected4 = "1.0.0.3" if ftype == "aaaa" else None
-    expected6 = "2001:db8::3" if ftype == "a" else None
-    check_any(addr, addr, "dual.signed", expected4, expected6, False)
-
-    isctest.log.debug(
-        "check that both A and AAAA are returned if both AAAA and A exist, signed, qtype=ANY, DO=1, unless break-dnssec is enabled"
-    )
-    if break_dnssec:
-        if ftype == "a":
-            expected4 = None
-        else:
-            expected6 = None
-        check_any(addr, addr, "dual.signed", expected4, expected6, True)
-    else:
-        check_any(addr, addr, "dual.signed", "1.0.0.3", "2001:db8::3", True)
-
-    expected4 = "1.0.0.6" if ftype == "aaaa" else None
-    expected6 = "2001:db8::6" if ftype == "a" else None
-
-    isctest.log.debug(
-        f"check that A/AAAA (and not {qtype}) is returned if both AAAA and A exist, unsigned, qtype=ANY, DO=0"
-    )
-    check_any(addr, addr, "dual.unsigned", expected4, expected6, False)
-
-    isctest.log.debug(
-        f"check that A/AAAA (and not {qtype}) is returned if both AAAA and A exist, unsigned, qtype=ANY, DO=1"
-    )
-    check_any(addr, addr, "dual.unsigned", expected4, expected6, True)
-
-    isctest.log.debug(
-        "check that both A and AAAA are returned if both AAAA and A exist, signed, qtype=ANY, query source does not match ACL"
-    )
-    check_any(addr, altaddr, "dual.unsigned", "1.0.0.6", "2001:db8::6", True)
-
     isctest.log.debug(
         f"check that {qtype} is omitted from additional section, qtype=NS, unsigned"
     )
diff --git a/bin/tests/system/host/tests.sh b/bin/tests/system/host/tests.sh
index 4bc3bf93d9..7312679d5a 100644
--- a/bin/tests/system/host/tests.sh
+++ b/bin/tests/system/host/tests.sh
@@ -117,9 +117,8 @@ ret=0
 $HOST -p ${PORT} -t ANY example.net 10.53.0.1 2>host.err${n} >host.out${n} || ret=1
 lines=$(grep -c 'Address:.10\.53\.0\.1#'"${PORT}" host.out${n})
 test $lines -eq 1 || ret=1
-lines=$(grep -c 'example.net has SOA record ns1.example.net. hostmaster.example.net. 1397051952 5 5 1814400 3600' host.out${n})
-test $lines -eq 1 || ret=1
-lines=$(grep -c 'example.net name server ns1.example.net.' host.out${n})
+# the answer is minimized to a single RRset (SOA or NS)
+lines=$(grep -c -e 'example.net has SOA record ns1.example.net. hostmaster.example.net. 1397051952 5 5 1814400 3600' -e 'example.net name server ns1.example.net.' host.out${n})
 test $lines -eq 1 || ret=1
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=$((status + ret))
diff --git a/bin/tests/system/nslookup/tests.sh b/bin/tests/system/nslookup/tests.sh
index 85dc1d72f0..ffae5f83bb 100644
--- a/bin/tests/system/nslookup/tests.sh
+++ b/bin/tests/system/nslookup/tests.sh
@@ -130,11 +130,8 @@ ret=0
 $NSLOOKUP -port=${PORT} -type=ANY example.net 10.53.0.1 2>nslookup.err${n} >nslookup.out${n} || ret=1
 lines=$(grep -c 'Address:.10\.53\.0\.1#'"${PORT}" nslookup.out${n})
 test $lines -eq 1 || ret=1
-lines=$(grep -c 'origin = ns1\.example\.net' nslookup.out${n})
-test $lines -eq 1 || ret=1
-lines=$(grep -c 'mail addr = hostmaster\.example\.net' nslookup.out${n})
-test $lines -eq 1 || ret=1
-lines=$(grep -c 'nameserver = ns1\.example\.net.' nslookup.out${n})
+# the answer is minimized to a single RRset (SOA or NS)
+lines=$(grep -c -e 'origin = ns1\.example\.net' -e 'nameserver = ns1\.example\.net.' nslookup.out${n})
 test $lines -eq 1 || ret=1
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=$((status + ret))
diff --git a/bin/tests/system/nsupdate/tests.sh b/bin/tests/system/nsupdate/tests.sh
index f42ddcbc10..8bacefc941 100755
--- a/bin/tests/system/nsupdate/tests.sh
+++ b/bin/tests/system/nsupdate/tests.sh
@@ -1243,10 +1243,12 @@ $DIG $DIGOPTS +tcp +noadd +nosea +nostat +noquest +nocomm +nocmd dnskey.test. \
 $NSUPDATE update.in.$n

 $DIG $DIGOPTS +tcp +noadd +nosea +nostat +noquest +nocomm +nocmd dnskey.test. \
-  @10.53.0.3 any >dig.out.ns3.$n || ret=1
+  @10.53.0.3 dnskey >dig.out.ns3.$n || ret=1
+$DIG $DIGOPTS +tcp +noadd +nosea +nostat +noquest +nocomm +nocmd dnskey.test. \
+  @10.53.0.3 type65534 >dig.out.ns3.65534.$n || ret=1

 grep "600.*DNSKEY" dig.out.ns3.$n >/dev/null || ret=1
-grep TYPE65534 dig.out.ns3.$n >/dev/null && ret=1
+grep TYPE65534 dig.out.ns3.65534.$n >/dev/null && ret=1
 if test $ret -ne 0; then
   echo_i "failed"
   status=1
@@ -2173,16 +2175,19 @@ send
 END
 $DIG $DIGOPTS @10.53.0.6 \
   +tcp +noadd +nosea +nostat +noquest +nocomm +nocmd \
-  ANY -x 10.53.0.5 >dig.out.ns6.test$n || ret=1
+  A -x 10.53.0.5 >dig.out.ns6.a.test$n || ret=1
+$DIG $DIGOPTS @10.53.0.6 \
+  +tcp +noadd +nosea +nostat +noquest +nocomm +nocmd \
+  AAAA -x 10.53.0.5 >dig.out.ns6.aaaa.test$n || ret=1
 nextpart ns6/named.run >nextpart.out.test$n
 grep "attempt to add more records than permitted by policy" nextpart.out.test$n >/dev/null || ret=1
 # the policy is 'grant * tcp-self . PTR(1) ANY(2) A;' so all the A
 # records should have been added as there is no limit and the first 2
 # of the AAAA records added as they match ANY(2).
-c1=$(awk '$4 == "A" { print }' dig.out.ns6.test$n | wc -l)
-c2=$(awk '$4 == "AAAA" { print }' dig.out.ns6.test$n | wc -l)
+c1=$(awk '$4 == "A" { print }' dig.out.ns6.a.test$n | wc -l)
+c2=$(awk '$4 == "AAAA" { print }' dig.out.ns6.aaaa.test$n | wc -l)
 test "$c1" -eq 3 -a "$c2" -eq 2 || ret=1
-grep "::ffff:1.2.3.2" dig.out.ns6.test$n >/dev/null && ret=1
+grep "::ffff:1.2.3.2" dig.out.ns6.aaaa.test$n >/dev/null && ret=1
 if test $ret -ne 0; then
   echo_i "failed"
   status=1
@@ -2644,10 +2649,12 @@ EOF
   update add _xxx.self-srv-no-type.example.com 3600 IN TXT a txt record
   send
 EOF
-  $DIG $DIGOPTS +tcp @10.53.0.7 _xxx.self-srv-no-type.example.com ANY >dig.out.ns7.test$n || ret=1
-  grep "status: NOERROR" dig.out.ns7.test$n >/dev/null || ret=1
-  grep '_xxx.self-srv-no-type.example.com.*SRV.*0 0 0 machine.example.com' dig.out.ns7.test$n >/dev/null || ret=1
-  grep '_xxx.self-srv-no-type.example.com.*TXT.*"a" "txt" "record"' dig.out.ns7.test$n >/dev/null || ret=1
+  $DIG $DIGOPTS +tcp @10.53.0.7 _xxx.self-srv-no-type.example.com SRV >dig.out.ns7.srv.test$n || ret=1
+  grep "status: NOERROR" dig.out.ns7.srv.test$n >/dev/null || ret=1
+  grep '_xxx.self-srv-no-type.example.com.*SRV.*0 0 0 machine.example.com' dig.out.ns7.srv.test$n >/dev/null || ret=1
+  $DIG $DIGOPTS +tcp @10.53.0.7 _xxx.self-srv-no-type.example.com TXT >dig.out.ns7.txt.test$n || ret=1
+  grep "status: NOERROR" dig.out.ns7.txt.test$n >/dev/null || ret=1
+  grep '_xxx.self-srv-no-type.example.com.*TXT.*"a" "txt" "record"' dig.out.ns7.txt.test$n >/dev/null || ret=1
   [ $ret = 0 ] || {
     echo_i "failed"
     status=1
diff --git a/bin/tests/system/redirect/tests.sh b/bin/tests/system/redirect/tests.sh
index 007b6f73de..e7d65a19b1 100644
--- a/bin/tests/system/redirect/tests.sh
+++ b/bin/tests/system/redirect/tests.sh
@@ -77,8 +77,8 @@ echo_i "checking ANY zone redirect works for nonexist ($n)"
 ret=0
 $DIG $DIGOPTS nonexist. @10.53.0.2 -b 10.53.0.2 any >dig.out.ns2.test$n || ret=1
 grep "status: NOERROR" dig.out.ns2.test$n >/dev/null || ret=1
-grep "100.100.100.1" dig.out.ns2.test$n >/dev/null || ret=1
-grep "2001:ffff:ffff::6464:6401" dig.out.ns2.test$n >/dev/null || ret=1
+# the answer is minimized to a single RRset
+grep -e "100.100.100.1" -e "2001:ffff:ffff::6464:6401" dig.out.ns2.test$n >/dev/null || ret=1
 n=$((n + 1))
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=$((status + ret))
@@ -133,8 +133,8 @@ echo_i "checking ANY zone redirect works for signed nonexist, DO=0 ($n)"
 ret=0
 $DIG $DIGOPTS nonexist.signed. @10.53.0.2 -b 10.53.0.2 any >dig.out.ns2.test$n || ret=1
 grep "status: NOERROR" dig.out.ns2.test$n >/dev/null || ret=1
-grep "100.100.100.1" dig.out.ns2.test$n >/dev/null || ret=1
-grep "2001:ffff:ffff::6464:6401" dig.out.ns2.test$n >/dev/null || ret=1
+# the answer is minimized to a single RRset
+grep -e "100.100.100.1" -e "2001:ffff:ffff::6464:6401" dig.out.ns2.test$n >/dev/null || ret=1
 n=$((n + 1))
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=$((status + ret))
@@ -220,8 +220,8 @@ echo_i "checking ANY zone redirect works for nonexist authoritative ($n)"
 ret=0
 $DIG $DIGOPTS nonexist. @10.53.0.1 -b 10.53.0.1 any >dig.out.ns1.test$n || ret=1
 grep "status: NOERROR" dig.out.ns1.test$n >/dev/null || ret=1
-grep "100.100.100.2" dig.out.ns1.test$n >/dev/null || ret=1
-grep "2001:ffff:ffff::6464:6402" dig.out.ns1.test$n >/dev/null || ret=1
+# the answer is minimized to a single RRset
+grep -e "100.100.100.2" -e "2001:ffff:ffff::6464:6402" dig.out.ns1.test$n >/dev/null || ret=1
 n=$((n + 1))
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=$((status + ret))
@@ -276,8 +276,8 @@ echo_i "checking ANY zone redirect works for signed nonexist, DO=0 authoritative
 ret=0
 $DIG $DIGOPTS nonexist.signed. @10.53.0.1 -b 10.53.0.1 any >dig.out.ns1.test$n || ret=1
 grep "status: NOERROR" dig.out.ns1.test$n >/dev/null || ret=1
-grep "100.100.100.2" dig.out.ns1.test$n >/dev/null || ret=1
-grep "2001:ffff:ffff::6464:6402" dig.out.ns1.test$n >/dev/null || ret=1
+# the answer is minimized to a single RRset
+grep -e "100.100.100.2" -e "2001:ffff:ffff::6464:6402" dig.out.ns1.test$n >/dev/null || ret=1
 n=$((n + 1))
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=$((status + ret))
@@ -409,8 +409,8 @@ echo_i "checking ANY nxdomain-redirect works for nonexist ($n)"
 ret=0
 $DIG $DIGOPTS nonexist. @10.53.0.4 -b 10.53.0.2 any >dig.out.ns4.test$n || ret=1
 grep "status: NOERROR" dig.out.ns4.test$n >/dev/null || ret=1
-grep "100.100.100.1" dig.out.ns4.test$n >/dev/null || ret=1
-grep "2001:ffff:ffff::6464:6401" dig.out.ns4.test$n >/dev/null || ret=1
+# the answer is minimized to a single RRset
+grep -e "100.100.100.1" -e "2001:ffff:ffff::6464:6401" dig.out.ns4.test$n >/dev/null || ret=1
 n=$((n + 1))
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=$((status + ret))
@@ -437,8 +437,8 @@ echo_i "checking ANY nxdomain-redirect works for signed nonexist, DO=0 ($n)"
 ret=0
 $DIG $DIGOPTS nonexist.signed. @10.53.0.4 -b 10.53.0.2 any >dig.out.ns4.test$n || ret=1
 grep "status: NOERROR" dig.out.ns4.test$n >/dev/null || ret=1
-grep "100.100.100.1" dig.out.ns4.test$n >/dev/null || ret=1
-grep "2001:ffff:ffff::6464:6401" dig.out.ns4.test$n >/dev/null || ret=1
+# the answer is minimized to a single RRset
+grep -e "100.100.100.1" -e "2001:ffff:ffff::6464:6401" dig.out.ns4.test$n >/dev/null || ret=1
 n=$((n + 1))
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=$((status + ret))
diff --git a/bin/tests/system/views/tests.sh b/bin/tests/system/views/tests.sh
index 90e4e10d04..f8d19281b1 100644
--- a/bin/tests/system/views/tests.sh
+++ b/bin/tests/system/views/tests.sh
@@ -29,10 +29,10 @@ RNDCCMD="$RNDC -c ../_common/rndc.conf -p ${CONTROLPORT} -s"
 status=0

 echo_i "fetching a.example from ns2's initial configuration"
-dig_with_opts a.example. @10.53.0.2 any >dig.out.ns2.1 || status=1
+dig_with_opts a.example. @10.53.0.2 a >dig.out.ns2.1 || status=1

 echo_i "fetching a.example from ns3's initial configuration"
-dig_with_opts a.example. @10.53.0.3 any >dig.out.ns3.1 || status=1
+dig_with_opts a.example. @10.53.0.3 a >dig.out.ns3.1 || status=1

 echo_i "copying in new configurations for ns2 and ns3"
 rm -f ns2/named.conf ns3/named.conf ns2/example.db
@@ -58,13 +58,13 @@ if [ $ret != 0 ]; then echo_i "failed"; fi
 status=$((status + ret))

 echo_i "fetching a.example from ns2's 10.53.0.4, source address 10.53.0.4"
-dig_with_opts -b 10.53.0.4 a.example. @10.53.0.4 any >dig.out.ns4.2 || status=1
+dig_with_opts -b 10.53.0.4 a.example. @10.53.0.4 a >dig.out.ns4.2 || status=1

 echo_i "fetching a.example from ns2's 10.53.0.2, source address 10.53.0.2"
-dig_with_opts -b 10.53.0.2 a.example. @10.53.0.2 any >dig.out.ns2.2 || status=1
+dig_with_opts -b 10.53.0.2 a.example. @10.53.0.2 a >dig.out.ns2.2 || status=1

 echo_i "fetching a.example from ns3's 10.53.0.3, source address defaulted"
-dig_with_opts @10.53.0.3 a.example. any >dig.out.ns3.2 || status=1
+dig_with_opts @10.53.0.3 a.example. a >dig.out.ns3.2 || status=1

 echo_i "comparing ns3's initial a.example to one from reconfigured 10.53.0.2"
 digcomp dig.out.ns3.1 dig.out.ns2.2 || status=1
diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst
index 43c8c66553..f0d19b4db5 100644
--- a/doc/arm/reference.rst
+++ b/doc/arm/reference.rst
@@ -2072,11 +2072,11 @@ Boolean Options

 .. namedconf:statement:: minimal-any
    :tags: query
-   :short: Controls whether the server replies with only one of the RRsets for a query name, when generating a positive response to a query of type ANY over UDP.
+   :short: Controls whether the server replies with only one of the RRsets for a query name, when generating a positive response to a query of type ANY.

    If set to ``yes``, the server replies with only one of
    the RRsets for the query name, and its covering RRSIGs if any,
-   when generating a positive response to a query of type ANY over UDP,
+   when generating a positive response to a query of type ANY,
    instead of replying with all known RRsets for the name. Similarly, a
    query for type RRSIG is answered with the RRSIG records covering
    only one type. This can reduce the impact of some kinds of attack
diff --git a/lib/ns/query.c b/lib/ns/query.c
index f897cb6d18..9d48e43478 100644
--- a/lib/ns/query.c
+++ b/lib/ns/query.c
@@ -7428,7 +7428,6 @@ query_respond_any(query_ctx_t *qctx) {
 			dns_rdataset_disassociate(qctx->rdataset);
 			hidden = true;
 		} else if (qctx->view->minimal_any &&
-			   !qctx->client->inner.tcp &&
 			   !qctx->client->inner.wantdnssec &&
 			   qctx->qtype == dns_rdatatype_any &&
 			   (dns_rdatatype_issig(qctx->rdataset->type)))
@@ -7437,7 +7436,6 @@ query_respond_any(query_ctx_t *qctx) {
 						  "minimal-any skip signature");
 			dns_rdataset_disassociate(qctx->rdataset);
 		} else if (qctx->view->minimal_any &&
-			   !qctx->client->inner.tcp &&
 			   onetype != dns_rdatatype_none &&
 			   qctx->rdataset->type != onetype &&
 			   qctx->rdataset->covers != onetype)
@@ -11452,9 +11450,7 @@ ns_query_start(ns_client_t *client, isc_nmhandle_t *handle) {
 	/*
 	 * Maybe turn on minimal responses for ANY queries.
 	 */
-	if (qtype == dns_rdatatype_any && client->inner.view->minimal_any &&
-	    !client->inner.tcp)
-	{
+	if (qtype == dns_rdatatype_any && client->inner.view->minimal_any) {
 		client->query.noauthority = true;
 		client->query.noadditional = true;
 	}