Skip to content

Commit

Permalink
resolved: add support for answering DNSSEC questions on the stub
Browse files Browse the repository at this point in the history
This substantially beefs up the local DNS stub feature set in order to
allow local clients to do DNSSEC validation through the stub.

Previously we'd return NOTIMP if we'd get a DO or DO+CD lookup. With
this change we'll instead:

1. If we get DO+CD requests (i.e. DNSSEC with no local checking) we'll
   proxy DNS queries and response mostly unmodified to/from upstream DNS
   servers if possible (this is called "bypass" mode).  We will patch in
   new request IDs, (and patch them back out on reply), so that we can
   sanely keep track of things.  We'll also maintain a minimal local
   cache for such lookups, always keeping the whole DNS packets in it
   (if we reply from cache we'll patch the TTLs of all included RRs).

2. If we get DO requests without CD (i.e. DNSSEC with local checking)
   we'll resolve and validate locally. In this mode we will not proxy
   packets, but generate our own. We will however cache the combination
   of answer RRs (along with their packet section assignments) we got
   back in the cache, and use this information to generate reply packets
   from the DNS stub.

In both cases: if we determine a lookup is to be answered from LLMNR or
mDNS we'll always revert to non-DNSSEC, non-proxy operation as before.
Answers will lack the DO bit then, since the data cannot be validated
via DNSSEC by the clients.

To make this logic more debuggable, this also adds query flags for
turning off RR sources. i.e. cache/network/zone/trust anchor/local
synthesis may now be disabled individually for each lookup.

The cache is substantially updated to make all this work: in addition to
caching simple RRs for lookup RR keys, we'll now cache the whole packets
and the whole combination of RRs, so that we can answer DO and DO+CD
replies sensibly according to the rules described above. This sounds
wasteful, but given that the
DnsResourceRecord/DnsResourceKey/DnsAnswer/DnsPacket
objects are all ref-counted and we try to merge references the actual
additional memory used should be limited (but this might be something to
optimize further later on).

To implement classic RR key lookups and new-style packet proxy lookups
(i.e. the ones necessary for DO+CD packet proxying, as described above)
DnsTransaction and DnsQuery objects now always maintain either a
DnsResourceKey/DnsQuestion as lookup key or a DnsPacket for "bypass"
mode.

Fixes: systemd#4621 systemd#17218
  • Loading branch information
poettering committed Dec 4, 2020
1 parent cba2991 commit 65688d2
Show file tree
Hide file tree
Showing 16 changed files with 1,151 additions and 363 deletions.
1 change: 1 addition & 0 deletions src/libsystemd/sd-bus/bus-common-errors.c
Expand Up @@ -75,6 +75,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK, ENXIO),
SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY),
SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_DOWN, ENETDOWN),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SOURCE, ESRCH),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DNSSD_SERVICE, ENOENT),
SD_BUS_ERROR_MAP(BUS_ERROR_DNSSD_SERVICE_EXISTS, EEXIST),

Expand Down
1 change: 1 addition & 0 deletions src/libsystemd/sd-bus/bus-common-errors.h
Expand Up @@ -73,6 +73,7 @@
#define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink"
#define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy"
#define BUS_ERROR_NETWORK_DOWN "org.freedesktop.resolve1.NetworkDown"
#define BUS_ERROR_NO_SOURCE "org.freedesktop.resolve1.NoSource"
#define BUS_ERROR_NO_SUCH_DNSSD_SERVICE "org.freedesktop.resolve1.NoSuchDnssdService"
#define BUS_ERROR_DNSSD_SERVICE_EXISTS "org.freedesktop.resolve1.DnssdServiceExists"
#define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError."
Expand Down
33 changes: 21 additions & 12 deletions src/resolve/resolved-bus.c
Expand Up @@ -101,6 +101,9 @@ static int reply_query_state(DnsQuery *q) {
* thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */
return sd_bus_reply_method_errorf(q->bus_request, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q));

case DNS_TRANSACTION_NO_SOURCE:
return sd_bus_reply_method_errorf(q->bus_request, BUS_ERROR_NO_SOURCE, "All suitable resolution sources turned off");

