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 "resolved-dns-domain.h"
26 #include "resolved-dns-packet.h"
28 /* Never allow more than 1K entries */
31 static 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
))
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_func
, dns_resource_key_compare_func
);
133 r
= hashmap_ensure_allocated(&z
->by_name
, dns_name_hash_func
, dns_name_compare_func
);
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
;
169 _cleanup_(dns_question_unrefp
) DnsQuestion
*question
= NULL
;
175 if (i
->probe_transaction
)
178 key
= dns_resource_key_new(i
->rr
->key
->class, DNS_TYPE_ANY
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
));
182 question
= dns_question_new(1);
186 r
= dns_question_add(question
, key
);
190 t
= dns_scope_find_transaction(i
->scope
, question
);
192 r
= dns_transaction_new(&t
, i
->scope
, question
);
197 r
= set_ensure_allocated(&t
->zone_items
, NULL
, NULL
);
201 r
= set_put(t
->zone_items
, i
);
205 i
->probe_transaction
= t
;
207 if (t
->state
== DNS_TRANSACTION_NULL
) {
210 r
= dns_transaction_go(t
);
214 dns_zone_item_probe_stop(i
);
219 dns_zone_item_ready(i
);
224 dns_transaction_gc(t
);
228 int dns_zone_put(DnsZone
*z
, DnsScope
*s
, DnsResourceRecord
*rr
, bool probe
) {
229 _cleanup_(dns_zone_item_freep
) DnsZoneItem
*i
= NULL
;
230 DnsZoneItem
*existing
;
237 if (rr
->key
->class == DNS_CLASS_ANY
)
239 if (rr
->key
->type
== DNS_TYPE_ANY
)
242 existing
= dns_zone_get(z
, rr
);
246 r
= dns_zone_init(z
);
250 i
= new0(DnsZoneItem
, 1);
255 i
->rr
= dns_resource_record_ref(rr
);
256 i
->probing_enabled
= probe
;
258 r
= dns_zone_link_item(z
, i
);
263 r
= dns_zone_item_probe_start(i
);
265 dns_zone_item_remove_and_free(z
, i
);
270 i
->state
= DNS_ZONE_ITEM_PROBING
;
272 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
278 int dns_zone_lookup(DnsZone
*z
, DnsQuestion
*q
, DnsAnswer
**ret_answer
, DnsAnswer
**ret_soa
, bool *ret_tentative
) {
279 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
280 unsigned i
, n_answer
= 0, n_soa
= 0;
281 bool tentative
= true;
289 if (q
->n_keys
<= 0) {
294 *ret_tentative
= false;
299 /* First iteration, count what we have */
300 for (i
= 0; i
< q
->n_keys
; i
++) {
301 DnsZoneItem
*j
, *first
;
303 if (q
->keys
[i
]->type
== DNS_TYPE_ANY
||
304 q
->keys
[i
]->class == DNS_CLASS_ANY
) {
305 bool found
= false, added
= false;
308 /* If this is a generic match, then we have to
309 * go through the list by the name and look
310 * for everything manually */
312 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
313 LIST_FOREACH(by_name
, j
, first
) {
314 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
319 k
= dns_resource_key_match_rr(q
->keys
[i
], j
->rr
);
335 /* If this is a specific match, then look for
336 * the right key immediately */
338 first
= hashmap_get(z
->by_key
, q
->keys
[i
]);
339 LIST_FOREACH(by_key
, j
, first
) {
340 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
348 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
349 LIST_FOREACH(by_name
, j
, first
) {
350 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
360 if (n_answer
<= 0 && n_soa
<= 0) {
365 *ret_tentative
= false;
371 answer
= dns_answer_new(n_answer
);
377 soa
= dns_answer_new(n_soa
);
382 /* Second iteration, actually add the RRs to the answers */
383 for (i
= 0; i
< q
->n_keys
; i
++) {
384 DnsZoneItem
*j
, *first
;
386 if (q
->keys
[i
]->type
== DNS_TYPE_ANY
||
387 q
->keys
[i
]->class == DNS_CLASS_ANY
) {
388 bool found
= false, added
= false;
391 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
392 LIST_FOREACH(by_name
, j
, first
) {
393 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
398 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
401 k
= dns_resource_key_match_rr(q
->keys
[i
], j
->rr
);
405 r
= dns_answer_add(answer
, j
->rr
);
413 if (found
&& !added
) {
414 r
= dns_answer_add_soa(soa
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]), LLMNR_DEFAULT_TTL
);
421 first
= hashmap_get(z
->by_key
, q
->keys
[i
]);
422 LIST_FOREACH(by_key
, j
, first
) {
423 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
)
431 r
= dns_answer_add(answer
, j
->rr
);
437 bool add_soa
= false;
439 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
440 LIST_FOREACH(by_name
, j
, first
) {
441 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
444 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
451 r
= dns_answer_add_soa(soa
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]), LLMNR_DEFAULT_TTL
);
459 *ret_answer
= answer
;
466 *ret_tentative
= tentative
;
471 void dns_zone_item_conflict(DnsZoneItem
*i
) {
472 _cleanup_free_
char *pretty
= NULL
;
476 dns_resource_record_to_string(i
->rr
, &pretty
);
477 log_info("Detected conflict on %s", strna(pretty
));
479 /* Withdraw the conflict item */
480 i
->state
= DNS_ZONE_ITEM_WITHDRAWN
;
482 /* Maybe change the hostname */
483 if (dns_name_equal(i
->scope
->manager
->hostname
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
)) > 0)
484 manager_next_hostname(i
->scope
->manager
);
487 void dns_zone_item_ready(DnsZoneItem
*i
) {
489 assert(i
->probe_transaction
);
491 if (i
->block_ready
> 0)
494 if (IN_SET(i
->probe_transaction
->state
, DNS_TRANSACTION_NULL
, DNS_TRANSACTION_PENDING
))
497 if (i
->probe_transaction
->state
!= DNS_TRANSACTION_SUCCESS
) {
498 _cleanup_free_
char *pretty
= NULL
;
500 dns_resource_record_to_string(i
->rr
, &pretty
);
501 log_debug("Record %s successfully probed.", strna(pretty
));
503 dns_zone_item_probe_stop(i
);
504 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
507 dns_zone_item_conflict(i
);