]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Temporarily remove TCP fallback after UDP timeouts
authorOndřej Surý <ondrej@isc.org>
Thu, 14 May 2026 08:04:20 +0000 (10:04 +0200)
committerOndřej Surý <ondrej@isc.org>
Tue, 19 May 2026 09:18:16 +0000 (11:18 +0200)
The retry path in resquery_send() that flipped DNS_FETCHOPT_TCP on a
query whose dispatch had already been bound as UDP in fctx_query() had
no effect on the transport actually used, but did leave a stale TCP
bit visible to downstream consumers (dnstap framing, cookie checks,
the AUTHORITY-NS spoofability guard).

The ineffective code has been removed from resquery_send().  The
TCP fallback functionality will be corrected and restored in the next
commit.

Assisted-by: Claude:claude-opus-4-7
bin/tests/system/dispatch/ans4/ans.py [new file with mode: 0644]
bin/tests/system/dispatch/ans4/tcp-only.db [new file with mode: 0644]
bin/tests/system/dispatch/ns1/root.db
bin/tests/system/dispatch/tests_tcponly.py [new file with mode: 0644]
lib/dns/resolver.c

diff --git a/bin/tests/system/dispatch/ans4/ans.py b/bin/tests/system/dispatch/ans4/ans.py
new file mode 100644 (file)
index 0000000..5ec4985
--- /dev/null
@@ -0,0 +1,42 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+from collections.abc import AsyncGenerator
+
+from isctest.asyncserver import (
+    AsyncDnsServer,
+    DnsProtocol,
+    DnsResponseSend,
+    QueryContext,
+    ResponseAction,
+    ResponseDrop,
+    ResponseHandler,
+)
+
+
+class TcpOnlyHandler(ResponseHandler):
+    async def get_responses(
+        self, qctx: QueryContext
+    ) -> AsyncGenerator[ResponseAction, None]:
+        if qctx.protocol == DnsProtocol.TCP:
+            yield DnsResponseSend(qctx.response)
+        else:
+            yield ResponseDrop()
+
+
+def main() -> None:
+    server = AsyncDnsServer()
+    server.install_response_handler(TcpOnlyHandler())
+    server.run()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/bin/tests/system/dispatch/ans4/tcp-only.db b/bin/tests/system/dispatch/ans4/tcp-only.db
new file mode 100644 (file)
index 0000000..1f95670
--- /dev/null
@@ -0,0 +1,15 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0.  If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+@                      3600    SOA     . . 1 1 1 1 1
+@                      3600    NS      ns
+ns                     3600    A       10.53.0.4
+foo                    3600    A       127.0.0.1
index eb9ad3ecf11d31143604e73bb87a26d07621f150..be1492082a072cbe271bd09fac52c44a727fba77 100644 (file)
@@ -3,3 +3,5 @@
 ns.nil.                300     A       10.53.0.1
 example.       300     NS      ns.example.
 ns.example.    300     A       10.53.0.2
+tcp-only.      300     NS      ns.tcp-only.
+ns.tcp-only.   300     A       10.53.0.4
diff --git a/bin/tests/system/dispatch/tests_tcponly.py b/bin/tests/system/dispatch/tests_tcponly.py
new file mode 100644 (file)
index 0000000..f87919e
--- /dev/null
@@ -0,0 +1,33 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+import dns.message
+import pytest
+
+import isctest
+
+pytestmark = pytest.mark.extra_artifacts(
+    [
+        "ans*/ans.run",
+    ]
+)
+
+
+def test_tcponly_not_resolved():
+    """
+    An authoritative server that only answers over TCP is unreachable
+    when its zone is queried over UDP: the resolver does not transparently
+    fall back to TCP after UDP timeouts. (This confirms the expected behavior
+    for this commit; TCP fallback will be restored in the next.)
+    """
+    msg = dns.message.make_query("foo.tcp-only.", "A")
+    res = isctest.query.udp(msg, "10.53.0.2", timeout=15)
+    isctest.check.servfail(res)
index faf69ba58819f8f4c3ec56c9bcdcb56e644d8d58..71bc2ac11e56e53b9c5e2ae4118ca7c58180ba13 100644 (file)
@@ -2553,33 +2553,16 @@ resquery_send(resquery_t *query) {
 
        if (fctx->timeout && (query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
                isc_sockaddr_t *sockaddr = &query->addrinfo->sockaddr;
-               struct tried *tried;
+               struct tried *tried = triededns(fctx, sockaddr);
 
                /*
                 * If this is the first timeout for this server in this
                 * fetch context, try setting EDNS UDP buffer size to
                 * the largest UDP response size we have seen from this
                 * server so far.
-                *
-                * If this server has already timed out twice or more in
-                * this fetch context, force TCP.
                 */
-               if ((tried = triededns(fctx, sockaddr)) != NULL) {
-                       if (tried->count == 1U) {
-                               hint = dns_adb_getudpsize(fctx->adb,
-                                                         query->addrinfo);
-                       } else if (tried->count >= 2U) {
-                               if ((query->options & DNS_FETCHOPT_TCP) == 0) {
-                                       /*
-                                        * Inform the ADB that we're ending a
-                                        * UDP fetch, and turn the query into
-                                        * a TCP query.
-                                        */
-                                       dns_adb_endudpfetch(fctx->adb,
-                                                           query->addrinfo);
-                                       query->options |= DNS_FETCHOPT_TCP;
-                               }
-                       }
+               if (tried != NULL && tried->count == 1U) {
+                       hint = dns_adb_getudpsize(fctx->adb, query->addrinfo);
                }
        }
        fctx->timeout = false;