Commit 1197956346 for bind

commit 1197956346ead5a7a0e509c7253e07102afab5d1
Author: OndÅ™ej Surý <ondrej@isc.org>
Date:   Fri Sep 4 11:21:26 2026 +0200

    Recover interface scanning after route socket overflow

    A burst of address changes can overflow the route socket, after which
    the kernel reports ENOBUFS on the next read. That was treated as a
    fatal error: automatic interface scanning was terminated, and on
    Linux, where the periodic rescan is disabled, named never learned of
    another address change until it was reconfigured.

    Treat the overflow as a signal instead. The read stays armed, and
    since the kernel keeps dropping messages until the queue has drained,
    every message read while recovering keeps a rescan pending; the first
    loop iteration that reads nothing runs the final scan. All rescans now
    go through a zero-delay timer, so the messages read in one iteration
    cost one scan rather than one each.

    The periodic rescan timer moves from named into the interface manager,
    which now owns both uses of it. The isc_interval_tonanosecs() macro,
    which expanded to a function that no longer exists, is fixed on the
    way because the timer code needs it.

diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h
index c1a0a4952c..643b43baa6 100644
--- a/bin/named/include/named/server.h
+++ b/bin/named/include/named/server.h
@@ -66,13 +66,10 @@ struct named_server {
 	dns_keystorelist_t keystorelist;
 	ns_interfacemgr_t *interfacemgr;

-	isc_timer_t *interface_timer;
 	isc_timer_t *heartbeat_timer;
 	isc_timer_t *pps_timer;
 	isc_timer_t *tat_timer;

-	uint32_t interface_interval;
-
 	atomic_int reload_status;

 	bool flushonshutdown;
diff --git a/bin/named/server.c b/bin/named/server.c
index 90b75696fc..fa800705b6 100644
--- a/bin/named/server.c
+++ b/bin/named/server.c
@@ -6522,18 +6522,6 @@ configure_server_quota(const cfg_obj_t **maps, enum cfg_clause name,
 	isc_quota_max(quota, cfg_obj_asuint32(obj));
 }

-/*
- * This event callback is invoked to do periodic network interface
- * scanning.
- */
-
-static void
-interface_timer_tick(void *arg) {
-	named_server_t *server = (named_server_t *)arg;
-
-	(void)ns_interfacemgr_scan(server->interfacemgr, false, false);
-}
-
 typedef struct {
 	isc_mem_t *mctx;
 	isc_loop_t *loop;
@@ -8412,7 +8400,6 @@ apply_configuration(cfg_obj_t *effectiveconfig, cfg_obj_t *userconfig,
 	result = named_config_get(maps, CFG_CLAUSE_INTERFACE_INTERVAL, &obj);
 	INSIST(result == ISC_R_SUCCESS);
 	interface_interval = cfg_obj_asduration(obj);
-	server->interface_interval = interface_interval;

 	/*
 	 * Enable automatic interface scans.
@@ -8424,33 +8411,14 @@ apply_configuration(cfg_obj_t *effectiveconfig, cfg_obj_t *userconfig,
 	server->sctx->interface_auto = cfg_obj_asboolean(obj);

 	if (server->sctx->interface_auto) {
-		if (ns_interfacemgr_dynamic_updates_are_reliable() &&
-		    server->interface_interval != 0)
-		{
-			/*
-			 * In some cases the user might expect a certain
-			 * behaviour from the rescan timer, let's try to deduce
-			 * that from the configuration options.
-			 */
-			isc_log_write(
-				NAMED_LOGCATEGORY_GENERAL,
-				NAMED_LOGMODULE_SERVER, ISC_LOG_INFO,
-				"Disabling periodic interface re-scans timer");
-			server->interface_interval = 0;
-		}
-
 		ns_interfacemgr_routeconnect(server->interfacemgr);
 	} else {
 		ns_interfacemgr_routedisconnect(server->interfacemgr);
 	}

-	if (server->interface_interval == 0) {
-		isc_timer_stop(server->interface_timer);
-	} else {
-		isc_interval_set(&interval, interface_interval, 0);
-		isc_timer_start(server->interface_timer, isc_timertype_ticker,
-				&interval);
-	}
+	ns_interfacemgr_routetimer(
+		server->interfacemgr,
+		&(isc_interval_t){ .seconds = interface_interval });

 	isc_interval_set(&interval, 1200, 0);
 	isc_timer_start(server->pps_timer, isc_timertype_ticker, &interval);
@@ -9322,9 +9290,6 @@ run_server(void *arg) {
 					  &server->interfacemgr),
 		   "creating interface manager");

