From: Ondřej Surý Date: Sun, 21 Jun 2026 18:20:35 +0000 (+0200) Subject: Add the tcp-reuse-timeout option X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=477130cf8e4e15d16ced13043644840cd92fb6c2;p=thirdparty%2Fbind9.git Add the tcp-reuse-timeout option The idle timeout that bounds how long a reused outgoing TCP/TLS connection is held open for reuse was only tunable through the 'named -T tcpidletimeout' developer hook added earlier on this branch. Make it a proper configuration option, tcp-reuse-timeout (options block, in units of 100 milliseconds like the other tcp-*-timeout options), and drop the -T hook. --- diff --git a/bin/include/defaultconfig.h b/bin/include/defaultconfig.h index d40b584d41e..46942971199 100644 --- a/bin/include/defaultconfig.h +++ b/bin/include/defaultconfig.h @@ -100,6 +100,7 @@ options {\n\ tcp-listen-queue 10;\n\ tcp-primaries-timeout 150;\n\ tcp-receive-buffer 0;\n\ + tcp-reuse-timeout 50;\n\ tcp-send-buffer 0;\n\ transfer-message-size 20480;\n\ transfers-in 10;\n\ diff --git a/bin/named/main.c b/bin/named/main.c index f7f7afb4225..37b77bc2ed2 100644 --- a/bin/named/main.c +++ b/bin/named/main.c @@ -127,7 +127,6 @@ extern unsigned int dns_zone_mkey_month; extern unsigned int dns_adb_entrywindow; extern unsigned int dns_adb_cachemin; extern size_t dns_dispatch_tcppipelining; -extern unsigned int dns_dispatch_tcp_idle_timeout; static bool want_stats = false; static char absolute_conffile[PATH_MAX]; @@ -748,8 +747,6 @@ parse_T_opt(char *option) { "least 1"); } dns_dispatch_tcppipelining = pipelining; - } else if (!strncmp(option, "tcpidletimeout=", 15)) { - dns_dispatch_tcp_idle_timeout = atoi(option + 15); } else { fprintf(stderr, "unknown -T flag '%s'\n", option); } diff --git a/bin/named/server.c b/bin/named/server.c index fc53de52494..be7ecd02251 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -169,6 +169,7 @@ #define MAX_ADVERTISED_TIMEOUT UINT32_C(UINT16_MAX * 100) #define MIN_PRIMARIES_TIMEOUT UINT32_C(2500) /* 2.5 seconds */ #define MAX_PRIMARIES_TIMEOUT UINT32_C(120000) /* 2 minutes */ +#define MAX_REUSE_TIMEOUT UINT32_C(120000) /* 2 minutes */ /*% * Check an operation for failure. Assumes that the function @@ -7723,7 +7724,7 @@ apply_configuration(cfg_obj_t *effectiveconfig, cfg_obj_t *bindkeys, ns_altsecretlist_t altsecrets, tmpaltsecrets; uint32_t softquota = 0; uint32_t max; - uint64_t initial, idle, keepalive, advertised, primaries; + uint64_t initial, idle, keepalive, advertised, primaries, reuse; bool loadbalancesockets; bool exclusive = false; dns_aclenv_t *env = @@ -8000,12 +8001,26 @@ apply_configuration(cfg_obj_t *effectiveconfig, cfg_obj_t *bindkeys, primaries = MIN_PRIMARIES_TIMEOUT; } + obj = NULL; + result = named_config_get(maps, "tcp-reuse-timeout", &obj); + INSIST(result == ISC_R_SUCCESS); + reuse = cfg_obj_asuint32(obj) * 100; + if (reuse > MAX_REUSE_TIMEOUT) { + cfg_obj_log(obj, ISC_LOG_WARNING, + "tcp-reuse-timeout value is out of range: " + "lowering to %" PRIu32, + MAX_REUSE_TIMEOUT / 100); + reuse = MAX_REUSE_TIMEOUT; + } + isc_nm_setinitialtimeout(initial); isc_nm_setprimariestimeout(primaries); isc_nm_setidletimeout(idle); isc_nm_setkeepalivetimeout(keepalive); isc_nm_setadvertisedtimeout(advertised); + dns_dispatchmgr_setreusetimeout(named_g_dispatchmgr, reuse); + #define CAP_IF_NOT_ZERO(v, min, max) \ if (v > 0 && v < min) { \ v = min; \ diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 86ae19f5ccf..e28b83c7f0c 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -3924,6 +3924,20 @@ system. option are expected to use TCP connections for more than one message. This value can be updated at runtime by using :option:`rndc tcp-timeouts`. +.. namedconf:statement:: tcp-reuse-timeout + :tags: query + :short: Sets the amount of time (in milliseconds) that an idle outgoing TCP connection is kept open for reuse. + + This sets the amount of time, in units of 100 milliseconds, that an idle + outgoing TCP or TLS connection opened by :iscman:`named` (for example, to a + forwarder or an authoritative server) is kept open after its last outstanding + response has completed, so that it can be reused by a later query instead of + being closed and reopened. The default is 50 (5 seconds), and the maximum is + 1200 (two minutes). A value of 0 disables keeping idle outgoing TCP or TLS + connections open for reuse; it does not affect sharing of a connection while + queries are still outstanding. Values above the maximum are adjusted with a + logged warning. + .. namedconf:statement:: tcp-advertised-timeout :tags: query :short: Sets the timeout value (in milliseconds) that the server sends in responses containing the EDNS TCP keepalive option. diff --git a/doc/misc/options b/doc/misc/options index 677c5181248..8303cdf98cd 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -308,6 +308,7 @@ options { tcp-listen-queue ; tcp-primaries-timeout ; tcp-receive-buffer ; + tcp-reuse-timeout ; tcp-send-buffer ; tkey-gssapi-keytab ; tls-port ; diff --git a/lib/dns/dispatch.c b/lib/dns/dispatch.c index dc31532290a..55cb9770c64 100644 --- a/lib/dns/dispatch.c +++ b/lib/dns/dispatch.c @@ -56,15 +56,6 @@ */ size_t dns_dispatch_tcppipelining = 256; -/* - * Idle timeout (in milliseconds) for a reused outgoing TCP connection. While a - * dispatch has no outstanding responses we keep a read pending so a - * peer-initiated close is noticed promptly; this bounds how long such an idle - * connection is kept open for reuse. Can be overridden via - * 'named -T tcpidletimeout=N'. - */ -unsigned int dns_dispatch_tcp_idle_timeout = 5000; - typedef ISC_LIST(dns_dispentry_t) dns_displist_t; struct dns_dispatchmgr { @@ -75,6 +66,8 @@ struct dns_dispatchmgr { dns_acl_t *blackhole; isc_stats_t *stats; + unsigned int reuse_timeout; + uint32_t nloops; struct cds_lfht **tcps; @@ -770,7 +763,7 @@ tcp_recv_processall(dns_displist_t *resps, isc_region_t *region) { static void tcp_startrecv_idle(dns_dispatch_t *disp) { - REQUIRE(dns_dispatch_tcp_idle_timeout > 0); + REQUIRE(disp->mgr->reuse_timeout > 0); /* * No outstanding responses, but the connection is still up and @@ -780,7 +773,7 @@ tcp_startrecv_idle(dns_dispatch_t *disp) { * out again as a dead reused connection. */ isc_nmhandle_cleartimeout(disp->handle); - isc_nmhandle_settimeout(disp->handle, dns_dispatch_tcp_idle_timeout); + isc_nmhandle_settimeout(disp->handle, disp->mgr->reuse_timeout); if (!disp->reading) { dispatch_log(disp, ISC_LOG_DEBUG(90), "keeping idle read on %p", disp->handle); @@ -947,7 +940,7 @@ tcp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region, */ if (ISC_LIST_EMPTY(disp->active) && disp->state == DNS_DISPATCHSTATE_CONNECTED && - dns_dispatch_tcp_idle_timeout > 0) + disp->mgr->reuse_timeout > 0) { tcp_startrecv_idle(disp); } @@ -1098,6 +1091,13 @@ dns_dispatchmgr_setavailports(dns_dispatchmgr_t *mgr, isc_portset_t *v4portset, return setavailports(mgr, v4portset, v6portset); } +void +dns_dispatchmgr_setreusetimeout(dns_dispatchmgr_t *mgr, + unsigned int reuse_timeout) { + REQUIRE(VALID_DISPATCHMGR(mgr)); + mgr->reuse_timeout = reuse_timeout; +} + static void dispatchmgr_destroy(dns_dispatchmgr_t *mgr) { REQUIRE(VALID_DISPATCHMGR(mgr)); @@ -1799,7 +1799,7 @@ tcp_dispentry_cancel(dns_dispentry_t *resp, isc_result_t result) { * dispatch is already shutting down, leave it alone so * it can be torn down. */ - if (dns_dispatch_tcp_idle_timeout > 0) { + if (disp->mgr->reuse_timeout > 0) { tcp_startrecv_idle(disp); } else if (disp->reading) { dispentry_log(resp, ISC_LOG_DEBUG(90), diff --git a/lib/dns/include/dns/dispatch.h b/lib/dns/include/dns/dispatch.h index 1ee127115b3..5f2432dd10d 100644 --- a/lib/dns/include/dns/dispatch.h +++ b/lib/dns/include/dns/dispatch.h @@ -151,6 +151,15 @@ dns_dispatchmgr_setavailports(dns_dispatchmgr_t *mgr, isc_portset_t *v4portset, *\li v6portset is NULL or a valid port set */ +void +dns_dispatchmgr_setreusetimeout(dns_dispatchmgr_t *mgr, unsigned int timeout); +/*%< + * Sets the idle timeout (in milliseconds) for a reused outgoing TCP connection. + * While a dispatch has no outstanding responses we keep a read pending so a + * peer-initiated close is noticed promptly; this bounds how long such an idle + * connection is kept open for reuse. + */ + void dns_dispatchmgr_setstats(dns_dispatchmgr_t *mgr, isc_stats_t *stats); /*%< diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index ea3b18aae67..30c3a7857e1 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -1591,6 +1591,7 @@ static cfg_clausedef_t options_clauses[] = { { "tcp-listen-queue", &cfg_type_uint32, 0, NULL }, { "tcp-primaries-timeout", &cfg_type_uint32, 0, NULL }, { "tcp-receive-buffer", &cfg_type_uint32, 0, NULL }, + { "tcp-reuse-timeout", &cfg_type_uint32, 0, NULL }, { "tcp-send-buffer", &cfg_type_uint32, 0, NULL }, { "tkey-dhkey", NULL, CFG_CLAUSEFLAG_ANCIENT, NULL }, { "tkey-domain", &cfg_type_qstring, CFG_CLAUSEFLAG_ANCIENT, NULL }, diff --git a/tests/dns/dispatch_test.c b/tests/dns/dispatch_test.c index ce4eee28508..91e7bb5e1ed 100644 --- a/tests/dns/dispatch_test.c +++ b/tests/dns/dispatch_test.c @@ -1012,6 +1012,8 @@ ISC_LOOP_TEST_IMPL(dispatch_tcp_reuse_after_close) { result = dns_dispatchmgr_create(isc_g_mctx, &test->dispatchmgr); assert_int_equal(result, ISC_R_SUCCESS); + dns_dispatchmgr_setreusetimeout(test->dispatchmgr, 5000); + result = dns_dispatch_createtcp( test->dispatchmgr, &tcp_connect_addr, &tcp_server_addr, NULL, DNS_DISPATCHTYPE_RESOLVER, 0, &test->dispatch);