]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-zone.c
resolved: add missing error code check when initializing DNS-over-TLS
[thirdparty/systemd.git] / src / resolve / resolved-dns-zone.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include "alloc-util.h"
4 #include "dns-domain.h"
5 #include "list.h"
6 #include "resolved-dns-packet.h"
7 #include "resolved-dns-zone.h"
8 #include "resolved-dnssd.h"
9 #include "string-util.h"
10
11 /* Never allow more than 1K entries */
12 #define ZONE_MAX 1024
13
14 void dns_zone_item_probe_stop(DnsZoneItem *i) {
15 DnsTransaction *t;
16 assert(i);
17
18 if (!i->probe_transaction)
19 return;
20
21 t = TAKE_PTR(i->probe_transaction);
22
23 set_remove(t->notify_zone_items, i);
24 set_remove(t->notify_zone_items_done, i);
25 dns_transaction_gc(t);
26 }
27
28 static void dns_zone_item_free(DnsZoneItem *i) {
29 if (!i)
30 return;
31
32 dns_zone_item_probe_stop(i);
33 dns_resource_record_unref(i->rr);
34
35 free(i);
36 }
37
38 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
39
40 static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) {
41 DnsZoneItem *first;
42
43 assert(z);
44
45 if (!i)
46 return;
47
48 first = hashmap_get(z->by_key, i->rr->key);
49 LIST_REMOVE(by_key, first, i);
50 if (first)
51 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
52 else
53 hashmap_remove(z->by_key, i->rr->key);
54
55 first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
56 LIST_REMOVE(by_name, first, i);
57 if (first)
58 assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
59 else
60 hashmap_remove(z->by_name, dns_resource_key_name(i->rr->key));
61
62 dns_zone_item_free(i);
63 }
64
65 void dns_zone_flush(DnsZone *z) {
66 DnsZoneItem *i;
67
68 assert(z);
69
70 while ((i = hashmap_first(z->by_key)))
71 dns_zone_item_remove_and_free(z, i);
72
73 assert(hashmap_size(z->by_key) == 0);
74 assert(hashmap_size(z->by_name) == 0);
75
76 z->by_key = hashmap_free(z->by_key);
77 z->by_name = hashmap_free(z->by_name);
78 }
79
80 DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
81 DnsZoneItem *i;
82
83 assert(z);
84 assert(rr);
85
86 LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key))
87 if (dns_resource_record_equal(i->rr, rr) > 0)
88 return i;
89
90 return NULL;
91 }
92
93 void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
94 DnsZoneItem *i;
95
96 assert(z);
97 assert(rr);
98
99 i = dns_zone_get(z, rr);
100 if (i)
101 dns_zone_item_remove_and_free(z, i);
102 }
103
104 int dns_zone_remove_rrs_by_key(DnsZone *z, DnsResourceKey *key) {
105 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
106 DnsResourceRecord *rr;
107 bool tentative;
108 int r;
109
110 r = dns_zone_lookup(z, key, 0, &answer, &soa, &tentative);
111 if (r < 0)
112 return r;
113
114 DNS_ANSWER_FOREACH(rr, answer)
115 dns_zone_remove_rr(z, rr);
116
117 return 0;
118 }
119
120 static int dns_zone_init(DnsZone *z) {
121 int r;
122
123 assert(z);
124
125 r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops);
126 if (r < 0)
127 return r;
128
129 r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops);
130 if (r < 0)
131 return r;
132
133 return 0;
134 }
135
136 static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
137 DnsZoneItem *first;
138 int r;
139
140 first = hashmap_get(z->by_key, i->rr->key);
141 if (first) {
142 LIST_PREPEND(by_key, first, i);
143 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
144 } else {
145 r = hashmap_put(z->by_key, i->rr->key, i);
146 if (r < 0)
147 return r;
148 }
149
150 first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
151 if (first) {
152 LIST_PREPEND(by_name, first, i);
153 assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
154 } else {
155 r = hashmap_put(z->by_name, dns_resource_key_name(i->rr->key), i);
156 if (r < 0)
157 return r;
158 }
159
160 return 0;
161 }
162
163 static int dns_zone_item_probe_start(DnsZoneItem *i) {
164 DnsTransaction *t;
165 int r;
166
167 assert(i);
168
169 if (i->probe_transaction)
170 return 0;
171
172 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);
173 if (!t) {
174 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
175
176 key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key));
177 if (!key)
178 return -ENOMEM;
179
180 r = dns_transaction_new(&t, i->scope, key);
181 if (r < 0)
182 return r;
183 }
184
185 r = set_ensure_allocated(&t->notify_zone_items, NULL);
186 if (r < 0)
187 goto gc;
188
189 r = set_ensure_allocated(&t->notify_zone_items_done, NULL);
190 if (r < 0)
191 goto gc;
192
193 r = set_put(t->notify_zone_items, i);
194 if (r < 0)
195 goto gc;
196
197 i->probe_transaction = t;
198 t->probing = true;
199
200 if (t->state == DNS_TRANSACTION_NULL) {
201
202 i->block_ready++;
203 r = dns_transaction_go(t);
204 i->block_ready--;
205
206 if (r < 0) {
207 dns_zone_item_probe_stop(i);
208 return r;
209 }
210 }
211
212 dns_zone_item_notify(i);
213 return 0;
214
215 gc:
216 dns_transaction_gc(t);
217 return r;
218 }
219
220 int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
221 _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
222 DnsZoneItem *existing;
223 int r;
224
225 assert(z);
226 assert(s);
227 assert(rr);
228
229 if (dns_class_is_pseudo(rr->key->class))
230 return -EINVAL;
231 if (dns_type_is_pseudo(rr->key->type))
232 return -EINVAL;
233
234 existing = dns_zone_get(z, rr);
235 if (existing)
236 return 0;
237
238 r = dns_zone_init(z);
239 if (r < 0)
240 return r;
241
242 i = new0(DnsZoneItem, 1);
243 if (!i)
244 return -ENOMEM;
245
246 i->scope = s;
247 i->rr = dns_resource_record_ref(rr);
248 i->probing_enabled = probe;
249
250 r = dns_zone_link_item(z, i);
251 if (r < 0)
252 return r;
253
254 if (probe) {
255 DnsZoneItem *first, *j;
256 bool established = false;
257
258 /* Check if there's already an RR with the same name
259 * established. If so, it has been probed already, and
260 * we don't need to probe again. */
261
262 LIST_FIND_HEAD(by_name, i, first);
263 LIST_FOREACH(by_name, j, first) {
264 if (i == j)
265 continue;
266
267 if (j->state == DNS_ZONE_ITEM_ESTABLISHED)
268 established = true;
269 }
270
271 if (established)
272 i->state = DNS_ZONE_ITEM_ESTABLISHED;
273 else {
274 i->state = DNS_ZONE_ITEM_PROBING;
275
276 r = dns_zone_item_probe_start(i);
277 if (r < 0) {
278 dns_zone_item_remove_and_free(z, i);
279 i = NULL;
280 return r;
281 }
282 }
283 } else
284 i->state = DNS_ZONE_ITEM_ESTABLISHED;
285
286 i = NULL;
287 return 0;
288 }
289
290 static int dns_zone_add_authenticated_answer(DnsAnswer *a, DnsZoneItem *i, int ifindex) {
291 DnsAnswerFlags flags;
292
293 /* From RFC 6762, Section 10.2
294 * "They (the rules about when to set the cache-flush bit) apply to
295 * startup announcements as described in Section 8.3, "Announcing",
296 * and to responses generated as a result of receiving query messages."
297 * So, set the cache-flush bit for mDNS answers except for DNS-SD
298 * service enumeration PTRs described in RFC 6763, Section 4.1. */
299 if (i->scope->protocol == DNS_PROTOCOL_MDNS &&
300 !dns_resource_key_is_dnssd_ptr(i->rr->key))
301 flags = DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHE_FLUSH;
302 else
303 flags = DNS_ANSWER_AUTHENTICATED;
304
305 return dns_answer_add(a, i->rr, ifindex, flags);
306 }
307
308 int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
309 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
310 unsigned n_answer = 0;
311 DnsZoneItem *j, *first;
312 bool tentative = true, need_soa = false;
313 int r;
314
315 /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the
316 * ifindex field in the answer with it */
317
318 assert(z);
319 assert(key);
320 assert(ret_answer);
321
322 /* First iteration, count what we have */
323
324 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
325 bool found = false, added = false;
326 int k;
327
328 /* If this is a generic match, then we have to
329 * go through the list by the name and look
330 * for everything manually */
331
332 first = hashmap_get(z->by_name, dns_resource_key_name(key));
333 LIST_FOREACH(by_name, j, first) {
334 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
335 continue;
336
337 found = true;
338
339 k = dns_resource_key_match_rr(key, j->rr, NULL);
340 if (k < 0)
341 return k;
342 if (k > 0) {
343 n_answer++;
344 added = true;
345 }
346
347 }
348
349 if (found && !added)
350 need_soa = true;
351
352 } else {
353 bool found = false;
354
355 /* If this is a specific match, then look for
356 * the right key immediately */
357
358 first = hashmap_get(z->by_key, key);
359 LIST_FOREACH(by_key, j, first) {
360 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
361 continue;
362
363 found = true;
364 n_answer++;
365 }
366
367 if (!found) {
368 first = hashmap_get(z->by_name, dns_resource_key_name(key));
369 LIST_FOREACH(by_name, j, first) {
370 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
371 continue;
372
373 need_soa = true;
374 break;
375 }
376 }
377 }
378
379 if (n_answer <= 0 && !need_soa)
380 goto return_empty;
381
382 if (n_answer > 0) {
383 answer = dns_answer_new(n_answer);
384 if (!answer)
385 return -ENOMEM;
386 }
387
388 if (need_soa) {
389 soa = dns_answer_new(1);
390 if (!soa)
391 return -ENOMEM;
392 }
393
394 /* Second iteration, actually add the RRs to the answers */
395 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
396 bool found = false, added = false;
397 int k;
398
399 first = hashmap_get(z->by_name, dns_resource_key_name(key));
400 LIST_FOREACH(by_name, j, first) {
401 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
402 continue;
403
404 found = true;
405
406 if (j->state != DNS_ZONE_ITEM_PROBING)
407 tentative = false;
408
409 k = dns_resource_key_match_rr(key, j->rr, NULL);
410 if (k < 0)
411 return k;
412 if (k > 0) {
413 r = dns_zone_add_authenticated_answer(answer, j, ifindex);
414 if (r < 0)
415 return r;
416
417 added = true;
418 }
419 }
420
421 if (found && !added) {
422 r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
423 if (r < 0)
424 return r;
425 }
426 } else {
427 bool found = false;
428
429 first = hashmap_get(z->by_key, key);
430 LIST_FOREACH(by_key, j, first) {
431 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
432 continue;
433
434 found = true;
435
436 if (j->state != DNS_ZONE_ITEM_PROBING)
437 tentative = false;
438
439 r = dns_zone_add_authenticated_answer(answer, j, ifindex);
440 if (r < 0)
441 return r;
442 }
443
444 if (!found) {
445 bool add_soa = false;
446
447 first = hashmap_get(z->by_name, dns_resource_key_name(key));
448 LIST_FOREACH(by_name, j, first) {
449 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
450 continue;
451
452 if (j->state != DNS_ZONE_ITEM_PROBING)
453 tentative = false;
454
455 add_soa = true;
456 }
457
458 if (add_soa) {
459 r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
460 if (r < 0)
461 return r;
462 }
463 }
464 }
465
466 /* If the caller sets ret_tentative to NULL, then use this as
467 * indication to not return tentative entries */
468
469 if (!ret_tentative && tentative)
470 goto return_empty;
471
472 *ret_answer = TAKE_PTR(answer);
473
474 if (ret_soa)
475 *ret_soa = TAKE_PTR(soa);
476
477 if (ret_tentative)
478 *ret_tentative = tentative;
479
480 return 1;
481
482 return_empty:
483 *ret_answer = NULL;
484
485 if (ret_soa)
486 *ret_soa = NULL;
487
488 if (ret_tentative)
489 *ret_tentative = false;
490
491 return 0;
492 }
493
494 void dns_zone_item_conflict(DnsZoneItem *i) {
495 assert(i);
496
497 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
498 return;
499
500 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr)));
501
502 dns_zone_item_probe_stop(i);
503
504 /* Withdraw the conflict item */
505 i->state = DNS_ZONE_ITEM_WITHDRAWN;
506
507 dnssd_signal_conflict(i->scope->manager, dns_resource_key_name(i->rr->key));
508
509 /* Maybe change the hostname */
510 if (manager_is_own_hostname(i->scope->manager, dns_resource_key_name(i->rr->key)) > 0)
511 manager_next_hostname(i->scope->manager);
512 }
513
514 void dns_zone_item_notify(DnsZoneItem *i) {
515 assert(i);
516 assert(i->probe_transaction);
517
518 if (i->block_ready > 0)
519 return;
520
521 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
522 return;
523
524 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
525 bool we_lost = false;
526
527 /* The probe got a successful reply. If we so far
528 * weren't established we just give up.
529 *
530 * In LLMNR case if we already
531 * were established, and the peer has the
532 * lexicographically larger IP address we continue
533 * and defend it. */
534
535 if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
536 log_debug("Got a successful probe for not yet established RR, we lost.");
537 we_lost = true;
538 } else if (i->probe_transaction->scope->protocol == DNS_PROTOCOL_LLMNR) {
539 assert(i->probe_transaction->received);
540 we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
541 if (we_lost)
542 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
543 }
544
545 if (we_lost) {
546 dns_zone_item_conflict(i);
547 return;
548 }
549
550 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
551 }
552
553 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr)));
554
555 dns_zone_item_probe_stop(i);
556 i->state = DNS_ZONE_ITEM_ESTABLISHED;
557 }
558
559 static int dns_zone_item_verify(DnsZoneItem *i) {
560 int r;
561
562 assert(i);
563
564 if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
565 return 0;
566
567 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr)));
568
569 i->state = DNS_ZONE_ITEM_VERIFYING;
570 r = dns_zone_item_probe_start(i);
571 if (r < 0) {
572 log_error_errno(r, "Failed to start probing for verifying RR: %m");
573 i->state = DNS_ZONE_ITEM_ESTABLISHED;
574 return r;
575 }
576
577 return 0;
578 }
579
580 int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
581 DnsZoneItem *i, *first;
582 int c = 0;
583
584 assert(zone);
585 assert(rr);
586
587 /* This checks whether a response RR we received from somebody
588 * else is one that we actually thought was uniquely ours. If
589 * so, we'll verify our RRs. */
590
591 /* No conflict if we don't have the name at all. */
592 first = hashmap_get(zone->by_name, dns_resource_key_name(rr->key));
593 if (!first)
594 return 0;
595
596 /* No conflict if we have the exact same RR */
597 if (dns_zone_get(zone, rr))
598 return 0;
599
600 /* No conflict if it is DNS-SD RR used for service enumeration. */
601 if (dns_resource_key_is_dnssd_ptr(rr->key))
602 return 0;
603
604 /* OK, somebody else has RRs for the same name. Yuck! Let's
605 * start probing again */
606
607 LIST_FOREACH(by_name, i, first) {
608 if (dns_resource_record_equal(i->rr, rr))
609 continue;
610
611 dns_zone_item_verify(i);
612 c++;
613 }
614
615 return c;
616 }
617
618 int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
619 DnsZoneItem *i, *first;
620 int c = 0;
621
622 assert(zone);
623
624 /* Somebody else notified us about a possible conflict. Let's
625 * verify if that's true. */
626
627 first = hashmap_get(zone->by_name, dns_resource_key_name(key));
628 if (!first)
629 return 0;
630
631 LIST_FOREACH(by_name, i, first) {
632 dns_zone_item_verify(i);
633 c++;
634 }
635
636 return c;
637 }
638
639 void dns_zone_verify_all(DnsZone *zone) {
640 DnsZoneItem *i;
641 Iterator iterator;
642
643 assert(zone);
644
645 HASHMAP_FOREACH(i, zone->by_key, iterator) {
646 DnsZoneItem *j;
647
648 LIST_FOREACH(by_key, j, i)
649 dns_zone_item_verify(j);
650 }
651 }
652
653 void dns_zone_dump(DnsZone *zone, FILE *f) {
654 Iterator iterator;
655 DnsZoneItem *i;
656
657 if (!zone)
658 return;
659
660 if (!f)
661 f = stdout;
662
663 HASHMAP_FOREACH(i, zone->by_key, iterator) {
664 DnsZoneItem *j;
665
666 LIST_FOREACH(by_key, j, i) {
667 const char *t;
668
669 t = dns_resource_record_to_string(j->rr);
670 if (!t) {
671 log_oom();
672 continue;
673 }
674
675 fputc('\t', f);
676 fputs(t, f);
677 fputc('\n', f);
678 }
679 }
680 }
681
682 bool dns_zone_is_empty(DnsZone *zone) {
683 if (!zone)
684 return true;
685
686 return hashmap_isempty(zone->by_key);
687 }
688
689 bool dns_zone_contains_name(DnsZone *z, const char *name) {
690 DnsZoneItem *i, *first;
691
692 first = hashmap_get(z->by_name, name);
693 if (!first)
694 return false;
695
696 LIST_FOREACH(by_name, i, first) {
697 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
698 continue;
699
700 return true;
701 }
702
703 return false;
704 }