diff options
Diffstat (limited to 'src/resolve')
39 files changed, 842 insertions, 203 deletions
diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c index 07d9582ccb..32537ce6e8 100644 --- a/src/resolve/resolve-tool.c +++ b/src/resolve/resolve-tool.c @@ -38,7 +38,7 @@ #include "strv.h" #include "terminal-util.h" -#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) +#define DNS_CALL_TIMEOUT_USEC (90*USEC_PER_SEC) static int arg_family = AF_UNSPEC; static int arg_ifindex = 0; @@ -1186,6 +1186,7 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, bool *empt {} }; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *ifi = NULL, *p = NULL; char ifname[IF_NAMESIZE] = ""; char **i; @@ -1213,9 +1214,10 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, bool *empt "org.freedesktop.resolve1", p, property_map, + &error, &link_info); if (r < 0) { - log_error_errno(r, "Failed to get link data for %i: %m", ifindex); + log_error_errno(r, "Failed to get link data for %i: %s", ifindex, bus_error_message(&error, r)); goto finish; } @@ -1405,6 +1407,7 @@ static int status_global(sd_bus *bus, bool *empty_line) { {} }; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; char **i; int r; @@ -1415,9 +1418,10 @@ static int status_global(sd_bus *bus, bool *empty_line) { "org.freedesktop.resolve1", "/org/freedesktop/resolve1", property_map, + &error, &global_info); if (r < 0) { - log_error_errno(r, "Failed to get global data: %m"); + log_error_errno(r, "Failed to get global data: %s", bus_error_message(&error, r)); goto finish; } @@ -1524,7 +1528,7 @@ static int status_all(sd_bus *bus) { static void help_protocol_types(void) { if (arg_legend) puts("Known protocol types:"); - puts("dns\nllmnr\nllmnr-ipv4\nllmnr-ipv6"); + puts("dns\nllmnr\nllmnr-ipv4\nllmnr-ipv6\nmdns\nmnds-ipv4\nmdns-ipv6"); } static void help_dns_types(void) { @@ -1722,6 +1726,12 @@ static int parse_argv(int argc, char *argv[]) { arg_flags |= SD_RESOLVED_LLMNR_IPV4; else if (streq(optarg, "llmnr-ipv6")) arg_flags |= SD_RESOLVED_LLMNR_IPV6; + else if (streq(optarg, "mdns")) + arg_flags |= SD_RESOLVED_MDNS; + else if (streq(optarg, "mdns-ipv4")) + arg_flags |= SD_RESOLVED_MDNS_IPV4; + else if (streq(optarg, "mdns-ipv6")) + arg_flags |= SD_RESOLVED_MDNS_IPV6; else { log_error("Unknown protocol specifier: %s", optarg); return -EINVAL; diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 2ca65e6953..2c50109388 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -211,7 +211,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { r = sd_bus_message_append( reply, "st", normalized, - SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); + SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, dns_query_fully_authenticated(q))); if (r < 0) goto finish; @@ -439,7 +439,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, q->answer_authenticated)); + r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, dns_query_fully_authenticated(q))); if (r < 0) goto finish; @@ -605,7 +605,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, q->answer_authenticated)); + r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, dns_query_fully_authenticated(q))); if (r < 0) goto finish; @@ -979,7 +979,7 @@ static void resolve_service_all_complete(DnsQuery *q) { reply, "ssst", name, type, domain, - SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); + SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, dns_query_fully_authenticated(q))); if (r < 0) goto finish; diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h index 4a92bd1150..11d2e25eeb 100644 --- a/src/resolve/resolved-dns-answer.h +++ b/src/resolve/resolved-dns-answer.h @@ -33,9 +33,11 @@ typedef struct DnsAnswerItem DnsAnswerItem; * Note that we usually encode the empty DnsAnswer object as a simple NULL. */ typedef enum DnsAnswerFlags { - DNS_ANSWER_AUTHENTICATED = 1, /* Item has been authenticated */ - DNS_ANSWER_CACHEABLE = 2, /* Item is subject to caching */ - DNS_ANSWER_SHARED_OWNER = 4, /* For mDNS: RRset may be owner by multiple peers */ + DNS_ANSWER_AUTHENTICATED = 1, /* Item has been authenticated */ + DNS_ANSWER_CACHEABLE = 2, /* Item is subject to caching */ + DNS_ANSWER_SHARED_OWNER = 4, /* For mDNS: RRset may be owner by multiple peers */ + DNS_ANSWER_CACHE_FLUSH = 8, /* For mDNS: sets cache-flush bit in the rrclass of response records */ + DNS_ANSWER_GOODBYE = 16, /* For mDNS: item is subject to disappear */ } DnsAnswerFlags; struct DnsAnswerItem { diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index 9233fb0ac1..46a25c2bee 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -34,6 +34,10 @@ /* We never keep any item longer than 2h in our cache */ #define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR) +/* How long to cache strange rcodes, i.e. rcodes != SUCCESS and != NXDOMAIN (specifically: that's only SERVFAIL for + * now) */ +#define CACHE_TTL_STRANGE_RCODE_USEC (30 * USEC_PER_SEC) + typedef enum DnsCacheItemType DnsCacheItemType; typedef struct DnsCacheItem DnsCacheItem; @@ -41,12 +45,14 @@ enum DnsCacheItemType { DNS_CACHE_POSITIVE, DNS_CACHE_NODATA, DNS_CACHE_NXDOMAIN, + DNS_CACHE_RCODE, /* "strange" RCODE (effective only SERVFAIL for now) */ }; struct DnsCacheItem { DnsCacheItemType type; DnsResourceKey *key; DnsResourceRecord *rr; + int rcode; usec_t until; bool authenticated:1; @@ -60,6 +66,27 @@ struct DnsCacheItem { LIST_FIELDS(DnsCacheItem, by_key); }; +static const char *dns_cache_item_type_to_string(DnsCacheItem *item) { + assert(item); + + switch (item->type) { + + case DNS_CACHE_POSITIVE: + return "POSITIVE"; + + case DNS_CACHE_NODATA: + return "NODATA"; + + case DNS_CACHE_NXDOMAIN: + return "NXDOMAIN"; + + case DNS_CACHE_RCODE: + return dns_rcode_to_string(item->rcode); + } + + return NULL; +} + static void dns_cache_item_free(DnsCacheItem *i) { if (!i) return; @@ -484,7 +511,6 @@ static int dns_cache_put_negative( assert(c); assert(key); - assert(soa); assert(owner_address); /* Never cache pseudo RR keys. DNS_TYPE_ANY is particularly @@ -495,13 +521,17 @@ static int dns_cache_put_negative( if (dns_type_is_pseudo(key->type)) return 0; - if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) { - log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - return 0; - } + if (IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) { + if (!soa) + return 0; - if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) + /* For negative replies, check if we have a TTL of a SOA */ + if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) { + log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + return 0; + } + } else if (rcode != DNS_RCODE_SERVFAIL) return 0; r = dns_cache_init(c); @@ -514,12 +544,17 @@ static int dns_cache_put_negative( if (!i) return -ENOMEM; - i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; - i->until = calculate_until(soa, nsec_ttl, timestamp, true); + i->type = + rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : + rcode == DNS_RCODE_NXDOMAIN ? DNS_CACHE_NXDOMAIN : DNS_CACHE_RCODE; + i->until = + i->type == DNS_CACHE_RCODE ? timestamp + CACHE_TTL_STRANGE_RCODE_USEC : + calculate_until(soa, nsec_ttl, timestamp, true); i->authenticated = authenticated; i->owner_family = owner_family; i->owner_address = *owner_address; i->prioq_idx = PRIOQ_IDX_NULL; + i->rcode = rcode; if (i->type == DNS_CACHE_NXDOMAIN) { /* NXDOMAIN entries should apply equally to all types, so we use ANY as @@ -543,7 +578,7 @@ static int dns_cache_put_negative( return r; log_debug("Added %s cache entry for %s "USEC_FMT"s", - i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", + dns_cache_item_type_to_string(i), dns_resource_key_to_string(i->key, key_str, sizeof key_str), (i->until - timestamp) / USEC_PER_SEC); @@ -615,6 +650,7 @@ int dns_cache_put( const union in_addr_union *owner_address) { DnsResourceRecord *soa = NULL, *rr; + bool weird_rcode = false; DnsAnswerFlags flags; unsigned cache_keys; int r, ifindex; @@ -624,18 +660,28 @@ int dns_cache_put( dns_cache_remove_previous(c, key, answer); - /* We only care for positive replies and NXDOMAINs, on all - * other replies we will simply flush the respective entries, - * and that's it */ - if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) - return 0; + /* We only care for positive replies and NXDOMAINs, on all other replies we will simply flush the respective + * entries, and that's it. (Well, with one further exception: since some DNS zones (akamai!) return SERVFAIL + * consistently for some lookups, and forwarders tend to propagate that we'll cache that too, but only for a + * short time.) */ - if (dns_answer_size(answer) <= 0) { - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + if (IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) { - log_debug("Not caching negative entry without a SOA record: %s", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - return 0; + if (dns_answer_size(answer) <= 0) { + char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + + log_debug("Not caching negative entry without a SOA record: %s", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + return 0; + } + + } else { + /* Only cache SERVFAIL as "weird" rcode for now. We can add more later, should that turn out to be + * beneficial. */ + if (rcode != DNS_RCODE_SERVFAIL) + return 0; + + weird_rcode = true; } cache_keys = dns_answer_size(answer); @@ -690,19 +736,20 @@ int dns_cache_put( 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 enable - * negative caching. */ + /* See https://tools.ietf.org/html/rfc2308, which say that a matching SOA record in the packet is used to + * enable negative caching. We apply one exception though: if we are about to cache a weird rcode we do so + * regardless of a SOA. */ r = dns_answer_find_soa(answer, key, &soa, &flags); if (r < 0) goto fail; - if (r == 0) - return 0; - - /* Refuse using the SOA data if it is unsigned, but the key is - * signed */ - if (authenticated && (flags & DNS_ANSWER_AUTHENTICATED) == 0) + if (r == 0 && !weird_rcode) return 0; + if (r > 0) { + /* Refuse using the SOA data if it is unsigned, but the key is + * signed */ + if (authenticated && (flags & DNS_ANSWER_AUTHENTICATED) == 0) + return 0; + } r = dns_cache_put_negative( c, @@ -799,6 +846,7 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod DnsCacheItem *j, *first, *nsec = NULL; bool have_authenticated = false, have_non_authenticated = false; usec_t current; + int found_rcode = -1; assert(c); assert(key); @@ -817,6 +865,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod *ret = NULL; *rcode = DNS_RCODE_SUCCESS; + *authenticated = false; + return 0; } @@ -831,6 +881,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod *ret = NULL; *rcode = DNS_RCODE_SUCCESS; + *authenticated = false; + return 0; } @@ -842,6 +894,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod n++; } else if (j->type == DNS_CACHE_NXDOMAIN) nxdomain = true; + else if (j->type == DNS_CACHE_RCODE) + found_rcode = j->rcode; if (j->authenticated) have_authenticated = true; @@ -849,6 +903,19 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod have_non_authenticated = true; } + if (found_rcode >= 0) { + log_debug("RCODE %s cache hit for %s", + dns_rcode_to_string(found_rcode), + dns_resource_key_to_string(key, key_str, sizeof(key_str))); + + *ret = NULL; + *rcode = found_rcode; + *authenticated = false; + + c->n_hit++; + return 1; + } + if (nsec && !IN_SET(key->type, DNS_TYPE_NSEC, DNS_TYPE_DS)) { /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC RRs from * the lower-zone of a zone cut, but the DS RRs are on the upper zone. */ @@ -980,7 +1047,7 @@ int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) { if (!j->shared_owner) continue; - r = dns_packet_append_rr(p, j->rr, NULL, NULL); + r = dns_packet_append_rr(p, j->rr, 0, NULL, NULL); if (r == -EMSGSIZE && p->protocol == DNS_PROTOCOL_MDNS) { /* For mDNS, if we're unable to stuff all known answers into the given packet, * allocate a new one, push the RR into that one and link it to the current one. @@ -995,7 +1062,7 @@ int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) { /* continue with new packet */ p = p->more; - r = dns_packet_append_rr(p, j->rr, NULL, NULL); + r = dns_packet_append_rr(p, j->rr, 0, NULL, NULL); } if (r < 0) @@ -1042,7 +1109,7 @@ void dns_cache_dump(DnsCache *cache, FILE *f) { fputs(dns_resource_key_to_string(j->key, key_str, sizeof key_str), f); fputs(" -- ", f); - fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f); + fputs(dns_cache_item_type_to_string(j), f); fputc('\n', f); } } diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 51327105d0..eddab58a81 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -1710,7 +1710,8 @@ static int dnssec_nsec_covers(DnsResourceRecord *rr, const char *name) { } static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name) { - const char *common_suffix, *wc; + _cleanup_free_ char *wc = NULL; + const char *common_suffix; int r; assert(rr); @@ -1734,7 +1735,10 @@ static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name) if (r <= 0) return r; - wc = strjoina("*.", common_suffix); + r = dns_name_concat("*", common_suffix, &wc); + if (r < 0) + return r; + return dns_name_between(dns_resource_key_name(rr->key), wc, rr->nsec.next_domain_name); } diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 337a8c473f..652970284e 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -569,8 +569,9 @@ fail: return r; } -int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) { +int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, const DnsAnswerFlags flags, size_t *start) { size_t saved_size; + uint16_t class; int r; assert(p); @@ -586,7 +587,8 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) if (r < 0) goto fail; - r = dns_packet_append_uint16(p, k->class, NULL); + class = flags & DNS_ANSWER_CACHE_FLUSH ? k->class | MDNS_RR_CACHE_FLUSH : k->class; + r = dns_packet_append_uint16(p, class, NULL); if (r < 0) goto fail; @@ -791,9 +793,10 @@ int dns_packet_truncate_opt(DnsPacket *p) { return 1; } -int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) { +int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAnswerFlags flags, size_t *start, size_t *rdata_start) { size_t saved_size, rdlength_offset, end, rdlength, rds; + uint32_t ttl; int r; assert(p); @@ -801,11 +804,12 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star saved_size = p->size; - r = dns_packet_append_key(p, rr->key, NULL); + r = dns_packet_append_key(p, rr->key, flags, NULL); if (r < 0) goto fail; - r = dns_packet_append_uint32(p, rr->ttl, NULL); + ttl = flags & DNS_ANSWER_GOODBYE ? 0 : rr->ttl; + r = dns_packet_append_uint32(p, ttl, NULL); if (r < 0) goto fail; @@ -1143,7 +1147,7 @@ int dns_packet_append_question(DnsPacket *p, DnsQuestion *q) { assert(p); DNS_QUESTION_FOREACH(key, q) { - r = dns_packet_append_key(p, key, NULL); + r = dns_packet_append_key(p, key, 0, NULL); if (r < 0) return r; } @@ -1153,12 +1157,13 @@ int dns_packet_append_question(DnsPacket *p, DnsQuestion *q) { int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a) { DnsResourceRecord *rr; + DnsAnswerFlags flags; int r; assert(p); - DNS_ANSWER_FOREACH(rr, a) { - r = dns_packet_append_rr(p, rr, NULL, NULL); + DNS_ANSWER_FOREACH_FLAGS(rr, flags, a) { + r = dns_packet_append_rr(p, rr, flags, NULL, NULL); if (r < 0) return r; } diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index 054dc88a85..2c92392e4d 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -209,8 +209,8 @@ int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start); int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start); int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, bool canonical_candidate, size_t *start); int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, bool canonical_candidate, size_t *start); -int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start); -int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start); +int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, const DnsAnswerFlags flags, size_t *start); +int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAnswerFlags flags, size_t *start, size_t *rdata_start); int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, int rcode, size_t *start); int dns_packet_append_question(DnsPacket *p, DnsQuestion *q); int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a); diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index e03db4d003..2b091e6c45 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -28,7 +28,7 @@ #include "string-util.h" /* How long to wait for the query in total */ -#define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC) +#define QUERY_TIMEOUT_USEC (60 * USEC_PER_SEC) #define CNAME_MAX 8 #define QUERIES_MAX 2048 @@ -403,6 +403,7 @@ DnsQuery *dns_query_free(DnsQuery *q) { sd_bus_track_unref(q->bus_track); dns_packet_unref(q->request_dns_packet); + dns_packet_unref(q->reply_dns_packet); if (q->request_dns_stream) { /* Detach the stream from our query, in case something else keeps a reference to it. */ @@ -810,6 +811,7 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { q->answer = dns_answer_unref(q->answer); q->answer_rcode = 0; q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + q->answer_authenticated = false; q->answer_errno = c->error_code; } @@ -846,15 +848,18 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { continue; default: - /* Any kind of failure? Store the data away, - * if there's nothing stored yet. */ - + /* Any kind of failure? Store the data away, if there's nothing stored yet. */ if (state == DNS_TRANSACTION_SUCCESS) continue; + /* If there's already an authenticated negative reply stored, then prefer that over any unauthenticated one */ + if (q->answer_authenticated && !t->answer_authenticated) + continue; + q->answer = dns_answer_unref(q->answer); q->answer_rcode = t->answer_rcode; q->answer_dnssec_result = t->answer_dnssec_result; + q->answer_authenticated = t->answer_authenticated; q->answer_errno = t->answer_errno; state = t->state; @@ -1028,6 +1033,9 @@ int dns_query_process_cname(DnsQuery *q) { if (q->flags & SD_RESOLVED_NO_CNAME) return -ELOOP; + if (!q->answer_authenticated) + q->previous_redirect_unauthenticated = true; + /* OK, let's actually follow the CNAME */ r = dns_query_cname_redirect(q, cname); if (r < 0) @@ -1115,3 +1123,9 @@ const char *dns_query_string(DnsQuery *q) { return dns_question_first_name(q->question_idna); } + +bool dns_query_fully_authenticated(DnsQuery *q) { + assert(q); + + return q->answer_authenticated && !q->previous_redirect_unauthenticated; +} diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index 49a35b846b..b8ea48f6af 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -71,7 +71,6 @@ struct DnsQuery { * family */ bool suppress_unroutable_family; - /* If true, the RR TTLs of the answer will be clamped by their current left validity in the cache */ bool clamp_ttl; @@ -90,6 +89,7 @@ struct DnsQuery { int answer_family; DnsSearchDomain *answer_search_domain; int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ + bool previous_redirect_unauthenticated; /* Bus client information */ sd_bus_message *request; @@ -102,6 +102,7 @@ struct DnsQuery { /* DNS stub information */ DnsPacket *request_dns_packet; DnsStream *request_dns_stream; + DnsPacket *reply_dns_packet; /* Completion callback */ void (*complete)(DnsQuery* q); @@ -139,3 +140,5 @@ DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol); const char *dns_query_string(DnsQuery *q); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuery*, dns_query_free); + +bool dns_query_fully_authenticated(DnsQuery *q); diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 209d565033..e8c05ed0da 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -1262,7 +1262,7 @@ int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { if (rr->wire_format && rr->wire_format_canonical == canonical) return 0; - r = dns_packet_append_rr(&packet, rr, &start, &rds); + r = dns_packet_append_rr(&packet, rr, 0, &start, &rds); if (r < 0) return r; diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 8dbc7f623b..ffaefbe3f2 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -124,6 +124,8 @@ DnsScope* dns_scope_free(DnsScope *s) { ordered_hashmap_free(s->conflict_queue); sd_event_source_unref(s->conflict_event_source); + sd_event_source_unref(s->announce_event_source); + dns_cache_flush(&s->cache); dns_zone_flush(&s->zone); @@ -549,7 +551,11 @@ static int dns_scope_multicast_membership(DnsScope *s, bool b, struct in_addr in .imr_ifindex = s->link->ifindex, }; - fd = manager_llmnr_ipv4_udp_fd(s->manager); + if (s->protocol == DNS_PROTOCOL_LLMNR) + fd = manager_llmnr_ipv4_udp_fd(s->manager); + else + fd = manager_mdns_ipv4_fd(s->manager); + if (fd < 0) return fd; @@ -568,7 +574,11 @@ static int dns_scope_multicast_membership(DnsScope *s, bool b, struct in_addr in .ipv6mr_interface = s->link->ifindex, }; - fd = manager_llmnr_ipv6_udp_fd(s->manager); + if (s->protocol == DNS_PROTOCOL_LLMNR) + fd = manager_llmnr_ipv6_udp_fd(s->manager); + else + fd = manager_mdns_ipv6_fd(s->manager); + if (fd < 0) return fd; @@ -601,7 +611,7 @@ int dns_scope_mdns_membership(DnsScope *s, bool b) { return dns_scope_multicast_membership(s, b, MDNS_MULTICAST_IPV4_ADDRESS, MDNS_MULTICAST_IPV6_ADDRESS); } -static int dns_scope_make_reply_packet( +int dns_scope_make_reply_packet( DnsScope *s, uint16_t id, int rcode, @@ -830,11 +840,11 @@ static int dns_scope_make_conflict_packet( DNS_PACKET_HEADER(p)->qdcount = htobe16(1); DNS_PACKET_HEADER(p)->arcount = htobe16(1); - r = dns_packet_append_key(p, rr->key, NULL); + r = dns_packet_append_key(p, rr->key, 0, NULL); if (r < 0) return r; - r = dns_packet_append_rr(p, rr, NULL, NULL); + r = dns_packet_append_rr(p, rr, 0, NULL, NULL); if (r < 0) return r; @@ -928,17 +938,19 @@ void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p) { assert(scope); assert(p); - if (p->protocol != DNS_PROTOCOL_LLMNR) + if (!IN_SET(p->protocol, DNS_PROTOCOL_LLMNR, DNS_PROTOCOL_MDNS)) return; if (DNS_PACKET_RRCOUNT(p) <= 0) return; - if (DNS_PACKET_LLMNR_C(p) != 0) - return; + if (p->protocol == DNS_PROTOCOL_LLMNR) { + if (DNS_PACKET_LLMNR_C(p) != 0) + return; - if (DNS_PACKET_LLMNR_T(p) != 0) - return; + if (DNS_PACKET_LLMNR_T(p) != 0) + return; + } if (manager_our_packet(scope->manager, p)) return; @@ -1041,3 +1053,77 @@ int dns_scope_ifindex(DnsScope *s) { return 0; } + +static int on_announcement_timeout(sd_event_source *s, usec_t usec, void *userdata) { + DnsScope *scope = userdata; + + assert(s); + + scope->announce_event_source = sd_event_source_unref(scope->announce_event_source); + + (void) dns_scope_announce(scope, false); + return 0; +} + +int dns_scope_announce(DnsScope *scope, bool goodbye) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + LinkAddress *a; + int r; + + if (!scope) + return 0; + + if (scope->protocol != DNS_PROTOCOL_MDNS) + return 0; + + answer = dns_answer_new(scope->link->n_addresses * 2); + if (!answer) + return log_oom(); + + LIST_FOREACH(addresses, a, scope->link->addresses) { + r = dns_answer_add(answer, a->mdns_address_rr, 0, goodbye ? DNS_ANSWER_GOODBYE : DNS_ANSWER_CACHE_FLUSH); + if (r < 0) + return log_debug_errno(r, "Failed to add address RR to answer: %m"); + + r = dns_answer_add(answer, a->mdns_ptr_rr, 0, goodbye ? DNS_ANSWER_GOODBYE : DNS_ANSWER_CACHE_FLUSH); + if (r < 0) + return log_debug_errno(r, "Failed to add PTR RR to answer: %m"); + } + + if (dns_answer_isempty(answer)) + return 0; + + r = dns_scope_make_reply_packet(scope, 0, DNS_RCODE_SUCCESS, NULL, answer, NULL, false, &p); + if (r < 0) + return log_debug_errno(r, "Failed to build reply packet: %m"); + + r = dns_scope_emit_udp(scope, -1, p); + if (r < 0) + return log_debug_errno(r, "Failed to send reply packet: %m"); + + /* In section 8.3 of RFC6762: "The Multicast DNS responder MUST send at least two unsolicited + * responses, one second apart." */ + if (!scope->announced) { + usec_t ts; + + scope->announced = true; + + assert_se(sd_event_now(scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + ts += MDNS_ANNOUNCE_DELAY; + + r = sd_event_add_time( + scope->manager->event, + &scope->announce_event_source, + clock_boottime_or_monotonic(), + ts, + MDNS_JITTER_RANGE_USEC, + on_announcement_timeout, scope); + if (r < 0) + return log_debug_errno(r, "Failed to schedule second announcement: %m"); + + (void) sd_event_source_set_description(scope->announce_event_source, "mdns-announce"); + } + + return 0; +} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 01a83a76b2..6f94b1fdcd 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -56,6 +56,9 @@ struct DnsScope { OrderedHashmap *conflict_queue; sd_event_source *conflict_event_source; + bool announced:1; + sd_event_source *announce_event_source; + RateLimit ratelimit; usec_t resend_timeout; @@ -96,6 +99,7 @@ void dns_scope_next_dns_server(DnsScope *s); int dns_scope_llmnr_membership(DnsScope *s, bool b); int dns_scope_mdns_membership(DnsScope *s, bool b); +int dns_scope_make_reply_packet(DnsScope *s, uint16_t id, int rcode, DnsQuestion *q, DnsAnswer *answer, DnsAnswer *soa, bool tentative, DnsPacket **ret); void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p); DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok); @@ -112,3 +116,5 @@ bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name); bool dns_scope_network_good(DnsScope *s); int dns_scope_ifindex(DnsScope *s); + +int dns_scope_announce(DnsScope *scope, bool goodbye); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 22c64e8491..5498f7b9cb 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -28,7 +28,7 @@ #include "string-util.h" /* After how much time to repeat classic DNS requests */ -#define DNS_TIMEOUT_MIN_USEC (500 * USEC_PER_MSEC) +#define DNS_TIMEOUT_MIN_USEC (750 * USEC_PER_MSEC) #define DNS_TIMEOUT_MAX_USEC (5 * USEC_PER_SEC) /* The amount of time to wait before retrying with a full feature set */ @@ -399,12 +399,24 @@ static bool dns_server_grace_period_expired(DnsServer *s) { } DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { + DnsServerFeatureLevel best; + assert(s); - if (s->possible_feature_level != DNS_SERVER_FEATURE_LEVEL_BEST && - dns_server_grace_period_expired(s)) { + /* Determine the best feature level we care about. If DNSSEC mode is off there's no point in using anything + * better than EDNS0, hence don't even try. */ + best = dns_server_get_dnssec_mode(s) == DNSSEC_NO ? + DNS_SERVER_FEATURE_LEVEL_EDNS0 : + DNS_SERVER_FEATURE_LEVEL_BEST; + + /* Clamp the feature level the highest level we care about. The DNSSEC mode might have changed since the last + * time, hence let's downgrade if we are still at a higher level. */ + if (s->possible_feature_level > best) + s->possible_feature_level = best; + + if (s->possible_feature_level < best && dns_server_grace_period_expired(s)) { - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; + s->possible_feature_level = best; dns_server_reset_counters(s); @@ -415,6 +427,8 @@ DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { dns_server_feature_level_to_string(s->possible_feature_level), dns_server_string(s)); + dns_server_flush_cache(s); + } else if (s->possible_feature_level <= s->verified_feature_level) s->possible_feature_level = s->verified_feature_level; else { @@ -451,18 +465,22 @@ DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0; } else if (s->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_UDP) { + s->possible_feature_level >= (dns_server_get_dnssec_mode(s) == DNSSEC_YES ? DNS_SERVER_FEATURE_LEVEL_LARGE : DNS_SERVER_FEATURE_LEVEL_UDP)) { /* We lost too many UDP packets in a row, and are on a feature level of UDP or higher. If the * packets are lost, maybe the server cannot parse them, hence downgrading sounds like a good - * idea. We might downgrade all the way down to TCP this way. */ + * idea. We might downgrade all the way down to TCP this way. + * + * If strict DNSSEC mode is used we won't downgrade below DO level however, as packet loss + * might have many reasons, a broken DNSSEC implementation being only one reason. And if the + * user is strict on DNSSEC, then let's assume that DNSSEC is not the fault here. */ log_debug("Lost too many UDP packets, downgrading feature level..."); s->possible_feature_level--; } else if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && s->packet_truncated && - s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) { + s->possible_feature_level > (dns_server_get_dnssec_mode(s) == DNSSEC_YES ? DNS_SERVER_FEATURE_LEVEL_LARGE : DNS_SERVER_FEATURE_LEVEL_UDP)) { /* We got too many TCP connection failures in a row, we had at least one truncated packet, and * are on a feature level above UDP. By downgrading things and getting rid of DNSSEC or EDNS0 @@ -566,7 +584,7 @@ void dns_server_warn_downgrade(DnsServer *server) { return; log_struct(LOG_NOTICE, - LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_DOWNGRADE), + "MESSAGE_ID=" SD_MESSAGE_DNSSEC_DOWNGRADE_STR, LOG_MESSAGE("Server %s does not support DNSSEC, downgrading to non-DNSSEC mode.", dns_server_string(server)), "DNS_SERVER=%s", dns_server_string(server), "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(server->possible_feature_level), @@ -779,6 +797,34 @@ bool dns_server_address_valid(int family, const union in_addr_union *sa) { return true; } +DnssecMode dns_server_get_dnssec_mode(DnsServer *s) { + assert(s); + + if (s->link) + return link_get_dnssec_mode(s->link); + + return manager_get_dnssec_mode(s->manager); +} + +void dns_server_flush_cache(DnsServer *s) { + DnsServer *current; + DnsScope *scope; + + assert(s); + + /* Flush the cache of the scope this server belongs to */ + + current = s->link ? s->link->current_dns_server : s->manager->current_dns_server; + if (current != s) + return; + + scope = s->link ? s->link->unicast_scope : s->manager->unicast_scope; + if (!scope) + return; + + dns_cache_flush(&scope->cache); +} + static const char* const dns_server_type_table[_DNS_SERVER_TYPE_MAX] = { [DNS_SERVER_SYSTEM] = "system", [DNS_SERVER_FALLBACK] = "fallback", diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index 83e288a202..bc95d53c6a 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -144,6 +144,10 @@ void manager_next_dns_server(Manager *m); bool dns_server_address_valid(int family, const union in_addr_union *sa); +DnssecMode dns_server_get_dnssec_mode(DnsServer *s); + DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref); extern const struct hash_ops dns_server_hash_ops; + +void dns_server_flush_cache(DnsServer *s); diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index 4a3c5f612f..7afbfedfb0 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -29,49 +29,33 @@ static int manager_dns_stub_udp_fd(Manager *m); static int manager_dns_stub_tcp_fd(Manager *m); static int dns_stub_make_reply_packet( - uint16_t id, - int rcode, + DnsPacket **p, DnsQuestion *q, - DnsAnswer *answer, - bool add_opt, /* add an OPT RR to this packet */ - bool edns0_do, /* set the EDNS0 DNSSEC OK bit */ - bool ad, /* set the DNSSEC authenticated data bit */ - DnsPacket **ret) { + DnsAnswer *answer) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; DnsResourceRecord *rr; unsigned c = 0; int r; + assert(p); + /* Note that we don't bother with any additional RRs, as this is stub is for local lookups only, and hence * roundtrips aren't expensive. */ - r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); - if (r < 0) - return r; - - /* If the client didn't do EDNS, clamp the rcode to 4 bit */ - if (!add_opt && rcode > 0xF) - rcode = DNS_RCODE_SERVFAIL; + if (!*p) { + r = dns_packet_new(p, DNS_PROTOCOL_DNS, 0); + if (r < 0) + return r; - DNS_PACKET_HEADER(p)->id = id; - DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( - 1 /* qr */, - 0 /* opcode */, - 0 /* aa */, - 0 /* tc */, - 1 /* rd */, - 1 /* ra */, - ad /* ad */, - 0 /* cd */, - rcode)); + r = dns_packet_append_question(*p, q); + if (r < 0) + return r; - r = dns_packet_append_question(p, q); - if (r < 0) - return r; - DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(q)); + DNS_PACKET_HEADER(*p)->qdcount = htobe16(dns_question_size(q)); + } DNS_ANSWER_FOREACH(rr, answer) { + r = dns_question_matches_rr(q, rr, NULL); if (r < 0) return r; @@ -86,13 +70,55 @@ static int dns_stub_make_reply_packet( continue; add: - r = dns_packet_append_rr(p, rr, NULL, NULL); + r = dns_packet_append_rr(*p, rr, 0, NULL, NULL); if (r < 0) return r; c++; } - DNS_PACKET_HEADER(p)->ancount = htobe16(c); + + DNS_PACKET_HEADER(*p)->ancount = htobe16(be16toh(DNS_PACKET_HEADER(*p)->ancount) + c); + + return 0; +} + +static int dns_stub_finish_reply_packet( + DnsPacket *p, + uint16_t id, + int rcode, + bool add_opt, /* add an OPT RR to this packet? */ + bool edns0_do, /* set the EDNS0 DNSSEC OK bit? */ + bool ad) { /* set the DNSSEC authenticated data bit? */ + + int r; + + assert(p); + + if (!add_opt) { + /* If the client can't to EDNS0, don't do DO either */ + edns0_do = false; + + /* If the client didn't do EDNS, clamp the rcode to 4 bit */ + if (rcode > 0xF) + rcode = DNS_RCODE_SERVFAIL; + } + + /* Don't set the AD bit unless DO is on, too */ + if (!edns0_do) + ad = false; + + DNS_PACKET_HEADER(p)->id = id; + + DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( + 1 /* qr */, + 0 /* opcode */, + 0 /* aa */, + 0 /* tc */, + 1 /* rd */, + 1 /* ra */, + ad /* ad */, + 0 /* cd */, + rcode)); if (add_opt) { r = dns_packet_append_opt(p, ADVERTISE_DATAGRAM_SIZE_MAX, edns0_do, rcode, NULL); @@ -100,9 +126,6 @@ static int dns_stub_make_reply_packet( return r; } - *ret = p; - p = NULL; - return 0; } @@ -148,14 +171,18 @@ static int dns_stub_send(Manager *m, DnsStream *s, DnsPacket *p, DnsPacket *repl return 0; } -static int dns_stub_send_failure(Manager *m, DnsStream *s, DnsPacket *p, int rcode) { +static int dns_stub_send_failure(Manager *m, DnsStream *s, DnsPacket *p, int rcode, bool authenticated) { _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; int r; assert(m); assert(p); - r = dns_stub_make_reply_packet(DNS_PACKET_ID(p), rcode, p->question, NULL, !!p->opt, DNS_PACKET_DO(p), false, &reply); + r = dns_stub_make_reply_packet(&reply, p->question, NULL); + if (r < 0) + return log_debug_errno(r, "Failed to make failure packet: %m"); + + r = dns_stub_finish_reply_packet(reply, DNS_PACKET_ID(p), rcode, !!p->opt, DNS_PACKET_DO(p), authenticated); if (r < 0) return log_debug_errno(r, "Failed to build failure packet: %m"); @@ -170,33 +197,47 @@ static void dns_stub_query_complete(DnsQuery *q) { switch (q->state) { - case DNS_TRANSACTION_SUCCESS: { - _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; + case DNS_TRANSACTION_SUCCESS: + + r = dns_stub_make_reply_packet(&q->reply_dns_packet, q->question_idna, q->answer); + if (r < 0) { + log_debug_errno(r, "Failed to build reply packet: %m"); + break; + } - r = dns_stub_make_reply_packet( + r = dns_query_process_cname(q); + if (r == -ELOOP) { + (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL, false); + break; + } + if (r < 0) { + log_debug_errno(r, "Failed to process CNAME: %m"); + break; + } + if (r == DNS_QUERY_RESTARTED) + return; + + r = dns_stub_finish_reply_packet( + q->reply_dns_packet, DNS_PACKET_ID(q->request_dns_packet), q->answer_rcode, - q->question_idna, - q->answer, !!q->request_dns_packet->opt, DNS_PACKET_DO(q->request_dns_packet), - DNS_PACKET_DO(q->request_dns_packet) && q->answer_authenticated, - &reply); + dns_query_fully_authenticated(q)); if (r < 0) { - log_debug_errno(r, "Failed to build reply packet: %m"); + log_debug_errno(r, "Failed to finish reply packet: %m"); break; } - (void) dns_stub_send(q->manager, q->request_dns_stream, q->request_dns_packet, reply); + (void) dns_stub_send(q->manager, q->request_dns_stream, q->request_dns_packet, q->reply_dns_packet); break; - } case DNS_TRANSACTION_RCODE_FAILURE: - (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, q->answer_rcode); + (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, q->answer_rcode, dns_query_fully_authenticated(q)); break; case DNS_TRANSACTION_NOT_FOUND: - (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_NXDOMAIN); + (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_NXDOMAIN, dns_query_fully_authenticated(q)); break; case DNS_TRANSACTION_TIMEOUT: @@ -212,7 +253,7 @@ static void dns_stub_query_complete(DnsQuery *q) { case DNS_TRANSACTION_NO_TRUST_ANCHOR: case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: case DNS_TRANSACTION_NETWORK_DOWN: - (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL); + (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL, false); break; case DNS_TRANSACTION_NULL: @@ -259,52 +300,52 @@ static void dns_stub_process_query(Manager *m, DnsStream *s, DnsPacket *p) { if (in_addr_is_localhost(p->family, &p->sender) <= 0 || in_addr_is_localhost(p->family, &p->destination) <= 0) { log_error("Got packet on unexpected IP range, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL); + dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL, false); goto fail; } r = dns_packet_extract(p); if (r < 0) { log_debug_errno(r, "Failed to extract resources from incoming packet, ignoring packet: %m"); - dns_stub_send_failure(m, s, p, DNS_RCODE_FORMERR); + dns_stub_send_failure(m, s, p, DNS_RCODE_FORMERR, false); goto fail; } if (!DNS_PACKET_VERSION_SUPPORTED(p)) { log_debug("Got EDNS OPT field with unsupported version number."); - dns_stub_send_failure(m, s, p, DNS_RCODE_BADVERS); + dns_stub_send_failure(m, s, p, DNS_RCODE_BADVERS, false); goto fail; } if (dns_type_is_obsolete(p->question->keys[0]->type)) { log_debug("Got message with obsolete key type, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP); + dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP, false); goto fail; } if (dns_type_is_zone_transer(p->question->keys[0]->type)) { log_debug("Got request for zone transfer, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP); + dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP, false); goto fail; } if (!DNS_PACKET_RD(p)) { /* If the "rd" bit is off (i.e. recursion was not requested), then refuse operation */ log_debug("Got request with recursion disabled, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_REFUSED); + dns_stub_send_failure(m, s, p, DNS_RCODE_REFUSED, false); goto fail; } if (DNS_PACKET_DO(p) && DNS_PACKET_CD(p)) { log_debug("Got request with DNSSEC CD bit set, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP); + dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP, false); goto fail; } - r = dns_query_new(m, &q, p->question, p->question, 0, SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_NO_CNAME); + r = dns_query_new(m, &q, p->question, p->question, 0, SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_SEARCH); if (r < 0) { log_error_errno(r, "Failed to generate query object: %m"); - dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL); + dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL, false); goto fail; } @@ -324,7 +365,7 @@ static void dns_stub_process_query(Manager *m, DnsStream *s, DnsPacket *p) { r = dns_query_go(q); if (r < 0) { log_error_errno(r, "Failed to start query: %m"); - dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL); + dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL, false); goto fail; } diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 2fce44ec8b..ecd7068683 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -31,6 +31,7 @@ #include "string-table.h" #define TRANSACTIONS_MAX 4096 +#define TRANSACTION_TCP_TIMEOUT_USEC (10U*USEC_PER_SEC) static void dns_transaction_reset_answer(DnsTransaction *t) { assert(t); @@ -318,7 +319,7 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { dns_resource_key_to_string(t->key, key_str, sizeof key_str); log_struct(LOG_NOTICE, - LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_FAILURE), + "MESSAGE_ID=" SD_MESSAGE_DNSSEC_FAILURE_STR, LOG_MESSAGE("DNSSEC validation failed for question %s: %s", key_str, dnssec_result_to_string(t->answer_dnssec_result)), "DNS_TRANSACTION=%" PRIu16, t->id, "DNS_QUESTION=%s", key_str, @@ -363,6 +364,8 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { SET_FOREACH_MOVE(z, t->notify_zone_items_done, t->notify_zone_items) dns_zone_item_notify(z); SWAP_TWO(t->notify_zone_items, t->notify_zone_items_done); + if (t->probing) + (void) dns_scope_announce(t->scope, false); SET_FOREACH_MOVE(d, t->notify_transactions_done, t->notify_transactions) dns_transaction_notify(d, t); @@ -830,7 +833,7 @@ 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); + log_debug("Processing incoming packet on transaction %" PRIu16". (rcode=%s)", t->id, dns_rcode_to_string(DNS_PACKET_RCODE(p))); switch (t->scope->protocol) { @@ -908,9 +911,13 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { /* Request failed, immediately try again with reduced features */ - if (t->current_feature_level <= DNS_SERVER_FEATURE_LEVEL_WORST) { - /* This was already at the lowest possible feature level? If so, we can't downgrade - * this transaction anymore, hence let's process the response, and accept the rcode. */ + if (t->current_feature_level <= DNS_SERVER_FEATURE_LEVEL_UDP) { + /* This was already at UDP feature level? If so, it doesn't make sense to downgrade + * this transaction anymore, hence let's process the response, and accept the + * rcode. Note that we don't retry on TCP, since that's a suitable way to mitigate + * packet loss, but is not going to give us better rcodes should we actually have + * managed to get them already at UDP level. */ + log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p))); break; } @@ -924,7 +931,16 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { dns_transaction_retry(t, false /* use the same server */); return; - } else if (DNS_PACKET_TC(p)) + } + + if (DNS_PACKET_RCODE(p) == DNS_RCODE_REFUSED) { + /* This server refused our request? If so, try again, use a different server */ + log_debug("Server returned REFUSED, switching servers, and retrying."); + dns_transaction_retry(t, true /* pick a new server */); + return; + } + + if (DNS_PACKET_TC(p)) dns_server_packet_truncated(t->server, t->current_feature_level); break; @@ -1003,15 +1019,20 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { if (r > 0) /* Transaction got restarted... */ return; - if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) { + if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR, DNS_PROTOCOL_MDNS)) { - /* Only consider responses with equivalent query section to the request */ - r = dns_packet_is_reply_for(p, t->key); - if (r < 0) - goto fail; - if (r == 0) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; + /* When dealing with protocols other than mDNS only consider responses with + * equivalent query section to the request. For mDNS this check doesn't make + * sense, because the section 6 of RFC6762 states that "Multicast DNS responses MUST NOT + * contain any questions in the Question Section". */ + if (t->scope->protocol != DNS_PROTOCOL_MDNS) { + r = dns_packet_is_reply_for(p, t->key); + if (r < 0) + goto fail; + if (r == 0) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } } /* Install the answer as answer to the transaction */ @@ -1119,7 +1140,7 @@ static int dns_transaction_emit_udp(DnsTransaction *t) { return r; if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP) - return -EAGAIN; + return -EAGAIN; /* Sorry, can't do UDP, try TCP! */ if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) return -EOPNOTSUPP; @@ -1196,15 +1217,26 @@ static usec_t transaction_get_resend_timeout(DnsTransaction *t) { assert(t); assert(t->scope); + switch (t->scope->protocol) { case DNS_PROTOCOL_DNS: + + /* When we do TCP, grant a much longer timeout, as in this case there's no need for us to quickly + * resend, as the kernel does that anyway for us, and we really don't want to interrupt it in that + * needlessly. */ + if (t->stream) + return TRANSACTION_TCP_TIMEOUT_USEC; + assert(t->server); return t->server->resend_timeout; case DNS_PROTOCOL_MDNS: assert(t->n_attempts > 0); - return (1 << (t->n_attempts - 1)) * USEC_PER_SEC; + if (t->probing) + return MDNS_PROBING_INTERVAL_USEC; + else + return (1 << (t->n_attempts - 1)) * USEC_PER_SEC; case DNS_PROTOCOL_LLMNR: return t->scope->resend_timeout; @@ -1358,7 +1390,7 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) { if (r < 0) return r; - r = dns_packet_append_key(p, t->key, NULL); + r = dns_packet_append_key(p, t->key, 0, NULL); if (r < 0) return r; @@ -1390,7 +1422,7 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) { if (qdcount >= UINT16_MAX) break; - r = dns_packet_append_key(p, other->key, NULL); + r = dns_packet_append_key(p, other->key, 0, NULL); /* * If we can't stuff more questions into the packet, just give up. @@ -1417,7 +1449,7 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) { if (r < 0) return r; - (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); + (void) sd_event_source_set_description(other->timeout_event_source, "dns-transaction-timeout"); other->state = DNS_TRANSACTION_PENDING; other->next_attempt_after = ts; @@ -1459,7 +1491,7 @@ static int dns_transaction_make_packet(DnsTransaction *t) { if (r < 0) return r; - r = dns_packet_append_key(p, t->key, NULL); + r = dns_packet_append_key(p, t->key, 0, NULL); if (r < 0) return r; @@ -1560,7 +1592,7 @@ int dns_transaction_go(DnsTransaction *t) { r = dns_transaction_emit_udp(t); if (r == -EMSGSIZE) log_debug("Sending query via TCP since it is too large."); - if (r == -EAGAIN) + else if (r == -EAGAIN) log_debug("Sending query via TCP since server doesn't support UDP."); if (r == -EMSGSIZE || r == -EAGAIN) r = dns_transaction_open_tcp(t); @@ -1977,8 +2009,18 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { r = dns_resource_key_match_rr(t->key, rr, NULL); if (r < 0) return r; - if (r == 0) - continue; + if (r == 0) { + /* Hmm, so this SOA RR doesn't match our original question. In this case, maybe this is + * a negative reply, and we need the a SOA RR's TTL in order to cache a negative entry? + * If so, we need to validate it, too. */ + + r = dns_answer_match_key(t->answer, t->key, NULL); + if (r < 0) + return r; + if (r > 0) /* positive reply, we won't need the SOA and hence don't need to validate + * it. */ + continue; + } r = dnssec_has_rrsig(t->answer, rr->key); if (r < 0) diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index 5a1df70422..a8d97738ef 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -78,6 +78,8 @@ struct DnsTransaction { bool clamp_ttl:1; + bool probing:1; + DnsPacket *sent, *received; DnsAnswer *answer; @@ -172,10 +174,20 @@ DnsTransactionSource dns_transaction_source_from_string(const char *s) _pure_; #define MDNS_JITTER_MIN_USEC (20 * USEC_PER_MSEC) #define MDNS_JITTER_RANGE_USEC (100 * USEC_PER_MSEC) +/* mDNS probing interval, see RFC 6762 Section 8.1 */ +#define MDNS_PROBING_INTERVAL_USEC (250 * USEC_PER_MSEC) + /* Maximum attempts to send DNS requests, across all DNS servers */ -#define DNS_TRANSACTION_ATTEMPTS_MAX 16 +#define DNS_TRANSACTION_ATTEMPTS_MAX 24 /* Maximum attempts to send LLMNR requests, see RFC 4795 Section 2.7 */ #define LLMNR_TRANSACTION_ATTEMPTS_MAX 3 -#define TRANSACTION_ATTEMPTS_MAX(p) ((p) == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_ATTEMPTS_MAX : DNS_TRANSACTION_ATTEMPTS_MAX) +/* Maximum attempts to send MDNS requests, see RFC 6762 Section 8.1 */ +#define MDNS_TRANSACTION_ATTEMPTS_MAX 3 + +#define TRANSACTION_ATTEMPTS_MAX(p) (((p) == DNS_PROTOCOL_LLMNR) ? \ + LLMNR_TRANSACTION_ATTEMPTS_MAX : \ + (((p) == DNS_PROTOCOL_MDNS) ? \ + MDNS_TRANSACTION_ATTEMPTS_MAX : \ + DNS_TRANSACTION_ATTEMPTS_MAX)) diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c index 9917b9e984..7e08cba4e1 100644 --- a/src/resolve/resolved-dns-trust-anchor.c +++ b/src/resolve/resolved-dns-trust-anchor.c @@ -547,10 +547,33 @@ int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey *ke } int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name) { + int r; + assert(d); assert(name); - return set_contains(d->negative_by_name, name); + for (;;) { + /* If the domain is listed as-is in the NTA database, then that counts */ + if (set_contains(d->negative_by_name, name)) + return true; + + /* If the domain isn't listed as NTA, but is listed as positive trust anchor, then that counts. See RFC + * 7646, section 1.1 */ + if (hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, name))) + return false; + + if (hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_KEY, name))) + return false; + + /* And now, let's look at the parent, and check that too */ + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + break; + } + + return false; } static int dns_trust_anchor_revoked_put(DnsTrustAnchor *d, DnsResourceRecord *rr) { @@ -594,7 +617,7 @@ static int dns_trust_anchor_remove_revoked(DnsTrustAnchor *d, DnsResourceRecord /* We found the key! Warn the user */ log_struct(LOG_WARNING, - LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED), + "MESSAGE_ID=" SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED_STR, LOG_MESSAGE("DNSSEC Trust anchor %s has been revoked. Please update the trust anchor, or upgrade your operating system."), strna(dns_resource_record_to_string(rr)), "TRUST_ANCHOR=%s", dns_resource_record_to_string(rr), NULL); diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index 746a979f47..ad024b54f5 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -196,6 +196,7 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) { goto gc; i->probe_transaction = t; + t->probing = true; if (t->state == DNS_TRANSACTION_NULL) { diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h index a41df37e6b..545ec958fb 100644 --- a/src/resolve/resolved-dns-zone.h +++ b/src/resolve/resolved-dns-zone.h @@ -37,6 +37,9 @@ typedef enum DnsZoneItemState DnsZoneItemState; /* RFC 4795 Section 2.8. suggests a TTL of 30s by default */ #define LLMNR_DEFAULT_TTL (30) +/* RFC 6762 Section 10. suggests a TTL of 120s by default */ +#define MDNS_DEFAULT_TTL (120) + enum DnsZoneItemState { DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index e7e5c5f5a7..44c0cd654f 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -85,6 +85,10 @@ Link *link_free(Link *l) { if (!l) return NULL; + /* Send goodbye messages. */ + dns_scope_announce(l->mdns_ipv4_scope, true); + dns_scope_announce(l->mdns_ipv6_scope, true); + link_flush_settings(l); while (l->addresses) @@ -665,6 +669,7 @@ int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr a->link = l; LIST_PREPEND(addresses, l->addresses, a); + l->n_addresses++; if (ret) *ret = a; @@ -679,6 +684,9 @@ LinkAddress *link_address_free(LinkAddress *a) { if (a->link) { LIST_REMOVE(addresses, a->link->addresses, a); + assert(a->link->n_addresses > 0); + a->link->n_addresses--; + if (a->llmnr_address_rr) { if (a->family == AF_INET && a->link->llmnr_ipv4_scope) dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); @@ -692,10 +700,26 @@ LinkAddress *link_address_free(LinkAddress *a) { else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); } + + if (a->mdns_address_rr) { + if (a->family == AF_INET && a->link->mdns_ipv4_scope) + dns_zone_remove_rr(&a->link->mdns_ipv4_scope->zone, a->mdns_address_rr); + else if (a->family == AF_INET6 && a->link->mdns_ipv6_scope) + dns_zone_remove_rr(&a->link->mdns_ipv6_scope->zone, a->mdns_address_rr); + } + + if (a->mdns_ptr_rr) { + if (a->family == AF_INET && a->link->mdns_ipv4_scope) + dns_zone_remove_rr(&a->link->mdns_ipv4_scope->zone, a->mdns_ptr_rr); + else if (a->family == AF_INET6 && a->link->mdns_ipv6_scope) + dns_zone_remove_rr(&a->link->mdns_ipv6_scope->zone, a->mdns_ptr_rr); + } } dns_resource_record_unref(a->llmnr_address_rr); dns_resource_record_unref(a->llmnr_ptr_rr); + dns_resource_record_unref(a->mdns_address_rr); + dns_resource_record_unref(a->mdns_ptr_rr); return mfree(a); } @@ -746,7 +770,7 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) { r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_ptr_rr, false); if (r < 0) - log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); + log_warning_errno(r, "Failed to add IPv4 PTR record to LLMNR zone: %m"); } else { if (a->llmnr_address_rr) { if (a->link->llmnr_ipv4_scope) @@ -760,6 +784,59 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) { a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); } } + + if (!force_remove && + link_address_relevant(a, true) && + a->link->mdns_ipv4_scope && + a->link->mdns_support == RESOLVE_SUPPORT_YES && + a->link->manager->mdns_support == RESOLVE_SUPPORT_YES) { + if (!a->link->manager->mdns_host_ipv4_key) { + a->link->manager->mdns_host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->mdns_hostname); + if (!a->link->manager->mdns_host_ipv4_key) { + r = -ENOMEM; + goto fail; + } + } + + if (!a->mdns_address_rr) { + a->mdns_address_rr = dns_resource_record_new(a->link->manager->mdns_host_ipv4_key); + if (!a->mdns_address_rr) { + r = -ENOMEM; + goto fail; + } + + a->mdns_address_rr->a.in_addr = a->in_addr.in; + a->mdns_address_rr->ttl = MDNS_DEFAULT_TTL; + } + + if (!a->mdns_ptr_rr) { + r = dns_resource_record_new_reverse(&a->mdns_ptr_rr, a->family, &a->in_addr, a->link->manager->mdns_hostname); + if (r < 0) + goto fail; + + a->mdns_ptr_rr->ttl = MDNS_DEFAULT_TTL; + } + + r = dns_zone_put(&a->link->mdns_ipv4_scope->zone, a->link->mdns_ipv4_scope, a->mdns_address_rr, true); + if (r < 0) + log_warning_errno(r, "Failed to add A record to MDNS zone: %m"); + + r = dns_zone_put(&a->link->mdns_ipv4_scope->zone, a->link->mdns_ipv4_scope, a->mdns_ptr_rr, false); + if (r < 0) + log_warning_errno(r, "Failed to add IPv4 PTR record to MDNS zone: %m"); + } else { + if (a->mdns_address_rr) { + if (a->link->mdns_ipv4_scope) + dns_zone_remove_rr(&a->link->mdns_ipv4_scope->zone, a->mdns_address_rr); + a->mdns_address_rr = dns_resource_record_unref(a->mdns_address_rr); + } + + if (a->mdns_ptr_rr) { + if (a->link->mdns_ipv4_scope) + dns_zone_remove_rr(&a->link->mdns_ipv4_scope->zone, a->mdns_ptr_rr); + a->mdns_ptr_rr = dns_resource_record_unref(a->mdns_ptr_rr); + } + } } if (a->family == AF_INET6) { @@ -817,6 +894,60 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) { a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); } } + + if (!force_remove && + link_address_relevant(a, true) && + a->link->mdns_ipv6_scope && + a->link->mdns_support == RESOLVE_SUPPORT_YES && + a->link->manager->mdns_support == RESOLVE_SUPPORT_YES) { + + if (!a->link->manager->mdns_host_ipv6_key) { + a->link->manager->mdns_host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->mdns_hostname); + if (!a->link->manager->mdns_host_ipv6_key) { + r = -ENOMEM; + goto fail; + } + } + + if (!a->mdns_address_rr) { + a->mdns_address_rr = dns_resource_record_new(a->link->manager->mdns_host_ipv6_key); + if (!a->mdns_address_rr) { + r = -ENOMEM; + goto fail; + } + + a->mdns_address_rr->aaaa.in6_addr = a->in_addr.in6; + a->mdns_address_rr->ttl = MDNS_DEFAULT_TTL; + } + + if (!a->mdns_ptr_rr) { + r = dns_resource_record_new_reverse(&a->mdns_ptr_rr, a->family, &a->in_addr, a->link->manager->mdns_hostname); + if (r < 0) + goto fail; + + a->mdns_ptr_rr->ttl = MDNS_DEFAULT_TTL; + } + + r = dns_zone_put(&a->link->mdns_ipv6_scope->zone, a->link->mdns_ipv6_scope, a->mdns_address_rr, true); + if (r < 0) + log_warning_errno(r, "Failed to add AAAA record to MDNS zone: %m"); + + r = dns_zone_put(&a->link->mdns_ipv6_scope->zone, a->link->mdns_ipv6_scope, a->mdns_ptr_rr, false); + if (r < 0) + log_warning_errno(r, "Failed to add IPv6 PTR record to MDNS zone: %m"); + } else { + if (a->mdns_address_rr) { + if (a->link->mdns_ipv6_scope) + dns_zone_remove_rr(&a->link->mdns_ipv6_scope->zone, a->mdns_address_rr); + a->mdns_address_rr = dns_resource_record_unref(a->mdns_address_rr); + } + + if (a->mdns_ptr_rr) { + if (a->link->mdns_ipv6_scope) + dns_zone_remove_rr(&a->link->mdns_ipv6_scope->zone, a->mdns_ptr_rr); + a->mdns_ptr_rr = dns_resource_record_unref(a->mdns_ptr_rr); + } + } } return; diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index c9b2a58c34..55a56b7906 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -47,6 +47,8 @@ struct LinkAddress { DnsResourceRecord *llmnr_address_rr; DnsResourceRecord *llmnr_ptr_rr; + DnsResourceRecord *mdns_address_rr; + DnsResourceRecord *mdns_ptr_rr; LIST_FIELDS(LinkAddress, addresses); }; @@ -58,6 +60,7 @@ struct Link { unsigned flags; LIST_HEAD(LinkAddress, addresses); + unsigned n_addresses; LIST_HEAD(DnsServer, dns_servers); DnsServer *current_dns_server; diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 667774b906..c4e4409fe3 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -322,28 +322,28 @@ static int manager_network_monitor_listen(Manager *m) { return 0; } -static int determine_hostname(char **llmnr_hostname, char **mdns_hostname) { +static int determine_hostname(char **full_hostname, char **llmnr_hostname, char **mdns_hostname) { _cleanup_free_ char *h = NULL, *n = NULL; char label[DNS_LABEL_MAX]; const char *p; int r, k; + assert(full_hostname); assert(llmnr_hostname); assert(mdns_hostname); - /* Extract and normalize the first label of the locally - * configured hostname, and check it's not "localhost". */ + /* Extract and normalize the first label of the locally configured hostname, and check it's not "localhost". */ - h = gethostname_malloc(); - if (!h) - return log_oom(); + r = gethostname_strict(&h); + if (r < 0) + return log_debug_errno(r, "Can't determine system hostname: %m"); p = h; r = dns_label_unescape(&p, label, sizeof(label)); if (r < 0) return log_error_errno(r, "Failed to unescape host name: %m"); if (r == 0) { - log_error("Couldn't find a single label in hosntame."); + log_error("Couldn't find a single label in hostname."); return -EINVAL; } @@ -374,32 +374,84 @@ static int determine_hostname(char **llmnr_hostname, char **mdns_hostname) { *llmnr_hostname = n; n = NULL; + *full_hostname = h; + h = NULL; + + return 0; +} + +static const char *fallback_hostname(void) { + + /* Determine the fall back hostname. For exposing this system to the outside world, we cannot have it to be + * "localhost" even if that's the compiled in hostname. In this case, let's revert to "linux" instead. */ + + if (is_localhost(FALLBACK_HOSTNAME)) + return "linux"; + + return FALLBACK_HOSTNAME; +} + +static int make_fallback_hostnames(char **full_hostname, char **llmnr_hostname, char **mdns_hostname) { + _cleanup_free_ char *n = NULL, *m = NULL; + char label[DNS_LABEL_MAX], *h; + const char *p; + int r; + + assert(full_hostname); + assert(llmnr_hostname); + assert(mdns_hostname); + + p = fallback_hostname(); + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + return log_error_errno(r, "Failed to unescape fallback host name: %m"); + + assert(r > 0); /* The fallback hostname must have at least one label */ + + r = dns_label_escape_new(label, r, &n); + if (r < 0) + return log_error_errno(r, "Failed to escape fallback hostname: %m"); + + r = dns_name_concat(n, "local", &m); + if (r < 0) + return log_error_errno(r, "Failed to concatenate mDNS hostname: %m"); + + h = strdup(fallback_hostname()); + if (!h) + return log_oom(); + + *llmnr_hostname = n; + n = NULL; + + *mdns_hostname = m; + m = NULL; + + *full_hostname = h; + return 0; } static int on_hostname_change(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - _cleanup_free_ char *llmnr_hostname = NULL, *mdns_hostname = NULL; + _cleanup_free_ char *full_hostname = NULL, *llmnr_hostname = NULL, *mdns_hostname = NULL; Manager *m = userdata; int r; assert(m); - r = determine_hostname(&llmnr_hostname, &mdns_hostname); + r = determine_hostname(&full_hostname, &llmnr_hostname, &mdns_hostname); if (r < 0) return 0; /* ignore invalid hostnames */ - if (streq(llmnr_hostname, m->llmnr_hostname) && streq(mdns_hostname, m->mdns_hostname)) + if (streq(full_hostname, m->full_hostname) && + streq(llmnr_hostname, m->llmnr_hostname) && + streq(mdns_hostname, m->mdns_hostname)) return 0; - log_info("System hostname changed to '%s'.", llmnr_hostname); + log_info("System hostname changed to '%s'.", full_hostname); - free(m->llmnr_hostname); - free(m->mdns_hostname); - - m->llmnr_hostname = llmnr_hostname; - m->mdns_hostname = mdns_hostname; - - llmnr_hostname = mdns_hostname = NULL; + free_and_replace(m->full_hostname, full_hostname); + free_and_replace(m->llmnr_hostname, llmnr_hostname); + free_and_replace(m->mdns_hostname, mdns_hostname); manager_refresh_rrs(m); @@ -428,18 +480,15 @@ static int manager_watch_hostname(Manager *m) { (void) sd_event_source_set_description(m->hostname_event_source, "hostname"); - r = determine_hostname(&m->llmnr_hostname, &m->mdns_hostname); + r = determine_hostname(&m->full_hostname, &m->llmnr_hostname, &m->mdns_hostname); if (r < 0) { - log_info("Defaulting to hostname 'linux'."); - m->llmnr_hostname = strdup("linux"); - if (!m->llmnr_hostname) - return log_oom(); - - m->mdns_hostname = strdup("linux.local"); - if (!m->mdns_hostname) - return log_oom(); + log_info("Defaulting to hostname '%s'.", fallback_hostname()); + + r = make_fallback_hostnames(&m->full_hostname, &m->llmnr_hostname, &m->mdns_hostname); + if (r < 0) + return r; } else - log_info("Using system hostname '%s'.", m->llmnr_hostname); + log_info("Using system hostname '%s'.", m->full_hostname); return 0; } @@ -498,7 +547,7 @@ int manager_new(Manager **ret) { m->hostname_fd = -1; m->llmnr_support = RESOLVE_SUPPORT_YES; - m->mdns_support = RESOLVE_SUPPORT_NO; + m->mdns_support = RESOLVE_SUPPORT_YES; m->dnssec_mode = DEFAULT_DNSSEC_MODE; m->enable_cache = true; m->dns_stub_listener_mode = DNS_STUB_LISTENER_UDP; @@ -621,9 +670,13 @@ Manager *manager_free(Manager *m) { dns_resource_key_unref(m->llmnr_host_ipv4_key); dns_resource_key_unref(m->llmnr_host_ipv6_key); + dns_resource_key_unref(m->mdns_host_ipv4_key); + dns_resource_key_unref(m->mdns_host_ipv6_key); sd_event_source_unref(m->hostname_event_source); safe_close(m->hostname_fd); + + free(m->full_hostname); free(m->llmnr_hostname); free(m->mdns_hostname); @@ -1007,6 +1060,8 @@ void manager_refresh_rrs(Manager *m) { m->llmnr_host_ipv4_key = dns_resource_key_unref(m->llmnr_host_ipv4_key); m->llmnr_host_ipv6_key = dns_resource_key_unref(m->llmnr_host_ipv6_key); + m->mdns_host_ipv4_key = dns_resource_key_unref(m->mdns_host_ipv4_key); + m->mdns_host_ipv6_key = dns_resource_key_unref(m->mdns_host_ipv6_key); HASHMAP_FOREACH(l, m->links, i) { link_add_rrs(l, true); @@ -1146,8 +1201,14 @@ int manager_is_own_hostname(Manager *m, const char *name) { return r; } - if (m->mdns_hostname) - return dns_name_equal(name, m->mdns_hostname); + if (m->mdns_hostname) { + r = dns_name_equal(name, m->mdns_hostname); + if (r != 0) + return r; + } + + if (m->full_hostname) + return dns_name_equal(name, m->full_hostname); return 0; } diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 6b2208ed94..97c52b7729 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -109,10 +109,13 @@ struct Manager { sd_event_source *bus_retry_event_source; /* The hostname we publish on LLMNR and mDNS */ + char *full_hostname; char *llmnr_hostname; char *mdns_hostname; DnsResourceKey *llmnr_host_ipv4_key; DnsResourceKey *llmnr_host_ipv6_key; + DnsResourceKey *mdns_host_ipv4_key; + DnsResourceKey *mdns_host_ipv6_key; /* Watch the system hostname */ int hostname_fd; diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index b13b1d0144..c40e8f75f0 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -67,6 +67,50 @@ eaddrinuse: return 0; } +static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; + DnsResourceKey *key = NULL; + bool tentative = false; + int r; + + assert(s); + assert(p); + + r = dns_packet_extract(p); + if (r < 0) + return log_debug_errno(r, "Failed to extract resource records from incoming packet: %m"); + + /* TODO: there might be more than one question in mDNS queries. */ + assert_return((dns_question_size(p->question) > 0), -EINVAL); + key = p->question->keys[0]; + + r = dns_zone_lookup(&s->zone, key, 0, &answer, &soa, &tentative); + if (r < 0) { + log_debug_errno(r, "Failed to lookup key: %m"); + return r; + } + if (r == 0) + return 0; + + r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, NULL, answer, NULL, false, &reply); + if (r < 0) { + log_debug_errno(r, "Failed to build reply packet: %m"); + return r; + } + + if (!ratelimit_test(&s->ratelimit)) + return 0; + + r = dns_scope_emit_udp(s, -1, reply); + if (r < 0) { + log_debug_errno(r, "Failed to send reply packet: %m"); + return r; + } + + return 0; +} + static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; Manager *m = userdata; @@ -77,6 +121,9 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us if (r <= 0) return r; + if (manager_our_packet(m, p)) + return 0; + scope = manager_find_scope(m, p); if (!scope) { log_warning("Got mDNS UDP packet on unknown scope. Ignoring."); @@ -115,9 +162,28 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us dns_name_endswith(name, "local") > 0)) return 0; + if (rr->ttl == 0) { + log_debug("Got a goodbye packet"); + /* See the section 10.1 of RFC6762 */ + rr->ttl = 1; + } + t = dns_scope_find_transaction(scope, rr->key, false); if (t) dns_transaction_process_reply(t, p); + + /* Also look for the various types of ANY transactions */ + t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), false); + if (t) + dns_transaction_process_reply(t, p); + + t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, rr->key->type, dns_resource_key_name(rr->key)), false); + if (t) + dns_transaction_process_reply(t, p); + + t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), false); + if (t) + dns_transaction_process_reply(t, p); } dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender); @@ -125,7 +191,11 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us } else if (dns_packet_validate_query(p) > 0) { log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); - dns_scope_process_query(scope, NULL, p); + r = mdns_scope_process_query(scope, p); + if (r < 0) { + log_debug_errno(r, "mDNS query processing failed: %m"); + return 0; + } } else log_debug("Invalid mDNS UDP packet."); diff --git a/src/resolve/resolved-mdns.h b/src/resolve/resolved-mdns.h index 5d274648f4..06bd3296be 100644 --- a/src/resolve/resolved-mdns.h +++ b/src/resolve/resolved-mdns.h @@ -22,6 +22,7 @@ #include "resolved-manager.h" #define MDNS_PORT 5353 +#define MDNS_ANNOUNCE_DELAY (1 * USEC_PER_SEC) int manager_mdns_ipv4_fd(Manager *m); int manager_mdns_ipv6_fd(Manager *m); diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c index 13f08f8a6c..3c62550872 100644 --- a/src/resolve/resolved-resolv-conf.c +++ b/src/resolve/resolved-resolv-conf.c @@ -203,13 +203,13 @@ static void write_resolv_conf_search( static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) { Iterator i; - fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n" + fputs("# This file is managed by man:systemd-resolved(8). Do not edit.\n#\n" "# This is a dynamic resolv.conf file for connecting local clients directly to\n" "# all known DNS servers.\n#\n" "# Third party programs must not access this file directly, but only through the\n" - "# symlink at /etc/resolv.conf. To manage resolv.conf(5) in a different way,\n" + "# symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a different way,\n" "# replace this symlink by a static file or a different symlink.\n#\n" - "# See systemd-resolved.service(8) for details about the supported modes of\n" + "# See man:systemd-resolved.service(8) for details about the supported modes of\n" "# operation for /etc/resolv.conf.\n\n", f); if (ordered_set_isempty(dns)) diff --git a/src/resolve/test-data/_443._tcp.fedoraproject.org.pkts b/src/resolve/test-data/_443._tcp.fedoraproject.org.pkts Binary files differdeleted file mode 100644 index a383c6286d..0000000000 --- a/src/resolve/test-data/_443._tcp.fedoraproject.org.pkts +++ /dev/null diff --git a/src/resolve/test-data/_openpgpkey.fedoraproject.org.pkts b/src/resolve/test-data/_openpgpkey.fedoraproject.org.pkts Binary files differdeleted file mode 100644 index 15de02e997..0000000000 --- a/src/resolve/test-data/_openpgpkey.fedoraproject.org.pkts +++ /dev/null diff --git a/src/resolve/test-data/fake-caa.pkts b/src/resolve/test-data/fake-caa.pkts Binary files differdeleted file mode 100644 index 1c3ecc5491..0000000000 --- a/src/resolve/test-data/fake-caa.pkts +++ /dev/null diff --git a/src/resolve/test-data/fedoraproject.org.pkts b/src/resolve/test-data/fedoraproject.org.pkts Binary files differdeleted file mode 100644 index 17874844d9..0000000000 --- a/src/resolve/test-data/fedoraproject.org.pkts +++ /dev/null diff --git a/src/resolve/test-data/gandi.net.pkts b/src/resolve/test-data/gandi.net.pkts Binary files differdeleted file mode 100644 index 5ef51e0c8e..0000000000 --- a/src/resolve/test-data/gandi.net.pkts +++ /dev/null diff --git a/src/resolve/test-data/google.com.pkts b/src/resolve/test-data/google.com.pkts Binary files differdeleted file mode 100644 index f98c4cd855..0000000000 --- a/src/resolve/test-data/google.com.pkts +++ /dev/null diff --git a/src/resolve/test-data/kyhwana.org.pkts b/src/resolve/test-data/kyhwana.org.pkts Binary files differdeleted file mode 100644 index e28a725c9a..0000000000 --- a/src/resolve/test-data/kyhwana.org.pkts +++ /dev/null diff --git a/src/resolve/test-data/root.pkts b/src/resolve/test-data/root.pkts Binary files differdeleted file mode 100644 index 54ba668c75..0000000000 --- a/src/resolve/test-data/root.pkts +++ /dev/null diff --git a/src/resolve/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts b/src/resolve/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts Binary files differdeleted file mode 100644 index a854249532..0000000000 --- a/src/resolve/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts +++ /dev/null diff --git a/src/resolve/test-data/teamits.com.pkts b/src/resolve/test-data/teamits.com.pkts Binary files differdeleted file mode 100644 index 11deb39677..0000000000 --- a/src/resolve/test-data/teamits.com.pkts +++ /dev/null diff --git a/src/resolve/test-data/zbyszek@fedoraproject.org.pkts b/src/resolve/test-data/zbyszek@fedoraproject.org.pkts Binary files differdeleted file mode 100644 index f0a6f982df..0000000000 --- a/src/resolve/test-data/zbyszek@fedoraproject.org.pkts +++ /dev/null diff --git a/src/resolve/test-dns-packet.c b/src/resolve/test-dns-packet.c index 956b155872..8cbe492526 100644 --- a/src/resolve/test-dns-packet.c +++ b/src/resolve/test-dns-packet.c @@ -29,6 +29,7 @@ #include "resolved-dns-rr.h" #include "string-util.h" #include "strv.h" +#include "tests.h" #include "unaligned.h" #define HASH_KEY SD_ID128_MAKE(d3,1e,48,90,4b,fa,4c,fe,af,9d,d5,a1,d7,2e,8a,b1) @@ -115,7 +116,7 @@ int main(int argc, char **argv) { N = argc - 1; fnames = argv + 1; } else { - assert_se(glob(RESOLVE_TEST_DIR "/*.pkts", GLOB_NOSORT, NULL, &g) == 0); + assert_se(glob(get_testdata_dir("/test-resolve/*.pkts"), GLOB_NOSORT, NULL, &g) == 0); N = g.gl_pathc; fnames = g.gl_pathv; } |