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