-	isc_timer_create(isc_loop_main(), interface_timer_tick, server,
-			 &server->interface_timer);
-
 	isc_timer_create(isc_loop_main(), tat_timer_tick, server,
 			 &server->tat_timer);

@@ -9412,7 +9377,6 @@ shutdown_server(void *arg) {
 		isc_mem_put(server->mctx, nsc, sizeof(*nsc));
 	}

-	isc_timer_destroy(&server->interface_timer);
 	isc_timer_destroy(&server->pps_timer);
 	isc_timer_destroy(&server->tat_timer);

diff --git a/lib/isc/include/isc/time.h b/lib/isc/include/isc/time.h
index c6347dcf87..bc0a1af68f 100644
--- a/lib/isc/include/isc/time.h
+++ b/lib/isc/include/isc/time.h
@@ -95,7 +95,7 @@ constexpr unsigned int NS_PER_SEC = 1000 * 1000 * 1000;
  */

 #define isc_interval_fromnanosecs(ns) isc_time_fromnanosecs(ns)
-#define isc_interval_tonanosecs(i)    isc_time_tonanosecs(i)
+#define isc_interval_tonanosecs(i)    isc_nanosecs_fromtime(*(i))

 /***
  *** Absolute Times
diff --git a/lib/isc/netmgr/udp.c b/lib/isc/netmgr/udp.c
index 02402e640c..593c2114d2 100644
--- a/lib/isc/netmgr/udp.c
+++ b/lib/isc/netmgr/udp.c
@@ -558,7 +558,7 @@ isc__nm_udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
 	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)handle);
 	isc__nm_uvreq_t *req = NULL;
 	uint32_t maxudp;
-	isc_result_t result;
+	isc_result_t result = ISC_R_SUCCESS;
 	isc_sockaddr_t sockaddr, *sa = NULL;

 	REQUIRE(VALID_NMSOCK(sock));
@@ -594,11 +594,14 @@ isc__nm_udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
 		 */
 		goto free;
 	}
-
 	/*
-	 * - If there was a networking error.
+	 * - If there was a networking error, except when route socket
+	 *   receives ENOBUFS - in that case, we want to pass the error
+	 *   and continue reading.
 	 */
-	if (nrecv < 0) {
+	if (sock->route_sock && nrecv == UV_ENOBUFS) {
+		result = ISC_R_NORESOURCES;
+	} else if (nrecv < 0) {
 		isc__nm_failed_read_cb(sock, isc_uverr2result(nrecv), false);
 		goto free;
 	}
@@ -619,6 +622,17 @@ isc__nm_udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
 		goto free;
 	}

+	if (result != ISC_R_SUCCESS) {
+		/*
+		 * Route socket errors do not carry a datagram or a source
+		 * address, but are delivered using the normal read callback so
+		 * the caller can resume reading.
+		 */
+		req = isc__nm_get_read_req(sock, NULL);
+		req->uvbuf = (uv_buf_t){ 0 };
+		goto callback;
+	}
+
 	/*
 	 * End of the current (iteration) datagram stream, just free the buffer.
 	 * The callback with nrecv == 0 and addr == NULL is called for both
@@ -645,12 +659,13 @@ isc__nm_udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
 	req = isc__nm_get_read_req(sock, sa);

 	/*
-	 * The callback will be called synchronously, because result is
-	 * ISC_R_SUCCESS, so we are ok of passing the buf directly.
+	 * The callback will be called synchronously, so we are ok passing the
+	 * buffer directly.
 	 */
 	req->uvbuf.base = buf->base;
 	req->uvbuf.len = nrecv;

