From 97c67192eadaffe67b803ec5b991a92bb1137d0b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 14 Jan 2016 18:03:03 +0100 Subject: resolved: when validating an RRset, store information about the synthesizing source and zone in each RR Having this information available is useful when we need to check whether various RRs are suitable for proofs. This information is stored in the RRs as number of labels to skip from the beginning of the owner name to reach the synthesizing source/signer. Simple accessor calls are then added to retrieve the signer/source from the RR using this information. This also moves validation of a a number of RRSIG parameters into a new call dnssec_rrsig_prepare() that as side-effect initializes the two numeric values. --- src/resolve/resolved-dns-rr.c | 58 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) (limited to 'src/resolve/resolved-dns-rr.c') diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index dbf840157f..53fd708365 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -344,6 +344,7 @@ DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) { rr->n_ref = 1; rr->key = dns_resource_key_ref(key); rr->expiry = USEC_INFINITY; + rr->n_skip_labels_signer = rr->n_skip_labels_source = (unsigned) -1; return rr; } @@ -1085,6 +1086,63 @@ int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { return 0; } +int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret) { + const char *n; + int r; + + assert(rr); + assert(ret); + + /* Returns the RRset's signer, if it is known. */ + + if (rr->n_skip_labels_signer == (unsigned) -1) + return -ENODATA; + + n = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_skip(n, rr->n_skip_labels_signer, &n); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + *ret = n; + return 0; +} + +int dns_resource_record_source(DnsResourceRecord *rr, const char **ret) { + const char *n; + int r; + + assert(rr); + assert(ret); + + /* Returns the RRset's synthesizing source, if it is known. */ + + if (rr->n_skip_labels_source == (unsigned) -1) + return -ENODATA; + + n = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_skip(n, rr->n_skip_labels_source, &n); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + *ret = n; + return 0; +} + +int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) { + const char *signer; + int r; + + r = dns_resource_record_signer(rr, &signer); + if (r < 0) + return r; + + return dns_name_equal(zone, signer); +} + static void dns_resource_record_hash_func(const void *i, struct siphash *state) { const DnsResourceRecord *rr = i; -- cgit v1.2.3-54-g00ecf From ab481675f98d3d3f12e7e48ba6d2159123b9c7bf Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 15 Jan 2016 02:21:22 +0100 Subject: resolved: complete NSEC non-existance proofs This fills in the last few gaps: - When checking if a domain is non-existing, also check that no wildcard for it exists - Ensure we don't base "covering" tests on NSEC RRs from a parent zone - Refuse to accept expanded wildcard NSEC RRs for absence proofs. --- src/resolve/resolved-dns-dnssec.c | 229 +++++++++++++++++++++++++++----------- src/resolve/resolved-dns-rr.c | 25 +++++ src/resolve/resolved-dns-rr.h | 1 + 3 files changed, 187 insertions(+), 68 deletions(-) (limited to 'src/resolve/resolved-dns-rr.c') diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index ac274843f0..2ac085dfd3 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -1617,7 +1617,30 @@ found_closest_encloser: return 0; } -static int dnssec_nsec_test_in_path(DnsResourceRecord *rr, const char *name) { +static int dnssec_nsec_wildcard_equal(DnsResourceRecord *rr, const char *name) { + char label[DNS_LABEL_MAX]; + const char *n; + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether the specified RR has a name beginning in "*.", and if the rest is a suffix of our name */ + + if (rr->n_skip_labels_source != 1) + return 0; + + n = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_label_unescape(&n, label, sizeof(label)); + if (r <= 0) + return r; + if (r != 1 || label[0] != '*') + return 0; + + return dns_name_endswith(name, n); +} + +static int dnssec_nsec_in_path(DnsResourceRecord *rr, const char *name) { const char *nn, *common_suffix; int r; @@ -1653,7 +1676,66 @@ static int dnssec_nsec_test_in_path(DnsResourceRecord *rr, const char *name) { return dns_name_endswith(name, common_suffix); } -static int dns_dnssec_test_wildcard_at_closest_encloser(DnsResourceRecord *rr, const char *name) { +static int dnssec_nsec_from_parent_zone(DnsResourceRecord *rr, const char *name) { + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether this NSEC originates to the parent zone or the child zone. */ + + r = dns_name_parent(&name); + if (r <= 0) + return r; + + r = dns_name_equal(name, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r <= 0) + return r; + + /* DNAME, and NS without SOA is an indication for a delegation. */ + if (bitmap_isset(rr->nsec.types, DNS_TYPE_DNAME)) + return 1; + + if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) + return 1; + + return 0; +} + +static int dnssec_nsec_covers(DnsResourceRecord *rr, const char *name) { + const char *common_suffix, *p; + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether the "Next Closer" is witin the space covered by the specified RR. */ + + r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix); + if (r < 0) + return r; + + for (;;) { + p = name; + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + return 0; + + r = dns_name_equal(name, common_suffix); + if (r < 0) + return r; + if (r > 0) + break; + } + + /* p is now the "Next Closer". */ + + return dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), p, rr->nsec.next_domain_name); +} + +static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name) { const char *common_suffix, *wc; int r; @@ -1701,85 +1783,96 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r if (rr->key->class != key->class) continue; - switch (rr->key->type) { + have_nsec3 = have_nsec3 || (rr->key->type == DNS_TYPE_NSEC3); - case DNS_TYPE_NSEC: + if (rr->key->type != DNS_TYPE_NSEC) + continue; - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), name); + /* The following checks only make sense for NSEC RRs that are not expanded from a wildcard */ + r = dns_resource_record_is_synthetic(rr); + if (r < 0) + return r; + if (r > 0) + continue; + + /* Check if this is a direct match. If so, we have encountered a NODATA case */ + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), name); + if (r < 0) + return r; + if (r == 0) { + /* If it's not a direct match, maybe it's a wild card match? */ + r = dnssec_nsec_wildcard_equal(rr, name); if (r < 0) return r; - if (r > 0) { - if (key->type == DNS_TYPE_DS) { - /* If we look for a DS RR and the server sent us the NSEC RR of the child zone - * we have a problem. For DS RRs we want the NSEC RR from the parent */ - if (bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) - continue; - } else { - /* For all RR types, ensure that if NS is set SOA is set too, so that we know - * we got the child's NSEC. */ - if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && - !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) - continue; - } - - if (bitmap_isset(rr->nsec.types, key->type)) - *result = DNSSEC_NSEC_FOUND; - else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME)) - *result = DNSSEC_NSEC_CNAME; - else - *result = DNSSEC_NSEC_NODATA; - - if (authenticated) - *authenticated = flags & DNS_ANSWER_AUTHENTICATED; - if (ttl) - *ttl = rr->ttl; - - return 0; + } + if (r > 0) { + if (key->type == DNS_TYPE_DS) { + /* If we look for a DS RR and the server sent us the NSEC RR of the child zone + * we have a problem. For DS RRs we want the NSEC RR from the parent */ + if (bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) + continue; + } else { + /* For all RR types, ensure that if NS is set SOA is set too, so that we know + * we got the child's NSEC. */ + if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && + !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) + continue; } - /* The following three checks only make sense for NSEC RRs that are not expanded from a wildcard */ - if (rr->n_skip_labels_source != 0) - continue; - - /* Check if the name we are looking for is an empty non-terminal within the owner or next name - * of the NSEC RR. */ - r = dnssec_nsec_test_in_path(rr, name); - if (r < 0) - return r; - if (r > 0) { + if (bitmap_isset(rr->nsec.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else *result = DNSSEC_NSEC_NODATA; - if (authenticated) - *authenticated = flags & DNS_ANSWER_AUTHENTICATED; - if (ttl) - *ttl = rr->ttl; + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + if (ttl) + *ttl = rr->ttl; - return 0; - } + return 0; + } - /* Check if this NSEC RR proves the absence of an explicit RR under this name */ - r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), name, rr->nsec.next_domain_name); - if (r < 0) - return r; - if (r > 0 && (!covering_rr || !covering_rr_authenticated)) { - covering_rr = rr; - covering_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; - } + /* Check if the name we are looking for is an empty non-terminal within the owner or next name + * of the NSEC RR. */ + r = dnssec_nsec_in_path(rr, name); + if (r < 0) + return r; + if (r > 0) { + *result = DNSSEC_NSEC_NODATA; - /* Check if this NSEC RR proves the absence of a wildcard RR under this name */ - r = dns_dnssec_test_wildcard_at_closest_encloser(rr, name); - if (r < 0) - return r; - if (r > 0 && (!wildcard_rr || !wildcard_rr_authenticated)) { - wildcard_rr = rr; - wildcard_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; - } + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + if (ttl) + *ttl = rr->ttl; - break; + return 0; + } - case DNS_TYPE_NSEC3: - have_nsec3 = true; - break; + /* The following two "covering" checks, are not useful if the NSEC is from the parent */ + r = dnssec_nsec_from_parent_zone(rr, name); + if (r < 0) + return r; + if (r > 0) + continue; + + /* Check if this NSEC RR proves the absence of an explicit RR under this name */ + r = dnssec_nsec_covers(rr, name); + if (r < 0) + return r; + if (r > 0 && (!covering_rr || !covering_rr_authenticated)) { + covering_rr = rr; + covering_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; + } + + /* Check if this NSEC RR proves the absence of a wildcard RR under this name */ + r = dnssec_nsec_covers_wildcard(rr, name); + if (r < 0) + return r; + if (r > 0 && (!wildcard_rr || !wildcard_rr_authenticated)) { + wildcard_rr = rr; + wildcard_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; } } diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 53fd708365..02c6b239d5 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -1136,6 +1136,8 @@ int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) { const char *signer; int r; + assert(rr); + r = dns_resource_record_signer(rr, &signer); if (r < 0) return r; @@ -1143,6 +1145,29 @@ int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) { return dns_name_equal(zone, signer); } +int dns_resource_record_is_synthetic(DnsResourceRecord *rr) { + int r; + + assert(rr); + + /* Returns > 0 if the RR is generated from a wildcard, and is not the asterisk name itself */ + + if (rr->n_skip_labels_source == (unsigned) -1) + return -ENODATA; + + if (rr->n_skip_labels_source == 0) + return 0; + + if (rr->n_skip_labels_source > 1) + return 1; + + r = dns_name_startswith(DNS_RESOURCE_KEY_NAME(rr->key), "*"); + if (r < 0) + return r; + + return !r; +} + static void dns_resource_record_hash_func(const void *i, struct siphash *state) { const DnsResourceRecord *rr = i; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 8e7bfaa7c7..8ab53211a9 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -309,6 +309,7 @@ int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical); int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret); int dns_resource_record_source(DnsResourceRecord *rr, const char **ret); int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone); +int dns_resource_record_is_synthetic(DnsResourceRecord *rr); DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i); bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b); -- cgit v1.2.3-54-g00ecf From f57e3cd5fa709ec0f52531eccba909ac0851927c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 15 Jan 2016 21:38:27 +0100 Subject: resolved: try to reduce number or DnsResourceKeys we keep around by merging them Quite often we read the same RR key multiple times from the same message. Try to replace them by a single object when we notice this. Do so again when we add things to the cache. This should reduce memory consumption a tiny bit. --- src/resolve/resolved-dns-cache.c | 13 +++++++++++++ src/resolve/resolved-dns-packet.c | 5 +++++ src/resolve/resolved-dns-rr.c | 40 +++++++++++++++++++++++++++++++++++++++ src/resolve/resolved-dns-rr.h | 4 +++- 4 files changed, 61 insertions(+), 1 deletion(-) (limited to 'src/resolve/resolved-dns-rr.c') diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index 301f383809..fdb34d11df 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -247,6 +247,19 @@ static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) { first = hashmap_get(c->by_key, i->key); if (first) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; + + /* Keep a reference to the original key, while we manipulate the list. */ + k = dns_resource_key_ref(first->key); + + /* Now, try to reduce the number of keys we keep */ + dns_resource_key_reduce(&first->key, &i->key); + + if (first->rr) + dns_resource_key_reduce(&first->rr->key, &i->key); + if (i->rr) + dns_resource_key_reduce(&i->rr->key, &i->key); + LIST_PREPEND(by_key, first, i); assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); } else { diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 57cfc1ab9b..0acbcfe261 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -2106,6 +2106,7 @@ int dns_packet_extract(DnsPacket *p) { n = DNS_PACKET_RRCOUNT(p); if (n > 0) { + DnsResourceRecord *previous = NULL; bool bad_opt = false; answer = dns_answer_new(n); @@ -2122,6 +2123,10 @@ int dns_packet_extract(DnsPacket *p) { if (r < 0) goto finish; + /* Try to reduce memory usage a bit */ + if (previous) + dns_resource_key_reduce(&rr->key, &previous->key); + if (rr->key->type == DNS_TYPE_OPT) { bool has_rfc6975; diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 02c6b239d5..7273ef3825 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -334,6 +334,46 @@ int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) { return 0; } +bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b) { + assert(a); + assert(b); + + /* Try to replace one RR key by another if they are identical, thus saving a bit of memory. Note that we do + * this only for RR keys, not for RRs themselves, as they carry a lot of additional metadata (where they come + * from, validity data, and suchlike), and cannot be replaced so easily by other RRs that have the same + * superficial data. */ + + if (!*a) + return false; + if (!*b) + return false; + + /* We refuse merging const keys */ + if ((*a)->n_ref == (unsigned) -1) + return false; + if ((*b)->n_ref == (unsigned) -1) + return false; + + /* Already the same? */ + if (*a == *b) + return true; + + /* Are they really identical? */ + if (dns_resource_key_equal(*a, *b) <= 0) + return false; + + /* Keep the one which already has more references. */ + if ((*a)->n_ref > (*b)->n_ref) { + dns_resource_key_unref(*b); + *b = dns_resource_key_ref(*a); + } else { + dns_resource_key_unref(*a); + *a = dns_resource_key_ref(*b); + } + + return true; +} + DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) { DnsResourceRecord *rr; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 8ab53211a9..d9c31e81c5 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -81,7 +81,7 @@ enum { }; struct DnsResourceKey { - unsigned n_ref; + unsigned n_ref; /* (unsigned -1) for const keys, see below */ uint16_t class, type; char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */ }; @@ -294,6 +294,8 @@ static inline bool dns_key_is_shared(const DnsResourceKey *key) { return IN_SET(key->type, DNS_TYPE_PTR); } +bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b); + 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); -- cgit v1.2.3-54-g00ecf