chiark / gitweb /
resolved: initialize counter
[elogind.git] / src / resolve / resolved-dns-zone.c
index 99ea420ff4e7a8a13eae0617a53a48298e0e4d5e..b53e957d761f321b864cad9a13bd11c3207f0141 100644 (file)
 /* Never allow more than 1K entries */
 #define ZONE_MAX 1024
 
-typedef struct DnsZoneItem DnsZoneItem;
+void dns_zone_item_probe_stop(DnsZoneItem *i) {
+        DnsTransaction *t;
+        assert(i);
 
-struct DnsZoneItem {
-        DnsResourceRecord *rr;
-        bool verified;
-        LIST_FIELDS(DnsZoneItem, by_key);
-        LIST_FIELDS(DnsZoneItem, by_name);
-};
+        if (!i->probe_transaction)
+                return;
+
+        t = i->probe_transaction;
+        i->probe_transaction = NULL;
+
+        set_remove(t->zone_items, i);
+        dns_transaction_gc(t);
+}
 
 static void dns_zone_item_free(DnsZoneItem *i) {
         if (!i)
                 return;
 
+        dns_zone_item_probe_stop(i);
         dns_resource_record_unref(i->rr);
+
         free(i);
 }
 
@@ -97,7 +104,7 @@ static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
         assert(rr);
 
         LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key))
-                if (dns_resource_record_equal(i->rr, rr))
+                if (dns_resource_record_equal(i->rr, rr) > 0)
                         return i;
 
         return NULL;
@@ -157,12 +164,74 @@ static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
         return 0;
 }
 
-int dns_zone_put(DnsZone *z, DnsResourceRecord *rr) {
+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;
+
+        assert(i);
+
+        if (i->probe_transaction)
+                return 0;
+
+        key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key));
+        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);
+        if (!t) {
+                r = dns_transaction_new(&t, i->scope, question);
+                if (r < 0)
+                        return r;
+        }
+
+        r = set_ensure_allocated(&t->zone_items, NULL, NULL);
+        if (r < 0)
+                goto gc;
+
+        r = set_put(t->zone_items, i);
+        if (r < 0)
+                goto gc;
+
+        i->probe_transaction = t;
+
+        if (t->state == DNS_TRANSACTION_NULL) {
+
+                i->block_ready++;
+                r = dns_transaction_go(t);
+                i->block_ready--;
+
+                if (r < 0) {
+                        dns_zone_item_probe_stop(i);
+                        return r;
+                }
+        }
+
+        dns_zone_item_ready(i);
+
+        return 0;
+
+gc:
+        dns_transaction_gc(t);
+        return r;
+}
+
+int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
         _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
         DnsZoneItem *existing;
         int r;
 
         assert(z);
+        assert(s);
         assert(rr);
 
         if (rr->key->class == DNS_CLASS_ANY)
@@ -182,19 +251,54 @@ int dns_zone_put(DnsZone *z, DnsResourceRecord *rr) {
         if (!i)
                 return -ENOMEM;
 
+        i->scope = s;
         i->rr = dns_resource_record_ref(rr);
+        i->probing_enabled = probe;
 
         r = dns_zone_link_item(z, i);
         if (r < 0)
                 return r;
 
+        if (probe) {
+                DnsZoneItem *first, *j;
+                bool established = false;
+
+                /* Check if there's already an RR with the same name
+                 * established. If so, it has been probed already, and
+                 * we don't ned to probe again. */
+
+                LIST_FIND_HEAD(by_name, i, first);
+                LIST_FOREACH(by_name, j, first) {
+                        if (i == j)
+                                continue;
+
+                        if (j->state == DNS_ZONE_ITEM_ESTABLISHED)
+                                established = true;
+                }
+
+                if (established)
+                        i->state = DNS_ZONE_ITEM_ESTABLISHED;
+                else {
+                        i->state = DNS_ZONE_ITEM_PROBING;
+
+                        r = dns_zone_item_probe_start(i);
+                        if (r < 0) {
+                                dns_zone_item_remove_and_free(z, i);
+                                i = NULL;
+                                return r;
+                        }
+                }
+        } else
+                i->state = DNS_ZONE_ITEM_ESTABLISHED;
+
         i = NULL;
         return 0;
 }
 