case DNS_TRANSACTION_RCODE_FAILURE: {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;

Expand Down Expand Up @@ -274,16 +277,24 @@ static int validate_and_mangle_flags(
*
* 1. Checks that the interface index is either 0 (meaning *all* interfaces) or positive
*
* 2. Only the protocols flags and the NO_CNAME flag are set, at most. Plus additional flags specific
* to our method, passed in the "ok" parameter.
* 2. Only the protocols flags and a bunch of NO_XYZ flags are set, at most. Plus additional flags
* specific to our method, passed in the "ok" parameter.
*
* 3. If zero protocol flags are specified it is automatically turned into *all* protocols. This way
* clients can simply pass 0 as flags and all will work as it should. They can also use this so
* that clients don't have to know all the protocols resolved implements, but can just specify 0
* to mean "all supported protocols".
*/

if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok))
if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|
SD_RESOLVED_NO_CNAME|
SD_RESOLVED_NO_VALIDATE|
SD_RESOLVED_NO_SYNTHESIZE|
SD_RESOLVED_NO_CACHE|
SD_RESOLVED_NO_ZONE|
SD_RESOLVED_NO_TRUST_ANCHOR|
SD_RESOLVED_NO_NETWORK|
ok))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter");

if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */
Expand Down Expand Up @@ -406,7 +417,7 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata,
if (r < 0 && r != -EALREADY)
return r;

r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, ifindex, flags);
r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, NULL, ifindex, flags);
if (r < 0)
return r;

Expand Down Expand Up @@ -548,7 +559,7 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s
if (r < 0)
return r;

r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
r = dns_query_new(m, &q, question, question, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH);
if (r < 0)
return r;

Expand Down Expand Up @@ -724,14 +735,12 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd
if (r < 0)
return r;

r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
/* Setting SD_RESOLVED_CLAMP_TTL: let's request that the TTL is fixed up for locally cached entries,
* after all we return it in the wire format blob. */
r = dns_query_new(m, &q, question, question, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_CLAMP_TTL);
if (r < 0)
return r;

/* Let's request that the TTL is fixed up for locally cached entries, after all we return it in the wire format
* blob */
q->clamp_ttl = true;

q->bus_request = sd_bus_message_ref(message);
q->complete = bus_method_resolve_record_complete;

Expand Down Expand Up @@ -1088,7 +1097,7 @@ static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifin
if (r < 0)
return r;

r = dns_query_new(q->manager, &aux, question, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH);
r = dns_query_new(q->manager, &aux, question, question, NULL, ifindex, q->flags|SD_RESOLVED_NO_SEARCH);
if (r < 0)
return r;

Expand Down Expand Up @@ -1258,7 +1267,7 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s
if (r < 0)
return r;

r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags|SD_RESOLVED_NO_SEARCH);
r = dns_query_new(m, &q, question_utf8, question_idna, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH);
if (r < 0)
return r;

Expand Down
25 changes: 25 additions & 0 deletions src/resolve/resolved-def.h
Expand Up @@ -27,6 +27,31 @@
/* Output: Result is authenticated */
#define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9)

/* Input: Don't DNSSEC validate request */
#define SD_RESOLVED_NO_VALIDATE (UINT64_C(1) << 10)

/* Input: Don't answer request from locally synthesized records (which includes /etc/hosts) */
#define SD_RESOLVED_NO_SYNTHESIZE (UINT64_C(1) << 11)

/* Input: Don't answer request from cache */
#define SD_RESOLVED_NO_CACHE (UINT64_C(1) << 12)

/* Input: Don't answer request from locally registered public LLMNR/mDNS RRs */
#define SD_RESOLVED_NO_ZONE (UINT64_C(1) << 13)

/* Input: Don't answer request from locally registered public LLMNR/mDNS RRs */
#define SD_RESOLVED_NO_TRUST_ANCHOR (UINT64_C(1) << 14)

/* Input: Don't go to network for this request */
#define SD_RESOLVED_NO_NETWORK (UINT64_C(1) << 15)

/* Input: Require that request is answered from a "primary" answer, i.e. not from RRs acquired as
* side-effect of a previous transaction */
#define SD_RESOLVED_REQUIRE_PRIMARY (UINT64_C(1) << 16)

/* Input: If reply is answered from cache, the TTLs will be adjusted by age of cache entry */
#define SD_RESOLVED_CLAMP_TTL (UINT64_C(1) << 17)

#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6)
#define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6)
#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS)
Expand Down

0 comments on commit 65688d2

Please sign in to comment.