Compare commits

...
7 Commits
Author SHA1 Message Date
Simon Kelley b72ecb3a59 Fix log message fields in wrong order in some auth replies. 2025-01-18 23:56:23 +00:00
Simon Kelley c221030f89 Rename cache_validated() to cache_not_validated().
Let's give the poor programmers a chance.
2025-01-18 23:26:06 +00:00
Simon Kelley 5bbea085d0 Fix subtle bug in arbitrary-RR caching.
If the client asks for DNSSEC RRs via the do bit, and
we have an answer cached, we can only return the cached
answer if the RR was not validated. This is because
we don't the extra info (RRSIGS, NSECs) for a complete
validated answer. In that case we have to forward again.

This bug was that the "is the cache entry validated" test was
in an outer loop rather than an inner one. A cache hit on
a different RRtype that wasn't validated would satify the
condition to use the cache, even if the cache entry for
the required RRtype didn't. The only time when there can be a mix
of validated and non validated cache entries for the same domain
is when most are not validated, but one is a negative cache for
a DS record.

This bug took a long time to find.
2025-01-18 23:15:53 +00:00
Simon Kelley 622cf03ab9 Fix fubar that could return unsigned NODATA response when do bit set. 2025-01-18 22:16:29 +00:00
Simon Kelley 8ce27433f8 Handle DS queries to auth zones.
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.
2025-01-18 08:57:14 +00:00
Simon Kelley 5d894620b4 Extend build fingerprinting to include CFLAGS.
If the value of CFLAGS is changed between builds, the makefile
will rebuid, in the same way as for COPTS.
2025-01-17 16:48:08 +00:00
Simon Kelley 71766c0c35 Tweak handling of duplicate DNS answers via UDP.
If we get a duplicate answer for a query via UDP which we have
either already received and started DNSSEC validation, or was
truncated and we've passed to TCP, then just ignore it.

The code was already in place, but had evolved wonky and
only worked for error replies which would otherwise prompt
a retransmit.
2025-01-13 20:30:37 +00:00
6 changed files with 79 additions and 36 deletions

No files matched your search

