


























Hi Simon,we've found a bug where locally-configured hostnames (from /etc/hosts, DHCP leases, or host-record) can return NXDOMAIN to clients instead of the expected local answer. The attached patch fixes this.
The problem has two parts:1) When a client queries a local hostname for a record type that has no local answer (e.g. AAAA when only an A record exists in /etc/hosts), answer_request() returns 0 and the query is forwarded upstream. Since the domain only exists locally, upstream returns NXDOMAIN. The correct response would be NODATA -- the domain exists, it just doesn't have a record of the requested type.
This is particularly visible with modern Linux resolvers like systemd-resolved as they routinely send AAAA and A queries independently at the same time. Most often, modern versions also issue HTTPS queries. The NXDOMAIN response to the AAAA/HTTPS query can cause the client to treat the entire domain as non-existent, even though the A answer comes back correctly.
2) There is already a safety net in process_reply() that converts upstream NXDOMAIN to NODATA for locally-known domains. However, this conversion is inside the !bogusanswer gate (line 793 on current master), so it is skipped when DNSSEC validation fails. For local hostnames under publicly-signed domains, the upstream NXDOMAIN proof can fail validation, bypassing the safety net.
The fix:- In answer_request(): before the final `return 0`, check whether the domain has any local record with F_HOSTS/F_DHCP/F_CONFIG. If so, return NODATA instead of forwarding. This prevents the upstream query entirely.
- In process_reply(): move the NXDOMAIN-to-NODATA conversion for locally-known domains before the bogusanswer check, so it applies regardless of DNSSEC validation status. Local host records are authoritative for domain existence.
Originally reported and investigated by me in https://github.com/pi-hole/FTL/issues/2841
Cheers, Dominik
From bb1f182beeb5ca5c8ecc4638d302cb6ed183acc8 Mon Sep 17 00:00:00 2001 From: Dominik <[email protected]> Date: Sun, 12 Apr 2026 13:17:53 +0200 Subject: [PATCH] Fix local host records being overridden by upstream NXDOMAIN When a locally-configured hostname (from /etc/hosts, DHCP, or host-record) is queried for a record type with no local answer (e.g. AAAA when only A exists), the query is forwarded upstream. If the domain only exists locally, upstream returns NXDOMAIN, which incorrectly overrides the local host record's existence. Fix in two places: 1. answer_request(): When no answer is found for the requested type but the domain has local host/DHCP/config records, return NODATA instead of forwarding. The domain exists locally, just not for the requested type. 2. process_reply(): Move the NXDOMAIN-to-NODATA conversion for locally-known domains before the DNSSEC bogus-answer gate. Local host records are authoritative for domain existence regardless of upstream DNSSEC validation status. --- src/forward.c | 24 +++++++++++++----------- src/rfc1035.c | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/forward.c b/src/forward.c index 1a7c586..501d8e4 100644 --- a/src/forward.c +++ b/src/forward.c @@ -788,21 +788,23 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server return resize_packet(header, n, pheader, plen); } + /* Convert NXDOMAIN to NODATA for locally-known domains. This must happen + unconditionally, even for DNSSEC BOGUS answers, since local host records + are authoritative for domain existence. */ + if (!(header->hb3 & HB3_TC) && rcode == NXDOMAIN && + extract_name(header, n, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0) && + (check_for_local_domain(daemon->namebuff, now) || lookup_domain(daemon->namebuff, F_CONFIG, NULL, NULL))) + { + header->hb3 |= HB3_AA; + SET_RCODE(header, NOERROR); + rcode = NOERROR; + cache_secure = 0; + } + if (header->hb3 & HB3_TC) log_query(F_UPSTREAM, NULL, NULL, "truncated", 0); else if (!bogusanswer || (header->hb4 & HB4_CD)) { - if (rcode == NXDOMAIN && extract_name(header, n, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0) && - (check_for_local_domain(daemon->namebuff, now) || lookup_domain(daemon->namebuff, F_CONFIG, NULL, NULL))) - { - /* if we forwarded a query for a locally known name (because it was for - an unknown type) and the answer is NXDOMAIN, convert that to NODATA, - since we know that the domain exists, even if upstream doesn't */ - header->hb3 |= HB3_AA; - SET_RCODE(header, NOERROR); - cache_secure = 0; - } - if (daemon->doctors && do_doctor(header, n, daemon->namebuff)) cache_secure = 0; diff --git a/src/rfc1035.c b/src/rfc1035.c index 0e31b83..4714d3f 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -2302,6 +2302,27 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } } + /* If we couldn't answer (e.g. AAAA query with only A host record, or an + unsupported RR type) but the domain has local records from /etc/hosts + or DHCP, return NODATA instead of forwarding upstream. This prevents + upstream NXDOMAIN responses from overriding local domain existence. */ + if (!ans) + { + struct crec *local; + + for (local = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME); + local; + local = cache_find_by_name(local, name, now, F_IPV4 | F_IPV6 | F_CNAME)) + if (local->flags & (F_HOSTS | F_DHCP | F_CONFIG)) + { + ans = 1; + sec_data = 0; + auth = 0; + log_query(F_NEG | F_CONFIG | (qtype == T_A ? F_IPV4 : F_IPV6), name, NULL, NULL, 0); + break; + } + } + if (!ans) return 0; /* failed to answer a question */ -- 2.43.0
_______________________________________________ Dnsmasq-discuss mailing list [email protected] https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。