summaryrefslogtreecommitdiff
path: root/src/resolve
diff options
context:
space:
mode:
Diffstat (limited to 'src/resolve')
-rw-r--r--src/resolve/dns-type.c43
-rw-r--r--src/resolve/dns-type.h5
-rw-r--r--src/resolve/resolved-bus.c6
-rw-r--r--src/resolve/resolved-def.h6
-rw-r--r--src/resolve/resolved-dns-answer.c363
-rw-r--r--src/resolve/resolved-dns-answer.h32
-rw-r--r--src/resolve/resolved-dns-cache.c284
-rw-r--r--src/resolve/resolved-dns-cache.h3
-rw-r--r--src/resolve/resolved-dns-dnssec.c413
-rw-r--r--src/resolve/resolved-dns-dnssec.h40
-rw-r--r--src/resolve/resolved-dns-packet.c204
-rw-r--r--src/resolve/resolved-dns-packet.h18
-rw-r--r--src/resolve/resolved-dns-query.c72
-rw-r--r--src/resolve/resolved-dns-query.h2
-rw-r--r--src/resolve/resolved-dns-question.c2
-rw-r--r--src/resolve/resolved-dns-rr.c53
-rw-r--r--src/resolve/resolved-dns-rr.h13
-rw-r--r--src/resolve/resolved-dns-scope.c101
-rw-r--r--src/resolve/resolved-dns-scope.h1
-rw-r--r--src/resolve/resolved-dns-transaction.c921
-rw-r--r--src/resolve/resolved-dns-transaction.h38
-rw-r--r--src/resolve/resolved-dns-zone.c12
-rw-r--r--src/resolve/resolved-dns-zone.h2
-rw-r--r--src/resolve/resolved-link.c24
-rw-r--r--src/resolve/resolved-link.h3
-rw-r--r--src/resolve/resolved-llmnr.c4
-rw-r--r--src/resolve/resolved-manager.c23
-rw-r--r--src/resolve/resolved-manager.h8
-rw-r--r--src/resolve/resolved-mdns.c290
-rw-r--r--src/resolve/resolved-mdns.h32
-rw-r--r--src/resolve/test-dnssec.c136
31 files changed, 2703 insertions, 451 deletions
diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c
index 63b4b36e88..8281da3b7c 100644
--- a/src/resolve/dns-type.c
+++ b/src/resolve/dns-type.c
@@ -44,7 +44,44 @@ int dns_type_from_string(const char *s) {
return sc->id;
}
-/* XXX: find an authoritative list of all pseudo types? */
-bool dns_type_is_pseudo(int n) {
- return IN_SET(n, DNS_TYPE_ANY, DNS_TYPE_AXFR, DNS_TYPE_IXFR, DNS_TYPE_OPT);
+bool dns_type_is_pseudo(uint16_t type) {
+
+ /* Checks whether the specified type is a "pseudo-type". What
+ * a "pseudo-type" precisely is, is defined only very weakly,
+ * but apparently entails all RR types that are not actually
+ * stored as RRs on the server and should hence also not be
+ * cached. We use this list primarily to validate NSEC type
+ * bitfields, and to verify what to cache. */
+
+ return IN_SET(type,
+ 0, /* A Pseudo RR type, according to RFC 2931 */
+ DNS_TYPE_ANY,
+ DNS_TYPE_AXFR,
+ DNS_TYPE_IXFR,
+ DNS_TYPE_OPT,
+ DNS_TYPE_TSIG,
+ DNS_TYPE_TKEY
+ );
+}
+
+bool dns_type_is_valid_query(uint16_t type) {
+
+ /* The types valid as questions in packets */
+
+ return !IN_SET(type,
+ 0,
+ DNS_TYPE_OPT,
+ DNS_TYPE_TSIG,
+ DNS_TYPE_TKEY);
+}
+
+bool dns_type_is_valid_rr(uint16_t type) {
+
+ /* The types valid as RR in packets (but not necessarily
+ * stored on servers). */
+
+ return !IN_SET(type,
+ DNS_TYPE_ANY,
+ DNS_TYPE_AXFR,
+ DNS_TYPE_IXFR);
}
diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h
index 950af36ee3..038a0d0e54 100644
--- a/src/resolve/dns-type.h
+++ b/src/resolve/dns-type.h
@@ -25,7 +25,10 @@
const char *dns_type_to_string(int type);
int dns_type_from_string(const char *s);
-bool dns_type_is_pseudo(int n);
+
+bool dns_type_is_pseudo(uint16_t type);
+bool dns_type_is_valid_query(uint16_t type);
+bool dns_type_is_valid_rr(uint16_t type);
/* DNS record types, taken from
* http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml.
diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c
index 0ceca56371..c8c0d3d9b8 100644
--- a/src/resolve/resolved-bus.c
+++ b/src/resolve/resolved-bus.c
@@ -60,6 +60,9 @@ static int reply_query_state(DnsQuery *q) {
case DNS_TRANSACTION_ABORTED:
return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted");
+ case DNS_TRANSACTION_DNSSEC_FAILED:
+ return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "DNSSEC validation failed");
+
case DNS_TRANSACTION_FAILURE: {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -550,6 +553,9 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd
if (r == 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name);
+ if (!dns_type_is_valid_query(type))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid RR type for query %" PRIu16, type);
+
r = check_ifindex_flags(ifindex, &flags, 0, error);
if (r < 0)
return r;
diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h
index db5ee57b51..6014d345f3 100644
--- a/src/resolve/resolved-def.h
+++ b/src/resolve/resolved-def.h
@@ -24,6 +24,8 @@
#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)
@@ -31,4 +33,6 @@
#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.c b/src/resolve/resolved-dns-answer.c
index 4db67f7278..5355303bd3 100644
--- a/src/resolve/resolved-dns-answer.c
+++ b/src/resolve/resolved-dns-answer.c
@@ -22,6 +22,7 @@
#include "alloc-util.h"
#include "dns-domain.h"
#include "resolved-dns-answer.h"
+#include "resolved-dns-dnssec.h"
#include "string-util.h"
DnsAnswer *dns_answer_new(unsigned n) {
@@ -46,6 +47,18 @@ DnsAnswer *dns_answer_ref(DnsAnswer *a) {
return a;
}
+static void dns_answer_flush(DnsAnswer *a) {
+ DnsResourceRecord *rr;
+
+ if (!a)
+ return;
+
+ DNS_ANSWER_FOREACH(rr, a)
+ dns_resource_record_unref(rr);
+
+ a->n_rrs = 0;
+}
+
DnsAnswer *dns_answer_unref(DnsAnswer *a) {
if (!a)
return NULL;
@@ -53,11 +66,7 @@ DnsAnswer *dns_answer_unref(DnsAnswer *a) {
assert(a->n_ref > 0);
if (a->n_ref == 1) {
- unsigned i;
-
- for (i = 0; i < a->n_rrs; i++)
- dns_resource_record_unref(a->items[i].rr);
-
+ dns_answer_flush(a);
free(a);
} else
a->n_ref--;
@@ -65,6 +74,35 @@ DnsAnswer *dns_answer_unref(DnsAnswer *a) {
return NULL;
}
+static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) {
+ assert(rr);
+
+ if (!a)
+ return -ENOSPC;
+
+ if (a->n_rrs >= a->n_allocated)
+ return -ENOSPC;
+
+ a->items[a->n_rrs].rr = dns_resource_record_ref(rr);
+ a->items[a->n_rrs].ifindex = ifindex;
+ a->n_rrs++;
+
+ return 1;
+}
+
+static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) {
+ DnsResourceRecord *rr;
+ int ifindex, r;
+
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, source) {
+ r = dns_answer_add_raw(a, rr, ifindex);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) {
unsigned i;
int r;
@@ -73,6 +111,8 @@ int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) {
if (!a)
return -ENOSPC;
+ if (a->n_ref > 1)
+ return -EBUSY;
for (i = 0; i < a->n_rrs; i++) {
if (a->items[i].ifindex != ifindex)
@@ -95,14 +135,33 @@ int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) {
}
}
- if (a->n_rrs >= a->n_allocated)
- return -ENOSPC;
+ return dns_answer_add_raw(a, rr, ifindex);
+}
- a->items[a->n_rrs].rr = dns_resource_record_ref(rr);
- a->items[a->n_rrs].ifindex = ifindex;
- a->n_rrs++;
+static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) {
+ DnsResourceRecord *rr;
+ int ifindex, r;
- return 1;
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, b) {
+ r = dns_answer_add(a, rr, ifindex);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex) {
+ int r;
+
+ assert(a);
+ assert(rr);
+
+ r = dns_answer_reserve_or_clone(a, 1);
+ if (r < 0)
+ return r;
+
+ return dns_answer_add(*a, rr, ifindex);
}
int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) {
@@ -131,8 +190,8 @@ int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) {
return dns_answer_add(a, soa, 0);
}
-int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key) {
- unsigned i;
+int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key) {
+ DnsResourceRecord *i;
int r;
assert(key);
@@ -140,8 +199,8 @@ int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key) {
if (!a)
return 0;
- for (i = 0; i < a->n_rrs; i++) {
- r = dns_resource_key_match_rr(key, a->items[i].rr, NULL);
+ DNS_ANSWER_FOREACH(i, a) {
+ r = dns_resource_key_match_rr(key, i, NULL);
if (r < 0)
return r;
if (r > 0)
@@ -151,24 +210,28 @@ int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key) {
return 0;
}
-int dns_answer_match_soa(DnsResourceKey *key, DnsResourceKey *soa) {
- if (soa->class != DNS_CLASS_IN)
- return 0;
+int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr) {
+ DnsResourceRecord *i;
+ int r;
- if (soa->type != DNS_TYPE_SOA)
- return 0;
+ assert(rr);
- if (!dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa)))
- return 0;
+ DNS_ANSWER_FOREACH(i, a) {
+ r = dns_resource_record_equal(i, rr);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return 1;
+ }
- return 1;
+ return 0;
}
-int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **ret) {
- unsigned i;
+int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret) {
+ DnsResourceRecord *rr;
+ int r;
assert(key);
- assert(ret);
if (!a)
return 0;
@@ -177,10 +240,40 @@ int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **r
if (key->type == DNS_TYPE_SOA)
return 0;
- for (i = 0; i < a->n_rrs; i++) {
+ DNS_ANSWER_FOREACH(rr, a) {
+ r = dns_resource_key_match_soa(key, rr->key);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ if (ret)
+ *ret = rr;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret) {
+ DnsResourceRecord *rr;
+ int r;
- if (dns_answer_match_soa(key, a->items[i].rr->key)) {
- *ret = a->items[i].rr;
+ assert(key);
+
+ if (!a)
+ return 0;
+
+ /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */
+ if (key->type == DNS_TYPE_CNAME || key->type == DNS_TYPE_DNAME)
+ return 0;
+
+ DNS_ANSWER_FOREACH(rr, a) {
+ r = dns_resource_key_match_cname_or_dname(key, rr->key, NULL);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ if (ret)
+ *ret = rr;
return 1;
}
}
@@ -188,41 +281,169 @@ int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **r
return 0;
}
-DnsAnswer *dns_answer_merge(DnsAnswer *a, DnsAnswer *b) {
- _cleanup_(dns_answer_unrefp) DnsAnswer *ret = NULL;
- DnsAnswer *k;
+int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *k = NULL;
+ int r;
+
+ assert(ret);
+
+ if (dns_answer_size(a) <= 0) {
+ *ret = dns_answer_ref(b);
+ return 0;
+ }
+
+ if (dns_answer_size(b) <= 0) {
+ *ret = dns_answer_ref(a);
+ return 0;
+ }
+
+ k = dns_answer_new(a->n_rrs + b->n_rrs);
+ if (!k)
+ return -ENOMEM;
+
+ r = dns_answer_add_raw_all(k, a);
+ if (r < 0)
+ return r;
+
+ r = dns_answer_add_all(k, b);
+ if (r < 0)
+ return r;
+
+ *ret = k;
+ k = NULL;
+
+ return 0;
+}
+
+int dns_answer_extend(DnsAnswer **a, DnsAnswer *b) {
+ DnsAnswer *merged;
+ int r;
+
+ assert(a);
+
+ r = dns_answer_merge(*a, b, &merged);
+ if (r < 0)
+ return r;
+
+ dns_answer_unref(*a);
+ *a = merged;
+
+ return 0;
+}
+
+int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) {
+ bool found = false, other = false;
+ DnsResourceRecord *rr;
unsigned i;
int r;
- if (a && (!b || b->n_rrs <= 0))
- return dns_answer_ref(a);
- if ((!a || a->n_rrs <= 0) && b)
- return dns_answer_ref(b);
+ assert(a);
+ assert(key);
- ret = dns_answer_new((a ? a->n_rrs : 0) + (b ? b->n_rrs : 0));
- if (!ret)
- return NULL;
+ /* Remove all entries matching the specified key from *a */
- if (a) {
- for (i = 0; i < a->n_rrs; i++) {
- r = dns_answer_add(ret, a->items[i].rr, a->items[i].ifindex);
- if (r < 0)
- return NULL;
- }
+ DNS_ANSWER_FOREACH(rr, *a) {
+ r = dns_resource_key_equal(rr->key, key);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ found = true;
+ else
+ other = true;
+
+ if (found && other)
+ break;
}
- if (b) {
- for (i = 0; i < b->n_rrs; i++) {
- r = dns_answer_add(ret, b->items[i].rr, b->items[i].ifindex);
+ if (!found)
+ return 0;
+
+ if (!other) {
+ *a = dns_answer_unref(*a); /* Return NULL for the empty answer */
+ return 1;
+ }
+
+ if ((*a)->n_ref > 1) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL;
+ int ifindex;
+
+ copy = dns_answer_new((*a)->n_rrs);
+ if (!copy)
+ return -ENOMEM;
+
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, *a) {
+ r = dns_resource_key_equal(rr->key, key);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+
+ r = dns_answer_add_raw(copy, rr, ifindex);
if (r < 0)
- return NULL;
+ return r;
}
+
+ dns_answer_unref(*a);
+ *a = copy;
+ copy = NULL;
+
+ return 1;
}
- k = ret;
- ret = NULL;
+ /* Only a single reference, edit in-place */
- return k;
+ i = 0;
+ for (;;) {
+ if (i >= (*a)->n_rrs)
+ break;
+
+ r = dns_resource_key_equal((*a)->items[i].rr->key, key);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ /* Kill this entry */
+
+ dns_resource_record_unref((*a)->items[i].rr);
+ memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1));
+ (*a)->n_rrs --;
+ continue;
+
+ } else
+ /* Keep this entry */
+ i++;
+ }
+
+ return 1;
+}
+
+int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key) {
+ DnsResourceRecord *rr_source;
+ int ifindex_source, r;
+
+ assert(a);
+ assert(key);
+
+ /* Copy all RRs matching the specified key from source into *a */
+
+ DNS_ANSWER_FOREACH_IFINDEX(rr_source, ifindex_source, source) {
+
+ r = dns_resource_key_equal(rr_source->key, key);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ /* Make space for at least one entry */
+ r = dns_answer_reserve_or_clone(a, 1);
+ if (r < 0)
+ return r;
+
+ r = dns_answer_add(*a, rr_source, ifindex_source);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
}
void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) {
@@ -261,6 +482,8 @@ void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) {
int dns_answer_reserve(DnsAnswer **a, unsigned n_free) {
DnsAnswer *n;
+ assert(a);
+
if (n_free <= 0)
return 0;
@@ -275,6 +498,9 @@ int dns_answer_reserve(DnsAnswer **a, unsigned n_free) {
if ((*a)->n_allocated >= ns)
return 0;
+ /* Allocate more than we need */
+ ns *= 2;
+
n = realloc(*a, offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * ns);
if (!n)
return -ENOMEM;
@@ -289,3 +515,36 @@ int dns_answer_reserve(DnsAnswer **a, unsigned n_free) {
*a = n;
return 0;
}
+
+int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *n = NULL;
+ int r;
+
+ assert(a);
+
+ /* Tries to extend the DnsAnswer object. And if that's not
+ * possibly, since we are not the sole owner, then allocate a
+ * new, appropriately sized one. Either way, after this call
+ * the object will only have a single reference, and has room
+ * for at least the specified number of RRs. */
+
+ r = dns_answer_reserve(a, n_free);
+ if (r != -EBUSY)
+ return r;
+
+ assert(*a);
+
+ n = dns_answer_new(((*a)->n_rrs + n_free) * 2);
+ if (!n)
+ return -ENOMEM;
+
+ r = dns_answer_add_raw_all(n, *a);
+ if (r < 0)
+ return r;
+
+ dns_answer_unref(*a);
+ *a = n;
+ n = NULL;
+
+ return 0;
+}
diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h
index 89c254b02e..56b462ed7e 100644
--- a/src/resolve/resolved-dns-answer.h
+++ b/src/resolve/resolved-dns-answer.h
@@ -30,7 +30,9 @@ typedef struct DnsAnswerItem DnsAnswerItem;
/* A simple array of resource records. We keep track of the
* originating ifindex for each RR where that makes sense, so that we
* can qualify A and AAAA RRs referring to a local link with the
- * right ifindex. */
+ * right ifindex.
+ *
+ * Note that we usually encode the empty answer as a simple NULL. */
struct DnsAnswerItem {
DnsResourceRecord *rr;
@@ -48,15 +50,29 @@ DnsAnswer *dns_answer_ref(DnsAnswer *a);
DnsAnswer *dns_answer_unref(DnsAnswer *a);
int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex);
+int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex);
int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl);
-int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key);
-int dns_answer_match_soa(DnsResourceKey *key, DnsResourceKey *soa);
-int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **ret);
-DnsAnswer *dns_answer_merge(DnsAnswer *a, DnsAnswer *b);
+int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key);
+int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr);
+
+int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret);
+int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret);
+
+int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret);
+int dns_answer_extend(DnsAnswer **a, DnsAnswer *b);
+
void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local);
int dns_answer_reserve(DnsAnswer **a, unsigned n_free);
+int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free);
+
+int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key);
+int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key);
+
+static inline unsigned dns_answer_size(DnsAnswer *a) {
+ return a ? a->n_rrs : 0;
+}
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref);
@@ -70,13 +86,13 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref);
#define DNS_ANSWER_FOREACH(kk, a) _DNS_ANSWER_FOREACH(UNIQ, kk, a)
-#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifindex, a) \
+#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifi, 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; \
+ (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \
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))
+ UNIQ_T(i, q)++, (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), (ifi) = ((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 bcb9994a8c..676fa08ffb 100644
--- a/src/resolve/resolved-dns-cache.c
+++ b/src/resolve/resolved-dns-cache.c
@@ -21,6 +21,7 @@
#include "alloc-util.h"
#include "dns-domain.h"
+#include "resolved-dns-answer.h"
#include "resolved-dns-cache.h"
#include "resolved-dns-packet.h"
#include "string-util.h"
@@ -275,7 +276,7 @@ static int dns_cache_put_positive(
_cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
_cleanup_free_ char *key_str = NULL;
DnsCacheItem *existing;
- int r;
+ int r, k;
assert(c);
assert(rr);
@@ -283,21 +284,25 @@ static int dns_cache_put_positive(
/* New TTL is 0? Delete the entry... */
if (rr->ttl <= 0) {
- r = dns_resource_key_to_string(rr->key, &key_str);
- if (r < 0)
- return r;
+ k = dns_cache_remove(c, rr->key);
- if (dns_cache_remove(c, rr->key))
- log_debug("Removed zero TTL entry from cache: %s", key_str);
- else
- log_debug("Not caching zero TTL cache entry: %s", key_str);
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(rr->key, &key_str);
+ if (r < 0)
+ return r;
+
+ if (k > 0)
+ log_debug("Removed zero TTL entry from cache: %s", key_str);
+ else
+ log_debug("Not caching zero TTL cache entry: %s", key_str);
+ }
return 0;
}
if (rr->key->class == DNS_CLASS_ANY)
return 0;
- if (rr->key->type == DNS_TYPE_ANY)
+ if (dns_type_is_pseudo(rr->key->type))
return 0;
/* Entry exists already? Update TTL and timestamp */
@@ -331,11 +336,13 @@ static int dns_cache_put_positive(
if (r < 0)
return r;
- r = dns_resource_key_to_string(i->key, &key_str);
- if (r < 0)
- return r;
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(i->key, &key_str);
+ if (r < 0)
+ return r;
- log_debug("Added cache entry for %s", key_str);
+ log_debug("Added cache entry for %s", key_str);
+ }
i = NULL;
return 0;
@@ -363,14 +370,18 @@ static int dns_cache_put_negative(
if (key->class == DNS_CLASS_ANY)
return 0;
- if (key->type == DNS_TYPE_ANY)
+ if (dns_type_is_pseudo(key->type))
+ /* ANY is particularly important to filter out as we
+ * use this as a pseudo-type for NXDOMAIN entries */
return 0;
if (soa_ttl <= 0) {
- r = dns_resource_key_to_string(key, &key_str);
- if (r < 0)
- return r;
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(key, &key_str);
+ if (r < 0)
+ return r;
- log_debug("Not caching negative entry with zero SOA TTL: %s", key_str);
+ log_debug("Not caching negative entry with zero SOA TTL: %s", key_str);
+ }
return 0;
}
@@ -389,22 +400,32 @@ static int dns_cache_put_negative(
return -ENOMEM;
i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN;
- i->key = dns_resource_key_ref(key);
i->until = timestamp + MIN(soa_ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
i->prioq_idx = PRIOQ_IDX_NULL;
i->owner_family = owner_family;
i->owner_address = *owner_address;
i->authenticated = authenticated;
+ if (i->type == DNS_CACHE_NXDOMAIN) {
+ /* NXDOMAIN entries should apply equally to all types, so we use ANY as
+ * a pseudo type for this purpose here. */
+ i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(key));
+ if (!i->key)
+ return -ENOMEM;
+ } else
+ i->key = dns_resource_key_ref(key);
+
r = dns_cache_link_item(c, i);
if (r < 0)
return r;
- r = dns_resource_key_to_string(i->key, &key_str);
- if (r < 0)
- return r;
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(i->key, &key_str);
+ if (r < 0)
+ return r;
- log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str);
+ log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str);
+ }
i = NULL;
return 0;
@@ -421,7 +442,7 @@ int dns_cache_put(
int owner_family,
const union in_addr_union *owner_address) {
- DnsResourceRecord *soa = NULL;
+ DnsResourceRecord *soa = NULL, *rr;
unsigned cache_keys, i;
int r;
@@ -433,11 +454,23 @@ int dns_cache_put(
dns_cache_remove(c, key);
}
- if (!answer)
+ if (!answer) {
+ if (log_get_max_level() >= LOG_DEBUG) {
+ _cleanup_free_ char *key_str = NULL;
+
+ r = dns_resource_key_to_string(key, &key_str);
+ if (r < 0)
+ return r;
+
+ log_debug("Not caching negative entry without a SOA record: %s", key_str);
+ }
+
return 0;
+ }
- for (i = 0; i < answer->n_rrs; i++)
- dns_cache_remove(c, answer->items[i].rr->key);
+ DNS_ANSWER_FOREACH(rr, answer)
+ if (rr->key->cache_flush)
+ dns_cache_remove(c, rr->key);
/* We only care for positive replies and NXDOMAINs, on all
* other replies we will simply flush the respective entries,
@@ -459,7 +492,9 @@ 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, authenticated, timestamp, owner_family, owner_address);
+ rr = answer->items[i].rr;
+
+ r = dns_cache_put_positive(c, rr, authenticated, timestamp, owner_family, owner_address);
if (r < 0)
goto fail;
}
@@ -468,15 +503,24 @@ int dns_cache_put(
return 0;
/* Third, add in negative entries if the key has no RR */
- r = dns_answer_contains(answer, key);
+ r = dns_answer_match_key(answer, key);
+ if (r < 0)
+ goto fail;
+ if (r > 0)
+ return 0;
+
+ /* But not if it has a matching CNAME/DNAME (the negative
+ * caching will be done on the canonical name, not on the
+ * alias) */
+ r = dns_answer_find_cname_or_dname(answer, key, NULL);
if (r < 0)
goto fail;
if (r > 0)
return 0;
- /* See https://tools.ietf.org/html/rfc2308, which
- * say that a matching SOA record in the packet
- * is used to to enable negative caching. */
+ /* See https://tools.ietf.org/html/rfc2308, which say that a
+ * matching SOA record in the packet is used to to enable
+ * negative caching. */
r = dns_answer_find_soa(answer, key, &soa);
if (r < 0)
@@ -484,31 +528,6 @@ int dns_cache_put(
if (r == 0)
return 0;
- /* Also, if the requested key is an alias, the negative response should
- be cached for each name in the redirect chain. Any CNAME record in
- the response is from the redirection chain, though only the final one
- is guaranteed to be included. This means that we cannot verify the
- chain and that we need to cache them all as it may be incomplete. */
- for (i = 0; i < answer->n_rrs; i++) {
- DnsResourceRecord *answer_rr = answer->items[i].rr;
-
- if (answer_rr->key->type == DNS_TYPE_CNAME) {
- _cleanup_(dns_resource_key_unrefp) DnsResourceKey *canonical_key = NULL;
-
- canonical_key = dns_resource_key_new_redirect(key, answer_rr);
- if (!canonical_key)
- goto fail;
-
- /* Let's not add negative cache entries for records outside the current zone. */
- if (!dns_answer_match_soa(canonical_key, soa->key))
- continue;
-
- 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, authenticated, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
if (r < 0)
goto fail;
@@ -540,36 +559,50 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D
* much, after all this is just a cache */
i = hashmap_get(c->by_key, k);
- if (i || IN_SET(k->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME, DNS_TYPE_NSEC))
+ if (i)
return i;
n = DNS_RESOURCE_KEY_NAME(k);
- /* 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)
+ /* Check if we have an NXDOMAIN cache item for the name, notice that we use
+ * the pseudo-type ANY for NXDOMAIN cache items. */
+ i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_ANY, n));
+ if (i && i->type == DNS_CACHE_NXDOMAIN)
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;
+ /* The following record types should never be redirected. See
+ * <https://tools.ietf.org/html/rfc4035#section-2.5>. */
+ if (!IN_SET(k->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME,
+ DNS_TYPE_NSEC3, DNS_TYPE_NSEC, DNS_TYPE_RRSIG,
+ DNS_TYPE_NXT, DNS_TYPE_SIG, DNS_TYPE_KEY)) {
+ /* 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. */
- for (;;) {
- char label[DNS_LABEL_MAX];
+ /* OK, let's look for cached DNAME records. */
+ for (;;) {
+ char label[DNS_LABEL_MAX];
+
+ if (isempty(n))
+ return NULL;
- if (isempty(n))
- return NULL;
+ i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n));
+ if (i)
+ return i;
- i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n));
+ /* Jump one label ahead */
+ r = dns_label_unescape(&n, label, sizeof(label));
+ if (r <= 0)
+ return NULL;
+ }
+ }
+
+ if (k-> type != DNS_TYPE_NSEC) {
+ /* 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;
-
- /* Jump one label ahead */
- r = dns_label_unescape(&n, label, sizeof(label));
- if (r <= 0)
- return NULL;
}
return NULL;
@@ -596,11 +629,13 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
/* 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)
- return r;
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(key, &key_str);
+ if (r < 0)
+ return r;
- log_debug("Ignoring cache for ANY lookup: %s", key_str);
+ log_debug("Ignoring cache for ANY lookup: %s", key_str);
+ }
*ret = NULL;
*rcode = DNS_RCODE_SUCCESS;
@@ -611,11 +646,13 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
if (!first) {
/* If one question cannot be answered we need to refresh */
- r = dns_resource_key_to_string(key, &key_str);
- if (r < 0)
- return r;
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(key, &key_str);
+ if (r < 0)
+ return r;
- log_debug("Cache miss for %s", key_str);
+ log_debug("Cache miss for %s", key_str);
+ }
*ret = NULL;
*rcode = DNS_RCODE_SUCCESS;
@@ -637,15 +674,17 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
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);
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(key, &key_str);
+ if (r < 0)
+ return r;
+
+ 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
+ * If it says the type doesn't exist report
* NODATA. Otherwise report a cache miss. */
*ret = NULL;
@@ -657,10 +696,16 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
!bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME);
}
- log_debug("%s cache hit for %s",
- n > 0 ? "Positive" :
- nxdomain ? "NXDOMAIN" : "NODATA",
- key_str);
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(key, &key_str);
+ if (r < 0)
+ return r;
+
+ log_debug("%s cache hit for %s",
+ n > 0 ? "Positive" :
+ nxdomain ? "NXDOMAIN" : "NODATA",
+ key_str);
+ }
if (n <= 0) {
*ret = NULL;
@@ -726,6 +771,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 5f91164785..0f28bbe543 100644
--- a/src/resolve/resolved-dns-cache.h
+++ b/src/resolve/resolved-dns-cache.h
@@ -32,6 +32,7 @@ typedef struct DnsCache {
} DnsCache;
#include "resolved-dns-answer.h"
+#include "resolved-dns-packet.h"
#include "resolved-dns-question.h"
#include "resolved-dns-rr.h"
@@ -45,3 +46,5 @@ int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_
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
index 2d06775dca..ed126505ad 100644
--- a/src/resolve/resolved-dns-dnssec.c
+++ b/src/resolve/resolved-dns-dnssec.c
@@ -23,6 +23,7 @@
#include "alloc-util.h"
#include "dns-domain.h"
+#include "hexdecoct.h"
#include "resolved-dns-dnssec.h"
#include "resolved-dns-packet.h"
#include "string-table.h"
@@ -40,8 +41,10 @@
* - Make trust anchor store read additional DS+DNSKEY data from disk
* - wildcard zones compatibility
* - multi-label zone compatibility
- * - DMSSEC cname/dname compatibility
+ * - DNSSEC cname/dname compatibility
* - per-interface DNSSEC setting
+ * - retry on failed validation
+ * - fix TTL for cache entries to match RRSIG TTL
* - DSA support
* - EC support?
*
@@ -64,6 +67,19 @@
* Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
*/
+static void initialize_libgcrypt(void) {
+ const char *p;
+
+ if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
+ return;
+
+ p = gcry_check_version("1.4.5");
+ assert(p);
+
+ gcry_control(GCRYCTL_DISABLE_SECMEM);
+ gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
+}
+
static bool dnssec_algorithm_supported(int algorithm) {
return IN_SET(algorithm,
DNSSEC_ALGORITHM_RSASHA1,
@@ -72,12 +88,6 @@ static bool dnssec_algorithm_supported(int algorithm) {
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;
@@ -193,11 +203,12 @@ static int dnssec_rsa_verify(
}
ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
- if (ge == GPG_ERR_BAD_SIGNATURE)
+ if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
r = 0;
- else if (ge != 0)
+ else if (ge != 0) {
+ log_debug("RSA signature check failed: %s", gpg_strerror(ge));
r = -EIO;
- else
+ } else
r = 1;
finish:
@@ -272,7 +283,8 @@ int dnssec_verify_rrset(
DnsResourceKey *key,
DnsResourceRecord *rrsig,
DnsResourceRecord *dnskey,
- usec_t realtime) {
+ usec_t realtime,
+ DnssecResult *result) {
uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
size_t exponent_size, modulus_size, hash_size;
@@ -285,6 +297,7 @@ int dnssec_verify_rrset(
assert(key);
assert(rrsig);
assert(dnskey);
+ assert(result);
assert(rrsig->key->type == DNS_TYPE_RRSIG);
assert(dnskey->key->type == DNS_TYPE_DNSKEY);
@@ -292,8 +305,10 @@ int dnssec_verify_rrset(
* 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 (!dnssec_algorithm_supported(rrsig->rrsig.algorithm)) {
+ *result = DNSSEC_UNSUPPORTED_ALGORITHM;
+ return 0;
+ }
if (a->n_rrs > VERIFY_RRS_MAX)
return -E2BIG;
@@ -301,8 +316,10 @@ int dnssec_verify_rrset(
r = dnssec_rrsig_expired(rrsig, realtime);
if (r < 0)
return r;
- if (r > 0)
- return DNSSEC_SIGNATURE_EXPIRED;
+ if (r > 0) {
+ *result = DNSSEC_SIGNATURE_EXPIRED;
+ return 0;
+ }
/* Collect all relevant RRs in a single array, so that we can look at the RRset */
list = newa(DnsResourceRecord *, a->n_rrs);
@@ -326,7 +343,9 @@ int dnssec_verify_rrset(
return -ENODATA;
/* Bring the RRs into canonical order */
- qsort_safe(list, n, sizeof(DnsResourceRecord), rr_compare);
+ qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare);
+
+ initialize_libgcrypt();
/* OK, the RRs are now in canonical order. Let's calculate the digest */
switch (rrsig->rrsig.algorithm) {
@@ -444,7 +463,8 @@ int dnssec_verify_rrset(
if (r < 0)
goto finish;
- r = r ? DNSSEC_VERIFIED : DNSSEC_INVALID;
+ *result = r ? DNSSEC_VALIDATED : DNSSEC_INVALID;
+ r = 0;
finish:
gcry_md_close(md);
@@ -476,7 +496,7 @@ int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnske
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));
+ return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer);
}
int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) {
@@ -499,15 +519,17 @@ int dnssec_verify_rrset_search(
DnsAnswer *a,
DnsResourceKey *key,
DnsAnswer *validated_dnskeys,
- usec_t realtime) {
+ usec_t realtime,
+ DnssecResult *result) {
- bool found_rrsig = false, found_dnskey = false;
+ bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
DnsResourceRecord *rrsig;
int r;
assert(key);
+ assert(result);
- /* Verifies all RRs from "a" that match the key "key", against DNSKEY RRs in "validated_dnskeys" */
+ /* Verifies all RRs from "a" that match the key "key", against DNSKEY and DS RRs in "validated_dnskeys" */
if (!a || a->n_rrs <= 0)
return -ENODATA;
@@ -516,6 +538,7 @@ int dnssec_verify_rrset_search(
DNS_ANSWER_FOREACH(rrsig, a) {
DnsResourceRecord *dnskey;
+ /* Is this an RRSIG RR that applies to RRs matching our key? */
r = dnssec_key_match_rrsig(key, rrsig);
if (r < 0)
return r;
@@ -524,16 +547,17 @@ int dnssec_verify_rrset_search(
found_rrsig = true;
+ /* Look for a matching key */
DNS_ANSWER_FOREACH(dnskey, validated_dnskeys) {
+ DnssecResult one_result;
+ /* Is this a DNSKEY RR that matches they key of our RRSIG? */
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. */
@@ -545,27 +569,61 @@ int dnssec_verify_rrset_search(
* the RRSet against the RRSIG and DNSKEY
* combination. */
- r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime);
- if (r < 0 && r != EOPNOTSUPP)
+ r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result);
+ if (r < 0)
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. */
+
+ switch (one_result) {
+
+ case DNSSEC_VALIDATED:
+ /* Yay, the RR has been validated,
+ * return immediately. */
+ *result = DNSSEC_VALIDATED;
+ return 0;
+
+ case DNSSEC_INVALID:
+ /* If the signature is invalid, 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. */
+ found_invalid = true;
+ continue;
+
+ case DNSSEC_UNSUPPORTED_ALGORITHM:
+ /* If the key algorithm is
+ unsupported, try another
+ RRSIG/DNSKEY pair, but remember we
+ encountered this, so that we can
+ return a proper error when we
+ encounter nothing better. */
+ found_unsupported_algorithm = true;
+ continue;
+
+ case DNSSEC_SIGNATURE_EXPIRED:
+ /* If the signature is expired, try
+ another one, but remember it, so
+ that we can return this */
+ found_expired_rrsig = true;
+ continue;
+
+ default:
+ assert_not_reached("Unexpected DNSSEC validation result");
+ }
}
}
- if (found_dnskey)
- return DNSSEC_INVALID;
-
- if (found_rrsig)
- return DNSSEC_MISSING_KEY;
+ if (found_expired_rrsig)
+ *result = DNSSEC_SIGNATURE_EXPIRED;
+ else if (found_unsupported_algorithm)
+ *result = DNSSEC_UNSUPPORTED_ALGORITHM;
+ else if (found_invalid)
+ *result = DNSSEC_INVALID;
+ else if (found_rrsig)
+ *result = DNSSEC_MISSING_KEY;
+ else
+ *result = DNSSEC_NO_SIGNATURE;
- return DNSSEC_NO_SIGNATURE;
+ return 0;
}
int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
@@ -633,9 +691,28 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
return (int) c;
}
+static int digest_to_gcrypt(uint8_t algorithm) {
+
+ /* Translates a DNSSEC digest algorithm into a gcrypt digest iedntifier */
+
+ switch (algorithm) {
+
+ case DNSSEC_DIGEST_SHA1:
+ return GCRY_MD_SHA1;
+
+ case DNSSEC_DIGEST_SHA256:
+ return GCRY_MD_SHA256;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
- gcry_md_hd_t md = NULL;
char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
+ gcry_md_hd_t md = NULL;
+ size_t hash_size;
+ int algorithm;
void *result;
int r;
@@ -653,45 +730,31 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
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:
+ initialize_libgcrypt();
- if (ds->ds.digest_size != 20)
- return 0;
+ algorithm = digest_to_gcrypt(ds->ds.digest_type);
+ if (algorithm < 0)
+ return algorithm;
- gcry_md_open(&md, GCRY_MD_SHA1, 0);
- break;
+ hash_size = gcry_md_get_algo_dlen(algorithm);
+ assert(hash_size > 0);
- case DNSSEC_DIGEST_SHA256:
-
- if (ds->ds.digest_size != 32)
- return 0;
-
- gcry_md_open(&md, GCRY_MD_SHA256, 0);
- break;
+ if (ds->ds.digest_size != hash_size)
+ return 0;
- default:
- assert_not_reached("Unknown digest");
- }
+ r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
+ if (r < 0)
+ return r;
+ gcry_md_open(&md, algorithm, 0);
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);
@@ -711,9 +774,227 @@ finish:
return r;
}
+int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) {
+ DnsResourceRecord *ds;
+ int r;
+
+ assert(dnskey);
+
+ if (dnskey->key->type != DNS_TYPE_DNSKEY)
+ return 0;
+
+ DNS_ANSWER_FOREACH(ds, validated_ds) {
+
+ if (ds->key->type != DNS_TYPE_DS)
+ continue;
+
+ r = dnssec_verify_dnskey(dnskey, ds);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) {
+ uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX];
+ gcry_md_hd_t md = NULL;
+ size_t hash_size;
+ int algorithm;
+ void *result;
+ unsigned k;
+ int r;
+
+ assert(nsec3);
+ assert(name);
+ assert(ret);
+
+ if (nsec3->key->type != DNS_TYPE_NSEC3)
+ return -EINVAL;
+
+ algorithm = digest_to_gcrypt(nsec3->nsec3.algorithm);
+ if (algorithm < 0)
+ return algorithm;
+
+ initialize_libgcrypt();
+
+ hash_size = gcry_md_get_algo_dlen(algorithm);
+ assert(hash_size > 0);
+
+ if (nsec3->nsec3.next_hashed_name_size != hash_size)
+ return -EINVAL;
+
+ r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true);
+ if (r < 0)
+ return r;
+
+ gcry_md_open(&md, algorithm, 0);
+ if (!md)
+ return -EIO;
+
+ gcry_md_write(md, wire_format, r);
+ gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
+
+ result = gcry_md_read(md, 0);
+ if (!result) {
+ r = -EIO;
+ goto finish;
+ }
+
+ for (k = 0; k < nsec3->nsec3.iterations; k++) {
+ uint8_t tmp[hash_size];
+ memcpy(tmp, result, hash_size);
+
+ gcry_md_reset(md);
+ gcry_md_write(md, tmp, hash_size);
+ gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
+
+ result = gcry_md_read(md, 0);
+ if (!result) {
+ r = -EIO;
+ goto finish;
+ }
+ }
+
+ memcpy(ret, result, hash_size);
+ r = (int) hash_size;
+
+finish:
+ gcry_md_close(md);
+ return r;
+}
+
+int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result) {
+ DnsResourceRecord *rr;
+ int r;
+
+ assert(key);
+ assert(result);
+
+ /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */
+
+ DNS_ANSWER_FOREACH(rr, answer) {
+
+ if (rr->key->class != key->class)
+ continue;
+
+ switch (rr->key->type) {
+
+ case DNS_TYPE_NSEC:
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key));
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ *result = bitmap_isset(rr->nsec.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
+ return 0;
+ }
+
+ r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key), rr->nsec.next_domain_name);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ *result = DNSSEC_NSEC_NXDOMAIN;
+ return 0;
+ }
+ break;
+
+ case DNS_TYPE_NSEC3: {
+ _cleanup_free_ void *decoded = NULL;
+ size_t decoded_size;
+ char label[DNS_LABEL_MAX];
+ uint8_t hashed[DNSSEC_HASH_SIZE_MAX];
+ int label_length, c, q;
+ const char *p;
+ bool covered;
+
+ /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
+ if (!IN_SET(rr->nsec3.flags, 0, 1))
+ continue;
+
+ p = DNS_RESOURCE_KEY_NAME(rr->key);
+ label_length = dns_label_unescape(&p, label, sizeof(label));
+ if (label_length < 0)
+ return label_length;
+ if (label_length == 0)
+ continue;
+
+ r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), p);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = unbase32hexmem(label, label_length, false, &decoded, &decoded_size);
+ if (r == -EINVAL)
+ continue;
+ if (r < 0)
+ return r;
+
+ if (decoded_size != rr->nsec3.next_hashed_name_size)
+ continue;
+
+ c = memcmp(decoded, rr->nsec3.next_hashed_name, decoded_size);
+ if (c == 0)
+ continue;
+
+ r = dnssec_nsec3_hash(rr, DNS_RESOURCE_KEY_NAME(key), hashed);
+ /* RFC 5155, Section 8.1 says we MUST ignore NSEC3 RRs with unknown algorithms */
+ if (r == -EOPNOTSUPP)
+ continue;
+ if (r < 0)
+ return r;
+ if ((size_t) r != decoded_size)
+ continue;
+
+ r = memcmp(decoded, hashed, decoded_size);
+ if (r == 0) {
+ *result = bitmap_isset(rr->nsec3.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
+ return 0;
+ }
+
+ q = memcmp(hashed, rr->nsec3.next_hashed_name, decoded_size);
+
+ covered = c < 0 ?
+ r < 0 && q < 0 :
+ q < 0 || r < 0;
+
+ if (covered) {
+ *result = DNSSEC_NSEC_NXDOMAIN;
+ return 0;
+ }
+
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ /* No approproate NSEC RR found, report this. */
+ *result = DNSSEC_NSEC_NO_RR;
+ return 0;
+}
+
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);
+
+static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
+ [DNSSEC_VALIDATED] = "validated",
+ [DNSSEC_INVALID] = "invalid",
+ [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired",
+ [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm",
+ [DNSSEC_NO_SIGNATURE] = "no-signature",
+ [DNSSEC_MISSING_KEY] = "missing-key",
+ [DNSSEC_UNSIGNED] = "unsigned",
+ [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
+ [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
+};
+DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);
diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h
index f4cb58988a..442e301302 100644
--- a/src/resolve/resolved-dns-dnssec.h
+++ b/src/resolve/resolved-dns-dnssec.h
@@ -22,6 +22,7 @@
***/
typedef enum DnssecMode DnssecMode;
+typedef enum DnssecResult DnssecResult;
#include "dns-domain.h"
#include "resolved-dns-answer.h"
@@ -41,27 +42,56 @@ enum DnssecMode {
_DNSSEC_MODE_INVALID = -1
};
-enum {
- DNSSEC_VERIFIED,
+enum DnssecResult {
+ /* These four are returned by dnssec_verify_rrset() */
+ DNSSEC_VALIDATED,
DNSSEC_INVALID,
+ DNSSEC_SIGNATURE_EXPIRED,
+ DNSSEC_UNSUPPORTED_ALGORITHM,
+
+ /* These two are added by dnssec_verify_rrset_search() */
DNSSEC_NO_SIGNATURE,
DNSSEC_MISSING_KEY,
- DNSSEC_SIGNATURE_EXPIRED,
+
+ /* These two are added by the DnsTransaction logic */
+ DNSSEC_UNSIGNED,
+ DNSSEC_FAILED_AUXILIARY,
+ DNSSEC_NSEC_MISMATCH,
+ _DNSSEC_RESULT_MAX,
+ _DNSSEC_RESULT_INVALID = -1
};
#define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2)
+/* The longest digest we'll ever generate, of all digest algorithms we support */
+#define DNSSEC_HASH_SIZE_MAX (MAX(20, 32))
+
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_rrset(DnsAnswer *answer, DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result);
+int dnssec_verify_rrset_search(DnsAnswer *answer, DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result);
int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds);
+int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds);
uint16_t dnssec_keytag(DnsResourceRecord *dnskey);
int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max);
+int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret);
+
+typedef enum DnssecNsecResult {
+ DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */
+ DNSSEC_NSEC_NXDOMAIN,
+ DNSSEC_NSEC_NODATA,
+ DNSSEC_NSEC_FOUND,
+} DnssecNsecResult;
+
+int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result);
+
const char* dnssec_mode_to_string(DnssecMode m) _const_;
DnssecMode dnssec_mode_from_string(const char *s) _pure_;
+
+const char* dnssec_result_to_string(DnssecResult m) _const_;
+DnssecResult dnssec_result_from_string(const char *s) _pure_;
diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c
index ea776f7916..c34ecc44f8 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -65,20 +65,18 @@ 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, bool dnssec_checking_disabled) {
- 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 */,
@@ -88,7 +86,23 @@ int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool
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 */,
@@ -98,6 +112,23 @@ int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool
0 /* ad */,
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;
@@ -122,6 +153,7 @@ static void dns_packet_free(DnsPacket *p) {
dns_question_unref(p->question);
dns_answer_unref(p->answer);
+ dns_resource_record_unref(p->opt);
while ((s = hashmap_steal_first_key(p->names)))
free(s);
@@ -139,6 +171,8 @@ DnsPacket *dns_packet_unref(DnsPacket *p) {
assert(p->n_ref > 0);
+ dns_packet_unref(p->more);
+
if (p->n_ref == 1)
dns_packet_free(p);
else
@@ -175,6 +209,7 @@ int dns_packet_validate_reply(DnsPacket *p) {
return -EBADMSG;
switch (p->protocol) {
+
case DNS_PROTOCOL_LLMNR:
/* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */
if (DNS_PACKET_QDCOUNT(p) != 1)
@@ -182,6 +217,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;
}
@@ -208,6 +250,7 @@ int dns_packet_validate_query(DnsPacket *p) {
return -EBADMSG;
switch (p->protocol) {
+
case DNS_PROTOCOL_LLMNR:
/* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */
if (DNS_PACKET_QDCOUNT(p) != 1)
@@ -223,6 +266,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;
}
@@ -383,10 +438,15 @@ int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_
return 0;
}
-int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start) {
+int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonical_candidate, size_t *start) {
uint8_t *w;
int r;
+ /* Append a label to a packet. Optionally, does this in DNSSEC
+ * canonical form, if this label is marked as a candidate for
+ * it, and the canonical form logic is enabled for the
+ * packet */
+
assert(p);
assert(d);
@@ -399,7 +459,7 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start
*(w++) = (uint8_t) l;
- if (p->canonical_form) {
+ if (p->canonical_form && canonical_candidate) {
size_t i;
/* Generate in canonical form, as defined by DNSSEC
@@ -424,6 +484,7 @@ int dns_packet_append_name(
DnsPacket *p,
const char *name,
bool allow_compression,
+ bool canonical_candidate,
size_t *start) {
size_t saved_size;
@@ -478,7 +539,7 @@ int dns_packet_append_name(
if (k > 0)
r = k;
- r = dns_packet_append_label(p, label, r, &n);
+ r = dns_packet_append_label(p, label, r, canonical_candidate, &n);
if (r < 0)
goto fail;
@@ -519,7 +580,7 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start)
saved_size = p->size;
- r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, NULL);
+ r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, true, NULL);
if (r < 0)
goto fail;
@@ -541,7 +602,7 @@ fail:
return r;
}
-static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, uint8_t *types, size_t *start) {
+static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, const uint8_t *types, size_t *start) {
size_t saved_size;
int r;
@@ -598,15 +659,16 @@ static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) {
}
window = n >> 8;
-
entry = n & 255;
bitmaps[entry / 8] |= 1 << (7 - (entry % 8));
}
- r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL);
- if (r < 0)
- goto fail;
+ if (bitmaps[entry / 8] != 0) {
+ r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL);
+ if (r < 0)
+ goto fail;
+ }
if (start)
*start = saved_size;
@@ -707,14 +769,14 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
if (r < 0)
goto fail;
- r = dns_packet_append_name(p, rr->srv.name, true, NULL);
+ r = dns_packet_append_name(p, rr->srv.name, true, false, NULL);
break;
case DNS_TYPE_PTR:
case DNS_TYPE_NS:
case DNS_TYPE_CNAME:
case DNS_TYPE_DNAME:
- r = dns_packet_append_name(p, rr->ptr.name, true, NULL);
+ r = dns_packet_append_name(p, rr->ptr.name, true, false, NULL);
break;
case DNS_TYPE_HINFO:
@@ -757,11 +819,11 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
break;
case DNS_TYPE_SOA:
- r = dns_packet_append_name(p, rr->soa.mname, true, NULL);
+ r = dns_packet_append_name(p, rr->soa.mname, true, false, NULL);
if (r < 0)
goto fail;
- r = dns_packet_append_name(p, rr->soa.rname, true, NULL);
+ r = dns_packet_append_name(p, rr->soa.rname, true, false, NULL);
if (r < 0)
goto fail;
@@ -789,7 +851,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
if (r < 0)
goto fail;
- r = dns_packet_append_name(p, rr->mx.exchange, true, NULL);
+ r = dns_packet_append_name(p, rr->mx.exchange, true, false, NULL);
break;
case DNS_TYPE_LOC:
@@ -893,7 +955,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
if (r < 0)
goto fail;
- r = dns_packet_append_name(p, rr->rrsig.signer, false, NULL);
+ r = dns_packet_append_name(p, rr->rrsig.signer, false, true, NULL);
if (r < 0)
goto fail;
@@ -901,7 +963,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
break;
case DNS_TYPE_NSEC:
- r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, NULL);
+ r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, false, NULL);
if (r < 0)
goto fail;
@@ -910,6 +972,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
goto fail;
break;
+
case DNS_TYPE_NSEC3:
r = dns_packet_append_uint8(p, rr->nsec3.algorithm, NULL);
if (r < 0)
@@ -944,6 +1007,8 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
goto fail;
break;
+
+ case DNS_TYPE_OPT:
case _DNS_TYPE_INVALID: /* unparseable */
default:
@@ -1392,6 +1457,7 @@ fail:
int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) {
_cleanup_free_ char *name = NULL;
+ bool cache_flush = true;
uint16_t class, type;
DnsResourceKey *key;
size_t saved_rindex;
@@ -1414,12 +1480,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;
+ else
+ cache_flush = false;
+ }
+
key = dns_resource_key_new_consume(class, type, name);
if (!key) {
r = -ENOMEM;
goto fail;
}
+ key->cache_flush = cache_flush;
+
name = NULL;
*ret = key;
@@ -1455,7 +1532,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
goto fail;
if (key->class == DNS_CLASS_ANY ||
- key->type == DNS_TYPE_ANY) {
+ !dns_type_is_valid_rr(key->type)) {
r = -EBADMSG;
goto fail;
}
@@ -1503,10 +1580,6 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
r = dns_packet_read_name(p, &rr->ptr.name, true, NULL);
break;
- case DNS_TYPE_OPT: /* we only care about the header */
- r = 0;
- break;
-
case DNS_TYPE_HINFO:
r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL);
if (r < 0)
@@ -1684,6 +1757,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
}
break;
+
case DNS_TYPE_SSHFP:
r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL);
if (r < 0)
@@ -1778,8 +1852,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 explictly 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;
@@ -1792,7 +1874,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;
@@ -1838,6 +1920,8 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
break;
}
+
+ case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */
default:
unparseable:
r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.size, NULL);
@@ -1892,6 +1976,11 @@ int dns_packet_extract(DnsPacket *p) {
if (r < 0)
goto finish;
+ if (!dns_type_is_valid_query(key->type)) {
+ r = -EBADMSG;
+ goto finish;
+ }
+
r = dns_question_add(question, key);
if (r < 0)
goto finish;
@@ -1913,9 +2002,26 @@ int dns_packet_extract(DnsPacket *p) {
if (r < 0)
goto finish;
- r = dns_answer_add(answer, rr, p->ifindex);
- if (r < 0)
- goto finish;
+ if (rr->key->type == DNS_TYPE_OPT) {
+
+ /* The OPT RR is only valid in the Additional section */
+ if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) {
+ r = -EBADMSG;
+ goto finish;
+ }
+
+ /* Two OPT RRs? */
+ if (p->opt) {
+ r = -EBADMSG;
+ goto finish;
+ }
+
+ p->opt = dns_resource_record_ref(rr);
+ } else {
+ r = dns_answer_add(answer, rr, p->ifindex);
+ if (r < 0)
+ goto finish;
+ }
}
}
@@ -1934,6 +2040,30 @@ finish:
return r;
}
+int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) {
+ int r;
+
+ assert(p);
+ assert(key);
+
+ /* Checks if the specified packet is a reply for the specified
+ * key and the specified key is the only one in the question
+ * section. */
+
+ if (DNS_PACKET_QR(p) != 1)
+ return 0;
+
+ /* Let's unpack the packet, if that hasn't happened yet. */
+ r = dns_packet_extract(p);
+ if (r < 0)
+ return r;
+
+ if (p->question->n_keys != 1)
+ return 0;
+
+ return dns_resource_key_equal(p->question->keys[0], key);
+}
+
static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = {
[DNS_RCODE_SUCCESS] = "SUCCESS",
[DNS_RCODE_FORMERR] = "FORMERR",
diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h
index aa2823cfb9..082e92833c 100644
--- a/src/resolve/resolved-dns-packet.h
+++ b/src/resolve/resolved-dns-packet.h
@@ -80,6 +80,7 @@ struct DnsPacket {
/* Parsed data */
DnsQuestion *question;
DnsAnswer *answer;
+ DnsResourceRecord *opt;
/* Packet reception metadata */
int ifindex;
@@ -88,6 +89,9 @@ struct DnsPacket {
uint16_t sender_port, destination_port;
uint32_t ttl;
+ /* For support of truncated packets */
+ DnsPacket *more;
+
bool on_stack:1;
bool extracted:1;
bool refuse_compression:1;
@@ -146,6 +150,8 @@ 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, 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);
@@ -155,14 +161,16 @@ int dns_packet_validate(DnsPacket *p);
int dns_packet_validate_reply(DnsPacket *p);
int dns_packet_validate_query(DnsPacket *p);
+int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key);
+
int dns_packet_append_blob(DnsPacket *p, const void *d, size_t sz, size_t *start);
int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start);
int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start);
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);
int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start);
-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_label(DnsPacket *p, const char *s, size_t l, bool canonical_candidate, size_t *start);
+int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, bool canonical_candidate, 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, size_t *rdata_start);
int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start);
@@ -225,6 +233,9 @@ 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 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 } })
+
static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) {
uint64_t f;
@@ -239,6 +250,9 @@ static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family,
case DNS_PROTOCOL_LLMNR:
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 089d9fb70d..405882a6ea 100644
--- a/src/resolve/resolved-dns-query.c
+++ b/src/resolve/resolved-dns-query.c
@@ -59,7 +59,7 @@ static void dns_query_candidate_stop(DnsQueryCandidate *c) {
assert(c);
while ((t = set_steal_first(c->transactions))) {
- set_remove(t->query_candidates, c);
+ set_remove(t->notify_query_candidates, c);
dns_transaction_gc(t);
}
}
@@ -116,32 +116,35 @@ static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResource
assert(c);
assert(key);
- r = set_ensure_allocated(&c->transactions, NULL);
- if (r < 0)
- return r;
-
t = dns_scope_find_transaction(c->scope, key, true);
if (!t) {
r = dns_transaction_new(&t, c->scope, key);
if (r < 0)
return r;
+ } else {
+ if (set_contains(c->transactions, t))
+ return 0;
}
- r = set_ensure_allocated(&t->query_candidates, NULL);
+ r = set_ensure_allocated(&c->transactions, NULL);
if (r < 0)
goto gc;
- r = set_put(t->query_candidates, c);
+ r = set_ensure_allocated(&t->notify_query_candidates, NULL);
+ if (r < 0)
+ goto gc;
+
+ r = set_put(t->notify_query_candidates, c);
if (r < 0)
goto gc;
r = set_put(c->transactions, t);
if (r < 0) {
- set_remove(t->query_candidates, c);
+ (void) set_remove(t->notify_query_candidates, c);
goto gc;
}
- return 0;
+ return 1;
gc:
dns_transaction_gc(t);
@@ -182,9 +185,21 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) {
switch (t->state) {
- case DNS_TRANSACTION_PENDING:
case DNS_TRANSACTION_NULL:
- return t->state;
+ /* If there's a NULL transaction pending, then
+ * this means not all transactions where
+ * started yet, and we were called from within
+ * the stackframe that is supposed to start
+ * remaining transactions. In this case,
+ * simply claim the candidate is pending. */
+
+ case DNS_TRANSACTION_PENDING:
+ case DNS_TRANSACTION_VALIDATING:
+ /* If there's one transaction currently in
+ * VALIDATING state, then this means there's
+ * also one in PENDING state, hence we can
+ * return PENDING immediately. */
+ return DNS_TRANSACTION_PENDING;
case DNS_TRANSACTION_SUCCESS:
state = t->state;
@@ -233,7 +248,7 @@ fail:
return r;
}
-void dns_query_candidate_ready(DnsQueryCandidate *c) {
+void dns_query_candidate_notify(DnsQueryCandidate *c) {
DnsTransactionState state;
int r;
@@ -241,7 +256,7 @@ void dns_query_candidate_ready(DnsQueryCandidate *c) {
state = dns_query_candidate_state(c);
- if (IN_SET(state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_NULL))
+ if (DNS_TRANSACTION_IS_LIVE(state))
return;
if (state != DNS_TRANSACTION_SUCCESS && c->search_domain) {
@@ -394,8 +409,8 @@ int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for) {
static void dns_query_complete(DnsQuery *q, DnsTransactionState state) {
assert(q);
- assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
- assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
+ assert(!DNS_TRANSACTION_IS_LIVE(state));
+ assert(DNS_TRANSACTION_IS_LIVE(q->state));
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
@@ -970,9 +985,10 @@ fail:
static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS;
+ bool has_authenticated = false, has_non_authenticated = false;
DnsTransaction *t;
Iterator i;
- bool has_authenticated = false, has_non_authenticated = false;
+ int r;
assert(q);
@@ -988,16 +1004,11 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
case DNS_TRANSACTION_SUCCESS: {
/* We found a successfuly reply, merge it into the answer */
- DnsAnswer *merged;
-
- merged = dns_answer_merge(q->answer, t->answer);
- if (!merged) {
+ r = dns_answer_extend(&q->answer, t->answer);
+ if (r < 0) {
dns_query_complete(q, DNS_TRANSACTION_RESOURCES);
return;
}
-
- dns_answer_unref(q->answer);
- q->answer = merged;
q->answer_rcode = t->answer_rcode;
if (t->answer_authenticated)
@@ -1009,8 +1020,9 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
break;
}
- case DNS_TRANSACTION_PENDING:
case DNS_TRANSACTION_NULL:
+ case DNS_TRANSACTION_PENDING:
+ case DNS_TRANSACTION_VALIDATING:
case DNS_TRANSACTION_ABORTED:
/* Ignore transactions that didn't complete */
continue;
@@ -1049,7 +1061,7 @@ void dns_query_ready(DnsQuery *q) {
bool pending = false;
assert(q);
- assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
+ assert(DNS_TRANSACTION_IS_LIVE(q->state));
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
@@ -1066,14 +1078,16 @@ void dns_query_ready(DnsQuery *q) {
switch (state) {
case DNS_TRANSACTION_SUCCESS:
- /* One of the transactions is successful,
+ /* One of the candidates is successful,
* let's use it, and copy its data out */
dns_query_accept(q, c);
return;
- case DNS_TRANSACTION_PENDING:
case DNS_TRANSACTION_NULL:
- /* One of the transactions is still going on, let's maybe wait for it */
+ case DNS_TRANSACTION_PENDING:
+ case DNS_TRANSACTION_VALIDATING:
+ /* One of the candidates is still going on,
+ * let's maybe wait for it */
pending = true;
break;
@@ -1096,6 +1110,8 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname)
assert(q);
+ log_debug("Following CNAME %s → %s", dns_question_first_name(q->question), cname->cname.name);
+
q->n_cname_redirects ++;
if (q->n_cname_redirects > CNAME_MAX)
return -ELOOP;
diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h
index b71bb2352b..d7f96c3ca4 100644
--- a/src/resolve/resolved-dns-query.h
+++ b/src/resolve/resolved-dns-query.h
@@ -95,7 +95,7 @@ struct DnsQuery {
};
DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c);
-void dns_query_candidate_ready(DnsQueryCandidate *c);
+void dns_query_candidate_notify(DnsQueryCandidate *c);
int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question, int family, uint64_t flags);
DnsQuery *dns_query_free(DnsQuery *q);
diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c
index 3249448d3b..4ed7434d3c 100644
--- a/src/resolve/resolved-dns-question.c
+++ b/src/resolve/resolved-dns-question.c
@@ -117,7 +117,7 @@ int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr, const char
return 0;
for (i = 0; i < q->n_keys; i++) {
- r = dns_resource_key_match_cname(q->keys[i], rr, search_domain);
+ r = dns_resource_key_match_cname_or_dname(q->keys[i], rr->key, search_domain);
if (r != 0)
return r;
}
diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c
index 934a18334c..74c9d87319 100644
--- a/src/resolve/resolved-dns-rr.c
+++ b/src/resolve/resolved-dns-rr.c
@@ -168,6 +168,9 @@ bool dns_resource_key_is_address(const DnsResourceKey *key) {
int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) {
int r;
+ if (a == b)
+ return 1;
+
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(a), DNS_RESOURCE_KEY_NAME(b));
if (r <= 0)
return r;
@@ -187,6 +190,9 @@ int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord
assert(key);
assert(rr);
+ if (key == rr->key)
+ return 1;
+
/* Checks if an rr matches the specified key. If a search
* domain is specified, it will also be checked if the key
* with the search domain suffixed might match the RR. */
@@ -214,19 +220,19 @@ int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord
return 0;
}
-int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain) {
+int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain) {
int r;
assert(key);
- assert(rr);
+ assert(cname);
- if (rr->key->class != key->class && key->class != DNS_CLASS_ANY)
+ if (cname->class != key->class && key->class != DNS_CLASS_ANY)
return 0;
- if (rr->key->type == DNS_TYPE_CNAME)
- r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(rr->key));
- else if (rr->key->type == DNS_TYPE_DNAME)
- r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(rr->key));
+ if (cname->type == DNS_TYPE_CNAME)
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname));
+ else if (cname->type == DNS_TYPE_DNAME)
+ r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname));
else
return 0;
@@ -240,14 +246,31 @@ int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRec
if (r < 0)
return r;
- if (rr->key->type == DNS_TYPE_CNAME)
- return dns_name_equal(joined, DNS_RESOURCE_KEY_NAME(rr->key));
- else if (rr->key->type == DNS_TYPE_DNAME)
- return dns_name_endswith(joined, DNS_RESOURCE_KEY_NAME(rr->key));
+ if (cname->type == DNS_TYPE_CNAME)
+ return dns_name_equal(joined, DNS_RESOURCE_KEY_NAME(cname));
+ else if (cname->type == DNS_TYPE_DNAME)
+ return dns_name_endswith(joined, DNS_RESOURCE_KEY_NAME(cname));
}
return 0;
+}
+
+int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa) {
+ assert(soa);
+ assert(key);
+
+ /* Checks whether 'soa' is a SOA record for the specified key. */
+ if (soa->class != DNS_CLASS_IN)
+ return 0;
+
+ if (soa->type != DNS_TYPE_SOA)
+ return 0;
+
+ if (!dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa)))
+ return 0;
+
+ return 1;
}
static void dns_resource_key_hash_func(const void *i, struct siphash *state) {
@@ -303,7 +326,7 @@ int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) {
t = tbuf;
}
- if (asprintf(&s, "%s %s %-5s", DNS_RESOURCE_KEY_NAME(key), c, t) < 0)
+ if (asprintf(&s, "%s. %s %-5s", DNS_RESOURCE_KEY_NAME(key), c, t) < 0)
return -ENOMEM;
*ret = s;
@@ -503,6 +526,9 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor
assert(a);
assert(b);
+ if (a == b)
+ return 1;
+
r = dns_resource_key_equal(a->key, b->key);
if (r <= 0)
return r;
@@ -1090,6 +1116,9 @@ DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) {
bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) {
+ if (a == b)
+ return true;
+
if (!a != !b)
return false;
diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h
index b82fa77562..632ee59994 100644
--- a/src/resolve/resolved-dns-rr.h
+++ b/src/resolve/resolved-dns-rr.h
@@ -45,6 +45,9 @@ enum {
#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 */
@@ -76,6 +79,7 @@ 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
@@ -110,7 +114,7 @@ struct DnsResourceRecord {
struct {
void *data;
size_t size;
- } generic;
+ } generic, opt;
struct {
uint16_t priority;
@@ -242,10 +246,15 @@ 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_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain);
+int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa);
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);
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
index a90692cdf4..5bd733a8ff 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;
@@ -392,6 +441,10 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
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;
+ /* Never respond to some of the domains listed in RFC6761 */
+ if (dns_name_endswith(domain, "invalid") > 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
@@ -409,7 +462,11 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
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)
+ 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;
@@ -449,7 +506,7 @@ int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) {
if (s->protocol == DNS_PROTOCOL_DNS) {
- /* On classic DNS, lookin up non-address RRs is always
+ /* On classic DNS, looking 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.) */
@@ -477,19 +534,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,
};
@@ -508,7 +561,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,
};
@@ -527,6 +580,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,
@@ -753,7 +822,11 @@ static int dns_scope_make_conflict_packet(
0 /* (ad) */,
0 /* (cd) */,
0));
- random_bytes(&DNS_PACKET_HEADER(p)->id, sizeof(uint16_t));
+
+ /* For mDNS, the transaction ID should always be 0 */
+ if (s->protocol != DNS_PROTOCOL_MDNS)
+ random_bytes(&DNS_PACKET_HEADER(p)->id, sizeof(uint16_t));
+
DNS_PACKET_HEADER(p)->qdcount = htobe16(1);
DNS_PACKET_HEADER(p)->arcount = htobe16(1);
diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h
index 15d9a1fd6f..2fc2e07deb 100644
--- a/src/resolve/resolved-dns-scope.h
+++ b/src/resolve/resolved-dns-scope.h
@@ -93,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);
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
index 1103a34c6f..f09788b0c6 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"
@@ -31,6 +32,7 @@
DnsTransaction* dns_transaction_free(DnsTransaction *t) {
DnsQueryCandidate *c;
DnsZoneItem *i;
+ DnsTransaction *z;
if (!t)
return NULL;
@@ -58,14 +60,25 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
dns_resource_key_unref(t->key);
- while ((c = set_steal_first(t->query_candidates)))
+ while ((c = set_steal_first(t->notify_query_candidates)))
set_remove(c->transactions, t);
+ set_free(t->notify_query_candidates);
- set_free(t->query_candidates);
-
- while ((i = set_steal_first(t->zone_items)))
+ while ((i = set_steal_first(t->notify_zone_items)))
i->probe_transaction = NULL;
- set_free(t->zone_items);
+ set_free(t->notify_zone_items);
+
+ while ((z = set_steal_first(t->notify_transactions)))
+ set_remove(z->dnssec_transactions, t);
+ set_free(t->notify_transactions);
+
+ while ((z = set_steal_first(t->dnssec_transactions))) {
+ set_remove(z->notify_transactions, t);
+ dns_transaction_gc(z);
+ }
+ set_free(t->dnssec_transactions);
+
+ dns_answer_unref(t->validated_keys);
free(t);
return NULL;
@@ -79,7 +92,9 @@ void dns_transaction_gc(DnsTransaction *t) {
if (t->block_gc > 0)
return;
- if (set_isempty(t->query_candidates) && set_isempty(t->zone_items))
+ if (set_isempty(t->notify_query_candidates) &&
+ set_isempty(t->notify_zone_items) &&
+ set_isempty(t->notify_transactions))
dns_transaction_free(t);
}
@@ -91,6 +106,14 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
assert(s);
assert(key);
+ /* Don't allow looking up invalid or pseudo RRs */
+ if (!dns_type_is_valid_query(key->type))
+ return -EINVAL;
+
+ /* We only support the IN class */
+ if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY)
+ return -EOPNOTSUPP;
+
r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL);
if (r < 0)
return r;
@@ -105,6 +128,7 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
t->dns_udp_fd = -1;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
+ t->dnssec_result = _DNSSEC_RESULT_INVALID;
t->key = dns_resource_key_ref(key);
/* Find a fresh, unused transaction id */
@@ -174,7 +198,7 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
log_debug("We have the lexicographically larger IP address and thus lost in the conflict.");
t->block_gc++;
- while ((z = set_first(t->zone_items))) {
+ while ((z = set_first(t->notify_zone_items))) {
/* First, make sure the zone item drops the reference
* to us */
dns_zone_item_probe_stop(z);
@@ -191,10 +215,11 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
DnsQueryCandidate *c;
DnsZoneItem *z;
+ DnsTransaction *d;
Iterator i;
assert(t);
- assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
+ assert(!DNS_TRANSACTION_IS_LIVE(state));
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
@@ -214,10 +239,12 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
/* Notify all queries that are interested, but make sure the
* transaction isn't freed while we are still looking at it */
t->block_gc++;
- SET_FOREACH(c, t->query_candidates, i)
- dns_query_candidate_ready(c);
- SET_FOREACH(z, t->zone_items, i)
- dns_zone_item_ready(z);
+ SET_FOREACH(c, t->notify_query_candidates, i)
+ dns_query_candidate_notify(c);
+ SET_FOREACH(z, t->notify_zone_items, i)
+ dns_zone_item_notify(z);
+ SET_FOREACH(d, t->notify_transactions, i)
+ dns_transaction_notify(d, t);
t->block_gc--;
dns_transaction_gc(t);
@@ -243,7 +270,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;
}
@@ -324,6 +351,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
t->server = dns_server_ref(server);
t->received = dns_packet_unref(t->received);
t->answer = dns_answer_unref(t->answer);
+ t->n_answer_cacheable = 0;
t->answer_rcode = 0;
t->stream->complete = on_stream_complete;
t->stream->transaction = t;
@@ -347,6 +375,62 @@ static void dns_transaction_next_dns_server(DnsTransaction *t) {
dns_scope_next_dns_server(t->scope);
}
+static void dns_transaction_cache_answer(DnsTransaction *t) {
+ assert(t);
+
+ /* For mDNS we cache whenever we get the packet, rather than
+ * in each transaction. */
+ if (!IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR))
+ return;
+
+ /* We never cache if this packet is from the local host, under
+ * the assumption that a locally running DNS server would
+ * cache this anyway, and probably knows better when to flush
+ * the cache then we could. */
+ if (!DNS_PACKET_SHALL_CACHE(t->received))
+ return;
+
+ dns_cache_put(&t->scope->cache,
+ t->key,
+ t->answer_rcode,
+ t->answer,
+ t->n_answer_cacheable,
+ t->answer_authenticated,
+ 0,
+ t->received->family,
+ &t->received->sender);
+}
+
+static void dns_transaction_process_dnssec(DnsTransaction *t) {
+ int r;
+
+ assert(t);
+
+ /* Are there ongoing DNSSEC transactions? If so, let's wait for them. */
+ if (!set_isempty(t->dnssec_transactions))
+ return;
+
+ /* All our auxiliary DNSSEC transactions are complete now. Try
+ * to validate our RRset now. */
+ r = dns_transaction_validate_dnssec(t);
+ if (r < 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ return;
+ }
+
+ if (!IN_SET(t->dnssec_result, _DNSSEC_RESULT_INVALID, DNSSEC_VALIDATED, DNSSEC_NO_SIGNATURE /* FOR NOW! */)) {
+ dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
+ return;
+ }
+
+ dns_transaction_cache_answer(t);
+
+ if (t->answer_rcode == DNS_RCODE_SUCCESS)
+ dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
+ else
+ dns_transaction_complete(t, DNS_TRANSACTION_FAILURE);
+}
+
void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
usec_t ts;
int r;
@@ -361,7 +445,10 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
* should hence not attempt to access the query or transaction
* after calling this function. */
+ log_debug("Processing incoming packet on transaction %" PRIu16".", t->id);
+
switch (t->scope->protocol) {
+
case DNS_PROTOCOL_LLMNR:
assert(t->scope->link);
@@ -384,6 +471,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;
@@ -415,12 +514,13 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
switch (t->scope->protocol) {
+
case DNS_PROTOCOL_DNS:
assert(t->server);
if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) {
- /* request failed, immediately try again with reduced features */
+ /* Request failed, immediately try again with reduced features */
log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p)));
dns_server_packet_failed(t->server, t->current_features);
@@ -436,16 +536,24 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
dns_server_packet_received(t->server, t->current_features, ts - t->start_usec, p->size);
break;
+
case DNS_PROTOCOL_LLMNR:
case DNS_PROTOCOL_MDNS:
dns_scope_packet_received(t->scope, ts - t->start_usec);
-
break;
+
default:
- break;
+ assert_not_reached("Invalid DNS protocol.");
}
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) {
@@ -474,41 +582,54 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
}
}
- /* Parse and update the cache */
+ /* Parse message, if it isn't parsed yet. */
r = dns_packet_extract(p);
if (r < 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;
+ if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) {
+
+ /* Only consider responses with equivalent query section to the request */
+ r = dns_packet_is_reply_for(p, t->key);
+ if (r < 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ return;
+ }
+ if (r == 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. However,
+ * if we know the message is authenticated, we might
+ * as well cache everything. */
+ if (t->answer_authenticated)
+ t->n_answer_cacheable = (unsigned) -1; /* everything! */
+ else
+ t->n_answer_cacheable = DNS_PACKET_ANCOUNT(t->received); /* only the answer section */
+
+ r = dns_transaction_request_dnssec_keys(t);
+ if (r < 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ return;
+ }
+ if (r > 0) {
+ /* There are DNSSEC transactions pending now. Update the state accordingly. */
+ t->state = DNS_TRANSACTION_VALIDATING;
+ 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);
-
- if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS)
- dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
- else
- dns_transaction_complete(t, DNS_TRANSACTION_FAILURE);
+ dns_transaction_process_dnssec(t);
}
static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
@@ -527,7 +648,7 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use
DNS_PACKET_ID(p) == t->id)
dns_transaction_process_reply(t, p);
else
- log_debug("Invalid DNS packet.");
+ log_debug("Invalid DNS packet, ignoring.");
return 0;
}
@@ -571,21 +692,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 */
@@ -598,38 +724,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, 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;
-}
-
static usec_t transaction_get_resend_timeout(DnsTransaction *t) {
assert(t);
assert(t->scope);
@@ -639,17 +733,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(DnsTransaction *t, usec_t ts) {
bool had_stream;
- usec_t ts;
int r;
assert(t);
@@ -658,11 +753,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;
@@ -675,12 +765,11 @@ 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);
t->answer = dns_answer_unref(t->answer);
+ t->n_answer_cacheable = 0;
t->answer_rcode = 0;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
@@ -700,7 +789,7 @@ int dns_transaction_go(DnsTransaction *t) {
/* Check the zone, but only if this transaction is not used
* for probing or verifying a zone item. */
- if (set_isempty(t->zone_items)) {
+ if (set_isempty(t->notify_zone_items)) {
r = dns_zone_lookup(&t->scope->zone, t->key, &t->answer, NULL, NULL);
if (r < 0)
@@ -716,7 +805,7 @@ int dns_transaction_go(DnsTransaction *t) {
/* Check the cache, but only if this transaction is not used
* for probing or verifying a zone item. */
- if (set_isempty(t->zone_items)) {
+ if (set_isempty(t->notify_zone_items)) {
/* Before trying the cache, let's make sure we figured out a
* server to use. Should this cause a change of server this
@@ -739,31 +828,210 @@ 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 coalesce 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(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);
+
+ /* 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(t, ts);
+ if (r <= 0)
+ return r;
+
+ if (log_get_max_level() >= LOG_DEBUG) {
+ _cleanup_free_ char *ks = NULL;
+
+ (void) dns_resource_key_to_string(t->key, &ks);
+
+ log_debug("Excercising transaction for <%s> on scope %s on %s/%s",
+ ks ? strstrip(ks) : "???",
+ 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;
}
@@ -810,22 +1078,458 @@ 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;
+}
+
+static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) {
+ DnsTransaction *aux;
+ int r;
+
+ assert(t);
+ assert(ret);
+ assert(key);
+
+ aux = dns_scope_find_transaction(t->scope, key, true);
+ if (!aux) {
+ r = dns_transaction_new(&aux, t->scope, key);
+ if (r < 0)
+ return r;
+ } else {
+ if (set_contains(t->dnssec_transactions, aux)) {
+ *ret = aux;
+ return 0;
+ }
+ }
+
+ r = set_ensure_allocated(&t->dnssec_transactions, NULL);
+ if (r < 0)
+ goto gc;
+
+ r = set_ensure_allocated(&aux->notify_transactions, NULL);
+ if (r < 0)
+ goto gc;
+
+ r = set_put(t->dnssec_transactions, aux);
+ if (r < 0)
+ goto gc;
+
+ r = set_put(aux->notify_transactions, t);
+ if (r < 0) {
+ (void) set_remove(t->dnssec_transactions, aux);
+ goto gc;
+ }
+
+ *ret = aux;
+ return 1;
+
+gc:
+ dns_transaction_gc(aux);
+ return r;
+}
+
+static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *a = NULL;
+ DnsTransaction *aux;
+ int r;
+
+ assert(t);
+ assert(key);
+
+ /* Try to get the data from the trust anchor */
+ r = dns_trust_anchor_lookup(&t->scope->manager->trust_anchor, key, &a);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ r = dns_answer_extend(&t->validated_keys, a);
+ if (r < 0)
+ return r;
+
+ return 0;
+ }
+
+ /* This didn't work, ask for it via the network/cache then. */
+ r = dns_transaction_add_dnssec_transaction(t, key, &aux);
+ if (r < 0)
+ return r;
+
+ if (aux->state == DNS_TRANSACTION_NULL) {
+ r = dns_transaction_go(aux);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
+ DnsResourceRecord *rr;
+ int r;
+
+ assert(t);
+
+ if (t->scope->dnssec_mode != DNSSEC_YES)
+ return 0;
+
+ DNS_ANSWER_FOREACH(rr, t->answer) {
+
+ switch (rr->key->type) {
+
+ case DNS_TYPE_RRSIG: {
+ /* For each RRSIG we request the matching DNSKEY */
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dnskey = NULL;
+
+ /* If this RRSIG is about a DNSKEY RR and the
+ * signer is the same as the owner, then we
+ * already have the DNSKEY, and we don't have
+ * to look for more. */
+ if (rr->rrsig.type_covered == DNS_TYPE_DNSKEY) {
+ r = dns_name_equal(rr->rrsig.signer, DNS_RESOURCE_KEY_NAME(rr->key));
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+ }
+
+ /* If the signer is not a parent of the owner,
+ * then the signature is bogus, let's ignore
+ * it. */
+ r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), rr->rrsig.signer);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ dnskey = dns_resource_key_new(rr->key->class, DNS_TYPE_DNSKEY, rr->rrsig.signer);
+ if (!dnskey)
+ return -ENOMEM;
+
+ log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (key tag: %" PRIu16 ").", t->id, rr->rrsig.key_tag);
+
+ r = dns_transaction_request_dnssec_rr(t, dnskey);
+ if (r < 0)
+ return r;
+ break;
+ }
+
+ case DNS_TYPE_DNSKEY: {
+ /* For each DNSKEY we request the matching DS */
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL;
+
+ ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key));
+ if (!ds)
+ return -ENOMEM;
+
+ log_debug("Requesting DS to validate transaction %" PRIu16" (key tag: %" PRIu16 ").", t->id, dnssec_keytag(rr));
+
+ r = dns_transaction_request_dnssec_rr(t, ds);
+ if (r < 0)
+ return r;
+
+ break;
+ }}
+ }
+
+ return !set_isempty(t->dnssec_transactions);
+}
+
+void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) {
+ int r;
+
+ assert(t);
+ assert(IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING));
+ assert(source);
+
+ /* Invoked whenever any of our auxiliary DNSSEC transactions
+ completed its work. We simply copy the answer from that
+ transaction over. */
+
+ if (source->state != DNS_TRANSACTION_SUCCESS) {
+ log_debug("Auxiliary DNSSEC RR query failed.");
+ t->dnssec_result = DNSSEC_FAILED_AUXILIARY;
+ } else {
+ r = dns_answer_extend(&t->validated_keys, source->answer);
+ if (r < 0) {
+ log_error_errno(r, "Failed to merge validated DNSSEC key data: %m");
+ t->dnssec_result = DNSSEC_FAILED_AUXILIARY;
+ }
+ }
+
+ /* Detach us from the DNSSEC transaction. */
+ (void) set_remove(t->dnssec_transactions, source);
+ (void) set_remove(source->notify_transactions, t);
+
+ /* If the state is still PENDING, we are still in the loop
+ * that adds further DNSSEC transactions, hence don't check if
+ * we are ready yet. If the state is VALIDATING however, we
+ * should check if we are complete now. */
+ if (t->state == DNS_TRANSACTION_VALIDATING)
+ dns_transaction_process_dnssec(t);
+}
+
+static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRecord *rr) {
+ int r;
+
+ assert(t);
+ assert(rr);
+
+ /* Check if the specified RR is the "primary" response,
+ * i.e. either matches the question precisely or is a
+ * CNAME/DNAME for it, or is any kind of NSEC/NSEC3 RR */
+
+ if (IN_SET(rr->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3))
+ return 1;
+
+ r = dns_resource_key_match_rr(t->key, rr, NULL);
+ if (r != 0)
+ return r;
+
+ r = dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL);
+ if (r != 0)
+ return r;
+
+ return 0;
+}
+
+static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) {
+ DnsResourceRecord *rr;
+ int ifindex, r;
+
+ assert(t);
+
+ /* Add all DNSKEY RRs from the answer that are validated by DS
+ * RRs from the list of validated keys to the lis of validated
+ * keys. */
+
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) {
+
+ r = dnssec_verify_dnskey_search(rr, t->validated_keys);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ /* If so, the DNSKEY is validated too. */
+ r = dns_answer_add_extend(&t->validated_keys, rr, ifindex);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int dns_transaction_validate_dnssec(DnsTransaction *t) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL;
+ bool dnskeys_finalized = false;
+ DnsResourceRecord *rr;
+ int r;
+
+ assert(t);
+
+ /* We have now collected all DS and DNSKEY RRs in
+ * t->validated_keys, let's see which RRs we can now
+ * authenticate with that. */
+
+ if (t->scope->dnssec_mode != DNSSEC_YES)
+ return 0;
+
+ /* Already validated */
+ if (t->dnssec_result != _DNSSEC_RESULT_INVALID)
+ return 0;
+
+ if (IN_SET(t->answer_source, DNS_TRANSACTION_ZONE, DNS_TRANSACTION_TRUST_ANCHOR)) {
+ t->dnssec_result = DNSSEC_VALIDATED;
+ t->answer_authenticated = true;
+ return 0;
+ }
+
+ if (log_get_max_level() >= LOG_DEBUG) {
+ _cleanup_free_ char *ks = NULL;
+
+ (void) dns_resource_key_to_string(t->key, &ks);
+ log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, ks ? strstrip(ks) : "???");
+ }
+
+ /* First see if there are DNSKEYs we already known a validated DS for. */
+ r = dns_transaction_validate_dnskey_by_ds(t);
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ bool changed = false;
+
+ DNS_ANSWER_FOREACH(rr, t->answer) {
+ DnssecResult result;
+
+ if (rr->key->type == DNS_TYPE_RRSIG)
+ continue;
+
+ r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result);
+ if (r < 0)
+ return r;
+
+ if (log_get_max_level() >= LOG_DEBUG) {
+ _cleanup_free_ char *rrs = NULL;
+
+ (void) dns_resource_record_to_string(rr, &rrs);
+ log_debug("Looking at %s: %s", rrs ? strstrip(rrs) : "???", dnssec_result_to_string(result));
+ }
+
+ if (result == DNSSEC_VALIDATED) {
+
+ /* Add the validated RRset to the new list of validated RRsets */
+ r = dns_answer_copy_by_key(&validated, t->answer, rr->key);
+ if (r < 0)
+ return r;
+
+ if (rr->key->type == DNS_TYPE_DNSKEY) {
+ /* If we just validated a
+ * DNSKEY RRset, then let's
+ * add these keys to the set
+ * of validated keys for this
+ * transaction. */
+
+ r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key);
+ if (r < 0)
+ return r;
+ }
+
+ /* Now, remove this RRset from the RRs still to process */
+ r = dns_answer_remove_by_key(&t->answer, rr->key);
+ if (r < 0)
+ return r;
+
+ /* Exit the loop, we dropped something from the answer, start from the beginning */
+ changed = true;
+ break;
+
+ } else if (dnskeys_finalized) {
+ /* If we haven't read all DNSKEYs yet
+ * a negative result of the validation
+ * is irrelevant, as there might be
+ * more DNSKEYs coming. */
+
+ r = dns_transaction_is_primary_response(t, rr);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ /* This is a primary response
+ * to our question, and it
+ * failed validation. That's
+ * fatal. */
+ t->dnssec_result = result;
+ return 0;
+ }
+
+ /* This is just some auxiliary
+ * data. Just remove the RRset and
+ * continue. */
+ r = dns_answer_remove_by_key(&t->answer, rr->key);
+ if (r < 0)
+ return r;
+
+ /* Exit the loop, we dropped something from the answer, start from the beginning */
+ changed = true;
+ break;
+ }
+ }
+
+ if (changed)
+ continue;
+
+ if (!dnskeys_finalized) {
+ /* OK, now we know we have added all DNSKEYs
+ * we possibly could to our validated
+ * list. Now run the whole thing once more,
+ * and strip everything we still cannot
+ * validate.
+ */
+ dnskeys_finalized = true;
+ continue;
+ }
+
+ /* We're done */
+ break;
+ }
+
+ dns_answer_unref(t->answer);
+ t->answer = validated;
+ validated = NULL;
+
+ /* Everything that's now in t->answer is known to be good, hence cacheable. */
+ t->n_answer_cacheable = (unsigned) -1; /* everything! */
+
+ /* At this point the answer only contains validated
+ * RRsets. Now, let's see if it actually answers the question
+ * we asked. If so, great! If it doesn't, then see if
+ * NSEC/NSEC3 can prove this. */
+ r = dns_answer_match_key(t->answer, t->key);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ /* Yes, it answer the question, everything is authenticated. */
+ t->dnssec_result = DNSSEC_VALIDATED;
+ t->answer_rcode = DNS_RCODE_SUCCESS;
+ t->answer_authenticated = true;
+ } else if (r == 0) {
+ DnssecNsecResult nr;
+
+ /* Bummer! Let's check NSEC/NSEC3 */
+ r = dnssec_test_nsec(t->answer, t->key, &nr);
+ if (r < 0)
+ return r;
+
+ switch (nr) {
+
+ case DNSSEC_NSEC_NXDOMAIN:
+ /* NSEC proves the domain doesn't exist. Very good. */
+ t->dnssec_result = DNSSEC_VALIDATED;
+ t->answer_rcode = DNS_RCODE_NXDOMAIN;
+ t->answer_authenticated = true;
+ break;
+
+ case DNSSEC_NSEC_NODATA:
+ /* NSEC proves that there's no data here, very good. */
+ t->dnssec_result = DNSSEC_VALIDATED;
+ t->answer_rcode = DNS_RCODE_SUCCESS;
+ t->answer_authenticated = true;
+ break;
+
+ case DNSSEC_NSEC_NO_RR:
+ /* No NSEC data? Bummer! */
+ t->dnssec_result = DNSSEC_UNSIGNED;
+ break;
+
+ case DNSSEC_NSEC_FOUND:
+ /* NSEC says it needs to be there, but we couldn't find it? Bummer! */
+ t->dnssec_result = DNSSEC_NSEC_MISMATCH;
+ break;
+
+ default:
+ assert_not_reached("Unexpected NSEC result.");
+ }
+ }
+
return 1;
}
static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = {
[DNS_TRANSACTION_NULL] = "null",
[DNS_TRANSACTION_PENDING] = "pending",
+ [DNS_TRANSACTION_VALIDATING] = "validating",
[DNS_TRANSACTION_FAILURE] = "failure",
[DNS_TRANSACTION_SUCCESS] = "success",
[DNS_TRANSACTION_NO_SERVERS] = "no-servers",
@@ -834,6 +1538,7 @@ static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX]
[DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply",
[DNS_TRANSACTION_RESOURCES] = "resources",
[DNS_TRANSACTION_ABORTED] = "aborted",
+ [DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed",
};
DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState);
diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h
index a3058ce6e8..1f35a4dd8f 100644
--- a/src/resolve/resolved-dns-transaction.h
+++ b/src/resolve/resolved-dns-transaction.h
@@ -28,6 +28,7 @@ typedef enum DnsTransactionSource DnsTransactionSource;
enum DnsTransactionState {
DNS_TRANSACTION_NULL,
DNS_TRANSACTION_PENDING,
+ DNS_TRANSACTION_VALIDATING,
DNS_TRANSACTION_FAILURE,
DNS_TRANSACTION_SUCCESS,
DNS_TRANSACTION_NO_SERVERS,
@@ -36,10 +37,13 @@ enum DnsTransactionState {
DNS_TRANSACTION_INVALID_REPLY,
DNS_TRANSACTION_RESOURCES,
DNS_TRANSACTION_ABORTED,
+ DNS_TRANSACTION_DNSSEC_FAILED,
_DNS_TRANSACTION_STATE_MAX,
_DNS_TRANSACTION_STATE_INVALID = -1
};
+#define DNS_TRANSACTION_IS_LIVE(state) IN_SET((state), DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)
+
enum DnsTransactionSource {
DNS_TRANSACTION_NETWORK,
DNS_TRANSACTION_CACHE,
@@ -60,18 +64,26 @@ struct DnsTransaction {
DnsResourceKey *key;
DnsTransactionState state;
+ DnssecResult dnssec_result;
+
uint16_t id;
- bool initial_jitter;
+ bool initial_jitter_scheduled;
+ bool initial_jitter_elapsed;
DnsPacket *sent, *received;
DnsAnswer *answer;
+ unsigned n_answer_cacheable; /* Specifies how many RRs of the answer shall be cached, from the beginning */
int answer_rcode;
DnsTransactionSource answer_source;
bool answer_authenticated;
+ /* Contains DS and DNSKEY RRs we already verified and need to authenticate this reply */
+ DnsAnswer *validated_keys;
+
usec_t start_usec;
+ usec_t next_attempt_after;
sd_event_source *timeout_event_source;
unsigned n_attempts;
@@ -81,7 +93,7 @@ struct DnsTransaction {
/* The active server */
DnsServer *server;
- /* the features of the DNS server at time of transaction start */
+ /* The features of the DNS server at time of transaction start */
DnsServerFeatureLevel current_features;
/* TCP connection logic, if we need it */
@@ -90,11 +102,21 @@ struct DnsTransaction {
/* Query candidates this transaction is referenced by and that
* shall be notified about this specific transaction
* completing. */
- Set *query_candidates;
+ Set *notify_query_candidates;
/* Zone items this transaction is referenced by and that shall
* be notified about completion. */
- Set *zone_items;
+ Set *notify_zone_items;
+
+ /* Other transactions that this transactions is referenced by
+ * and that shall be notified about completion. This is used
+ * when transactions want to validate their RRsets, but need
+ * another DNSKEY or DS RR to do so. */
+ Set *notify_transactions;
+
+ /* The opposite direction: the transactions this transaction
+ * created in order to request DNSKEY or DS RRs. */
+ Set *dnssec_transactions;
unsigned block_gc;
@@ -110,6 +132,10 @@ int dns_transaction_go(DnsTransaction *t);
void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p);
void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state);
+void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source);
+int dns_transaction_validate_dnssec(DnsTransaction *t);
+int dns_transaction_request_dnssec_keys(DnsTransaction *t);
+
const char* dns_transaction_state_to_string(DnsTransactionState p) _const_;
DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_;
@@ -119,6 +145,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-zone.c b/src/resolve/resolved-dns-zone.c
index 78f44d51a2..8046e2ed34 100644
--- a/src/resolve/resolved-dns-zone.c
+++ b/src/resolve/resolved-dns-zone.c
@@ -39,7 +39,7 @@ void dns_zone_item_probe_stop(DnsZoneItem *i) {
t = i->probe_transaction;
i->probe_transaction = NULL;
- set_remove(t->zone_items, i);
+ set_remove(t->notify_zone_items, i);
dns_transaction_gc(t);
}
@@ -184,11 +184,11 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) {
return r;
}
- r = set_ensure_allocated(&t->zone_items, NULL);
+ r = set_ensure_allocated(&t->notify_zone_items, NULL);
if (r < 0)
goto gc;
- r = set_put(t->zone_items, i);
+ r = set_put(t->notify_zone_items, i);
if (r < 0)
goto gc;
@@ -206,7 +206,7 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) {
}
}
- dns_zone_item_ready(i);
+ dns_zone_item_notify(i);
return 0;
gc:
@@ -491,7 +491,7 @@ void dns_zone_item_conflict(DnsZoneItem *i) {
manager_next_hostname(i->scope->manager);
}
-void dns_zone_item_ready(DnsZoneItem *i) {
+void dns_zone_item_notify(DnsZoneItem *i) {
_cleanup_free_ char *pretty = NULL;
assert(i);
@@ -500,7 +500,7 @@ void dns_zone_item_ready(DnsZoneItem *i) {
if (i->block_ready > 0)
return;
- if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING))
+ if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
return;
if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h
index 44a8624c30..dbd6a2a368 100644
--- a/src/resolve/resolved-dns-zone.h
+++ b/src/resolve/resolved-dns-zone.h
@@ -70,7 +70,7 @@ void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr);
int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **answer, DnsAnswer **soa, bool *tentative);
void dns_zone_item_conflict(DnsZoneItem *i);
-void dns_zone_item_ready(DnsZoneItem *i);
+void dns_zone_item_notify(DnsZoneItem *i);
int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr);
int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key);
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 5a3696ccb0..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,6 +473,7 @@ 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;
@@ -528,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;
}
@@ -559,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);
@@ -1024,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 1056f23ab7..b52273403a 100644
--- a/src/resolve/resolved-manager.h
+++ b/src/resolve/resolved-manager.h
@@ -54,6 +54,7 @@ struct Manager {
sd_event *event;
Support llmnr_support;
+ Support mdns_support;
/* Network */
Hashmap *links;
@@ -102,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..d6973a6999
--- /dev/null
+++ b/src/resolve/resolved-mdns.c
@@ -0,0 +1,290 @@
+/*-*- 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) {
+ DnsResourceRecord *rr;
+
+ 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);
+
+ DNS_ANSWER_FOREACH(rr, p->answer) {
+ const char *name = DNS_RESOURCE_KEY_NAME(rr->key);
+ DnsTransaction *t;
+
+ /* If the received reply packet contains ANY record that is not .local or .in-addr.arpa,
+ * we assume someone's playing tricks on us and discard the packet completely. */
+ if (!(dns_name_endswith(name, "in-addr.arpa") > 0 ||
+ dns_name_endswith(name, "local") > 0))
+ return 0;
+
+ 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/test-dnssec.c b/src/resolve/test-dnssec.c
index 0b2ffeeddd..807eeb3d9a 100644
--- a/src/resolve/test-dnssec.c
+++ b/src/resolve/test-dnssec.c
@@ -27,6 +27,103 @@
#include "resolved-dns-dnssec.h"
#include "resolved-dns-rr.h"
#include "string-util.h"
+#include "hexdecoct.h"
+
+static void test_dnssec_verify_rrset2(void) {
+
+ static const uint8_t signature_blob[] = {
+ 0x48, 0x45, 0xc8, 0x8b, 0xc0, 0x14, 0x92, 0xf5, 0x15, 0xc6, 0x84, 0x9d, 0x2f, 0xe3, 0x32, 0x11,
+ 0x7d, 0xf1, 0xe6, 0x87, 0xb9, 0x42, 0xd3, 0x8b, 0x9e, 0xaf, 0x92, 0x31, 0x0a, 0x53, 0xad, 0x8b,
+ 0xa7, 0x5c, 0x83, 0x39, 0x8c, 0x28, 0xac, 0xce, 0x6e, 0x9c, 0x18, 0xe3, 0x31, 0x16, 0x6e, 0xca,
+ 0x38, 0x31, 0xaf, 0xd9, 0x94, 0xf1, 0x84, 0xb1, 0xdf, 0x5a, 0xc2, 0x73, 0x22, 0xf6, 0xcb, 0xa2,
+ 0xe7, 0x8c, 0x77, 0x0c, 0x74, 0x2f, 0xc2, 0x13, 0xb0, 0x93, 0x51, 0xa9, 0x4f, 0xae, 0x0a, 0xda,
+ 0x45, 0xcc, 0xfd, 0x43, 0x99, 0x36, 0x9a, 0x0d, 0x21, 0xe0, 0xeb, 0x30, 0x65, 0xd4, 0xa0, 0x27,
+ 0x37, 0x3b, 0xe4, 0xc1, 0xc5, 0xa1, 0x2a, 0xd1, 0x76, 0xc4, 0x7e, 0x64, 0x0e, 0x5a, 0xa6, 0x50,
+ 0x24, 0xd5, 0x2c, 0xcc, 0x6d, 0xe5, 0x37, 0xea, 0xbd, 0x09, 0x34, 0xed, 0x24, 0x06, 0xa1, 0x22,
+ };
+
+ static const uint8_t dnskey_blob[] = {
+ 0x03, 0x01, 0x00, 0x01, 0xc3, 0x7f, 0x1d, 0xd1, 0x1c, 0x97, 0xb1, 0x13, 0x34, 0x3a, 0x9a, 0xea,
+ 0xee, 0xd9, 0x5a, 0x11, 0x1b, 0x17, 0xc7, 0xe3, 0xd4, 0xda, 0x20, 0xbc, 0x5d, 0xba, 0x74, 0xe3,
+ 0x37, 0x99, 0xec, 0x25, 0xce, 0x93, 0x7f, 0xbd, 0x22, 0x73, 0x7e, 0x14, 0x71, 0xe0, 0x60, 0x07,
+ 0xd4, 0x39, 0x8b, 0x5e, 0xe9, 0xba, 0x25, 0xe8, 0x49, 0xe9, 0x34, 0xef, 0xfe, 0x04, 0x5c, 0xa5,
+ 0x27, 0xcd, 0xa9, 0xda, 0x70, 0x05, 0x21, 0xab, 0x15, 0x82, 0x24, 0xc3, 0x94, 0xf5, 0xd7, 0xb7,
+ 0xc4, 0x66, 0xcb, 0x32, 0x6e, 0x60, 0x2b, 0x55, 0x59, 0x28, 0x89, 0x8a, 0x72, 0xde, 0x88, 0x56,
+ 0x27, 0x95, 0xd9, 0xac, 0x88, 0x4f, 0x65, 0x2b, 0x68, 0xfc, 0xe6, 0x41, 0xc1, 0x1b, 0xef, 0x4e,
+ 0xd6, 0xc2, 0x0f, 0x64, 0x88, 0x95, 0x5e, 0xdd, 0x3a, 0x02, 0x07, 0x50, 0xa9, 0xda, 0xa4, 0x49,
+ 0x74, 0x62, 0xfe, 0xd7,
+ };
+
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *nsec = NULL, *rrsig = NULL, *dnskey = NULL;
+ _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+ _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL;
+ DnssecResult result;
+
+ nsec = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC, "nasa.gov");
+ assert_se(nsec);
+
+ nsec->nsec.next_domain_name = strdup("3D-Printing.nasa.gov");
+ assert_se(nsec->nsec.next_domain_name);
+
+ nsec->nsec.types = bitmap_new();
+ assert_se(nsec->nsec.types);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_A) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NS) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_SOA) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_MX) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_TXT) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_RRSIG) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NSEC) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_DNSKEY) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, 65534) >= 0);
+
+ assert_se(dns_resource_record_to_string(nsec, &x) >= 0);
+ log_info("NSEC: %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_NSEC;
+ rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256;
+ rrsig->rrsig.labels = 2;
+ rrsig->rrsig.original_ttl = 300;
+ rrsig->rrsig.expiration = 0x5689002f;
+ rrsig->rrsig.inception = 0x56617230;
+ rrsig->rrsig.key_tag = 30390;
+ 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(nsec->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, nsec, 0) >= 0);
+
+ /* Validate the RR as it if was 2015-12-11 today */
+ assert_se(dnssec_verify_rrset(answer, nsec->key, rrsig, dnskey, 1449849318*USEC_PER_SEC, &result) >= 0);
+ assert_se(result == DNSSEC_VALIDATED);
+}
static void test_dnssec_verify_rrset(void) {
@@ -56,6 +153,7 @@ static void test_dnssec_verify_rrset(void) {
_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;
+ DnssecResult result;
a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov");
assert_se(a);
@@ -106,7 +204,8 @@ static void test_dnssec_verify_rrset(void) {
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);
+ assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC, &result) >= 0);
+ assert_se(result == DNSSEC_VALIDATED);
}
static void test_dnssec_verify_dns_key(void) {
@@ -207,11 +306,46 @@ static void test_dnssec_canonicalize(void) {
test_dnssec_canonicalize_one("FOO..bar.", NULL, -EINVAL);
}
+static void test_dnssec_nsec3_hash(void) {
+ static const uint8_t salt[] = { 0xB0, 0x1D, 0xFA, 0xCE };
+ static const uint8_t next_hashed_name[] = { 0x84, 0x10, 0x26, 0x53, 0xc9, 0xfa, 0x4d, 0x85, 0x6c, 0x97, 0x82, 0xe2, 0x8f, 0xdf, 0x2d, 0x5e, 0x87, 0x69, 0xc4, 0x52 };
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
+ _cleanup_free_ char *a = NULL, *b = NULL;
+ uint8_t h[DNSSEC_HASH_SIZE_MAX];
+ int k;
+
+ /* The NSEC3 RR for eurid.eu on 2015-12-14. */
+ rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC3, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM.eurid.eu.");
+ assert_se(rr);
+
+ rr->nsec3.algorithm = DNSSEC_DIGEST_SHA1;
+ rr->nsec3.flags = 1;
+ rr->nsec3.iterations = 1;
+ rr->nsec3.salt = memdup(salt, sizeof(salt));
+ assert_se(rr->nsec3.salt);
+ rr->nsec3.salt_size = sizeof(salt);
+ rr->nsec3.next_hashed_name = memdup(next_hashed_name, sizeof(next_hashed_name));
+ assert_se(rr->nsec3.next_hashed_name);
+ rr->nsec3.next_hashed_name_size = sizeof(next_hashed_name);
+
+ assert_se(dns_resource_record_to_string(rr, &a) >= 0);
+ log_info("NSEC3: %s", a);
+
+ k = dnssec_nsec3_hash(rr, "eurid.eu", &h);
+ assert_se(k >= 0);
+
+ b = base32hexmem(h, k, false);
+ assert_se(b);
+ assert_se(strcasecmp(b, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM") == 0);
+}
+
int main(int argc, char*argv[]) {
test_dnssec_canonicalize();
test_dnssec_verify_dns_key();
test_dnssec_verify_rrset();
+ test_dnssec_verify_rrset2();
+ test_dnssec_nsec3_hash();
return 0;
}