-int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswer **ret_soa) {
+int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
         _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
         unsigned i, n_answer = 0, n_soa = 0;
+        bool tentative = true;
         int r;
 
         assert(z);
@@ -205,40 +309,70 @@ int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswe
         if (q->n_keys <= 0) {
                 *ret_answer = NULL;
                 *ret_soa = NULL;
+
+                if (ret_tentative)
+                        *ret_tentative = false;
+
                 return 0;
         }
 
         /* First iteration, count what we have */
         for (i = 0; i < q->n_keys; i++) {
-                DnsZoneItem *j;
+                DnsZoneItem *j, *first;
 
                 if (q->keys[i]->type == DNS_TYPE_ANY ||
                     q->keys[i]->class == DNS_CLASS_ANY) {
+                        bool found = false, added = false;
                         int k;
 
                         /* If this is a generic match, then we have to
                          * go through the list by the name and look
                          * for everything manually */
 
-                        j = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
-                        LIST_FOREACH(by_name, j, j) {
+                        first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
+                        LIST_FOREACH(by_name, j, first) {
+                                if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
+                                        continue;
+
+                                found = true;
+
                                 k = dns_resource_key_match_rr(q->keys[i], j->rr);
                                 if (k < 0)
                                         return k;
-                                if (k == 0)
-                                        n_soa++;
-                                else
+                                if (k > 0) {
                                         n_answer++;
+                                        added = true;
+                                }
+
                         }
 
+                        if (found && !added)
+                                n_soa++;
+
                 } else {
-                        j = hashmap_get(z->by_key, q->keys[i]);
-                        if (j) {
-                                LIST_FOREACH(by_key, j, j)
-                                        n_answer++;
-                        } else {
-                                if (hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i])))
-                                        n_soa ++;
+                        bool found = false;
+
+                        /* If this is a specific match, then look for
+                         * the right key immediately */
+
+                        first = hashmap_get(z->by_key, q->keys[i]);
+                        LIST_FOREACH(by_key, j, first) {
+                                if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
+                                        continue;
+
+                                found = true;
+                                n_answer++;
+                        }
+
+                        if (!found) {
+                                first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
+                                LIST_FOREACH(by_name, j, first) {
+                                        if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
+                                                continue;
+
+                                        n_soa++;
+                                        break;
+                                }
                         }
                 }
         }
@@ -246,6 +380,10 @@ int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswe
         if (n_answer <= 0 && n_soa <= 0) {
                 *ret_answer = NULL;
                 *ret_soa = NULL;
+
+                if (ret_tentative)
+                        *ret_tentative = false;
+
                 return 0;
         }
 
