[Dnsmasq-discuss] [PATCH 2/2] Drop cached answers when a hosts file read at runtime claims the name

Dominik Derigs git at dl6er.de
Sun Oct 4 11:44:22 UTC 2026


From: DL6ER <dl6er at dl6er.de>

`--hostsdir` exists to add records without a restart, and `inotify_check()`
already calls `cache_remove_uid()` so a reloaded file drops the records it
supplied last time. Nothing drops what the *cache* holds for those names,
though, and `add_hosts_entry()` links the new entry in through `cache_hash()`
without going near `cache_scan_free()`, so no conflict scan happens on this
path at all.

A name resolved from upstream before its record appeared therefore keeps that
answer, and the two are then served together:

    foo.example.com   10.0.0.5       4FRI  H
    foo.example.com   198.51.100.9   4F

which a client load-balances across until the cached copy expires. Half the
connections go to the public address for a name the administrator just
defined locally.

Remove the entries which did not come from local configuration before hashing
the new record in. Only an incremental read can hit this, as bulk reads happen
before any query is answered, and `rhash` already distinguishes the two. The
scan is per name and confined to one hash bucket, so it costs nothing on the
startup path it is skipped on.

Signed-off-by: DL6ER <dl6er at dl6er.de>
---
 src/cache.c | 32 +++++++++++++++++++++++++++++++-
 1 file changed, 31 insertions(+), 1 deletion(-)

diff --git a/src/cache.c b/src/cache.c
index cbcf575..1815826 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -1364,6 +1364,32 @@ struct crec *cache_find_by_addr(struct crec *crecp, union all_addr *addr,
   return NULL;
 }
 
+/* Remove any entry for this name which did not come from local configuration.
+   A hosts file read at runtime can give a name a local record whilst an answer
+   for it is in the cache: that answer would otherwise be served beside the new
+   record, or instead of it for the types the record does not cover, until its
+   TTL runs out. Expiry is not consulted, the entry goes either way. */
+static void cache_flush_name(char *name)
+{
+  struct crec *crecp, *tmp, **up;
+
+  for (up = hash_bucket(name), crecp = *up; crecp; crecp = tmp)
+    {
+      tmp = crecp->hash_next;
+
+      if ((crecp->flags & F_FORWARD) &&
+	  !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) &&
+	  hostname_isequal(cache_get_name(crecp), name))
+	{
+	  *up = tmp;
+	  cache_unlink(crecp);
+	  cache_free(crecp);
+	}
+      else
+	up = &crecp->hash_next;
+    }
+}
+
 static void add_hosts_entry(struct crec *cache, union all_addr *addr, int addrlen, 
 			    unsigned int index, struct crec **rhash, int hashsz)
 {
@@ -1425,7 +1451,11 @@ static void add_hosts_entry(struct crec *cache, union all_addr *addr, int addrle
     }
 
   cache->uid = index;
-  memcpy(&cache->addr, addr, addrlen);  
+  memcpy(&cache->addr, addr, addrlen);
+  /* Bulk reads happen before any query, so only an incremental read can find
+     a cached answer for the name it is about to make local. */
+  if (!rhash)
+    cache_flush_name(cache->name.namep);
   cache_hash(cache);
   make_non_terminals(cache);
 }
-- 
2.43.0




More information about the Dnsmasq-discuss mailing list