Merge pull request #2092 from poettering/dnssec2

Second DNSSEC patch set
This commit is contained in:
Tom Gundersen 2015-12-04 14:22:29 +01:00
commit 8eb3655cdb
28 changed files with 422 additions and 108 deletions

View File

@ -5184,6 +5184,8 @@ systemd_resolved_SOURCES = \
src/resolve/resolved-dns-stream.c \
src/resolve/resolved-dns-dnssec.h \
src/resolve/resolved-dns-dnssec.c \
src/resolve/resolved-dns-trust-anchor.h \
src/resolve/resolved-dns-trust-anchor.c \
src/resolve/dns-type.c \
src/resolve/dns-type.h

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

@ -234,6 +234,41 @@ int config_parse_support(
return 0;
}
int config_parse_dnssec(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Manager *m = data;
DnssecMode mode;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
mode = dnssec_mode_from_string(rvalue);
if (mode < 0) {
r = parse_boolean(rvalue);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNSSEC mode '%s'. Ignoring.", rvalue);
return 0;
}
mode = r ? DNSSEC_YES : DNSSEC_NO;
}
m->unicast_scope->dnssec_mode = mode;
return 0;
}
int manager_parse_config_file(Manager *m) {
int r;

View File

@ -36,3 +36,4 @@ const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned len
int config_parse_dns_servers(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_search_domains(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_support(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_dnssec(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);

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;
@ -522,7 +529,6 @@ fail:
}
static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, DnsResourceKey *k) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *nsec_key = NULL, *cname_key = NULL;
DnsCacheItem *i;
const char *n;
int r;
@ -540,35 +546,23 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D
n = DNS_RESOURCE_KEY_NAME(k);
/* Check if we have an NSEC record instead for the name. */
nsec_key = dns_resource_key_new(k->class, DNS_TYPE_NSEC, n);
if (!nsec_key)
return NULL;
i = hashmap_get(c->by_key, nsec_key);
i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n));
if (i)
return i;
/* Check if we have a CNAME record instead */
cname_key = dns_resource_key_new_cname(k);
if (!cname_key)
return NULL;
i = hashmap_get(c->by_key, cname_key);
i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n));
if (i)
return i;
/* OK, let's look for cached DNAME records. */
for (;;) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *dname_key = NULL;
char label[DNS_LABEL_MAX];
if (isempty(n))
return NULL;
dname_key = dns_resource_key_new(k->class, DNS_TYPE_DNAME, n);
if (!dname_key)
return NULL;
i = hashmap_get(c->by_key, dname_key);
i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n));
if (i)
return i;
@ -581,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)
@ -629,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);
@ -648,8 +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);
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",
@ -660,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;
}
@ -678,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

@ -25,17 +25,34 @@
#include "dns-domain.h"
#include "resolved-dns-dnssec.h"
#include "resolved-dns-packet.h"
#include "string-table.h"
/* Open question:
*
* How does the DNSSEC canonical form of a hostname with a label
* containing a dot look like, the way DNS-SD does it?
*
* TODO:
*
* - Iterative validation
* - NSEC proof of non-existance
* - NSEC3 proof of non-existance
* - Make trust anchor store read additional DS+DNSKEY data from disk
* - wildcard zones compatibility
* - multi-label zone compatibility
* - DMSSEC cname/dname compatibility
* - per-interface DNSSEC setting
* - DSA support
* - EC support?
*
* */
#define VERIFY_RRS_MAX 256
#define MAX_KEY_SIZE (32*1024)
/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
/*
* The DNSSEC Chain of trust:
*
@ -228,10 +245,14 @@ static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
inception = rrsig->rrsig.inception * USEC_PER_SEC;
if (inception > expiration)
return -EINVAL;
return -EKEYREJECTED;
/* Permit a certain amount of clock skew of 10% of the valid time range */
/* Permit a certain amount of clock skew of 10% of the valid
* time range. This takes inspiration from unbound's
* resolver. */
skew = (expiration - inception) / 10;
if (skew > SKEW_MAX)
skew = SKEW_MAX;
if (inception < skew)
inception = 0;
@ -690,3 +711,10 @@ finish:
gcry_md_close(md);
return r;
}
static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
[DNSSEC_NO] = "no",
[DNSSEC_TRUST] = "trust",
[DNSSEC_YES] = "yes",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);