+callback:
 	sock->reading = false;

 	/*
@@ -666,7 +681,7 @@ isc__nm_udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,

 	REQUIRE(!sock->processing);
 	sock->processing = true;
-	isc__nm_readcb(sock, req, ISC_R_SUCCESS, false);
+	isc__nm_readcb(sock, req, result, false);
 	sock->processing = false;

 free:
diff --git a/lib/ns/include/ns/interfacemgr.h b/lib/ns/include/ns/interfacemgr.h
index bb428596e8..b87818ab02 100644
--- a/lib/ns/include/ns/interfacemgr.h
+++ b/lib/ns/include/ns/interfacemgr.h
@@ -50,6 +50,7 @@
 #include <isc/refcount.h>
 #include <isc/result.h>
 #include <isc/sockaddr.h>
+#include <isc/time.h>

 #include <dns/geoip.h>

@@ -198,14 +199,6 @@ ns_interfacemgr_getclientmgr(ns_interfacemgr_t *mgr);
  * (This cannot be run from outside a network manager thread.)
  */

-bool
-ns_interfacemgr_dynamic_updates_are_reliable(void);
-/*%<
- * Returns 'true' if periodic interface re-scans timer should be
- * disabled. That is the case on the platforms where kernel-based
- * mechanisms for tracking networking interface states is reliable enough.
- */
-
 void
 ns_interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr,
 		    const char *name, ns_interface_t **ifpret);
@@ -213,3 +206,7 @@ ns_interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr,
  * Create an interface 'name' associated with address 'addr'. If
  * 'name' is NULL then it is set to "default".
  */
+
+void
+ns_interfacemgr_routetimer(ns_interfacemgr_t *mgr,
+			   isc_interval_t    *interface_interval);
diff --git a/lib/ns/interfacemgr.c b/lib/ns/interfacemgr.c
index eda77e034e..ac14ac97b5 100644
--- a/lib/ns/interfacemgr.c
+++ b/lib/ns/interfacemgr.c
@@ -23,6 +23,7 @@
 #include <isc/random.h>
 #include <isc/string.h>
 #include <isc/tid.h>
+#include <isc/time.h>
 #include <isc/util.h>

 #include <dns/acl.h>
@@ -76,7 +77,11 @@ struct ns_interfacemgr {
 	int backlog;		     /*%< Listen queue size */
 	atomic_bool shuttingdown;    /*%< Interfacemgr shutting down */
 	ns_clientmgr_t **clientmgrs; /*%< Client managers */
-	isc_nmhandle_t *route;
+	isc_nmhandle_t *route_handle;
+	isc_timer_t *route_timer;
+	isc_interval_t interface_interval;
+	bool route_lost;
+	bool route_seen;
 };

 static void
@@ -85,6 +90,36 @@ purge_old_interfaces(ns_interfacemgr_t *mgr);
 static void
 clearlistenon(ns_interfacemgr_t *mgr);

