From 4b902310c30b81e6cb0652d5c264f40cb260fb81 Mon Sep 17 00:00:00 2001 From: Dominik Derigs Date: Fri, 19 Jun 2026 20:32:41 +0200 Subject: [PATCH] Don't mix local (hosts/DHCP/config) records with cached upstream data When a name has an authoritative local record (from /etc/hosts, a hostsdir file, DHCP or a config "address") and a record for the same name is also present in the cache from earlier upstream resolution, answer_request() walked the entire cache chain for that name and added both to the reply. With --use-stale-cache the expired upstream record additionally set *stale, triggering a needless upstream refresh even though an authoritative local answer was available. This is reachable whenever an upstream answer is cached before the local record exists, e.g. a hostsdir file is created or modified at run time: the inotify reload re-reads that hosts file but does not flush the rest of the cache. The client then receives both the local address and a stale upstream address, and the query is forwarded. Flushing the cache (a restart) hides it until the name is resolved upstream again. cache_find_by_name() returns all local (F_HOSTS/F_DHCP/F_CONFIG) records for a name ahead of any cached records. So once a local record has answered, stop at the first non-local record: do not add it to the reply and do not let it set *stale. --- src/rfc1035.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/rfc1035.c b/src/rfc1035.c index 16df1b1..5534446 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -2028,6 +2028,12 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, crecp = save; } + /* An authoritative local record (/etc/hosts, DHCP or config) must + not be mixed in a reply with cached upstream records for the same + name. cache_find_by_name() returns all local records ahead of any + cached records, so remember whether we started from a local one. */ + int local_auth = (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) != 0; + /* If the client asked for DNSSEC don't use cached data. */ if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || (rd_bit && (!do_bit || cache_not_validated(crecp)) )) @@ -2035,6 +2041,12 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, { int stale_flag = 0; + /* Once a local record has answered, stop at the first cached + upstream record: don't add it to the reply and don't let it + trigger a stale refresh (which would forward the query). */ + if (local_auth && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) + break; + if (crec_isstale(crecp, now)) { if (stale) -- 2.43.0