1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2014 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 #include "alloc-util.h"
22 #include "dns-domain.h"
24 #include "resolved-dns-packet.h"
25 #include "resolved-dns-zone.h"
26 #include "string-util.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
->notify_zone_items
, i
);
42 set_remove(t
->notify_zone_items_done
, i
);
43 dns_transaction_gc(t
);
46 static void dns_zone_item_free(DnsZoneItem
*i
) {
50 dns_zone_item_probe_stop(i
);
51 dns_resource_record_unref(i
->rr
);
56 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem
*, dns_zone_item_free
);
58 static void dns_zone_item_remove_and_free(DnsZone
*z
, DnsZoneItem
*i
) {
66 first
= hashmap_get(z
->by_key
, i
->rr
->key
);
67 LIST_REMOVE(by_key
, first
, i
);
69 assert_se(hashmap_replace(z
->by_key
, first
->rr
->key
, first
) >= 0);
71 hashmap_remove(z
->by_key
, i
->rr
->key
);
73 first
= hashmap_get(z
->by_name
, dns_resource_key_name(i
->rr
->key
));
74 LIST_REMOVE(by_name
, first
, i
);
76 assert_se(hashmap_replace(z
->by_name
, dns_resource_key_name(first
->rr
->key
), first
) >= 0);
78 hashmap_remove(z
->by_name
, dns_resource_key_name(i
->rr
->key
));
80 dns_zone_item_free(i
);
83 void dns_zone_flush(DnsZone
*z
) {
88 while ((i
= hashmap_first(z
->by_key
)))
89 dns_zone_item_remove_and_free(z
, i
);
91 assert(hashmap_size(z
->by_key
) == 0);
92 assert(hashmap_size(z
->by_name
) == 0);
94 z
->by_key
= hashmap_free(z
->by_key
);
95 z
->by_name
= hashmap_free(z
->by_name
);
98 static DnsZoneItem
* dns_zone_get(DnsZone
*z
, DnsResourceRecord
*rr
) {
104 LIST_FOREACH(by_key
, i
, hashmap_get(z
->by_key
, rr
->key
))
105 if (dns_resource_record_equal(i
->rr
, rr
) > 0)
111 void dns_zone_remove_rr(DnsZone
*z
, DnsResourceRecord
*rr
) {
117 i
= dns_zone_get(z
, rr
);
119 dns_zone_item_remove_and_free(z
, i
);
122 int dns_zone_remove_rrs_by_key(DnsZone
*z
, DnsResourceKey
*key
) {
123 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
124 DnsResourceRecord
*rr
;
128 r
= dns_zone_lookup(z
, key
, 0, &answer
, &soa
, &tentative
);
132 DNS_ANSWER_FOREACH(rr
, answer
)
133 dns_zone_remove_rr(z
, rr
);
138 static int dns_zone_init(DnsZone
*z
) {
143 r
= hashmap_ensure_allocated(&z
->by_key
, &dns_resource_key_hash_ops
);
147 r
= hashmap_ensure_allocated(&z
->by_name
, &dns_name_hash_ops
);
154 static int dns_zone_link_item(DnsZone
*z
, DnsZoneItem
*i
) {
158 first
= hashmap_get(z
->by_key
, i
->rr
->key
);
160 LIST_PREPEND(by_key
, first
, i
);
161 assert_se(hashmap_replace(z
->by_key
, first
->rr
->key
, first
) >= 0);
163 r
= hashmap_put(z
->by_key
, i
->rr
->key
, i
);
168 first
= hashmap_get(z
->by_name
, dns_resource_key_name(i
->rr
->key
));
170 LIST_PREPEND(by_name
, first
, i
);
171 assert_se(hashmap_replace(z
->by_name
, dns_resource_key_name(first
->rr
->key
), first
) >= 0);
173 r
= hashmap_put(z
->by_name
, dns_resource_key_name(i
->rr
->key
), i
);
181 static int dns_zone_item_probe_start(DnsZoneItem
*i
) {
187 if (i
->probe_transaction
)
190 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);
192 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*key
= NULL
;
194 key
= dns_resource_key_new(i
->rr
->key
->class, DNS_TYPE_ANY
, dns_resource_key_name(i
->rr
->key
));
198 r
= dns_transaction_new(&t
, i
->scope
, key
);
203 r
= set_ensure_allocated(&t
->notify_zone_items
, NULL
);
207 r
= set_ensure_allocated(&t
->notify_zone_items_done
, NULL
);
211 r
= set_put(t
->notify_zone_items
, i
);
215 i
->probe_transaction
= t
;
218 if (t
->state
== DNS_TRANSACTION_NULL
) {
221 r
= dns_transaction_go(t
);
225 dns_zone_item_probe_stop(i
);
230 dns_zone_item_notify(i
);
234 dns_transaction_gc(t
);
238 int dns_zone_put(DnsZone
*z
, DnsScope
*s
, DnsResourceRecord
*rr
, bool probe
) {
239 _cleanup_(dns_zone_item_freep
) DnsZoneItem
*i
= NULL
;
240 DnsZoneItem
*existing
;
247 if (dns_class_is_pseudo(rr
->key
->class))
249 if (dns_type_is_pseudo(rr
->key
->type
))
252 existing
= dns_zone_get(z
, rr
);
256 r
= dns_zone_init(z
);
260 i
= new0(DnsZoneItem
, 1);
265 i
->rr
= dns_resource_record_ref(rr
);
266 i
->probing_enabled
= probe
;
268 r
= dns_zone_link_item(z
, i
);
273 DnsZoneItem
*first
, *j
;
274 bool established
= false;
276 /* Check if there's already an RR with the same name
277 * established. If so, it has been probed already, and
278 * we don't ned to probe again. */
280 LIST_FIND_HEAD(by_name
, i
, first
);
281 LIST_FOREACH(by_name
, j
, first
) {
285 if (j
->state
== DNS_ZONE_ITEM_ESTABLISHED
)
290 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
292 i
->state
= DNS_ZONE_ITEM_PROBING
;
294 r
= dns_zone_item_probe_start(i
);
296 dns_zone_item_remove_and_free(z
, i
);
302 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
308 int dns_zone_lookup(DnsZone
*z
, DnsResourceKey
*key
, int ifindex
, DnsAnswer
**ret_answer
, DnsAnswer
**ret_soa
, bool *ret_tentative
) {
309 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
310 unsigned n_answer
= 0;
311 DnsZoneItem
*j
, *first
;
312 bool tentative
= true, need_soa
= false;
315 /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the
316 * ifindex field in the answer with it */
322 /* First iteration, count what we have */
324 if (key
->type
== DNS_TYPE_ANY
|| key
->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(key
));
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(key
, j
->rr
, NULL
);
355 /* If this is a specific match, then look for
356 * the right key immediately */
358 first
= hashmap_get(z
->by_key
, key
);
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(key
));
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
))
379 if (n_answer
<= 0 && !need_soa
)
383 answer
= dns_answer_new(n_answer
);
389 soa
= dns_answer_new(1);
394 /* Second iteration, actually add the RRs to the answers */
395 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
396 bool found
= false, added
= false;
399 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
400 LIST_FOREACH(by_name
, j
, first
) {
401 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
406 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
409 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
413 r
= dns_answer_add(answer
, j
->rr
, ifindex
, DNS_ANSWER_AUTHENTICATED
);
421 if (found
&& !added
) {
422 r
= dns_answer_add_soa(soa
, dns_resource_key_name(key
), LLMNR_DEFAULT_TTL
, ifindex
);
429 first
= hashmap_get(z
->by_key
, key
);
430 LIST_FOREACH(by_key
, j
, first
) {
431 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
436 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
439 r
= dns_answer_add(answer
, j
->rr
, ifindex
, DNS_ANSWER_AUTHENTICATED
);
445 bool add_soa
= false;
447 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
448 LIST_FOREACH(by_name
, j
, first
) {
449 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
452 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
459 r
= dns_answer_add_soa(soa
, dns_resource_key_name(key
), LLMNR_DEFAULT_TTL
, ifindex
);
466 /* If the caller sets ret_tentative to NULL, then use this as
467 * indication to not return tentative entries */
469 if (!ret_tentative
&& tentative
)
472 *ret_answer
= answer
;
481 *ret_tentative
= tentative
;
492 *ret_tentative
= false;
497 void dns_zone_item_conflict(DnsZoneItem
*i
) {
500 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_VERIFYING
, DNS_ZONE_ITEM_ESTABLISHED
))
503 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i
->rr
)));
505 dns_zone_item_probe_stop(i
);
507 /* Withdraw the conflict item */
508 i
->state
= DNS_ZONE_ITEM_WITHDRAWN
;
510 /* Maybe change the hostname */
511 if (manager_is_own_hostname(i
->scope
->manager
, dns_resource_key_name(i
->rr
->key
)) > 0)
512 manager_next_hostname(i
->scope
->manager
);
515 void dns_zone_item_notify(DnsZoneItem
*i
) {
517 assert(i
->probe_transaction
);
519 if (i
->block_ready
> 0)
522 if (IN_SET(i
->probe_transaction
->state
, DNS_TRANSACTION_NULL
, DNS_TRANSACTION_PENDING
, DNS_TRANSACTION_VALIDATING
))
525 if (i
->probe_transaction
->state
== DNS_TRANSACTION_SUCCESS
) {
526 bool we_lost
= false;
528 /* The probe got a successful reply. If we so far
529 * weren't established we just give up. If we already
530 * were established, and the peer has the
531 * lexicographically larger IP address we continue
534 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
)) {
535 log_debug("Got a successful probe for not yet established RR, we lost.");
538 assert(i
->probe_transaction
->received
);
539 we_lost
= memcmp(&i
->probe_transaction
->received
->sender
, &i
->probe_transaction
->received
->destination
, FAMILY_ADDRESS_SIZE(i
->probe_transaction
->received
->family
)) < 0;
541 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
545 dns_zone_item_conflict(i
);
549 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
552 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i
->rr
)));
554 dns_zone_item_probe_stop(i
);
555 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
558 static int dns_zone_item_verify(DnsZoneItem
*i
) {
563 if (i
->state
!= DNS_ZONE_ITEM_ESTABLISHED
)
566 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i
->rr
)));
568 i
->state
= DNS_ZONE_ITEM_VERIFYING
;
569 r
= dns_zone_item_probe_start(i
);
571 log_error_errno(r
, "Failed to start probing for verifying RR: %m");
572 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
579 int dns_zone_check_conflicts(DnsZone
*zone
, DnsResourceRecord
*rr
) {
580 DnsZoneItem
*i
, *first
;
586 /* This checks whether a response RR we received from somebody
587 * else is one that we actually thought was uniquely ours. If
588 * so, we'll verify our RRs. */
590 /* No conflict if we don't have the name at all. */
591 first
= hashmap_get(zone
->by_name
, dns_resource_key_name(rr
->key
));
595 /* No conflict if we have the exact same RR */
596 if (dns_zone_get(zone
, rr
))
599 /* OK, somebody else has RRs for the same name. Yuck! Let's
600 * start probing again */
602 LIST_FOREACH(by_name
, i
, first
) {
603 if (dns_resource_record_equal(i
->rr
, rr
))
606 dns_zone_item_verify(i
);
613 int dns_zone_verify_conflicts(DnsZone
*zone
, DnsResourceKey
*key
) {
614 DnsZoneItem
*i
, *first
;
619 /* Somebody else notified us about a possible conflict. Let's
620 * verify if that's true. */
622 first
= hashmap_get(zone
->by_name
, dns_resource_key_name(key
));
626 LIST_FOREACH(by_name
, i
, first
) {
627 dns_zone_item_verify(i
);
634 void dns_zone_verify_all(DnsZone
*zone
) {
640 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
643 LIST_FOREACH(by_key
, j
, i
)
644 dns_zone_item_verify(j
);
648 void dns_zone_dump(DnsZone
*zone
, FILE *f
) {
658 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
661 LIST_FOREACH(by_key
, j
, i
) {
664 t
= dns_resource_record_to_string(j
->rr
);
677 bool dns_zone_is_empty(DnsZone
*zone
) {
681 return hashmap_isempty(zone
->by_key
);
684 bool dns_zone_contains_name(DnsZone
*z
, const char *name
) {
685 DnsZoneItem
*i
, *first
;
687 first
= hashmap_get(z
->by_name
, name
);
691 LIST_FOREACH(by_name
, i
, first
) {
692 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))