diff options
Diffstat (limited to 'src/resolve')
34 files changed, 2451 insertions, 336 deletions
diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index f86c4ceb05..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; @@ -425,8 +425,6 @@ fail: } static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int ifindex) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - size_t start; int r; assert(m); @@ -443,17 +441,11 @@ static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int i if (r < 0) return r; - r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); + r = dns_resource_record_to_wire_format(rr, false); if (r < 0) return r; - p->refuse_compression = true; - - r = dns_packet_append_rr(p, rr, &start); - if (r < 0) - return r; - - r = sd_bus_message_append_array(m, 'y', DNS_PACKET_DATA(p) + start, p->size - start); + r = sd_bus_message_append_array(m, 'y', rr->wire_format, rr->wire_format_size); if (r < 0) return r; @@ -518,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; @@ -867,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..6014d345f3 100644 --- a/src/resolve/resolved-def.h +++ b/src/resolve/resolved-def.h @@ -24,10 +24,15 @@ #define SD_RESOLVED_DNS (UINT64_C(1) << 0) #define SD_RESOLVED_LLMNR_IPV4 (UINT64_C(1) << 1) #define SD_RESOLVED_LLMNR_IPV6 (UINT64_C(1) << 2) +#define SD_RESOLVED_MDNS_IPV4 (UINT64_C(1) << 3) +#define SD_RESOLVED_MDNS_IPV6 (UINT64_C(1) << 4) #define SD_RESOLVED_NO_CNAME (UINT64_C(1) << 5) #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) +#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) diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h index 8814919deb..89c254b02e 100644 --- a/src/resolve/resolved-dns-answer.h +++ b/src/resolve/resolved-dns-answer.h @@ -24,6 +24,7 @@ typedef struct DnsAnswer DnsAnswer; typedef struct DnsAnswerItem DnsAnswerItem; +#include "macro.h" #include "resolved-dns-rr.h" /* A simple array of resource records. We keep track of the @@ -59,19 +60,23 @@ int dns_answer_reserve(DnsAnswer **a, unsigned n_free); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); -#define DNS_ANSWER_FOREACH(kk, a) \ - for (unsigned _i = ({ \ +#define _DNS_ANSWER_FOREACH(q, kk, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ 0; \ - }); \ - (a) && ((_i) < (a)->n_rrs); \ - _i++, (kk) = (_i < (a)->n_rrs ? (a)->items[_i].rr : NULL)) + }); \ + (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q)++, (kk) = (UNIQ_T(i, q) < (a)->n_rrs ? (a)->items[UNIQ_T(i, q)].rr : NULL)) -#define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) \ - for (unsigned _i = ({ \ +#define DNS_ANSWER_FOREACH(kk, a) _DNS_ANSWER_FOREACH(UNIQ, kk, a) + +#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifindex, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ (ifindex) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ 0; \ - }); \ - (a) && ((_i) < (a)->n_rrs); \ - _i++, (kk) = ((_i < (a)->n_rrs) ? (a)->items[_i].rr : NULL), (ifindex) = ((_i < (a)->n_rrs) ? (a)->items[_i].ifindex : 0)) + }); \ + (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q)++, (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), (ifindex) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0)) + +#define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) _DNS_ANSWER_FOREACH_IFINDEX(UNIQ, kk, ifindex, a) diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index d963ce6e00..1774ae6cb8 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,12 @@ 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); + DnsResourceRecord *rr = answer->items[i].rr; + + if (rr->key->cache_flush) + dns_cache_remove(c, rr->key); + + r = dns_cache_put_positive(c, rr, authenticated, timestamp, owner_family, owner_address); if (r < 0) goto fail; } @@ -496,13 +508,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; @@ -521,8 +533,7 @@ fail: return r; } -static DnsCacheItem *dns_cache_get_by_key_follow_cname(DnsCache *c, DnsResourceKey *k) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *cname_key = NULL; +static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, DnsResourceKey *k) { DnsCacheItem *i; const char *n; int r; @@ -534,32 +545,29 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname(DnsCache *c, DnsResourceK * much, after all this is just a cache */ i = hashmap_get(c->by_key, k); - if (i || k->type == DNS_TYPE_CNAME || k->type == DNS_TYPE_DNAME) + if (i || IN_SET(k->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME, DNS_TYPE_NSEC)) return i; - /* Check if we have a CNAME record instead */ - cname_key = dns_resource_key_new_cname(k); - if (!cname_key) - return NULL; + n = DNS_RESOURCE_KEY_NAME(k); - i = hashmap_get(c->by_key, cname_key); + /* Check if we have an NSEC record instead for the name. */ + 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 */ + 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. */ - n = DNS_RESOURCE_KEY_NAME(k); 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; @@ -572,23 +580,26 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname(DnsCache *c, DnsResourceK 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; - 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) @@ -601,7 +612,7 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r return 0; } - first = dns_cache_get_by_key_follow_cname(c, key); + first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key); if (!first) { /* If one question cannot be answered we need to refresh */ @@ -617,24 +628,49 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r } LIST_FOREACH(by_key, j, first) { - if (j->rr) + if (j->rr) { + if (j->rr->key->type == DNS_TYPE_NSEC) + nsec = j; + n++; - else if (j->type == DNS_CACHE_NXDOMAIN) + } 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); if (r < 0) return r; + if (nsec && key->type != DNS_TYPE_NSEC) { + log_debug("NSEC NODATA cache hit for %s", key_str); + + /* We only found an NSEC record that matches our name. + * If it says the type doesn't exit report + * NODATA. Otherwise report a cache miss. */ + + *ret = NULL; + *rcode = DNS_RCODE_SUCCESS; + *authenticated = nsec->authenticated; + + 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", - nxdomain ? "NXDOMAIN" : - n > 0 ? "Positive" : "NODATA", + n > 0 ? "Positive" : + nxdomain ? "NXDOMAIN" : "NODATA", key_str); if (n <= 0) { *ret = NULL; *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; + *authenticated = have_authenticated && !have_non_authenticated; return 1; } @@ -653,6 +689,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; @@ -694,6 +731,57 @@ int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_ return 1; } +int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) { + unsigned ancount = 0; + Iterator iterator; + DnsCacheItem *i; + int r; + + assert(cache); + assert(p); + + HASHMAP_FOREACH(i, cache->by_key, iterator) { + DnsCacheItem *j; + + LIST_FOREACH(by_key, j, i) { + _cleanup_free_ char *t = NULL; + + if (!j->rr) + continue; + + if (!dns_key_is_shared(j->rr->key)) + continue; + + r = dns_packet_append_rr(p, j->rr, NULL, NULL); + if (r == -EMSGSIZE && p->protocol == DNS_PROTOCOL_MDNS) { + /* For mDNS, if we're unable to stuff all known answers into the given packet, + * allocate a new one, push the RR into that one and link it to the current one. + */ + + DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); + ancount = 0; + + r = dns_packet_new_query(&p->more, p->protocol, 0, true); + if (r < 0) + return r; + + /* continue with new packet */ + p = p->more; + r = dns_packet_append_rr(p, j->rr, NULL, NULL); + } + + if (r < 0) + return r; + + ancount ++; + } + } + + DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); + + return 0; +} + void dns_cache_dump(DnsCache *cache, FILE *f) { Iterator iterator; DnsCacheItem *i; diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h index 164435b4fb..0f28bbe543 100644 --- a/src/resolve/resolved-dns-cache.h +++ b/src/resolve/resolved-dns-cache.h @@ -21,7 +21,6 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ - #include "hashmap.h" #include "list.h" #include "prioq.h" @@ -33,16 +32,19 @@ typedef struct DnsCache { } DnsCache; #include "resolved-dns-answer.h" +#include "resolved-dns-packet.h" #include "resolved-dns-question.h" #include "resolved-dns-rr.h" 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); void dns_cache_dump(DnsCache *cache, FILE *f); bool dns_cache_is_empty(DnsCache *cache); + +int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p); diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c new file mode 100644 index 0000000000..2d06775dca --- /dev/null +++ b/src/resolve/resolved-dns-dnssec.c @@ -0,0 +1,719 @@ +/*-*- 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 <gcrypt.h> + +#include "alloc-util.h" +#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: + * + * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone + * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree + * DS RRs are protected like normal RRs + * + * Example chain: + * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS + */ + +static bool dnssec_algorithm_supported(int algorithm) { + return IN_SET(algorithm, + DNSSEC_ALGORITHM_RSASHA1, + DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, + DNSSEC_ALGORITHM_RSASHA256, + DNSSEC_ALGORITHM_RSASHA512); +} + +static bool dnssec_digest_supported(int digest) { + return IN_SET(digest, + DNSSEC_DIGEST_SHA1, + DNSSEC_DIGEST_SHA256); +} + +uint16_t dnssec_keytag(DnsResourceRecord *dnskey) { + const uint8_t *p; + uint32_t sum; + size_t i; + + /* The algorithm from RFC 4034, Appendix B. */ + + assert(dnskey); + assert(dnskey->key->type == DNS_TYPE_DNSKEY); + + sum = (uint32_t) dnskey->dnskey.flags + + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm); + + p = dnskey->dnskey.key; + + for (i = 0; i < dnskey->dnskey.key_size; i++) + sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i]; + + sum += (sum >> 16) & UINT32_C(0xFFFF); + + return sum & UINT32_C(0xFFFF); +} + +static int rr_compare(const void *a, const void *b) { + DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b; + size_t m; + int r; + + /* Let's order the RRs according to RFC 4034, Section 6.3 */ + + assert(x); + assert(*x); + assert((*x)->wire_format); + assert(y); + assert(*y); + assert((*y)->wire_format); + + m = MIN((*x)->wire_format_size, (*y)->wire_format_size); + + r = memcmp((*x)->wire_format, (*y)->wire_format, m); + if (r != 0) + return r; + + if ((*x)->wire_format_size < (*y)->wire_format_size) + return -1; + else if ((*x)->wire_format_size > (*y)->wire_format_size) + return 1; + + return 0; +} + +static int dnssec_rsa_verify( + const char *hash_algorithm, + const void *signature, size_t signature_size, + const void *data, size_t data_size, + const void *exponent, size_t exponent_size, + const void *modulus, size_t modulus_size) { + + gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; + gcry_mpi_t n = NULL, e = NULL, s = NULL; + gcry_error_t ge; + int r; + + assert(hash_algorithm); + + ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&signature_sexp, + NULL, + "(sig-val (rsa (s %m)))", + s); + + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&data_sexp, + NULL, + "(data (flags pkcs1) (hash %s %b))", + hash_algorithm, + (int) data_size, + data); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&public_key_sexp, + NULL, + "(public-key (rsa (n %m) (e %m)))", + n, + e); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); + if (ge == GPG_ERR_BAD_SIGNATURE) + r = 0; + else if (ge != 0) + r = -EIO; + else + r = 1; + +finish: + if (e) + gcry_mpi_release(e); + if (n) + gcry_mpi_release(n); + if (s) + gcry_mpi_release(s); + + if (public_key_sexp) + gcry_sexp_release(public_key_sexp); + if (signature_sexp) + gcry_sexp_release(signature_sexp); + if (data_sexp) + gcry_sexp_release(data_sexp); + + return r; +} + +static void md_add_uint8(gcry_md_hd_t md, uint8_t v) { + gcry_md_write(md, &v, sizeof(v)); +} + +static void md_add_uint16(gcry_md_hd_t md, uint16_t v) { + v = htobe16(v); + gcry_md_write(md, &v, sizeof(v)); +} + +static void md_add_uint32(gcry_md_hd_t md, uint32_t v) { + v = htobe32(v); + gcry_md_write(md, &v, sizeof(v)); +} + +static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) { + usec_t expiration, inception, skew; + + assert(rrsig); + assert(rrsig->key->type == DNS_TYPE_RRSIG); + + if (realtime == USEC_INFINITY) + realtime = now(CLOCK_REALTIME); + + expiration = rrsig->rrsig.expiration * USEC_PER_SEC; + inception = rrsig->rrsig.inception * USEC_PER_SEC; + + if (inception > expiration) + return -EKEYREJECTED; + + /* 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; + else + inception -= skew; + + if (expiration + skew < expiration) + expiration = USEC_INFINITY; + else + expiration += skew; + + return realtime < inception || realtime > expiration; +} + +int dnssec_verify_rrset( + DnsAnswer *a, + DnsResourceKey *key, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey, + usec_t realtime) { + + uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX]; + size_t exponent_size, modulus_size, hash_size; + void *exponent, *modulus, *hash; + DnsResourceRecord **list, *rr; + gcry_md_hd_t md = NULL; + size_t k, n = 0; + int r; + + assert(key); + assert(rrsig); + assert(dnskey); + assert(rrsig->key->type == DNS_TYPE_RRSIG); + assert(dnskey->key->type == DNS_TYPE_DNSKEY); + + /* Verifies the the RRSet matching the specified "key" in "a", + * using the signature "rrsig" and the key "dnskey". It's + * assumed the RRSIG and DNSKEY match. */ + + if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm)) + return -EOPNOTSUPP; + + if (a->n_rrs > VERIFY_RRS_MAX) + return -E2BIG; + + r = dnssec_rrsig_expired(rrsig, realtime); + if (r < 0) + return r; + if (r > 0) + return DNSSEC_SIGNATURE_EXPIRED; + + /* Collect all relevant RRs in a single array, so that we can look at the RRset */ + list = newa(DnsResourceRecord *, a->n_rrs); + + DNS_ANSWER_FOREACH(rr, a) { + r = dns_resource_key_equal(key, rr->key); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We need the wire format for ordering, and digest calculation */ + r = dns_resource_record_to_wire_format(rr, true); + if (r < 0) + return r; + + list[n++] = rr; + } + + if (n <= 0) + return -ENODATA; + + /* Bring the RRs into canonical order */ + qsort_safe(list, n, sizeof(DnsResourceRecord), rr_compare); + + /* OK, the RRs are now in canonical order. Let's calculate the digest */ + switch (rrsig->rrsig.algorithm) { + + case DNSSEC_ALGORITHM_RSASHA1: + case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: + gcry_md_open(&md, GCRY_MD_SHA1, 0); + hash_size = 20; + break; + + case DNSSEC_ALGORITHM_RSASHA256: + gcry_md_open(&md, GCRY_MD_SHA256, 0); + hash_size = 32; + break; + + case DNSSEC_ALGORITHM_RSASHA512: + gcry_md_open(&md, GCRY_MD_SHA512, 0); + hash_size = 64; + break; + + default: + assert_not_reached("Unknown digest"); + } + + if (!md) + return -EIO; + + md_add_uint16(md, rrsig->rrsig.type_covered); + md_add_uint8(md, rrsig->rrsig.algorithm); + md_add_uint8(md, rrsig->rrsig.labels); + md_add_uint32(md, rrsig->rrsig.original_ttl); + md_add_uint32(md, rrsig->rrsig.expiration); + md_add_uint32(md, rrsig->rrsig.inception); + md_add_uint16(md, rrsig->rrsig.key_tag); + + r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true); + if (r < 0) + goto finish; + gcry_md_write(md, wire_format_name, r); + + for (k = 0; k < n; k++) { + size_t l; + rr = list[k]; + + r = dns_name_to_wire_format(DNS_RESOURCE_KEY_NAME(rr->key), wire_format_name, sizeof(wire_format_name), true); + if (r < 0) + goto finish; + gcry_md_write(md, wire_format_name, r); + + md_add_uint16(md, rr->key->type); + md_add_uint16(md, rr->key->class); + md_add_uint32(md, rrsig->rrsig.original_ttl); + + assert(rr->wire_format_rdata_offset <= rr->wire_format_size); + l = rr->wire_format_size - rr->wire_format_rdata_offset; + assert(l <= 0xFFFF); + + md_add_uint16(md, (uint16_t) l); + gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l); + } + + hash = gcry_md_read(md, 0); + if (!hash) { + r = -EIO; + goto finish; + } + + if (*(uint8_t*) dnskey->dnskey.key == 0) { + /* exponent is > 255 bytes long */ + + exponent = (uint8_t*) dnskey->dnskey.key + 3; + exponent_size = + ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) | + ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]); + + if (exponent_size < 256) { + r = -EINVAL; + goto finish; + } + + if (3 + exponent_size >= dnskey->dnskey.key_size) { + r = -EINVAL; + goto finish; + } + + modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size; + modulus_size = dnskey->dnskey.key_size - 3 - exponent_size; + + } else { + /* exponent is <= 255 bytes long */ + + exponent = (uint8_t*) dnskey->dnskey.key + 1; + exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0]; + + if (exponent_size <= 0) { + r = -EINVAL; + goto finish; + } + + if (1 + exponent_size >= dnskey->dnskey.key_size) { + r = -EINVAL; + goto finish; + } + + modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size; + modulus_size = dnskey->dnskey.key_size - 1 - exponent_size; + } + + r = dnssec_rsa_verify( + gcry_md_algo_name(gcry_md_get_algo(md)), + rrsig->rrsig.signature, rrsig->rrsig.signature_size, + hash, hash_size, + exponent, exponent_size, + modulus, modulus_size); + if (r < 0) + goto finish; + + r = r ? DNSSEC_VERIFIED : DNSSEC_INVALID; + +finish: + gcry_md_close(md); + return r; +} + +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) { + + assert(rrsig); + assert(dnskey); + + /* Checks if the specified DNSKEY RR matches the key used for + * the signature in the specified RRSIG RR */ + + if (rrsig->key->type != DNS_TYPE_RRSIG) + return -EINVAL; + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return 0; + if (dnskey->key->class != rrsig->key->class) + return 0; + if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) + return 0; + if (dnskey->dnskey.protocol != 3) + return 0; + if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm) + return 0; + + if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag) + return 0; + + return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(rrsig->key)); +} + +int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) { + assert(key); + assert(rrsig); + + /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */ + + if (rrsig->key->type != DNS_TYPE_RRSIG) + return 0; + if (rrsig->key->class != key->class) + return 0; + if (rrsig->rrsig.type_covered != key->type) + return 0; + + return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key)); +} + +int dnssec_verify_rrset_search( + DnsAnswer *a, + DnsResourceKey *key, + DnsAnswer *validated_dnskeys, + usec_t realtime) { + + bool found_rrsig = false, found_dnskey = false; + DnsResourceRecord *rrsig; + int r; + + assert(key); + + /* Verifies all RRs from "a" that match the key "key", against DNSKEY RRs in "validated_dnskeys" */ + + if (!a || a->n_rrs <= 0) + return -ENODATA; + + /* Iterate through each RRSIG RR. */ + DNS_ANSWER_FOREACH(rrsig, a) { + DnsResourceRecord *dnskey; + + r = dnssec_key_match_rrsig(key, rrsig); + if (r < 0) + return r; + if (r == 0) + continue; + + found_rrsig = true; + + DNS_ANSWER_FOREACH(dnskey, validated_dnskeys) { + + r = dnssec_rrsig_match_dnskey(rrsig, dnskey); + if (r < 0) + return r; + if (r == 0) + continue; + + found_dnskey = true; + + /* Take the time here, if it isn't set yet, so + * that we do all validations with the same + * time. */ + if (realtime == USEC_INFINITY) + realtime = now(CLOCK_REALTIME); + + /* Yay, we found a matching RRSIG with a matching + * DNSKEY, awesome. Now let's verify all entries of + * the RRSet against the RRSIG and DNSKEY + * combination. */ + + r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime); + if (r < 0 && r != EOPNOTSUPP) + return r; + if (r == DNSSEC_VERIFIED) + return DNSSEC_VERIFIED; + + /* If the signature is invalid, or done using + an unsupported algorithm, let's try another + key and/or signature. After all they + key_tags and stuff are not unique, and + might be shared by multiple keys. */ + } + } + + if (found_dnskey) + return DNSSEC_INVALID; + + if (found_rrsig) + return DNSSEC_MISSING_KEY; + + return DNSSEC_NO_SIGNATURE; +} + +int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { + size_t c = 0; + int r; + + /* Converts the specified hostname into DNSSEC canonicalized + * form. */ + + if (buffer_max < 2) + return -ENOBUFS; + + for (;;) { + size_t i; + + r = dns_label_unescape(&n, buffer, buffer_max); + if (r < 0) + return r; + if (r == 0) + break; + if (r > 0) { + int k; + + /* DNSSEC validation is always done on the ASCII version of the label */ + k = dns_label_apply_idna(buffer, r, buffer, buffer_max); + if (k < 0) + return k; + if (k > 0) + r = k; + } + + if (buffer_max < (size_t) r + 2) + return -ENOBUFS; + + /* The DNSSEC canonical form is not clear on what to + * do with dots appearing in labels, the way DNS-SD + * does it. Refuse it for now. */ + + if (memchr(buffer, '.', r)) + return -EINVAL; + + for (i = 0; i < (size_t) r; i ++) { + if (buffer[i] >= 'A' && buffer[i] <= 'Z') + buffer[i] = buffer[i] - 'A' + 'a'; + } + + buffer[r] = '.'; + + buffer += r + 1; + c += r + 1; + + buffer_max -= r + 1; + } + + if (c <= 0) { + /* Not even a single label: this is the root domain name */ + + assert(buffer_max > 2); + buffer[0] = '.'; + buffer[1] = 0; + + return 1; + } + + return (int) c; +} + +int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) { + gcry_md_hd_t md = NULL; + char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX]; + void *result; + int r; + + assert(dnskey); + assert(ds); + + /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */ + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return -EINVAL; + if (ds->key->type != DNS_TYPE_DS) + return -EINVAL; + if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) + return -EKEYREJECTED; + if (dnskey->dnskey.protocol != 3) + return -EKEYREJECTED; + + if (!dnssec_algorithm_supported(dnskey->dnskey.algorithm)) + return -EOPNOTSUPP; + if (!dnssec_digest_supported(ds->ds.digest_type)) + return -EOPNOTSUPP; + + if (dnskey->dnskey.algorithm != ds->ds.algorithm) + return 0; + if (dnssec_keytag(dnskey) != ds->ds.key_tag) + return 0; + + switch (ds->ds.digest_type) { + + case DNSSEC_DIGEST_SHA1: + + if (ds->ds.digest_size != 20) + return 0; + + gcry_md_open(&md, GCRY_MD_SHA1, 0); + break; + + case DNSSEC_DIGEST_SHA256: + + if (ds->ds.digest_size != 32) + return 0; + + gcry_md_open(&md, GCRY_MD_SHA256, 0); + break; + + default: + assert_not_reached("Unknown digest"); + } + + if (!md) + return -EIO; + + r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name)); + if (r < 0) + goto finish; + + gcry_md_write(md, owner_name, r); + md_add_uint16(md, dnskey->dnskey.flags); + md_add_uint8(md, dnskey->dnskey.protocol); + md_add_uint8(md, dnskey->dnskey.algorithm); + gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size); + + result = gcry_md_read(md, 0); + if (!result) { + r = -EIO; + goto finish; + } + + r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0; + +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 new file mode 100644 index 0000000000..f4cb58988a --- /dev/null +++ b/src/resolve/resolved-dns-dnssec.h @@ -0,0 +1,67 @@ +/*-*- 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 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, + DNSSEC_NO_SIGNATURE, + DNSSEC_MISSING_KEY, + DNSSEC_SIGNATURE_EXPIRED, +}; + +#define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2) + +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey); +int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig); + +int dnssec_verify_rrset(DnsAnswer *answer, DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime); +int dnssec_verify_rrset_search(DnsAnswer *a, DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime); + +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 75ca23fd08..f753b3522f 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -65,30 +65,44 @@ 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) { - DnsPacket *p; - DnsPacketHeader *h; - int r; +void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated) { - assert(ret); + DnsPacketHeader *h; - r = dns_packet_new(&p, protocol, mtu); - if (r < 0) - return r; + assert(p); h = DNS_PACKET_HEADER(p); - if (protocol == DNS_PROTOCOL_LLMNR) + switch(p->protocol) { + case DNS_PROTOCOL_LLMNR: + assert(!truncated); + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, 0 /* opcode */, 0 /* c */, - 0 /* tc */, + 0/* tc */, 0 /* t */, 0 /* ra */, 0 /* ad */, 0 /* cd */, 0 /* rcode */)); - else + break; + + case DNS_PROTOCOL_MDNS: + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* aa */, + truncated /* tc */, + 0 /* rd (ask for recursion) */, + 0 /* ra */, + 0 /* ad */, + 0 /* cd */, + 0 /* rcode */)); + break; + + default: + assert(!truncated); + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, 0 /* opcode */, 0 /* aa */, @@ -96,8 +110,25 @@ 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 */)); + } +} + +int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) { + DnsPacket *p; + int r; + + assert(ret); + + r = dns_packet_new(&p, protocol, mtu); + if (r < 0) + return r; + + /* Always set the TC bit to 0 initially. + * If there are multiple packets later, we'll update the bit shortly before sending. + */ + dns_packet_set_flags(p, dnssec_checking_disabled, false); *ret = p; return 0; @@ -108,6 +139,8 @@ DnsPacket *dns_packet_ref(DnsPacket *p) { if (!p) return NULL; + assert(!p->on_stack); + assert(p->n_ref > 0); p->n_ref++; return p; @@ -126,7 +159,9 @@ static void dns_packet_free(DnsPacket *p) { hashmap_free(p->names); free(p->_data); - free(p); + + if (!p->on_stack) + free(p); } DnsPacket *dns_packet_unref(DnsPacket *p) { @@ -135,6 +170,9 @@ DnsPacket *dns_packet_unref(DnsPacket *p) { assert(p->n_ref > 0); + if (p->more) + dns_packet_unref(p->more); + if (p->n_ref == 1) dns_packet_free(p); else @@ -178,6 +216,13 @@ int dns_packet_validate_reply(DnsPacket *p) { break; + case DNS_PROTOCOL_MDNS: + /* RFC 6762, Section 18 */ + if (DNS_PACKET_RCODE(p) != 0) + return -EBADMSG; + + break; + default: break; } @@ -219,6 +264,18 @@ int dns_packet_validate_query(DnsPacket *p) { break; + case DNS_PROTOCOL_MDNS: + /* RFC 6762, Section 18 */ + if (DNS_PACKET_AA(p) != 0 || + DNS_PACKET_RD(p) != 0 || + DNS_PACKET_RA(p) != 0 || + DNS_PACKET_AD(p) != 0 || + DNS_PACKET_CD(p) != 0 || + DNS_PACKET_RCODE(p) != 0) + return -EBADMSG; + + break; + default: break; } @@ -351,25 +408,10 @@ int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) { } int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start) { - void *d; - size_t l; - int r; - assert(p); assert(s); - l = strlen(s); - if (l > 255) - return -E2BIG; - - r = dns_packet_extend(p, 1 + l, &d, start); - if (r < 0) - return r; - - ((uint8_t*) d)[0] = (uint8_t) l; - memcpy(((uint8_t*) d) + 1, s, l); - - return 0; + return dns_packet_append_raw_string(p, s, strlen(s), start); } int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start) { @@ -395,7 +437,7 @@ int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_ } int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start) { - void *w; + uint8_t *w; int r; assert(p); @@ -404,12 +446,29 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start if (l > DNS_LABEL_MAX) return -E2BIG; - r = dns_packet_extend(p, 1 + l, &w, start); + r = dns_packet_extend(p, 1 + l, (void**) &w, start); if (r < 0) return r; - ((uint8_t*) w)[0] = (uint8_t) l; - memcpy(((uint8_t*) w) + 1, d, l); + *(w++) = (uint8_t) l; + + if (p->canonical_form) { + size_t i; + + /* Generate in canonical form, as defined by DNSSEC + * RFC 4034, Section 6.2, i.e. all lower-case. */ + + for (i = 0; i < l; i++) { + if (d[i] >= 'A' && d[i] <= 'Z') + w[i] = (uint8_t) (d[i] - 'A' + 'a'); + else + w[i] = (uint8_t) d[i]; + } + } else + /* Otherwise, just copy the string unaltered. This is + * essential for DNS-SD, where the casing of labels + * matters and needs to be retained. */ + memcpy(w, d, l); return 0; } @@ -662,8 +721,8 @@ fail: return r; } -int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start) { - size_t saved_size, rdlength_offset, end, rdlength; +int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) { + size_t saved_size, rdlength_offset, end, rdlength, rds; int r; assert(p); @@ -684,6 +743,8 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; + rds = p->size - saved_size; + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { case DNS_TYPE_SRV: @@ -841,11 +902,11 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star break; case DNS_TYPE_DNSKEY: - r = dns_packet_append_uint16(p, dnskey_to_flags(rr), NULL); + r = dns_packet_append_uint16(p, rr->dnskey.flags, NULL); if (r < 0) goto fail; - r = dns_packet_append_uint8(p, 3u, NULL); + r = dns_packet_append_uint8(p, rr->dnskey.protocol, NULL); if (r < 0) goto fail; @@ -962,6 +1023,9 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (start) *start = saved_size; + if (rdata_start) + *rdata_start = rds; + return 0; fail: @@ -1381,6 +1445,7 @@ fail: int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { _cleanup_free_ char *name = NULL; + bool cache_flush = false; uint16_t class, type; DnsResourceKey *key; size_t saved_rindex; @@ -1403,12 +1468,23 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { if (r < 0) goto fail; + if (p->protocol == DNS_PROTOCOL_MDNS) { + /* See RFC6762, Section 10.2 */ + + if (class & MDNS_RR_CACHE_FLUSH) { + class &= ~MDNS_RR_CACHE_FLUSH; + cache_flush = true; + } + } + key = dns_resource_key_new_consume(class, type, name); if (!key) { r = -ENOMEM; goto fail; } + key->cache_flush = cache_flush; + name = NULL; *ret = key; @@ -1427,17 +1503,6 @@ static bool loc_size_ok(uint8_t size) { return m <= 9 && e <= 9 && (m > 0 || e == 0); } -static int dnskey_parse_flags(DnsResourceRecord *rr, uint16_t flags) { - assert(rr); - - if (flags & ~(DNSKEY_FLAG_SEP | DNSKEY_FLAG_ZONE_KEY)) - return -EBADMSG; - - rr->dnskey.zone_key_flag = flags & DNSKEY_FLAG_ZONE_KEY; - rr->dnskey.sep_flag = flags & DNSKEY_FLAG_SEP; - return 0; -} - int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; @@ -1706,28 +1771,15 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { break; - case DNS_TYPE_DNSKEY: { - uint16_t flags; - uint8_t proto; - - r = dns_packet_read_uint16(p, &flags, NULL); - if (r < 0) - goto fail; - - r = dnskey_parse_flags(rr, flags); + case DNS_TYPE_DNSKEY: + r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL); if (r < 0) goto fail; - r = dns_packet_read_uint8(p, &proto, NULL); + r = dns_packet_read_uint8(p, &rr->dnskey.protocol, NULL); if (r < 0) goto fail; - /* protocol is required to be always 3 */ - if (proto != 3) { - r = -EBADMSG; - goto fail; - } - r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL); if (r < 0) goto fail; @@ -1744,7 +1796,6 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { } break; - } case DNS_TYPE_RRSIG: r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL); @@ -1792,8 +1843,16 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { break; - case DNS_TYPE_NSEC: - r = dns_packet_read_name(p, &rr->nsec.next_domain_name, false, NULL); + case DNS_TYPE_NSEC: { + + /* + * RFC6762, section 18.14 explicly states mDNS should use name compression. + * This contradicts RFC3845, section 2.1.1 + */ + + bool allow_compressed = p->protocol == DNS_PROTOCOL_MDNS; + + r = dns_packet_read_name(p, &rr->nsec.next_domain_name, allow_compressed, NULL); if (r < 0) goto fail; @@ -1806,7 +1865,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { * without the NSEC bit set. */ break; - + } case DNS_TYPE_NSEC3: { uint8_t size; @@ -1976,17 +2035,3 @@ static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = { [DNS_PROTOCOL_LLMNR] = "llmnr", }; DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol); - -static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { - [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", - [DNSSEC_ALGORITHM_DH] = "DH", - [DNSSEC_ALGORITHM_DSA] = "DSA", - [DNSSEC_ALGORITHM_ECC] = "ECC", - [DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1", - [DNSSEC_ALGORITHM_DSA_NSEC3_SHA1] = "DSA-NSEC3-SHA1", - [DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1", - [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", - [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", - [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", -}; -DEFINE_STRING_TABLE_LOOKUP(dnssec_algorithm, int); diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index 25dfb2642f..3d84cb622b 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -88,8 +88,13 @@ struct DnsPacket { uint16_t sender_port, destination_port; uint32_t ttl; - bool extracted; - bool refuse_compression; + /* For support of truncated packets */ + DnsPacket *more; + + bool on_stack:1; + bool extracted:1; + bool refuse_compression:1; + bool canonical_form:1; }; static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { @@ -142,7 +147,9 @@ 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); + +void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated); DnsPacket *dns_packet_ref(DnsPacket *p); DnsPacket *dns_packet_unref(DnsPacket *p); @@ -162,7 +169,7 @@ int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_ int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, size_t *start); int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, size_t *start); int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start); -int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start); +int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start); int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start); void dns_packet_truncate(DnsPacket *p, size_t sz); @@ -223,42 +230,25 @@ 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 } }) -#define DNSKEY_FLAG_ZONE_KEY (1u << 8) -#define DNSKEY_FLAG_SEP (1u << 0) - -static inline uint16_t dnskey_to_flags(const DnsResourceRecord *rr) { - return (rr->dnskey.zone_key_flag * DNSKEY_FLAG_ZONE_KEY | - rr->dnskey.sep_flag * DNSKEY_FLAG_SEP); -} - -/* http://tools.ietf.org/html/rfc4034#appendix-A.1 */ -enum { - DNSSEC_ALGORITHM_RSAMD5 = 1, - DNSSEC_ALGORITHM_DH, - DNSSEC_ALGORITHM_DSA, - DNSSEC_ALGORITHM_ECC, - DNSSEC_ALGORITHM_RSASHA1, - DNSSEC_ALGORITHM_DSA_NSEC3_SHA1, - DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, - DNSSEC_ALGORITHM_INDIRECT = 252, - DNSSEC_ALGORITHM_PRIVATEDNS, - DNSSEC_ALGORITHM_PRIVATEOID, - _DNSSEC_ALGORITHM_MAX_DEFINED -}; +#define MDNS_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 251U) }) +#define MDNS_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xfb } }) -const char* dnssec_algorithm_to_string(int i) _const_; -int dnssec_algorithm_from_string(const char *s) _pure_; +static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) { + uint64_t f; -static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family) { + /* Converts a protocol + family into a flags field as used in queries and responses */ - /* Converts a protocol + family into a flags field as used in queries */ + 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); + + case DNS_PROTOCOL_MDNS: + return family == AF_INET6 ? SD_RESOLVED_MDNS_IPV6 : SD_RESOLVED_MDNS_IPV4; default: break; diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index a96cf439ad..089d9fb70d 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -86,7 +86,6 @@ DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c) { } static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) { - _cleanup_(dns_search_domain_unrefp) DnsSearchDomain *previous = NULL; DnsSearchDomain *next = NULL; assert(c); @@ -431,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); @@ -971,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); @@ -998,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; } @@ -1027,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-question.h b/src/resolve/resolved-dns-question.h index e77116c03a..5ffb63e250 100644 --- a/src/resolve/resolved-dns-question.h +++ b/src/resolve/resolved-dns-question.h @@ -23,9 +23,10 @@ typedef struct DnsQuestion DnsQuestion; +#include "macro.h" #include "resolved-dns-rr.h" -/* A simple array of resources keys */ +/* A simple array of resource keys */ struct DnsQuestion { unsigned n_ref; @@ -55,10 +56,12 @@ const char *dns_question_first_name(DnsQuestion *q); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref); -#define DNS_QUESTION_FOREACH(key, q) \ - for (unsigned _i = ({ \ +#define _DNS_QUESTION_FOREACH(u, key, q) \ + for (unsigned UNIQ_T(i, u) = ({ \ (key) = ((q) && (q)->n_keys > 0) ? (q)->keys[0] : NULL; \ 0; \ }); \ - (q) && ((_i) < (q)->n_keys); \ - _i++, (key) = (_i < (q)->n_keys ? (q)->keys[_i] : NULL)) + (q) && (UNIQ_T(i, u) < (q)->n_keys); \ + UNIQ_T(i, u)++, (key) = (UNIQ_T(i, u) < (q)->n_keys ? (q)->keys[UNIQ_T(i, u)] : NULL)) + +#define DNS_QUESTION_FOREACH(key, q) _DNS_QUESTION_FOREACH(UNIQ, key, q) diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 4a1abb0cdc..934a18334c 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -27,6 +27,7 @@ #include "hexdecoct.h" #include "resolved-dns-packet.h" #include "resolved-dns-rr.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" @@ -50,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; @@ -136,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++; @@ -146,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) { @@ -157,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; @@ -416,6 +424,7 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { free(rr->generic.data); } + free(rr->wire_format); dns_resource_key_unref(rr->key); } @@ -576,8 +585,8 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor memcmp(a->sshfp.fingerprint, b->sshfp.fingerprint, a->sshfp.fingerprint_size) == 0; case DNS_TYPE_DNSKEY: - return a->dnskey.zone_key_flag == b->dnskey.zone_key_flag && - a->dnskey.sep_flag == b->dnskey.sep_flag && + return a->dnskey.flags == b->dnskey.flags && + a->dnskey.protocol == b->dnskey.protocol && a->dnskey.algorithm == b->dnskey.algorithm && a->dnskey.key_size == b->dnskey.key_size && memcmp(a->dnskey.key, b->dnskey.key, a->dnskey.key_size) == 0; @@ -883,9 +892,10 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { if (!t) return -ENOMEM; - r = asprintf(&s, "%s %u 3 %.*s%.*u %s", + r = asprintf(&s, "%s %u %u %.*s%.*u %s", k, - dnskey_to_flags(rr), + rr->dnskey.flags, + rr->dnskey.protocol, alg ? -1 : 0, alg, alg ? 0 : 1, alg ? 0u : (unsigned) rr->dnskey.algorithm, t); @@ -993,6 +1003,51 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { return 0; } +int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { + + DnsPacket packet = { + .n_ref = 1, + .protocol = DNS_PROTOCOL_DNS, + .on_stack = true, + .refuse_compression = true, + .canonical_form = canonical, + }; + + size_t start, rds; + int r; + + assert(rr); + + /* Generates the RR in wire-format, optionally in the + * canonical form as discussed in the DNSSEC RFC 4034, Section + * 6.2. We allocate a throw-away DnsPacket object on the stack + * here, because we need some book-keeping for memory + * management, and can reuse the DnsPacket serializer, that + * can generate the canonical form, too, but also knows label + * compression and suchlike. */ + + if (rr->wire_format && rr->wire_format_canonical == canonical) + return 0; + + r = dns_packet_append_rr(&packet, rr, &start, &rds); + if (r < 0) + return r; + + assert(start == 0); + assert(packet._data); + + free(rr->wire_format); + rr->wire_format = packet._data; + rr->wire_format_size = packet.size; + rr->wire_format_rdata_offset = rds; + rr->wire_format_canonical = canonical; + + packet._data = NULL; + dns_packet_unref(&packet); + + return 0; +} + const char *dns_class_to_string(uint16_t class) { switch (class) { @@ -1049,3 +1104,25 @@ bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) { return dns_txt_item_equal(a->items_next, b->items_next); } + +static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { + [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", + [DNSSEC_ALGORITHM_DH] = "DH", + [DNSSEC_ALGORITHM_DSA] = "DSA", + [DNSSEC_ALGORITHM_ECC] = "ECC", + [DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1", + [DNSSEC_ALGORITHM_DSA_NSEC3_SHA1] = "DSA-NSEC3-SHA1", + [DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1", + [DNSSEC_ALGORITHM_RSASHA256] = "RSASHA256", + [DNSSEC_ALGORITHM_RSASHA512] = "RSASHA512", + [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", + [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", + [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", +}; +DEFINE_STRING_TABLE_LOOKUP(dnssec_algorithm, int); + +static const char* const dnssec_digest_table[_DNSSEC_DIGEST_MAX_DEFINED] = { + [DNSSEC_DIGEST_SHA1] = "SHA1", + [DNSSEC_DIGEST_SHA256] = "SHA256", +}; +DEFINE_STRING_TABLE_LOOKUP(dnssec_digest, int); diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index a092961823..5c2306ba96 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -41,12 +41,60 @@ enum { _DNS_CLASS_INVALID = -1 }; +/* DNSKEY RR flags */ +#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8) +#define DNSKEY_FLAG_SEP (UINT16_C(1) << 0) + +/* mDNS RR flags */ +#define MDNS_RR_CACHE_FLUSH (UINT16_C(1) << 15) + +/* DNSSEC algorithm identifiers, see + * http://tools.ietf.org/html/rfc4034#appendix-A.1 and + * https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ +enum { + DNSSEC_ALGORITHM_RSAMD5 = 1, + DNSSEC_ALGORITHM_DH, + DNSSEC_ALGORITHM_DSA, + DNSSEC_ALGORITHM_ECC, + DNSSEC_ALGORITHM_RSASHA1, + DNSSEC_ALGORITHM_DSA_NSEC3_SHA1, + DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, + DNSSEC_ALGORITHM_RSASHA256 = 8, /* RFC 5702 */ + DNSSEC_ALGORITHM_RSASHA512 = 10, /* RFC 5702 */ + DNSSEC_ALGORITHM_INDIRECT = 252, + DNSSEC_ALGORITHM_PRIVATEDNS, + DNSSEC_ALGORITHM_PRIVATEOID, + _DNSSEC_ALGORITHM_MAX_DEFINED +}; + +/* DNSSEC digest identifiers, see + * https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ +enum { + DNSSEC_DIGEST_SHA1 = 1, + DNSSEC_DIGEST_SHA256 = 2, + _DNSSEC_DIGEST_MAX_DEFINED +}; + struct DnsResourceKey { unsigned n_ref; uint16_t class, type; char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */ + bool cache_flush:1; }; +/* 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); @@ -57,7 +105,11 @@ struct DnsResourceRecord { unsigned n_ref; DnsResourceKey *key; uint32_t ttl; - bool unparseable; + bool unparseable:1; + bool wire_format_canonical:1; + void *wire_format; + size_t wire_format_size; + size_t wire_format_rdata_offset; union { struct { void *data; @@ -135,8 +187,8 @@ struct DnsResourceRecord { /* http://tools.ietf.org/html/rfc4034#section-2.1 */ struct { - bool zone_key_flag:1; - bool sep_flag:1; + uint16_t flags; + uint8_t protocol; uint8_t algorithm; void* key; size_t key_size; @@ -186,19 +238,22 @@ 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); int dns_resource_key_to_string(const DnsResourceKey *key, char **ret); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref); +static inline bool dns_key_is_shared(const DnsResourceKey *key) { + return IN_SET(key->type, DNS_TYPE_PTR); +} + DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key); DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name); DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr); @@ -209,6 +264,8 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref); +int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical); + DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i); bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b); @@ -216,3 +273,9 @@ const char *dns_class_to_string(uint16_t type); int dns_class_from_string(const char *name, uint16_t *class); extern const struct hash_ops dns_resource_key_hash_ops; + +const char* dnssec_algorithm_to_string(int i) _const_; +int dnssec_algorithm_from_string(const char *s) _pure_; + +const char *dnssec_digest_to_string(int i) _const_; +int dnssec_digest_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 09e6872d26..4d83ac597c 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -30,6 +30,7 @@ #include "random-util.h" #include "resolved-dns-scope.h" #include "resolved-llmnr.h" +#include "resolved-mdns.h" #include "socket-util.h" #include "strv.h" @@ -59,6 +60,7 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int LIST_PREPEND(scopes, m->dns_scopes, s); dns_scope_llmnr_membership(s, true); + dns_scope_mdns_membership(s, true); log_debug("New scope on link %s, protocol %s, family %s", l ? l->name : "*", dns_protocol_to_string(protocol), family == AF_UNSPEC ? "*" : af_to_name(family)); @@ -95,6 +97,7 @@ DnsScope* dns_scope_free(DnsScope *s) { log_debug("Removing scope on link %s, protocol %s, family %s", s->link ? s->link->name : "*", dns_protocol_to_string(s->protocol), s->family == AF_UNSPEC ? "*" : af_to_name(s->family)); dns_scope_llmnr_membership(s, false); + dns_scope_mdns_membership(s, false); dns_scope_abort_transactions(s); while (s->query_candidates) @@ -158,11 +161,10 @@ void dns_scope_packet_lost(DnsScope *s, usec_t usec) { s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC); } -int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { +static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { union in_addr_union addr; int ifindex = 0, r; int family; - uint16_t port; uint32_t mtu; size_t saved_size = 0; @@ -228,7 +230,6 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { return -EBUSY; family = s->family; - port = LLMNR_PORT; if (family == AF_INET) { addr.in = LLMNR_MULTICAST_IPV4_ADDRESS; @@ -241,7 +242,30 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { if (fd < 0) return fd; - r = manager_send(s->manager, fd, ifindex, family, &addr, port, p); + r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, p); + if (r < 0) + return r; + + break; + + case DNS_PROTOCOL_MDNS: + if (!ratelimit_test(&s->ratelimit)) + return -EBUSY; + + family = s->family; + + if (family == AF_INET) { + addr.in = MDNS_MULTICAST_IPV4_ADDRESS; + fd = manager_mdns_ipv4_fd(s->manager); + } else if (family == AF_INET6) { + addr.in6 = MDNS_MULTICAST_IPV6_ADDRESS; + fd = manager_mdns_ipv6_fd(s->manager); + } else + return -EAFNOSUPPORT; + if (fd < 0) + return fd; + + r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, p); if (r < 0) return r; @@ -254,6 +278,31 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { return 1; } +int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { + int r; + + assert(s); + assert(p); + assert(p->protocol == s->protocol); + assert((s->protocol == DNS_PROTOCOL_DNS) != (fd < 0)); + + do { + /* If there are multiple linked packets, set the TC bit in all but the last of them */ + if (p->more) { + assert(p->protocol == DNS_PROTOCOL_MDNS); + dns_packet_set_flags(p, true, true); + } + + r = dns_scope_emit_one(s, fd, server, p); + if (r < 0) + return r; + + p = p->more; + } while(p); + + return 0; +} + static int dns_scope_socket(DnsScope *s, int type, int family, const union in_addr_union *address, uint16_t port, DnsServer **server) { DnsServer *srv = NULL; _cleanup_close_ int fd = -1; @@ -368,13 +417,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 +435,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 +453,16 @@ 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 && + /* If networks use .local in their private setups, they are supposed to also add .local to their search + * domains, which we already checked above. Otherwise, we consider .local specific to mDNS and won't + * send such queries ordinary DNS servers. */ + dns_name_endswith(domain, "local") == 0) return DNS_SCOPE_MAYBE; return DNS_SCOPE_NO; @@ -434,8 +496,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 */ @@ -449,19 +530,15 @@ int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) { return true; } -int dns_scope_llmnr_membership(DnsScope *s, bool b) { +static int dns_scope_multicast_membership(DnsScope *s, bool b, struct in_addr in, struct in6_addr in6) { int fd; assert(s); - - if (s->protocol != DNS_PROTOCOL_LLMNR) - return 0; - assert(s->link); if (s->family == AF_INET) { struct ip_mreqn mreqn = { - .imr_multiaddr = LLMNR_MULTICAST_IPV4_ADDRESS, + .imr_multiaddr = in, .imr_ifindex = s->link->ifindex, }; @@ -480,7 +557,7 @@ int dns_scope_llmnr_membership(DnsScope *s, bool b) { } else if (s->family == AF_INET6) { struct ipv6_mreq mreq = { - .ipv6mr_multiaddr = LLMNR_MULTICAST_IPV6_ADDRESS, + .ipv6mr_multiaddr = in6, .ipv6mr_interface = s->link->ifindex, }; @@ -499,6 +576,22 @@ int dns_scope_llmnr_membership(DnsScope *s, bool b) { return 0; } +int dns_scope_llmnr_membership(DnsScope *s, bool b) { + + if (s->protocol != DNS_PROTOCOL_LLMNR) + return 0; + + return dns_scope_multicast_membership(s, b, LLMNR_MULTICAST_IPV4_ADDRESS, LLMNR_MULTICAST_IPV6_ADDRESS); +} + +int dns_scope_mdns_membership(DnsScope *s, bool b) { + + if (s->protocol != DNS_PROTOCOL_MDNS) + return 0; + + return dns_scope_multicast_membership(s, b, MDNS_MULTICAST_IPV4_ADDRESS, MDNS_MULTICAST_IPV6_ADDRESS); +} + static int dns_scope_make_reply_packet( DnsScope *s, uint16_t id, @@ -549,7 +642,7 @@ static int dns_scope_make_reply_packet( if (answer) { for (i = 0; i < answer->n_rrs; i++) { - r = dns_packet_append_rr(p, answer->items[i].rr, NULL); + r = dns_packet_append_rr(p, answer->items[i].rr, NULL, NULL); if (r < 0) return r; } @@ -559,7 +652,7 @@ static int dns_scope_make_reply_packet( if (soa) { for (i = 0; i < soa->n_rrs; i++) { - r = dns_packet_append_rr(p, soa->items[i].rr, NULL); + r = dns_packet_append_rr(p, soa->items[i].rr, NULL, NULL); if (r < 0) return r; } @@ -733,7 +826,7 @@ static int dns_scope_make_conflict_packet( if (r < 0) return r; - r = dns_packet_append_rr(p, rr, NULL); + r = dns_packet_append_rr(p, rr, NULL, NULL); if (r < 0) return r; @@ -901,34 +994,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..2fc2e07deb 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; @@ -91,6 +93,7 @@ DnsServer *dns_scope_get_dns_server(DnsScope *s); void dns_scope_next_dns_server(DnsScope *s); int dns_scope_llmnr_membership(DnsScope *s, bool b); +int dns_scope_mdns_membership(DnsScope *s, bool b); void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p); @@ -102,6 +105,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..90f07e6c4b 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -24,6 +24,7 @@ #include "dns-domain.h" #include "fd-util.h" #include "random-util.h" +#include "resolved-dns-cache.h" #include "resolved-dns-transaction.h" #include "resolved-llmnr.h" #include "string-table.h" @@ -243,7 +244,7 @@ static int on_stream_complete(DnsStream *s, int error) { } if (dns_packet_validate_reply(p) <= 0) { - log_debug("Invalid LLMNR TCP packet."); + log_debug("Invalid TCP reply packet."); dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); return 0; } @@ -384,6 +385,18 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { break; + case DNS_PROTOCOL_MDNS: + assert(t->scope->link); + + /* For mDNS we will not accept any packets from other interfaces */ + if (p->ifindex != t->scope->link->ifindex) + return; + + if (p->family != t->scope->family) + return; + + break; + case DNS_PROTOCOL_DNS: break; @@ -446,6 +459,13 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { } if (DNS_PACKET_TC(p)) { + + /* Truncated packets for mDNS are not allowed. Give up immediately. */ + if (t->scope->protocol == DNS_PROTOCOL_MDNS) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + /* Response was truncated, let's try again with good old TCP */ r = dns_transaction_open_tcp(t); if (r == -ESRCH) { @@ -454,7 +474,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { return; } if (r < 0) { - /* On LLMNR, if we cannot connect to the host, + /* On LLMNR and mDNS, if we cannot connect to the host, * we immediately give up */ if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); @@ -481,21 +501,32 @@ 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); + if (t->scope->protocol == DNS_PROTOCOL_DNS) { + /* 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; + } - /* 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), + t->answer_authenticated, + 0, + p->family, + &p->sender); } - /* 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); - if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS) dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); else @@ -562,21 +593,26 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat assert(s); assert(t); - /* Timeout reached? Increase the timeout for the server used */ - switch (t->scope->protocol) { - case DNS_PROTOCOL_DNS: - assert(t->server); + if (!t->initial_jitter_scheduled || t->initial_jitter_elapsed) { + /* Timeout reached? Increase the timeout for the server used */ + switch (t->scope->protocol) { + case DNS_PROTOCOL_DNS: + assert(t->server); - dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec); + dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec); - break; - case DNS_PROTOCOL_LLMNR: - case DNS_PROTOCOL_MDNS: - dns_scope_packet_lost(t->scope, usec - t->start_usec); + break; + case DNS_PROTOCOL_LLMNR: + case DNS_PROTOCOL_MDNS: + dns_scope_packet_lost(t->scope, usec - t->start_usec); - break; - default: - assert_not_reached("Invalid DNS protocol."); + break; + default: + assert_not_reached("Invalid DNS protocol."); + } + + if (t->initial_jitter_scheduled) + t->initial_jitter_elapsed = true; } /* ...and try again with a new server */ @@ -589,38 +625,6 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat return 0; } -static int dns_transaction_make_packet(DnsTransaction *t) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - int r; - - assert(t); - - if (t->sent) - return 0; - - r = dns_packet_new_query(&p, t->scope->protocol, 0); - if (r < 0) - return r; - - r = dns_scope_good_key(t->scope, t->key); - if (r < 0) - return r; - if (r == 0) - return -EDOM; - - r = dns_packet_append_key(p, t->key, NULL); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->qdcount = htobe16(1); - DNS_PACKET_HEADER(p)->id = t->id; - - t->sent = p; - p = NULL; - - return 0; -} - static usec_t transaction_get_resend_timeout(DnsTransaction *t) { assert(t); assert(t->scope); @@ -630,17 +634,18 @@ static usec_t transaction_get_resend_timeout(DnsTransaction *t) { assert(t->server); return t->server->resend_timeout; - case DNS_PROTOCOL_LLMNR: case DNS_PROTOCOL_MDNS: + assert(t->n_attempts > 0); + return (1 << (t->n_attempts - 1)) * USEC_PER_SEC; + case DNS_PROTOCOL_LLMNR: return t->scope->resend_timeout; default: assert_not_reached("Invalid DNS protocol."); } } -int dns_transaction_go(DnsTransaction *t) { +static int dns_transaction_prepare_next_attempt(DnsTransaction *t, usec_t ts) { bool had_stream; - usec_t ts; int r; assert(t); @@ -649,11 +654,6 @@ int dns_transaction_go(DnsTransaction *t) { dns_transaction_stop(t); - log_debug("Excercising transaction on scope %s on %s/%s", - dns_protocol_to_string(t->scope->protocol), - t->scope->link ? t->scope->link->name : "*", - t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family)); - if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); return 0; @@ -666,8 +666,6 @@ int dns_transaction_go(DnsTransaction *t) { return 0; } - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - t->n_attempts++; t->start_usec = ts; t->received = dns_packet_unref(t->received); @@ -675,7 +673,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 +697,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 +715,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) { @@ -715,31 +728,203 @@ int dns_transaction_go(DnsTransaction *t) { } } - if (t->scope->protocol == DNS_PROTOCOL_LLMNR && !t->initial_jitter) { - usec_t jitter; + return 1; +} + +static int dns_transaction_make_packet_mdns(DnsTransaction *t) { + + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + bool add_known_answers = false; + DnsTransaction *other; + unsigned qdcount; + usec_t ts; + int r; + + assert(t); + assert(t->scope->protocol == DNS_PROTOCOL_MDNS); + + /* Discard any previously prepared packet, so we can start over and coaleasce again */ + t->sent = dns_packet_unref(t->sent); + + r = dns_packet_new_query(&p, t->scope->protocol, 0, false); + if (r < 0) + return r; + + r = dns_packet_append_key(p, t->key, NULL); + if (r < 0) + return r; + + qdcount = 1; + + if (dns_key_is_shared(t->key)) + add_known_answers = true; + + /* + * For mDNS, we want to coalesce as many open queries in pending transactions into one single + * query packet on the wire as possible. To achieve that, we iterate through all pending transactions + * in our current scope, and see whether their timing contraints allow them to be sent. + */ + + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + LIST_FOREACH(transactions_by_scope, other, t->scope->transactions) { + + /* Skip ourselves */ + if (other == t) + continue; + + if (other->state != DNS_TRANSACTION_PENDING) + continue; + + if (other->next_attempt_after > ts) + continue; + + if (qdcount >= UINT16_MAX) + break; + + r = dns_packet_append_key(p, other->key, NULL); + + /* + * If we can't stuff more questions into the packet, just give up. + * One of the 'other' transactions will fire later and take care of the rest. + */ + if (r == -EMSGSIZE) + break; + + if (r < 0) + return r; + + r = dns_transaction_prepare_next_attempt(other, ts); + if (r <= 0) + continue; + + ts += transaction_get_resend_timeout(other); + + r = sd_event_add_time( + other->scope->manager->event, + &other->timeout_event_source, + clock_boottime_or_monotonic(), + ts, 0, + on_transaction_timeout, other); + if (r < 0) + return r; + + other->state = DNS_TRANSACTION_PENDING; + other->next_attempt_after = ts; + + qdcount ++; + + if (dns_key_is_shared(other->key)) + add_known_answers = true; + } + + DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount); + DNS_PACKET_HEADER(p)->id = t->id; + + /* Append known answer section if we're asking for any shared record */ + if (add_known_answers) { + r = dns_cache_export_shared_to_packet(&t->scope->cache, p); + if (r < 0) + return r; + } + + t->sent = p; + p = NULL; + + return 0; +} + +static int dns_transaction_make_packet(DnsTransaction *t) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + int r; + + assert(t); + + if (t->scope->protocol == DNS_PROTOCOL_MDNS) + return dns_transaction_make_packet_mdns(t); + + if (t->sent) + return 0; + + r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES); + if (r < 0) + return r; + + r = dns_scope_good_key(t->scope, t->key); + if (r < 0) + return r; + if (r == 0) + return -EDOM; + + r = dns_packet_append_key(p, t->key, NULL); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->qdcount = htobe16(1); + DNS_PACKET_HEADER(p)->id = t->id; + + t->sent = p; + p = NULL; + + return 0; +} + +int dns_transaction_go(DnsTransaction *t) { + usec_t ts; + int r; + + assert(t); + + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + r = dns_transaction_prepare_next_attempt(t, ts); + if (r <= 0) + return r; + + log_debug("Excercising transaction on scope %s on %s/%s", + dns_protocol_to_string(t->scope->protocol), + t->scope->link ? t->scope->link->name : "*", + t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family)); + + if (!t->initial_jitter_scheduled && + (t->scope->protocol == DNS_PROTOCOL_LLMNR || + t->scope->protocol == DNS_PROTOCOL_MDNS)) { + usec_t jitter, accuracy; /* RFC 4795 Section 2.7 suggests all queries should be * delayed by a random time from 0 to JITTER_INTERVAL. */ - t->initial_jitter = true; + t->initial_jitter_scheduled = true; random_bytes(&jitter, sizeof(jitter)); - jitter %= LLMNR_JITTER_INTERVAL_USEC; + + switch (t->scope->protocol) { + case DNS_PROTOCOL_LLMNR: + jitter %= LLMNR_JITTER_INTERVAL_USEC; + accuracy = LLMNR_JITTER_INTERVAL_USEC; + break; + case DNS_PROTOCOL_MDNS: + jitter %= MDNS_JITTER_RANGE_USEC; + jitter += MDNS_JITTER_MIN_USEC; + accuracy = MDNS_JITTER_RANGE_USEC; + break; + default: + assert_not_reached("bad protocol"); + } r = sd_event_add_time( t->scope->manager->event, &t->timeout_event_source, clock_boottime_or_monotonic(), - ts + jitter, - LLMNR_JITTER_INTERVAL_USEC, + ts + jitter, accuracy, on_transaction_timeout, t); if (r < 0) return r; t->n_attempts = 0; + t->next_attempt_after = ts; t->state = DNS_TRANSACTION_PENDING; - log_debug("Delaying LLMNR transaction for " USEC_FMT "us.", jitter); + log_debug("Delaying %s transaction for " USEC_FMT "us.", dns_protocol_to_string(t->scope->protocol), jitter); return 0; } @@ -786,16 +971,20 @@ int dns_transaction_go(DnsTransaction *t) { return dns_transaction_go(t); } + ts += transaction_get_resend_timeout(t); + r = sd_event_add_time( t->scope->manager->event, &t->timeout_event_source, clock_boottime_or_monotonic(), - ts + transaction_get_resend_timeout(t), 0, + ts, 0, on_transaction_timeout, t); if (r < 0) return r; t->state = DNS_TRANSACTION_PENDING; + t->next_attempt_after = ts; + return 1; } @@ -817,5 +1006,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..af08b20e44 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 }; @@ -61,15 +62,18 @@ struct DnsTransaction { DnsTransactionState state; uint16_t id; - bool initial_jitter; + bool initial_jitter_scheduled; + bool initial_jitter_elapsed; DnsPacket *sent, *received; DnsAnswer *answer; int answer_rcode; DnsTransactionSource answer_source; + bool answer_authenticated; usec_t start_usec; + usec_t next_attempt_after; sd_event_source *timeout_event_source; unsigned n_attempts; @@ -117,6 +121,10 @@ DnsTransactionSource dns_transaction_source_from_string(const char *s) _pure_; /* LLMNR Jitter interval, see RFC 4795 Section 7 */ #define LLMNR_JITTER_INTERVAL_USEC (100 * USEC_PER_MSEC) +/* mDNS Jitter interval, see RFC 6762 Section 5.2 */ +#define MDNS_JITTER_MIN_USEC (20 * USEC_PER_MSEC) +#define MDNS_JITTER_RANGE_USEC (100 * USEC_PER_MSEC) + /* Maximum attempts to send DNS requests, across all DNS servers */ #define DNS_TRANSACTION_ATTEMPTS_MAX 16 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-link.c b/src/resolve/resolved-link.c index ddd9427dab..84100bd988 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -77,6 +77,8 @@ Link *link_free(Link *l) { dns_scope_free(l->unicast_scope); dns_scope_free(l->llmnr_ipv4_scope); dns_scope_free(l->llmnr_ipv6_scope); + dns_scope_free(l->mdns_ipv4_scope); + dns_scope_free(l->mdns_ipv6_scope); free(l); return NULL; @@ -118,6 +120,28 @@ static void link_allocate_scopes(Link *l) { } } else l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope); + + if (link_relevant(l, AF_INET) && + l->mdns_support != SUPPORT_NO && + l->manager->mdns_support != SUPPORT_NO) { + if (!l->mdns_ipv4_scope) { + r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET); + if (r < 0) + log_warning_errno(r, "Failed to allocate mDNS IPv4 scope: %m"); + } + } else + l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope); + + if (link_relevant(l, AF_INET6) && + l->mdns_support != SUPPORT_NO && + l->manager->mdns_support != SUPPORT_NO) { + if (!l->mdns_ipv6_scope) { + r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6); + if (r < 0) + log_warning_errno(r, "Failed to allocate mDNS IPv6 scope: %m"); + } + } else + l->mdns_ipv6_scope = dns_scope_free(l->mdns_ipv6_scope); } void link_add_rrs(Link *l, bool force_remove) { diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index eb00015bd5..a3b406bbc2 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -67,10 +67,13 @@ struct Link { unsigned n_search_domains; Support llmnr_support; + Support mdns_support; DnsScope *unicast_scope; DnsScope *llmnr_ipv4_scope; DnsScope *llmnr_ipv6_scope; + DnsScope *mdns_ipv4_scope; + DnsScope *mdns_ipv6_scope; char name[IF_NAMESIZE]; uint32_t mtu; diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c index 6a7ff9d245..ed754c3899 100644 --- a/src/resolve/resolved-llmnr.c +++ b/src/resolve/resolved-llmnr.c @@ -461,10 +461,8 @@ int manager_llmnr_ipv6_tcp_fd(Manager *m) { } r = sd_event_add_io(m->event, &m->llmnr_ipv6_tcp_event_source, m->llmnr_ipv6_tcp_fd, EPOLLIN, on_llmnr_stream, m); - if (r < 0) { - r = -errno; + if (r < 0) goto fail; - } return m->llmnr_ipv6_tcp_fd; diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 62562f0d24..a2677f442a 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -40,6 +40,7 @@ #include "resolved-llmnr.h" #include "resolved-manager.h" #include "resolved-resolv-conf.h" +#include "resolved-mdns.h" #include "socket-util.h" #include "string-table.h" #include "string-util.h" @@ -472,12 +473,17 @@ int manager_new(Manager **ret) { m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1; m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1; + m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1; m->hostname_fd = -1; m->llmnr_support = SUPPORT_YES; 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; @@ -524,6 +530,10 @@ int manager_start(Manager *m) { if (r < 0) return r; + r = manager_mdns_start(m); + if (r < 0) + return r; + return 0; } @@ -555,6 +565,7 @@ Manager *manager_free(Manager *m) { sd_event_source_unref(m->rtnl_event_source); manager_llmnr_stop(m); + manager_mdns_stop(m); sd_bus_slot_unref(m->prepare_for_sleep_slot); sd_event_source_unref(m->bus_retry_event_source); @@ -572,6 +583,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; @@ -1018,11 +1031,25 @@ DnsScope* manager_find_scope(Manager *m, DnsPacket *p) { if (!l) return NULL; - if (p->protocol == DNS_PROTOCOL_LLMNR) { + switch (p->protocol) { + case DNS_PROTOCOL_LLMNR: if (p->family == AF_INET) return l->llmnr_ipv4_scope; else if (p->family == AF_INET6) return l->llmnr_ipv6_scope; + + break; + + case DNS_PROTOCOL_MDNS: + if (p->family == AF_INET) + return l->mdns_ipv4_scope; + else if (p->family == AF_INET6) + return l->mdns_ipv6_scope; + + break; + + default: + break; } return NULL; diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index d00c444583..b52273403a 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 @@ -53,6 +54,7 @@ struct Manager { sd_event *event; Support llmnr_support; + Support mdns_support; /* Network */ Hashmap *links; @@ -85,6 +87,8 @@ struct Manager { bool read_resolv_conf:1; usec_t resolv_conf_mtime; + DnsTrustAnchor trust_anchor; + LIST_HEAD(DnsScope, dns_scopes); DnsScope *unicast_scope; @@ -99,6 +103,13 @@ struct Manager { sd_event_source *llmnr_ipv4_tcp_event_source; sd_event_source *llmnr_ipv6_tcp_event_source; + /* mDNS */ + int mdns_ipv4_fd; + int mdns_ipv6_fd; + + sd_event_source *mdns_ipv4_event_source; + sd_event_source *mdns_ipv6_event_source; + /* dbus */ sd_bus *bus; sd_event_source *bus_retry_event_source; diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c new file mode 100644 index 0000000000..abe63d58c1 --- /dev/null +++ b/src/resolve/resolved-mdns.c @@ -0,0 +1,286 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Daniel Mack + + 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 <resolv.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "fd-util.h" +#include "resolved-manager.h" +#include "resolved-mdns.h" + +void manager_mdns_stop(Manager *m) { + assert(m); + + m->mdns_ipv4_event_source = sd_event_source_unref(m->mdns_ipv4_event_source); + m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); + + m->mdns_ipv6_event_source = sd_event_source_unref(m->mdns_ipv6_event_source); + m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); +} + +int manager_mdns_start(Manager *m) { + int r; + + assert(m); + + if (m->mdns_support == SUPPORT_NO) + return 0; + + r = manager_mdns_ipv4_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + if (socket_ipv6_is_supported()) { + r = manager_mdns_ipv6_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + } + + return 0; + +eaddrinuse: + log_warning("There appears to be another mDNS responder running. Turning off mDNS support."); + m->mdns_support = SUPPORT_NO; + manager_mdns_stop(m); + + return 0; +} + +static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + Manager *m = userdata; + DnsScope *scope; + int r; + + r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p); + if (r <= 0) + return r; + + scope = manager_find_scope(m, p); + if (!scope) { + log_warning("Got mDNS UDP packet on unknown scope. Ignoring."); + return 0; + } + + if (dns_packet_validate_reply(p) > 0) { + unsigned i; + + log_debug("Got mDNS reply packet"); + + /* + * mDNS is different from regular DNS and LLMNR with regard to handling responses. + * While on other protocols, we can ignore every answer that doesn't match a question + * we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all + * incoming information, regardless of the DNS packet ID. + * + * Hence, extract the packet here, and try to find a transaction for answer the we got + * and complete it. Also store the new information in scope's cache. + */ + r = dns_packet_extract(p); + if (r < 0) { + log_debug("mDNS packet extraction failed."); + return 0; + } + + dns_scope_check_conflicts(scope, p); + + for (i = 0; i < p->answer->n_rrs; i++) { + DnsResourceRecord *rr; + DnsTransaction *t; + + rr = p->answer->items[i].rr; + + t = dns_scope_find_transaction(scope, rr->key, false); + if (t) + dns_transaction_process_reply(t, p); + } + + dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, + p->answer->n_rrs, false, 0, p->family, &p->sender); + + } else if (dns_packet_validate_query(p) > 0) { + log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); + + dns_scope_process_query(scope, NULL, p); + } else + log_debug("Invalid mDNS UDP packet."); + + return 0; +} + +int manager_mdns_ipv4_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(MDNS_PORT), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; + int r; + + assert(m); + + if (m->mdns_ipv4_fd >= 0) + return m->mdns_ipv4_fd; + + m->mdns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->mdns_ipv4_fd < 0) + return -errno; + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Disable Don't-Fragment bit in the IP header */ + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, m->mdns_ipv4_fd, EPOLLIN, on_mdns_packet, m); + if (r < 0) + goto fail; + + return m->mdns_ipv4_fd; + +fail: + m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); + return r; +} + +int manager_mdns_ipv6_fd(Manager *m) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(MDNS_PORT), + }; + static const int one = 1, ttl = 255; + int r; + + assert(m); + + if (m->mdns_ipv6_fd >= 0) + return m->mdns_ipv6_fd; + + m->mdns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->mdns_ipv6_fd < 0) + return -errno; + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m); + if (r < 0) + goto fail; + + return m->mdns_ipv6_fd; + +fail: + m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); + return r; +} diff --git a/src/resolve/resolved-mdns.h b/src/resolve/resolved-mdns.h new file mode 100644 index 0000000000..8a84010615 --- /dev/null +++ b/src/resolve/resolved-mdns.h @@ -0,0 +1,32 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Daniel Mack + + 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 "resolved-manager.h" + +#define MDNS_PORT 5353 + +int manager_mdns_ipv4_fd(Manager *m); +int manager_mdns_ipv6_fd(Manager *m); + +void manager_mdns_stop(Manager *m); +int manager_mdns_start(Manager *m); 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 diff --git a/src/resolve/test-dnssec.c b/src/resolve/test-dnssec.c new file mode 100644 index 0000000000..0b2ffeeddd --- /dev/null +++ b/src/resolve/test-dnssec.c @@ -0,0 +1,217 @@ +/*-*- 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 <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h> + +#include "alloc-util.h" +#include "resolved-dns-dnssec.h" +#include "resolved-dns-rr.h" +#include "string-util.h" + +static void test_dnssec_verify_rrset(void) { + + static const uint8_t signature_blob[] = { + 0x7f, 0x79, 0xdd, 0x5e, 0x89, 0x79, 0x18, 0xd0, 0x34, 0x86, 0x8c, 0x72, 0x77, 0x75, 0x48, 0x4d, + 0xc3, 0x7d, 0x38, 0x04, 0xab, 0xcd, 0x9e, 0x4c, 0x82, 0xb0, 0x92, 0xca, 0xe9, 0x66, 0xe9, 0x6e, + 0x47, 0xc7, 0x68, 0x8c, 0x94, 0xf6, 0x69, 0xcb, 0x75, 0x94, 0xe6, 0x30, 0xa6, 0xfb, 0x68, 0x64, + 0x96, 0x1a, 0x84, 0xe1, 0xdc, 0x16, 0x4c, 0x83, 0x6c, 0x44, 0xf2, 0x74, 0x4d, 0x74, 0x79, 0x8f, + 0xf3, 0xf4, 0x63, 0x0d, 0xef, 0x5a, 0xe7, 0xe2, 0xfd, 0xf2, 0x2b, 0x38, 0x7c, 0x28, 0x96, 0x9d, + 0xb6, 0xcd, 0x5c, 0x3b, 0x57, 0xe2, 0x24, 0x78, 0x65, 0xd0, 0x9e, 0x77, 0x83, 0x09, 0x6c, 0xff, + 0x3d, 0x52, 0x3f, 0x6e, 0xd1, 0xed, 0x2e, 0xf9, 0xee, 0x8e, 0xa6, 0xbe, 0x9a, 0xa8, 0x87, 0x76, + 0xd8, 0x77, 0xcc, 0x96, 0xa0, 0x98, 0xa1, 0xd1, 0x68, 0x09, 0x43, 0xcf, 0x56, 0xd9, 0xd1, 0x66, + }; + + static const uint8_t dnskey_blob[] = { + 0x03, 0x01, 0x00, 0x01, 0x9b, 0x49, 0x9b, 0xc1, 0xf9, 0x9a, 0xe0, 0x4e, 0xcf, 0xcb, 0x14, 0x45, + 0x2e, 0xc9, 0xf9, 0x74, 0xa7, 0x18, 0xb5, 0xf3, 0xde, 0x39, 0x49, 0xdf, 0x63, 0x33, 0x97, 0x52, + 0xe0, 0x8e, 0xac, 0x50, 0x30, 0x8e, 0x09, 0xd5, 0x24, 0x3d, 0x26, 0xa4, 0x49, 0x37, 0x2b, 0xb0, + 0x6b, 0x1b, 0xdf, 0xde, 0x85, 0x83, 0xcb, 0x22, 0x4e, 0x60, 0x0a, 0x91, 0x1a, 0x1f, 0xc5, 0x40, + 0xb1, 0xc3, 0x15, 0xc1, 0x54, 0x77, 0x86, 0x65, 0x53, 0xec, 0x10, 0x90, 0x0c, 0x91, 0x00, 0x5e, + 0x15, 0xdc, 0x08, 0x02, 0x4c, 0x8c, 0x0d, 0xc0, 0xac, 0x6e, 0xc4, 0x3e, 0x1b, 0x80, 0x19, 0xe4, + 0xf7, 0x5f, 0x77, 0x51, 0x06, 0x87, 0x61, 0xde, 0xa2, 0x18, 0x0f, 0x40, 0x8b, 0x79, 0x72, 0xfa, + 0x8d, 0x1a, 0x44, 0x47, 0x0d, 0x8e, 0x3a, 0x2d, 0xc7, 0x39, 0xbf, 0x56, 0x28, 0x97, 0xd9, 0x20, + 0x4f, 0x00, 0x51, 0x3b, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL; + + a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov"); + assert_se(a); + + a->a.in_addr.s_addr = inet_addr("52.0.14.116"); + + assert_se(dns_resource_record_to_string(a, &x) >= 0); + log_info("A: %s", x); + + rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); + assert_se(rrsig); + + rrsig->rrsig.type_covered = DNS_TYPE_A; + rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; + rrsig->rrsig.labels = 2; + rrsig->rrsig.original_ttl = 600; + rrsig->rrsig.expiration = 0x5683135c; + rrsig->rrsig.inception = 0x565b7da8; + rrsig->rrsig.key_tag = 63876; + rrsig->rrsig.signer = strdup("Nasa.Gov."); + assert_se(rrsig->rrsig.signer); + rrsig->rrsig.signature_size = sizeof(signature_blob); + rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); + assert_se(rrsig->rrsig.signature); + + assert_se(dns_resource_record_to_string(rrsig, &y) >= 0); + log_info("RRSIG: %s", y); + + dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); + assert_se(dnskey); + + dnskey->dnskey.flags = 256; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; + dnskey->dnskey.key_size = sizeof(dnskey_blob); + dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); + assert_se(dnskey->dnskey.key); + + assert_se(dns_resource_record_to_string(dnskey, &z) >= 0); + log_info("DNSKEY: %s", z); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey)); + + assert_se(dnssec_key_match_rrsig(a->key, rrsig) > 0); + assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey) > 0); + + answer = dns_answer_new(1); + assert_se(answer); + assert_se(dns_answer_add(answer, a, 0) >= 0); + + /* Validate the RR as it if was 2015-12-2 today */ + assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC) == DNSSEC_VERIFIED); +} + +static void test_dnssec_verify_dns_key(void) { + + static const uint8_t ds1_fprint[] = { + 0x46, 0x8B, 0xC8, 0xDD, 0xC7, 0xE8, 0x27, 0x03, 0x40, 0xBB, 0x8A, 0x1F, 0x3B, 0x2E, 0x45, 0x9D, + 0x80, 0x67, 0x14, 0x01, + }; + static const uint8_t ds2_fprint[] = { + 0x8A, 0xEE, 0x80, 0x47, 0x05, 0x5F, 0x83, 0xD1, 0x48, 0xBA, 0x8F, 0xF6, 0xDD, 0xA7, 0x60, 0xCE, + 0x94, 0xF7, 0xC7, 0x5E, 0x52, 0x4C, 0xF2, 0xE9, 0x50, 0xB9, 0x2E, 0xCB, 0xEF, 0x96, 0xB9, 0x98, + }; + static const uint8_t dnskey_blob[] = { + 0x03, 0x01, 0x00, 0x01, 0xa8, 0x12, 0xda, 0x4f, 0xd2, 0x7d, 0x54, 0x14, 0x0e, 0xcc, 0x5b, 0x5e, + 0x45, 0x9c, 0x96, 0x98, 0xc0, 0xc0, 0x85, 0x81, 0xb1, 0x47, 0x8c, 0x7d, 0xe8, 0x39, 0x50, 0xcc, + 0xc5, 0xd0, 0xf2, 0x00, 0x81, 0x67, 0x79, 0xf6, 0xcc, 0x9d, 0xad, 0x6c, 0xbb, 0x7b, 0x6f, 0x48, + 0x97, 0x15, 0x1c, 0xfd, 0x0b, 0xfe, 0xd3, 0xd7, 0x7d, 0x9f, 0x81, 0x26, 0xd3, 0xc5, 0x65, 0x49, + 0xcf, 0x46, 0x62, 0xb0, 0x55, 0x6e, 0x47, 0xc7, 0x30, 0xef, 0x51, 0xfb, 0x3e, 0xc6, 0xef, 0xde, + 0x27, 0x3f, 0xfa, 0x57, 0x2d, 0xa7, 0x1d, 0x80, 0x46, 0x9a, 0x5f, 0x14, 0xb3, 0xb0, 0x2c, 0xbe, + 0x72, 0xca, 0xdf, 0xb2, 0xff, 0x36, 0x5b, 0x4f, 0xec, 0x58, 0x8e, 0x8d, 0x01, 0xe9, 0xa9, 0xdf, + 0xb5, 0x60, 0xad, 0x52, 0x4d, 0xfc, 0xa9, 0x3e, 0x8d, 0x35, 0x95, 0xb3, 0x4e, 0x0f, 0xca, 0x45, + 0x1b, 0xf7, 0xef, 0x3a, 0x88, 0x25, 0x08, 0xc7, 0x4e, 0x06, 0xc1, 0x62, 0x1a, 0xce, 0xd8, 0x77, + 0xbd, 0x02, 0x65, 0xf8, 0x49, 0xfb, 0xce, 0xf6, 0xa8, 0x09, 0xfc, 0xde, 0xb2, 0x09, 0x9d, 0x39, + 0xf8, 0x63, 0x9c, 0x32, 0x42, 0x7c, 0xa0, 0x30, 0x86, 0x72, 0x7a, 0x4a, 0xc6, 0xd4, 0xb3, 0x2d, + 0x24, 0xef, 0x96, 0x3f, 0xc2, 0xda, 0xd3, 0xf2, 0x15, 0x6f, 0xda, 0x65, 0x4b, 0x81, 0x28, 0x68, + 0xf4, 0xfe, 0x3e, 0x71, 0x4f, 0x50, 0x96, 0x72, 0x58, 0xa1, 0x89, 0xdd, 0x01, 0x61, 0x39, 0x39, + 0xc6, 0x76, 0xa4, 0xda, 0x02, 0x70, 0x3d, 0xc0, 0xdc, 0x8d, 0x70, 0x72, 0x04, 0x90, 0x79, 0xd4, + 0xec, 0x65, 0xcf, 0x49, 0x35, 0x25, 0x3a, 0x14, 0x1a, 0x45, 0x20, 0xeb, 0x31, 0xaf, 0x92, 0xba, + 0x20, 0xd3, 0xcd, 0xa7, 0x13, 0x44, 0xdc, 0xcf, 0xf0, 0x27, 0x34, 0xb9, 0xe7, 0x24, 0x6f, 0x73, + 0xe7, 0xea, 0x77, 0x03, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds1 = NULL, *ds2 = NULL; + _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL; + + /* The two DS RRs in effect for nasa.gov on 2015-12-01. */ + ds1 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "nasa.gov"); + assert_se(ds1); + + ds1->ds.key_tag = 47857; + ds1->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; + ds1->ds.digest_type = DNSSEC_DIGEST_SHA1; + ds1->ds.digest_size = sizeof(ds1_fprint); + ds1->ds.digest = memdup(ds1_fprint, ds1->ds.digest_size); + assert_se(ds1->ds.digest); + + assert_se(dns_resource_record_to_string(ds1, &a) >= 0); + log_info("DS1: %s", a); + + ds2 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "NASA.GOV"); + assert_se(ds2); + + ds2->ds.key_tag = 47857; + ds2->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; + ds2->ds.digest_type = DNSSEC_DIGEST_SHA256; + ds2->ds.digest_size = sizeof(ds2_fprint); + ds2->ds.digest = memdup(ds2_fprint, ds2->ds.digest_size); + assert_se(ds2->ds.digest); + + assert_se(dns_resource_record_to_string(ds2, &b) >= 0); + log_info("DS2: %s", b); + + dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nasa.GOV"); + assert_se(dnskey); + + dnskey->dnskey.flags = 257; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; + dnskey->dnskey.key_size = sizeof(dnskey_blob); + dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); + assert_se(dnskey->dnskey.key); + + assert_se(dns_resource_record_to_string(dnskey, &c) >= 0); + log_info("DNSKEY: %s", c); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey)); + + assert_se(dnssec_verify_dnskey(dnskey, ds1) > 0); + assert_se(dnssec_verify_dnskey(dnskey, ds2) > 0); +} + +static void test_dnssec_canonicalize_one(const char *original, const char *canonical, int r) { + char canonicalized[DNSSEC_CANONICAL_HOSTNAME_MAX]; + + assert_se(dnssec_canonicalize(original, canonicalized, sizeof(canonicalized)) == r); + if (r < 0) + return; + + assert_se(streq(canonicalized, canonical)); +} + +static void test_dnssec_canonicalize(void) { + test_dnssec_canonicalize_one("", ".", 1); + test_dnssec_canonicalize_one(".", ".", 1); + test_dnssec_canonicalize_one("foo", "foo.", 4); + test_dnssec_canonicalize_one("foo.", "foo.", 4); + test_dnssec_canonicalize_one("FOO.", "foo.", 4); + test_dnssec_canonicalize_one("FOO.bar.", "foo.bar.", 8); + test_dnssec_canonicalize_one("FOO..bar.", NULL, -EINVAL); +} + +int main(int argc, char*argv[]) { + + test_dnssec_canonicalize(); + test_dnssec_verify_dns_key(); + test_dnssec_verify_rrset(); + + return 0; +} |