There are two functional changes in this commit.
1) When searching for an in-flight DNSSEC query to use
(rather than starting a new one), compare the already
sent query (stored in the frec "stash" field, rather than
using the hash of the query. This is probably faster (no hash
calculation) and eliminates having to worry about the
consequences of a hash collision.
2) Check for dependency loops in DNSSEC validation,
say validating A requires DS B and validating DS B
requires DNSKEY C and validating DNSKEY C requires DS B.
This should never happen in correctly signed records, but it's
likely the case that sufficiently broken ones can cause
our validation code requests to exhibit cycles.
The result is that the ->blocking_query list
can form a cycle, and under certain circumstances that can lock us in
an infinite loop.
Instead we transform the situation into an ABANDONED state.
Previously, hash_questions() would return a random hash
if the packet was malformed, and probably the hash of a previous
query. Now handle this as an error.
The 2.86 domain-match rewrite changed matching from
whole-labels to substring matching, so example.com
would match example.com and www.example.com, as before,
but also goodexample.com, which is a regression. This
restores the original behaviour.
Also restore the behaviour of --rebind-domain-ok=//
to match domains with onlt a single label and no dots.
Thanks to Sung Pae for reporting these bugs and supplying
an initial patch.
The IDs logged when --log-queries=extra is in effect
can be wrong in three cases.
1) When query is retried in response to a a SERVFAIL or REFUSED
answer from upstream. In this case the ID of an unrelated query will
appear in the answer log lines.
2) When the same query arrives from two clients. The query is
sent upstream once, as designed, and the result returned to both clients,
as designed, but the reply to the first client gets the log-ID of the
second query in error.
3) When a query arrives, is sent upstream, and the reply comes back,
but the transaction is blocked awaiting a DNSSEC query needed to validate
the reply. If the client retries the query in this state, the blocking
DNSSEC query will be resent, as designed, but that send will be logged with
the ID of the original, currently blocked, query.
Thanks to Dominik Derigs for his analysis of this problem.
The domain-match rewrite didn't take into account
that domain names are case-insensitive, so things like
--address=/Example.com/.....
didn't work correctly.
Behaviour to stop infinite loops when all servers return REFUSED
was wrongly activated on client retries, resulting in
incorrect REFUSED replies to client retries.
Thanks to Johannes Stezenbach for finding the problem.
This patch also changes the method of calling querystr() such that
it is only called when logging is enabled, to eliminate any
possible performance problems from searching the larger table.
This extends query filtering support beyond what is currently possible
with the `--ipset` configuration option, by adding support for:
1) Specifying allowlists on a per-client basis, based on their
associated Linux connection track mark.
2) Dynamic configuration of allowlists via Ubus.
3) Reporting when a DNS query resolves or is rejected via Ubus.
4) DNS name patterns containing wildcards.
Disallowed queries are not forwarded; they are rejected
with a REFUSED error code.
Signed-off-by: Etan Kissling <etan_kissling@apple.com>
(addressed reviewer feedback)
Signed-off-by: Etan Kissling <etan.kissling@gmail.com>
In the specific case of configuring an A record for a domain
address=/example.com/1.2.3.4
queries for *example.com for any other type will now return
NOERR, and not the previous erroneous NXDOMAIN. The same thing
applies for
address=/example.com/::1:2:3:4
address=/example.com/#
If we retry a DNSSEC query because our client retries on us, and
we have an answer but are waiting on a DNSSEC query to validate it,
log the name of the DNSSEC query, not the client's query.
The sharing point for DNSSEC RR data used to be when it entered the
cache, having been validated. After that queries requiring the KEY or
DS records would share the cached values. There is a common case in
dual-stack hosts that queries for A and AAAA records for the same
domain are made simultaneously. If required keys were not in the
cache, this would result in two requests being sent upstream for the
same key data (and all the subsequent chain-of-trust queries.) Now we
combine these requests and elide the duplicates, resulting in fewer
queries upstream and better performance. To keep a better handle on
what's going on, the "extra" logging mode has been modified to
associate queries and answers for DNSSEC queries in the same way as
ordinary queries. The requesting address and port have been removed
from DNSSEC logging lines, since this is no longer strictly defined.
This used to have a global limit, but that has a problem when using
different servers for different upstream domains. Queries which are
routed by domain to an upstream server which is not responding will
build up and trigger the limit, which breaks DNS service for all other
domains which could be handled by other servers. The change is to make
the limit per server-group, where a server group is the set of servers
configured for a particular domain. In the common case, where only
default servers are declared, there is no effective change.
This should be largely transparent, but it drastically
improves performance and reduces memory foot-print when
configuring large numbers domains of the form
local=/adserver.com/
or
local=/adserver.com/#
Lookup times now grow as log-to-base-2 of the number of domains,
rather than greater than linearly, as before.
The change makes multiple addresses associated with a domain work
address=/example.com/1.2.3.4
address=/example.com/5.6.7.8
It also handles multiple upstream servers for a domain better; using
the same try/retry alogrithms as non domain-specific servers. This
also applies to DNSSEC-generated queries.
Finally, some of the oldest and gnarliest code in dnsmasq has had
a significant clean-up. It's far from perfect, but it _is_ better.
If two queries arrive a second or so apart, they cannot be a try and
a retry from the same client (retries are at least three seconds apart.)
It's therefore safe not to forward the second query, but answer them
both when the reply arrives for the first.
This changes the behaviour introduced in
141a26f979
We re-introduce the distinction between a query
which is retried from the same source, and one which is
repeated from different sources.
In the later case, we still forward the query, to avoid
problems when the reply to the first query is lost
(see f8cf456920) but we suppress the behaviour
that's used on a retry, when the query is sent to
all available servers in parallel.
Retry -> all servers.
Repeat -> next server.
This avoids a significant increase in upstream traffic on
busy instances which see lots of queries for common names.
It does mean the clients which repeat queries from new source ports,
rather than retrying them from the same source port, will see
different behaviour, but it in fact restores the pre-2.83 behaviour,
so it's not expected to be a practical problem.
One change to server_test_type forgot to set SERV_DO_DNSSEC. One new
place still can be reused.
Fixes commit e10a9239e1, thanks to
Xingcong Li for spotting it.
One part in dnssec retry path did not dump sent retry into dump file.
Make sure it is dumped all times it is sent by common function shared on
multiple places. Reduce a bit also server sending.
CVE-2021-3448 applies.
It's possible to specify the source address or interface to be
used when contacting upstream nameservers: server=8.8.8.8@1.2.3.4
or server=8.8.8.8@1.2.3.4#66 or server=8.8.8.8@eth0, and all of
these have, until now, used a single socket, bound to a fixed
port. This was originally done to allow an error (non-existent
interface, or non-local address) to be detected at start-up. This
means that any upstream servers specified in such a way don't use
random source ports, and are more susceptible to cache-poisoning
attacks.
We now use random ports where possible, even when the
source is specified, so server=8.8.8.8@1.2.3.4 or
server=8.8.8.8@eth0 will use random source
ports. server=8.8.8.8@1.2.3.4#66 or any use of --query-port will
use the explicitly configured port, and should only be done with
understanding of the security implications.
Note that this change changes non-existing interface, or non-local
source address errors from fatal to run-time. The error will be
logged and communiction with the server not possible.