diff options
Diffstat (limited to 'src/resolve')
27 files changed, 1466 insertions, 498 deletions
diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c index e1087b3219..63b4b36e88 100644 --- a/src/resolve/dns-type.c +++ b/src/resolve/dns-type.c @@ -44,7 +44,7 @@ int dns_type_from_string(const char *s) { return sc->id; } -/* XXX: find an authorotative list of all pseudo types? */ +/* XXX: find an authoritative list of all pseudo types? */ bool dns_type_is_pseudo(int n) { return IN_SET(n, DNS_TYPE_ANY, DNS_TYPE_AXFR, DNS_TYPE_IXFR, DNS_TYPE_OPT); } diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 6db12511f9..bf1b7c8ab4 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -91,13 +91,17 @@ static int reply_query_state(DnsQuery *q) { } } -static int append_address(sd_bus_message *reply, DnsResourceRecord *rr) { +static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifindex) { int r; assert(reply); assert(rr); - r = sd_bus_message_open_container(reply, 'r', "iay"); + r = sd_bus_message_open_container(reply, 'r', "iiay"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "i", ifindex); if (r < 0) return r; @@ -145,11 +149,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { if (r < 0) goto finish; - r = sd_bus_message_append(reply, "i", q->answer_ifindex); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(iay)"); + r = sd_bus_message_open_container(reply, 'a', "(iiay)"); if (r < 0) goto finish; @@ -157,27 +157,27 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { answer = dns_answer_ref(q->answer); for (i = 0; i < answer->n_rrs; i++) { - r = dns_question_matches_rr(q->question, answer->rrs[i]); + r = dns_question_matches_rr(q->question, answer->items[i].rr); if (r < 0) goto finish; if (r == 0) { /* Hmm, if this is not an address record, maybe it's a cname? If so, remember this */ - r = dns_question_matches_cname(q->question, answer->rrs[i]); + r = dns_question_matches_cname(q->question, answer->items[i].rr); if (r < 0) goto finish; if (r > 0) - cname = dns_resource_record_ref(answer->rrs[i]); + cname = dns_resource_record_ref(answer->items[i].rr); continue; } - r = append_address(reply, answer->rrs[i]); + r = append_address(reply, answer->items[i].rr, answer->items[i].ifindex); if (r < 0) goto finish; if (!canonical) - canonical = dns_resource_record_ref(answer->rrs[i]); + canonical = dns_resource_record_ref(answer->items[i].rr); added ++; } @@ -191,7 +191,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { /* This has a cname? Then update the query with the * new cname. */ - r = dns_query_cname_redirect(q, cname->cname.name); + r = dns_query_cname_redirect(q, cname); if (r < 0) { if (r == -ELOOP) r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop on '%s'", q->request_hostname); @@ -204,32 +204,26 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { /* Before we restart the query, let's see if any of * the RRs we already got already answers our query */ for (i = 0; i < answer->n_rrs; i++) { - r = dns_question_matches_rr(q->question, answer->rrs[i]); + r = dns_question_matches_rr(q->question, answer->items[i].rr); if (r < 0) goto finish; if (r == 0) continue; - r = append_address(reply, answer->rrs[i]); + r = append_address(reply, answer->items[i].rr, answer->items[i].ifindex); if (r < 0) goto finish; if (!canonical) - canonical = dns_resource_record_ref(answer->rrs[i]); + canonical = dns_resource_record_ref(answer->items[i].rr); added++; } - // what about the cache? - /* If we didn't find anything, then let's restart the * query, this time with the cname */ if (added <= 0) { r = dns_query_go(q); - if (r == -ESRCH) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); - goto finish; - } if (r < 0) { r = sd_bus_reply_method_errno(q->request, -r, NULL); goto finish; @@ -346,10 +340,6 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, r = dns_query_go(q); if (r < 0) { dns_query_free(q); - - if (r == -ESRCH) - sd_bus_error_setf(error, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); - return r; } @@ -373,11 +363,7 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { if (r < 0) goto finish; - r = sd_bus_message_append(reply, "i", q->answer_ifindex); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "s"); + r = sd_bus_message_open_container(reply, 'a', "(is)"); if (r < 0) goto finish; @@ -385,13 +371,13 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { answer = dns_answer_ref(q->answer); for (i = 0; i < answer->n_rrs; i++) { - r = dns_question_matches_rr(q->question, answer->rrs[i]); + r = dns_question_matches_rr(q->question, answer->items[i].rr); if (r < 0) goto finish; if (r == 0) continue; - r = sd_bus_message_append(reply, "s", answer->rrs[i]->ptr.name); + r = sd_bus_message_append(reply, "(is)", answer->items[i].ifindex, answer->items[i].rr->ptr.name); if (r < 0) goto finish; @@ -498,10 +484,6 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s r = dns_query_go(q); if (r < 0) { dns_query_free(q); - - if (r == -ESRCH) - sd_bus_error_setf(error, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); - return r; } @@ -525,11 +507,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) { if (r < 0) goto finish; - r = sd_bus_message_append(reply, "i", q->answer_ifindex); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(qqay)"); + r = sd_bus_message_open_container(reply, 'a', "(iqqay)"); if (r < 0) goto finish; @@ -540,7 +518,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) { _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; size_t start; - r = dns_question_matches_rr(q->question, answer->rrs[i]); + r = dns_question_matches_rr(q->question, answer->items[i].rr); if (r < 0) goto finish; if (r == 0) @@ -550,15 +528,20 @@ static void bus_method_resolve_record_complete(DnsQuery *q) { if (r < 0) goto finish; - r = dns_packet_append_rr(p, answer->rrs[i], &start); + p->refuse_compression = true; + + r = dns_packet_append_rr(p, answer->items[i].rr, &start); if (r < 0) goto finish; - r = sd_bus_message_open_container(reply, 'r', "qqay"); + r = sd_bus_message_open_container(reply, 'r', "iqqay"); if (r < 0) goto finish; - r = sd_bus_message_append(reply, "qq", answer->rrs[i]->key->class, answer->rrs[i]->key->type); + r = sd_bus_message_append(reply, "iqq", + answer->items[i].ifindex, + answer->items[i].rr->key->class, + answer->items[i].rr->key->type); if (r < 0) goto finish; @@ -650,10 +633,6 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd r = dns_query_go(q); if (r < 0) { dns_query_free(q); - - if (r == -ESRCH) - sd_bus_error_setf(error, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); - return r; } @@ -662,9 +641,9 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd static const sd_bus_vtable resolve_vtable[] = { SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("ResolveHostname", "isit", "ia(iay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResolveAddress", "iiayt", "iast", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResolveRecord", "isqqt", "ia(qqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResolveHostname", "isit", "a(iiay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResolveAddress", "iiayt", "a(is)t", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResolveRecord", "isqqt", "a(iqqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END, }; diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index 7af63b0a82..cc8d5fa76a 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -95,7 +95,7 @@ int config_parse_dnsv( /* Otherwise add to the list */ r = manager_parse_dns_server(m, ltype, rvalue); if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, -r, "Failed to parse DNS server string '%s'. Ignoring.", rvalue); + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNS server string '%s'. Ignoring.", rvalue); return 0; } } @@ -131,7 +131,7 @@ int config_parse_support( if (support < 0) { r = parse_boolean(rvalue); if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, -r, "Failed to parse support level '%s'. Ignoring.", rvalue); + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse support level '%s'. Ignoring.", rvalue); return 0; } diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c index f77b98e505..89b9b0e1ea 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -25,7 +25,7 @@ DnsAnswer *dns_answer_new(unsigned n) { DnsAnswer *a; - a = malloc0(offsetof(DnsAnswer, rrs) + sizeof(DnsResourceRecord*) * n); + a = malloc0(offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * n); if (!a) return NULL; @@ -54,7 +54,7 @@ DnsAnswer *dns_answer_unref(DnsAnswer *a) { unsigned i; for (i = 0; i < a->n_rrs; i++) - dns_resource_record_unref(a->rrs[i]); + dns_resource_record_unref(a->items[i].rr); free(a); } else @@ -63,25 +63,30 @@ DnsAnswer *dns_answer_unref(DnsAnswer *a) { return NULL; } -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr) { +int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) { unsigned i; int r; - assert(a); assert(rr); + if (!a) + return -ENOSPC; + for (i = 0; i < a->n_rrs; i++) { - r = dns_resource_record_equal(a->rrs[i], rr); + if (a->items[i].ifindex != ifindex) + continue; + + r = dns_resource_record_equal(a->items[i].rr, rr); if (r < 0) return r; if (r > 0) { /* Entry already exists, keep the entry with * the higher RR, or the one with TTL 0 */ - if (rr->ttl == 0 || (rr->ttl > a->rrs[i]->ttl && a->rrs[i]->ttl != 0)) { + if (rr->ttl == 0 || (rr->ttl > a->items[i].rr->ttl && a->items[i].rr->ttl != 0)) { dns_resource_record_ref(rr); - dns_resource_record_unref(a->rrs[i]); - a->rrs[i] = rr; + dns_resource_record_unref(a->items[i].rr); + a->items[i].rr = rr; } return 0; @@ -91,7 +96,10 @@ int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr) { if (a->n_rrs >= a->n_allocated) return -ENOSPC; - a->rrs[a->n_rrs++] = dns_resource_record_ref(rr); + a->items[a->n_rrs].rr = dns_resource_record_ref(rr); + a->items[a->n_rrs].ifindex = ifindex; + a->n_rrs++; + return 1; } @@ -118,18 +126,20 @@ int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) { soa->soa.expire = 1; soa->soa.minimum = ttl; - return dns_answer_add(a, soa); + return dns_answer_add(a, soa, 0); } int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key) { unsigned i; int r; - assert(a); assert(key); + if (!a) + return 0; + for (i = 0; i < a->n_rrs; i++) { - r = dns_resource_key_match_rr(key, a->rrs[i]); + r = dns_resource_key_match_rr(key, a->items[i].rr); if (r < 0) return r; if (r > 0) @@ -139,27 +149,36 @@ int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key) { return 0; } +int dns_answer_match_soa(DnsResourceKey *key, DnsResourceKey *soa) { + if (soa->class != DNS_CLASS_IN) + return 0; + + if (soa->type != DNS_TYPE_SOA) + return 0; + + if (!dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa))) + return 0; + + return 1; +} + int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **ret) { unsigned i; - assert(a); assert(key); assert(ret); + if (!a) + return 0; + /* For a SOA record we can never find a matching SOA record */ if (key->type == DNS_TYPE_SOA) return 0; for (i = 0; i < a->n_rrs; i++) { - if (a->rrs[i]->key->class != DNS_CLASS_IN) - continue; - - if (a->rrs[i]->key->type != DNS_TYPE_SOA) - continue; - - if (dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(a->rrs[i]->key))) { - *ret = a->rrs[i]; + if (dns_answer_match_soa(key, a->items[i].rr->key)) { + *ret = a->items[i].rr; return 1; } } @@ -184,7 +203,7 @@ DnsAnswer *dns_answer_merge(DnsAnswer *a, DnsAnswer *b) { if (a) { for (i = 0; i < a->n_rrs; i++) { - r = dns_answer_add(ret, a->rrs[i]); + r = dns_answer_add(ret, a->items[i].rr, a->items[i].ifindex); if (r < 0) return NULL; } @@ -192,7 +211,7 @@ DnsAnswer *dns_answer_merge(DnsAnswer *a, DnsAnswer *b) { if (b) { for (i = 0; i < b->n_rrs; i++) { - r = dns_answer_add(ret, b->rrs[i]); + r = dns_answer_add(ret, b->items[i].rr, b->items[i].ifindex); if (r < 0) return NULL; } @@ -205,9 +224,11 @@ DnsAnswer *dns_answer_merge(DnsAnswer *a, DnsAnswer *b) { } void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) { - DnsResourceRecord **rrs; + DnsAnswerItem *items; unsigned i, start, end; - assert(a); + + if (!a) + return; if (a->n_rrs <= 1) return; @@ -218,19 +239,51 @@ void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) { /* RFC 4795, Section 2.6 suggests we should order entries * depending on whether the sender is a link-local address. */ - rrs = newa(DnsResourceRecord*, a->n_rrs); + items = newa(DnsAnswerItem, a->n_rrs); for (i = 0; i < a->n_rrs; i++) { - if (a->rrs[i]->key->class == DNS_CLASS_IN && - ((a->rrs[i]->key->type == DNS_TYPE_A && in_addr_is_link_local(AF_INET, (union in_addr_union*) &a->rrs[i]->a.in_addr) != prefer_link_local) || - (a->rrs[i]->key->type == DNS_TYPE_AAAA && in_addr_is_link_local(AF_INET6, (union in_addr_union*) &a->rrs[i]->aaaa.in6_addr) != prefer_link_local))) + if (a->items[i].rr->key->class == DNS_CLASS_IN && + ((a->items[i].rr->key->type == DNS_TYPE_A && in_addr_is_link_local(AF_INET, (union in_addr_union*) &a->items[i].rr->a.in_addr) != prefer_link_local) || + (a->items[i].rr->key->type == DNS_TYPE_AAAA && in_addr_is_link_local(AF_INET6, (union in_addr_union*) &a->items[i].rr->aaaa.in6_addr) != prefer_link_local))) /* Order address records that are are not preferred to the end of the array */ - rrs[end--] = a->rrs[i]; + items[end--] = a->items[i]; else /* Order all other records to the beginning of the array */ - rrs[start++] = a->rrs[i]; + items[start++] = a->items[i]; } assert(start == end+1); - memcpy(a->rrs, rrs, sizeof(DnsResourceRecord*) * a->n_rrs); + memcpy(a->items, items, sizeof(DnsAnswerItem) * a->n_rrs); +} + +int dns_answer_reserve(DnsAnswer **a, unsigned n_free) { + DnsAnswer *n; + + if (n_free <= 0) + return 0; + + if (*a) { + unsigned ns; + + if ((*a)->n_ref > 1) + return -EBUSY; + + ns = (*a)->n_rrs + n_free; + + if ((*a)->n_allocated >= ns) + return 0; + + n = realloc(*a, offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * ns); + if (!n) + return -ENOMEM; + + n->n_allocated = ns; + } else { + n = dns_answer_new(n_free); + if (!n) + return -ENOMEM; + } + + *a = n; + return 0; } diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h index af3e462ed5..044d73b19c 100644 --- a/src/resolve/resolved-dns-answer.h +++ b/src/resolve/resolved-dns-answer.h @@ -22,27 +22,39 @@ ***/ typedef struct DnsAnswer DnsAnswer; +typedef struct DnsAnswerItem DnsAnswerItem; #include "resolved-dns-rr.h" -/* A simple array of resource records */ +/* A simple array of resource records. We keep track of the + * originating ifindex for each RR where that makes sense, so that we + * can qualify A and AAAA RRs referring to a local link with the + * right ifindex. */ + +struct DnsAnswerItem { + DnsResourceRecord *rr; + int ifindex; +}; struct DnsAnswer { unsigned n_ref; unsigned n_rrs, n_allocated; - DnsResourceRecord* rrs[0]; + DnsAnswerItem items[0]; }; DnsAnswer *dns_answer_new(unsigned n); DnsAnswer *dns_answer_ref(DnsAnswer *a); DnsAnswer *dns_answer_unref(DnsAnswer *a); -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr); +int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex); int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl); int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key); +int dns_answer_match_soa(DnsResourceKey *key, DnsResourceKey *soa); int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **ret); DnsAnswer *dns_answer_merge(DnsAnswer *a, DnsAnswer *b); void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local); +int dns_answer_reserve(DnsAnswer **a, unsigned n_free); + DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index be52891681..ab13636bc1 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -95,14 +95,19 @@ void dns_cache_flush(DnsCache *c) { c->by_expiry = prioq_free(c->by_expiry); } -static void dns_cache_remove(DnsCache *c, DnsResourceKey *key) { +static bool dns_cache_remove(DnsCache *c, DnsResourceKey *key) { DnsCacheItem *i; + bool exist = false; assert(c); assert(key); - while ((i = hashmap_get(c->by_key, key))) + while ((i = hashmap_get(c->by_key, key))) { dns_cache_item_remove_and_free(c, i); + exist = true; + } + + return exist; } static void dns_cache_make_space(DnsCache *c, unsigned add) { @@ -152,7 +157,7 @@ void dns_cache_prune(DnsCache *c) { break; if (t <= 0) - t = now(CLOCK_BOOTTIME); + t = now(clock_boottime_or_monotonic()); if (i->until > t) break; @@ -236,12 +241,11 @@ static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsReso i->type = DNS_CACHE_POSITIVE; - if (!i->by_key_prev) { + if (!i->by_key_prev) /* We are the first item in the list, we need to * update the key used in the hashmap */ assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0); - } dns_resource_record_ref(rr); dns_resource_record_unref(i->rr); @@ -263,6 +267,7 @@ static int dns_cache_put_positive( const union in_addr_union *owner_address) { _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; + _cleanup_free_ char *key_str = NULL; DnsCacheItem *existing; int r; @@ -272,7 +277,15 @@ static int dns_cache_put_positive( /* New TTL is 0? Delete the entry... */ if (rr->ttl <= 0) { - dns_cache_remove(c, rr->key); + r = dns_resource_key_to_string(rr->key, &key_str); + if (r < 0) + return r; + + if (dns_cache_remove(c, rr->key)) + log_debug("Removed zero TTL entry from cache: %s", key_str); + else + log_debug("Not caching zero TTL cache entry: %s", key_str); + return 0; } @@ -311,6 +324,12 @@ static int dns_cache_put_positive( if (r < 0) return r; + r = dns_resource_key_to_string(i->key, &key_str); + if (r < 0) + return r; + + log_debug("Added cache entry for %s", key_str); + i = NULL; return 0; } @@ -325,6 +344,7 @@ static int dns_cache_put_negative( const union in_addr_union *owner_address) { _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; + _cleanup_free_ char *key_str = NULL; int r; assert(c); @@ -337,8 +357,15 @@ static int dns_cache_put_negative( return 0; if (key->type == DNS_TYPE_ANY) return 0; - if (soa_ttl <= 0) + if (soa_ttl <= 0) { + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("Not caching negative entry with zero SOA TTL: %s", key_str); + return 0; + } if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) return 0; @@ -364,13 +391,19 @@ static int dns_cache_put_negative( if (r < 0) return r; + r = dns_resource_key_to_string(i->key, &key_str); + if (r < 0) + return r; + + log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str); + i = NULL; return 0; } int dns_cache_put( DnsCache *c, - DnsQuestion *q, + DnsResourceKey *key, int rcode, DnsAnswer *answer, unsigned max_rrs, @@ -378,22 +411,23 @@ int dns_cache_put( int owner_family, const union in_addr_union *owner_address) { - unsigned i; + DnsResourceRecord *soa = NULL; + unsigned cache_keys, i; int r; assert(c); - assert(q); - /* First, delete all matching old RRs, so that we only keep - * complete by_key in place. */ - for (i = 0; i < q->n_keys; i++) - dns_cache_remove(c, q->keys[i]); + if (key) { + /* First, if we were passed a key, delete all matching old RRs, + * so that we only keep complete by_key in place. */ + dns_cache_remove(c, key); + } if (!answer) return 0; for (i = 0; i < answer->n_rrs; i++) - dns_cache_remove(c, answer->rrs[i]->key); + dns_cache_remove(c, answer->items[i].rr->key); /* We only care for positive replies and NXDOMAINs, on all * other replies we will simply flush the respective entries, @@ -402,98 +436,171 @@ int dns_cache_put( if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) return 0; + cache_keys = answer->n_rrs; + + if (key) + cache_keys ++; + /* Make some space for our new entries */ - dns_cache_make_space(c, answer->n_rrs + q->n_keys); + dns_cache_make_space(c, cache_keys); if (timestamp <= 0) - timestamp = now(CLOCK_BOOTTIME); + timestamp = now(clock_boottime_or_monotonic()); /* 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->rrs[i], timestamp, owner_family, owner_address); + r = dns_cache_put_positive(c, answer->items[i].rr, timestamp, owner_family, owner_address); if (r < 0) goto fail; } - /* Third, add in negative entries for all keys with no RR */ - for (i = 0; i < q->n_keys; i++) { - DnsResourceRecord *soa = NULL; + if (!key) + return 0; + + /* Third, add in negative entries if the key has no RR */ + r = dns_answer_contains(answer, key); + if (r < 0) + goto fail; + if (r > 0) + return 0; - r = dns_answer_contains(answer, q->keys[i]); - if (r < 0) - goto fail; - if (r > 0) - continue; + /* See https://tools.ietf.org/html/rfc2308, which + * say that a matching SOA record in the packet + * is used to to enable negative caching. */ - r = dns_answer_find_soa(answer, q->keys[i], &soa); - if (r < 0) - goto fail; - if (r == 0) - continue; + r = dns_answer_find_soa(answer, key, &soa); + if (r < 0) + goto fail; + if (r == 0) + return 0; - r = dns_cache_put_negative(c, q->keys[i], rcode, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address); - if (r < 0) - goto fail; + /* Also, if the requested key is an alias, the negative response should + be cached for each name in the redirect chain. Any CNAME record in + the response is from the redirection chain, though only the final one + is guaranteed to be included. This means that we cannot verify the + chain and that we need to cache them all as it may be incomplete. */ + for (i = 0; i < answer->n_rrs; i++) { + DnsResourceRecord *answer_rr = answer->items[i].rr; + + if (answer_rr->key->type == DNS_TYPE_CNAME) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *canonical_key = NULL; + + canonical_key = dns_resource_key_new_redirect(key, answer_rr); + if (!canonical_key) + goto fail; + + /* Let's not add negative cache entries for records outside the current zone. */ + if (!dns_answer_match_soa(canonical_key, soa->key)) + continue; + + r = dns_cache_put_negative(c, canonical_key, rcode, 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); + if (r < 0) + goto fail; + return 0; fail: /* Adding all RRs failed. Let's clean up what we already * added, just in case */ - for (i = 0; i < q->n_keys; i++) - dns_cache_remove(c, q->keys[i]); + if (key) + dns_cache_remove(c, key); + for (i = 0; i < answer->n_rrs; i++) - dns_cache_remove(c, answer->rrs[i]->key); + dns_cache_remove(c, answer->items[i].rr->key); return r; } -int dns_cache_lookup(DnsCache *c, DnsQuestion *q, int *rcode, DnsAnswer **ret) { +static DnsCacheItem *dns_cache_get_by_key_follow_cname(DnsCache *c, DnsResourceKey *k) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *cname_key = NULL; + DnsCacheItem *i, *j; + + assert(c); + assert(k); + + i = hashmap_get(c->by_key, k); + if (i || k->type == DNS_TYPE_CNAME) + return i; + + /* check if we have a CNAME record instead */ + cname_key = dns_resource_key_new_cname(k); + if (!cname_key) + return NULL; + + j = hashmap_get(c->by_key, cname_key); + if (j) + return j; + + return i; +} + +int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret) { _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - unsigned i, n = 0; + unsigned n = 0; int r; bool nxdomain = false; + _cleanup_free_ char *key_str = NULL; + DnsCacheItem *j, *first; assert(c); - assert(q); + assert(key); assert(rcode); assert(ret); - if (q->n_keys <= 0) { + if (key->type == DNS_TYPE_ANY || + key->class == DNS_CLASS_ANY) { + + /* If we have ANY lookups we simply refresh */ + + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("Ignoring cache for ANY lookup: %s", key_str); + *ret = NULL; - *rcode = 0; + *rcode = DNS_RCODE_SUCCESS; return 0; } - for (i = 0; i < q->n_keys; i++) { - DnsCacheItem *j; + first = dns_cache_get_by_key_follow_cname(c, key); + if (!first) { + /* If one question cannot be answered we need to refresh */ - if (q->keys[i]->type == DNS_TYPE_ANY || - q->keys[i]->class == DNS_CLASS_ANY) { - /* If we have ANY lookups we simply refresh */ - *ret = NULL; - *rcode = 0; - return 0; - } + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; - j = hashmap_get(c->by_key, q->keys[i]); - if (!j) { - /* If one question cannot be answered we need to refresh */ - *ret = NULL; - *rcode = 0; - return 0; - } + log_debug("Cache miss for %s", key_str); - LIST_FOREACH(by_key, j, j) { - if (j->rr) - n++; - else if (j->type == DNS_CACHE_NXDOMAIN) - nxdomain = true; - } + *ret = NULL; + *rcode = DNS_RCODE_SUCCESS; + return 0; } + LIST_FOREACH(by_key, j, first) { + if (j->rr) + n++; + else if (j->type == DNS_CACHE_NXDOMAIN) + nxdomain = true; + } + + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("%s cache hit for %s", + nxdomain ? "NXDOMAIN" : + n > 0 ? "Positive" : "NODATA", + key_str); + if (n <= 0) { *ret = NULL; *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; @@ -504,17 +611,13 @@ int dns_cache_lookup(DnsCache *c, DnsQuestion *q, int *rcode, DnsAnswer **ret) { if (!answer) return -ENOMEM; - for (i = 0; i < q->n_keys; i++) { - DnsCacheItem *j; + LIST_FOREACH(by_key, j, first) { + if (!j->rr) + continue; - j = hashmap_get(c->by_key, q->keys[i]); - LIST_FOREACH(by_key, j, j) { - if (j->rr) { - r = dns_answer_add(answer, j->rr); - if (r < 0) - return r; - } - } + r = dns_answer_add(answer, j->rr, 0); + if (r < 0) + return r; } *ret = answer; @@ -559,3 +662,54 @@ int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_ /* There's a conflict */ return 1; } + +void dns_cache_dump(DnsCache *cache, FILE *f) { + Iterator iterator; + DnsCacheItem *i; + int r; + + if (!cache) + return; + + if (!f) + f = stdout; + + HASHMAP_FOREACH(i, cache->by_key, iterator) { + DnsCacheItem *j; + + LIST_FOREACH(by_key, j, i) { + _cleanup_free_ char *t = NULL; + + fputc('\t', f); + + if (j->rr) { + r = dns_resource_record_to_string(j->rr, &t); + if (r < 0) { + log_oom(); + continue; + } + + fputs(t, f); + fputc('\n', f); + } else { + r = dns_resource_key_to_string(j->key, &t); + if (r < 0) { + log_oom(); + continue; + } + + fputs(t, f); + fputs(" -- ", f); + fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f); + fputc('\n', f); + } + } + } +} + +bool dns_cache_is_empty(DnsCache *cache) { + if (!cache) + return true; + + return hashmap_isempty(cache->by_key); +} diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h index 8a9b3d459d..60cf6a4784 100644 --- a/src/resolve/resolved-dns-cache.h +++ b/src/resolve/resolved-dns-cache.h @@ -39,7 +39,10 @@ typedef struct DnsCache { void dns_cache_flush(DnsCache *c); void dns_cache_prune(DnsCache *c); -int dns_cache_put(DnsCache *c, DnsQuestion *q, 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, DnsQuestion *q, int *rcode, DnsAnswer **answer); +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_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address); + +void dns_cache_dump(DnsCache *cache, FILE *f); +bool dns_cache_is_empty(DnsCache *cache); diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 649e8b74e1..bebd1ee4a6 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -388,14 +388,21 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start return 0; } -int dns_packet_append_name(DnsPacket *p, const char *name, - bool allow_compression, size_t *start) { +int dns_packet_append_name( + DnsPacket *p, + const char *name, + bool allow_compression, + size_t *start) { + size_t saved_size; int r; assert(p); assert(name); + if (p->refuse_compression) + allow_compression = false; + saved_size = p->size; while (*name) { @@ -508,23 +515,21 @@ static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t l assert(p); assert(types); + assert(length > 0); saved_size = p->size; - if (length != 0) { - - r = dns_packet_append_uint8(p, window, NULL); - if (r < 0) - goto fail; + r = dns_packet_append_uint8(p, window, NULL); + if (r < 0) + goto fail; - r = dns_packet_append_uint8(p, length, NULL); - if (r < 0) - goto fail; + r = dns_packet_append_uint8(p, length, NULL); + if (r < 0) + goto fail; - r = dns_packet_append_blob(p, types, length, NULL); - if (r < 0) - goto fail; - } + r = dns_packet_append_blob(p, types, length, NULL); + if (r < 0) + goto fail; if (start) *start = saved_size; @@ -538,7 +543,7 @@ fail: static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { Iterator i; uint8_t window = 0; - uint8_t len = 0; + uint8_t entry = 0; uint8_t bitmaps[32] = {}; unsigned n; size_t saved_size; @@ -550,30 +555,24 @@ static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { saved_size = p->size; BITMAP_FOREACH(n, types, i) { - uint8_t entry; - assert(n <= 0xffff); - if ((n << 8) != window) { - r = dns_packet_append_type_window(p, window, len, bitmaps, NULL); + if ((n >> 8) != window && bitmaps[entry / 8] != 0) { + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); if (r < 0) goto fail; - if (len > 0) { - len = 0; - zero(bitmaps); - } + zero(bitmaps); } - window = n << 8; - len ++; + window = n >> 8; entry = n & 255; bitmaps[entry / 8] |= 1 << (7 - (entry % 8)); } - r = dns_packet_append_type_window(p, window, len, bitmaps, NULL); + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); if (r < 0) goto fail; @@ -1061,8 +1060,12 @@ fail: return r; } -int dns_packet_read_name(DnsPacket *p, char **_ret, - bool allow_compression, size_t *start) { +int dns_packet_read_name( + DnsPacket *p, + char **_ret, + bool allow_compression, + size_t *start) { + size_t saved_rindex, after_rindex = 0, jump_barrier; _cleanup_free_ char *ret = NULL; size_t n = 0, allocated = 0; @@ -1072,6 +1075,9 @@ int dns_packet_read_name(DnsPacket *p, char **_ret, assert(p); assert(_ret); + if (p->refuse_compression) + allow_compression = false; + saved_rindex = p->rindex; jump_barrier = p->rindex; @@ -1164,6 +1170,7 @@ static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *sta uint8_t window; uint8_t length; const uint8_t *bitmap; + uint8_t bit = 0; unsigned i; bool found = false; size_t saved_rindex; @@ -1195,10 +1202,10 @@ static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *sta for (i = 0; i < length; i++) { uint8_t bitmask = 1 << 7; - uint8_t bit = 0; if (!bitmap[i]) { found = false; + bit += 8; continue; } @@ -1673,8 +1680,12 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { if (r < 0) goto fail; - /* NSEC RRs with empty bitmpas makes no sense, but the RFC does not explicitly forbid them - so we allow it */ + /* The types bitmap must contain at least the NSEC record itself, so an empty bitmap means + something went wrong */ + if (bitmap_isclear(rr->nsec.types)) { + r = -EBADMSG; + goto fail; + } break; @@ -1715,7 +1726,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { if (r < 0) goto fail; - r = dns_packet_read_type_windows(p, &rr->nsec.types, offset + rdlength - p->rindex, NULL); + r = dns_packet_read_type_windows(p, &rr->nsec3.types, offset + rdlength - p->rindex, NULL); if (r < 0) goto fail; @@ -1798,7 +1809,7 @@ int dns_packet_extract(DnsPacket *p) { if (r < 0) goto finish; - r = dns_answer_add(answer, rr); + r = dns_answer_add(answer, rr, p->ifindex); if (r < 0) goto finish; } diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index 58559c85df..fbbabaf232 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -78,7 +78,7 @@ struct DnsPacket { DnsQuestion *question; DnsAnswer *answer; - /* Packet reception meta data */ + /* Packet reception metadata */ int ifindex; int family, ipproto; union in_addr_union sender, destination; @@ -86,6 +86,7 @@ struct DnsPacket { uint32_t ttl; bool extracted; + bool refuse_compression; }; static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { @@ -120,15 +121,15 @@ static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { #define DNS_PACKET_ARCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->arcount) #define DNS_PACKET_MAKE_FLAGS(qr, opcode, aa, tc, rd, ra, ad, cd, rcode) \ - (((uint16_t) !!qr << 15) | \ - ((uint16_t) (opcode & 15) << 11) | \ - ((uint16_t) !!aa << 10) | \ - ((uint16_t) !!tc << 9) | \ - ((uint16_t) !!rd << 8) | \ - ((uint16_t) !!ra << 7) | \ - ((uint16_t) !!ad << 5) | \ - ((uint16_t) !!cd << 4) | \ - ((uint16_t) (rcode & 15))) + (((uint16_t) !!(qr) << 15) | \ + ((uint16_t) ((opcode) & 15) << 11) | \ + ((uint16_t) !!(aa) << 10) | /* on LLMNR: c */ \ + ((uint16_t) !!(tc) << 9) | \ + ((uint16_t) !!(rd) << 8) | /* on LLMNR: t */ \ + ((uint16_t) !!(ra) << 7) | \ + ((uint16_t) !!(ad) << 5) | \ + ((uint16_t) !!(cd) << 4) | \ + ((uint16_t) ((rcode) & 15))) static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) { return @@ -238,11 +239,16 @@ static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family) /* Converts a protocol + family into a flags field as used in queries */ - if (protocol == DNS_PROTOCOL_DNS) + switch (protocol) { + case DNS_PROTOCOL_DNS: return SD_RESOLVED_DNS; - if (protocol == DNS_PROTOCOL_LLMNR) + case DNS_PROTOCOL_LLMNR: return family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4; + default: + break; + } + return 0; } diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 418d9721ef..4b1d18b2ef 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -19,6 +19,9 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include "hostname-util.h" +#include "dns-domain.h" +#include "local-addresses.h" #include "resolved-dns-query.h" @@ -135,31 +138,20 @@ static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { } static int dns_query_add_transaction(DnsQuery *q, DnsScope *s, DnsResourceKey *key) { - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; DnsTransaction *t; int r; assert(q); assert(s); + assert(key); r = set_ensure_allocated(&q->transactions, NULL); if (r < 0) return r; - if (key) { - question = dns_question_new(1); - if (!question) - return -ENOMEM; - - r = dns_question_add(question, key); - if (r < 0) - return r; - } else - question = dns_question_ref(q->question); - - t = dns_scope_find_transaction(s, question, true); + t = dns_scope_find_transaction(s, key, true); if (!t) { - r = dns_transaction_new(&t, s, question); + r = dns_transaction_new(&t, s, key); if (r < 0) return r; } @@ -186,32 +178,434 @@ gc: } static int dns_query_add_transaction_split(DnsQuery *q, DnsScope *s) { + unsigned i; int r; assert(q); assert(s); - if (s->protocol == DNS_PROTOCOL_MDNS) { - r = dns_query_add_transaction(q, s, NULL); + /* Create one transaction per question key */ + + for (i = 0; i < q->question->n_keys; i++) { + r = dns_query_add_transaction(q, s, q->question->keys[i]); if (r < 0) return r; - } else { - unsigned i; + } + + return 0; +} - /* On DNS and LLMNR we can only send a single - * question per datagram, hence issue multiple - * transactions. */ +static int SYNTHESIZE_IFINDEX(int ifindex) { - for (i = 0; i < q->question->n_keys; i++) { - r = dns_query_add_transaction(q, s, q->question->keys[i]); - if (r < 0) - return r; + /* When the caller asked for resolving on a specific + * interface, we synthesize the answer for that + * interface. However, if nothing specific was claimed and we + * only return localhost RRs, we synthesize the answer for + * localhost. */ + + if (ifindex > 0) + return ifindex; + + return LOOPBACK_IFINDEX; +} + +static int SYNTHESIZE_FAMILY(uint64_t flags) { + + /* Picks an address family depending on set flags. This is + * purely for synthesized answers, where the family we return + * for the reply should match what was requested in the + * question, even though we are synthesizing the answer + * here. */ + + if (!(flags & SD_RESOLVED_DNS)) { + if (flags & SD_RESOLVED_LLMNR_IPV4) + return AF_INET; + if (flags & SD_RESOLVED_LLMNR_IPV6) + return AF_INET6; + } + + return AF_UNSPEC; +} + +static DnsProtocol SYNTHESIZE_PROTOCOL(uint64_t flags) { + + /* Similar as SYNTHESIZE_FAMILY() but does this for the + * protocol. If resolving via DNS was requested, we claim it + * was DNS. Similar, if nothing specific was + * requested. However, if only resolving via LLMNR was + * requested we return that. */ + + if (flags & SD_RESOLVED_DNS) + return DNS_PROTOCOL_DNS; + if (flags & SD_RESOLVED_LLMNR) + return DNS_PROTOCOL_LLMNR; + + return DNS_PROTOCOL_DNS; +} + +static int dns_type_to_af(uint16_t t) { + switch (t) { + + case DNS_TYPE_A: + return AF_INET; + + case DNS_TYPE_AAAA: + return AF_INET6; + + case DNS_TYPE_ANY: + return AF_UNSPEC; + + default: + return -EINVAL; + } +} + +static int synthesize_localhost_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) { + int r; + + assert(q); + assert(key); + assert(answer); + + r = dns_answer_reserve(answer, 2); + if (r < 0) + return r; + + if (IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_ANY)) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, DNS_RESOURCE_KEY_NAME(key)); + if (!rr) + return -ENOMEM; + + rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK); + + r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex)); + if (r < 0) + return r; + } + + if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, DNS_RESOURCE_KEY_NAME(key)); + if (!rr) + return -ENOMEM; + + rr->aaaa.in6_addr = in6addr_loopback; + + r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex)); + if (r < 0) + return r; + } + + return 0; +} + +static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, from); + if (!rr) + return -ENOMEM; + + rr->ptr.name = strdup(to); + if (!rr->ptr.name) + return -ENOMEM; + + return dns_answer_add(*answer, rr, ifindex); +} + +static int synthesize_localhost_ptr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) { + int r; + + assert(q); + assert(key); + assert(answer); + + r = dns_answer_reserve(answer, 1); + if (r < 0) + return r; + + if (IN_SET(key->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) { + r = answer_add_ptr(answer, DNS_RESOURCE_KEY_NAME(key), "localhost", SYNTHESIZE_IFINDEX(q->ifindex)); + if (r < 0) + return r; + } + + return 0; +} + +static int answer_add_addresses_rr( + DnsAnswer **answer, + const char *name, + struct local_address *addresses, + unsigned n_addresses) { + + unsigned j; + int r; + + assert(answer); + assert(name); + + r = dns_answer_reserve(answer, n_addresses); + if (r < 0) + return r; + + for (j = 0; j < n_addresses; j++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + r = dns_resource_record_new_address(&rr, addresses[j].family, &addresses[j].address, name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, addresses[j].ifindex); + if (r < 0) + return r; + } + + return 0; +} + +static int answer_add_addresses_ptr( + DnsAnswer **answer, + const char *name, + struct local_address *addresses, + unsigned n_addresses, + int af, const union in_addr_union *match) { + + unsigned j; + int r; + + assert(answer); + assert(name); + + for (j = 0; j < n_addresses; j++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + if (af != AF_UNSPEC) { + + if (addresses[j].family != af) + continue; + + if (match && !in_addr_equal(af, match, &addresses[j].address)) + continue; } + + r = dns_answer_reserve(answer, 1); + if (r < 0) + return r; + + r = dns_resource_record_new_reverse(&rr, addresses[j].family, &addresses[j].address, name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, addresses[j].ifindex); + if (r < 0) + return r; } return 0; } +static int synthesize_system_hostname_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n = 0, af; + + assert(q); + assert(key); + assert(answer); + + af = dns_type_to_af(key->type); + if (af >= 0) { + n = local_addresses(q->manager->rtnl, q->ifindex, af, &addresses); + if (n < 0) + return n; + + if (n == 0) { + struct local_address buffer[2]; + + /* If we have no local addresses then use ::1 + * and 127.0.0.2 as local ones. */ + + if (af == AF_INET || af == AF_UNSPEC) + buffer[n++] = (struct local_address) { + .family = AF_INET, + .ifindex = SYNTHESIZE_IFINDEX(q->ifindex), + .address.in.s_addr = htobe32(0x7F000002), + }; + + if (af == AF_INET6 || af == AF_UNSPEC) + buffer[n++] = (struct local_address) { + .family = AF_INET6, + .ifindex = SYNTHESIZE_IFINDEX(q->ifindex), + .address.in6 = in6addr_loopback, + }; + + return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), buffer, n); + } + } + + return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); +} + +static int synthesize_system_hostname_ptr(DnsQuery *q, int af, const union in_addr_union *address, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n, r; + + assert(q); + assert(address); + assert(answer); + + if (af == AF_INET && address->in.s_addr == htobe32(0x7F000002)) { + + /* Always map the IPv4 address 127.0.0.2 to the local + * hostname, in addition to "localhost": */ + + r = dns_answer_reserve(answer, 3); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->llmnr_hostname, SYNTHESIZE_IFINDEX(q->ifindex)); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->mdns_hostname, SYNTHESIZE_IFINDEX(q->ifindex)); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", SYNTHESIZE_IFINDEX(q->ifindex)); + if (r < 0) + return r; + + return 0; + } + + n = local_addresses(q->manager->rtnl, q->ifindex, af, &addresses); + if (n < 0) + return n; + + r = answer_add_addresses_ptr(answer, q->manager->llmnr_hostname, addresses, n, af, address); + if (r < 0) + return r; + + return answer_add_addresses_ptr(answer, q->manager->mdns_hostname, addresses, n, af, address); +} + +static int synthesize_gateway_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n = 0, af; + + assert(q); + assert(key); + assert(answer); + + af = dns_type_to_af(key->type); + if (af >= 0) { + n = local_gateways(q->manager->rtnl, q->ifindex, af, &addresses); + if (n < 0) + return n; + } + + return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); +} + +static int synthesize_gateway_ptr(DnsQuery *q, int af, const union in_addr_union *address, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n; + + assert(q); + assert(address); + assert(answer); + + n = local_gateways(q->manager->rtnl, q->ifindex, af, &addresses); + if (n < 0) + return n; + + return answer_add_addresses_ptr(answer, "gateway", addresses, n, af, address); +} + +static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + unsigned i; + int r; + + assert(q); + assert(state); + + /* Tries to synthesize localhost RR replies where appropriate */ + + if (!IN_SET(*state, + DNS_TRANSACTION_FAILURE, + DNS_TRANSACTION_NO_SERVERS, + DNS_TRANSACTION_TIMEOUT, + DNS_TRANSACTION_ATTEMPTS_MAX_REACHED)) + return 0; + + for (i = 0; i < q->question->n_keys; i++) { + union in_addr_union address; + const char *name; + int af; + + if (q->question->keys[i]->class != DNS_CLASS_IN && + q->question->keys[i]->class != DNS_CLASS_ANY) + continue; + + name = DNS_RESOURCE_KEY_NAME(q->question->keys[i]); + + if (is_localhost(name)) { + + r = synthesize_localhost_rr(q, q->question->keys[i], &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize localhost RRs: %m"); + + } else if (manager_is_own_hostname(q->manager, name)) { + + r = synthesize_system_hostname_rr(q, q->question->keys[i], &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize system hostname RRs: %m"); + + } else if (is_gateway_hostname(name)) { + + r = synthesize_gateway_rr(q, q->question->keys[i], &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize gateway RRs: %m"); + + } else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 && dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0) || + dns_name_equal(name, "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) { + + r = synthesize_localhost_ptr(q, q->question->keys[i], &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m"); + + } else if (dns_name_address(name, &af, &address) > 0) { + + r = synthesize_system_hostname_ptr(q, af, &address, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize system hostname PTR RR: %m"); + + r = synthesize_gateway_ptr(q, af, &address, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize gateway hostname PTR RR: %m"); + } + } + + if (!answer) + return 0; + + dns_answer_unref(q->answer); + q->answer = answer; + answer = NULL; + + q->answer_family = SYNTHESIZE_FAMILY(q->flags); + q->answer_protocol = SYNTHESIZE_PROTOCOL(q->flags); + q->answer_rcode = DNS_RCODE_SUCCESS; + + *state = DNS_TRANSACTION_SUCCESS; + + return 1; +} + int dns_query_go(DnsQuery *q) { DnsScopeMatch found = DNS_SCOPE_NO; DnsScope *s, *first = NULL; @@ -253,8 +647,13 @@ int dns_query_go(DnsQuery *q) { } } - if (found == DNS_SCOPE_NO) - return -ESRCH; + if (found == DNS_SCOPE_NO) { + DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; + + dns_query_synthesize_reply(q, &state); + dns_query_complete(q, state); + return 1; + } r = dns_query_add_transaction_split(q, first); if (r < 0) @@ -276,7 +675,6 @@ int dns_query_go(DnsQuery *q) { } q->answer = dns_answer_unref(q->answer); - q->answer_ifindex = 0; q->answer_rcode = 0; q->answer_family = AF_UNSPEC; q->answer_protocol = _DNS_PROTOCOL_INVALID; @@ -423,15 +821,17 @@ void dns_query_ready(DnsQuery *q) { if (IN_SET(state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_FAILURE)) { q->answer = dns_answer_ref(answer); q->answer_rcode = rcode; - q->answer_ifindex = (scope && scope->link) ? scope->link->ifindex : 0; q->answer_protocol = scope ? scope->protocol : _DNS_PROTOCOL_INVALID; q->answer_family = scope ? scope->family : AF_UNSPEC; } + /* Try to synthesize a reply if we couldn't resolve something. */ + dns_query_synthesize_reply(q, &state); + dns_query_complete(q, state); } -int dns_query_cname_redirect(DnsQuery *q, const char *name) { +int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) { _cleanup_(dns_question_unrefp) DnsQuestion *nq = NULL; int r; @@ -440,7 +840,7 @@ int dns_query_cname_redirect(DnsQuery *q, const char *name) { if (q->n_cname_redirects > CNAME_MAX) return -ELOOP; - r = dns_question_cname_redirect(q->question, name, &nq); + r = dns_question_cname_redirect(q->question, cname, &nq); if (r < 0) return r; diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index 5a319f0a62..e7063d9678 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -45,7 +45,6 @@ struct DnsQuery { /* Discovered data */ DnsAnswer *answer; - int answer_ifindex; int answer_family; DnsProtocol answer_protocol; int answer_rcode; @@ -73,7 +72,7 @@ DnsQuery *dns_query_free(DnsQuery *q); int dns_query_go(DnsQuery *q); void dns_query_ready(DnsQuery *q); -int dns_query_cname_redirect(DnsQuery *q, const char *name); +int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname); int dns_query_bus_track(DnsQuery *q, sd_bus_message *m); diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c index 4d71f5e3d4..1507f22da0 100644 --- a/src/resolve/resolved-dns-question.c +++ b/src/resolve/resolved-dns-question.c @@ -68,9 +68,11 @@ int dns_question_add(DnsQuestion *q, DnsResourceKey *key) { unsigned i; int r; - assert(q); assert(key); + if (!q) + return -ENOSPC; + for (i = 0; i < q->n_keys; i++) { r = dns_resource_key_equal(q->keys[i], key); if (r < 0) @@ -90,9 +92,11 @@ int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr) { unsigned i; int r; - assert(q); assert(rr); + if (!q) + return 0; + for (i = 0; i < q->n_keys; i++) { r = dns_resource_key_match_rr(q->keys[i], rr); if (r != 0) @@ -106,9 +110,11 @@ int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr) { unsigned i; int r; - assert(q); assert(rr); + if (!q) + return 0; + for (i = 0; i < q->n_keys; i++) { r = dns_resource_key_match_cname(q->keys[i], rr); if (r != 0) @@ -123,7 +129,8 @@ int dns_question_is_valid(DnsQuestion *q) { unsigned i; int r; - assert(q); + if (!q) + return 0; if (q->n_keys <= 0) return 0; @@ -151,16 +158,19 @@ int dns_question_is_superset(DnsQuestion *q, DnsQuestion *other) { unsigned j; int r; - assert(q); - assert(other); - /* Checks if all keys in "other" are also contained in "q" */ + if (!other) + return 1; + for (j = 0; j < other->n_keys; j++) { DnsResourceKey *b = other->keys[j]; bool found = false; unsigned i; + if (!q) + return 0; + for (i = 0; i < q->n_keys; i++) { DnsResourceKey *a = q->keys[i]; @@ -188,18 +198,71 @@ int dns_question_is_superset(DnsQuestion *q, DnsQuestion *other) { return 1; } -int dns_question_cname_redirect(DnsQuestion *q, const char *name, DnsQuestion **ret) { +int dns_question_contains(DnsQuestion *a, DnsResourceKey *k) { + unsigned j; + int r; + + assert(k); + + if (!a) + return 0; + + for (j = 0; j < a->n_keys; j++) { + r = dns_resource_key_equal(a->keys[j], k); + if (r != 0) + return r; + } + + return 0; +} + +int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) { + unsigned j; + int r; + + if (!a) + return !b || b->n_keys == 0; + if (!b) + return a->n_keys == 0; + + /* Checks if all keys in a are also contained b, and vice versa */ + + for (j = 0; j < a->n_keys; j++) { + r = dns_question_contains(b, a->keys[j]); + if (r <= 0) + return r; + } + + for (j = 0; j < b->n_keys; j++) { + r = dns_question_contains(a, b->keys[j]); + if (r <= 0) + return r; + } + + return 1; +} + +int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) { _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL; bool same = true; unsigned i; int r; - assert(q); - assert(name); + assert(cname); assert(ret); + if (!q) { + n = dns_question_new(0); + if (!n) + return -ENOMEM; + + *ret = n; + n = 0; + return 0; + } + for (i = 0; i < q->n_keys; i++) { - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), name); + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), cname->cname.name); if (r < 0) return r; @@ -223,7 +286,7 @@ int dns_question_cname_redirect(DnsQuestion *q, const char *name, DnsQuestion ** for (i = 0; i < q->n_keys; i++) { _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; - k = dns_resource_key_new(q->keys[i]->class, q->keys[i]->type, name); + k = dns_resource_key_new_redirect(q->keys[i], cname); if (!k) return -ENOMEM; @@ -237,38 +300,3 @@ int dns_question_cname_redirect(DnsQuestion *q, const char *name, DnsQuestion ** return 1; } - -int dns_question_endswith(DnsQuestion *q, const char *suffix) { - unsigned i; - - assert(q); - assert(suffix); - - for (i = 0; i < q->n_keys; i++) { - int k; - - k = dns_name_endswith(DNS_RESOURCE_KEY_NAME(q->keys[i]), suffix); - if (k <= 0) - return k; - } - - return 1; -} - -int dns_question_extract_reverse_address(DnsQuestion *q, int *family, union in_addr_union *address) { - unsigned i; - - assert(q); - assert(family); - assert(address); - - for (i = 0; i < q->n_keys; i++) { - int k; - - k = dns_name_address(DNS_RESOURCE_KEY_NAME(q->keys[i]), family, address); - if (k != 0) - return k; - } - - return 0; -} diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h index 4ba2fe9f0e..13cd1f20f3 100644 --- a/src/resolve/resolved-dns-question.h +++ b/src/resolve/resolved-dns-question.h @@ -43,10 +43,9 @@ int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr); int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr); int dns_question_is_valid(DnsQuestion *q); int dns_question_is_superset(DnsQuestion *q, DnsQuestion *other); +int dns_question_contains(DnsQuestion *a, DnsResourceKey *k); +int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b); -int dns_question_cname_redirect(DnsQuestion *q, const char *name, DnsQuestion **ret); - -int dns_question_endswith(DnsQuestion *q, const char *suffix); -int dns_question_extract_reverse_address(DnsQuestion *q, int *family, union in_addr_union *address); +int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref); diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index ad7ca26cfe..2bc8cc1639 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -48,6 +48,19 @@ 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) { + assert(key); + assert(cname); + + return dns_resource_key_new(key->class, key->type, cname->cname.name); +} + DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name) { DnsResourceKey *k; @@ -133,15 +146,14 @@ int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRec return dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key)); } -static unsigned long dns_resource_key_hash_func(const void *i, const uint8_t hash_key[HASH_KEY_SIZE]) { +static void dns_resource_key_hash_func(const void *i, struct siphash *state) { const DnsResourceKey *k = i; - unsigned long ul; - ul = dns_name_hash_func(DNS_RESOURCE_KEY_NAME(k), hash_key); - ul = ul * hash_key[0] + ul + k->class; - ul = ul * hash_key[1] + ul + k->type; + assert(k); - return ul; + dns_name_hash_func(DNS_RESOURCE_KEY_NAME(k), state); + siphash24_compress(&k->class, sizeof(k->class), state); + siphash24_compress(&k->type, sizeof(k->type), state); } static int dns_resource_key_compare_func(const void *a, const void *b) { @@ -350,6 +362,36 @@ int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const u return 0; } +int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name) { + DnsResourceRecord *rr; + + assert(ret); + assert(address); + assert(family); + + if (family == AF_INET) { + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, name); + if (!rr) + return -ENOMEM; + + rr->a.in_addr = address->in; + + } else if (family == AF_INET6) { + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, name); + if (!rr) + return -ENOMEM; + + rr->aaaa.in6_addr = address->in6; + } else + return -EAFNOSUPPORT; + + *ret = rr; + + return 0; +} + int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b) { int r; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 0f40f3ceef..9e2207c0aa 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -177,6 +177,8 @@ 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_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname); 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); @@ -191,6 +193,7 @@ DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, c DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr); DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr); int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name); +int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name); int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b); int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 927a1ddc26..9e6f595a1b 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -34,6 +34,10 @@ #define MULTICAST_RATELIMIT_INTERVAL_USEC (1*USEC_PER_SEC) #define MULTICAST_RATELIMIT_BURST 1000 +/* After how much time to repeat LLMNR requests, see RFC 4795 Section 7 */ +#define MULTICAST_RESEND_TIMEOUT_MIN_USEC (100 * USEC_PER_MSEC) +#define MULTICAST_RESEND_TIMEOUT_MAX_USEC (1 * USEC_PER_SEC) + int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int family) { DnsScope *s; @@ -48,6 +52,7 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int s->link = l; s->protocol = protocol; s->family = family; + s->resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC; LIST_PREPEND(scopes, m->dns_scopes, s); @@ -73,8 +78,7 @@ DnsScope* dns_scope_free(DnsScope *s) { dns_scope_llmnr_membership(s, false); - while ((t = s->transactions)) { - + while ((t = hashmap_steal_first(s->transactions))) { /* Abort the transaction, but make sure it is not * freed while we still look at it */ @@ -85,6 +89,8 @@ DnsScope* dns_scope_free(DnsScope *s) { dns_transaction_free(t); } + hashmap_free(s->transactions); + while ((rr = ordered_hashmap_steal_first(s->conflict_queue))) dns_resource_record_unref(rr); @@ -125,6 +131,23 @@ void dns_scope_next_dns_server(DnsScope *s) { manager_next_dns_server(s->manager); } +void dns_scope_packet_received(DnsScope *s, usec_t rtt) { + assert(s); + + if (rtt > s->max_rtt) { + s->max_rtt = rtt; + s->resend_timeout = MIN(MAX(MULTICAST_RESEND_TIMEOUT_MIN_USEC, s->max_rtt * 2), + MULTICAST_RESEND_TIMEOUT_MAX_USEC); + } +} + +void dns_scope_packet_lost(DnsScope *s, usec_t usec) { + assert(s); + + if (s->resend_timeout <= usec) + s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC); +} + int dns_scope_emit(DnsScope *s, int fd, DnsPacket *p) { union in_addr_union addr; int ifindex = 0, r; @@ -143,7 +166,8 @@ int dns_scope_emit(DnsScope *s, int fd, DnsPacket *p) { } else mtu = manager_find_mtu(s->manager); - if (s->protocol == DNS_PROTOCOL_DNS) { + switch (s->protocol) { + case DNS_PROTOCOL_DNS: if (DNS_PACKET_QDCOUNT(p) > 1) return -EOPNOTSUPP; @@ -157,8 +181,9 @@ int dns_scope_emit(DnsScope *s, int fd, DnsPacket *p) { if (r < 0) return r; - } else if (s->protocol == DNS_PROTOCOL_LLMNR) { + break; + case DNS_PROTOCOL_LLMNR: if (DNS_PACKET_QDCOUNT(p) > 1) return -EOPNOTSUPP; @@ -182,8 +207,12 @@ int dns_scope_emit(DnsScope *s, int fd, DnsPacket *p) { r = manager_send(s->manager, fd, ifindex, family, &addr, port, p); if (r < 0) return r; - } else + + break; + + default: return -EAFNOSUPPORT; + } return 1; } @@ -303,45 +332,53 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family) & flags) == 0) return DNS_SCOPE_NO; - STRV_FOREACH(i, s->domains) - if (dns_name_endswith(domain, *i) > 0) - return DNS_SCOPE_YES; - if (dns_name_root(domain) != 0) return DNS_SCOPE_NO; - if (is_localhost(domain)) + /* Never resolve any loopback hostname or IP address via DNS, + * LLMNR or mDNS. Instead, always rely on synthesized RRs for + * these. */ + if (is_localhost(domain) || + dns_name_endswith(domain, "127.in-addr.arpa") > 0 || + 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; - if (s->protocol == DNS_PROTOCOL_DNS) { + STRV_FOREACH(i, s->domains) + if (dns_name_endswith(domain, *i) > 0) + return DNS_SCOPE_YES; + + switch (s->protocol) { + case DNS_PROTOCOL_DNS: if (dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 && dns_name_endswith(domain, "0.8.e.f.ip6.arpa") == 0 && dns_name_single_label(domain) == 0) return DNS_SCOPE_MAYBE; return DNS_SCOPE_NO; - } - if (s->protocol == DNS_PROTOCOL_MDNS) { - if (dns_name_endswith(domain, "254.169.in-addr.arpa") > 0 || - dns_name_endswith(domain, "0.8.e.f.ip6.arpa") > 0 || - (dns_name_endswith(domain, "local") > 0 && dns_name_equal(domain, "local") == 0)) + case DNS_PROTOCOL_MDNS: + if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || + (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || + (dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */ + dns_name_equal(domain, "local") == 0 && /* but not the single-label "local" name itself */ + manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via mDNS */ return DNS_SCOPE_MAYBE; return DNS_SCOPE_NO; - } - if (s->protocol == DNS_PROTOCOL_LLMNR) { - if (dns_name_endswith(domain, "in-addr.arpa") > 0 || - dns_name_endswith(domain, "ip6.arpa") > 0 || - (dns_name_single_label(domain) > 0 && - dns_name_equal(domain, "gateway") <= 0)) /* don't resolve "gateway" with LLMNR, let nss-myhostname handle this */ + case DNS_PROTOCOL_LLMNR: + if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || + (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || + (dns_name_single_label(domain) > 0 && /* only resolve single label names via LLMNR */ + !is_gateway_hostname(domain) && /* don't resolve "gateway" with LLMNR, let nss-myhostname handle this */ + manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via LLMNR */ return DNS_SCOPE_MAYBE; return DNS_SCOPE_NO; - } - assert_not_reached("Unknown scope protocol"); + default: + assert_not_reached("Unknown scope protocol"); + } } int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) { @@ -463,7 +500,7 @@ static int dns_scope_make_reply_packet( if (answer) { for (i = 0; i < answer->n_rrs; i++) { - r = dns_packet_append_rr(p, answer->rrs[i], NULL); + r = dns_packet_append_rr(p, answer->items[i].rr, NULL); if (r < 0) return r; } @@ -473,7 +510,7 @@ static int dns_scope_make_reply_packet( if (soa) { for (i = 0; i < soa->n_rrs; i++) { - r = dns_packet_append_rr(p, soa->rrs[i], NULL); + r = dns_packet_append_rr(p, soa->items[i].rr, NULL); if (r < 0) return r; } @@ -498,7 +535,7 @@ static void dns_scope_verify_conflicts(DnsScope *s, DnsPacket *p) { dns_zone_verify_conflicts(&s->zone, p->question->keys[n]); if (p->answer) for (n = 0; n < p->answer->n_rrs; n++) - dns_zone_verify_conflicts(&s->zone, p->answer->rrs[n]->key); + dns_zone_verify_conflicts(&s->zone, p->answer->items[n].rr->key); } void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) { @@ -587,30 +624,26 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) { } } -DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsQuestion *question, bool cache_ok) { +DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok) { DnsTransaction *t; assert(scope); - assert(question); - - /* Try to find an ongoing transaction that is a equal or a - * superset of the specified question */ - - LIST_FOREACH(transactions_by_scope, t, scope->transactions) { + assert(key); - /* Refuse reusing transactions that completed based on - * cached data instead of a real packet, if that's - * requested. */ - if (!cache_ok && - IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_FAILURE) && - !t->received) - continue; + /* Try to find an ongoing transaction that is a equal to the + * specified question */ + t = hashmap_get(scope->transactions, key); + if (!t) + return NULL; - if (dns_question_is_superset(t->question, question) > 0) - return t; - } + /* Refuse reusing transactions that completed based on cached + * data instead of a real packet, if that's requested. */ + if (!cache_ok && + IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_FAILURE) && + !t->received) + return NULL; - return NULL; + return t; } static int dns_scope_make_conflict_packet( @@ -766,16 +799,48 @@ void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p) { /* Check for conflicts against the local zone. If we * found one, we won't check any further */ - r = dns_zone_check_conflicts(&scope->zone, p->answer->rrs[i]); + r = dns_zone_check_conflicts(&scope->zone, p->answer->items[i].rr); if (r != 0) continue; /* Check for conflicts against the local cache. If so, * send out an advisory query, to inform everybody */ - r = dns_cache_check_conflicts(&scope->cache, p->answer->rrs[i], p->family, &p->sender); + r = dns_cache_check_conflicts(&scope->cache, p->answer->items[i].rr, p->family, &p->sender); if (r <= 0) continue; - dns_scope_notify_conflict(scope, p->answer->rrs[i]); + dns_scope_notify_conflict(scope, p->answer->items[i].rr); + } +} + +void dns_scope_dump(DnsScope *s, FILE *f) { + assert(s); + + if (!f) + f = stdout; + + fputs("[Scope protocol=", f); + fputs(dns_protocol_to_string(s->protocol), f); + + if (s->link) { + fputs(" interface=", f); + fputs(s->link->name, f); + } + + if (s->family != AF_UNSPEC) { + fputs(" family=", f); + fputs(af_to_name(s->family), f); + } + + fputs("]\n", f); + + if (!dns_zone_is_empty(&s->zone)) { + fputs("ZONE:\n", f); + dns_zone_dump(&s->zone, f); + } + + if (!dns_cache_is_empty(&s->cache)) { + fputs("CACHE:\n", f); + dns_cache_dump(&s->cache, f); } } diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 29479ad550..b75f212897 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -57,7 +57,10 @@ struct DnsScope { RateLimit ratelimit; - LIST_HEAD(DnsTransaction, transactions); + usec_t resend_timeout; + usec_t max_rtt; + + Hashmap *transactions; LIST_FIELDS(DnsScope, scopes); }; @@ -65,6 +68,9 @@ struct DnsScope { int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol p, int family); DnsScope* dns_scope_free(DnsScope *s); +void dns_scope_packet_received(DnsScope *s, usec_t rtt); +void dns_scope_packet_lost(DnsScope *s, usec_t usec); + int dns_scope_emit(DnsScope *s, int fd, DnsPacket *p); int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port, DnsServer **server); int dns_scope_udp_dns_socket(DnsScope *s, DnsServer **server); @@ -79,7 +85,9 @@ int dns_scope_llmnr_membership(DnsScope *s, bool b); void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p); -DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsQuestion *question, bool cache_ok); +DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok); int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr); void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p); + +void dns_scope_dump(DnsScope *s, FILE *f); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 92e48ae442..8693911e65 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -23,6 +23,10 @@ #include "resolved-dns-server.h" +/* After how much time to repeat classic DNS requests */ +#define DNS_TIMEOUT_MIN_USEC (500 * USEC_PER_MSEC) +#define DNS_TIMEOUT_MAX_USEC (5 * USEC_PER_SEC) + int dns_server_new( Manager *m, DnsServer **ret, @@ -45,6 +49,7 @@ int dns_server_new( s->type = type; s->family = family; s->address = *in_addr; + s->resend_timeout = DNS_TIMEOUT_MIN_USEC; if (type == DNS_SERVER_LINK) { LIST_FIND_TAIL(servers, l->dns_servers, tail); @@ -115,14 +120,30 @@ DnsServer* dns_server_unref(DnsServer *s) { return NULL; } -static unsigned long dns_server_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) { +void dns_server_packet_received(DnsServer *s, usec_t rtt) { + assert(s); + + if (rtt > s->max_rtt) { + s->max_rtt = rtt; + s->resend_timeout = MIN(MAX(DNS_TIMEOUT_MIN_USEC, s->max_rtt * 2), + DNS_TIMEOUT_MAX_USEC); + } +} + +void dns_server_packet_lost(DnsServer *s, usec_t usec) { + assert(s); + + if (s->resend_timeout <= usec) + s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC); +} + +static void dns_server_hash_func(const void *p, struct siphash *state) { const DnsServer *s = p; - uint64_t u; - siphash24((uint8_t*) &u, &s->address, FAMILY_ADDRESS_SIZE(s->family), hash_key); - u = u * hash_key[0] + u + s->family; + assert(s); - return u; + siphash24_compress(&s->family, sizeof(s->family), state); + siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state); } static int dns_server_compare_func(const void *a, const void *b) { diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index 06059e8829..10111fd6bd 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -46,6 +46,9 @@ struct DnsServer { int family; union in_addr_union address; + usec_t resend_timeout; + usec_t max_rtt; + bool marked:1; LIST_FIELDS(DnsServer, servers); @@ -62,6 +65,9 @@ int dns_server_new( DnsServer* dns_server_ref(DnsServer *s); DnsServer* dns_server_unref(DnsServer *s); +void dns_server_packet_received(DnsServer *s, usec_t rtt); +void dns_server_packet_lost(DnsServer *s, usec_t usec); + DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref); extern const struct hash_ops dns_server_hash_ops; diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index b235fda3d2..b30473dd7e 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -24,6 +24,7 @@ #include "resolved-llmnr.h" #include "resolved-dns-transaction.h" #include "random-util.h" +#include "dns-domain.h" DnsTransaction* dns_transaction_free(DnsTransaction *t) { DnsQuery *q; @@ -34,24 +35,25 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { sd_event_source_unref(t->timeout_event_source); - dns_question_unref(t->question); dns_packet_unref(t->sent); dns_packet_unref(t->received); dns_answer_unref(t->cached); - sd_event_source_unref(t->dns_event_source); - safe_close(t->dns_fd); + sd_event_source_unref(t->dns_udp_event_source); + safe_close(t->dns_udp_fd); dns_server_unref(t->server); dns_stream_free(t->stream); if (t->scope) { - LIST_REMOVE(transactions_by_scope, t->scope->transactions, t); + hashmap_remove(t->scope->transactions, t->key); if (t->id != 0) hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id)); } + dns_resource_key_unref(t->key); + while ((q = set_steal_first(t->queries))) set_remove(q->transactions, t); set_free(t->queries); @@ -76,26 +78,30 @@ void dns_transaction_gc(DnsTransaction *t) { dns_transaction_free(t); } -int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsQuestion *q) { +int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) { _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL; int r; assert(ret); assert(s); - assert(q); + assert(key); r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL); if (r < 0) return r; + r = hashmap_ensure_allocated(&s->transactions, &dns_resource_key_hash_ops); + if (r < 0) + return r; + t = new0(DnsTransaction, 1); if (!t) return -ENOMEM; - t->dns_fd = -1; - - t->question = dns_question_ref(q); + t->dns_udp_fd = -1; + t->key = dns_resource_key_ref(key); + /* Find a fresh, unused transaction id */ do random_bytes(&t->id, sizeof(t->id)); while (t->id == 0 || @@ -107,7 +113,12 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsQuestion *q) { return r; } - LIST_PREPEND(transactions_by_scope, s->transactions, t); + r = hashmap_put(s->transactions, t->key, t); + if (r < 0) { + hashmap_remove(s->manager->dns_transactions, UINT_TO_PTR(t->id)); + return r; + } + t->scope = s; if (ret) @@ -175,9 +186,6 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { assert(t); assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)); - if (!IN_SET(t->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)) - return; - /* Note that this call might invalidate the query. Callers * should hence not attempt to access the query or transaction * after calling this function. */ @@ -252,11 +260,13 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { if (t->stream) return 0; - if (t->scope->protocol == DNS_PROTOCOL_DNS) + switch (t->scope->protocol) { + case DNS_PROTOCOL_DNS: fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53, &server); - else if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { + break; - /* When we already received a query to this (but it was truncated), send to its sender address */ + case DNS_PROTOCOL_LLMNR: + /* When we already received a reply to this (but it was truncated), send to its sender address */ if (t->received) fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port, NULL); else { @@ -266,16 +276,23 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { /* Otherwise, try to talk to the owner of a * the IP address, in case this is a reverse * PTR lookup */ - r = dns_question_extract_reverse_address(t->question, &family, &address); + + r = dns_name_address(DNS_RESOURCE_KEY_NAME(t->key), &family, &address); if (r < 0) return r; if (r == 0) return -EINVAL; + if (family != t->scope->family) + return -ESRCH; fd = dns_scope_tcp_socket(t->scope, family, &address, LLMNR_PORT, NULL); } - } else + + break; + + default: return -EAFNOSUPPORT; + } if (fd < 0) return fd; @@ -292,7 +309,6 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { return r; } - dns_server_unref(t->server); t->server = dns_server_ref(server); t->received = dns_packet_unref(t->received); @@ -312,24 +328,28 @@ static void dns_transaction_next_dns_server(DnsTransaction *t) { assert(t); t->server = dns_server_unref(t->server); - t->dns_event_source = sd_event_source_unref(t->dns_event_source); - t->dns_fd = safe_close(t->dns_fd); + t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source); + t->dns_udp_fd = safe_close(t->dns_udp_fd); dns_scope_next_dns_server(t->scope); } void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { + usec_t ts; int r; assert(t); assert(p); assert(t->state == DNS_TRANSACTION_PENDING); + assert(t->scope); + assert(t->scope->manager); /* Note that this call might invalidate the query. Callers * should hence not attempt to access the query or transaction * after calling this function. */ - if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { + switch (t->scope->protocol) { + case DNS_PROTOCOL_LLMNR: assert(t->scope->link); /* For LLMNR we will not accept any packets from other @@ -348,6 +368,14 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { dns_transaction_tentative(t, p); return; } + + break; + + case DNS_PROTOCOL_DNS: + break; + + default: + assert_not_reached("Invalid DNS protocol."); } if (t->received != p) { @@ -369,6 +397,24 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { } } + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + switch (t->scope->protocol) { + case DNS_PROTOCOL_DNS: + assert(t->server); + + dns_server_packet_received(t->server, ts - t->start_usec); + + break; + case DNS_PROTOCOL_LLMNR: + case DNS_PROTOCOL_MDNS: + dns_scope_packet_received(t->scope, ts - t->start_usec); + + break; + default: + break; + } + if (DNS_PACKET_TC(p)) { /* Response was truncated, let's try again with good old TCP */ r = dns_transaction_open_tcp(t); @@ -406,14 +452,13 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { } /* Only consider responses with equivalent query section to the request */ - if (!dns_question_is_superset(p->question, t->question) || - !dns_question_is_superset(t->question, p->question)) { + 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; } /* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */ - dns_cache_put(&t->scope->cache, p->question, 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), 0, p->family, &p->sender); if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS) dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); @@ -434,9 +479,9 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use return r; if (dns_packet_validate_reply(p) > 0 && - DNS_PACKET_ID(p) == t->id) { + DNS_PACKET_ID(p) == t->id) dns_transaction_process_reply(t, p); - } else + else log_debug("Invalid DNS packet."); return 0; @@ -455,16 +500,16 @@ static int dns_transaction_emit(DnsTransaction *t) { if (fd < 0) return fd; - r = sd_event_add_io(t->scope->manager->event, &t->dns_event_source, fd, EPOLLIN, on_dns_packet, t); + r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t); if (r < 0) return r; - t->dns_fd = fd; + t->dns_udp_fd = fd; fd = -1; t->server = dns_server_ref(server); } - r = dns_scope_emit(t->scope, t->dns_fd, t->sent); + r = dns_scope_emit(t->scope, t->dns_udp_fd, t->sent); if (r < 0) return r; @@ -481,6 +526,12 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat /* Timeout reached? Try again, with a new server */ dns_transaction_next_dns_server(t); + /* ... and possibly increased timeout */ + if (t->server) + dns_server_packet_lost(t->server, usec - t->start_usec); + else + dns_scope_packet_lost(t->scope, usec - t->start_usec); + r = dns_transaction_go(t); if (r < 0) dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); @@ -490,7 +541,6 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat static int dns_transaction_make_packet(DnsTransaction *t) { _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - unsigned n, added = 0; int r; assert(t); @@ -502,24 +552,17 @@ static int dns_transaction_make_packet(DnsTransaction *t) { if (r < 0) return r; - for (n = 0; n < t->question->n_keys; n++) { - r = dns_scope_good_key(t->scope, t->question->keys[n]); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_packet_append_key(p, t->question->keys[n], NULL); - if (r < 0) - return r; - - added++; - } - - if (added <= 0) + r = dns_scope_good_key(t->scope, t->key); + if (r < 0) + return r; + if (r == 0) return -EDOM; - DNS_PACKET_HEADER(p)->qdcount = htobe16(added); + r = dns_packet_append_key(p, t->key, NULL); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->qdcount = htobe16(1); DNS_PACKET_HEADER(p)->id = t->id; t->sent = p; @@ -528,8 +571,26 @@ static int dns_transaction_make_packet(DnsTransaction *t) { return 0; } +static usec_t transaction_get_resend_timeout(DnsTransaction *t) { + assert(t); + assert(t->scope); + + switch (t->scope->protocol) { + case DNS_PROTOCOL_DNS: + assert(t->server); + + return t->server->resend_timeout; + case DNS_PROTOCOL_LLMNR: + case DNS_PROTOCOL_MDNS: + return t->scope->resend_timeout; + default: + assert_not_reached("Invalid DNS protocol."); + } +} + int dns_transaction_go(DnsTransaction *t) { bool had_stream; + usec_t ts; int r; assert(t); @@ -555,7 +616,10 @@ int dns_transaction_go(DnsTransaction *t) { return 0; } + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + t->n_attempts++; + t->start_usec = ts; t->received = dns_packet_unref(t->received); t->cached = dns_answer_unref(t->cached); t->cached_rcode = 0; @@ -572,11 +636,10 @@ 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->question, &t->cached_rcode, &t->cached); + r = dns_cache_lookup(&t->scope->cache, t->key, &t->cached_rcode, &t->cached); if (r < 0) return r; if (r > 0) { - log_debug("Cache hit!"); if (t->cached_rcode == DNS_RCODE_SUCCESS) dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); else @@ -600,7 +663,7 @@ int dns_transaction_go(DnsTransaction *t) { t->scope->manager->event, &t->timeout_event_source, clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + jitter, + ts + jitter, LLMNR_JITTER_INTERVAL_USEC, on_transaction_timeout, t); if (r < 0) @@ -613,8 +676,6 @@ int dns_transaction_go(DnsTransaction *t) { return 0; } - log_debug("Cache miss!"); - /* Otherwise, we need to ask the network */ r = dns_transaction_make_packet(t); if (r == -EDOM) { @@ -628,8 +689,8 @@ int dns_transaction_go(DnsTransaction *t) { return r; if (t->scope->protocol == DNS_PROTOCOL_LLMNR && - (dns_question_endswith(t->question, "in-addr.arpa") > 0 || - dns_question_endswith(t->question, "ip6.arpa") > 0)) { + (dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "in-addr.arpa") > 0 || + dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "ip6.arpa") > 0)) { /* RFC 4795, Section 2.4. says reverse lookups shall * always be made via TCP on LLMNR */ @@ -660,7 +721,7 @@ int dns_transaction_go(DnsTransaction *t) { t->scope->manager->event, &t->timeout_event_source, clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + TRANSACTION_TIMEOUT_USEC(t->scope->protocol), 0, + ts + transaction_get_resend_timeout(t), 0, on_transaction_timeout, t); if (r < 0) return r; diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index a8f4267bc8..acf6a6f651 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -47,7 +47,7 @@ enum DnsTransactionState { struct DnsTransaction { DnsScope *scope; - DnsQuestion *question; + DnsResourceKey *key; DnsTransactionState state; uint16_t id; @@ -58,13 +58,14 @@ struct DnsTransaction { DnsAnswer *cached; int cached_rcode; + usec_t start_usec; sd_event_source *timeout_event_source; unsigned n_attempts; - int dns_fd; - sd_event_source *dns_event_source; + int dns_udp_fd; + sd_event_source *dns_udp_event_source; - /* the active server */ + /* The active server */ DnsServer *server; /* TCP connection logic, if we need it */ @@ -83,7 +84,7 @@ struct DnsTransaction { LIST_FIELDS(DnsTransaction, transactions_by_scope); }; -int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsQuestion *q); +int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key); DnsTransaction* dns_transaction_free(DnsTransaction *t); void dns_transaction_gc(DnsTransaction *t); @@ -95,20 +96,13 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state); const char* dns_transaction_state_to_string(DnsTransactionState p) _const_; DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_; -/* After how much time to repeat classic DNS requests */ -#define DNS_TRANSACTION_TIMEOUT_USEC (5 * USEC_PER_SEC) - -/* After how much time to repeat LLMNR requests, see RFC 4795 Section 7 */ -#define LLMNR_TRANSACTION_TIMEOUT_USEC (1 * USEC_PER_SEC) - /* LLMNR Jitter interval, see RFC 4795 Section 7 */ #define LLMNR_JITTER_INTERVAL_USEC (100 * USEC_PER_MSEC) /* Maximum attempts to send DNS requests, across all DNS servers */ -#define DNS_TRANSACTION_ATTEMPTS_MAX 8 +#define DNS_TRANSACTION_ATTEMPTS_MAX 16 /* Maximum attempts to send LLMNR requests, see RFC 4795 Section 2.7 */ #define LLMNR_TRANSACTION_ATTEMPTS_MAX 3 -#define TRANSACTION_TIMEOUT_USEC(p) (p == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_TIMEOUT_USEC : DNS_TRANSACTION_TIMEOUT_USEC) #define TRANSACTION_ATTEMPTS_MAX(p) (p == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_ATTEMPTS_MAX : DNS_TRANSACTION_ATTEMPTS_MAX) diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index 32d771a954..8a59bd1c3c 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -90,11 +90,8 @@ void dns_zone_flush(DnsZone *z) { assert(hashmap_size(z->by_key) == 0); assert(hashmap_size(z->by_name) == 0); - hashmap_free(z->by_key); - z->by_key = NULL; - - hashmap_free(z->by_name); - z->by_name = NULL; + z->by_key = hashmap_free(z->by_key); + z->by_name = hashmap_free(z->by_name); } static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) { @@ -166,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; - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; DnsTransaction *t; int r; @@ -179,17 +175,9 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) { if (!key) return -ENOMEM; - question = dns_question_new(1); - if (!question) - return -ENOMEM; - - r = dns_question_add(question, key); - if (r < 0) - return r; - - t = dns_scope_find_transaction(i->scope, question, false); + t = dns_scope_find_transaction(i->scope, key, false); if (!t) { - r = dns_transaction_new(&t, i->scope, question); + r = dns_transaction_new(&t, i->scope, key); if (r < 0) return r; } @@ -217,7 +205,6 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) { } dns_zone_item_ready(i); - return 0; gc: @@ -422,7 +409,7 @@ int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswe if (k < 0) return k; if (k > 0) { - r = dns_answer_add(answer, j->rr); + r = dns_answer_add(answer, j->rr, 0); if (r < 0) return r; @@ -448,7 +435,7 @@ int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswe if (j->state != DNS_ZONE_ITEM_PROBING) tentative = false; - r = dns_answer_add(answer, j->rr); + r = dns_answer_add(answer, j->rr, 0); if (r < 0) return r; } @@ -505,7 +492,7 @@ void dns_zone_item_conflict(DnsZoneItem *i) { i->state = DNS_ZONE_ITEM_WITHDRAWN; /* Maybe change the hostname */ - if (dns_name_equal(i->scope->manager->hostname, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0) + if (manager_is_own_hostname(i->scope->manager, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0) manager_next_hostname(i->scope->manager); } @@ -646,3 +633,40 @@ void dns_zone_verify_all(DnsZone *zone) { dns_zone_item_verify(j); } } + +void dns_zone_dump(DnsZone *zone, FILE *f) { + Iterator iterator; + DnsZoneItem *i; + int r; + + if (!zone) + return; + + if (!f) + f = stdout; + + HASHMAP_FOREACH(i, zone->by_key, iterator) { + DnsZoneItem *j; + + LIST_FOREACH(by_key, j, i) { + _cleanup_free_ char *t = NULL; + + r = dns_resource_record_to_string(j->rr, &t); + if (r < 0) { + log_oom(); + continue; + } + + fputc('\t', f); + fputs(t, f); + fputc('\n', f); + } + } +} + +bool dns_zone_is_empty(DnsZone *zone) { + if (!zone) + return true; + + return hashmap_isempty(zone->by_key); +} diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h index 71851265c6..495d17cdb1 100644 --- a/src/resolve/resolved-dns-zone.h +++ b/src/resolve/resolved-dns-zone.h @@ -78,3 +78,6 @@ int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key); void dns_zone_verify_all(DnsZone *zone); void dns_zone_item_probe_stop(DnsZoneItem *i); + +void dns_zone_dump(DnsZone *zone, FILE *f); +bool dns_zone_is_empty(DnsZone *zone); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index d66b3a88fc..b9fd8e3dbc 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -238,8 +238,7 @@ static int link_update_domains(Link *l) { if (!l->unicast_scope) return 0; - strv_free(l->unicast_scope->domains); - l->unicast_scope->domains = NULL; + l->unicast_scope->domains = strv_free(l->unicast_scope->domains); r = sd_network_link_get_domains(l->ifindex, &l->unicast_scope->domains); @@ -419,16 +418,16 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) { a->link->llmnr_support == SUPPORT_YES && a->link->manager->llmnr_support == SUPPORT_YES) { - if (!a->link->manager->host_ipv4_key) { - a->link->manager->host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->hostname); - if (!a->link->manager->host_ipv4_key) { + if (!a->link->manager->llmnr_host_ipv4_key) { + a->link->manager->llmnr_host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->llmnr_hostname); + if (!a->link->manager->llmnr_host_ipv4_key) { r = -ENOMEM; goto fail; } } if (!a->llmnr_address_rr) { - a->llmnr_address_rr = dns_resource_record_new(a->link->manager->host_ipv4_key); + a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv4_key); if (!a->llmnr_address_rr) { r = -ENOMEM; goto fail; @@ -439,7 +438,7 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) { } if (!a->llmnr_ptr_rr) { - r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->hostname); + r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); if (r < 0) goto fail; @@ -476,16 +475,16 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) { a->link->llmnr_support == SUPPORT_YES && a->link->manager->llmnr_support == SUPPORT_YES) { - if (!a->link->manager->host_ipv6_key) { - a->link->manager->host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->hostname); - if (!a->link->manager->host_ipv6_key) { + if (!a->link->manager->llmnr_host_ipv6_key) { + a->link->manager->llmnr_host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->llmnr_hostname); + if (!a->link->manager->llmnr_host_ipv6_key) { r = -ENOMEM; goto fail; } } if (!a->llmnr_address_rr) { - a->llmnr_address_rr = dns_resource_record_new(a->link->manager->host_ipv6_key); + a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv6_key); if (!a->llmnr_address_rr) { r = -ENOMEM; goto fail; @@ -496,7 +495,7 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) { } if (!a->llmnr_ptr_rr) { - r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->hostname); + r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); if (r < 0) goto fail; diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 5be01d3cb8..de924e3ed9 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -176,8 +176,7 @@ static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, voi break; case RTM_DELADDR: - if (a) - link_address_free(a); + link_address_free(a); break; } @@ -311,51 +310,84 @@ static int manager_network_monitor_listen(Manager *m) { return 0; } -static int determine_hostname(char **ret) { +static int determine_hostname(char **llmnr_hostname, char **mdns_hostname) { _cleanup_free_ char *h = NULL, *n = NULL; - int r; + char label[DNS_LABEL_MAX]; + const char *p; + int r, k; - assert(ret); + assert(llmnr_hostname); + assert(mdns_hostname); + + /* 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(); - if (!utf8_is_valid(h)) { + 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."); + return -EINVAL; + } + + k = dns_label_undo_idna(label, r, label, sizeof(label)); + if (k < 0) + return log_error_errno(k, "Failed to undo IDNA: %m"); + if (k > 0) + r = k; + + if (!utf8_is_valid(label)) { log_error("System hostname is not UTF-8 clean."); return -EINVAL; } - r = dns_name_normalize(h, &n); - if (r < 0) { - log_error("System hostname '%s' cannot be normalized.", h); - return r; + r = dns_label_escape(label, r, &n); + if (r < 0) + return log_error_errno(r, "Failed to escape host name: %m"); + + if (is_localhost(n)) { + log_debug("System hostname is 'localhost', ignoring."); + return -EINVAL; } - *ret = n; + r = dns_name_concat(n, "local", mdns_hostname); + if (r < 0) + return log_error_errno(r, "Failed to determine mDNS hostname: %m"); + + *llmnr_hostname = n; n = NULL; return 0; } static int on_hostname_change(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - _cleanup_free_ char *h = NULL; + _cleanup_free_ char *llmnr_hostname = NULL, *mdns_hostname = NULL; Manager *m = userdata; int r; assert(m); - r = determine_hostname(&h); + r = determine_hostname(&llmnr_hostname, &mdns_hostname); if (r < 0) return 0; /* ignore invalid hostnames */ - if (streq(h, m->hostname)) + if (streq(llmnr_hostname, m->llmnr_hostname) && streq(mdns_hostname, m->mdns_hostname)) return 0; - log_info("System hostname changed to '%s'.", h); - free(m->hostname); - m->hostname = h; - h = NULL; + log_info("System hostname changed to '%s'.", llmnr_hostname); + + free(m->llmnr_hostname); + free(m->mdns_hostname); + + m->llmnr_hostname = llmnr_hostname; + m->mdns_hostname = mdns_hostname; + + llmnr_hostname = mdns_hostname = NULL; manager_refresh_rrs(m); @@ -382,15 +414,44 @@ static int manager_watch_hostname(Manager *m) { return log_error_errno(r, "Failed to add hostname event source: %m"); } - r = determine_hostname(&m->hostname); + r = determine_hostname(&m->llmnr_hostname, &m->mdns_hostname); if (r < 0) { log_info("Defaulting to hostname 'linux'."); - m->hostname = strdup("linux"); - if (!m->hostname) + 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(); } else - log_info("Using system hostname '%s'.", m->hostname); + log_info("Using system hostname '%s'.", m->llmnr_hostname); + + return 0; +} + +static int manager_sigusr1(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + _cleanup_free_ char *buffer = NULL; + _cleanup_fclose_ FILE *f = NULL; + Manager *m = userdata; + size_t size = 0; + DnsScope *scope; + + assert(s); + assert(si); + assert(m); + + f = open_memstream(&buffer, &size); + if (!f) + return log_oom(); + LIST_FOREACH(scopes, scope, m->dns_scopes) + dns_scope_dump(scope, f); + + if (fflush_and_check(f) < 0) + return log_oom(); + + log_dump(LOG_INFO, buffer); return 0; } @@ -444,6 +505,8 @@ int manager_new(Manager **ret) { if (r < 0) return r; + (void) sd_event_add_signal(m->event, &m->sigusr1_event_source, SIGUSR1, manager_sigusr1, m); + *ret = m; m = NULL; @@ -491,14 +554,17 @@ Manager *manager_free(Manager *m) { sd_event_source_unref(m->bus_retry_event_source); sd_bus_unref(m->bus); + sd_event_source_unref(m->sigusr1_event_source); + sd_event_unref(m->event); - dns_resource_key_unref(m->host_ipv4_key); - dns_resource_key_unref(m->host_ipv6_key); + dns_resource_key_unref(m->llmnr_host_ipv4_key); + dns_resource_key_unref(m->llmnr_host_ipv6_key); - safe_close(m->hostname_fd); sd_event_source_unref(m->hostname_event_source); - free(m->hostname); + safe_close(m->hostname_fd); + free(m->llmnr_hostname); + free(m->mdns_hostname); free(m); @@ -553,8 +619,7 @@ int manager_read_resolv_conf(Manager *m) { } if (fstat(fileno(f), &st) < 0) { - log_error_errno(errno, "Failed to stat open file: %m"); - r = -errno; + r = log_error_errno(errno, "Failed to stat open file: %m"); goto clear; } @@ -1230,8 +1295,8 @@ void manager_refresh_rrs(Manager *m) { assert(m); - m->host_ipv4_key = dns_resource_key_unref(m->host_ipv4_key); - m->host_ipv6_key = dns_resource_key_unref(m->host_ipv6_key); + 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); HASHMAP_FOREACH(l, m->links, i) { link_add_rrs(l, true); @@ -1242,14 +1307,15 @@ void manager_refresh_rrs(Manager *m) { int manager_next_hostname(Manager *m) { const char *p; uint64_t u, a; - char *h; + char *h, *k; + int r; assert(m); - p = strchr(m->hostname, 0); + p = strchr(m->llmnr_hostname, 0); assert(p); - while (p > m->hostname) { + while (p > m->llmnr_hostname) { if (!strchr("0123456789", p[-1])) break; @@ -1269,13 +1335,22 @@ int manager_next_hostname(Manager *m) { random_bytes(&a, sizeof(a)); u += 1 + a % 10; - if (asprintf(&h, "%.*s%" PRIu64, (int) (p - m->hostname), m->hostname, u) < 0) + if (asprintf(&h, "%.*s%" PRIu64, (int) (p - m->llmnr_hostname), m->llmnr_hostname, u) < 0) return -ENOMEM; - log_info("Hostname conflict, changing published hostname from '%s' to '%s'.", m->hostname, h); + r = dns_name_concat(h, "local", &k); + if (r < 0) { + free(h); + return r; + } + + log_info("Hostname conflict, changing published hostname from '%s' to '%s'.", m->llmnr_hostname, h); - free(m->hostname); - m->hostname = h; + free(m->llmnr_hostname); + m->llmnr_hostname = h; + + free(m->mdns_hostname); + m->mdns_hostname = k; manager_refresh_rrs(m); @@ -1357,6 +1432,24 @@ void manager_flush_dns_servers(Manager *m, DnsServerType t) { } } +int manager_is_own_hostname(Manager *m, const char *name) { + int r; + + assert(m); + assert(name); + + if (m->llmnr_hostname) { + r = dns_name_equal(name, m->llmnr_hostname); + if (r != 0) + return r; + } + + if (m->mdns_hostname) + return dns_name_equal(name, m->mdns_hostname); + + return 0; +} + static const char* const support_table[_SUPPORT_MAX] = { [SUPPORT_NO] = "no", [SUPPORT_YES] = "yes", diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 53b5acb33c..fe7fe99505 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -91,9 +91,10 @@ struct Manager { sd_event_source *bus_retry_event_source; /* The hostname we publish on LLMNR and mDNS */ - char *hostname; - DnsResourceKey *host_ipv4_key; - DnsResourceKey *host_ipv6_key; + char *llmnr_hostname; + char *mdns_hostname; + DnsResourceKey *llmnr_host_ipv4_key; + DnsResourceKey *llmnr_host_ipv6_key; /* Watch the system hostname */ int hostname_fd; @@ -101,6 +102,8 @@ struct Manager { /* Watch for system suspends */ sd_bus_slot *prepare_for_sleep_slot; + + sd_event_source *sigusr1_event_source; }; /* Manager */ @@ -140,5 +143,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); #define EXTRA_CMSG_SPACE 1024 +int manager_is_own_hostname(Manager *m, const char *name); + const char* support_to_string(Support p) _const_; int support_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c index 0af5545f8e..32e61af925 100644 --- a/src/resolve/resolved.c +++ b/src/resolve/resolved.c @@ -71,7 +71,7 @@ int main(int argc, char *argv[]) { if (r < 0) goto finish; - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, -1) >= 0); r = manager_new(&m); if (r < 0) { |