

























Hi Simon, all, we have run into a case where dnsmasq returns an authoritative local record together with a cached upstream record for the same name, and additionally forwards the query upstream even though it already has a local answer.
It happens when an upstream answer for a name is cached before a local record for that same name comes into existence, which is easy to hit with a hostsdir: adding or modifying a hosts file there triggers an inotify reload of just that
file, without flushing the rest of the cache. The previously cached upstreamrecord stays around (and, with --use-stale-cache, lingers as a stale entry once its TTL passes). On the next query, answer_request() walks the whole cache chain
for the name and adds both records to the reply; the expired upstream record also sets *stale, which forwards the query to refresh it.A restart clears the cache and the symptom disappears until the name is resolved
upstream again, which is what makes it look intermittent. Minimal reproducer (two loopback instances, no real network): # upstream, authoritative for repro.test with a short TTL dnsmasq --port=5392 --listen-address=127.0.0.1 --bind-interfaces \ --no-resolv --no-hosts --keep-in-foreground \ --address=/repro.test/192.0.2.50 --local-ttl=2 & mkdir -p /tmp/hd # under test dnsmasq --port=5391 --listen-address=127.0.0.1 --bind-interfaces \ --no-resolv --no-hosts --keep-in-foreground \ --server=127.0.0.1#5392 --hostsdir=/tmp/hd \ --use-stale-cache=3600 --log-queries=extra &dig +short @127.0.0.1 -p 5391 repro.test A # prime cache -> 192.0.2.50
echo "192.168.0.99 repro.test" > /tmp/hd/custom.listsleep 4 # let the upstream entry go stale
dig +noall +answer @127.0.0.1 -p 5391 repro.test A Before the patch the final answer contains both 192.168.0.99 (local) and192.0.2.50 (stale upstream), and the log shows the query being forwarded. After
the patch only 192.168.0.99 is returned, with no forward. The fix relies on cache_find_by_name() returning all local(F_HOSTS/F_DHCP/F_CONFIG) records for a name ahead of any cached records: once a local record has answered, stop at the first non-local record so it is neither added to the reply nor allowed to set *stale. Multiple local records, and the
normal case of multiple cached upstream records with no local record, are unaffected. Patch attached. Warm regards, Dominik
From 4b902310c30b81e6cb0652d5c264f40cb260fb81 Mon Sep 17 00:00:00 2001 From: Dominik Derigs <[email protected]> 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
_______________________________________________ Dnsmasq-discuss mailing list [email protected] https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。