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