resolved: add a concept of "authenticated" responses

This adds a new SD_RESOLVED_AUTHENTICATED flag for responses we return
on the bus. When set, then the data has been authenticated. For now this
mostly reflects the DNSSEC AD bit, if DNSSEC=trust is set. As soon as
the client-side validation is complete it will be hooked up to this flag
too.

We also set this bit whenver we generated the data ourselves, for
example, because it originates in our local LLMNR zone, or from the
built-in trust anchor database.

The "systemd-resolve-host" tool has been updated to show the flag state
for the data it shows.
This commit is contained in:
Lennart Poettering 2015-12-03 21:04:52 +01:00
parent 3ba27cd339
commit 931851e8e4
11 changed files with 75 additions and 31 deletions

View File

@ -67,6 +67,8 @@ static void print_source(uint64_t flags, usec_t rtt) {
fputc('.', stdout);
fputc('\n', stdout);
printf("-- Data is authenticated: %s\n", yes_no(flags & SD_RESOLVED_AUTHENTICATED));
}
static int resolve_host(sd_bus *bus, const char *name) {

View File

@ -197,7 +197,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) {
r = sd_bus_message_append(
reply, "st",
DNS_RESOURCE_KEY_NAME(canonical->key),
SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family));
SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated));
if (r < 0)
goto finish;
@ -344,7 +344,7 @@ static void bus_method_resolve_address_complete(DnsQuery *q) {
if (r < 0)
goto finish;
r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family));
r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated));
if (r < 0)
goto finish;
@ -510,7 +510,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) {
if (r < 0)
goto finish;
r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family));
r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated));
if (r < 0)
goto finish;
@ -859,7 +859,7 @@ static void resolve_service_all_complete(DnsQuery *q) {
reply,
"ssst",
name, type, domain,
SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family));
SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated));
if (r < 0)
goto finish;

View File

@ -28,6 +28,7 @@
#define SD_RESOLVED_NO_TXT (UINT64_C(1) << 6)
#define SD_RESOLVED_NO_ADDRESS (UINT64_C(1) << 7)
#define SD_RESOLVED_NO_SEARCH (UINT64_C(1) << 8)
#define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9)
#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6)
#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_LLMNR|SD_RESOLVED_DNS)

View File

