--- /dev/null
+From 8ce27433f8b2e17c557cb55e4f16941d309deeac Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Fri, 17 Jan 2025 17:49:29 +0000
+Subject: [PATCH] Handle DS queries to auth zones.
+Origin: upstream, v2.91test8
+
+When dnsmasq is configured to act as an authoritative server and has
+an authoritative zone configured, and recieves a query for
+that zone _as_forwarder_ it answers the query directly rather
+than forwarding it. This doesn't affect the answer, but it
+saves dnsmasq forwarding the query to the recusor upstream,
+whch then bounces it back to dnsmasq in auth mode. The
+exception should be when the query is for the root of zone, for a DS
+RR. The answer to that has to come from the parent, via the
+recursor, and will typically be a proof-of-nonexistence since
+dnsmasq doesn't support signed zones. This patch suppresses
+local answers and forces forwarding to the upstream recursor
+for such queries. It stops breakage when a DNSSEC validating
+client makes queries to dnsmasq acting as forwarder for a zone
+for which it is authoritative.
+
+[ukleinek: drop changes to CHANGELOG to prevent conflicts]
+---
+ src/forward.c | 52 +++++++++++++++++++++++++++++++++++++--------------
+ 1 file changed, 38 insertions(+), 14 deletions(-)
+
+--- a/src/forward.c
++++ b/src/forward.c
+@@ -1744,15 +1744,27 @@ void receive_query(struct listener *list
+ #endif
+
+ #ifdef HAVE_AUTH
+- /* find queries for zones we're authoritative for, and answer them directly */
++ /* Find queries for zones we're authoritative for, and answer them directly.
++ The exception to this is DS queries for the zone route. They
++ have to come from the parent zone. Since dnsmasq's auth server
++ can't do DNSSEC, the zone will be unsigned, and anything using
++ dnsmasq as a forwarder and doing validation will be expecting to
++ see the proof of non-existence from the parent. */
+ if (!auth_dns && !option_bool(OPT_LOCALISE))
+ for (zone = daemon->auth_zones; zone; zone = zone->next)
+- if (in_zone(zone, daemon->namebuff, NULL))
+- {
+- auth_dns = 1;
+- local_auth = 1;
+- break;
+- }
++ {
++ char *cut;
++
++ if (in_zone(zone, daemon->namebuff, &cut))
++ {
++ if (type != T_DS || cut)
++ {
++ auth_dns = 1;
++ local_auth = 1;
++ }
++ break;
++ }
++ }
+ #endif
+
+ #ifdef HAVE_LOOP
+@@ -2268,15 +2280,27 @@ unsigned char *tcp_request(int confd, ti
+ &peer_addr, auth_dns ? "auth" : "query", qtype);
+
+ #ifdef HAVE_AUTH
+- /* find queries for zones we're authoritative for, and answer them directly */
++ /* Find queries for zones we're authoritative for, and answer them directly.
++ The exception to this is DS queries for the zone route. They
++ have to come from the parent zone. Since dnsmasq's auth server
++ can't do DNSSEC, the zone will be unsigned, and anything using
++ dnsmasq as a forwarder and doing validation will be expecting to
++ see the proof of non-existence from the parent. */
+ if (!auth_dns && !option_bool(OPT_LOCALISE))
+ for (zone = daemon->auth_zones; zone; zone = zone->next)
+- if (in_zone(zone, daemon->namebuff, NULL))
+- {
+- auth_dns = 1;
+- local_auth = 1;
+- break;
+- }
++ {
++ char *cut;
++
++ if (in_zone(zone, daemon->namebuff, &cut))
++ {
++ if (qtype != T_DS || cut)
++ {
++ auth_dns = 1;
++ local_auth = 1;
++ }
++ break;
++ }
++ }
+ #endif
+ }
+ }