]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/resolve/resolved-dns-zone.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / resolve / resolved-dns-zone.c
... / ...
CommitLineData
1/* SPDX-License-Identifier: LGPL-2.1+ */
2/***
3 This file is part of systemd.
4
5 Copyright 2014 Lennart Poettering
6
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.
11
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.
16
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/>.
19***/
20
21#include "alloc-util.h"
22#include "dns-domain.h"
23#include "list.h"
24#include "resolved-dns-packet.h"
25#include "resolved-dns-zone.h"
26#include "string-util.h"
27
28/* Never allow more than 1K entries */
29#define ZONE_MAX 1024
30
31void dns_zone_item_probe_stop(DnsZoneItem *i) {
32 DnsTransaction *t;
33 assert(i);
34
35 if (!i->probe_transaction)
36 return;
37
38 t = i->probe_transaction;
39 i->probe_transaction = NULL;
40
41 set_remove(t->notify_zone_items, i);
42 set_remove(t->notify_zone_items_done, i);
43 dns_transaction_gc(t);
44}
45
46static void dns_zone_item_free(DnsZoneItem *i) {
47 if (!i)
48 return;
49
50 dns_zone_item_probe_stop(i);
51 dns_resource_record_unref(i->rr);
52
53 free(i);
54}
55
56DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
57
58static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) {
59 DnsZoneItem *first;
60
61 assert(z);
62
63 if (!i)
64 return;
65
66 first = hashmap_get(z->by_key, i->rr->key);
67 LIST_REMOVE(by_key, first, i);
68 if (first)
69 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
70 else
71 hashmap_remove(z->by_key, i->rr->key);
72
73 first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
74 LIST_REMOVE(by_name, first, i);
75 if (first)
76 assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
77 else
78 hashmap_remove(z->by_name, dns_resource_key_name(i->rr->key));
79
80 dns_zone_item_free(i);
81}
82
83void dns_zone_flush(DnsZone *z) {
84 DnsZoneItem *i;
85
86 assert(z);
87
88 while ((i = hashmap_first(z->by_key)))
89 dns_zone_item_remove_and_free(z, i);
90
91 assert(hashmap_size(z->by_key) == 0);
92 assert(hashmap_size(z->by_name) == 0);
93
94 z->by_key = hashmap_free(z->by_key);
95 z->by_name = hashmap_free(z->by_name);
96}
97
98static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
99 DnsZoneItem *i;
100
101 assert(z);
102 assert(rr);
103
104 LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key))
105 if (dns_resource_record_equal(i->rr, rr) > 0)
106 return i;
107
108 return NULL;
109}
110
111void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
112 DnsZoneItem *i;
113
114 assert(z);
115 assert(rr);
116
117 i = dns_zone_get(z, rr);
118 if (i)
119 dns_zone_item_remove_and_free(z, i);
120}
121
122static int dns_zone_init(DnsZone *z) {
123 int r;
124
125 assert(z);
126
127 r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops);
128 if (r < 0)
129 return r;
130
131 r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops);
132 if (r < 0)
133 return r;
134
135 return 0;
136}
137
138static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
139 DnsZoneItem *first;
140 int r;
141
142 first = hashmap_get(z->by_key, i->rr->key);
143 if (first) {
144 LIST_PREPEND(by_key, first, i);
145 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
146 } else {
147 r = hashmap_put(z->by_key, i->rr->key, i);
148 if (r < 0)
149 return r;
150 }
151
152 first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
153 if (first) {
154 LIST_PREPEND(by_name, first, i);
155 assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
156 } else {
157 r = hashmap_put(z->by_name, dns_resource_key_name(i->rr->key), i);
158 if (r < 0)
159 return r;
160 }
161
162 return 0;
163}
164
165static int dns_zone_item_probe_start(DnsZoneItem *i) {
166 DnsTransaction *t;
167 int r;
168
169 assert(i);
170
171 if (i->probe_transaction)
172 return 0;
173
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);
175 if (!t) {
176 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
177
178 key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key));
179 if (!key)
180 return -ENOMEM;
181
182 r = dns_transaction_new(&t, i->scope, key);
183 if (r < 0)
184 return r;
185 }
186
187 r = set_ensure_allocated(&t->notify_zone_items, NULL);
188 if (r < 0)
189 goto gc;
190
191 r = set_ensure_allocated(&t->notify_zone_items_done, NULL);
192 if (r < 0)
193 goto gc;
194
195 r = set_put(t->notify_zone_items, i);
196 if (r < 0)
197 goto gc;
198
199 i->probe_transaction = t;
200 t->probing = true;
201
202 if (t->state == DNS_TRANSACTION_NULL) {
203
204 i->block_ready++;
205 r = dns_transaction_go(t);
206 i->block_ready--;
207
208 if (r < 0) {
209 dns_zone_item_probe_stop(i);
210 return r;
211 }
212 }
213
214 dns_zone_item_notify(i);
215 return 0;
216
217gc:
218 dns_transaction_gc(t);
219 return r;
220}
221
222int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
223 _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
224 DnsZoneItem *existing;
225 int r;
226
227 assert(z);
228 assert(s);
229 assert(rr);
230
231 if (dns_class_is_pseudo(rr->key->class))
232 return -EINVAL;
233 if (dns_type_is_pseudo(rr->key->type))
234 return -EINVAL;
235
236 existing = dns_zone_get(z, rr);
237 if (existing)
238 return 0;
239
240 r = dns_zone_init(z);
241 if (r < 0)
242 return r;
243
244 i = new0(DnsZoneItem, 1);
245 if (!i)
246 return -ENOMEM;
247
248 i->scope = s;
249 i->rr = dns_resource_record_ref(rr);
250 i->probing_enabled = probe;
251
252 r = dns_zone_link_item(z, i);
253 if (r < 0)
254 return r;
255
256 if (probe) {
257 DnsZoneItem *first, *j;
258 bool established = false;
259
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. */
263
264 LIST_FIND_HEAD(by_name, i, first);
265 LIST_FOREACH(by_name, j, first) {
266 if (i == j)
267 continue;
268
269 if (j->state == DNS_ZONE_ITEM_ESTABLISHED)
270 established = true;
271 }
272
273 if (established)
274 i->state = DNS_ZONE_ITEM_ESTABLISHED;
275 else {
276 i->state = DNS_ZONE_ITEM_PROBING;
277
278 r = dns_zone_item_probe_start(i);
279 if (r < 0) {
280 dns_zone_item_remove_and_free(z, i);
281 i = NULL;
282 return r;
283 }
284 }
285 } else
286 i->state = DNS_ZONE_ITEM_ESTABLISHED;
287
288 i = NULL;
289 return 0;
290}
291
292int 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;
297 int r;
298
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 */
301
302 assert(z);
303 assert(key);
304 assert(ret_answer);
305
306 /* First iteration, count what we have */
307
308 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
309 bool found = false, added = false;
310 int k;
311
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 */
315
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))
319 continue;
320
321 found = true;
322
323 k = dns_resource_key_match_rr(key, j->rr, NULL);
324 if (k < 0)
325 return k;
326 if (k > 0) {
327 n_answer++;
328 added = true;
329 }
330
331 }
332
333 if (found && !added)
334 need_soa = true;
335
336 } else {
337 bool found = false;
338
339 /* If this is a specific match, then look for
340 * the right key immediately */
341
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))
345 continue;
346
347 found = true;
348 n_answer++;
349 }
350
351 if (!found) {
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))
355 continue;
356
357 need_soa = true;
358 break;
359 }
360 }
361 }
362
363 if (n_answer <= 0 && !need_soa)
364 goto return_empty;
365
366 if (n_answer > 0) {
367 answer = dns_answer_new(n_answer);
368 if (!answer)
369 return -ENOMEM;
370 }
371
372 if (need_soa) {
373 soa = dns_answer_new(1);
374 if (!soa)
375 return -ENOMEM;
376 }
377
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;
381 int k;
382
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))
386 continue;
387
388 found = true;
389
390 if (j->state != DNS_ZONE_ITEM_PROBING)
391 tentative = false;
392
393 k = dns_resource_key_match_rr(key, j->rr, NULL);
394 if (k < 0)
395 return k;
396 if (k > 0) {
397 r = dns_answer_add(answer, j->rr, ifindex, DNS_ANSWER_AUTHENTICATED);
398 if (r < 0)
399 return r;
400
401 added = true;
402 }
403 }
404
405 if (found && !added) {
406 r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
407 if (r < 0)
408 return r;
409 }
410 } else {
411 bool found = false;
412
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))
416 continue;
417
418 found = true;
419
420 if (j->state != DNS_ZONE_ITEM_PROBING)
421 tentative = false;
422
423 r = dns_answer_add(answer, j->rr, ifindex, DNS_ANSWER_AUTHENTICATED);
424 if (r < 0)
425 return r;
426 }
427
428 if (!found) {
429 bool add_soa = false;
430
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))
434 continue;
435
436 if (j->state != DNS_ZONE_ITEM_PROBING)
437 tentative = false;
438
439 add_soa = true;
440 }
441
442 if (add_soa) {
443 r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
444 if (r < 0)
445 return r;
446 }
447 }
448 }
449
450 /* If the caller sets ret_tentative to NULL, then use this as
451 * indication to not return tentative entries */
452
453 if (!ret_tentative && tentative)
454 goto return_empty;
455
456 *ret_answer = answer;
457 answer = NULL;
458
459 if (ret_soa) {
460 *ret_soa = soa;
461 soa = NULL;
462 }
463
464 if (ret_tentative)
465 *ret_tentative = tentative;
466
467 return 1;
468
469return_empty:
470 *ret_answer = NULL;
471
472 if (ret_soa)
473 *ret_soa = NULL;
474
475 if (ret_tentative)
476 *ret_tentative = false;
477
478 return 0;
479}
480
481void dns_zone_item_conflict(DnsZoneItem *i) {
482 assert(i);
483
484 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
485 return;
486
487 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr)));
488
489 dns_zone_item_probe_stop(i);
490
491 /* Withdraw the conflict item */
492 i->state = DNS_ZONE_ITEM_WITHDRAWN;
493
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);
497}
498
499void dns_zone_item_notify(DnsZoneItem *i) {
500 assert(i);
501 assert(i->probe_transaction);
502
503 if (i->block_ready > 0)
504 return;
505
506 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
507 return;
508
509 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
510 bool we_lost = false;
511
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
516 * and defend it. */
517
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.");
520 we_lost = true;
521 } else {
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;
524 if (we_lost)
525 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
526 }
527
528 if (we_lost) {
529 dns_zone_item_conflict(i);
530 return;
531 }
532
533 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
534 }
535
536 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr)));
537
538 dns_zone_item_probe_stop(i);
539 i->state = DNS_ZONE_ITEM_ESTABLISHED;
540}
541
542static int dns_zone_item_verify(DnsZoneItem *i) {
543 int r;
544
545 assert(i);
546
547 if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
548 return 0;
549
550 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr)));
551
552 i->state = DNS_ZONE_ITEM_VERIFYING;
553 r = dns_zone_item_probe_start(i);
554 if (r < 0) {
555 log_error_errno(r, "Failed to start probing for verifying RR: %m");
556 i->state = DNS_ZONE_ITEM_ESTABLISHED;
557 return r;
558 }
559
560 return 0;
561}
562
563int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
564 DnsZoneItem *i, *first;
565 int c = 0;
566
567 assert(zone);
568 assert(rr);
569
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. */
573
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));
576 if (!first)
577 return 0;
578
579 /* No conflict if we have the exact same RR */
580 if (dns_zone_get(zone, rr))
581 return 0;
582
583 /* OK, somebody else has RRs for the same name. Yuck! Let's
584 * start probing again */
585
586 LIST_FOREACH(by_name, i, first) {
587 if (dns_resource_record_equal(i->rr, rr))
588 continue;
589
590 dns_zone_item_verify(i);
591 c++;
592 }
593
594 return c;
595}
596
597int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
598 DnsZoneItem *i, *first;
599 int c = 0;
600
601 assert(zone);
602
603 /* Somebody else notified us about a possible conflict. Let's
604 * verify if that's true. */
605
606 first = hashmap_get(zone->by_name, dns_resource_key_name(key));
607 if (!first)
608 return 0;
609
610 LIST_FOREACH(by_name, i, first) {
611 dns_zone_item_verify(i);
612 c++;
613 }
614
615 return c;
616}
617
618void dns_zone_verify_all(DnsZone *zone) {
619 DnsZoneItem *i;
620 Iterator iterator;
621
622 assert(zone);
623
624 HASHMAP_FOREACH(i, zone->by_key, iterator) {
625 DnsZoneItem *j;
626
627 LIST_FOREACH(by_key, j, i)
628 dns_zone_item_verify(j);
629 }
630}
631
632void dns_zone_dump(DnsZone *zone, FILE *f) {
633 Iterator iterator;
634 DnsZoneItem *i;
635
636 if (!zone)
637 return;
638
639 if (!f)
640 f = stdout;
641
642 HASHMAP_FOREACH(i, zone->by_key, iterator) {
643 DnsZoneItem *j;
644
645 LIST_FOREACH(by_key, j, i) {
646 const char *t;
647
648 t = dns_resource_record_to_string(j->rr);
649 if (!t) {
650 log_oom();
651 continue;
652 }
653
654 fputc('\t', f);
655 fputs(t, f);
656 fputc('\n', f);
657 }
658 }
659}
660
661bool dns_zone_is_empty(DnsZone *zone) {
662 if (!zone)
663 return true;
664
665 return hashmap_isempty(zone->by_key);
666}