summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Gundersen <teg@jklm.no>2015-12-04 14:22:29 +0100
committerTom Gundersen <teg@jklm.no>2015-12-04 14:22:29 +0100
commit8eb3655cdb1910537615bb69c5ecf90dcc9d81c3 (patch)
treeaafbaca07fc1125352ecaf0f0e4c6473f455ff80
parent517d12cc0c94e09f8b4783e84ee0d2d5df3e9b5a (diff)
parentbb1fa24261fd60ec1df6c6c42940c5f764d9246d (diff)
Merge pull request #2092 from poettering/dnssec2
Second DNSSEC patch set
-rw-r--r--Makefile.am2
-rw-r--r--src/resolve-host/resolve-host.c2
-rw-r--r--src/resolve/resolved-bus.c8
-rw-r--r--src/resolve/resolved-conf.c35
-rw-r--r--src/resolve/resolved-conf.h1
-rw-r--r--src/resolve/resolved-def.h1
-rw-r--r--src/resolve/resolved-dns-cache.c61
-rw-r--r--src/resolve/resolved-dns-cache.h4
-rw-r--r--src/resolve/resolved-dns-dnssec.c32
-rw-r--r--src/resolve/resolved-dns-dnssec.h20
-rw-r--r--src/resolve/resolved-dns-packet.c4
-rw-r--r--src/resolve/resolved-dns-packet.h13
-rw-r--r--src/resolve/resolved-dns-query.c25
-rw-r--r--src/resolve/resolved-dns-query.h1
-rw-r--r--src/resolve/resolved-dns-rr.c19
-rw-r--r--src/resolve/resolved-dns-rr.h16
-rw-r--r--src/resolve/resolved-dns-scope.c71
-rw-r--r--src/resolve/resolved-dns-scope.h3
-rw-r--r--src/resolve/resolved-dns-server.h3
-rw-r--r--src/resolve/resolved-dns-transaction.c43
-rw-r--r--src/resolve/resolved-dns-transaction.h2
-rw-r--r--src/resolve/resolved-dns-trust-anchor.c101
-rw-r--r--src/resolve/resolved-dns-trust-anchor.h39
-rw-r--r--src/resolve/resolved-dns-zone.c13
-rw-r--r--src/resolve/resolved-gperf.gperf1
-rw-r--r--src/resolve/resolved-manager.c6
-rw-r--r--src/resolve/resolved-manager.h3
-rw-r--r--src/resolve/resolved.conf.in1
28 files changed, 422 insertions, 108 deletions
diff --git a/Makefile.am b/Makefile.am
index edae4a309b..db916f72c5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5184,6 +5184,8 @@ systemd_resolved_SOURCES = \
src/resolve/resolved-dns-stream.c \
src/resolve/resolved-dns-dnssec.h \
src/resolve/resolved-dns-dnssec.c \
+ src/resolve/resolved-dns-trust-anchor.h \
+ src/resolve/resolved-dns-trust-anchor.c \
src/resolve/dns-type.c \
src/resolve/dns-type.h
diff --git a/src/resolve-host/resolve-host.c b/src/resolve-host/resolve-host.c
index 36dfc70e00..0f154d9798 100644
--- a/src/resolve-host/resolve-host.c
+++ b/src/resolve-host/resolve-host.c
@@ -67,6 +67,8 @@ static void print_source(uint64_t flags, usec_t rtt) {
fputc('.', stdout);
fputc('\n', stdout);
+
+ printf("-- Data is authenticated: %s\n", yes_no(flags & SD_RESOLVED_AUTHENTICATED));
}
static int resolve_host(sd_bus *bus, const char *name) {
diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c
index ddde3af0c3..0ceca56371 100644
--- a/src/resolve/resolved-bus.c
+++ b/src/resolve/resolved-bus.c
@@ -197,7 +197,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) {
r = sd_bus_message_append(
reply, "st",
DNS_RESOURCE_KEY_NAME(canonical->key),
- SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family));
+ SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated));
if (r < 0)
goto finish;
@@ -344,7 +344,7 @@ static void bus_method_resolve_address_complete(DnsQuery *q) {
if (r < 0)
goto finish;
- r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family));
+ r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated));
if (r < 0)
goto finish;
@@ -510,7 +510,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) {
if (r < 0)
goto finish;
- r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family));
+ r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated));
if (r < 0)
goto finish;
@@ -859,7 +859,7 @@ static void resolve_service_all_complete(DnsQuery *q) {
reply,
"ssst",
name, type, domain,
- SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family));
+ SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated));
if (r < 0)
goto finish;
diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c
index 3fc7d9ae3d..1b2f3e336e 100644
--- a/src/resolve/resolved-conf.c
+++ b/src/resolve/resolved-conf.c
@@ -234,6 +234,41 @@ int config_parse_support(
return 0;
}
+int config_parse_dnssec(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Manager *m = data;
+ DnssecMode mode;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ mode = dnssec_mode_from_string(rvalue);
+ if (mode < 0) {
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNSSEC mode '%s'. Ignoring.", rvalue);
+ return 0;
+ }
+
+ mode = r ? DNSSEC_YES : DNSSEC_NO;
+ }
+
+ m->unicast_scope->dnssec_mode = mode;
+ return 0;
+}
+
int manager_parse_config_file(Manager *m) {
int r;
diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h
index 28d2549d35..668ea02bba 100644
--- a/src/resolve/resolved-conf.h
+++ b/src/resolve/resolved-conf.h
@@ -36,3 +36,4 @@ const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned len
int config_parse_dns_servers(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_search_domains(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_support(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_dnssec(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h
index be29f51663..db5ee57b51 100644
--- a/src/resolve/resolved-def.h
+++ b/src/resolve/resolved-def.h
@@ -28,6 +28,7 @@
#define SD_RESOLVED_NO_TXT (UINT64_C(1) << 6)
#define SD_RESOLVED_NO_ADDRESS (UINT64_C(1) << 7)
#define SD_RESOLVED_NO_SEARCH (UINT64_C(1) << 8)
+#define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9)
#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6)
#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_LLMNR|SD_RESOLVED_DNS)
diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c
index 3f34017789..bcb9994a8c 100644
--- a/src/resolve/resolved-dns-cache.c
+++ b/src/resolve/resolved-dns-cache.c
@@ -46,6 +46,7 @@ struct DnsCacheItem {
usec_t until;
DnsCacheItemType type;
unsigned prioq_idx;
+ bool authenticated;
int owner_family;
union in_addr_union owner_address;
LIST_FIELDS(DnsCacheItem, by_key);
@@ -237,7 +238,7 @@ static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) {
return NULL;
}
-static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, usec_t timestamp) {
+static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, bool authenticated, usec_t timestamp) {
assert(c);
assert(i);
assert(rr);
@@ -257,6 +258,7 @@ static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsReso
dns_resource_key_unref(i->key);
i->key = dns_resource_key_ref(rr->key);
+ i->authenticated = authenticated;
i->until = timestamp + MIN(rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
prioq_reshuffle(c->by_expiry, i, &i->prioq_idx);
@@ -265,6 +267,7 @@ static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsReso
static int dns_cache_put_positive(
DnsCache *c,
DnsResourceRecord *rr,
+ bool authenticated,
usec_t timestamp,
int owner_family,
const union in_addr_union *owner_address) {
@@ -300,7 +303,7 @@ static int dns_cache_put_positive(
/* Entry exists already? Update TTL and timestamp */
existing = dns_cache_get(c, rr);
if (existing) {
- dns_cache_item_update_positive(c, existing, rr, timestamp);
+ dns_cache_item_update_positive(c, existing, rr, authenticated, timestamp);
return 0;
}
@@ -322,6 +325,7 @@ static int dns_cache_put_positive(
i->prioq_idx = PRIOQ_IDX_NULL;
i->owner_family = owner_family;
i->owner_address = *owner_address;
+ i->authenticated = authenticated;
r = dns_cache_link_item(c, i);
if (r < 0)
@@ -341,6 +345,7 @@ static int dns_cache_put_negative(
DnsCache *c,
DnsResourceKey *key,
int rcode,
+ bool authenticated,
usec_t timestamp,
uint32_t soa_ttl,
int owner_family,
@@ -389,6 +394,7 @@ static int dns_cache_put_negative(
i->prioq_idx = PRIOQ_IDX_NULL;
i->owner_family = owner_family;
i->owner_address = *owner_address;
+ i->authenticated = authenticated;
r = dns_cache_link_item(c, i);
if (r < 0)
@@ -410,6 +416,7 @@ int dns_cache_put(
int rcode,
DnsAnswer *answer,
unsigned max_rrs,
+ bool authenticated,
usec_t timestamp,
int owner_family,
const union in_addr_union *owner_address) {
@@ -452,7 +459,7 @@ int dns_cache_put(
/* Second, add in positive entries for all contained RRs */
for (i = 0; i < MIN(max_rrs, answer->n_rrs); i++) {
- r = dns_cache_put_positive(c, answer->items[i].rr, timestamp, owner_family, owner_address);
+ r = dns_cache_put_positive(c, answer->items[i].rr, authenticated, timestamp, owner_family, owner_address);
if (r < 0)
goto fail;
}
@@ -496,13 +503,13 @@ int dns_cache_put(
if (!dns_answer_match_soa(canonical_key, soa->key))
continue;
- r = dns_cache_put_negative(c, canonical_key, rcode, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
+ r = dns_cache_put_negative(c, canonical_key, rcode, authenticated, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
if (r < 0)
goto fail;
}
}
- r = dns_cache_put_negative(c, key, rcode, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
+ r = dns_cache_put_negative(c, key, rcode, authenticated, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
if (r < 0)
goto fail;
@@ -522,7 +529,6 @@ fail:
}
static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, DnsResourceKey *k) {
- _cleanup_(dns_resource_key_unrefp) DnsResourceKey *nsec_key = NULL, *cname_key = NULL;
DnsCacheItem *i;
const char *n;
int r;
@@ -540,35 +546,23 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D
n = DNS_RESOURCE_KEY_NAME(k);
/* Check if we have an NSEC record instead for the name. */
- nsec_key = dns_resource_key_new(k->class, DNS_TYPE_NSEC, n);
- if (!nsec_key)
- return NULL;
-
- i = hashmap_get(c->by_key, nsec_key);
+ i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n));
if (i)
return i;
/* Check if we have a CNAME record instead */
- cname_key = dns_resource_key_new_cname(k);
- if (!cname_key)
- return NULL;
- i = hashmap_get(c->by_key, cname_key);
+ i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n));
if (i)
return i;
/* OK, let's look for cached DNAME records. */
for (;;) {
- _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dname_key = NULL;
char label[DNS_LABEL_MAX];
if (isempty(n))
return NULL;
- dname_key = dns_resource_key_new(k->class, DNS_TYPE_DNAME, n);
- if (!dname_key)
- return NULL;
-
- i = hashmap_get(c->by_key, dname_key);
+ i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n));
if (i)
return i;
@@ -581,24 +575,26 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D
return NULL;
}
-int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret) {
+int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret, bool *authenticated) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
unsigned n = 0;
int r;
bool nxdomain = false;
_cleanup_free_ char *key_str = NULL;
- DnsResourceRecord *nsec = NULL;
- DnsCacheItem *j, *first;
+ DnsCacheItem *j, *first, *nsec = NULL;
+ bool have_authenticated = false, have_non_authenticated = false;
assert(c);
assert(key);
assert(rcode);
assert(ret);
+ assert(authenticated);
if (key->type == DNS_TYPE_ANY ||
key->class == DNS_CLASS_ANY) {
- /* If we have ANY lookups we simply refresh */
+ /* If we have ANY lookups we don't use the cache, so
+ * that the caller refreshes via the network. */
r = dns_resource_key_to_string(key, &key_str);
if (r < 0)
@@ -629,10 +625,16 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
LIST_FOREACH(by_key, j, first) {
if (j->rr) {
if (j->rr->key->type == DNS_TYPE_NSEC)
- nsec = j->rr;
+ nsec = j;
+
n++;
} else if (j->type == DNS_CACHE_NXDOMAIN)
nxdomain = true;
+
+ if (j->authenticated)
+ have_authenticated = true;
+ else
+ have_non_authenticated = true;
}
r = dns_resource_key_to_string(key, &key_str);
@@ -648,8 +650,11 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
*ret = NULL;
*rcode = DNS_RCODE_SUCCESS;
+ *authenticated = nsec->authenticated;
- return !bitmap_isset(nsec->nsec.types, key->type);
+ return !bitmap_isset(nsec->rr->nsec.types, key->type) &&
+ !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) &&
+ !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME);
}
log_debug("%s cache hit for %s",
@@ -660,6 +665,7 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
if (n <= 0) {
*ret = NULL;
*rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS;
+ *authenticated = have_authenticated && !have_non_authenticated;
return 1;
}
@@ -678,6 +684,7 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
*ret = answer;
*rcode = DNS_RCODE_SUCCESS;
+ *authenticated = have_authenticated && !have_non_authenticated;
answer = NULL;
return n;
diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h
index 561d31ad99..5f91164785 100644
--- a/src/resolve/resolved-dns-cache.h
+++ b/src/resolve/resolved-dns-cache.h
@@ -38,8 +38,8 @@ typedef struct DnsCache {
void dns_cache_flush(DnsCache *c);
void dns_cache_prune(DnsCache *c);
-int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, unsigned max_rrs, usec_t timestamp, int owner_family, const union in_addr_union *owner_address);
-int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **answer);
+int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, unsigned max_rrs, bool authenticated, usec_t timestamp, int owner_family, const union in_addr_union *owner_address);
+int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **answer, bool *authenticated);
int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address);
diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c
index a32e938045..bb0e0ab3da 100644
--- a/src/resolve/resolved-dns-dnssec.c
+++ b/src/resolve/resolved-dns-dnssec.c
@@ -25,17 +25,34 @@
#include "dns-domain.h"
#include "resolved-dns-dnssec.h"
#include "resolved-dns-packet.h"
+#include "string-table.h"
/* Open question:
*
* How does the DNSSEC canonical form of a hostname with a label
* containing a dot look like, the way DNS-SD does it?
*
+ * TODO:
+ *
+ * - Iterative validation
+ * - NSEC proof of non-existance
+ * - NSEC3 proof of non-existance
+ * - Make trust anchor store read additional DS+DNSKEY data from disk
+ * - wildcard zones compatibility
+ * - multi-label zone compatibility
+ * - DMSSEC cname/dname compatibility
+ * - per-interface DNSSEC setting
+ * - DSA support
+ * - EC support?
+ *
* */
#define VERIFY_RRS_MAX 256
#define MAX_KEY_SIZE (32*1024)
+/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
+#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
+
/*
* The DNSSEC Chain of trust:
*
@@ -228,10 +245,14 @@ static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
inception = rrsig->rrsig.inception * USEC_PER_SEC;
if (inception > expiration)
- return -EINVAL;
+ return -EKEYREJECTED;
- /* Permit a certain amount of clock skew of 10% of the valid time range */
+ /* Permit a certain amount of clock skew of 10% of the valid
+ * time range. This takes inspiration from unbound's
+ * resolver. */
skew = (expiration - inception) / 10;
+ if (skew > SKEW_MAX)
+ skew = SKEW_MAX;
if (inception < skew)
inception = 0;
@@ -690,3 +711,10 @@ finish:
gcry_md_close(md);
return r;
}
+
+static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
+ [DNSSEC_NO] = "no",
+ [DNSSEC_TRUST] = "trust",
+ [DNSSEC_YES] = "yes",
+};
+DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);
diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h
index 8f812bc1fb..f4cb58988a 100644
--- a/src/resolve/resolved-dns-dnssec.h
+++ b/src/resolve/resolved-dns-dnssec.h
@@ -21,10 +21,26 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
+typedef enum DnssecMode DnssecMode;
+
#include "dns-domain.h"
#include "resolved-dns-answer.h"
#include "resolved-dns-rr.h"
+enum DnssecMode {
+ /* No DNSSEC validation is done */
+ DNSSEC_NO,
+
+ /* Trust the AD bit sent by the server. UNSAFE! */
+ DNSSEC_TRUST,
+
+ /* Validate locally, if the server knows DO, but if not, don't. Don't trust the AD bit */
+ DNSSEC_YES,
+
+ _DNSSEC_MODE_MAX,
+ _DNSSEC_MODE_INVALID = -1
+};
+
enum {
DNSSEC_VERIFIED,
DNSSEC_INVALID,
@@ -33,7 +49,6 @@ enum {
DNSSEC_SIGNATURE_EXPIRED,
};
-
#define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2)
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey);
@@ -47,3 +62,6 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds);
uint16_t dnssec_keytag(DnsResourceRecord *dnskey);
int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max);
+
+const char* dnssec_mode_to_string(DnssecMode m) _const_;
+DnssecMode dnssec_mode_from_string(const char *s) _pure_;
diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c
index 2a010ef507..ea776f7916 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -65,7 +65,7 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
return 0;
}
-int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
+int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) {
DnsPacket *p;
DnsPacketHeader *h;
int r;
@@ -96,7 +96,7 @@ int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
1 /* rd (ask for recursion) */,
0 /* ra */,
0 /* ad */,
- 0 /* cd */,
+ dnssec_checking_disabled /* cd */,
0 /* rcode */));
*ret = p;
diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h
index a6b88e6c79..aa2823cfb9 100644
--- a/src/resolve/resolved-dns-packet.h
+++ b/src/resolve/resolved-dns-packet.h
@@ -144,7 +144,7 @@ static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) {
}
int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t mtu);
-int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t mtu);
+int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled);
DnsPacket *dns_packet_ref(DnsPacket *p);
DnsPacket *dns_packet_unref(DnsPacket *p);
@@ -225,16 +225,19 @@ DnsProtocol dns_protocol_from_string(const char *s) _pure_;
#define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) })
#define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } })
-static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family) {
+static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) {
+ uint64_t f;
- /* Converts a protocol + family into a flags field as used in queries */
+ /* Converts a protocol + family into a flags field as used in queries and responses */
+
+ f = authenticated ? SD_RESOLVED_AUTHENTICATED : 0;
switch (protocol) {
case DNS_PROTOCOL_DNS:
- return SD_RESOLVED_DNS;
+ return f|SD_RESOLVED_DNS;
case DNS_PROTOCOL_LLMNR:
- return family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4;
+ return f|(family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4);
default:
break;
diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c
index 0f3a0dd21b..089d9fb70d 100644
--- a/src/resolve/resolved-dns-query.c
+++ b/src/resolve/resolved-dns-query.c
@@ -430,15 +430,17 @@ static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) {
return r;
/* If this a single-label domain on DNS, we might append a suitable search domain first. */
- r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question));
- if (r < 0)
- goto fail;
- if (r > 0) {
- /* OK, we need a search domain now. Let's find one for this scope */
-
- r = dns_query_candidate_next_search_domain(c);
- if (r <= 0) /* if there's no search domain, then we won't add any transaction. */
+ if ((q->flags & SD_RESOLVED_NO_SEARCH) == 0) {
+ r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question));
+ if (r < 0)
goto fail;
+ if (r > 0) {
+ /* OK, we need a search domain now. Let's find one for this scope */
+
+ r = dns_query_candidate_next_search_domain(c);
+ if (r <= 0) /* if there's no search domain, then we won't add any transaction. */
+ goto fail;
+ }
}
r = dns_query_candidate_setup_transactions(c);
@@ -970,6 +972,7 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS;
DnsTransaction *t;
Iterator i;
+ bool has_authenticated = false, has_non_authenticated = false;
assert(q);
@@ -997,6 +1000,11 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
q->answer = merged;
q->answer_rcode = t->answer_rcode;
+ if (t->answer_authenticated)
+ has_authenticated = true;
+ else
+ has_non_authenticated = true;
+
state = DNS_TRANSACTION_SUCCESS;
break;
}
@@ -1026,6 +1034,7 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
q->answer_protocol = c->scope->protocol;
q->answer_family = c->scope->family;
+ q->answer_authenticated = has_authenticated && !has_non_authenticated;
dns_search_domain_unref(q->answer_search_domain);
q->answer_search_domain = dns_search_domain_ref(c->search_domain);
diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h
index a9d7904a8d..b71bb2352b 100644
--- a/src/resolve/resolved-dns-query.h
+++ b/src/resolve/resolved-dns-query.h
@@ -75,6 +75,7 @@ struct DnsQuery {
DnsProtocol answer_protocol;
int answer_family;
DnsSearchDomain *answer_search_domain;
+ bool answer_authenticated;
/* Bus client information */
sd_bus_message *request;
diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c
index 281e228b12..934a18334c 100644
--- a/src/resolve/resolved-dns-rr.c
+++ b/src/resolve/resolved-dns-rr.c
@@ -51,12 +51,6 @@ DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *
return k;
}
-DnsResourceKey* dns_resource_key_new_cname(const DnsResourceKey *key) {
- assert(key);
-
- return dns_resource_key_new(key->class, DNS_TYPE_CNAME, DNS_RESOURCE_KEY_NAME(key));
-}
-
DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname) {
int r;
@@ -137,6 +131,10 @@ DnsResourceKey* dns_resource_key_ref(DnsResourceKey *k) {
if (!k)
return NULL;
+ /* Static/const keys created with DNS_RESOURCE_KEY_CONST will
+ * set this to -1, they should not be reffed/unreffed */
+ assert(k->n_ref != (unsigned) -1);
+
assert(k->n_ref > 0);
k->n_ref++;
@@ -147,6 +145,7 @@ DnsResourceKey* dns_resource_key_unref(DnsResourceKey *k) {
if (!k)
return NULL;
+ assert(k->n_ref != (unsigned) -1);
assert(k->n_ref > 0);
if (k->n_ref == 1) {
@@ -158,6 +157,14 @@ DnsResourceKey* dns_resource_key_unref(DnsResourceKey *k) {
return NULL;
}
+bool dns_resource_key_is_address(const DnsResourceKey *key) {
+ assert(key);
+
+ /* Check if this is an A or AAAA resource key */
+
+ return key->class == DNS_CLASS_IN && IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_AAAA);
+}
+
int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) {
int r;
diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h
index 2a103aab8d..b82fa77562 100644
--- a/src/resolve/resolved-dns-rr.h
+++ b/src/resolve/resolved-dns-rr.h
@@ -78,6 +78,19 @@ struct DnsResourceKey {
char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */
};
+/* Creates a temporary resource key. This is only useful to quickly
+ * look up something, without allocating a full DnsResourceKey object
+ * for it. Note that it is not OK to take references to this kind of
+ * resource key object. */
+#define DNS_RESOURCE_KEY_CONST(c, t, n) \
+ ((DnsResourceKey) { \
+ .n_ref = (unsigned) -1, \
+ .class = c, \
+ .type = t, \
+ ._name = (char*) n, \
+ })
+
+
struct DnsTxtItem {
size_t length;
LIST_FIELDS(DnsTxtItem, items);
@@ -221,13 +234,12 @@ static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) {
}
DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name);
-DnsResourceKey* dns_resource_key_new_cname(const DnsResourceKey *key);
-DnsResourceKey* dns_resource_key_new_dname(const DnsResourceKey *key);
DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname);
int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name);
DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name);
DnsResourceKey* dns_resource_key_ref(DnsResourceKey *key);
DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key);
+bool dns_resource_key_is_address(const DnsResourceKey *key);
int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b);
int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain);
int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain);
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
index 846280e8b8..a90692cdf4 100644
--- a/src/resolve/resolved-dns-scope.c
+++ b/src/resolve/resolved-dns-scope.c
@@ -368,13 +368,14 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
assert(s);
assert(domain);
- if (ifindex != 0 && (!s->link || s->link->ifindex != ifindex))
- return DNS_SCOPE_NO;
+ /* Checks if the specified domain is something to look up on
+ * this scope. Note that this accepts non-qualified hostnames,
+ * i.e. those without any search path prefixed yet. */
- if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family) & flags) == 0)
+ if (ifindex != 0 && (!s->link || s->link->ifindex != ifindex))
return DNS_SCOPE_NO;
- if (dns_name_is_root(domain))
+ if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family, 0) & flags) == 0)
return DNS_SCOPE_NO;
/* Never resolve any loopback hostname or IP address via DNS,
@@ -385,6 +386,12 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
dns_name_equal(domain, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0)
return DNS_SCOPE_NO;
+ /* Never respond to some of the domains listed in RFC6303 */
+ if (dns_name_endswith(domain, "0.in-addr.arpa") > 0 ||
+ dns_name_equal(domain, "255.255.255.255.in-addr.arpa") > 0 ||
+ dns_name_equal(domain, "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0)
+ return DNS_SCOPE_NO;
+
/* Always honour search domains for routing queries. Note that
* we return DNS_SCOPE_YES here, rather than just
* DNS_SCOPE_MAYBE, which means wildcard scopes won't be
@@ -397,10 +404,12 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
case DNS_PROTOCOL_DNS:
- if ((!dns_name_is_single_label(domain) ||
- (!(flags & SD_RESOLVED_NO_SEARCH) && dns_scope_has_search_domains(s))) &&
- dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 &&
- dns_name_endswith(domain, "0.8.e.f.ip6.arpa") == 0)
+ /* Exclude link-local IP ranges */
+ if (dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 &&
+ dns_name_endswith(domain, "8.e.f.ip6.arpa") == 0 &&
+ dns_name_endswith(domain, "9.e.f.ip6.arpa") == 0 &&
+ dns_name_endswith(domain, "a.e.f.ip6.arpa") == 0 &&
+ dns_name_endswith(domain, "b.e.f.ip6.arpa") == 0)
return DNS_SCOPE_MAYBE;
return DNS_SCOPE_NO;
@@ -434,8 +443,27 @@ int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) {
assert(s);
assert(key);
- if (s->protocol == DNS_PROTOCOL_DNS)
- return true;
+ /* Check if it makes sense to resolve the specified key on
+ * this scope. Note that this call assumes as fully qualified
+ * name, i.e. the search suffixes already appended. */
+
+ if (s->protocol == DNS_PROTOCOL_DNS) {
+
+ /* On classic DNS, lookin up non-address RRs is always
+ * fine. (Specifically, we want to permit looking up
+ * DNSKEY and DS records on the root and top-level
+ * domains.) */
+ if (!dns_resource_key_is_address(key))
+ return true;
+
+ /* However, we refuse to look up A and AAAA RRs on the
+ * root and single-label domains, under the assumption
+ * that those should be resolved via LLMNR or search
+ * path only, and should not be leaked onto the
+ * internet. */
+ return !(dns_name_is_single_label(DNS_RESOURCE_KEY_NAME(key)) ||
+ dns_name_is_root(DNS_RESOURCE_KEY_NAME(key)));
+ }
/* On mDNS and LLMNR, send A and AAAA queries only on the
* respective scopes */
@@ -901,34 +929,13 @@ void dns_scope_dump(DnsScope *s, FILE *f) {
DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s) {
assert(s);
- /* Returns the list of *local* search domains -- not the
- * global ones. */
-
if (s->protocol != DNS_PROTOCOL_DNS)
return NULL;
if (s->link)
return s->link->search_domains;
- return NULL;
-}
-
-bool dns_scope_has_search_domains(DnsScope *s) {
- assert(s);
-
- /* Tests if there are *any* search domains suitable for this
- * scope. This means either local or global ones */
-
- if (s->protocol != DNS_PROTOCOL_DNS)
- return false;
-
- if (s->manager->search_domains)
- return true;
-
- if (s->link && s->link->search_domains)
- return true;
-
- return false;
+ return s->manager->search_domains;
}
bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) {
diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h
index 0480f702f8..15d9a1fd6f 100644
--- a/src/resolve/resolved-dns-scope.h
+++ b/src/resolve/resolved-dns-scope.h
@@ -26,6 +26,7 @@
typedef struct DnsScope DnsScope;
#include "resolved-dns-cache.h"
+#include "resolved-dns-dnssec.h"
#include "resolved-dns-packet.h"
#include "resolved-dns-server.h"
#include "resolved-dns-zone.h"
@@ -44,6 +45,7 @@ struct DnsScope {
DnsProtocol protocol;
int family;
+ DnssecMode dnssec_mode;
Link *link;
@@ -102,6 +104,5 @@ void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p);
void dns_scope_dump(DnsScope *s, FILE *f);
DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s);
-bool dns_scope_has_search_domains(DnsScope *s);
bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name);
diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h
index 00366a48c9..b07fc3af3d 100644
--- a/src/resolve/resolved-dns-server.h
+++ b/src/resolve/resolved-dns-server.h
@@ -61,10 +61,11 @@ struct DnsServer {
int family;
union in_addr_union address;
+ bool marked:1;
+
usec_t resend_timeout;
usec_t max_rtt;
- bool marked:1;
DnsServerFeatureLevel verified_features;
DnsServerFeatureLevel possible_features;
size_t received_udp_packet_max;
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
index 90133cb332..1103a34c6f 100644
--- a/src/resolve/resolved-dns-transaction.c
+++ b/src/resolve/resolved-dns-transaction.c
@@ -481,20 +481,29 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
return;
}
- /* Install the answer as answer to the transaction */
- dns_answer_unref(t->answer);
- t->answer = dns_answer_ref(p->answer);
- t->answer_rcode = DNS_PACKET_RCODE(p);
-
/* Only consider responses with equivalent query section to the request */
if (p->question->n_keys != 1 || dns_resource_key_equal(p->question->keys[0], t->key) <= 0) {
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
return;
}
+ /* Install the answer as answer to the transaction */
+ dns_answer_unref(t->answer);
+ t->answer = dns_answer_ref(p->answer);
+ t->answer_rcode = DNS_PACKET_RCODE(p);
+ t->answer_authenticated = t->scope->dnssec_mode == DNSSEC_TRUST && DNS_PACKET_AD(p);
+
/* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */
if (DNS_PACKET_SHALL_CACHE(p))
- dns_cache_put(&t->scope->cache, t->key, DNS_PACKET_RCODE(p), p->answer, DNS_PACKET_ANCOUNT(p), 0, p->family, &p->sender);
+ dns_cache_put(&t->scope->cache,
+ t->key,
+ DNS_PACKET_RCODE(p),
+ p->answer,
+ DNS_PACKET_ANCOUNT(p),
+ t->answer_authenticated,
+ 0,
+ p->family,
+ &p->sender);
if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS)
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
@@ -598,7 +607,7 @@ static int dns_transaction_make_packet(DnsTransaction *t) {
if (t->sent)
return 0;
- r = dns_packet_new_query(&p, t->scope->protocol, 0);
+ r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES);
if (r < 0)
return r;
@@ -675,7 +684,21 @@ int dns_transaction_go(DnsTransaction *t) {
t->answer_rcode = 0;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
- /* Check the zone, but obly if this transaction is not used
+ /* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */
+ if (t->scope->protocol == DNS_PROTOCOL_DNS) {
+ r = dns_trust_anchor_lookup(&t->scope->manager->trust_anchor, t->key, &t->answer);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ t->answer_rcode = DNS_RCODE_SUCCESS;
+ t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR;
+ t->answer_authenticated = true;
+ dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
+ return 0;
+ }
+ }
+
+ /* Check the zone, but only if this transaction is not used
* for probing or verifying a zone item. */
if (set_isempty(t->zone_items)) {
@@ -685,6 +708,7 @@ int dns_transaction_go(DnsTransaction *t) {
if (r > 0) {
t->answer_rcode = DNS_RCODE_SUCCESS;
t->answer_source = DNS_TRANSACTION_ZONE;
+ t->answer_authenticated = true;
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
return 0;
}
@@ -702,7 +726,7 @@ int dns_transaction_go(DnsTransaction *t) {
/* Let's then prune all outdated entries */
dns_cache_prune(&t->scope->cache);
- r = dns_cache_lookup(&t->scope->cache, t->key, &t->answer_rcode, &t->answer);
+ r = dns_cache_lookup(&t->scope->cache, t->key, &t->answer_rcode, &t->answer, &t->answer_authenticated);
if (r < 0)
return r;
if (r > 0) {
@@ -817,5 +841,6 @@ static const char* const dns_transaction_source_table[_DNS_TRANSACTION_SOURCE_MA
[DNS_TRANSACTION_NETWORK] = "network",
[DNS_TRANSACTION_CACHE] = "cache",
[DNS_TRANSACTION_ZONE] = "zone",
+ [DNS_TRANSACTION_TRUST_ANCHOR] = "trust-anchor",
};
DEFINE_STRING_TABLE_LOOKUP(dns_transaction_source, DnsTransactionSource);
diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h
index 5778913cc8..a3058ce6e8 100644
--- a/src/resolve/resolved-dns-transaction.h
+++ b/src/resolve/resolved-dns-transaction.h
@@ -44,6 +44,7 @@ enum DnsTransactionSource {
DNS_TRANSACTION_NETWORK,
DNS_TRANSACTION_CACHE,
DNS_TRANSACTION_ZONE,
+ DNS_TRANSACTION_TRUST_ANCHOR,
_DNS_TRANSACTION_SOURCE_MAX,
_DNS_TRANSACTION_SOURCE_INVALID = -1
};
@@ -68,6 +69,7 @@ struct DnsTransaction {
DnsAnswer *answer;
int answer_rcode;
DnsTransactionSource answer_source;
+ bool answer_authenticated;
usec_t start_usec;
sd_event_source *timeout_event_source;
diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c
new file mode 100644
index 0000000000..e55bdaa1ed
--- /dev/null
+++ b/src/resolve/resolved-dns-trust-anchor.c
@@ -0,0 +1,101 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "alloc-util.h"
+#include "resolved-dns-trust-anchor.h"
+
+/* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml */
+static const uint8_t root_digest[] =
+ { 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60,
+ 0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 };
+
+int dns_trust_anchor_load(DnsTrustAnchor *d) {
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
+ _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+ int r;
+
+ assert(d);
+
+ r = hashmap_ensure_allocated(&d->by_key, &dns_resource_key_hash_ops);
+ if (r < 0)
+ return r;
+
+ if (hashmap_get(d->by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, ".")))
+ return 0;
+
+ /* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */
+ rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "");
+ if (!rr)
+ return -ENOMEM;
+
+ rr->ds.key_tag = 19036;
+ rr->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256;
+ rr->ds.digest_type = DNSSEC_DIGEST_SHA256;
+ rr->ds.digest_size = sizeof(root_digest);
+ rr->ds.digest = memdup(root_digest, rr->ds.digest_size);
+ if (!rr->ds.digest)
+ return -ENOMEM;
+
+ answer = dns_answer_new(1);
+ if (!answer)
+ return -ENOMEM;
+
+ r = dns_answer_add(answer, rr, 0);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(d->by_key, rr->key, answer);
+ if (r < 0)
+ return r;
+
+ answer = NULL;
+ return 0;
+}
+
+void dns_trust_anchor_flush(DnsTrustAnchor *d) {
+ DnsAnswer *a;
+
+ assert(d);
+
+ while ((a = hashmap_steal_first(d->by_key)))
+ dns_answer_unref(a);
+
+ d->by_key = hashmap_free(d->by_key);
+}
+
+int dns_trust_anchor_lookup(DnsTrustAnchor *d, DnsResourceKey *key, DnsAnswer **ret) {
+ DnsAnswer *a;
+
+ assert(d);
+ assert(key);
+ assert(ret);
+
+ /* We only serve DS and DNSKEY RRs. */
+ if (!IN_SET(key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY))
+ return 0;
+
+ a = hashmap_get(d->by_key, key);
+ if (!a)
+ return 0;
+
+ *ret = dns_answer_ref(a);
+ return 1;
+}
diff --git a/src/resolve/resolved-dns-trust-anchor.h b/src/resolve/resolved-dns-trust-anchor.h
new file mode 100644
index 0000000000..06f3723914
--- /dev/null
+++ b/src/resolve/resolved-dns-trust-anchor.h
@@ -0,0 +1,39 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct DnsTrustAnchor DnsTrustAnchor;
+
+#include "hashmap.h"
+#include "resolved-dns-answer.h"
+#include "resolved-dns-rr.h"
+
+/* This contains a fixed database mapping domain names to DS or DNSKEY records. */
+
+struct DnsTrustAnchor {
+ Hashmap *by_key;
+};
+
+int dns_trust_anchor_load(DnsTrustAnchor *d);
+void dns_trust_anchor_flush(DnsTrustAnchor *d);
+
+int dns_trust_anchor_lookup(DnsTrustAnchor *d, DnsResourceKey* key, DnsAnswer **answer);
diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c
index 493d11dd14..78f44d51a2 100644
--- a/src/resolve/resolved-dns-zone.c
+++ b/src/resolve/resolved-dns-zone.c
@@ -163,7 +163,6 @@ static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
}
static int dns_zone_item_probe_start(DnsZoneItem *i) {
- _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
DnsTransaction *t;
int r;
@@ -172,12 +171,14 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) {
if (i->probe_transaction)
return 0;
- key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key));
- if (!key)
- return -ENOMEM;
-
- t = dns_scope_find_transaction(i->scope, key, false);
+ t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key)), false);
if (!t) {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
+
+ key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key));
+ if (!key)
+ return -ENOMEM;
+
r = dns_transaction_new(&t, i->scope, key);
if (r < 0)
return r;
diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf
index 50662656d5..c815eae850 100644
--- a/src/resolve/resolved-gperf.gperf
+++ b/src/resolve/resolved-gperf.gperf
@@ -18,3 +18,4 @@ Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0
Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0
Resolve.Domains, config_parse_search_domains, 0, 0
Resolve.LLMNR, config_parse_support, 0, offsetof(Manager, llmnr_support)
+Resolve.DNSSEC, config_parse_dnssec, 0, 0
diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c
index 62562f0d24..5a3696ccb0 100644
--- a/src/resolve/resolved-manager.c
+++ b/src/resolve/resolved-manager.c
@@ -478,6 +478,10 @@ int manager_new(Manager **ret) {
m->read_resolv_conf = true;
m->need_builtin_fallbacks = true;
+ r = dns_trust_anchor_load(&m->trust_anchor);
+ if (r < 0)
+ return r;
+
r = sd_event_default(&m->event);
if (r < 0)
return r;
@@ -572,6 +576,8 @@ Manager *manager_free(Manager *m) {
free(m->llmnr_hostname);
free(m->mdns_hostname);
+ dns_trust_anchor_flush(&m->trust_anchor);
+
free(m);
return NULL;
diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h
index d00c444583..1056f23ab7 100644
--- a/src/resolve/resolved-manager.h
+++ b/src/resolve/resolved-manager.h
@@ -44,6 +44,7 @@ enum Support {
#include "resolved-dns-search-domain.h"
#include "resolved-dns-server.h"
#include "resolved-dns-stream.h"
+#include "resolved-dns-trust-anchor.h"
#include "resolved-link.h"
#define MANAGER_SEARCH_DOMAINS_MAX 32
@@ -85,6 +86,8 @@ struct Manager {
bool read_resolv_conf:1;
usec_t resolv_conf_mtime;
+ DnsTrustAnchor trust_anchor;
+
LIST_HEAD(DnsScope, dns_scopes);
DnsScope *unicast_scope;
diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in
index 39ecf83217..efc9c6733a 100644
--- a/src/resolve/resolved.conf.in
+++ b/src/resolve/resolved.conf.in
@@ -16,3 +16,4 @@
#FallbackDNS=@DNS_SERVERS@
#Domains=
#LLMNR=yes
+#DNSSEC=no