+15
View File
@@ -82,6 +82,21 @@ version 2.91
to queries which arrive a dnsmasq already carrying an EDNS client
subnet.
Handle DS queries to auth zones. 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.
version 2.90
Fix reversion in --rev-server introduced in 2.88 which
+2 -2
View File
@@ -71,8 +71,8 @@ nft_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_NFTSET $(PKG_CONFIG
nft_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_NFTSET $(PKG_CONFIG) --libs libnftables`
version = -DVERSION='\"`$(top)/bld/get-version $(top)`\"'
sum?=$(shell $(CC) -DDNSMASQ_COMPILE_OPTS $(COPTS) -E $(top)/$(SRC)/dnsmasq.h | ( md5sum 2>/dev/null || md5 ) | cut -f 1 -d ' ')
sum!=$(CC) -DDNSMASQ_COMPILE_OPTS $(COPTS) -E $(top)/$(SRC)/dnsmasq.h | ( md5sum 2>/dev/null || md5 ) | cut -f 1 -d ' '
sum?=$(shell echo $(CC) -DDNSMASQ_COMPILE_FLAGS="$(CFLAGS)" -DDNSMASQ_COMPILE_OPTS $(COPTS) -E $(top)/$(SRC)/dnsmasq.h | ( md5sum 2>/dev/null || md5 ) | cut -f 1 -d ' ')
sum!=echo $(CC) -DDNSMASQ_COMPILE_FLAGS="$(CFLAGS)" -DDNSMASQ_COMPILE_OPTS $(COPTS) -E $(top)/$(SRC)/dnsmasq.h | ( md5sum 2>/dev/null || md5 ) | cut -f 1 -d ' '
copts_conf = .copts_$(sum)
objs = cache.o rfc1035.o util.o option.o forward.o network.o \
+1 -1
View File
@@ -513,7 +513,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
nxdomain = 0;
if ((crecp->flags & flag) && (local_query || filter_zone(zone, flag, &(crecp->addr))))
{
log_query(crecp->flags, name, &crecp->addr, record_source(crecp->uid), 0);
log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr, record_source(crecp->uid), 0);
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
daemon->auth_ttl, NULL, qtype, C_IN,
qtype == T_A ? "4" : "6", &crecp->addr))
+5
View File
@@ -362,6 +362,11 @@ HAVE_SOCKADDR_SA_LEN
#define HAVE_INOTIFY
#endif
/* This never compiles code, it's only used by the makefile to fingerprint builds. */
#ifdef DNSMASQ_COMPILE_FLAGS
static char *compile_flags = DNSMASQ_COMPILE_FLAGS;
#endif
/* Define a string indicating which options are in use.
DNSMASQ_COMPILE_OPTS is only defined in dnsmasq.c */
+45 -23
View File
@@ -822,10 +822,6 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
daemon->log_display_id = forward->frec_src.log_id;
/* We've had a reply already, which we're validating. Ignore this duplicate */
if (forward->blocking_query || (forward->flags & FREC_GONE_TO_TCP))
return;
/* Find the original query that started it all.... */
for (orig = forward; orig->dependent; orig = orig->dependent);
@@ -1152,18 +1148,20 @@ void reply_query(int fd, time_t now)
if (daemon->ignore_addr && RCODE(header) == NOERROR &&
check_for_ignored_address(header, n))
return;
if ((RCODE(header) == REFUSED || RCODE(header) == SERVFAIL) && forward->forwardall == 0)
/* for broken servers, attempt to send to another one. */
{
#ifdef HAVE_DNSSEC
/* The query MAY have got a good answer, and be awaiting
the results of further queries, in which case
The Stash contains something else and we don't need to retry anyway. */
the stash contains something else and we don't need to retry anyway.
We may also have already got a truncated reply, and be in the process
of doing the query by TCP so can ignore further, probably truncated, UDP answers. */
if (forward->blocking_query || (forward->flags & FREC_GONE_TO_TCP))
return;
#endif
if ((RCODE(header) == REFUSED || RCODE(header) == SERVFAIL) && forward->forwardall == 0)
/* for broken servers, attempt to send to another one. */
{
/* Get the saved query back. */
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
@@ -1693,15 +1691,27 @@ void receive_query(struct listener *listen, time_t now)
&source_addr, auth_dns ? "auth" : "query", type);
#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
@@ -2327,15 +2337,27 @@ unsigned char *tcp_request(int confd, time_t now,
&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
norebind = domain_no_rebind(daemon->namebuff);
+11 -10
View File
@@ -1559,7 +1559,7 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now)
return daemon->max_ttl;
}
static int cache_validated(const struct crec *crecp)
static int cache_not_validated(const struct crec *crecp)
{
return (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK));
}
@@ -1660,7 +1660,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
/* 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_validated(crecp))))
(rd_bit && (!do_bit || cache_not_validated(crecp))))
{
if (crecp->flags & F_CONFIG || qtype == T_CNAME)
ans = 1;
@@ -1819,7 +1819,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
the zone is unsigned, which implies that we're doing
validation. */
if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
(rd_bit && (!do_bit || cache_validated(crecp)) ))
(rd_bit && (!do_bit || cache_not_validated(crecp)) ))
{
do
{
@@ -1975,7 +1975,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
/* 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_validated(crecp)) ))
(rd_bit && (!do_bit || cache_not_validated(crecp)) ))
do
{
int stale_flag = 0;
@@ -2157,19 +2157,19 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (!ans)
{
if ((crecp = cache_find_by_name(NULL, name, now, F_RR | F_NXDOMAIN)) &&
rd_bit && (!do_bit || cache_validated(crecp)))
if ((crecp = cache_find_by_name(NULL, name, now, F_RR | F_NXDOMAIN)) && rd_bit)
do
{
int flags = crecp->flags;
unsigned short rrtype;
if (flags & F_KEYTAG)
rrtype = crecp->addr.rrblock.rrtype;
else
rrtype = crecp->addr.rrdata.rrtype;
if ((flags & F_NXDOMAIN) || rrtype == qtype)
if (((flags & F_NXDOMAIN) || rrtype == qtype) &&
(!do_bit || cache_not_validated(crecp)))
{
char *rrdata = NULL;
unsigned short rrlen = 0;
@@ -2232,14 +2232,15 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
}
if (qtype != T_ANY && !ans && rr_on_list(daemon->filter_rr, qtype))
if (qtype != T_ANY && !ans && rr_on_list(daemon->filter_rr, qtype) && !do_bit)
{
/* We don't have a cached answer and when we get an answer from upstream we're going to
filter it anyway. If we have a cached answer for the domain for another RRtype then
that may be enough to tell us if the answer should be NODATA and save the round trip.
Cached NXDOMAIN has already been handled, so here we look for any record for the domain,
since its existence allows us to return a NODATA answer. Note that we never set the AD flag,
since we didn't authenticate the record. */
since we didn't authenticate the record; this doesn't work if we want auth data, so
don't use this shortcut in that case. */
if (cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_RR | F_CNAME))
{