]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolved-dns-zone.c
resolved: detect and handle mDNS race condition upon probing
[thirdparty/systemd.git] / src / resolve / resolved-dns-zone.c
index f60b0bddc16ba5adb600ad8aebfe9d5da7426588..ca92699f82431a1f2912977f1fbda6105563d57f 100644 (file)
@@ -1,5 +1,4 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -40,6 +39,7 @@ void dns_zone_item_probe_stop(DnsZoneItem *i) {
         i->probe_transaction = NULL;
 
         set_remove(t->notify_zone_items, i);
+        set_remove(t->notify_zone_items_done, i);
         dns_transaction_gc(t);
 }
 
@@ -70,12 +70,12 @@ static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) {
         else
                 hashmap_remove(z->by_key, i->rr->key);
 
-        first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
+        first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
         LIST_REMOVE(by_name, first, i);
         if (first)
-                assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0);
+                assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
         else
-                hashmap_remove(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
+                hashmap_remove(z->by_name, dns_resource_key_name(i->rr->key));
 
         dns_zone_item_free(i);
 }
@@ -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);
@@ -119,6 +119,22 @@ void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
                 dns_zone_item_remove_and_free(z, i);
 }
 
+int dns_zone_remove_rrs_by_key(DnsZone *z, DnsResourceKey *key) {
+        _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
+        DnsResourceRecord *rr;
+        bool tentative;
+        int r;
+
+        r = dns_zone_lookup(z, key, 0, &answer, &soa, &tentative);
+        if (r < 0)
+                return r;
+
+        DNS_ANSWER_FOREACH(rr, answer)
+                dns_zone_remove_rr(z, rr);
+
+        return 0;
+}
+
 static int dns_zone_init(DnsZone *z) {
         int r;
 
@@ -149,12 +165,12 @@ static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
                         return r;
         }
 
-        first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
+        first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
         if (first) {
                 LIST_PREPEND(by_name, first, i);
-                assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0);
+                assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
         } else {
-                r = hashmap_put(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key), i);
+                r = hashmap_put(z->by_name, dns_resource_key_name(i->rr->key), i);
                 if (r < 0)
                         return r;
         }
@@ -171,11 +187,11 @@ static int dns_zone_item_probe_start(DnsZoneItem *i)  {
         if (i->probe_transaction)
                 return 0;
 
-        t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key)), false);
+        t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)), false);
         if (!t) {
                 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
 
-                key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key));
+                key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key));
                 if (!key)
                         return -ENOMEM;
 
@@ -188,11 +204,16 @@ static int dns_zone_item_probe_start(DnsZoneItem *i)  {
         if (r < 0)
                 goto gc;
 
+        r = set_ensure_allocated(&t->notify_zone_items_done, NULL);
+        if (r < 0)
+                goto gc;
+
         r = set_put(t->notify_zone_items, i);
         if (r < 0)
                 goto gc;
 
         i->probe_transaction = t;
+        t->probing = true;
 
         if (t->state == DNS_TRANSACTION_NULL) {
 
@@ -284,13 +305,34 @@ int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
         return 0;
 }
 
-int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
+static int dns_zone_add_authenticated_answer(DnsAnswer *a, DnsZoneItem *i, int ifindex) {
+        DnsAnswerFlags flags;
+
+        /* From RFC 6762, Section 10.2
+         * "They (the rules about when to set the cache-flush bit) apply to
+         * startup announcements as described in Section 8.3, "Announcing",
+         * and to responses generated as a result of receiving query messages."
+         * So, set the cache-flush bit for mDNS answers except for DNS-SD
+         * service enumeration PTRs described in RFC 6763, Section 4.1. */
+        if (i->scope->protocol == DNS_PROTOCOL_MDNS &&
+            !dns_resource_key_is_dnssd_ptr(i->rr->key))
+                flags = DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHE_FLUSH;
+        else
+                flags = DNS_ANSWER_AUTHENTICATED;
+
+        return dns_answer_add(a, i->rr, ifindex, flags);
+}
+
+int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
         _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
         unsigned n_answer = 0;
         DnsZoneItem *j, *first;
         bool tentative = true, need_soa = false;
         int r;
 
+        /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the
+         * ifindex field in the answer with it */
+
         assert(z);
         assert(key);
         assert(ret_answer);
@@ -305,7 +347,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns
                  * go through the list by the name and look
                  * for everything manually */
 
-                first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key));
+                first = hashmap_get(z->by_name, dns_resource_key_name(key));
                 LIST_FOREACH(by_name, j, first) {
                         if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
                                 continue;
@@ -341,7 +383,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns
                 }
 
                 if (!found) {
-                        first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key));
+                        first = hashmap_get(z->by_name, dns_resource_key_name(key));
                         LIST_FOREACH(by_name, j, first) {
                                 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
                                         continue;
@@ -372,7 +414,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns
                 bool found = false, added = false;
                 int k;
 
-                first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key));
+                first = hashmap_get(z->by_name, dns_resource_key_name(key));
                 LIST_FOREACH(by_name, j, first) {
                         if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
                                 continue;
@@ -386,7 +428,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns
                         if (k < 0)
                                 return k;
                         if (k > 0) {
-                                r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED);
+                                r = dns_zone_add_authenticated_answer(answer, j, ifindex);
                                 if (r < 0)
                                         return r;
 
@@ -395,7 +437,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns
                 }
 
                 if (found && !added) {
-                        r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(key), LLMNR_DEFAULT_TTL);
+                        r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
                         if (r < 0)
                                 return r;
                 }
