]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-zone.c
util-lib: split our string related calls from util.[ch] into its own file string...
[thirdparty/systemd.git] / src / resolve / resolved-dns-zone.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2014 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
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->zone_items, i);
42 dns_transaction_gc(t);
43 }
44
45 static void dns_zone_item_free(DnsZoneItem *i) {
46 if (!i)
47 return;
48
49 dns_zone_item_probe_stop(i);
50 dns_resource_record_unref(i->rr);
51
52 free(i);
53 }
54
55 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
56
57 static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) {
58 DnsZoneItem *first;
59
60 assert(z);
61
62 if (!i)
63 return;
64
65 first = hashmap_get(z->by_key, i->rr->key);
66 LIST_REMOVE(by_key, first, i);
67 if (first)
68 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
69 else
70 hashmap_remove(z->by_key, i->rr->key);
71
72 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
73 LIST_REMOVE(by_name, first, i);
74 if (first)
75 assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0);
76 else
77 hashmap_remove(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
78
79 dns_zone_item_free(i);
80 }
81
82 void dns_zone_flush(DnsZone *z) {
83 DnsZoneItem *i;
84
85 assert(z);
86
87 while ((i = hashmap_first(z->by_key)))
88 dns_zone_item_remove_and_free(z, i);
89
90 assert(hashmap_size(z->by_key) == 0);
91 assert(hashmap_size(z->by_name) == 0);
92
93 z->by_key = hashmap_free(z->by_key);
94 z->by_name = hashmap_free(z->by_name);
95 }
96
97 static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
98 DnsZoneItem *i;
99
100 assert(z);
101 assert(rr);
102
103 LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key))
104 if (dns_resource_record_equal(i->rr, rr) > 0)
105 return i;
106
107 return NULL;
108 }
109
110 void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
111 DnsZoneItem *i;
112
113 assert(z);
114 assert(rr);
115
116 i = dns_zone_get(z, rr);
117 if (i)
118 dns_zone_item_remove_and_free(z, i);
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_resource_key_unrefp) DnsResourceKey *key = NULL;
166 DnsTransaction *t;
167 int r;
168
169 assert(i);
170
171 if (i->probe_transaction)
172 return 0;
173
174 key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key));
175 if (!key)
176 return -ENOMEM;
177
178 t = dns_scope_find_transaction(i->scope, key, false);
179 if (!t) {
180 r = dns_transaction_new(&t, i->scope, key);
181 if (r < 0)
182 return r;
183 }
184
185 r = set_ensure_allocated(&t->zone_items, NULL);
186 if (r < 0)
187 goto gc;
188
189 r = set_put(t->zone_items, i);
190 if (r < 0)
191 goto gc;
192
193 i->probe_transaction = t;
194
195 if (t->state == DNS_TRANSACTION_NULL) {
196
197 i->block_ready++;
198 r = dns_transaction_go(t);
199 i->block_ready--;
200
201 if (r < 0) {
202 dns_zone_item_probe_stop(i);
203 return r;
204 }
205 }
206
207 dns_zone_item_ready(i);
208 return 0;
209
210 gc:
211 dns_transaction_gc(t);
212 return r;
213 }
214
215 int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
216 _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
217 DnsZoneItem *existing;
218 int r;
219
220 assert(z);
221 assert(s);
222 assert(rr);
223
224 if (rr->key->class == DNS_CLASS_ANY)
225 return -EINVAL;
226 if (rr->key->type == DNS_TYPE_ANY)
227 return -EINVAL;
228
229 existing = dns_zone_get(z, rr);
230 if (existing)
231 return 0;
232
233 r = dns_zone_init(z);
234 if (r < 0)
235 return r;
236
237 i = new0(DnsZoneItem, 1);
238 if (!i)
239 return -ENOMEM;
240
241 i->scope = s;
242 i->rr = dns_resource_record_ref(rr);
243 i->probing_enabled = probe;
244
245 r = dns_zone_link_item(z, i);
246 if (r < 0)
247 return r;
248
249 if (probe) {
250 DnsZoneItem *first, *j;
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 ned to probe again. */
256
257 LIST_FIND_HEAD(by_name, i, first);
258 LIST_FOREACH(by_name, j, first) {
259 if (i == j)
260 continue;
261
262 if (j->state == DNS_ZONE_ITEM_ESTABLISHED)
263 established = true;
264 }
265
266 if (established)
267 i->state = DNS_ZONE_ITEM_ESTABLISHED;
268 else {
269 i->state = DNS_ZONE_ITEM_PROBING;
270
271 r = dns_zone_item_probe_start(i);
272 if (r < 0) {
273 dns_zone_item_remove_and_free(z, i);
274 i = NULL;
275 return r;
276 }
277 }
278 } else
279 i->state = DNS_ZONE_ITEM_ESTABLISHED;
280
281 i = NULL;
282 return 0;
283 }
284
285 int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
286 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
287 unsigned i, n_answer = 0, n_soa = 0;
288 bool tentative = true;
289 int r;
290
291 assert(z);
292 assert(q);
293 assert(ret_answer);
294 assert(ret_soa);
295
296 if (q->n_keys <= 0) {
297 *ret_answer = NULL;
298 *ret_soa = NULL;
299
300 if (ret_tentative)
301 *ret_tentative = false;
302
303 return 0;
304 }
305
306 /* First iteration, count what we have */
307 for (i = 0; i < q->n_keys; i++) {
308 DnsZoneItem *j, *first;
309
310 if (q->keys[i]->type == DNS_TYPE_ANY ||
311 q->keys[i]->class == DNS_CLASS_ANY) {
312 bool found = false, added = false;
313 int k;
314
315 /* If this is a generic match, then we have to
316 * go through the list by the name and look
317 * for everything manually */
318
319 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
320 LIST_FOREACH(by_name, j, first) {
321 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
322 continue;
323
324 found = true;
325
326 k = dns_resource_key_match_rr(q->keys[i], j->rr);
327 if (k < 0)
328 return k;
329 if (k > 0) {
330 n_answer++;
331 added = true;
332 }
333
334 }
335
336 if (found && !added)
337 n_soa++;
338
339 } else {
340 bool found = false;
341
342 /* If this is a specific match, then look for
343 * the right key immediately */
344
345 first = hashmap_get(z->by_key, q->keys[i]);
346 LIST_FOREACH(by_key, j, first) {
347 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
348 continue;
349
350 found = true;
351 n_answer++;
352 }
353
354 if (!found) {
355 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
356 LIST_FOREACH(by_name, j, first) {
357 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
358 continue;
359
360 n_soa++;
361 break;
362 }
363 }
364 }
365 }
366
367 if (n_answer <= 0 && n_soa <= 0) {
368 *ret_answer = NULL;
369 *ret_soa = NULL;
370
371 if (ret_tentative)
372 *ret_tentative = false;
373
374 return 0;
375 }
376
377 if (n_answer > 0) {
378 answer = dns_answer_new(n_answer);
379 if (!answer)
380 return -ENOMEM;
381 }
382
383 if (n_soa > 0) {
384 soa = dns_answer_new(n_soa);
385 if (!soa)
386 return -ENOMEM;
387 }
388
389 /* Second iteration, actually add the RRs to the answers */
390 for (i = 0; i < q->n_keys; i++) {
391 DnsZoneItem *j, *first;
392
393 if (q->keys[i]->type == DNS_TYPE_ANY ||
394 q->keys[i]->class == DNS_CLASS_ANY) {
395 bool found = false, added = false;
396 int k;
397
398 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
399 LIST_FOREACH(by_name, j, first) {
400 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
401 continue;
402
403 found = true;
404
405 if (j->state != DNS_ZONE_ITEM_PROBING)
406 tentative = false;
407
408 k = dns_resource_key_match_rr(q->keys[i], j->rr);
409 if (k < 0)
410 return k;
411 if (k > 0) {
412 r = dns_answer_add(answer, j->rr, 0);
413 if (r < 0)
414 return r;
415
416 added = true;
417 }
418 }
419
420 if (found && !added) {
421 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL);
422 if (r < 0)
423 return r;
424 }
425 } else {
426 bool found = false;
427
428 first = hashmap_get(z->by_key, q->keys[i]);
429 LIST_FOREACH(by_key, j, first) {
430 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
431 continue;
432
433 found = true;
434
435 if (j->state != DNS_ZONE_ITEM_PROBING)
436 tentative = false;
437
438 r = dns_answer_add(answer, j->rr, 0);
439 if (r < 0)
440 return r;
441 }
442
443 if (!found) {
444 bool add_soa = false;
445
446 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
447 LIST_FOREACH(by_name, j, first) {
448 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
449 continue;
450
451 if (j->state != DNS_ZONE_ITEM_PROBING)
452 tentative = false;
453
454 add_soa = true;
455 }
456
457 if (add_soa) {
458 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL);
459 if (r < 0)
460 return r;
461 }
462 }
463 }
464 }
465
466 *ret_answer = answer;
467 answer = NULL;
468
469 *ret_soa = soa;
470 soa = NULL;
471
472 if (ret_tentative)
473 *ret_tentative = tentative;
474
475 return 1;
476 }
477
478 void dns_zone_item_conflict(DnsZoneItem *i) {
479 _cleanup_free_ char *pretty = NULL;
480
481 assert(i);
482
483 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
484 return;
485
486 dns_resource_record_to_string(i->rr, &pretty);
487 log_info("Detected conflict on %s", strna(pretty));
488
489 dns_zone_item_probe_stop(i);
490
491 /* Withdraw the conflict item */
492 i->state = DNS_ZONE_ITEM_WITHDRAWN;
493
494 /* Maybe change the hostname */
495 if (manager_is_own_hostname(i->scope->manager, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0)
496 manager_next_hostname(i->scope->manager);
497 }
498
499 void dns_zone_item_ready(DnsZoneItem *i) {
500 _cleanup_free_ char *pretty = NULL;
501
502 assert(i);
503 assert(i->probe_transaction);
504
505 if (i->block_ready > 0)
506 return;
507
508 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING))
509 return;
510
511 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
512 bool we_lost = false;
513
514 /* The probe got a successful reply. If we so far
515 * weren't established we just give up. If we already
516 * were established, and the peer has the
517 * lexicographically larger IP address we continue
518 * and defend it. */
519
520 if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
521 log_debug("Got a successful probe for not yet established RR, we lost.");
522 we_lost = true;
523 } else {
524 assert(i->probe_transaction->received);
525 we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
526 if (we_lost)
527 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
528 }
529
530 if (we_lost) {
531 dns_zone_item_conflict(i);
532 return;
533 }
534
535 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
536 }
537
538 dns_resource_record_to_string(i->rr, &pretty);
539 log_debug("Record %s successfully probed.", strna(pretty));
540
541 dns_zone_item_probe_stop(i);
542 i->state = DNS_ZONE_ITEM_ESTABLISHED;
543 }
544
545 static int dns_zone_item_verify(DnsZoneItem *i) {
546 _cleanup_free_ char *pretty = NULL;
547 int r;
548
549 assert(i);
550
551 if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
552 return 0;
553
554 dns_resource_record_to_string(i->rr, &pretty);
555 log_debug("Verifying RR %s", strna(pretty));
556
557 i->state = DNS_ZONE_ITEM_VERIFYING;
558 r = dns_zone_item_probe_start(i);
559 if (r < 0) {
560 log_error_errno(r, "Failed to start probing for verifying RR: %m");
561 i->state = DNS_ZONE_ITEM_ESTABLISHED;
562 return r;
563 }
564
565 return 0;
566 }
567
568 int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
569 DnsZoneItem *i, *first;
570 int c = 0;
571
572 assert(zone);
573 assert(rr);
574
575 /* This checks whether a response RR we received from somebody
576 * else is one that we actually thought was uniquely ours. If
577 * so, we'll verify our RRs. */
578
579 /* No conflict if we don't have the name at all. */
580 first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(rr->key));
581 if (!first)
582 return 0;
583
584 /* No conflict if we have the exact same RR */
585 if (dns_zone_get(zone, rr))
586 return 0;
587
588 /* OK, somebody else has RRs for the same name. Yuck! Let's
589 * start probing again */
590
591 LIST_FOREACH(by_name, i, first) {
592 if (dns_resource_record_equal(i->rr, rr))
593 continue;
594
595 dns_zone_item_verify(i);
596 c++;
597 }
598
599 return c;
600 }
601
602 int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
603 DnsZoneItem *i, *first;
604 int c = 0;
605
606 assert(zone);
607
608 /* Somebody else notified us about a possible conflict. Let's
609 * verify if that's true. */
610
611 first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(key));
612 if (!first)
613 return 0;
614
615 LIST_FOREACH(by_name, i, first) {
616 dns_zone_item_verify(i);
617 c++;
618 }
619
620 return c;
621 }
622
623 void dns_zone_verify_all(DnsZone *zone) {
624 DnsZoneItem *i;
625 Iterator iterator;
626
627 assert(zone);
628
629 HASHMAP_FOREACH(i, zone->by_key, iterator) {
630 DnsZoneItem *j;
631
632 LIST_FOREACH(by_key, j, i)
633 dns_zone_item_verify(j);
634 }
635 }
636
637 void dns_zone_dump(DnsZone *zone, FILE *f) {
638 Iterator iterator;
639 DnsZoneItem *i;
640 int r;
641
642 if (!zone)
643 return;
644
645 if (!f)
646 f = stdout;
647
648 HASHMAP_FOREACH(i, zone->by_key, iterator) {
649 DnsZoneItem *j;
650
651 LIST_FOREACH(by_key, j, i) {
652 _cleanup_free_ char *t = NULL;
653
654 r = dns_resource_record_to_string(j->rr, &t);
655 if (r < 0) {
656 log_oom();
657 continue;
658 }
659
660 fputc('\t', f);
661 fputs(t, f);
662 fputc('\n', f);
663 }
664 }
665 }
666
667 bool dns_zone_is_empty(DnsZone *zone) {
668 if (!zone)
669 return true;
670
671 return hashmap_isempty(zone->by_key);
672 }