@ -46,6 +46,7 @@ struct DnsCacheItem {
usec_t until;
DnsCacheItemType type;
unsigned prioq_idx;
bool authenticated;
int owner_family;
union in_addr_union owner_address;
LIST_FIELDS(DnsCacheItem, by_key);
@ -237,7 +238,7 @@ static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) {
return NULL;
}
static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, usec_t timestamp) {
static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, bool authenticated, usec_t timestamp) {
assert(c);
assert(i);
assert(rr);
@ -257,6 +258,7 @@ static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsReso
dns_resource_key_unref(i->key);
i->key = dns_resource_key_ref(rr->key);
i->authenticated = authenticated;
i->until = timestamp + MIN(rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
prioq_reshuffle(c->by_expiry, i, &i->prioq_idx);
@ -265,6 +267,7 @@ static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsReso
static int dns_cache_put_positive(
DnsCache *c,
DnsResourceRecord *rr,
bool authenticated,
usec_t timestamp,
int owner_family,
const union in_addr_union *owner_address) {
@ -300,7 +303,7 @@ static int dns_cache_put_positive(
/* Entry exists already? Update TTL and timestamp */
existing = dns_cache_get(c, rr);
if (existing) {
dns_cache_item_update_positive(c, existing, rr, timestamp);
dns_cache_item_update_positive(c, existing, rr, authenticated, timestamp);
return 0;
}
@ -322,6 +325,7 @@ static int dns_cache_put_positive(
i->prioq_idx = PRIOQ_IDX_NULL;
i->owner_family = owner_family;
i->owner_address = *owner_address;
i->authenticated = authenticated;
r = dns_cache_link_item(c, i);
if (r < 0)
@ -341,6 +345,7 @@ static int dns_cache_put_negative(
DnsCache *c,
DnsResourceKey *key,
int rcode,
bool authenticated,
usec_t timestamp,
uint32_t soa_ttl,
int owner_family,
@ -389,6 +394,7 @@ static int dns_cache_put_negative(
i->prioq_idx = PRIOQ_IDX_NULL;
i->owner_family = owner_family;
i->owner_address = *owner_address;
i->authenticated = authenticated;
r = dns_cache_link_item(c, i);
if (r < 0)
@ -410,6 +416,7 @@ int dns_cache_put(
int rcode,
DnsAnswer *answer,
unsigned max_rrs,
bool authenticated,
usec_t timestamp,
int owner_family,
const union in_addr_union *owner_address) {
@ -452,7 +459,7 @@ int dns_cache_put(
/* Second, add in positive entries for all contained RRs */
for (i = 0; i < MIN(max_rrs, answer->n_rrs); i++) {
r = dns_cache_put_positive(c, answer->items[i].rr, timestamp, owner_family, owner_address);
r = dns_cache_put_positive(c, answer->items[i].rr, authenticated, timestamp, owner_family, owner_address);
if (r < 0)
goto fail;
}
@ -496,13 +503,13 @@ int dns_cache_put(
if (!dns_answer_match_soa(canonical_key, soa->key))
continue;
r = dns_cache_put_negative(c, canonical_key, rcode, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
r = dns_cache_put_negative(c, canonical_key, rcode, authenticated, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
if (r < 0)
goto fail;
}
}
r = dns_cache_put_negative(c, key, rcode, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
r = dns_cache_put_negative(c, key, rcode, authenticated, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
if (r < 0)
goto fail;
@ -568,24 +575,26 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D
return NULL;
}
int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret) {
int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret, bool *authenticated) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
unsigned n = 0;
int r;
bool nxdomain = false;
_cleanup_free_ char *key_str = NULL;
DnsResourceRecord *nsec = NULL;
DnsCacheItem *j, *first;
DnsCacheItem *j, *first, *nsec = NULL;
bool have_authenticated = false, have_non_authenticated = false;
assert(c);
assert(key);
assert(rcode);
assert(ret);
assert(authenticated);
if (key->type == DNS_TYPE_ANY ||
key->class == DNS_CLASS_ANY) {
/* If we have ANY lookups we simply refresh */
/* If we have ANY lookups we don't use the cache, so
* that the caller refreshes via the network. */
r = dns_resource_key_to_string(key, &key_str);
if (r < 0)
@ -616,10 +625,16 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
LIST_FOREACH(by_key, j, first) {
if (j->rr) {
if (j->rr->key->type == DNS_TYPE_NSEC)
nsec = j->rr;
nsec = j;
n++;
} else if (j->type == DNS_CACHE_NXDOMAIN)
nxdomain = true;
if (j->authenticated)
have_authenticated = true;
else
have_non_authenticated = true;
}
r = dns_resource_key_to_string(key, &key_str);
@ -635,10 +650,11 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
*ret = NULL;
*rcode = DNS_RCODE_SUCCESS;
*authenticated = nsec->authenticated;
return !bitmap_isset(nsec->nsec.types, key->type) &&
!bitmap_isset(nsec->nsec.types, DNS_TYPE_CNAME) &&
!bitmap_isset(nsec->nsec.types, DNS_TYPE_DNAME);
return !bitmap_isset(nsec->rr->nsec.types, key->type) &&
!bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) &&
!bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME);
}
log_debug("%s cache hit for %s",
@ -649,6 +665,7 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
if (n <= 0) {
*ret = NULL;
*rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS;
*authenticated = have_authenticated && !have_non_authenticated;
return 1;
}
@ -667,6 +684,7 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
*ret = answer;
*rcode = DNS_RCODE_SUCCESS;
*authenticated = have_authenticated && !have_non_authenticated;
answer = NULL;
return n;

View File

@ -38,8 +38,8 @@ typedef struct DnsCache {
void dns_cache_flush(DnsCache *c);
void dns_cache_prune(DnsCache *c);
int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, unsigned max_rrs, usec_t timestamp, int owner_family, const union in_addr_union *owner_address);
int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **answer);
int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, unsigned max_rrs, bool authenticated, usec_t timestamp, int owner_family, const union in_addr_union *owner_address);
int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **answer, bool *authenticated);
int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address);

View File

@ -225,16 +225,19 @@ DnsProtocol dns_protocol_from_string(const char *s) _pure_;
#define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) })
#define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } })
static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family) {
static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) {
uint64_t f;
/* Converts a protocol + family into a flags field as used in queries */
/* Converts a protocol + family into a flags field as used in queries and responses */
f = authenticated ? SD_RESOLVED_AUTHENTICATED : 0;
switch (protocol) {
case DNS_PROTOCOL_DNS:
return SD_RESOLVED_DNS;
return f|SD_RESOLVED_DNS;
case DNS_PROTOCOL_LLMNR:
return family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4;
return f|(family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4);
default:
break;

View File

@ -972,6 +972,7 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS;
DnsTransaction *t;
Iterator i;
bool has_authenticated = false, has_non_authenticated = false;
assert(q);
@ -999,6 +1000,11 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
q->answer = merged;
q->answer_rcode = t->answer_rcode;
if (t->answer_authenticated)
has_authenticated = true;
else
has_non_authenticated = true;
state = DNS_TRANSACTION_SUCCESS;
break;
}
@ -1028,6 +1034,7 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
q->answer_protocol = c->scope->protocol;
q->answer_family = c->scope->family;
q->answer_authenticated = has_authenticated && !has_non_authenticated;
dns_search_domain_unref(q->answer_search_domain);
q->answer_search_domain = dns_search_domain_ref(c->search_domain);

View File

@ -75,6 +75,7 @@ struct DnsQuery {
DnsProtocol answer_protocol;
int answer_family;
DnsSearchDomain *answer_search_domain;
bool answer_authenticated;
/* Bus client information */
sd_bus_message *request;

View File

@ -375,7 +375,7 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
if (ifindex != 0 && (!s->link || s->link->ifindex != ifindex))
return DNS_SCOPE_NO;
if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family) & flags) == 0)
if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family, 0) & flags) == 0)
return DNS_SCOPE_NO;
/* Never resolve any loopback hostname or IP address via DNS,

View File

@ -481,20 +481,29 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
return;
}
/* Install the answer as answer to the transaction */
dns_answer_unref(t->answer);
t->answer = dns_answer_ref(p->answer);
t->answer_rcode = DNS_PACKET_RCODE(p);
/* Only consider responses with equivalent query section to the request */
if (p->question->n_keys != 1 || dns_resource_key_equal(p->question->keys[0], t->key) <= 0) {
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
return;
}
/* Install the answer as answer to the transaction */
dns_answer_unref(t->answer);
t->answer = dns_answer_ref(p->answer);
t->answer_rcode = DNS_PACKET_RCODE(p);
t->answer_authenticated = t->scope->dnssec_mode == DNSSEC_TRUST && DNS_PACKET_AD(p);
/* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */
if (DNS_PACKET_SHALL_CACHE(p))
dns_cache_put(&t->scope->cache, t->key, DNS_PACKET_RCODE(p), p->answer, DNS_PACKET_ANCOUNT(p), 0, p->family, &p->sender);
dns_cache_put(&t->scope->cache,
t->key,
DNS_PACKET_RCODE(p),
p->answer,
DNS_PACKET_ANCOUNT(p),
t->answer_authenticated,
0,
p->family,
&p->sender);
if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS)
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
@ -683,6 +692,7 @@ int dns_transaction_go(DnsTransaction *t) {
if (r > 0) {
t->answer_rcode = DNS_RCODE_SUCCESS;
t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR;
t->answer_authenticated = true;
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
return 0;
}
@ -698,6 +708,7 @@ int dns_transaction_go(DnsTransaction *t) {
if (r > 0) {
t->answer_rcode = DNS_RCODE_SUCCESS;
t->answer_source = DNS_TRANSACTION_ZONE;
t->answer_authenticated = true;
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
return 0;
}
@ -715,7 +726,7 @@ int dns_transaction_go(DnsTransaction *t) {
/* Let's then prune all outdated entries */
dns_cache_prune(&t->scope->cache);
r = dns_cache_lookup(&t->scope->cache, t->key, &t->answer_rcode, &t->answer);
r = dns_cache_lookup(&t->scope->cache, t->key, &t->answer_rcode, &t->answer, &t->answer_authenticated);
if (r < 0)
return r;
if (r > 0) {

View File

@ -69,6 +69,7 @@ struct DnsTransaction {
DnsAnswer *answer;
int answer_rcode;
DnsTransactionSource answer_source;
bool answer_authenticated;
usec_t start_usec;
sd_event_source *timeout_event_source;