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 "resolved-dnssd.h"
27 #include "string-util.h"
29 /* Never allow more than 1K entries */
32 void dns_zone_item_probe_stop(DnsZoneItem
*i
) {
36 if (!i
->probe_transaction
)
39 t
= i
->probe_transaction
;
40 i
->probe_transaction
= NULL
;
42 set_remove(t
->notify_zone_items
, i
);
43 set_remove(t
->notify_zone_items_done
, i
);
44 dns_transaction_gc(t
);
47 static void dns_zone_item_free(DnsZoneItem
*i
) {
51 dns_zone_item_probe_stop(i
);
52 dns_resource_record_unref(i
->rr
);
57 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem
*, dns_zone_item_free
);
59 static void dns_zone_item_remove_and_free(DnsZone
*z
, DnsZoneItem
*i
) {
67 first
= hashmap_get(z
->by_key
, i
->rr
->key
);
68 LIST_REMOVE(by_key
, first
, i
);
70 assert_se(hashmap_replace(z
->by_key
, first
->rr
->key
, first
) >= 0);
72 hashmap_remove(z
->by_key
, i
->rr
->key
);
74 first
= hashmap_get(z
->by_name
, dns_resource_key_name(i
->rr
->key
));
75 LIST_REMOVE(by_name
, first
, i
);
77 assert_se(hashmap_replace(z
->by_name
, dns_resource_key_name(first
->rr
->key
), first
) >= 0);
79 hashmap_remove(z
->by_name
, dns_resource_key_name(i
->rr
->key
));
81 dns_zone_item_free(i
);
84 void dns_zone_flush(DnsZone
*z
) {
89 while ((i
= hashmap_first(z
->by_key
)))
90 dns_zone_item_remove_and_free(z
, i
);
92 assert(hashmap_size(z
->by_key
) == 0);
93 assert(hashmap_size(z
->by_name
) == 0);
95 z
->by_key
= hashmap_free(z
->by_key
);
96 z
->by_name
= hashmap_free(z
->by_name
);
99 DnsZoneItem
* dns_zone_get(DnsZone
*z
, DnsResourceRecord
*rr
) {
105 LIST_FOREACH(by_key
, i
, hashmap_get(z
->by_key
, rr
->key
))
106 if (dns_resource_record_equal(i
->rr
, rr
) > 0)
112 void dns_zone_remove_rr(DnsZone
*z
, DnsResourceRecord
*rr
) {
118 i
= dns_zone_get(z
, rr
);
120 dns_zone_item_remove_and_free(z
, i
);
123 int dns_zone_remove_rrs_by_key(DnsZone
*z
, DnsResourceKey
*key
) {
124 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
125 DnsResourceRecord
*rr
;
129 r
= dns_zone_lookup(z
, key
, 0, &answer
, &soa
, &tentative
);
133 DNS_ANSWER_FOREACH(rr
, answer
)
134 dns_zone_remove_rr(z
, rr
);
139 static int dns_zone_init(DnsZone
*z
) {
144 r
= hashmap_ensure_allocated(&z
->by_key
, &dns_resource_key_hash_ops
);
148 r
= hashmap_ensure_allocated(&z
->by_name
, &dns_name_hash_ops
);
155 static int dns_zone_link_item(DnsZone
*z
, DnsZoneItem
*i
) {
159 first
= hashmap_get(z
->by_key
, i
->rr
->key
);
161 LIST_PREPEND(by_key
, first
, i
);
162 assert_se(hashmap_replace(z
->by_key
, first
->rr
->key
, first
) >= 0);
164 r
= hashmap_put(z
->by_key
, i
->rr
->key
, i
);
169 first
= hashmap_get(z
->by_name
, dns_resource_key_name(i
->rr
->key
));
171 LIST_PREPEND(by_name
, first
, i
);
172 assert_se(hashmap_replace(z
->by_name
, dns_resource_key_name(first
->rr
->key
), first
) >= 0);
174 r
= hashmap_put(z
->by_name
, dns_resource_key_name(i
->rr
->key
), i
);
182 static int dns_zone_item_probe_start(DnsZoneItem
*i
) {
188 if (i
->probe_transaction
)
191 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);
193 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*key
= NULL
;
195 key
= dns_resource_key_new(i
->rr
->key
->class, DNS_TYPE_ANY
, dns_resource_key_name(i
->rr
->key
));
199 r
= dns_transaction_new(&t
, i
->scope
, key
);
204 r
= set_ensure_allocated(&t
->notify_zone_items
, NULL
);
208 r
= set_ensure_allocated(&t
->notify_zone_items_done
, NULL
);
212 r
= set_put(t
->notify_zone_items
, i
);
216 i
->probe_transaction
= t
;
219 if (t
->state
== DNS_TRANSACTION_NULL
) {
222 r
= dns_transaction_go(t
);
226 dns_zone_item_probe_stop(i
);
231 dns_zone_item_notify(i
);
235 dns_transaction_gc(t
);
239 int dns_zone_put(DnsZone
*z
, DnsScope
*s
, DnsResourceRecord
*rr
, bool probe
) {
240 _cleanup_(dns_zone_item_freep
) DnsZoneItem
*i
= NULL
;
241 DnsZoneItem
*existing
;
248 if (dns_class_is_pseudo(rr
->key
->class))
250 if (dns_type_is_pseudo(rr
->key
->type
))
253 existing
= dns_zone_get(z
, rr
);
257 r
= dns_zone_init(z
);
261 i
= new0(DnsZoneItem
, 1);
266 i
->rr
= dns_resource_record_ref(rr
);
267 i
->probing_enabled
= probe
;
269 r
= dns_zone_link_item(z
, i
);
274 DnsZoneItem
*first
, *j
;
275 bool established
= false;
277 /* Check if there's already an RR with the same name
278 * established. If so, it has been probed already, and
279 * we don't ned to probe again. */
281 LIST_FIND_HEAD(by_name
, i
, first
);
282 LIST_FOREACH(by_name
, j
, first
) {
286 if (j
->state
== DNS_ZONE_ITEM_ESTABLISHED
)
291 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
293 i
->state
= DNS_ZONE_ITEM_PROBING
;
295 r
= dns_zone_item_probe_start(i
);
297 dns_zone_item_remove_and_free(z
, i
);
303 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
309 static int dns_zone_add_authenticated_answer(DnsAnswer
*a
, DnsZoneItem
*i
, int ifindex
) {
310 DnsAnswerFlags flags
;
312 /* From RFC 6762, Section 10.2
313 * "They (the rules about when to set the cache-flush bit) apply to
314 * startup announcements as described in Section 8.3, "Announcing",
315 * and to responses generated as a result of receiving query messages."
316 * So, set the cache-flush bit for mDNS answers except for DNS-SD
317 * service enumeration PTRs described in RFC 6763, Section 4.1. */
318 if (i
->scope
->protocol
== DNS_PROTOCOL_MDNS
&&
319 !dns_resource_key_is_dnssd_ptr(i
->rr
->key
))
320 flags
= DNS_ANSWER_AUTHENTICATED
|DNS_ANSWER_CACHE_FLUSH
;
322 flags
= DNS_ANSWER_AUTHENTICATED
;
324 return dns_answer_add(a
, i
->rr
, ifindex
, flags
);
327 int dns_zone_lookup(DnsZone
*z
, DnsResourceKey
*key
, int ifindex
, DnsAnswer
**ret_answer
, DnsAnswer
**ret_soa
, bool *ret_tentative
) {
328 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
329 unsigned n_answer
= 0;
330 DnsZoneItem
*j
, *first
;
331 bool tentative
= true, need_soa
= false;
334 /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the
335 * ifindex field in the answer with it */
341 /* First iteration, count what we have */
343 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
344 bool found
= false, added
= false;
347 /* If this is a generic match, then we have to
348 * go through the list by the name and look
349 * for everything manually */
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
))
358 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
374 /* If this is a specific match, then look for
375 * the right key immediately */
377 first
= hashmap_get(z
->by_key
, key
);
378 LIST_FOREACH(by_key
, j
, first
) {
379 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
387 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
388 LIST_FOREACH(by_name
, j
, first
) {
389 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
398 if (n_answer
<= 0 && !need_soa
)
402 answer
= dns_answer_new(n_answer
);
408 soa
= dns_answer_new(1);
413 /* Second iteration, actually add the RRs to the answers */
414 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
415 bool found
= false, added
= false;
418 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
419 LIST_FOREACH(by_name
, j
, first
) {
420 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
425 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
428 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
432 r
= dns_zone_add_authenticated_answer(answer
, j
, ifindex
);
440 if (found
&& !added
) {
441 r
= dns_answer_add_soa(soa
, dns_resource_key_name(key
), LLMNR_DEFAULT_TTL
, ifindex
);
448 first
= hashmap_get(z
->by_key
, key
);
449 LIST_FOREACH(by_key
, j
, first
) {
450 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
455 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
458 r
= dns_zone_add_authenticated_answer(answer
, j
, ifindex
);
464 bool add_soa
= false;
466 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
467 LIST_FOREACH(by_name
, j
, first
) {
468 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
471 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
478 r
= dns_answer_add_soa(soa
, dns_resource_key_name(key
), LLMNR_DEFAULT_TTL
, ifindex
);
485 /* If the caller sets ret_tentative to NULL, then use this as
486 * indication to not return tentative entries */
488 if (!ret_tentative
&& tentative
)
491 *ret_answer
= answer
;
500 *ret_tentative
= tentative
;
511 *ret_tentative
= false;
516 void dns_zone_item_conflict(DnsZoneItem
*i
) {
519 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_VERIFYING
, DNS_ZONE_ITEM_ESTABLISHED
))
522 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i
->rr
)));
524 dns_zone_item_probe_stop(i
);
526 /* Withdraw the conflict item */
527 i
->state
= DNS_ZONE_ITEM_WITHDRAWN
;
529 dnssd_signal_conflict(i
->scope
->manager
, dns_resource_key_name(i
->rr
->key
));
531 /* Maybe change the hostname */
532 if (manager_is_own_hostname(i
->scope
->manager
, dns_resource_key_name(i
->rr
->key
)) > 0)
533 manager_next_hostname(i
->scope
->manager
);
536 void dns_zone_item_notify(DnsZoneItem
*i
) {
538 assert(i
->probe_transaction
);
540 if (i
->block_ready
> 0)
543 if (IN_SET(i
->probe_transaction
->state
, DNS_TRANSACTION_NULL
, DNS_TRANSACTION_PENDING
, DNS_TRANSACTION_VALIDATING
))
546 if (i
->probe_transaction
->state
== DNS_TRANSACTION_SUCCESS
) {
547 bool we_lost
= false;
549 /* The probe got a successful reply. If we so far
550 * weren't established we just give up.
552 * In LLMNR case if we already
553 * were established, and the peer has the
554 * lexicographically larger IP address we continue
557 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
)) {
558 log_debug("Got a successful probe for not yet established RR, we lost.");
560 } else if (i
->probe_transaction
->scope
->protocol
== DNS_PROTOCOL_LLMNR
) {
561 assert(i
->probe_transaction
->received
);
562 we_lost
= memcmp(&i
->probe_transaction
->received
->sender
, &i
->probe_transaction
->received
->destination
, FAMILY_ADDRESS_SIZE(i
->probe_transaction
->received
->family
)) < 0;
564 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
568 dns_zone_item_conflict(i
);
572 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
575 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i
->rr
)));
577 dns_zone_item_probe_stop(i
);
578 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
581 static int dns_zone_item_verify(DnsZoneItem
*i
) {
586 if (i
->state
!= DNS_ZONE_ITEM_ESTABLISHED
)
589 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i
->rr
)));
591 i
->state
= DNS_ZONE_ITEM_VERIFYING
;
592 r
= dns_zone_item_probe_start(i
);
594 log_error_errno(r
, "Failed to start probing for verifying RR: %m");
595 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
602 int dns_zone_check_conflicts(DnsZone
*zone
, DnsResourceRecord
*rr
) {
603 DnsZoneItem
*i
, *first
;
609 /* This checks whether a response RR we received from somebody
610 * else is one that we actually thought was uniquely ours. If
611 * so, we'll verify our RRs. */
613 /* No conflict if we don't have the name at all. */
614 first
= hashmap_get(zone
->by_name
, dns_resource_key_name(rr
->key
));
618 /* No conflict if we have the exact same RR */
619 if (dns_zone_get(zone
, rr
))
622 /* No conflict if it is DNS-SD RR used for service enumeration. */
623 if (dns_resource_key_is_dnssd_ptr(rr
->key
))
626 /* OK, somebody else has RRs for the same name. Yuck! Let's
627 * start probing again */
629 LIST_FOREACH(by_name
, i
, first
) {
630 if (dns_resource_record_equal(i
->rr
, rr
))
633 dns_zone_item_verify(i
);
640 int dns_zone_verify_conflicts(DnsZone
*zone
, DnsResourceKey
*key
) {
641 DnsZoneItem
*i
, *first
;
646 /* Somebody else notified us about a possible conflict. Let's
647 * verify if that's true. */
649 first
= hashmap_get(zone
->by_name
, dns_resource_key_name(key
));
653 LIST_FOREACH(by_name
, i
, first
) {
654 dns_zone_item_verify(i
);
661 void dns_zone_verify_all(DnsZone
*zone
) {
667 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
670 LIST_FOREACH(by_key
, j
, i
)
671 dns_zone_item_verify(j
);
675 void dns_zone_dump(DnsZone
*zone
, FILE *f
) {
685 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
688 LIST_FOREACH(by_key
, j
, i
) {
691 t
= dns_resource_record_to_string(j
->rr
);
704 bool dns_zone_is_empty(DnsZone
*zone
) {
708 return hashmap_isempty(zone
->by_key
);
711 bool dns_zone_contains_name(DnsZone
*z
, const char *name
) {
712 DnsZoneItem
*i
, *first
;
714 first
= hashmap_get(z
->by_name
, name
);
718 LIST_FOREACH(by_name
, i
, first
) {
719 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))