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/>.
24 #include "resolved-dns-zone.h"
25 #include "dns-domain.h"
26 #include "resolved-dns-packet.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
->zone_items
, i
);
42 dns_transaction_gc(t
);
45 static void dns_zone_item_free(DnsZoneItem
*i
) {
49 dns_zone_item_probe_stop(i
);
50 dns_resource_record_unref(i
->rr
);
55 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem
*, dns_zone_item_free
);
57 static void dns_zone_item_remove_and_free(DnsZone
*z
, DnsZoneItem
*i
) {
65 first
= hashmap_get(z
->by_key
, i
->rr
->key
);
66 LIST_REMOVE(by_key
, first
, i
);
68 assert_se(hashmap_replace(z
->by_key
, first
->rr
->key
, first
) >= 0);
70 hashmap_remove(z
->by_key
, i
->rr
->key
);
72 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
));
73 LIST_REMOVE(by_name
, first
, i
);
75 assert_se(hashmap_replace(z
->by_name
, DNS_RESOURCE_KEY_NAME(first
->rr
->key
), first
) >= 0);
77 hashmap_remove(z
->by_name
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
));
79 dns_zone_item_free(i
);
82 void dns_zone_flush(DnsZone
*z
) {
87 while ((i
= hashmap_first(z
->by_key
)))
88 dns_zone_item_remove_and_free(z
, i
);
90 assert(hashmap_size(z
->by_key
) == 0);
91 assert(hashmap_size(z
->by_name
) == 0);
93 hashmap_free(z
->by_key
);
96 hashmap_free(z
->by_name
);
100 static DnsZoneItem
* dns_zone_get(DnsZone
*z
, DnsResourceRecord
*rr
) {
106 LIST_FOREACH(by_key
, i
, hashmap_get(z
->by_key
, rr
->key
))
107 if (dns_resource_record_equal(i
->rr
, rr
) > 0)
113 void dns_zone_remove_rr(DnsZone
*z
, DnsResourceRecord
*rr
) {
119 i
= dns_zone_get(z
, rr
);
121 dns_zone_item_remove_and_free(z
, i
);
124 static int dns_zone_init(DnsZone
*z
) {
129 r
= hashmap_ensure_allocated(&z
->by_key
, &dns_resource_key_hash_ops
);
133 r
= hashmap_ensure_allocated(&z
->by_name
, &dns_name_hash_ops
);
140 static int dns_zone_link_item(DnsZone
*z
, DnsZoneItem
*i
) {
144 first
= hashmap_get(z
->by_key
, i
->rr
->key
);
146 LIST_PREPEND(by_key
, first
, i
);
147 assert_se(hashmap_replace(z
->by_key
, first
->rr
->key
, first
) >= 0);
149 r
= hashmap_put(z
->by_key
, i
->rr
->key
, i
);
154 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
));
156 LIST_PREPEND(by_name
, first
, i
);
157 assert_se(hashmap_replace(z
->by_name
, DNS_RESOURCE_KEY_NAME(first
->rr
->key
), first
) >= 0);
159 r
= hashmap_put(z
->by_name
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
), i
);
167 static int dns_zone_item_probe_start(DnsZoneItem
*i
) {
168 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*key
= NULL
;
174 if (i
->probe_transaction
)
177 key
= dns_resource_key_new(i
->rr
->key
->class, DNS_TYPE_ANY
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
));
181 t
= dns_scope_find_transaction(i
->scope
, key
, false);
183 r
= dns_transaction_new(&t
, i
->scope
, key
);
188 r
= set_ensure_allocated(&t
->zone_items
, NULL
);
192 r
= set_put(t
->zone_items
, i
);
196 i
->probe_transaction
= t
;
198 if (t
->state
== DNS_TRANSACTION_NULL
) {
201 r
= dns_transaction_go(t
);
205 dns_zone_item_probe_stop(i
);
210 dns_zone_item_ready(i
);
214 dns_transaction_gc(t
);
218 int dns_zone_put(DnsZone
*z
, DnsScope
*s
, DnsResourceRecord
*rr
, bool probe
) {
219 _cleanup_(dns_zone_item_freep
) DnsZoneItem
*i
= NULL
;
220 DnsZoneItem
*existing
;
227 if (rr
->key
->class == DNS_CLASS_ANY
)
229 if (rr
->key
->type
== DNS_TYPE_ANY
)
232 existing
= dns_zone_get(z
, rr
);
236 r
= dns_zone_init(z
);
240 i
= new0(DnsZoneItem
, 1);
245 i
->rr
= dns_resource_record_ref(rr
);
246 i
->probing_enabled
= probe
;
248 r
= dns_zone_link_item(z
, i
);
253 DnsZoneItem
*first
, *j
;
254 bool established
= false;
256 /* Check if there's already an RR with the same name
257 * established. If so, it has been probed already, and
258 * we don't ned to probe again. */
260 LIST_FIND_HEAD(by_name
, i
, first
);
261 LIST_FOREACH(by_name
, j
, first
) {
265 if (j
->state
== DNS_ZONE_ITEM_ESTABLISHED
)
270 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
272 i
->state
= DNS_ZONE_ITEM_PROBING
;
274 r
= dns_zone_item_probe_start(i
);
276 dns_zone_item_remove_and_free(z
, i
);
282 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
288 int dns_zone_lookup(DnsZone
*z
, DnsQuestion
*q
, DnsAnswer
**ret_answer
, DnsAnswer
**ret_soa
, bool *ret_tentative
) {
289 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
290 unsigned i
, n_answer
= 0, n_soa
= 0;
291 bool tentative
= true;
299 if (q
->n_keys
<= 0) {
304 *ret_tentative
= false;
309 /* First iteration, count what we have */
310 for (i
= 0; i
< q
->n_keys
; i
++) {
311 DnsZoneItem
*j
, *first
;
313 if (q
->keys
[i
]->type
== DNS_TYPE_ANY
||
314 q
->keys
[i
]->class == DNS_CLASS_ANY
) {
315 bool found
= false, added
= false;
318 /* If this is a generic match, then we have to
319 * go through the list by the name and look
320 * for everything manually */
322 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
323 LIST_FOREACH(by_name
, j
, first
) {
324 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
329 k
= dns_resource_key_match_rr(q
->keys
[i
], j
->rr
);
345 /* If this is a specific match, then look for
346 * the right key immediately */
348 first
= hashmap_get(z
->by_key
, q
->keys
[i
]);
349 LIST_FOREACH(by_key
, j
, first
) {
350 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
358 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
359 LIST_FOREACH(by_name
, j
, first
) {
360 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
370 if (n_answer
<= 0 && n_soa
<= 0) {
375 *ret_tentative
= false;
381 answer
= dns_answer_new(n_answer
);
387 soa
= dns_answer_new(n_soa
);
392 /* Second iteration, actually add the RRs to the answers */
393 for (i
= 0; i
< q
->n_keys
; i
++) {
394 DnsZoneItem
*j
, *first
;
396 if (q
->keys
[i
]->type
== DNS_TYPE_ANY
||
397 q
->keys
[i
]->class == DNS_CLASS_ANY
) {
398 bool found
= false, added
= false;
401 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
402 LIST_FOREACH(by_name
, j
, first
) {
403 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
408 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
411 k
= dns_resource_key_match_rr(q
->keys
[i
], j
->rr
);
415 r
= dns_answer_add(answer
, j
->rr
, 0);
423 if (found
&& !added
) {
424 r
= dns_answer_add_soa(soa
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]), LLMNR_DEFAULT_TTL
);
431 first
= hashmap_get(z
->by_key
, q
->keys
[i
]);
432 LIST_FOREACH(by_key
, j
, first
) {
433 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
438 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
441 r
= dns_answer_add(answer
, j
->rr
, 0);
447 bool add_soa
= false;
449 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
450 LIST_FOREACH(by_name
, j
, first
) {
451 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
)
461 r
= dns_answer_add_soa(soa
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]), LLMNR_DEFAULT_TTL
);
469 *ret_answer
= answer
;
476 *ret_tentative
= tentative
;
481 void dns_zone_item_conflict(DnsZoneItem
*i
) {
482 _cleanup_free_
char *pretty
= NULL
;
486 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_VERIFYING
, DNS_ZONE_ITEM_ESTABLISHED
))
489 dns_resource_record_to_string(i
->rr
, &pretty
);
490 log_info("Detected conflict on %s", strna(pretty
));
492 dns_zone_item_probe_stop(i
);
494 /* Withdraw the conflict item */
495 i
->state
= DNS_ZONE_ITEM_WITHDRAWN
;
497 /* Maybe change the hostname */
498 if (manager_is_own_hostname(i
->scope
->manager
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
)) > 0)
499 manager_next_hostname(i
->scope
->manager
);
502 void dns_zone_item_ready(DnsZoneItem
*i
) {
503 _cleanup_free_
char *pretty
= NULL
;
506 assert(i
->probe_transaction
);
508 if (i
->block_ready
> 0)
511 if (IN_SET(i
->probe_transaction
->state
, DNS_TRANSACTION_NULL
, DNS_TRANSACTION_PENDING
))
514 if (i
->probe_transaction
->state
== DNS_TRANSACTION_SUCCESS
) {
515 bool we_lost
= false;
517 /* The probe got a successful reply. If we so far
518 * weren't established we just give up. If we already
519 * were established, and the peer has the
520 * lexicographically larger IP address we continue
523 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
)) {
524 log_debug("Got a successful probe for not yet established RR, we lost.");
527 assert(i
->probe_transaction
->received
);
528 we_lost
= memcmp(&i
->probe_transaction
->received
->sender
, &i
->probe_transaction
->received
->destination
, FAMILY_ADDRESS_SIZE(i
->probe_transaction
->received
->family
)) < 0;
530 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
534 dns_zone_item_conflict(i
);
538 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
541 dns_resource_record_to_string(i
->rr
, &pretty
);
542 log_debug("Record %s successfully probed.", strna(pretty
));
544 dns_zone_item_probe_stop(i
);
545 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
548 static int dns_zone_item_verify(DnsZoneItem
*i
) {
549 _cleanup_free_
char *pretty
= NULL
;
554 if (i
->state
!= DNS_ZONE_ITEM_ESTABLISHED
)
557 dns_resource_record_to_string(i
->rr
, &pretty
);
558 log_debug("Verifying RR %s", strna(pretty
));
560 i
->state
= DNS_ZONE_ITEM_VERIFYING
;
561 r
= dns_zone_item_probe_start(i
);
563 log_error_errno(r
, "Failed to start probing for verifying RR: %m");
564 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
571 int dns_zone_check_conflicts(DnsZone
*zone
, DnsResourceRecord
*rr
) {
572 DnsZoneItem
*i
, *first
;
578 /* This checks whether a response RR we received from somebody
579 * else is one that we actually thought was uniquely ours. If
580 * so, we'll verify our RRs. */
582 /* No conflict if we don't have the name at all. */
583 first
= hashmap_get(zone
->by_name
, DNS_RESOURCE_KEY_NAME(rr
->key
));
587 /* No conflict if we have the exact same RR */
588 if (dns_zone_get(zone
, rr
))
591 /* OK, somebody else has RRs for the same name. Yuck! Let's
592 * start probing again */
594 LIST_FOREACH(by_name
, i
, first
) {
595 if (dns_resource_record_equal(i
->rr
, rr
))
598 dns_zone_item_verify(i
);
605 int dns_zone_verify_conflicts(DnsZone
*zone
, DnsResourceKey
*key
) {
606 DnsZoneItem
*i
, *first
;
611 /* Somebody else notified us about a possible conflict. Let's
612 * verify if that's true. */
614 first
= hashmap_get(zone
->by_name
, DNS_RESOURCE_KEY_NAME(key
));
618 LIST_FOREACH(by_name
, i
, first
) {
619 dns_zone_item_verify(i
);
626 void dns_zone_verify_all(DnsZone
*zone
) {
632 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
635 LIST_FOREACH(by_key
, j
, i
)
636 dns_zone_item_verify(j
);