2 This file is part of systemd.
4 Copyright 2014 Lennart Poettering
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 #include "alloc-util.h"
21 #include "dns-domain.h"
23 #include "resolved-dns-packet.h"
24 #include "resolved-dns-zone.h"
25 #include "string-util.h"
27 /* Never allow more than 1K entries */
30 void dns_zone_item_probe_stop(DnsZoneItem
*i
) {
34 if (!i
->probe_transaction
)
37 t
= i
->probe_transaction
;
38 i
->probe_transaction
= NULL
;
40 set_remove(t
->notify_zone_items
, i
);
41 set_remove(t
->notify_zone_items_done
, 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 z
->by_key
= hashmap_free(z
->by_key
);
94 z
->by_name
= hashmap_free(z
->by_name
);
97 static DnsZoneItem
* dns_zone_get(DnsZone
*z
, DnsResourceRecord
*rr
) {
103 LIST_FOREACH(by_key
, i
, hashmap_get(z
->by_key
, rr
->key
))
104 if (dns_resource_record_equal(i
->rr
, rr
) > 0)
110 void dns_zone_remove_rr(DnsZone
*z
, DnsResourceRecord
*rr
) {
116 i
= dns_zone_get(z
, rr
);
118 dns_zone_item_remove_and_free(z
, i
);
121 static int dns_zone_init(DnsZone
*z
) {
126 r
= hashmap_ensure_allocated(&z
->by_key
, &dns_resource_key_hash_ops
);
130 r
= hashmap_ensure_allocated(&z
->by_name
, &dns_name_hash_ops
);
137 static int dns_zone_link_item(DnsZone
*z
, DnsZoneItem
*i
) {
141 first
= hashmap_get(z
->by_key
, i
->rr
->key
);
143 LIST_PREPEND(by_key
, first
, i
);
144 assert_se(hashmap_replace(z
->by_key
, first
->rr
->key
, first
) >= 0);
146 r
= hashmap_put(z
->by_key
, i
->rr
->key
, i
);
151 first
= hashmap_get(z
->by_name
, dns_resource_key_name(i
->rr
->key
));
153 LIST_PREPEND(by_name
, first
, i
);
154 assert_se(hashmap_replace(z
->by_name
, dns_resource_key_name(first
->rr
->key
), first
) >= 0);
156 r
= hashmap_put(z
->by_name
, dns_resource_key_name(i
->rr
->key
), i
);
164 static int dns_zone_item_probe_start(DnsZoneItem
*i
) {
170 if (i
->probe_transaction
)
173 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);
175 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*key
= NULL
;
177 key
= dns_resource_key_new(i
->rr
->key
->class, DNS_TYPE_ANY
, dns_resource_key_name(i
->rr
->key
));
181 r
= dns_transaction_new(&t
, i
->scope
, key
);
186 r
= set_ensure_allocated(&t
->notify_zone_items
, NULL
);
190 r
= set_ensure_allocated(&t
->notify_zone_items_done
, NULL
);
194 r
= set_put(t
->notify_zone_items
, i
);
198 i
->probe_transaction
= t
;
201 if (t
->state
== DNS_TRANSACTION_NULL
) {
204 r
= dns_transaction_go(t
);
208 dns_zone_item_probe_stop(i
);
213 dns_zone_item_notify(i
);
217 dns_transaction_gc(t
);
221 int dns_zone_put(DnsZone
*z
, DnsScope
*s
, DnsResourceRecord
*rr
, bool probe
) {
222 _cleanup_(dns_zone_item_freep
) DnsZoneItem
*i
= NULL
;
223 DnsZoneItem
*existing
;
230 if (dns_class_is_pseudo(rr
->key
->class))
232 if (dns_type_is_pseudo(rr
->key
->type
))
235 existing
= dns_zone_get(z
, rr
);
239 r
= dns_zone_init(z
);
243 i
= new0(DnsZoneItem
, 1);
248 i
->rr
= dns_resource_record_ref(rr
);
249 i
->probing_enabled
= probe
;
251 r
= dns_zone_link_item(z
, i
);
256 DnsZoneItem
*first
, *j
;
257 bool established
= false;
259 /* Check if there's already an RR with the same name
260 * established. If so, it has been probed already, and
261 * we don't ned to probe again. */
263 LIST_FIND_HEAD(by_name
, i
, first
);
264 LIST_FOREACH(by_name
, j
, first
) {
268 if (j
->state
== DNS_ZONE_ITEM_ESTABLISHED
)
273 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
275 i
->state
= DNS_ZONE_ITEM_PROBING
;
277 r
= dns_zone_item_probe_start(i
);
279 dns_zone_item_remove_and_free(z
, i
);
285 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
291 int dns_zone_lookup(DnsZone
*z
, DnsResourceKey
*key
, int ifindex
, DnsAnswer
**ret_answer
, DnsAnswer
**ret_soa
, bool *ret_tentative
) {
292 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
293 unsigned n_answer
= 0;
294 DnsZoneItem
*j
, *first
;
295 bool tentative
= true, need_soa
= false;
298 /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the
299 * ifindex field in the answer with it */
305 /* First iteration, count what we have */
307 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
308 bool found
= false, added
= false;
311 /* If this is a generic match, then we have to
312 * go through the list by the name and look
313 * for everything manually */
315 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
316 LIST_FOREACH(by_name
, j
, first
) {
317 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
322 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
338 /* If this is a specific match, then look for
339 * the right key immediately */
341 first
= hashmap_get(z
->by_key
, key
);
342 LIST_FOREACH(by_key
, j
, first
) {
343 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
351 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
352 LIST_FOREACH(by_name
, j
, first
) {
353 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
362 if (n_answer
<= 0 && !need_soa
)
366 answer
= dns_answer_new(n_answer
);
372 soa
= dns_answer_new(1);
377 /* Second iteration, actually add the RRs to the answers */
378 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
379 bool found
= false, added
= false;
382 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
383 LIST_FOREACH(by_name
, j
, first
) {
384 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
389 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
392 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
396 r
= dns_answer_add(answer
, j
->rr
, ifindex
, DNS_ANSWER_AUTHENTICATED
);
404 if (found
&& !added
) {
405 r
= dns_answer_add_soa(soa
, dns_resource_key_name(key
), LLMNR_DEFAULT_TTL
, ifindex
);
412 first
= hashmap_get(z
->by_key
, key
);
413 LIST_FOREACH(by_key
, j
, first
) {
414 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
419 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
422 r
= dns_answer_add(answer
, j
->rr
, ifindex
, DNS_ANSWER_AUTHENTICATED
);
428 bool add_soa
= false;
430 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
431 LIST_FOREACH(by_name
, j
, first
) {
432 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
435 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
442 r
= dns_answer_add_soa(soa
, dns_resource_key_name(key
), LLMNR_DEFAULT_TTL
, ifindex
);
449 /* If the caller sets ret_tentative to NULL, then use this as
450 * indication to not return tentative entries */
452 if (!ret_tentative
&& tentative
)
455 *ret_answer
= answer
;
464 *ret_tentative
= tentative
;
475 *ret_tentative
= false;
480 void dns_zone_item_conflict(DnsZoneItem
*i
) {
483 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_VERIFYING
, DNS_ZONE_ITEM_ESTABLISHED
))
486 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i
->rr
)));
488 dns_zone_item_probe_stop(i
);
490 /* Withdraw the conflict item */
491 i
->state
= DNS_ZONE_ITEM_WITHDRAWN
;
493 /* Maybe change the hostname */
494 if (manager_is_own_hostname(i
->scope
->manager
, dns_resource_key_name(i
->rr
->key
)) > 0)
495 manager_next_hostname(i
->scope
->manager
);
498 void dns_zone_item_notify(DnsZoneItem
*i
) {
500 assert(i
->probe_transaction
);
502 if (i
->block_ready
> 0)
505 if (IN_SET(i
->probe_transaction
->state
, DNS_TRANSACTION_NULL
, DNS_TRANSACTION_PENDING
, DNS_TRANSACTION_VALIDATING
))
508 if (i
->probe_transaction
->state
== DNS_TRANSACTION_SUCCESS
) {
509 bool we_lost
= false;
511 /* The probe got a successful reply. If we so far
512 * weren't established we just give up. If we already
513 * were established, and the peer has the
514 * lexicographically larger IP address we continue
517 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
)) {
518 log_debug("Got a successful probe for not yet established RR, we lost.");
521 assert(i
->probe_transaction
->received
);
522 we_lost
= memcmp(&i
->probe_transaction
->received
->sender
, &i
->probe_transaction
->received
->destination
, FAMILY_ADDRESS_SIZE(i
->probe_transaction
->received
->family
)) < 0;
524 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
528 dns_zone_item_conflict(i
);
532 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
535 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i
->rr
)));
537 dns_zone_item_probe_stop(i
);
538 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
541 static int dns_zone_item_verify(DnsZoneItem
*i
) {
546 if (i
->state
!= DNS_ZONE_ITEM_ESTABLISHED
)
549 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i
->rr
)));
551 i
->state
= DNS_ZONE_ITEM_VERIFYING
;
552 r
= dns_zone_item_probe_start(i
);
554 log_error_errno(r
, "Failed to start probing for verifying RR: %m");
555 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
562 int dns_zone_check_conflicts(DnsZone
*zone
, DnsResourceRecord
*rr
) {
563 DnsZoneItem
*i
, *first
;
569 /* This checks whether a response RR we received from somebody
570 * else is one that we actually thought was uniquely ours. If
571 * so, we'll verify our RRs. */
573 /* No conflict if we don't have the name at all. */
574 first
= hashmap_get(zone
->by_name
, dns_resource_key_name(rr
->key
));
578 /* No conflict if we have the exact same RR */
579 if (dns_zone_get(zone
, rr
))
582 /* OK, somebody else has RRs for the same name. Yuck! Let's
583 * start probing again */
585 LIST_FOREACH(by_name
, i
, first
) {
586 if (dns_resource_record_equal(i
->rr
, rr
))
589 dns_zone_item_verify(i
);
596 int dns_zone_verify_conflicts(DnsZone
*zone
, DnsResourceKey
*key
) {
597 DnsZoneItem
*i
, *first
;
602 /* Somebody else notified us about a possible conflict. Let's
603 * verify if that's true. */
605 first
= hashmap_get(zone
->by_name
, dns_resource_key_name(key
));
609 LIST_FOREACH(by_name
, i
, first
) {
610 dns_zone_item_verify(i
);
617 void dns_zone_verify_all(DnsZone
*zone
) {
623 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
626 LIST_FOREACH(by_key
, j
, i
)
627 dns_zone_item_verify(j
);
631 void dns_zone_dump(DnsZone
*zone
, FILE *f
) {
641 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
644 LIST_FOREACH(by_key
, j
, i
) {
647 t
= dns_resource_record_to_string(j
->rr
);
660 bool dns_zone_is_empty(DnsZone
*zone
) {
664 return hashmap_isempty(zone
->by_key
);