Temporarily remove TCP fallback after UDP timeouts

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
(cherry picked from commit 01523a078a)
This commit is contained in:
Ondřej Surý 2026-05-14 10:04:20 +02:00 committed by Ondřej Surý (GitLab job 7417718)
parent 0fe9e2c923
commit f82c6f0cba
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;