+/*
+ * This event callback is invoked to do periodic network interface
+ * scanning.
+ */
+
+static void
+interface_timer_tick(void *arg) {
+	ns_interfacemgr_t *mgr = arg;
+
+	(void)ns_interfacemgr_scan(mgr, false, false);
+
+	if (mgr->route_seen) {
+		mgr->route_seen = false;
+		if (mgr->route_lost) {
+			isc_timer_start(mgr->route_timer, isc_timertype_once,
+					isc_interval_zero);
+			return;
+		}
+	}
+	if (mgr->route_lost) {
+		mgr->route_lost = false;
+	}
+
+	/* Re-arm the periodic timer as needed */
+	if (isc_interval_tonanosecs(&mgr->interface_interval) != 0) {
+		isc_timer_start(mgr->route_timer, isc_timertype_once,
+				&mgr->interface_interval);
+	}
+}
+
 #if defined(RTM_NEWADDR) && defined(RTM_DELADDR)
 static bool
 need_rescan(ns_interfacemgr_t *mgr, struct MSGHDR *rtm, size_t len) {
@@ -194,7 +229,7 @@ route_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
 	   void *arg) {
 	ns_interfacemgr_t *mgr = (ns_interfacemgr_t *)arg;
 	struct MSGHDR *rtm = NULL;
-	size_t rtmlen;
+	size_t rtmlen = 0;

 	isc_log_write(NS_LOGCATEGORY_NETWORK, NS_LOGMODULE_INTERFACEMGR,
 		      ISC_LOG_DEBUG(9), "route_recv: %s",
@@ -206,7 +241,37 @@ route_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,

 	switch (eresult) {
 	case ISC_R_SUCCESS:
+		rtm = (struct MSGHDR *)region->base;
+		rtmlen = region->length;
+
+#ifdef RTM_VERSION
+		if (rtm->rtm_version != RTM_VERSION) {
+			isc_log_write(
+				NS_LOGCATEGORY_NETWORK,
+				NS_LOGMODULE_INTERFACEMGR, ISC_LOG_ERROR,
+				"automatic interface rescanning disabled: "
+				"rtm->rtm_version mismatch (%u != %u) "
+				"recompile required",
+				rtm->rtm_version, RTM_VERSION);
+			isc_nmhandle_detach(&mgr->route_handle);
+			ns_interfacemgr_detach(&mgr);
+			return;
+		}
+#endif /* ifdef RTM_VERSION */
 		break;
+	case ISC_R_NORESOURCES:
+		if (mgr->sctx->interface_auto) {
+			/* There might have been route that we missed */
+			mgr->route_seen = true;
+			mgr->route_lost = true;
+			isc_log_write(NS_LOGCATEGORY_NETWORK,
+				      NS_LOGMODULE_INTERFACEMGR,
+				      ISC_LOG_WARNING,
+				      "automatic interface scanning missed "
+				      "messages: route socket overflowed");
+			break;
+		}
+		FALLTHROUGH;
 	default:
 		isc_log_write(NS_LOGCATEGORY_NETWORK, NS_LOGMODULE_INTERFACEMGR,
 			      ISC_LOG_ERROR,
@@ -220,31 +285,29 @@ route_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
 		return;
 	}

-	rtm = (struct MSGHDR *)region->base;
-	rtmlen = region->length;
-
-#ifdef RTM_VERSION
-	if (rtm->rtm_version != RTM_VERSION) {
-		isc_log_write(NS_LOGCATEGORY_NETWORK, NS_LOGMODULE_INTERFACEMGR,
-			      ISC_LOG_ERROR,
-			      "automatic interface rescanning disabled: "
-			      "rtm->rtm_version mismatch (%u != %u) "
-			      "recompile required",
-			      rtm->rtm_version, RTM_VERSION);
-		isc_nmhandle_detach(&mgr->route);
-		ns_interfacemgr_detach(&mgr);
-		return;
-	}
-#endif /* ifdef RTM_VERSION */
-
-	REQUIRE(mgr->route != NULL);
+	REQUIRE(mgr->route_handle != NULL);

 #if defined(RTM_NEWADDR) && defined(RTM_DELADDR)
-	if (need_rescan(mgr, rtm, rtmlen) && mgr->sctx->interface_auto) {
-		ns_interfacemgr_scan(mgr, false, false);
+	if (mgr->route_lost) {
+		/*
+		 * If we are recovering from the lost route, any route message
+		 * needs to trigger the interface re-scan.
+		 */
+		mgr->route_seen = true;
+	} else if (mgr->sctx->interface_auto && need_rescan(mgr, rtm, rtmlen)) {
+		mgr->route_seen = true;
 	}
 #endif /* if defined(RTM_NEWADDR) && defined(RTM_DELADDR) */

+	if (mgr->route_seen) {
+		/*
+		 * Fire up the timer if we have seen a new route,
+		 * this will restart the timer if already running.
+		 */
+		isc_timer_start(mgr->route_timer, isc_timertype_once,
+				isc_interval_zero);
+	}
+
 	isc_nm_read(handle, route_recv, mgr);
 	return;
 }
@@ -262,9 +325,9 @@ route_connected(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) {
 		return;
 	}

-	INSIST(mgr->route == NULL);
+	INSIST(mgr->route_handle == NULL);

-	isc_nmhandle_attach(handle, &mgr->route);
+	isc_nmhandle_attach(handle, &mgr->route_handle);
 	isc_nm_read(handle, route_recv, mgr);
 }

@@ -324,6 +387,9 @@ ns_interfacemgr_create(isc_mem_t *mctx, ns_server_t *sctx,
 		RUNTIME_CHECK(result == ISC_R_SUCCESS);
 	}

+	isc_timer_create(isc_loop_main(), interface_timer_tick, mgr,
+			 &mgr->route_timer);
+
 	return ISC_R_SUCCESS;

 cleanup_lock:
@@ -338,7 +404,7 @@ ns_interfacemgr_routeconnect(ns_interfacemgr_t *mgr) {
 	REQUIRE(NS_INTERFACEMGR_VALID(mgr));
 	REQUIRE(isc_tid() == 0);

-	if (mgr->route != NULL) {
+	if (mgr->route_handle != NULL) {
 		return;
 	}

@@ -358,12 +424,12 @@ ns_interfacemgr_routedisconnect(ns_interfacemgr_t *mgr) {
 	REQUIRE(NS_INTERFACEMGR_VALID(mgr));
 	REQUIRE(isc_tid() == 0);

-	if (mgr->route == NULL) {
+	if (mgr->route_handle == NULL) {
 		return;
 	}

-	isc_nmhandle_close(mgr->route);
-	isc_nmhandle_detach(&mgr->route);
+	isc_nmhandle_close(mgr->route_handle);
+	isc_nmhandle_detach(&mgr->route_handle);
 	ns_interfacemgr_detach(&mgr);
 }

@@ -428,13 +494,15 @@ ns_interfacemgr_shutdown(ns_interfacemgr_t *mgr) {

 	purge_old_interfaces(mgr);

-	if (mgr->route != NULL) {
-		isc_nm_cancelread(mgr->route);
+	if (mgr->route_handle != NULL) {
+		isc_nm_cancelread(mgr->route_handle);
 	}

 	for (size_t i = 0; i < mgr->ncpus; i++) {
 		ns_clientmgr_shutdown(mgr->clientmgrs[i]);
 	}
+
+	isc_timer_destroy(&mgr->route_timer);
 }

 void
@@ -1358,16 +1426,34 @@ ns_interfacemgr_getclientmgr(ns_interfacemgr_t *mgr) {
 	return mgr->clientmgrs[tid];
 }

-bool
-ns_interfacemgr_dynamic_updates_are_reliable(void) {
+void
+ns_interfacemgr_routetimer(ns_interfacemgr_t *mgr,
+			   isc_interval_t *interface_interval) {
+	REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+	mgr->interface_interval = *interface_interval;
+
+	if (isc_interval_tonanosecs(&mgr->interface_interval) == 0) {
+		isc_timer_stop(mgr->route_timer);
+		return;
+	}
+
 #if defined(LINUX_NETLINK_AVAILABLE)
 	/*
 	 * Let's disable periodic interface rescans on Linux, as there a
 	 * reliable kernel-based mechanism for tracking interface state
 	 * changes is available.
 	 */
-	return true;
-#else
-	return false;
-#endif /* LINUX_NETLINK_AVAILABLE */
+	if (mgr->sctx->interface_auto) {
+		isc_log_write(NS_LOGCATEGORY_NETWORK, NS_LOGMODULE_INTERFACEMGR,
+			      ISC_LOG_INFO,
+			      "Disabling periodic interface re-scans timer");
+		mgr->interface_interval = *isc_interval_zero;
+		isc_timer_stop(mgr->route_timer);
+		return;
+	}
+#endif /*LINUX_NETLINK_AVAILABLE */
+
+	isc_timer_start(mgr->route_timer, isc_timertype_once,
+			&mgr->interface_interval);
 }