Compare commits

...
18 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
Simon Kelley da58455508 Tweak 7d915a0bb9
A downstream query may have gone to TCP, not just DNSSEC queries.
2025-01-13 11:03:30 +00:00
Simon Kelley b915c9a661 Attempt to keep running if a child process dies.
If a child process dies unexpectedly, log the error and
try and tidy up so the main process continues to run and
doesn't block awaiting the dead child.
2025-01-13 10:56:19 +00:00
Simon Kelley 424aaa0f9d Fix another 509afcd1d2 SNAFU 2025-01-13 10:32:55 +00:00
Andrew Sayers c72c895869 Improve "no upstream servers configured" when D-Bus is enabled
Print a specific INFO message instead of a generic WARNING message,
so users know what to do.

Starting dnsmasq without upstream servers indicates a problem by default,
but is perfectly normal with D-Bus enabled.  For example, NetworkManager
starts dnsmasq with no upstream servers, then immediately populates it
over D-Bus.
2025-01-12 22:32:32 +00:00
Simon Kelley b7156116c2 Fix SNAFU in 509afcd1d2 2025-01-12 22:28:12 +00:00
Simon Kelley 7d915a0bb9 Don't do retries over UDP when we've sent the query by TCP. 2025-01-12 22:02:05 +00:00
Simon Kelley 509afcd1d2 Refactor poll() loop.
Handling events on file descriptors can result in new file
descriptors being created or old ones being deleted. As such
the results of the last call to poll() become invalid in subtle
ways.

After handling each file descriptor in  check_dns_listeners()
return, to go around the poll() loop again and get valid data
for the new situation.