@@ -412,7 +454,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns
                         if (j->state != DNS_ZONE_ITEM_PROBING)
                                 tentative = false;
 
-                        r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED);
+                        r = dns_zone_add_authenticated_answer(answer, j, ifindex);
                         if (r < 0)
                                 return r;
                 }
@@ -420,7 +462,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns
                 if (!found) {
                         bool add_soa = false;
 
-                        first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key));
+                        first = hashmap_get(z->by_name, dns_resource_key_name(key));
                         LIST_FOREACH(by_name, j, first) {
                                 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
                                         continue;
@@ -432,7 +474,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns
                         }
 
                         if (add_soa) {
-                                r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(key), LLMNR_DEFAULT_TTL);
+                                r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
                                 if (r < 0)
                                         return r;
                         }
@@ -484,7 +526,7 @@ void dns_zone_item_conflict(DnsZoneItem *i) {
         i->state = DNS_ZONE_ITEM_WITHDRAWN;
 
         /* Maybe change the hostname */
-        if (manager_is_own_hostname(i->scope->manager, 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);
 }
 
@@ -502,7 +544,9 @@ void dns_zone_item_notify(DnsZoneItem *i) {
                 bool we_lost = false;
 
                 /* The probe got a successful reply. If we so far
-                 * weren't established we just give up. If we already
+                 * weren't established we just give up.
+                 *
+                 * In LLMNR case if we already
                  * were established, and the peer has the
                  * lexicographically larger IP address we continue
                  * and defend it. */
@@ -510,7 +554,7 @@ void dns_zone_item_notify(DnsZoneItem *i) {
                 if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
                         log_debug("Got a successful probe for not yet established RR, we lost.");
                         we_lost = true;
-                } else {
+                } else if (i->probe_transaction->scope->protocol == DNS_PROTOCOL_LLMNR) {
                         assert(i->probe_transaction->received);
                         we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
                         if (we_lost)
@@ -564,7 +608,7 @@ int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
          * so, we'll verify our RRs. */
 
         /* No conflict if we don't have the name at all. */
-        first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(rr->key));
+        first = hashmap_get(zone->by_name, dns_resource_key_name(rr->key));
         if (!first)
                 return 0;
 
@@ -595,7 +639,7 @@ int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
         /* Somebody else notified us about a possible conflict. Let's
          * verify if that's true. */
 
-        first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(key));
+        first = hashmap_get(zone->by_name, dns_resource_key_name(key));
         if (!first)
                 return 0;
 
@@ -656,3 +700,20 @@ bool dns_zone_is_empty(DnsZone *zone) {
 
         return hashmap_isempty(zone->by_key);
 }
+
+bool dns_zone_contains_name(DnsZone *z, const char *name) {
+        DnsZoneItem *i, *first;
+
+        first = hashmap_get(z->by_name, name);
+        if (!first)
+                return false;
+
+        LIST_FOREACH(by_name, i, first) {
+                if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
+                        continue;
+
+                return true;
+        }
+
+        return false;
+}