@@ -263,35 +401,73 @@ int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswe
 
         /* Second iteration, actually add the RRs to the answers */
         for (i = 0; i < q->n_keys; i++) {
-                DnsZoneItem *j;
+                DnsZoneItem *j, *first;
 
                 if (q->keys[i]->type == DNS_TYPE_ANY ||
                     q->keys[i]->class == DNS_CLASS_ANY) {
+                        bool found = false, added = false;
                         int k;
 
-                        j = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
-                        LIST_FOREACH(by_name, j, j) {
+                        first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
+                        LIST_FOREACH(by_name, j, first) {
+                                if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
+                                        continue;
+
+                                found = true;
+
+                                if (j->state != DNS_ZONE_ITEM_PROBING)
+                                        tentative = false;
+
                                 k = dns_resource_key_match_rr(q->keys[i], j->rr);
                                 if (k < 0)
                                         return k;
-                                if (k == 0)
-                                        r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL);
-                                else
+                                if (k > 0) {
                                         r = dns_answer_add(answer, j->rr);
+                                        if (r < 0)
+                                                return r;
+
+                                        added = true;
+                                }
+                        }
+
+                        if (found && !added) {
+                                r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL);
                                 if (r < 0)
                                         return r;
                         }
                 } else {
+                        bool found = false;
 
-                        j = hashmap_get(z->by_key, q->keys[i]);
-                        if (j) {
-                                LIST_FOREACH(by_key, j, j) {
-                                        r = dns_answer_add(answer, j->rr);
-                                        if (r < 0)
-                                                return r;
+                        first = hashmap_get(z->by_key, q->keys[i]);
+                        LIST_FOREACH(by_key, j, first) {
+                                if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
+                                        continue;
+
+                                found = true;
+
+                                if (j->state != DNS_ZONE_ITEM_PROBING)
+                                        tentative = false;
+
+                                r = dns_answer_add(answer, j->rr);
+                                if (r < 0)
+                                        return r;
+                        }
+
+                        if (!found) {
+                                bool add_soa = false;
+
+                                first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
+                                LIST_FOREACH(by_name, j, first) {
+                                        if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
+                                                continue;
+
+                                        if (j->state != DNS_ZONE_ITEM_PROBING)
+                                                tentative = false;
+
+                                        add_soa = true;
                                 }
-                        } else {
-                                if (hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]))) {
+
+                                if (add_soa) {
                                         r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL);
                                         if (r < 0)
                                                 return r;
@@ -306,5 +482,167 @@ int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswe
         *ret_soa = soa;
         soa = NULL;
 
+        if (ret_tentative)
+                *ret_tentative = tentative;
+
         return 1;
 }
+
+void dns_zone_item_conflict(DnsZoneItem *i) {
+        _cleanup_free_ char *pretty = NULL;
+
+        assert(i);
+
+        if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
+                return;
+
+        dns_resource_record_to_string(i->rr, &pretty);
+        log_info("Detected conflict on %s", strna(pretty));
+
+        dns_zone_item_probe_stop(i);
+
+        /* Withdraw the conflict item */
+        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)
+                manager_next_hostname(i->scope->manager);
+}
+
+void dns_zone_item_ready(DnsZoneItem *i) {
+        _cleanup_free_ char *pretty = NULL;
+
+        assert(i);
+        assert(i->probe_transaction);
+
+        if (i->block_ready > 0)
+                return;
+
+        if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING))
+                return;
+
+        if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
+                bool we_lost = false;
+
+                /* The probe got a successful reply. If we so far
+                 * weren't established we just give up. If we already
+                 * were established, and the peer has the
+                 * lexicographically larger IP address we continue
+                 * and defend it. */
+
+                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 {
+                        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)
+                                log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
+                }
+
+                if (we_lost) {
+                        dns_zone_item_conflict(i);
+                        return;
+                }
+
+                log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
+        }
+
+        dns_resource_record_to_string(i->rr, &pretty);
+        log_debug("Record %s successfully probed.", strna(pretty));
+
+        dns_zone_item_probe_stop(i);
+        i->state = DNS_ZONE_ITEM_ESTABLISHED;
+}
+
+static int dns_zone_item_verify(DnsZoneItem *i) {
+        _cleanup_free_ char *pretty = NULL;
+        int r;
+
+        assert(i);
+
+        if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
+                return 0;
+
+        dns_resource_record_to_string(i->rr, &pretty);
+        log_debug("Verifying RR %s", strna(pretty));
+
+        i->state = DNS_ZONE_ITEM_VERIFYING;
+        r = dns_zone_item_probe_start(i);
+        if (r < 0) {
+                log_error("Failed to start probing for verifying RR: %s", strerror(-r));
+                i->state = DNS_ZONE_ITEM_ESTABLISHED;
+                return r;
+        }
+
+        return 0;
+}
+
+int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
+        DnsZoneItem *i, *first;
+        int c = 0;
+
+        assert(zone);
+        assert(rr);
+
+        /* This checks whether a response RR we received from somebody
+         * else is one that we actually thought was uniquely ours. If
+         * 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));
+        if (!first)
+                return 0;
+
+        /* No conflict if we have the exact same RR */
+        if (dns_zone_get(zone, rr))
+                return 0;
+
+        /* OK, somebody else has RRs for the same name. Yuck! Let's
+         * start probing again */
+
+        LIST_FOREACH(by_name, i, first) {
+                if (dns_resource_record_equal(i->rr, rr))
+                        continue;
+
+                dns_zone_item_verify(i);
+                c++;
+        }
+
+        return c;
+}
+
+int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
+        DnsZoneItem *i, *first;
+        int c = 0;
+
+        assert(zone);
+
+        /* 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));
+        if (!first)
+                return 0;
+
+        LIST_FOREACH(by_name, i, first) {
+                dns_zone_item_verify(i);
+                c++;
+        }
+
+        return c;
+}
+
+void dns_zone_verify_all(DnsZone *zone) {
+        DnsZoneItem *i;
+        Iterator iterator;
+
+        assert(zone);
+
+        HASHMAP_FOREACH(i, zone->by_key, iterator) {
+                DnsZoneItem *j;
+
+                LIST_FOREACH(by_key, j, i)
+                        dns_zone_item_verify(j);
+        }
+}