]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-zone.c
resolved: announce DNS-SD records in mDNS scopes
[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 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
31 void 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
46 static 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
56 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
57
58 static 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
83 void 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
98 static 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
111 void 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
122 int dns_zone_remove_rrs_by_key(DnsZone *z, DnsResourceKey *key) {
123 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
124 DnsResourceRecord *rr;
125 bool tentative;
126 int r;
127
128 r = dns_zone_lookup(z, key, 0, &answer, &soa, &tentative);
129 if (r < 0)
130 return r;
131
132 DNS_ANSWER_FOREACH(rr, answer)
133 dns_zone_remove_rr(z, rr);
134
135 return 0;
136 }
137
138 static int dns_zone_init(DnsZone *z) {
139 int r;
140
141 assert(z);
142
143 r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops);
144 if (r < 0)
145 return r;
146
147 r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops);
148 if (r < 0)
149 return r;
150
151 return 0;
152 }
153
154 static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
155 DnsZoneItem *first;
156 int r;
157
158 first = hashmap_get(z->by_key, i->rr->key);
159 if (first) {
160 LIST_PREPEND(by_key, first, i);
161 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
162 } else {
163 r = hashmap_put(z->by_key, i->rr->key, i);
164 if (r < 0)
165 return r;
166 }
167
168 first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
169 if (first) {
170 LIST_PREPEND(by_name, first, i);
171 assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
172 } else {
173 r = hashmap_put(z->by_name, dns_resource_key_name(i->rr->key), i);
174 if (r < 0)
175 return r;
176 }
177
178 return 0;
179 }
180
181 static int dns_zone_item_probe_start(DnsZoneItem *i) {
182 DnsTransaction *t;
183 int r;
184
185 assert(i);
186
187 if (i->probe_transaction)
188 return 0;
189
190 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);
191 if (!t) {
192 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
193
194 key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key));
195 if (!key)
196 return -ENOMEM;
197
198 r = dns_transaction_new(&t, i->scope, key);
199 if (r < 0)
200 return r;
201 }
202
203 r = set_ensure_allocated(&t->notify_zone_items, NULL);
204 if (r < 0)
205 goto gc;
206
207 r = set_ensure_allocated(&t->notify_zone_items_done, NULL);
208 if (r < 0)
209 goto gc;
210
211 r = set_put(t->notify_zone_items, i);
212 if (r < 0)
213 goto gc;
214
215 i->probe_transaction = t;
216 t->probing = true;
217
218 if (t->state == DNS_TRANSACTION_NULL) {
219
220 i->block_ready++;
221 r = dns_transaction_go(t);
222 i->block_ready--;
223
224 if (r < 0) {
225 dns_zone_item_probe_stop(i);
226 return r;
227 }
228 }
229
230 dns_zone_item_notify(i);
231 return 0;
232
233 gc:
234 dns_transaction_gc(t);
235 return r;
236 }
237
238 int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
239 _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
240 DnsZoneItem *existing;
241 int r;
242
243 assert(z);
244 assert(s);
245 assert(rr);
246
247 if (dns_class_is_pseudo(rr->key->class))
248 return -EINVAL;
249 if (dns_type_is_pseudo(rr->key->type))
250 return -EINVAL;
251
252 existing = dns_zone_get(z, rr);
253 if (existing)
254 return 0;
255
256 r = dns_zone_init(z);
257 if (r < 0)
258 return r;
259
260 i = new0(DnsZoneItem, 1);
261 if (!i)
262 return -ENOMEM;
263
264 i->scope = s;
265 i->rr = dns_resource_record_ref(rr);
266 i->probing_enabled = probe;
267
268 r = dns_zone_link_item(z, i);
269 if (r < 0)
270 return r;
271
272 if (probe) {
273 DnsZoneItem *first, *j;
274 bool established = false;
275
276 /* Check if there's already an RR with the same name
277 * established. If so, it has been probed already, and
278 * we don't ned to probe again. */
279
280 LIST_FIND_HEAD(by_name, i, first);
281 LIST_FOREACH(by_name, j, first) {
282 if (i == j)
283 continue;
284
285 if (j->state == DNS_ZONE_ITEM_ESTABLISHED)
286 established = true;
287 }
288
289 if (established)
290 i->state = DNS_ZONE_ITEM_ESTABLISHED;
291 else {
292 i->state = DNS_ZONE_ITEM_PROBING;
293
294 r = dns_zone_item_probe_start(i);
295 if (r < 0) {
296 dns_zone_item_remove_and_free(z, i);
297 i = NULL;
298 return r;
299 }
300 }
301 } else
302 i->state = DNS_ZONE_ITEM_ESTABLISHED;
303
304 i = NULL;
305 return 0;
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_answer_add(answer, j->rr, ifindex, DNS_ANSWER_AUTHENTICATED);
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_answer_add(answer, j->rr, ifindex, DNS_ANSWER_AUTHENTICATED);
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 = answer;
473 answer = NULL;
474
475 if (ret_soa) {
476 *ret_soa = soa;
477 soa = NULL;
478 }
479
480 if (ret_tentative)
481 *ret_tentative = tentative;
482
483 return 1;
484
485 return_empty:
486 *ret_answer = NULL;
487
488 if (ret_soa)
489 *ret_soa = NULL;
490
491 if (ret_tentative)
492 *ret_tentative = false;
493
494 return 0;
495 }
496
497 void dns_zone_item_conflict(DnsZoneItem *i) {
498 assert(i);
499
500 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
501 return;
502
503 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr)));
504
505 dns_zone_item_probe_stop(i);
506
507 /* Withdraw the conflict item */
508 i->state = DNS_ZONE_ITEM_WITHDRAWN;
509
510 /* Maybe change the hostname */
511 if (manager_is_own_hostname(i->scope->manager, dns_resource_key_name(i->rr->key)) > 0)
512 manager_next_hostname(i->scope->manager);
513 }
514
515 void dns_zone_item_notify(DnsZoneItem *i) {
516 assert(i);
517 assert(i->probe_transaction);
518
519 if (i->block_ready > 0)
520 return;
521
522 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
523 return;
524
525 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
526 bool we_lost = false;
527
528 /* The probe got a successful reply. If we so far
529 * weren't established we just give up. If we already
530 * were established, and the peer has the
531 * lexicographically larger IP address we continue
532 * and defend it. */
533
534 if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
535 log_debug("Got a successful probe for not yet established RR, we lost.");
536 we_lost = true;
537 } else {
538 assert(i->probe_transaction->received);
539 we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
540 if (we_lost)
541 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
542 }
543
544 if (we_lost) {
545 dns_zone_item_conflict(i);
546 return;
547 }
548
549 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
550 }
551
552 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr)));
553
554 dns_zone_item_probe_stop(i);
555 i->state = DNS_ZONE_ITEM_ESTABLISHED;
556 }
557
558 static int dns_zone_item_verify(DnsZoneItem *i) {
559 int r;
560
561 assert(i);
562
563 if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
564 return 0;
565
566 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr)));
567
568 i->state = DNS_ZONE_ITEM_VERIFYING;
569 r = dns_zone_item_probe_start(i);
570 if (r < 0) {
571 log_error_errno(r, "Failed to start probing for verifying RR: %m");
572 i->state = DNS_ZONE_ITEM_ESTABLISHED;
573 return r;
574 }
575
576 return 0;
577 }
578
579 int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
580 DnsZoneItem *i, *first;
581 int c = 0;
582
583 assert(zone);
584 assert(rr);
585
586 /* This checks whether a response RR we received from somebody
587 * else is one that we actually thought was uniquely ours. If
588 * so, we'll verify our RRs. */
589
590 /* No conflict if we don't have the name at all. */
591 first = hashmap_get(zone->by_name, dns_resource_key_name(rr->key));
592 if (!first)
593 return 0;
594
595 /* No conflict if we have the exact same RR */
596 if (dns_zone_get(zone, rr))
597 return 0;
598
599 /* OK, somebody else has RRs for the same name. Yuck! Let's
600 * start probing again */
601
602 LIST_FOREACH(by_name, i, first) {
603 if (dns_resource_record_equal(i->rr, rr))
604 continue;
605
606 dns_zone_item_verify(i);
607 c++;
608 }
609
610 return c;
611 }
612
613 int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
614 DnsZoneItem *i, *first;
615 int c = 0;
616
617 assert(zone);
618
619 /* Somebody else notified us about a possible conflict. Let's
620 * verify if that's true. */
621
622 first = hashmap_get(zone->by_name, dns_resource_key_name(key));
623 if (!first)
624 return 0;
625
626 LIST_FOREACH(by_name, i, first) {
627 dns_zone_item_verify(i);
628 c++;
629 }
630
631 return c;
632 }
633
634 void dns_zone_verify_all(DnsZone *zone) {
635 DnsZoneItem *i;
636 Iterator iterator;
637
638 assert(zone);
639
640 HASHMAP_FOREACH(i, zone->by_key, iterator) {
641 DnsZoneItem *j;
642
643 LIST_FOREACH(by_key, j, i)
644 dns_zone_item_verify(j);
645 }
646 }
647
648 void dns_zone_dump(DnsZone *zone, FILE *f) {
649 Iterator iterator;
650 DnsZoneItem *i;
651
652 if (!zone)
653 return;
654
655 if (!f)
656 f = stdout;
657
658 HASHMAP_FOREACH(i, zone->by_key, iterator) {
659 DnsZoneItem *j;
660
661 LIST_FOREACH(by_key, j, i) {
662 const char *t;
663
664 t = dns_resource_record_to_string(j->rr);
665 if (!t) {
666 log_oom();
667 continue;
668 }
669
670 fputc('\t', f);
671 fputs(t, f);
672 fputc('\n', f);
673 }
674 }
675 }
676
677 bool dns_zone_is_empty(DnsZone *zone) {
678 if (!zone)
679 return true;
680
681 return hashmap_isempty(zone->by_key);
682 }
683
684 bool dns_zone_contains_name(DnsZone *z, const char *name) {
685 DnsZoneItem *i, *first;
686
687 first = hashmap_get(z->by_name, name);
688 if (!first)
689 return false;
690
691 LIST_FOREACH(by_name, i, first) {
692 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
693 continue;
694
695 return true;
696 }
697
698 return false;
699 }