From 5464c961865e049279dc696a93fbaf08b8bc39ce Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 11 Nov 2020 14:44:35 +0100 Subject: [PATCH 1/4] random-util: add random_u64_range() that acquires a random number from a certain range, unbiased So far we have been quite sloppy with this and ignored modulus and range bias. Let's do something about, and add the option to do better. --- src/basic/random-util.c | 19 +++++++++++++++++++ src/basic/random-util.h | 2 ++ 2 files changed, 21 insertions(+) diff --git a/src/basic/random-util.c b/src/basic/random-util.c index c831f06dac..eca8187571 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -494,3 +494,22 @@ int random_write_entropy(int fd, const void *seed, size_t size, bool credit) { return 1; } + +int random_u64_range(uint64_t m) { + uint64_t x, remainder; + + /* Generates a random number in the range 0…m-1, unbiased. (Java's algorithm) */ + + if (m == 0) /* Let's take m == 0 as special case to return an integer from the full range */ + return random_u64(); + if (m == 1) + return 0; + + remainder = UINT64_MAX % m; + + do { + x = random_u64(); + } while (x >= UINT64_MAX - remainder); + + return x % m; +} diff --git a/src/basic/random-util.h b/src/basic/random-util.h index f661fc093a..1d5fb60fa2 100644 --- a/src/basic/random-util.h +++ b/src/basic/random-util.h @@ -40,3 +40,5 @@ int rdrand(unsigned long *ret); size_t random_pool_size(void); int random_write_entropy(int fd, const void *seed, size_t size, bool credit); + +int random_u64_range(uint64_t max); From 5b2d8ffb5a1ebbd1e7c5359a9b924454f9dac031 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 5 Nov 2020 13:33:52 +0100 Subject: [PATCH 2/4] answer: add helper for randomizing RR of answers --- src/resolve/resolved-dns-answer.c | 21 +++++++++++++++++++++ src/resolve/resolved-dns-answer.h | 2 ++ 2 files changed, 23 insertions(+) diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c index 051a2a0f25..8591ed0f3b 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -4,6 +4,7 @@ #include "alloc-util.h" #include "dns-domain.h" +#include "random-util.h" #include "resolved-dns-answer.h" #include "resolved-dns-dnssec.h" #include "string-util.h" @@ -898,3 +899,23 @@ int dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname) { return 0; } + +void dns_answer_randomize(DnsAnswer *a) { + size_t n; + + /* Permutes the answer list randomly (Knuth shuffle) */ + + n = dns_answer_size(a); + if (n <= 1) + return; + + for (size_t i = 0; i < n; i++) { + size_t k; + + k = random_u64_range(n); + if (k == i) + continue; + + SWAP_TWO(a->items[i], a->items[k]); + } +} diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h index cc2cd93a33..f90ca67019 100644 --- a/src/resolve/resolved-dns-answer.h +++ b/src/resolve/resolved-dns-answer.h @@ -80,6 +80,8 @@ static inline bool dns_answer_isempty(DnsAnswer *a) { void dns_answer_dump(DnsAnswer *answer, FILE *f); +void dns_answer_randomize(DnsAnswer *a); + DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); #define _DNS_ANSWER_FOREACH(q, kk, a) \ From 48662847ecd28f418a911a5fa0e7f2c71495cbaf Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 5 Nov 2020 13:34:22 +0100 Subject: [PATCH 3/4] =?UTF-8?q?answer:=20minor=20refactor=20=E2=80=93=20mo?= =?UTF-8?q?ve=20link=20local=20check=20into=20RR=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/resolve/resolved-dns-answer.c | 5 +---- src/resolve/resolved-dns-rr.c | 14 ++++++++++++++ src/resolve/resolved-dns-rr.h | 2 ++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c index 8591ed0f3b..7746cb08ea 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -713,10 +713,7 @@ void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) { items = newa(DnsAnswerItem, a->n_rrs); for (i = 0; i < a->n_rrs; i++) { - - 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))) + if (dns_resource_record_is_link_local_address(a->items[i].rr) != prefer_link_local) /* Order address records that are not preferred to the end of the array */ items[end--] = a->items[i]; else diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 19e075479f..9e64c6dd31 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -1721,6 +1721,20 @@ int dns_resource_record_clamp_ttl(DnsResourceRecord **rr, uint32_t max_ttl) { return 1; } +bool dns_resource_record_is_link_local_address(DnsResourceRecord *rr) { + + if (rr->key->class != DNS_CLASS_IN) + return false; + + if (rr->key->type == DNS_TYPE_A) + return in4_addr_is_link_local(&rr->a.in_addr); + + if (rr->key->type == DNS_TYPE_AAAA) + return IN6_IS_ADDR_LINKLOCAL(&rr->aaaa.in6_addr); + + return false; +} + DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) { DnsTxtItem *n; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index aa17d6d391..d788d797fc 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -324,6 +324,8 @@ int dns_resource_record_is_synthetic(DnsResourceRecord *rr); int dns_resource_record_clamp_ttl(DnsResourceRecord **rr, uint32_t max_ttl); +bool dns_resource_record_is_link_local_address(DnsResourceRecord *rr); + DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i); bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b); DnsTxtItem *dns_txt_item_copy(DnsTxtItem *i); From 3f0a7b3a3224681fec0133fc5ef8d30f0f9b4fa5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 5 Nov 2020 14:10:40 +0100 Subject: [PATCH 4/4] resolved: randomize RR order in answers each time we get something from the cache This allows some minimal, crappy load balancing. Fixes: #16297 --- src/resolve/resolved-dns-transaction.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index c5233a1e19..43359f3307 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -1410,6 +1410,30 @@ static usec_t transaction_get_resend_timeout(DnsTransaction *t) { } } +static void dns_transaction_randomize_answer(DnsTransaction *t) { + int r; + + assert(t); + + /* Randomizes the order of the answer array. This is done for all cached responses, so that we return + * a different order each time. We do this only for DNS traffic, in order to do some minimal, crappy + * load balancing. We don't do this for LLMNR or mDNS, since the order (preferring link-local + * addresses, and such like) might have meaning there, and load balancing is pointless. */ + + if (t->scope->protocol != DNS_PROTOCOL_DNS) + return; + + /* No point in randomizing, if there's just one RR */ + if (dns_answer_size(t->answer) <= 1) + return; + + r = dns_answer_reserve_or_clone(&t->answer, 0); + if (r < 0) /* If this fails, just don't randomize, this is non-essential stuff after all */ + return (void) log_debug_errno(r, "Failed to clone answer record, not randomizing RR order of answer: %m"); + + dns_answer_randomize(t->answer); +} + static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { int r; @@ -1530,6 +1554,8 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { if (r < 0) return r; if (r > 0) { + dns_transaction_randomize_answer(t); + if (t->bypass && t->scope->protocol == DNS_PROTOCOL_DNS && !t->received) /* When bypass mode is on, do not use cached data unless it came with a full * packet. */