View File

@ -21,10 +21,26 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
typedef enum DnssecMode DnssecMode;
#include "dns-domain.h"
#include "resolved-dns-answer.h"
#include "resolved-dns-rr.h"
enum DnssecMode {
/* No DNSSEC validation is done */
DNSSEC_NO,
/* Trust the AD bit sent by the server. UNSAFE! */
DNSSEC_TRUST,
/* Validate locally, if the server knows DO, but if not, don't. Don't trust the AD bit */
DNSSEC_YES,
_DNSSEC_MODE_MAX,
_DNSSEC_MODE_INVALID = -1
};
enum {
DNSSEC_VERIFIED,
DNSSEC_INVALID,
@ -33,7 +49,6 @@ enum {
DNSSEC_SIGNATURE_EXPIRED,
};
#define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2)
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey);
@ -47,3 +62,6 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds);
uint16_t dnssec_keytag(DnsResourceRecord *dnskey);
int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max);
const char* dnssec_mode_to_string(DnssecMode m) _const_;
DnssecMode dnssec_mode_from_string(const char *s) _pure_;

View File

@ -65,7 +65,7 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
return 0;
}
int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) {
DnsPacket *p;
DnsPacketHeader *h;
int r;
@ -96,7 +96,7 @@ int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
1 /* rd (ask for recursion) */,
0 /* ra */,
0 /* ad */,
0 /* cd */,
dnssec_checking_disabled /* cd */,
0 /* rcode */));
*ret = p;

View File

@ -144,7 +144,7 @@ static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) {
}
int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t mtu);
int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t mtu);
int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled);
DnsPacket *dns_packet_ref(DnsPacket *p);
DnsPacket *dns_packet_unref(DnsPacket *p);
@ -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

@ -430,15 +430,17 @@ static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) {
return r;
/* If this a single-label domain on DNS, we might append a suitable search domain first. */
r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question));
if (r < 0)
goto fail;
if (r > 0) {
/* OK, we need a search domain now. Let's find one for this scope */
r = dns_query_candidate_next_search_domain(c);
if (r <= 0) /* if there's no search domain, then we won't add any transaction. */
if ((q->flags & SD_RESOLVED_NO_SEARCH) == 0) {
r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question));
if (r < 0)
goto fail;
if (r > 0) {
/* OK, we need a search domain now. Let's find one for this scope */
r = dns_query_candidate_next_search_domain(c);
if (r <= 0) /* if there's no search domain, then we won't add any transaction. */
goto fail;
}
}
r = dns_query_candidate_setup_transactions(c);
@ -970,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);
@ -997,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;
}
@ -1026,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

@ -51,12 +51,6 @@ DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *
return k;
}
DnsResourceKey* dns_resource_key_new_cname(const DnsResourceKey *key) {
assert(key);
return dns_resource_key_new(key->class, DNS_TYPE_CNAME, DNS_RESOURCE_KEY_NAME(key));
}
DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname) {
int r;
@ -137,6 +131,10 @@ DnsResourceKey* dns_resource_key_ref(DnsResourceKey *k) {
if (!k)
return NULL;
/* Static/const keys created with DNS_RESOURCE_KEY_CONST will
* set this to -1, they should not be reffed/unreffed */
assert(k->n_ref != (unsigned) -1);
assert(k->n_ref > 0);
k->n_ref++;
@ -147,6 +145,7 @@ DnsResourceKey* dns_resource_key_unref(DnsResourceKey *k) {
if (!k)
return NULL;
assert(k->n_ref != (unsigned) -1);
assert(k->n_ref > 0);
if (k->n_ref == 1) {
@ -158,6 +157,14 @@ DnsResourceKey* dns_resource_key_unref(DnsResourceKey *k) {
return NULL;
}
bool dns_resource_key_is_address(const DnsResourceKey *key) {
assert(key);
/* Check if this is an A or AAAA resource key */
return key->class == DNS_CLASS_IN && IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_AAAA);
}
int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) {
int r;

View File

@ -78,6 +78,19 @@ struct DnsResourceKey {
char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */
};
/* Creates a temporary resource key. This is only useful to quickly
* look up something, without allocating a full DnsResourceKey object
* for it. Note that it is not OK to take references to this kind of
* resource key object. */
#define DNS_RESOURCE_KEY_CONST(c, t, n) \
((DnsResourceKey) { \
.n_ref = (unsigned) -1, \
.class = c, \
.type = t, \
._name = (char*) n, \
})
struct DnsTxtItem {
size_t length;
LIST_FIELDS(DnsTxtItem, items);
@ -221,13 +234,12 @@ static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) {
}
DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name);
DnsResourceKey* dns_resource_key_new_cname(const DnsResourceKey *key);
DnsResourceKey* dns_resource_key_new_dname(const DnsResourceKey *key);
DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname);
int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name);
DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name);
DnsResourceKey* dns_resource_key_ref(DnsResourceKey *key);
DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key);
bool dns_resource_key_is_address(const DnsResourceKey *key);
int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b);
int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain);
int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain);

