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
= TAKE_PTR(i
->probe_transaction
);
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 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 static int dns_zone_add_authenticated_answer(DnsAnswer
*a
, DnsZoneItem
*i
, int ifindex
) {
309 DnsAnswerFlags flags
;
311 /* From RFC 6762, Section 10.2
312 * "They (the rules about when to set the cache-flush bit) apply to
313 * startup announcements as described in Section 8.3, "Announcing",
314 * and to responses generated as a result of receiving query messages."
315 * So, set the cache-flush bit for mDNS answers except for DNS-SD
316 * service enumeration PTRs described in RFC 6763, Section 4.1. */
317 if (i
->scope
->protocol
== DNS_PROTOCOL_MDNS
&&
318 !dns_resource_key_is_dnssd_ptr(i
->rr
->key
))
319 flags
= DNS_ANSWER_AUTHENTICATED
|DNS_ANSWER_CACHE_FLUSH
;
321 flags
= DNS_ANSWER_AUTHENTICATED
;
323 return dns_answer_add(a
, i
->rr
, ifindex
, flags
);
326 int dns_zone_lookup(DnsZone
*z
, DnsResourceKey
*key
, int ifindex
, DnsAnswer
**ret_answer
, DnsAnswer
**ret_soa
, bool *ret_tentative
) {
327 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
328 unsigned n_answer
= 0;
329 DnsZoneItem
*j
, *first
;
330 bool tentative
= true, need_soa
= false;
333 /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the
334 * ifindex field in the answer with it */
340 /* First iteration, count what we have */
342 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
343 bool found
= false, added
= false;
346 /* If this is a generic match, then we have to
347 * go through the list by the name and look
348 * for everything manually */
350 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
351 LIST_FOREACH(by_name
, j
, first
) {
352 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
357 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
373 /* If this is a specific match, then look for
374 * the right key immediately */
376 first
= hashmap_get(z
->by_key
, key
);
377 LIST_FOREACH(by_key
, j
, first
) {
378 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
386 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
387 LIST_FOREACH(by_name
, j
, first
) {
388 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
397 if (n_answer
<= 0 && !need_soa
)
401 answer
= dns_answer_new(n_answer
);
407 soa
= dns_answer_new(1);
412 /* Second iteration, actually add the RRs to the answers */
413 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
414 bool found
= false, added
= false;
417 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
418 LIST_FOREACH(by_name
, j
, first
) {
419 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
424 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
427 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
431 r
= dns_zone_add_authenticated_answer(answer
, j
, ifindex
);
439 if (found
&& !added
) {
440 r
= dns_answer_add_soa(soa
, dns_resource_key_name(key
), LLMNR_DEFAULT_TTL
, ifindex
);
447 first
= hashmap_get(z
->by_key
, key
);
448 LIST_FOREACH(by_key
, j
, first
) {
449 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
454 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
457 r
= dns_zone_add_authenticated_answer(answer
, j
, ifindex
);
463 bool add_soa
= false;
465 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
466 LIST_FOREACH(by_name
, j
, first
) {
467 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
470 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
477 r
= dns_answer_add_soa(soa
, dns_resource_key_name(key
), LLMNR_DEFAULT_TTL
, ifindex
);
484 /* If the caller sets ret_tentative to NULL, then use this as
485 * indication to not return tentative entries */
487 if (!ret_tentative
&& tentative
)
490 *ret_answer
= answer
;
499 *ret_tentative
= tentative
;
510 *ret_tentative
= false;
515 void dns_zone_item_conflict(DnsZoneItem
*i
) {
518 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_VERIFYING
, DNS_ZONE_ITEM_ESTABLISHED
))
521 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i
->rr
)));
523 dns_zone_item_probe_stop(i
);
525 /* Withdraw the conflict item */
526 i
->state
= DNS_ZONE_ITEM_WITHDRAWN
;
528 dnssd_signal_conflict(i
->scope
->manager
, dns_resource_key_name(i
->rr
->key
));
530 /* Maybe change the hostname */
531 if (manager_is_own_hostname(i
->scope
->manager
, dns_resource_key_name(i
->rr
->key
)) > 0)
532 manager_next_hostname(i
->scope
->manager
);
535 void dns_zone_item_notify(DnsZoneItem
*i
) {
537 assert(i
->probe_transaction
);
539 if (i
->block_ready
> 0)
542 if (IN_SET(i
->probe_transaction
->state
, DNS_TRANSACTION_NULL
, DNS_TRANSACTION_PENDING
, DNS_TRANSACTION_VALIDATING
))
545 if (i
->probe_transaction
->state
== DNS_TRANSACTION_SUCCESS
) {
546 bool we_lost
= false;
548 /* The probe got a successful reply. If we so far
549 * weren't established we just give up.
551 * In LLMNR case if we already
552 * were established, and the peer has the
553 * lexicographically larger IP address we continue
556 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
)) {
557 log_debug("Got a successful probe for not yet established RR, we lost.");
559 } else if (i
->probe_transaction
->scope
->protocol
== DNS_PROTOCOL_LLMNR
) {
560 assert(i
->probe_transaction
->received
);
561 we_lost
= memcmp(&i
->probe_transaction
->received
->sender
, &i
->probe_transaction
->received
->destination
, FAMILY_ADDRESS_SIZE(i
->probe_transaction
->received
->family
)) < 0;
563 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
567 dns_zone_item_conflict(i
);
571 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
574 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i
->rr
)));
576 dns_zone_item_probe_stop(i
);
577 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
580 static int dns_zone_item_verify(DnsZoneItem
*i
) {
585 if (i
->state
!= DNS_ZONE_ITEM_ESTABLISHED
)
588 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i
->rr
)));
590 i
->state
= DNS_ZONE_ITEM_VERIFYING
;
591 r
= dns_zone_item_probe_start(i
);
593 log_error_errno(r
, "Failed to start probing for verifying RR: %m");
594 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
601 int dns_zone_check_conflicts(DnsZone
*zone
, DnsResourceRecord
*rr
) {
602 DnsZoneItem
*i
, *first
;
608 /* This checks whether a response RR we received from somebody
609 * else is one that we actually thought was uniquely ours. If
610 * so, we'll verify our RRs. */
612 /* No conflict if we don't have the name at all. */
613 first
= hashmap_get(zone
->by_name
, dns_resource_key_name(rr
->key
));
617 /* No conflict if we have the exact same RR */
618 if (dns_zone_get(zone
, rr
))
621 /* No conflict if it is DNS-SD RR used for service enumeration. */
622 if (dns_resource_key_is_dnssd_ptr(rr
->key
))
625 /* OK, somebody else has RRs for the same name. Yuck! Let's
626 * start probing again */
628 LIST_FOREACH(by_name
, i
, first
) {
629 if (dns_resource_record_equal(i
->rr
, rr
))
632 dns_zone_item_verify(i
);
639 int dns_zone_verify_conflicts(DnsZone
*zone
, DnsResourceKey
*key
) {
640 DnsZoneItem
*i
, *first
;
645 /* Somebody else notified us about a possible conflict. Let's
646 * verify if that's true. */
648 first
= hashmap_get(zone
->by_name
, dns_resource_key_name(key
));
652 LIST_FOREACH(by_name
, i
, first
) {
653 dns_zone_item_verify(i
);
660 void dns_zone_verify_all(DnsZone
*zone
) {
666 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
669 LIST_FOREACH(by_key
, j
, i
)
670 dns_zone_item_verify(j
);
674 void dns_zone_dump(DnsZone
*zone
, FILE *f
) {
684 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
687 LIST_FOREACH(by_key
, j
, i
) {
690 t
= dns_resource_record_to_string(j
->rr
);
703 bool dns_zone_is_empty(DnsZone
*zone
) {
707 return hashmap_isempty(zone
->by_key
);
710 bool dns_zone_contains_name(DnsZone
*z
, const char *name
) {
711 DnsZoneItem
*i
, *first
;
713 first
= hashmap_get(z
->by_name
, name
);
717 LIST_FOREACH(by_name
, i
, first
) {
718 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))