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