View File

@ -368,13 +368,14 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
assert(s);
assert(domain);
/* Checks if the specified domain is something to look up on
* this scope. Note that this accepts non-qualified hostnames,
* i.e. those without any search path prefixed yet. */
if (ifindex != 0 && (!s->link || s->link->ifindex != ifindex))
return DNS_SCOPE_NO;
if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family) & flags) == 0)
return DNS_SCOPE_NO;
if (dns_name_is_root(domain))
if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family, 0) & flags) == 0)
return DNS_SCOPE_NO;
/* Never resolve any loopback hostname or IP address via DNS,
@ -385,6 +386,12 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
dns_name_equal(domain, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0)
return DNS_SCOPE_NO;
/* Never respond to some of the domains listed in RFC6303 */
if (dns_name_endswith(domain, "0.in-addr.arpa") > 0 ||
dns_name_equal(domain, "255.255.255.255.in-addr.arpa") > 0 ||
dns_name_equal(domain, "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0)
return DNS_SCOPE_NO;
/* Always honour search domains for routing queries. Note that
* we return DNS_SCOPE_YES here, rather than just
* DNS_SCOPE_MAYBE, which means wildcard scopes won't be
@ -397,10 +404,12 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
case DNS_PROTOCOL_DNS:
if ((!dns_name_is_single_label(domain) ||
(!(flags & SD_RESOLVED_NO_SEARCH) && dns_scope_has_search_domains(s))) &&
dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 &&
dns_name_endswith(domain, "0.8.e.f.ip6.arpa") == 0)
/* Exclude link-local IP ranges */
if (dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 &&
dns_name_endswith(domain, "8.e.f.ip6.arpa") == 0 &&
dns_name_endswith(domain, "9.e.f.ip6.arpa") == 0 &&
dns_name_endswith(domain, "a.e.f.ip6.arpa") == 0 &&
dns_name_endswith(domain, "b.e.f.ip6.arpa") == 0)
return DNS_SCOPE_MAYBE;
return DNS_SCOPE_NO;
@ -434,8 +443,27 @@ int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) {
assert(s);
assert(key);
if (s->protocol == DNS_PROTOCOL_DNS)
return true;
/* Check if it makes sense to resolve the specified key on
* this scope. Note that this call assumes as fully qualified
* name, i.e. the search suffixes already appended. */
if (s->protocol == DNS_PROTOCOL_DNS) {
/* On classic DNS, lookin up non-address RRs is always
* fine. (Specifically, we want to permit looking up
* DNSKEY and DS records on the root and top-level
* domains.) */
if (!dns_resource_key_is_address(key))
return true;
/* However, we refuse to look up A and AAAA RRs on the
* root and single-label domains, under the assumption
* that those should be resolved via LLMNR or search
* path only, and should not be leaked onto the
* internet. */
return !(dns_name_is_single_label(DNS_RESOURCE_KEY_NAME(key)) ||
dns_name_is_root(DNS_RESOURCE_KEY_NAME(key)));
}
/* On mDNS and LLMNR, send A and AAAA queries only on the
* respective scopes */
@ -901,34 +929,13 @@ void dns_scope_dump(DnsScope *s, FILE *f) {
DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s) {
assert(s);
/* Returns the list of *local* search domains -- not the
* global ones. */
if (s->protocol != DNS_PROTOCOL_DNS)
return NULL;
if (s->link)
return s->link->search_domains;
return NULL;
}
bool dns_scope_has_search_domains(DnsScope *s) {
assert(s);
/* Tests if there are *any* search domains suitable for this
* scope. This means either local or global ones */
if (s->protocol != DNS_PROTOCOL_DNS)
return false;
if (s->manager->search_domains)
return true;
if (s->link && s->link->search_domains)
return true;
return false;
return s->manager->search_domains;
}
bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) {

View File

@ -26,6 +26,7 @@
typedef struct DnsScope DnsScope;
#include "resolved-dns-cache.h"
#include "resolved-dns-dnssec.h"
#include "resolved-dns-packet.h"
#include "resolved-dns-server.h"
#include "resolved-dns-zone.h"
@ -44,6 +45,7 @@ struct DnsScope {
DnsProtocol protocol;
int family;
DnssecMode dnssec_mode;
Link *link;
@ -102,6 +104,5 @@ void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p);
void dns_scope_dump(DnsScope *s, FILE *f);
DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s);
bool dns_scope_has_search_domains(DnsScope *s);
bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name);

