1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include "alloc-util.h"
23 #include "dns-domain.h"
25 #include "resolved-dns-packet.h"
26 #include "resolved-dns-zone.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 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_put(t
->notify_zone_items
, i
);
195 i
->probe_transaction
= t
;
197 if (t
->state
== DNS_TRANSACTION_NULL
) {
200 r
= dns_transaction_go(t
);
204 dns_zone_item_probe_stop(i
);
209 dns_zone_item_notify(i
);
213 dns_transaction_gc(t
);
217 int dns_zone_put(DnsZone
*z
, DnsScope
*s
, DnsResourceRecord
*rr
, bool probe
) {
218 _cleanup_(dns_zone_item_freep
) DnsZoneItem
*i
= NULL
;
219 DnsZoneItem
*existing
;
226 if (dns_class_is_pseudo(rr
->key
->class))
228 if (dns_type_is_pseudo(rr
->key
->type
))
231 existing
= dns_zone_get(z
, rr
);
235 r
= dns_zone_init(z
);
239 i
= new0(DnsZoneItem
, 1);
244 i
->rr
= dns_resource_record_ref(rr
);
245 i
->probing_enabled
= probe
;
247 r
= dns_zone_link_item(z
, i
);
252 DnsZoneItem
*first
, *j
;
253 bool established
= false;
255 /* Check if there's already an RR with the same name
256 * established. If so, it has been probed already, and
257 * we don't ned to probe again. */
259 LIST_FIND_HEAD(by_name
, i
, first
);
260 LIST_FOREACH(by_name
, j
, first
) {
264 if (j
->state
== DNS_ZONE_ITEM_ESTABLISHED
)
269 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
271 i
->state
= DNS_ZONE_ITEM_PROBING
;
273 r
= dns_zone_item_probe_start(i
);
275 dns_zone_item_remove_and_free(z
, i
);
281 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
287 int dns_zone_lookup(DnsZone
*z
, DnsResourceKey
*key
, DnsAnswer
**ret_answer
, DnsAnswer
**ret_soa
, bool *ret_tentative
) {
288 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
289 unsigned n_answer
= 0;
290 DnsZoneItem
*j
, *first
;
291 bool tentative
= true, need_soa
= false;
298 /* First iteration, count what we have */
300 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
301 bool found
= false, added
= false;
304 /* If this is a generic match, then we have to
305 * go through the list by the name and look
306 * for everything manually */
308 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(key
));
309 LIST_FOREACH(by_name
, j
, first
) {
310 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
315 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
331 /* If this is a specific match, then look for
332 * the right key immediately */
334 first
= hashmap_get(z
->by_key
, key
);
335 LIST_FOREACH(by_key
, j
, first
) {
336 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
344 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(key
));
345 LIST_FOREACH(by_name
, j
, first
) {
346 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
355 if (n_answer
<= 0 && !need_soa
)
359 answer
= dns_answer_new(n_answer
);
365 soa
= dns_answer_new(1);
370 /* Second iteration, actually add the RRs to the answers */
371 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
372 bool found
= false, added
= false;
375 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(key
));
376 LIST_FOREACH(by_name
, j
, first
) {
377 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
382 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
385 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
389 r
= dns_answer_add(answer
, j
->rr
, 0, DNS_ANSWER_AUTHENTICATED
);
397 if (found
&& !added
) {
398 r
= dns_answer_add_soa(soa
, DNS_RESOURCE_KEY_NAME(key
), LLMNR_DEFAULT_TTL
);
405 first
= hashmap_get(z
->by_key
, key
);
406 LIST_FOREACH(by_key
, j
, first
) {
407 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
412 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
415 r
= dns_answer_add(answer
, j
->rr
, 0, DNS_ANSWER_AUTHENTICATED
);
421 bool add_soa
= false;
423 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(key
));
424 LIST_FOREACH(by_name
, j
, first
) {
425 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
428 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
435 r
= dns_answer_add_soa(soa
, DNS_RESOURCE_KEY_NAME(key
), LLMNR_DEFAULT_TTL
);
442 /* If the caller sets ret_tentative to NULL, then use this as
443 * indication to not return tentative entries */
445 if (!ret_tentative
&& tentative
)
448 *ret_answer
= answer
;
457 *ret_tentative
= tentative
;
468 *ret_tentative
= false;
473 void dns_zone_item_conflict(DnsZoneItem
*i
) {
474 _cleanup_free_
char *pretty
= NULL
;
478 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_VERIFYING
, DNS_ZONE_ITEM_ESTABLISHED
))
481 dns_resource_record_to_string(i
->rr
, &pretty
);
482 log_info("Detected conflict on %s", strna(pretty
));
484 dns_zone_item_probe_stop(i
);
486 /* Withdraw the conflict item */
487 i
->state
= DNS_ZONE_ITEM_WITHDRAWN
;
489 /* Maybe change the hostname */
490 if (manager_is_own_hostname(i
->scope
->manager
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
)) > 0)
491 manager_next_hostname(i
->scope
->manager
);
494 void dns_zone_item_notify(DnsZoneItem
*i
) {
495 _cleanup_free_
char *pretty
= NULL
;
498 assert(i
->probe_transaction
);
500 if (i
->block_ready
> 0)
503 if (IN_SET(i
->probe_transaction
->state
, DNS_TRANSACTION_NULL
, DNS_TRANSACTION_PENDING
, DNS_TRANSACTION_VALIDATING
))
506 if (i
->probe_transaction
->state
== DNS_TRANSACTION_SUCCESS
) {
507 bool we_lost
= false;
509 /* The probe got a successful reply. If we so far
510 * weren't established we just give up. If we already
511 * were established, and the peer has the
512 * lexicographically larger IP address we continue
515 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
)) {
516 log_debug("Got a successful probe for not yet established RR, we lost.");
519 assert(i
->probe_transaction
->received
);
520 we_lost
= memcmp(&i
->probe_transaction
->received
->sender
, &i
->probe_transaction
->received
->destination
, FAMILY_ADDRESS_SIZE(i
->probe_transaction
->received
->family
)) < 0;
522 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
526 dns_zone_item_conflict(i
);
530 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
533 dns_resource_record_to_string(i
->rr
, &pretty
);
534 log_debug("Record %s successfully probed.", strna(pretty
));
536 dns_zone_item_probe_stop(i
);
537 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
540 static int dns_zone_item_verify(DnsZoneItem
*i
) {
541 _cleanup_free_
char *pretty
= NULL
;
546 if (i
->state
!= DNS_ZONE_ITEM_ESTABLISHED
)
549 dns_resource_record_to_string(i
->rr
, &pretty
);
550 log_debug("Verifying RR %s", strna(pretty
));
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
) {
643 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
646 LIST_FOREACH(by_key
, j
, i
) {
647 _cleanup_free_
char *t
= NULL
;
649 r
= dns_resource_record_to_string(j
->rr
, &t
);
662 bool dns_zone_is_empty(DnsZone
*zone
) {
666 return hashmap_isempty(zone
->by_key
);