From: Dmitry Rozhkov Date: Tue, 17 Oct 2017 08:35:06 +0000 (+0300) Subject: resolved: detect and handle mDNS race condition upon probing X-Git-Tag: v236~33^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8d67e72cbe240912e5408bc2a30d5f303678c2b8;p=thirdparty%2Fsystemd.git resolved: detect and handle mDNS race condition upon probing As discussed in RFC 6762, Section 8.2 a race condition may happen when two hosts are probing for the same name simultaniously. Detect and handle such race conditions. --- diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index ea81056119b..792a16d6930 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -365,7 +365,7 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { SET_FOREACH_MOVE(z, t->notify_zone_items_done, t->notify_zone_items) dns_zone_item_notify(z); SWAP_TWO(t->notify_zone_items, t->notify_zone_items_done); - if (t->probing) + if (t->probing && t->state == DNS_TRANSACTION_ATTEMPTS_MAX_REACHED) (void) dns_scope_announce(t->scope, false); SET_FOREACH_MOVE(d, t->notify_transactions_done, t->notify_transactions) diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index 55db3529599..ca92699f824 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -95,7 +95,7 @@ void dns_zone_flush(DnsZone *z) { z->by_name = hashmap_free(z->by_name); } -static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) { +DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) { DnsZoneItem *i; assert(z); diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h index 896f2298558..c9c74403519 100644 --- a/src/resolve/resolved-dns-zone.h +++ b/src/resolve/resolved-dns-zone.h @@ -67,6 +67,7 @@ struct DnsZoneItem { void dns_zone_flush(DnsZone *z); int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe); +DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr); void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr); int dns_zone_remove_rrs_by_key(DnsZone *z, DnsResourceKey *key); diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index 1cc1195717e..38e2c542273 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -22,10 +22,13 @@ #include #include +#include "alloc-util.h" #include "fd-util.h" #include "resolved-manager.h" #include "resolved-mdns.h" +#define CLEAR_CACHE_FLUSH(x) (~MDNS_RR_CACHE_FLUSH & (x)) + void manager_mdns_stop(Manager *m) { assert(m); @@ -68,10 +71,145 @@ eaddrinuse: return 0; } +static int mdns_rr_compare(const void *a, const void *b) { + DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b; + size_t m; + int r; + + assert(x); + assert(*x); + assert(y); + assert(*y); + + if (CLEAR_CACHE_FLUSH((*x)->key->class) < CLEAR_CACHE_FLUSH((*y)->key->class)) + return -1; + else if (CLEAR_CACHE_FLUSH((*x)->key->class) > CLEAR_CACHE_FLUSH((*y)->key->class)) + return 1; + + if ((*x)->key->type < (*y)->key->type) + return -1; + else if ((*x)->key->type > (*y)->key->type) + return 1; + + r = dns_resource_record_to_wire_format(*x, false); + if (r < 0) { + log_warning_errno(r, "Can't wire-format RR: %m"); + return 0; + } + + r = dns_resource_record_to_wire_format(*y, false); + if (r < 0) { + log_warning_errno(r, "Can't wire-format RR: %m"); + return 0; + } + + m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y)); + + r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m); + if (r != 0) + return r; + + if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) + return -1; + else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) + return 1; + + return 0; +} + +static int proposed_rrs_cmp(DnsResourceRecord **x, unsigned x_size, DnsResourceRecord **y, unsigned y_size) { + unsigned m; + int r; + + m = MIN(x_size, y_size); + for (unsigned i = 0; i < m; i++) { + r = mdns_rr_compare(&x[i], &y[i]); + if (r != 0) + return r; + } + + if (x_size < y_size) + return -1; + if (x_size > y_size) + return 1; + + return 0; +} + +static int mdns_packet_extract_matching_rrs(DnsPacket *p, DnsResourceKey *key, DnsResourceRecord ***ret_rrs) { + _cleanup_free_ DnsResourceRecord **list = NULL; + unsigned n = 0, size = 0; + int r; + + assert(p); + assert(key); + assert(ret_rrs); + assert_return(DNS_PACKET_NSCOUNT(p) > 0, -EINVAL); + + for (unsigned i = DNS_PACKET_ANCOUNT(p); i < (DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)); i++) { + r = dns_resource_key_match_rr(key, p->answer->items[i].rr, NULL); + if (r < 0) + return r; + if (r > 0) + size++; + } + + if (size == 0) + return 0; + + list = new(DnsResourceRecord *, size); + if (!list) + return -ENOMEM; + + for (unsigned i = DNS_PACKET_ANCOUNT(p); i < (DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)); i++) { + r = dns_resource_key_match_rr(key, p->answer->items[i].rr, NULL); + if (r < 0) + return r; + if (r > 0) + list[n++] = p->answer->items[i].rr; + } + assert(n == size); + qsort_safe(list, size, sizeof(DnsResourceRecord*), mdns_rr_compare); + + *ret_rrs = list; + list = NULL; + + return size; +} + +static int mdns_do_tiebreak(DnsResourceKey *key, DnsAnswer *answer, DnsPacket *p) { + _cleanup_free_ DnsResourceRecord **our = NULL, **remote = NULL; + DnsResourceRecord *rr; + unsigned i = 0; + unsigned size; + int r; + + size = dns_answer_size(answer); + our = new(DnsResourceRecord *, size); + if (!our) + return -ENOMEM; + + DNS_ANSWER_FOREACH(rr, answer) + our[i++] = rr; + qsort_safe(our, size, sizeof(DnsResourceRecord*), mdns_rr_compare); + + r = mdns_packet_extract_matching_rrs(p, key, &remote); + if (r < 0) + return r; + + assert(r > 0); + + if (proposed_rrs_cmp(remote, r, our, size) > 0) + return 1; + + return 0; +} + static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) { _cleanup_(dns_answer_unrefp) DnsAnswer *full_answer = NULL; _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; DnsResourceKey *key = NULL; + DnsResourceRecord *rr; bool tentative = false; int r; @@ -91,6 +229,31 @@ static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) { if (r < 0) return log_debug_errno(r, "Failed to lookup key: %m"); + if (tentative && DNS_PACKET_NSCOUNT(p) > 0) { + /* + * A race condition detected with the probe packet from + * a remote host. + * Do simultaneous probe tiebreaking as described in + * RFC 6762, Section 8.2. In case we lost don't reply + * the question and withdraw conflicting RRs. + */ + r = mdns_do_tiebreak(key, answer, p); + if (r < 0) + return log_debug_errno(r, "Failed to do tiebreaking"); + + if (r > 0) { /* we lost */ + DNS_ANSWER_FOREACH(rr, answer) { + DnsZoneItem *i; + + i = dns_zone_get(&s->zone, rr); + if (i) + dns_zone_item_conflict(i); + } + + continue; + } + } + r = dns_answer_extend(&full_answer, answer); if (r < 0) return log_debug_errno(r, "Failed to extend answer: %m");