View File

@ -61,10 +61,11 @@ struct DnsServer {
int family;
union in_addr_union address;
bool marked:1;
usec_t resend_timeout;
usec_t max_rtt;
bool marked:1;
DnsServerFeatureLevel verified_features;
DnsServerFeatureLevel possible_features;
size_t received_udp_packet_max;

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);
@ -598,7 +607,7 @@ static int dns_transaction_make_packet(DnsTransaction *t) {
if (t->sent)
return 0;
r = dns_packet_new_query(&p, t->scope->protocol, 0);
r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES);
if (r < 0)
return r;
@ -675,7 +684,21 @@ int dns_transaction_go(DnsTransaction *t) {
t->answer_rcode = 0;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
/* Check the zone, but obly if this transaction is not used
/* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */
if (t->scope->protocol == DNS_PROTOCOL_DNS) {
r = dns_trust_anchor_lookup(&t->scope->manager->trust_anchor, t->key, &t->answer);
if (r < 0)
return r;
if (r > 0) {
t->answer_rcode = DNS_RCODE_SUCCESS;
t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR;
t->answer_authenticated = true;
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
return 0;
}
}
/* Check the zone, but only if this transaction is not used
* for probing or verifying a zone item. */
if (set_isempty(t->zone_items)) {
@ -685,6 +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;
}
@ -702,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) {
@ -817,5 +841,6 @@ static const char* const dns_transaction_source_table[_DNS_TRANSACTION_SOURCE_MA
[DNS_TRANSACTION_NETWORK] = "network",
[DNS_TRANSACTION_CACHE] = "cache",
[DNS_TRANSACTION_ZONE] = "zone",
[DNS_TRANSACTION_TRUST_ANCHOR] = "trust-anchor",
};
DEFINE_STRING_TABLE_LOOKUP(dns_transaction_source, DnsTransactionSource);

View File

@ -44,6 +44,7 @@ enum DnsTransactionSource {
DNS_TRANSACTION_NETWORK,
DNS_TRANSACTION_CACHE,
DNS_TRANSACTION_ZONE,
DNS_TRANSACTION_TRUST_ANCHOR,
_DNS_TRANSACTION_SOURCE_MAX,
_DNS_TRANSACTION_SOURCE_INVALID = -1
};
@ -68,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;

View File

@ -0,0 +1,101 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "alloc-util.h"
#include "resolved-dns-trust-anchor.h"
/* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml */
static const uint8_t root_digest[] =
{ 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60,
0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 };
int dns_trust_anchor_load(DnsTrustAnchor *d) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
int r;
assert(d);
r = hashmap_ensure_allocated(&d->by_key, &dns_resource_key_hash_ops);
if (r < 0)
return r;
if (hashmap_get(d->by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, ".")))
return 0;
/* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */
rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "");
if (!rr)
return -ENOMEM;
rr->ds.key_tag = 19036;
rr->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256;
rr->ds.digest_type = DNSSEC_DIGEST_SHA256;
rr->ds.digest_size = sizeof(root_digest);
rr->ds.digest = memdup(root_digest, rr->ds.digest_size);
if (!rr->ds.digest)
return -ENOMEM;
answer = dns_answer_new(1);
if (!answer)
return -ENOMEM;
r = dns_answer_add(answer, rr, 0);
if (r < 0)
return r;
r = hashmap_put(d->by_key, rr->key, answer);
if (r < 0)
return r;
answer = NULL;
return 0;
}
void dns_trust_anchor_flush(DnsTrustAnchor *d) {
DnsAnswer *a;
assert(d);
while ((a = hashmap_steal_first(d->by_key)))
dns_answer_unref(a);
d->by_key = hashmap_free(d->by_key);
}
int dns_trust_anchor_lookup(DnsTrustAnchor *d, DnsResourceKey *key, DnsAnswer **ret) {
DnsAnswer *a;
assert(d);
assert(key);
assert(ret);
/* We only serve DS and DNSKEY RRs. */
if (!IN_SET(key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY))
return 0;
a = hashmap_get(d->by_key, key);
if (!a)
return 0;
*ret = dns_answer_ref(a);
return 1;
}

