[9.18] rem: usr: Remove ineffective TCP fallback after repeated UDP timeouts
Some checks are pending
CodeQL / Analyze (push) Waiting to run
SonarCloud / Build and analyze (push) Waiting to run

When an authoritative server failed to respond to two consecutive
UDP queries, named marked the next retry as TCP but still sent it
over UDP, producing misleading dnstap records. The ineffective
retry path has been removed; a corrected TCP fallback will be
restored in future BIND 9 versions.

Closes #5529

Backport of MR !12022

Merge branch 'backport-5529-fix-tcp-fallback-after-udp-timeouts-9.18' into 'bind-9.18'

See merge request isc-projects/bind9!12050
This commit is contained in:
Ondřej Surý 2026-05-19 13:05:19 +02:00
commit 9b53e4be29
5 changed files with 95 additions and 20 deletions

View file

@ -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()

View file

@ -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

View file

@ -14,3 +14,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

View file

@ -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)

View file

@ -2675,33 +2675,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;