1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
24 #include "resolved-dns-zone.h"
25 #include "resolved-dns-domain.h"
26 #include "resolved-dns-packet.h"
28 /* Never allow more than 1K entries */
31 void dns_zone_item_probe_stop(DnsZoneItem *i) {
35 if (!i->probe_transaction)
38 t = i->probe_transaction;
39 i->probe_transaction = NULL;
41 set_remove(t->zone_items, i);
42 dns_transaction_gc(t);
45 static void dns_zone_item_free(DnsZoneItem *i) {
49 dns_zone_item_probe_stop(i);
50 dns_resource_record_unref(i->rr);
55 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
57 static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) {
65 first = hashmap_get(z->by_key, i->rr->key);
66 LIST_REMOVE(by_key, first, i);
68 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
70 hashmap_remove(z->by_key, i->rr->key);
72 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
73 LIST_REMOVE(by_name, first, i);
75 assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0);
77 hashmap_remove(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
79 dns_zone_item_free(i);
82 void dns_zone_flush(DnsZone *z) {
87 while ((i = hashmap_first(z->by_key)))
88 dns_zone_item_remove_and_free(z, i);
90 assert(hashmap_size(z->by_key) == 0);
91 assert(hashmap_size(z->by_name) == 0);
93 hashmap_free(z->by_key);
96 hashmap_free(z->by_name);
100 static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
106 LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key))
107 if (dns_resource_record_equal(i->rr, rr) > 0)
113 void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
119 i = dns_zone_get(z, rr);
121 dns_zone_item_remove_and_free(z, i);
124 static int dns_zone_init(DnsZone *z) {
129 r = hashmap_ensure_allocated(&z->by_key, dns_resource_key_hash_func, dns_resource_key_compare_func);
133 r = hashmap_ensure_allocated(&z->by_name, dns_name_hash_func, dns_name_compare_func);
140 static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
144 first = hashmap_get(z->by_key, i->rr->key);
146 LIST_PREPEND(by_key, first, i);
147 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
149 r = hashmap_put(z->by_key, i->rr->key, i);
154 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
156 LIST_PREPEND(by_name, first, i);
157 assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0);
159 r = hashmap_put(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key), i);
167 static int dns_zone_item_probe_start(DnsZoneItem *i) {
168 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
169 _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
175 if (i->probe_transaction)
178 key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key));
182 question = dns_question_new(1);
186 r = dns_question_add(question, key);
190 t = dns_scope_find_transaction(i->scope, question, false);
192 r = dns_transaction_new(&t, i->scope, question);
197 r = set_ensure_allocated(&t->zone_items, NULL, NULL);
201 r = set_put(t->zone_items, i);
205 i->probe_transaction = t;
207 if (t->state == DNS_TRANSACTION_NULL) {
210 r = dns_transaction_go(t);
214 dns_zone_item_probe_stop(i);
219 dns_zone_item_ready(i);
224 dns_transaction_gc(t);
228 int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
229 _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
230 DnsZoneItem *existing;
237 if (rr->key->class == DNS_CLASS_ANY)
239 if (rr->key->type == DNS_TYPE_ANY)
242 existing = dns_zone_get(z, rr);
246 r = dns_zone_init(z);
250 i = new0(DnsZoneItem, 1);
255 i->rr = dns_resource_record_ref(rr);
256 i->probing_enabled = probe;
258 r = dns_zone_link_item(z, i);
263 DnsZoneItem *first, *j;
264 bool established = false;
266 /* Check if there's already an RR with the same name
267 * established. If so, it has been probed already, and
268 * we don't ned to probe again. */
270 LIST_FIND_HEAD(by_name, i, first);
271 LIST_FOREACH(by_name, j, first) {
275 if (j->state == DNS_ZONE_ITEM_ESTABLISHED)
280 i->state = DNS_ZONE_ITEM_ESTABLISHED;
282 i->state = DNS_ZONE_ITEM_PROBING;
284 r = dns_zone_item_probe_start(i);
286 dns_zone_item_remove_and_free(z, i);
292 i->state = DNS_ZONE_ITEM_ESTABLISHED;
298 int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
299 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
300 unsigned i, n_answer = 0, n_soa = 0;
301 bool tentative = true;
309 if (q->n_keys <= 0) {
314 *ret_tentative = false;
319 /* First iteration, count what we have */
320 for (i = 0; i < q->n_keys; i++) {
321 DnsZoneItem *j, *first;
323 if (q->keys[i]->type == DNS_TYPE_ANY ||
324 q->keys[i]->class == DNS_CLASS_ANY) {
325 bool found = false, added = false;
328 /* If this is a generic match, then we have to
329 * go through the list by the name and look
330 * for everything manually */
332 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
333 LIST_FOREACH(by_name, j, first) {
334 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
339 k = dns_resource_key_match_rr(q->keys[i], j->rr);
355 /* If this is a specific match, then look for
356 * the right key immediately */
358 first = hashmap_get(z->by_key, q->keys[i]);
359 LIST_FOREACH(by_key, j, first) {
360 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
368 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
369 LIST_FOREACH(by_name, j, first) {
370 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
380 if (n_answer <= 0 && n_soa <= 0) {
385 *ret_tentative = false;
391 answer = dns_answer_new(n_answer);
397 soa = dns_answer_new(n_soa);
402 /* Second iteration, actually add the RRs to the answers */
403 for (i = 0; i < q->n_keys; i++) {
404 DnsZoneItem *j, *first;
406 if (q->keys[i]->type == DNS_TYPE_ANY ||
407 q->keys[i]->class == DNS_CLASS_ANY) {
408 bool found = false, added = false;
411 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
412 LIST_FOREACH(by_name, j, first) {
413 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
418 if (j->state != DNS_ZONE_ITEM_PROBING)
421 k = dns_resource_key_match_rr(q->keys[i], j->rr);
425 r = dns_answer_add(answer, j->rr);
433 if (found && !added) {
434 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL);
441 first = hashmap_get(z->by_key, q->keys[i]);
442 LIST_FOREACH(by_key, j, first) {
443 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
448 if (j->state != DNS_ZONE_ITEM_PROBING)
451 r = dns_answer_add(answer, j->rr);
457 bool add_soa = false;
459 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
460 LIST_FOREACH(by_name, j, first) {
461 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
464 if (j->state != DNS_ZONE_ITEM_PROBING)
471 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL);
479 *ret_answer = answer;
486 *ret_tentative = tentative;
491 void dns_zone_item_conflict(DnsZoneItem *i) {
492 _cleanup_free_ char *pretty = NULL;
496 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
499 dns_resource_record_to_string(i->rr, &pretty);
500 log_info("Detected conflict on %s", strna(pretty));
502 dns_zone_item_probe_stop(i);
504 /* Withdraw the conflict item */
505 i->state = DNS_ZONE_ITEM_WITHDRAWN;
507 /* Maybe change the hostname */
508 if (dns_name_equal(i->scope->manager->hostname, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0)
509 manager_next_hostname(i->scope->manager);
512 void dns_zone_item_ready(DnsZoneItem *i) {
513 _cleanup_free_ char *pretty = NULL;
516 assert(i->probe_transaction);
518 if (i->block_ready > 0)
521 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING))
524 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
525 bool we_lost = false;
527 /* The probe got a successful reply. If we so far
528 * weren't established we just give up. If we already
529 * were established, and the peer has the
530 * lexicographically larger IP address we continue
533 if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
534 log_debug("Got a successful probe for not yet established RR, we lost.");
537 assert(i->probe_transaction->received);
538 we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
540 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
544 dns_zone_item_conflict(i);
548 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
551 dns_resource_record_to_string(i->rr, &pretty);
552 log_debug("Record %s successfully probed.", strna(pretty));
554 dns_zone_item_probe_stop(i);
555 i->state = DNS_ZONE_ITEM_ESTABLISHED;
558 static int dns_zone_item_verify(DnsZoneItem *i) {
559 _cleanup_free_ char *pretty = NULL;
564 if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
567 dns_resource_record_to_string(i->rr, &pretty);
568 log_debug("Verifying RR %s", strna(pretty));
570 i->state = DNS_ZONE_ITEM_VERIFYING;
571 r = dns_zone_item_probe_start(i);
573 log_error("Failed to start probing for verifying RR: %s", strerror(-r));
574 i->state = DNS_ZONE_ITEM_ESTABLISHED;
581 int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
582 DnsZoneItem *i, *first;
588 /* This checks whether a response RR we received from somebody
589 * else is one that we actually thought was uniquely ours. If
590 * so, we'll verify our RRs. */
592 /* No conflict if we don't have the name at all. */
593 first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(rr->key));
597 /* No conflict if we have the exact same RR */
598 if (dns_zone_get(zone, rr))
601 /* OK, somebody else has RRs for the same name. Yuck! Let's
602 * start probing again */
604 LIST_FOREACH(by_name, i, first) {
605 if (dns_resource_record_equal(i->rr, rr))
608 dns_zone_item_verify(i);
615 int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
616 DnsZoneItem *i, *first;
621 /* Somebody else notified us about a possible conflict. Let's
622 * verify if that's true. */
624 first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(key));
628 LIST_FOREACH(by_name, i, first) {
629 dns_zone_item_verify(i);
636 void dns_zone_verify_all(DnsZone *zone) {
642 HASHMAP_FOREACH(i, zone->by_key, iterator) {
645 LIST_FOREACH(by_key, j, i)
646 dns_zone_item_verify(j);