View File

@ -0,0 +1,39 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
#pragma once
/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
typedef struct DnsTrustAnchor DnsTrustAnchor;
#include "hashmap.h"
#include "resolved-dns-answer.h"
#include "resolved-dns-rr.h"
/* This contains a fixed database mapping domain names to DS or DNSKEY records. */
struct DnsTrustAnchor {
Hashmap *by_key;
};
int dns_trust_anchor_load(DnsTrustAnchor *d);
void dns_trust_anchor_flush(DnsTrustAnchor *d);
int dns_trust_anchor_lookup(DnsTrustAnchor *d, DnsResourceKey* key, DnsAnswer **answer);

View File

@ -163,7 +163,6 @@ static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
}
static int dns_zone_item_probe_start(DnsZoneItem *i) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
DnsTransaction *t;
int r;
@ -172,12 +171,14 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) {
if (i->probe_transaction)
return 0;
key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key));
if (!key)
return -ENOMEM;
t = dns_scope_find_transaction(i->scope, key, false);
t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key)), false);
if (!t) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key));
if (!key)
return -ENOMEM;
r = dns_transaction_new(&t, i->scope, key);
if (r < 0)
return r;

View File

@ -18,3 +18,4 @@ Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0
Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0
Resolve.Domains, config_parse_search_domains, 0, 0
Resolve.LLMNR, config_parse_support, 0, offsetof(Manager, llmnr_support)
Resolve.DNSSEC, config_parse_dnssec, 0, 0

View File

@ -478,6 +478,10 @@ int manager_new(Manager **ret) {
m->read_resolv_conf = true;
m->need_builtin_fallbacks = true;
r = dns_trust_anchor_load(&m->trust_anchor);
if (r < 0)
return r;
r = sd_event_default(&m->event);
if (r < 0)
return r;
@ -572,6 +576,8 @@ Manager *manager_free(Manager *m) {
free(m->llmnr_hostname);
free(m->mdns_hostname);
dns_trust_anchor_flush(&m->trust_anchor);
free(m);
return NULL;

View File

@ -44,6 +44,7 @@ enum Support {
#include "resolved-dns-search-domain.h"
#include "resolved-dns-server.h"
#include "resolved-dns-stream.h"
#include "resolved-dns-trust-anchor.h"
#include "resolved-link.h"
#define MANAGER_SEARCH_DOMAINS_MAX 32
@ -85,6 +86,8 @@ struct Manager {
bool read_resolv_conf:1;
usec_t resolv_conf_mtime;
DnsTrustAnchor trust_anchor;
LIST_HEAD(DnsScope, dns_scopes);
DnsScope *unicast_scope;

View File

@ -16,3 +16,4 @@
#FallbackDNS=@DNS_SERVERS@
#Domains=
#LLMNR=yes
#DNSSEC=no