Thanks to Dominik Derigs for his indefatigable sleuthing of this one.
2025-01-12 21:36:09 +00:00
Simon Kelley 51343bd9a2 Treat replies with CD flag set the same for UDP and TCP code paths. 2025-01-12 16:25:07 +00:00
Simon Kelley b58276a73c Return EDE OTHER error when DNSSEC validation abandoned.
This distinguishes the case where we found a message was bogus
from cases where the process failed.
2025-01-12 16:00:09 +00:00
Matthias Andree f162d344c0 cache: Fix potential NULL deref in arcane situations. 2025-01-08 23:34:12 +00:00
Simon Kelley 0003db15cb Fix crash introduced in 6656790f24 2025-01-07 23:08:35 +00:00
8 changed files with 207 additions and 93 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 to queries which arrive a dnsmasq already carrying an EDNS client
subnet. 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 version 2.90
Fix reversion in --rev-server introduced in 2.88 which 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` nft_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_NFTSET $(PKG_CONFIG) --libs libnftables`
version = -DVERSION='\"`$(top)/bld/get-version $(top)`\"' 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?=$(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!=$(CC) -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) copts_conf = .copts_$(sum)
objs = cache.o rfc1035.o util.o option.o forward.o network.o \ 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; nxdomain = 0;
if ((crecp->flags & flag) && (local_query || filter_zone(zone, flag, &(crecp->addr)))) 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, if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
daemon->auth_ttl, NULL, qtype, C_IN, daemon->auth_ttl, NULL, qtype, C_IN,
qtype == T_A ? "4" : "6", &crecp->addr)) qtype == T_A ? "4" : "6", &crecp->addr))
+1 -1
View File
@@ -479,7 +479,7 @@ static struct crec *cache_scan_free(char *name, union all_addr *addr, unsigned s
if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name)) if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name))
{ {
int rrmatch = 0; int rrmatch = 0;
if (crecp->flags & flags & F_RR) if (addr && (crecp->flags & flags & F_RR))
{ {
unsigned short rrc = (crecp->flags & F_KEYTAG) ? crecp->addr.rrblock.rrtype : crecp->addr.rrdata.rrtype; unsigned short rrc = (crecp->flags & F_KEYTAG) ? crecp->addr.rrblock.rrtype : crecp->addr.rrdata.rrtype;
unsigned short rra = (flags & F_KEYTAG) ? addr->rrblock.rrtype : addr->rrdata.rrtype; unsigned short rra = (flags & F_KEYTAG) ? addr->rrblock.rrtype : addr->rrdata.rrtype;
+5
View File
@@ -362,6 +362,11 @@ HAVE_SOCKADDR_SA_LEN
#define HAVE_INOTIFY #define HAVE_INOTIFY
#endif #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. /* Define a string indicating which options are in use.
DNSMASQ_COMPILE_OPTS is only defined in dnsmasq.c */ DNSMASQ_COMPILE_OPTS is only defined in dnsmasq.c */
+105 -49
View File
@@ -32,6 +32,7 @@ static volatile int pipewrite;
static void set_dns_listeners(void); static void set_dns_listeners(void);
static void set_tftp_listeners(void); static void set_tftp_listeners(void);
static void check_dns_listeners(time_t now); static void check_dns_listeners(time_t now);
static void do_tcp_connection(struct listener *listener, time_t now, int slot);
static void sig_handler(int sig); static void sig_handler(int sig);
static void async_event(int pipe, time_t now); static void async_event(int pipe, time_t now);
static void fatal_event(struct event_desc *ev, char *msg); static void fatal_event(struct event_desc *ev, char *msg);
@@ -961,8 +962,15 @@ int main (int argc, char **argv)
my_syslog(LOG_WARNING, _("warning: ignoring resolv-file flag because no-resolv is set")); my_syslog(LOG_WARNING, _("warning: ignoring resolv-file flag because no-resolv is set"));
daemon->resolv_files = NULL; daemon->resolv_files = NULL;
if (!daemon->servers) if (!daemon->servers)
{
#ifdef HAVE_DBUS
if (option_bool(OPT_DBUS))
my_syslog(LOG_INFO, _("no upstream servers configured - please set them from DBus"));
else
#endif
my_syslog(LOG_WARNING, _("warning: no upstream servers configured")); my_syslog(LOG_WARNING, _("warning: no upstream servers configured"));
} }
}
if (daemon->max_logs != 0) if (daemon->max_logs != 0)
my_syslog(LOG_INFO, _("asynchronous logging enabled, queue limit is %d messages"), daemon->max_logs); my_syslog(LOG_INFO, _("asynchronous logging enabled, queue limit is %d messages"), daemon->max_logs);
@@ -1476,7 +1484,7 @@ static void async_event(int pipe, time_t now)
{ {
pid_t p; pid_t p;
struct event_desc ev; struct event_desc ev;
int i, check = 0; int wstatus, i, check = 0;
char *msg; char *msg;
/* NOTE: the memory used to return msg is leaked: use msgs in events only /* NOTE: the memory used to return msg is leaked: use msgs in events only
@@ -1539,7 +1547,7 @@ static void async_event(int pipe, time_t now)
case EVENT_CHILD: case EVENT_CHILD:
/* See Stevens 5.10 */ /* See Stevens 5.10 */
while ((p = waitpid(-1, NULL, WNOHANG)) != 0) while ((p = waitpid(-1, &wstatus, WNOHANG)) != 0)
if (p == -1) if (p == -1)
{ {
if (errno != EINTR) if (errno != EINTR)
@@ -1550,6 +1558,20 @@ static void async_event(int pipe, time_t now)
if (daemon->tcp_pids[i] == p) if (daemon->tcp_pids[i] == p)
{ {
daemon->tcp_pids[i] = 0; daemon->tcp_pids[i] = 0;
if (!WIFEXITED(wstatus))
{
/* If a helper process dies, (eg with SIGSEV)
log that and attempt to patch things up so that the
parent can continue to function. */
my_syslog(LOG_WARNING, _("TCP helper process %u died unexpectedly"), (unsigned int)p);
if (daemon->tcp_pipes[i] != -1)
{
close(daemon->tcp_pipes[i]);
daemon->tcp_pipes[i] = -1;
}
}
/* tcp_pipes == -1 && tcp_pids == 0 required to free slot */ /* tcp_pipes == -1 && tcp_pids == 0 required to free slot */
if (daemon->tcp_pipes[i] == -1) if (daemon->tcp_pipes[i] == -1)
daemon->metrics[METRIC_TCP_CONNECTIONS]--; daemon->metrics[METRIC_TCP_CONNECTIONS]--;
@@ -1832,22 +1854,17 @@ static void check_dns_listeners(time_t now)
struct listener *listener; struct listener *listener;
struct randfd_list *rfl; struct randfd_list *rfl;
int i; int i;
int pipefd[2];
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) /* Note that handling events here can create or destroy fds and
if (poll_check(serverfdp->fd, POLLIN)) render the result of the last poll() call invalid. Once
reply_query(serverfdp->fd, now); we find an fd that needs service, do it, then return to go around the
poll() loop again. This avoid really, really, wierd bugs. */
for (i = 0; i < daemon->numrrand; i++)
if (daemon->randomsocks[i].refcount != 0 &&
poll_check(daemon->randomsocks[i].fd, POLLIN))
reply_query(daemon->randomsocks[i].fd, now);
/* Check overflow random sockets too. */
for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next)
if (poll_check(rfl->rfd->fd, POLLIN))
reply_query(rfl->rfd->fd, now);
if (!option_bool(OPT_DEBUG))
for (i = 0; i < daemon->max_procs; i++)
if (daemon->tcp_pipes[i] != -1 &&
poll_check(daemon->tcp_pipes[i], POLLIN | POLLHUP))
{
/* Races. The child process can die before we read all of the data from the /* Races. The child process can die before we read all of the data from the
pipe, or vice versa. Therefore send tcp_pids to zero when we wait() the pipe, or vice versa. Therefore send tcp_pids to zero when we wait() the
process, and tcp_pipes to -1 and close the FD when we read the last process, and tcp_pipes to -1 and close the FD when we read the last
@@ -1855,11 +1872,7 @@ static void check_dns_listeners(time_t now)
The order of these events is indeterminate, and both are needed The order of these events is indeterminate, and both are needed
to free the process slot. Once the child process has gone, poll() to free the process slot. Once the child process has gone, poll()
returns POLLHUP, not POLLIN, so have to check for both here. */ returns POLLHUP, not POLLIN, so have to check for both here. */
if (!option_bool(OPT_DEBUG)) if (!cache_recv_insert(now, daemon->tcp_pipes[i]))
for (i = 0; i < daemon->max_procs; i++)
if (daemon->tcp_pipes[i] != -1 &&
poll_check(daemon->tcp_pipes[i], POLLIN | POLLHUP) &&
!cache_recv_insert(now, daemon->tcp_pipes[i]))
{ {
close(daemon->tcp_pipes[i]); close(daemon->tcp_pipes[i]);
daemon->tcp_pipes[i] = -1; daemon->tcp_pipes[i] = -1;
@@ -1867,11 +1880,38 @@ static void check_dns_listeners(time_t now)
if (daemon->tcp_pids[i] == 0) if (daemon->tcp_pids[i] == 0)
daemon->metrics[METRIC_TCP_CONNECTIONS]--; daemon->metrics[METRIC_TCP_CONNECTIONS]--;
} }
return;
}
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
if (poll_check(serverfdp->fd, POLLIN))
{
reply_query(serverfdp->fd, now);
return;
}
for (i = 0; i < daemon->numrrand; i++)
if (daemon->randomsocks[i].refcount != 0 &&
poll_check(daemon->randomsocks[i].fd, POLLIN))
{
reply_query(daemon->randomsocks[i].fd, now);
return;
}
/* Check overflow random sockets too. */
for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next)
if (poll_check(rfl->rfd->fd, POLLIN))
{
reply_query(rfl->rfd->fd, now);
return;
}
for (listener = daemon->listeners; listener; listener = listener->next) for (listener = daemon->listeners; listener; listener = listener->next)
{
if (listener->fd != -1 && poll_check(listener->fd, POLLIN)) if (listener->fd != -1 && poll_check(listener->fd, POLLIN))
{
receive_query(listener, now); receive_query(listener, now);
return;
}
/* check to see if we have a free tcp process slot. /* check to see if we have a free tcp process slot.
Note that we can't assume that because we had Note that we can't assume that because we had
@@ -1882,23 +1922,39 @@ static void check_dns_listeners(time_t now)
if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1) if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1)
break; break;
if (listener->tcpfd != -1 && i >= 0 && poll_check(listener->tcpfd, POLLIN)) if (i >= 0)
for (listener = daemon->listeners; listener; listener = listener->next)
if (listener->tcpfd != -1 && poll_check(listener->tcpfd, POLLIN))
{ {
do_tcp_connection(listener, now, i);
return;
}
}
static void do_tcp_connection(struct listener *listener, time_t now, int slot)
{
int confd, client_ok = 1; int confd, client_ok = 1;
struct irec *iface = NULL; struct irec *iface = NULL;
pid_t p; pid_t p;
union mysockaddr tcp_addr; union mysockaddr tcp_addr;
socklen_t tcp_len = sizeof(union mysockaddr); socklen_t tcp_len = sizeof(union mysockaddr);
unsigned char a = 0, *buff;
struct server *s;
int flags, auth_dns;
struct in_addr netmask;
int pipefd[2];
while ((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR); while ((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR);
if (confd == -1) if (confd == -1)
continue; return;
if (getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1) if (getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1)
{ {
closeconandreturn:
shutdown(confd, SHUT_RDWR);
close(confd); close(confd);
continue; return;
} }
/* Make sure that the interface list is up-to-date. /* Make sure that the interface list is up-to-date.
@@ -1961,17 +2017,26 @@ static void check_dns_listeners(time_t now)
} }
if (!client_ok) if (!client_ok)
goto closeconandreturn;
if (!option_bool(OPT_DEBUG))
{ {
shutdown(confd, SHUT_RDWR); if (pipe(pipefd) == -1)
close(confd); goto closeconandreturn; /* pipe failed */
}
else if (!option_bool(OPT_DEBUG) && pipe(pipefd) == 0 && (p = fork()) != 0) if ((p = fork()) == -1)
{ {
close(pipefd[1]); /* parent needs read pipe end. */ /* fork failed */
if (p == -1)
close(pipefd[0]); close(pipefd[0]);
else close(pipefd[1]);
goto closeconandreturn;
}
if (p != 0)
{ {
/* fork() done: parent side */
close(pipefd[1]); /* parent needs read pipe end. */
#ifdef HAVE_LINUX_NETWORK #ifdef HAVE_LINUX_NETWORK
/* The child process inherits the netlink socket, /* The child process inherits the netlink socket,
which it never uses, but when the parent (us) which it never uses, but when the parent (us)
@@ -1987,17 +2052,16 @@ static void check_dns_listeners(time_t now)
is sent by the child after it has closed the is sent by the child after it has closed the
netlink socket. */ netlink socket. */
unsigned char a;
read_write(pipefd[0], &a, 1, RW_READ); read_write(pipefd[0], &a, 1, RW_READ);
#endif #endif
/* i holds index of free slot */
daemon->tcp_pids[i] = p; daemon->tcp_pids[slot] = p;
daemon->tcp_pipes[i] = pipefd[0]; daemon->tcp_pipes[slot] = pipefd[0];
daemon->metrics[METRIC_TCP_CONNECTIONS]++; daemon->metrics[METRIC_TCP_CONNECTIONS]++;
if (daemon->metrics[METRIC_TCP_CONNECTIONS] > daemon->max_procs_used) if (daemon->metrics[METRIC_TCP_CONNECTIONS] > daemon->max_procs_used)
daemon->max_procs_used = daemon->metrics[METRIC_TCP_CONNECTIONS]; daemon->max_procs_used = daemon->metrics[METRIC_TCP_CONNECTIONS];
}
close(confd); close(confd);
/* The child can use up to TCP_MAX_QUERIES ids, so skip that many. */ /* The child can use up to TCP_MAX_QUERIES ids, so skip that many. */
@@ -2007,14 +2071,10 @@ static void check_dns_listeners(time_t now)
if (option_bool(OPT_DNSSEC_VALID)) if (option_bool(OPT_DNSSEC_VALID))
daemon->log_id += daemon->limit[LIMIT_WORK]; daemon->log_id += daemon->limit[LIMIT_WORK];
#endif #endif
return;
}
} }
else
{
unsigned char *buff;
struct server *s;
int flags;
struct in_addr netmask;
int auth_dns;
if (iface) if (iface)
{ {
@@ -2033,8 +2093,6 @@ static void check_dns_listeners(time_t now)
{ {
#ifdef HAVE_LINUX_NETWORK #ifdef HAVE_LINUX_NETWORK
/* See comment above re: netlink socket. */ /* See comment above re: netlink socket. */
unsigned char a = 0;
close(daemon->netlinkfd); close(daemon->netlinkfd);
read_write(pipefd[1], &a, 1, RW_WRITE); read_write(pipefd[1], &a, 1, RW_WRITE);
#endif #endif
@@ -2068,11 +2126,9 @@ static void check_dns_listeners(time_t now)
flush_log(); flush_log();
_exit(0); _exit(0);
} }
}
}
}
} }
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
/* If a DNSSEC query over UDP returns a truncated answer, /* If a DNSSEC query over UDP returns a truncated answer,
we swap to the TCP path. This routine is responsible for forking we swap to the TCP path. This routine is responsible for forking
+68 -31
View File
@@ -163,7 +163,7 @@ static int domain_no_rebind(char *domain)
return 0; return 0;
} }
static int forward_query(int udpfd, union mysockaddr *udpaddr, static void forward_query(int udpfd, union mysockaddr *udpaddr,
union all_addr *dst_addr, unsigned int dst_iface, union all_addr *dst_addr, unsigned int dst_iface,
struct dns_header *header, size_t plen, size_t replylimit, time_t now, struct dns_header *header, size_t plen, size_t replylimit, time_t now,
struct frec *forward, unsigned int fwd_flags, int fast_retry) struct frec *forward, unsigned int fwd_flags, int fast_retry)
@@ -190,6 +190,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
{ {
old_src = 1; old_src = 1;
old_reply = 1; old_reply = 1;
fwd_flags = forward->flags;
} }
else if (gotname && (forward = lookup_frec(daemon->namebuff, C_IN, (int)rrtype, -1, fwd_flags, else if (gotname && (forward = lookup_frec(daemon->namebuff, C_IN, (int)rrtype, -1, fwd_flags,
FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION | FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION |
@@ -253,7 +254,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
it's safe to wait for the reply from the first without it's safe to wait for the reply from the first without
forwarding the second. */ forwarding the second. */
if (difftime(now, forward->time) < 2) if (difftime(now, forward->time) < 2)
return 0; return;
} }
/* use our id when resending */ /* use our id when resending */
@@ -373,6 +374,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
while (forward->blocking_query) while (forward->blocking_query)
forward = forward->blocking_query; forward = forward->blocking_query;
/* Don't retry if we've already sent it via TCP. */
if (forward->flags & FREC_GONE_TO_TCP)
return;
if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY))
{ {
/* log_id should match previous DNSSEC query. */ /* log_id should match previous DNSSEC query. */
@@ -385,7 +390,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
/* Find suitable servers: should never fail. */ /* Find suitable servers: should never fail. */
if (!filter_servers(forward->sentto->arrayposn, F_DNSSECOK, &first, &last)) if (!filter_servers(forward->sentto->arrayposn, F_DNSSECOK, &first, &last))
return 0; return;
is_dnssec = 1; is_dnssec = 1;
forward->forwardall = 1; forward->forwardall = 1;
@@ -501,8 +506,9 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
if (forwarded || is_dnssec) if (forwarded || is_dnssec)
{ {
daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]++;
forward->forward_timestamp = dnsmasq_milliseconds(); forward->forward_timestamp = dnsmasq_milliseconds();
return 1; return;
} }
/* could not send on, prepare to return */ /* could not send on, prepare to return */
@@ -514,9 +520,9 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
if (udpfd != -1) if (udpfd != -1)
{ {
if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, (char *)(header + replylimit), first, last, ede))) if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, (char *)(header + replylimit), first, last, ede)))
return 0; return;
if (forward->flags & FREC_HAS_PHEADER) if (fwd_flags & FREC_HAS_PHEADER)
{ {
u16 swap = htons((u16)ede); u16 swap = htons((u16)ede);
@@ -542,7 +548,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface); send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface);
} }
return 0; daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
return;
} }
/* Check if any frecs need to do a retry, and action that if so. /* Check if any frecs need to do a retry, and action that if so.
@@ -779,10 +786,9 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
header->ancount = htons(0); header->ancount = htons(0);
header->nscount = htons(0); header->nscount = htons(0);
header->arcount = htons(0); header->arcount = htons(0);
ede = EDE_DNSSEC_BOGUS;
} }
} }
else if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure) else if (ad_reqd && cache_secure)
header->hb4 |= HB4_AD; header->hb4 |= HB4_AD;
/* If the requestor didn't set the DO bit, don't return DNSSEC info. */ /* If the requestor didn't set the DO bit, don't return DNSSEC info. */
@@ -816,10 +822,6 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
daemon->log_display_id = forward->frec_src.log_id; 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.... */ /* Find the original query that started it all.... */
for (orig = forward; orig->dependent; orig = orig->dependent); for (orig = forward; orig->dependent; orig = orig->dependent);
@@ -1147,17 +1149,19 @@ void reply_query(int fd, time_t now)
check_for_ignored_address(header, n)) check_for_ignored_address(header, n))
return; return;
if ((RCODE(header) == REFUSED || RCODE(header) == SERVFAIL) && forward->forwardall == 0)
/* for broken servers, attempt to send to another one. */
{
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
/* The query MAY have got a good answer, and be awaiting /* The query MAY have got a good answer, and be awaiting
the results of further queries, in which case 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)) if (forward->blocking_query || (forward->flags & FREC_GONE_TO_TCP))
return; return;
#endif #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. */ /* Get the saved query back. */
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
@@ -1243,20 +1247,25 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s
char *result, *domain = "result"; char *result, *domain = "result";
union all_addr a; union all_addr a;
a.log.ede = ede = errflags_to_ede(status); ede = errflags_to_ede(status);
if (STAT_ISEQUAL(status, STAT_ABANDONED)) if (STAT_ISEQUAL(status, STAT_ABANDONED))
{ {
result = "ABANDONED"; result = "ABANDONED";
status = STAT_BOGUS; status = STAT_BOGUS;
if (ede == EDE_UNSET)
ede = EDE_OTHER;
} }
else else
result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS")); result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS"));
if (STAT_ISEQUAL(status, STAT_SECURE)) if (STAT_ISEQUAL(status, STAT_SECURE))
cache_secure = 1; cache_secure = 1;
else if (STAT_ISEQUAL(status, STAT_BOGUS)) else if (STAT_ISEQUAL(status, STAT_BOGUS))
{ {
if (ede == EDE_UNSET)
ede = EDE_DNSSEC_BOGUS;
no_cache_dnssec = 1; no_cache_dnssec = 1;
bogusanswer = 1; bogusanswer = 1;
@@ -1264,6 +1273,7 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s
domain = daemon->namebuff; domain = daemon->namebuff;
} }
a.log.ede = ede;
log_query(F_SECSTAT, domain, &a, result, 0); log_query(F_SECSTAT, domain, &a, result, 0);
} }
} }
@@ -1681,15 +1691,27 @@ void receive_query(struct listener *listen, time_t now)
&source_addr, auth_dns ? "auth" : "query", type); &source_addr, auth_dns ? "auth" : "query", type);
#ifdef HAVE_AUTH #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)) if (!auth_dns && !option_bool(OPT_LOCALISE))
for (zone = daemon->auth_zones; zone; zone = zone->next) for (zone = daemon->auth_zones; zone; zone = zone->next)
if (in_zone(zone, daemon->namebuff, NULL)) {
char *cut;
if (in_zone(zone, daemon->namebuff, &cut))
{
if (type != T_DS || cut)
{ {
auth_dns = 1; auth_dns = 1;
local_auth = 1; local_auth = 1;
}
break; break;
} }
}
#endif #endif
#ifdef HAVE_LOOP #ifdef HAVE_LOOP
@@ -1845,11 +1867,8 @@ void receive_query(struct listener *listen, time_t now)
blockdata_free(saved_question); blockdata_free(saved_question);
saved_question = NULL; saved_question = NULL;
if (forward_query(fd, &source_addr, &dst_addr, if_index, header, (size_t)n, forward_query(fd, &source_addr, &dst_addr, if_index, header, (size_t)n,
udp_size, now, NULL, fwd_flags, 0)) udp_size, now, NULL, fwd_flags, 0);
daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]++;
else
daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
} }
blockdata_free(saved_question); blockdata_free(saved_question);
@@ -2318,15 +2337,27 @@ unsigned char *tcp_request(int confd, time_t now,
&peer_addr, auth_dns ? "auth" : "query", qtype); &peer_addr, auth_dns ? "auth" : "query", qtype);
#ifdef HAVE_AUTH #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)) if (!auth_dns && !option_bool(OPT_LOCALISE))
for (zone = daemon->auth_zones; zone; zone = zone->next) for (zone = daemon->auth_zones; zone; zone = zone->next)
if (in_zone(zone, daemon->namebuff, NULL)) {
char *cut;
if (in_zone(zone, daemon->namebuff, &cut))
{
if (qtype != T_DS || cut)
{ {
auth_dns = 1; auth_dns = 1;
local_auth = 1; local_auth = 1;
}
break; break;
} }
}
#endif #endif
norebind = domain_no_rebind(daemon->namebuff); norebind = domain_no_rebind(daemon->namebuff);
@@ -2394,8 +2425,7 @@ unsigned char *tcp_request(int confd, time_t now,
size = saved_size; size = saved_size;
/* save state of "cd" flag in query */ /* save state of "cd" flag in query */
if ((checking_disabled = header->hb4 & HB4_CD)) checking_disabled = header->hb4 & HB4_CD;
no_cache_dnssec = 1;
if (lookup_domain(daemon->namebuff, gotname, &first, &last)) if (lookup_domain(daemon->namebuff, gotname, &first, &last))
flags = is_local_answer(now, first, daemon->namebuff); flags = is_local_answer(now, first, daemon->namebuff);
@@ -2447,7 +2477,9 @@ unsigned char *tcp_request(int confd, time_t now,
/* Clear this in case we don't call tcp_key_recurse() below */ /* Clear this in case we don't call tcp_key_recurse() below */
memset(daemon->rr_status, 0, sizeof(*daemon->rr_status) * daemon->rr_status_sz); memset(daemon->rr_status, 0, sizeof(*daemon->rr_status) * daemon->rr_status_sz);
if (!checking_disabled && (master->flags & SERV_DO_DNSSEC)) if (checking_disabled || (header->hb4 & HB4_CD))
no_cache_dnssec = 1;
else if (master->flags & SERV_DO_DNSSEC)
{ {
int keycount = daemon->limit[LIMIT_WORK]; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ int keycount = daemon->limit[LIMIT_WORK]; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
int validatecount = daemon->limit[LIMIT_CRYPTO]; int validatecount = daemon->limit[LIMIT_CRYPTO];
@@ -2456,12 +2488,14 @@ unsigned char *tcp_request(int confd, time_t now,
char *result, *domain = "result"; char *result, *domain = "result";
union all_addr a; union all_addr a;
a.log.ede = ede = errflags_to_ede(status); ede = errflags_to_ede(status);
if (STAT_ISEQUAL(status, STAT_ABANDONED)) if (STAT_ISEQUAL(status, STAT_ABANDONED))
{ {
result = "ABANDONED"; result = "ABANDONED";
status = STAT_BOGUS; status = STAT_BOGUS;
if (ede == EDE_UNSET)
ede = EDE_OTHER;
} }
else else
result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS")); result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS"));
@@ -2470,6 +2504,8 @@ unsigned char *tcp_request(int confd, time_t now,
cache_secure = 1; cache_secure = 1;
else if (STAT_ISEQUAL(status, STAT_BOGUS)) else if (STAT_ISEQUAL(status, STAT_BOGUS))
{ {
if (ede == EDE_UNSET)
ede = EDE_DNSSEC_BOGUS;
no_cache_dnssec = 1; no_cache_dnssec = 1;
bogusanswer = 1; bogusanswer = 1;
@@ -2477,6 +2513,7 @@ unsigned char *tcp_request(int confd, time_t now,
domain = daemon->namebuff; domain = daemon->namebuff;
} }
a.log.ede = ede;
log_query(F_SECSTAT, domain, &a, result, 0); log_query(F_SECSTAT, domain, &a, result, 0);
if ((daemon->limit[LIMIT_CRYPTO] - validatecount) > (int)daemon->metrics[METRIC_CRYPTO_HWM]) if ((daemon->limit[LIMIT_CRYPTO] - validatecount) > (int)daemon->metrics[METRIC_CRYPTO_HWM])
+10 -9
View File
@@ -1559,7 +1559,7 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now)
return daemon->max_ttl; 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)); 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 the client asked for DNSSEC don't use cached data. */
if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || 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) if (crecp->flags & F_CONFIG || qtype == T_CNAME)
ans = 1; 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 the zone is unsigned, which implies that we're doing
validation. */ validation. */
if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || 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 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 the client asked for DNSSEC don't use cached data. */
if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || 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 do
{ {
int stale_flag = 0; int stale_flag = 0;
@@ -2157,8 +2157,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (!ans) if (!ans)
{ {
if ((crecp = cache_find_by_name(NULL, name, now, F_RR | F_NXDOMAIN)) && if ((crecp = cache_find_by_name(NULL, name, now, F_RR | F_NXDOMAIN)) && rd_bit)
rd_bit && (!do_bit || cache_validated(crecp)))
do do
{ {
int flags = crecp->flags; int flags = crecp->flags;
@@ -2169,7 +2168,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
else else
rrtype = crecp->addr.rrdata.rrtype; 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; char *rrdata = NULL;
unsigned short rrlen = 0; 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 /* 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 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. 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, 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 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)) if (cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_RR | F_CNAME))
{ {