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