Merge pull request #2129 from poettering/dnssec3

Third DNSSEC patch series
This commit is contained in:
Tom Gundersen 2015-12-10 15:22:18 +01:00
commit 553947b77c
23 changed files with 1085 additions and 223 deletions

View file

@ -708,7 +708,7 @@ AC_ARG_ENABLE([gcrypt],
if test "x${have_gcrypt}" != xno ; then
m4_define([AM_PATH_LIBGCRYPT_FAIL],
[{ test "x$have_gcrypt" != xyes || AC_MSG_ERROR([*** GCRYPT headers not found.]); }]
[{ test "x$have_gcrypt" != xyes || AC_MSG_ERROR([*** GCRYPT/GPG-ERROR headers not found.]); }]
)
m4_ifdef([AM_PATH_LIBGCRYPT], [AM_PATH_LIBGCRYPT(
[1.4.5],
@ -723,12 +723,22 @@ if test "x${have_gcrypt}" != xno ; then
[AM_PATH_LIBGCRYPT_FAIL]
)
if test "x$have_gcrypt" = xyes ; then
GCRYPT_LIBS="$LIBGCRYPT_LIBS"
GCRYPT_CFLAGS="$LIBGCRYPT_CFLAGS"
have_gpg_error=no
m4_ifdef([AM_PATH_GPG_ERROR], [AM_PATH_GPG_ERROR(
[1.12],
[have_gpg_error=yes],
[AM_PATH_LIBGCRYPT_FAIL]
)],
[AM_PATH_LIBGCRYPT_FAIL]
)
if test "x$have_gcrypt" = xyes -a "x$have_gpg_error" = xyes ; then
GCRYPT_LIBS="$LIBGCRYPT_LIBS $GPG_ERROR_LIBS"
GCRYPT_CFLAGS="$LIBGCRYPT_CFLAGS $GPG_ERROR_CFLAGS"
AC_DEFINE(HAVE_GCRYPT, 1, [GCRYPT available])
else
have_gcrypt=no
have_gpg_error=no
fi
else
GCRYPT_LIBS=

View file

@ -169,8 +169,7 @@ JournalFile* journal_file_close(JournalFile *f) {
safe_close(f->fd);
free(f->path);
if (f->mmap)
mmap_cache_unref(f->mmap);
mmap_cache_unref(f->mmap);
ordered_hashmap_free_free(f->chain_cache);

View file

@ -348,7 +348,10 @@ static void mmap_cache_free(MMapCache *m) {
}
MMapCache* mmap_cache_unref(MMapCache *m) {
assert(m);
if (!m)
return NULL;
assert(m->n_ref > 0);
m->n_ref --;

View file

@ -45,6 +45,6 @@ int dns_type_from_string(const char *s) {
}
/* XXX: find an authoritative list of all pseudo types? */
bool dns_type_is_pseudo(int n) {
bool dns_type_is_pseudo(uint16_t n) {
return IN_SET(n, DNS_TYPE_ANY, DNS_TYPE_AXFR, DNS_TYPE_IXFR, DNS_TYPE_OPT);
}

View file

@ -25,7 +25,7 @@
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 n);
/* DNS record types, taken from
* http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml.

View file

@ -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;

View file

@ -46,6 +46,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 +65,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 +73,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 +110,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 +134,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 +189,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 +198,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,21 +209,25 @@ 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;
assert(key);
assert(ret);
@ -177,10 +239,9 @@ 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++) {
if (dns_answer_match_soa(key, a->items[i].rr->key)) {
*ret = a->items[i].rr;
DNS_ANSWER_FOREACH(rr, a) {
if (dns_resource_key_match_soa(key, rr->key)) {
*ret = rr;
return 1;
}
}
@ -188,41 +249,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 (r < 0)
return NULL;
}
if (!found)
return 0;
if (!other) {
*a = dns_answer_unref(*a); /* Return NULL for the empty answer */
return 1;
}
k = ret;
ret = NULL;
if ((*a)->n_ref > 1) {
_cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL;
int ifindex;
return k;
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 r;
}
dns_answer_unref(*a);
*a = copy;
copy = NULL;
return 1;
}
/* Only a single reference, edit in-place */
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 +450,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 +466,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 +483,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;
}

View file

@ -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,28 @@ 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_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 +85,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)

View file

@ -473,15 +473,15 @@ 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;
/* 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)
@ -489,31 +489,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;

View file

@ -40,7 +40,7 @@
* - 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
* - DSA support
* - EC support?
@ -193,11 +193,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 +273,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 +287,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);
@ -301,8 +304,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 +331,7 @@ 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);
/* OK, the RRs are now in canonical order. Let's calculate the digest */
switch (rrsig->rrsig.algorithm) {
@ -444,7 +449,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 +482,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 +505,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;
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;
@ -524,7 +532,9 @@ int dnssec_verify_rrset_search(
found_rrsig = true;
/* Look for a matching key */
DNS_ANSWER_FOREACH(dnskey, validated_dnskeys) {
DnssecResult one_result;
r = dnssec_rrsig_match_dnskey(rrsig, dnskey);
if (r < 0)
@ -545,11 +555,13 @@ int dnssec_verify_rrset_search(
* the RRSet against the RRSIG and DNSKEY
* combination. */
r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime);
r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result);
if (r < 0 && r != EOPNOTSUPP)
return r;
if (r == DNSSEC_VERIFIED)
return DNSSEC_VERIFIED;
if (one_result == DNSSEC_VALIDATED) {
*result = DNSSEC_VALIDATED;
return 0;
}
/* If the signature is invalid, or done using
an unsupported algorithm, let's try another
@ -560,12 +572,13 @@ int dnssec_verify_rrset_search(
}
if (found_dnskey)
return DNSSEC_INVALID;
*result = DNSSEC_INVALID;
else if (found_rrsig)
*result = DNSSEC_MISSING_KEY;
else
*result = DNSSEC_NO_SIGNATURE;
if (found_rrsig)
return DNSSEC_MISSING_KEY;
return DNSSEC_NO_SIGNATURE;
return 0;
}
int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
@ -653,16 +666,14 @@ 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;
if (!dnssec_digest_supported(ds->ds.digest_type))
return -EOPNOTSUPP;
switch (ds->ds.digest_type) {
case DNSSEC_DIGEST_SHA1:
@ -711,9 +722,44 @@ 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;
}
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_UNSIGNED] = "unsigned",
[DNSSEC_NO_SIGNATURE] = "no-signature",
[DNSSEC_MISSING_KEY] = "missing-key",
[DNSSEC_SIGNATURE_EXPIRED] = "signature-expired",
[DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);

View file

@ -22,6 +22,7 @@
***/
typedef enum DnssecMode DnssecMode;
typedef enum DnssecResult DnssecResult;
#include "dns-domain.h"
#include "resolved-dns-answer.h"
@ -41,12 +42,16 @@ enum DnssecMode {
_DNSSEC_MODE_INVALID = -1
};
enum {
DNSSEC_VERIFIED,
enum DnssecResult {
DNSSEC_VALIDATED,
DNSSEC_INVALID,
DNSSEC_UNSIGNED,
DNSSEC_NO_SIGNATURE,
DNSSEC_MISSING_KEY,
DNSSEC_SIGNATURE_EXPIRED,
DNSSEC_FAILED_AUXILIARY,
_DNSSEC_RESULT_MAX,
_DNSSEC_RESULT_INVALID = -1
};
#define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2)
@ -54,10 +59,11 @@ enum {
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);
@ -65,3 +71,6 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max);
const char* dnssec_mode_to_string(DnssecMode m) _const_;
DnssecMode dnssec_mode_from_string(const char *s) _pure_;
const char* dnssec_result_to_string(DnssecResult m) _const_;
DnssecResult dnssec_result_from_string(const char *s) _pure_;

View file

@ -153,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);
@ -209,6 +210,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)
@ -249,6 +251,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)
@ -963,6 +966,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)
@ -997,6 +1001,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:
@ -1520,7 +1526,9 @@ 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) {
key->type == DNS_TYPE_ANY ||
key->type == DNS_TYPE_AXFR ||
key->type == DNS_TYPE_IXFR) {
r = -EBADMSG;
goto fail;
}
@ -1568,10 +1576,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)
@ -1749,6 +1753,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)
@ -1911,6 +1916,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);
@ -1986,9 +1993,16 @@ 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) {
if (p->opt)
return -EBADMSG;
p->opt = dns_resource_record_ref(rr);
} else {
r = dns_answer_add(answer, rr, p->ifindex);
if (r < 0)
goto finish;
}
}
}
@ -2007,6 +2021,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",

