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 static int dns_zone_init(DnsZone
*z
) {
127 r
= hashmap_ensure_allocated(&z
->by_key
, &dns_resource_key_hash_ops
);
131 r
= hashmap_ensure_allocated(&z
->by_name
, &dns_name_hash_ops
);
138 static int dns_zone_link_item(DnsZone
*z
, DnsZoneItem
*i
) {
142 first
= hashmap_get(z
->by_key
, i
->rr
->key
);
144 LIST_PREPEND(by_key
, first
, i
);
145 assert_se(hashmap_replace(z
->by_key
, first
->rr
->key
, first
) >= 0);
147 r
= hashmap_put(z
->by_key
, i
->rr
->key
, i
);
152 first
= hashmap_get(z
->by_name
, dns_resource_key_name(i
->rr
->key
));
154 LIST_PREPEND(by_name
, first
, i
);
155 assert_se(hashmap_replace(z
->by_name
, dns_resource_key_name(first
->rr
->key
), first
) >= 0);
157 r
= hashmap_put(z
->by_name
, dns_resource_key_name(i
->rr
->key
), i
);
165 static int dns_zone_item_probe_start(DnsZoneItem
*i
) {
171 if (i
->probe_transaction
)
174 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);
176 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*key
= NULL
;
178 key
= dns_resource_key_new(i
->rr
->key
->class, DNS_TYPE_ANY
, dns_resource_key_name(i
->rr
->key
));
182 r
= dns_transaction_new(&t
, i
->scope
, key
);
187 r
= set_ensure_allocated(&t
->notify_zone_items
, NULL
);
191 r
= set_ensure_allocated(&t
->notify_zone_items_done
, NULL
);
195 r
= set_put(t
->notify_zone_items
, i
);
199 i
->probe_transaction
= t
;
202 if (t
->state
== DNS_TRANSACTION_NULL
) {
205 r
= dns_transaction_go(t
);
209 dns_zone_item_probe_stop(i
);
214 dns_zone_item_notify(i
);
218 dns_transaction_gc(t
);
222 int dns_zone_put(DnsZone
*z
, DnsScope
*s
, DnsResourceRecord
*rr
, bool probe
) {
223 _cleanup_(dns_zone_item_freep
) DnsZoneItem
*i
= NULL
;
224 DnsZoneItem
*existing
;
231 if (dns_class_is_pseudo(rr
->key
->class))
233 if (dns_type_is_pseudo(rr
->key
->type
))
236 existing
= dns_zone_get(z
, rr
);
240 r
= dns_zone_init(z
);
244 i
= new0(DnsZoneItem
, 1);
249 i
->rr
= dns_resource_record_ref(rr
);
250 i
->probing_enabled
= probe
;
252 r
= dns_zone_link_item(z
, i
);
257 DnsZoneItem
*first
, *j
;
258 bool established
= false;
260 /* Check if there's already an RR with the same name
261 * established. If so, it has been probed already, and
262 * we don't ned to probe again. */
264 LIST_FIND_HEAD(by_name
, i
, first
);
265 LIST_FOREACH(by_name
, j
, first
) {
269 if (j
->state
== DNS_ZONE_ITEM_ESTABLISHED
)
274 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
276 i
->state
= DNS_ZONE_ITEM_PROBING
;
278 r
= dns_zone_item_probe_start(i
);
280 dns_zone_item_remove_and_free(z
, i
);
286 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
292 int dns_zone_lookup(DnsZone
*z
, DnsResourceKey
*key
, int ifindex
, DnsAnswer
**ret_answer
, DnsAnswer
**ret_soa
, bool *ret_tentative
) {
293 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
294 unsigned n_answer
= 0;
295 DnsZoneItem
*j
, *first
;
296 bool tentative
= true, need_soa
= false;
299 /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the
300 * ifindex field in the answer with it */
306 /* First iteration, count what we have */
308 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
309 bool found
= false, added
= false;
312 /* If this is a generic match, then we have to
313 * go through the list by the name and look
314 * for everything manually */
316 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
317 LIST_FOREACH(by_name
, j
, first
) {
318 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
323 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
339 /* If this is a specific match, then look for
340 * the right key immediately */
342 first
= hashmap_get(z
->by_key
, key
);
343 LIST_FOREACH(by_key
, j
, first
) {
344 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
352 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
353 LIST_FOREACH(by_name
, j
, first
) {
354 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
363 if (n_answer
<= 0 && !need_soa
)
367 answer
= dns_answer_new(n_answer
);
373 soa
= dns_answer_new(1);
378 /* Second iteration, actually add the RRs to the answers */
379 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
380 bool found
= false, added
= false;
383 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
384 LIST_FOREACH(by_name
, j
, first
) {
385 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
390 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
393 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
397 r
= dns_answer_add(answer
, j
->rr
, ifindex
, DNS_ANSWER_AUTHENTICATED
);
405 if (found
&& !added
) {
406 r
= dns_answer_add_soa(soa
, dns_resource_key_name(key
), LLMNR_DEFAULT_TTL
, ifindex
);
413 first
= hashmap_get(z
->by_key
, key
);
414 LIST_FOREACH(by_key
, j
, first
) {
415 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
420 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
423 r
= dns_answer_add(answer
, j
->rr
, ifindex
, DNS_ANSWER_AUTHENTICATED
);
429 bool add_soa
= false;
431 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
432 LIST_FOREACH(by_name
, j
, first
) {
433 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
)
443 r
= dns_answer_add_soa(soa
, dns_resource_key_name(key
), LLMNR_DEFAULT_TTL
, ifindex
);
450 /* If the caller sets ret_tentative to NULL, then use this as
451 * indication to not return tentative entries */
453 if (!ret_tentative
&& tentative
)
456 *ret_answer
= answer
;
465 *ret_tentative
= tentative
;
476 *ret_tentative
= false;
481 void dns_zone_item_conflict(DnsZoneItem
*i
) {
484 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_VERIFYING
, DNS_ZONE_ITEM_ESTABLISHED
))
487 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i
->rr
)));
489 dns_zone_item_probe_stop(i
);
491 /* Withdraw the conflict item */
492 i
->state
= DNS_ZONE_ITEM_WITHDRAWN
;
494 /* Maybe change the hostname */
495 if (manager_is_own_hostname(i
->scope
->manager
, dns_resource_key_name(i
->rr
->key
)) > 0)
496 manager_next_hostname(i
->scope
->manager
);
499 void dns_zone_item_notify(DnsZoneItem
*i
) {
501 assert(i
->probe_transaction
);
503 if (i
->block_ready
> 0)
506 if (IN_SET(i
->probe_transaction
->state
, DNS_TRANSACTION_NULL
, DNS_TRANSACTION_PENDING
, DNS_TRANSACTION_VALIDATING
))
509 if (i
->probe_transaction
->state
== DNS_TRANSACTION_SUCCESS
) {
510 bool we_lost
= false;
512 /* The probe got a successful reply. If we so far
513 * weren't established we just give up. If we already
514 * were established, and the peer has the
515 * lexicographically larger IP address we continue
518 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
)) {
519 log_debug("Got a successful probe for not yet established RR, we lost.");
522 assert(i
->probe_transaction
->received
);
523 we_lost
= memcmp(&i
->probe_transaction
->received
->sender
, &i
->probe_transaction
->received
->destination
, FAMILY_ADDRESS_SIZE(i
->probe_transaction
->received
->family
)) < 0;
525 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
529 dns_zone_item_conflict(i
);
533 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
536 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i
->rr
)));
538 dns_zone_item_probe_stop(i
);
539 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
542 static int dns_zone_item_verify(DnsZoneItem
*i
) {
547 if (i
->state
!= DNS_ZONE_ITEM_ESTABLISHED
)
550 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i
->rr
)));
552 i
->state
= DNS_ZONE_ITEM_VERIFYING
;
553 r
= dns_zone_item_probe_start(i
);
555 log_error_errno(r
, "Failed to start probing for verifying RR: %m");
556 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
563 int dns_zone_check_conflicts(DnsZone
*zone
, DnsResourceRecord
*rr
) {
564 DnsZoneItem
*i
, *first
;
570 /* This checks whether a response RR we received from somebody
571 * else is one that we actually thought was uniquely ours. If
572 * so, we'll verify our RRs. */
574 /* No conflict if we don't have the name at all. */
575 first
= hashmap_get(zone
->by_name
, dns_resource_key_name(rr
->key
));
579 /* No conflict if we have the exact same RR */
580 if (dns_zone_get(zone
, rr
))
583 /* OK, somebody else has RRs for the same name. Yuck! Let's
584 * start probing again */
586 LIST_FOREACH(by_name
, i
, first
) {
587 if (dns_resource_record_equal(i
->rr
, rr
))
590 dns_zone_item_verify(i
);
597 int dns_zone_verify_conflicts(DnsZone
*zone
, DnsResourceKey
*key
) {
598 DnsZoneItem
*i
, *first
;
603 /* Somebody else notified us about a possible conflict. Let's
604 * verify if that's true. */
606 first
= hashmap_get(zone
->by_name
, dns_resource_key_name(key
));
610 LIST_FOREACH(by_name
, i
, first
) {
611 dns_zone_item_verify(i
);
618 void dns_zone_verify_all(DnsZone
*zone
) {
624 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
627 LIST_FOREACH(by_key
, j
, i
)
628 dns_zone_item_verify(j
);
632 void dns_zone_dump(DnsZone
*zone
, FILE *f
) {
642 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
645 LIST_FOREACH(by_key
, j
, i
) {
648 t
= dns_resource_record_to_string(j
->rr
);
661 bool dns_zone_is_empty(DnsZone
*zone
) {
665 return hashmap_isempty(zone
->by_key
);