View file

@ -80,6 +80,7 @@ struct DnsPacket {
/* Parsed data */
DnsQuestion *question;
DnsAnswer *answer;
DnsResourceRecord *opt;
/* Packet reception metadata */
int ifindex;
@ -160,6 +161,8 @@ 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);

View file

@ -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);
@ -183,13 +186,20 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) {
switch (t->state) {
case DNS_TRANSACTION_PENDING:
case DNS_TRANSACTION_NULL:
return t->state;
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;
break;
case DNS_TRANSACTION_NULL:
assert_not_reached("Transaction not started?");
default:
if (state != DNS_TRANSACTION_SUCCESS)
state = t->state;
@ -233,7 +243,7 @@ fail:
return r;
}
void dns_query_candidate_ready(DnsQueryCandidate *c) {
void dns_query_candidate_notify(DnsQueryCandidate *c) {
DnsTransactionState state;
int r;
@ -241,7 +251,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 +404,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 +980,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 +999,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 +1015,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 +1056,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 +1073,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 +1105,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;

View file

@ -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);

View file

@ -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. */
@ -247,7 +253,24 @@ int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRec
}
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;

View file

@ -114,7 +114,7 @@ struct DnsResourceRecord {
struct {
void *data;
size_t size;
} generic;
} generic, opt;
struct {
uint16_t priority;
@ -247,6 +247,7 @@ 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_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);

View file

@ -441,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

View file

@ -32,6 +32,7 @@
DnsTransaction* dns_transaction_free(DnsTransaction *t) {
DnsQueryCandidate *c;
DnsZoneItem *i;
DnsTransaction *z;
if (!t)
return NULL;
@ -59,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;
@ -80,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);
}
@ -92,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 (IN_SET(key->type, DNS_TYPE_OPT, 0, DNS_TYPE_TSIG, DNS_TYPE_TKEY))
return -EINVAL;
/* We only support the IN class */
if (key->class != DNS_CLASS_IN)
return -EOPNOTSUPP;
r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL);
if (r < 0)
return r;
@ -106,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 */
@ -175,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);
@ -192,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
@ -215,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);
@ -348,6 +374,73 @@ static void dns_transaction_next_dns_server(DnsTransaction *t) {
dns_scope_next_dns_server(t->scope);
}
static void dns_transaction_cache_answer(DnsTransaction *t) {
unsigned n_cache;
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;
/* 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)
n_cache = dns_answer_size(t->answer);
else
n_cache = DNS_PACKET_ANCOUNT(t->received);
dns_cache_put(&t->scope->cache,
t->key,
t->answer_rcode,
t->answer,
n_cache,
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;
@ -362,7 +455,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);
@ -428,12 +524,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);
@ -449,13 +546,14 @@ 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;
break;
default:
break;
assert_not_reached("Invalid DNS protocol.");
}
if (DNS_PACKET_TC(p)) {
@ -474,7 +572,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
return;
}
if (r < 0) {
/* On LLMNR and mDNS, if we cannot connect to the host,
/* On LLMNR, if we cannot connect to the host,
* we immediately give up */
if (t->scope->protocol == DNS_PROTOCOL_LLMNR) {
dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
@ -494,16 +592,22 @@ 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;
}
if (t->scope->protocol == DNS_PROTOCOL_DNS) {
if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) {
/* 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) {
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;
}
@ -514,23 +618,19 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
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);
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;
}
}
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) {
@ -549,7 +649,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;
}
@ -644,7 +744,7 @@ static usec_t transaction_get_resend_timeout(DnsTransaction *t) {
}
}
static int dns_transaction_prepare_next_attempt(DnsTransaction *t, usec_t ts) {
static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
bool had_stream;
int r;
@ -689,7 +789,7 @@ static int dns_transaction_prepare_next_attempt(DnsTransaction *t, usec_t ts) {
/* 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)
@ -705,7 +805,7 @@ static int dns_transaction_prepare_next_attempt(DnsTransaction *t, usec_t ts) {
/* 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
@ -794,7 +894,7 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
if (r < 0)
return r;
r = dns_transaction_prepare_next_attempt(other, ts);
r = dns_transaction_prepare(other, ts);
if (r <= 0)
continue;
@ -876,14 +976,22 @@ int dns_transaction_go(DnsTransaction *t) {
assert(t);
assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
r = dns_transaction_prepare_next_attempt(t, ts);
r = dns_transaction_prepare(t, ts);
if (r <= 0)
return r;
log_debug("Excercising transaction on scope %s on %s/%s",
dns_protocol_to_string(t->scope->protocol),
t->scope->link ? t->scope->link->name : "*",
t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family));
if (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 ||
@ -988,9 +1096,364 @@ int dns_transaction_go(DnsTransaction *t) {
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);
}
int dns_transaction_validate_dnssec(DnsTransaction *t) {
_cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL;
DnsResourceRecord *rr;
int ifindex, 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. */
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;
}
for (;;) {
bool changed = false, missing_key_for_transaction = 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));
}
switch (result) {
case 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;
changed = true;
break;
case DNSSEC_INVALID:
case DNSSEC_NO_SIGNATURE:
case DNSSEC_SIGNATURE_EXPIRED:
/* Is this the RRset that we were looking for? If so, this is fatal for the whole transaction */
r = dns_resource_key_match_rr(t->key, rr, NULL);
if (r < 0)
return r;
if (r > 0) {
t->dnssec_result = result;
return 0;
}
/* Is this a CNAME for a record we were looking for? If so, it's also fatal for the whole transaction */
r = dns_resource_key_match_cname(t->key, rr, NULL);
if (r < 0)
return r;
if (r > 0) {
t->dnssec_result = result;
return 0;
}
/* This is just something auxiliary. Just remove the RRset and continue. */
r = dns_answer_remove_by_key(&t->answer, rr->key);
if (r < 0)
return r;
changed = true;
break;
case DNSSEC_MISSING_KEY:
/* They key is missing? Let's continue
* with the next iteration, maybe
* we'll find it in an DNSKEY RRset
* later on. */
r = dns_resource_key_equal(rr->key, t->key);
if (r < 0)
return r;
if (r > 0)
missing_key_for_transaction = true;
break;
default:
assert_not_reached("Unexpected DNSSEC result");
}
if (changed)
break;
}
if (changed)
continue;
/* This didn't work either, there's no point in
* continuing. */
if (missing_key_for_transaction) {
t->dnssec_result = DNSSEC_MISSING_KEY;
return 0;
}
break;
}
dns_answer_unref(t->answer);
t->answer = validated;
validated = NULL;
t->answer_authenticated = true;
t->dnssec_result = DNSSEC_VALIDATED;
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",
@ -999,6 +1462,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);

View file

@ -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,6 +64,8 @@ struct DnsTransaction {
DnsResourceKey *key;
DnsTransactionState state;
DnssecResult dnssec_result;
uint16_t id;
bool initial_jitter_scheduled;
@ -72,6 +78,9 @@ struct DnsTransaction {
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;
@ -83,7 +92,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 */
@ -92,11 +101,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;
@ -112,6 +131,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_;

View file

@ -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) {

View file

@ -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);

View file

@ -56,6 +56,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 +107,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) {