]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-zone.c
resolved: when matching up DNSKEY and DS RRs, it's fine if we don't support the DNSKE...
[thirdparty/systemd.git] / src / resolve / resolved-dns-zone.c
CommitLineData
623a4c97
LP
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
b5efdb8a 22#include "alloc-util.h"
4ad7f276 23#include "dns-domain.h"
07630cea 24#include "list.h"
623a4c97 25#include "resolved-dns-packet.h"
07630cea
LP
26#include "resolved-dns-zone.h"
27#include "string-util.h"
623a4c97
LP
28
29/* Never allow more than 1K entries */
30#define ZONE_MAX 1024
31
3ef64445 32void dns_zone_item_probe_stop(DnsZoneItem *i) {
ec2c5e43
LP
33 DnsTransaction *t;
34 assert(i);
623a4c97 35
ec2c5e43
LP
36 if (!i->probe_transaction)
37 return;
38
39 t = i->probe_transaction;
40 i->probe_transaction = NULL;
41
42 set_remove(t->zone_items, i);
43 dns_transaction_gc(t);
44}
623a4c97
LP
45
46static void dns_zone_item_free(DnsZoneItem *i) {
47 if (!i)
48 return;
49
ec2c5e43 50 dns_zone_item_probe_stop(i);
623a4c97 51 dns_resource_record_unref(i->rr);
ec2c5e43 52
623a4c97
LP
53 free(i);
54}
55
56DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
57
58static 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
83void 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
525d3cc7
LP
94 z->by_key = hashmap_free(z->by_key);
95 z->by_name = hashmap_free(z->by_name);
623a4c97
LP
96}
97
98static 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))
3ef77d04 105 if (dns_resource_record_equal(i->rr, rr) > 0)
623a4c97
LP
106 return i;
107
108 return NULL;
109}
110
111void 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
122static int dns_zone_init(DnsZone *z) {
123 int r;
124
125 assert(z);
126
d5099efc 127 r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops);
623a4c97
LP
128 if (r < 0)
129 return r;
130
d5099efc 131 r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops);
623a4c97
LP
132 if (r < 0)
133 return r;
134
135 return 0;
136}
137
138static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
139 DnsZoneItem *first;
140 int r;
141
142 first = hashmap_get(z->by_key, i->rr->key);
143 if (first) {
144 LIST_PREPEND(by_key, first, i);
145 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
146 } else {
147 r = hashmap_put(z->by_key, i->rr->key, i);
148 if (r < 0)
149 return r;
150 }
151
152 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
153 if (first) {
154 LIST_PREPEND(by_name, first, i);
155 assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0);
156 } else {
157 r = hashmap_put(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key), i);
158 if (r < 0)
159 return r;
160 }
161
162 return 0;
163}
164
ec2c5e43 165static int dns_zone_item_probe_start(DnsZoneItem *i) {
ec2c5e43
LP
166 DnsTransaction *t;
167 int r;
168
169 assert(i);
170
171 if (i->probe_transaction)
172 return 0;
173
1b4f6e79 174 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);
ec2c5e43 175 if (!t) {
1b4f6e79
LP
176 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
177
178 key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key));
179 if (!key)
180 return -ENOMEM;
181
f52e61da 182 r = dns_transaction_new(&t, i->scope, key);
ec2c5e43
LP
183 if (r < 0)
184 return r;
185 }
186
d5099efc 187 r = set_ensure_allocated(&t->zone_items, NULL);
ec2c5e43
LP
188 if (r < 0)
189 goto gc;
190
191 r = set_put(t->zone_items, i);
192 if (r < 0)
193 goto gc;
194
195 i->probe_transaction = t;
196
197 if (t->state == DNS_TRANSACTION_NULL) {
198
199 i->block_ready++;
200 r = dns_transaction_go(t);
201 i->block_ready--;
202
203 if (r < 0) {
204 dns_zone_item_probe_stop(i);
205 return r;
206 }
207 }
208
209 dns_zone_item_ready(i);
ec2c5e43
LP
210 return 0;
211
212gc:
213 dns_transaction_gc(t);
214 return r;
215}
216
217int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
623a4c97
LP
218 _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
219 DnsZoneItem *existing;
220 int r;
221
222 assert(z);
ec2c5e43 223 assert(s);
623a4c97
LP
224 assert(rr);
225
1d3b690f
LP
226 if (rr->key->class == DNS_CLASS_ANY)
227 return -EINVAL;
228 if (rr->key->type == DNS_TYPE_ANY)
229 return -EINVAL;
230
623a4c97
LP
231 existing = dns_zone_get(z, rr);
232 if (existing)
233 return 0;
234
235 r = dns_zone_init(z);
236 if (r < 0)
237 return r;
238
239 i = new0(DnsZoneItem, 1);
240 if (!i)
241 return -ENOMEM;
242
ec2c5e43 243 i->scope = s;
623a4c97 244 i->rr = dns_resource_record_ref(rr);
ec2c5e43 245 i->probing_enabled = probe;
623a4c97
LP
246
247 r = dns_zone_link_item(z, i);
248 if (r < 0)
249 return r;
250
ec2c5e43 251 if (probe) {
cd1b20f9
LP
252 DnsZoneItem *first, *j;
253 bool established = false;
254
255 /* Check if there's already an RR with the same name
256 * established. If so, it has been probed already, and
257 * we don't ned to probe again. */
258
259 LIST_FIND_HEAD(by_name, i, first);
260 LIST_FOREACH(by_name, j, first) {
261 if (i == j)
262 continue;
263
264 if (j->state == DNS_ZONE_ITEM_ESTABLISHED)
265 established = true;
ec2c5e43
LP
266 }
267
cd1b20f9
LP
268 if (established)
269 i->state = DNS_ZONE_ITEM_ESTABLISHED;
270 else {
60eb3f7c
LP
271 i->state = DNS_ZONE_ITEM_PROBING;
272
cd1b20f9
LP
273 r = dns_zone_item_probe_start(i);
274 if (r < 0) {
275 dns_zone_item_remove_and_free(z, i);
276 i = NULL;
277 return r;
278 }
cd1b20f9 279 }
ec2c5e43
LP
280 } else
281 i->state = DNS_ZONE_ITEM_ESTABLISHED;
282
623a4c97
LP
283 i = NULL;
284 return 0;
285}
286
5032b16d 287int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
8bf52d3d 288 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
5032b16d
LP
289 unsigned n_answer = 0;
290 DnsZoneItem *j, *first;
291 bool tentative = true, need_soa = false;
d5323661 292 int r;
623a4c97
LP
293
294 assert(z);
5032b16d 295 assert(key);
8bf52d3d 296 assert(ret_answer);
623a4c97 297
5032b16d 298 /* First iteration, count what we have */
ec2c5e43 299
5032b16d
LP
300 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
301 bool found = false, added = false;
302 int k;
623a4c97 303
5032b16d
LP
304 /* If this is a generic match, then we have to
305 * go through the list by the name and look
306 * for everything manually */
623a4c97 307
5032b16d
LP
308 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key));
309 LIST_FOREACH(by_name, j, first) {
310 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
311 continue;
d5323661 312
5032b16d 313 found = true;
d5323661 314
801ad6a6 315 k = dns_resource_key_match_rr(key, j->rr, NULL);
5032b16d
LP
316 if (k < 0)
317 return k;
318 if (k > 0) {
319 n_answer++;
320 added = true;
321 }
ec2c5e43 322
5032b16d 323 }
ec2c5e43 324
5032b16d
LP
325 if (found && !added)
326 need_soa = true;
ec2c5e43 327
5032b16d
LP
328 } else {
329 bool found = false;
d5323661 330
5032b16d
LP
331 /* If this is a specific match, then look for
332 * the right key immediately */
ec2c5e43 333
5032b16d
LP
334 first = hashmap_get(z->by_key, key);
335 LIST_FOREACH(by_key, j, first) {
336 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
337 continue;
ec2c5e43 338
5032b16d
LP
339 found = true;
340 n_answer++;
341 }
ec2c5e43 342
5032b16d
LP
343 if (!found) {
344 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key));
345 LIST_FOREACH(by_name, j, first) {
ec2c5e43
LP
346 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
347 continue;
348
5032b16d
LP
349 need_soa = true;
350 break;
d5323661 351 }
d5323661 352 }
623a4c97
LP
353 }
354
5032b16d
LP
355 if (n_answer <= 0 && !need_soa)
356 goto return_empty;
623a4c97 357
8bf52d3d
LP
358 if (n_answer > 0) {
359 answer = dns_answer_new(n_answer);
360 if (!answer)
361 return -ENOMEM;
362 }
623a4c97 363
5032b16d
LP
364 if (need_soa) {
365 soa = dns_answer_new(1);
8bf52d3d
LP
366 if (!soa)
367 return -ENOMEM;
368 }
369
370 /* Second iteration, actually add the RRs to the answers */
5032b16d
LP
371 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
372 bool found = false, added = false;
373 int k;
d5323661 374
5032b16d
LP
375 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key));
376 LIST_FOREACH(by_name, j, first) {
377 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
378 continue;
ec2c5e43 379
5032b16d 380 found = true;
ec2c5e43 381
5032b16d
LP
382 if (j->state != DNS_ZONE_ITEM_PROBING)
383 tentative = false;
ec2c5e43 384
801ad6a6 385 k = dns_resource_key_match_rr(key, j->rr, NULL);
5032b16d
LP
386 if (k < 0)
387 return k;
388 if (k > 0) {
389 r = dns_answer_add(answer, j->rr, 0);
d5323661
LP
390 if (r < 0)
391 return r;
5032b16d
LP
392
393 added = true;
d5323661 394 }
5032b16d 395 }
d5323661 396
5032b16d
LP
397 if (found && !added) {
398 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(key), LLMNR_DEFAULT_TTL);
399 if (r < 0)
400 return r;
401 }
402 } else {
403 bool found = false;
ec2c5e43 404
5032b16d
LP
405 first = hashmap_get(z->by_key, key);
406 LIST_FOREACH(by_key, j, first) {
407 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
408 continue;
ec2c5e43 409
5032b16d 410 found = true;
ec2c5e43 411
5032b16d
LP
412 if (j->state != DNS_ZONE_ITEM_PROBING)
413 tentative = false;
ec2c5e43 414
5032b16d
LP
415 r = dns_answer_add(answer, j->rr, 0);
416 if (r < 0)
417 return r;
418 }
ec2c5e43 419
5032b16d
LP
420 if (!found) {
421 bool add_soa = false;
ec2c5e43 422
5032b16d
LP
423 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key));
424 LIST_FOREACH(by_name, j, first) {
425 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
426 continue;
ec2c5e43 427
5032b16d
LP
428 if (j->state != DNS_ZONE_ITEM_PROBING)
429 tentative = false;
ec2c5e43 430
5032b16d
LP
431 add_soa = true;
432 }
433
434 if (add_soa) {
435 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(key), LLMNR_DEFAULT_TTL);
436 if (r < 0)
437 return r;
d5323661 438 }
623a4c97
LP
439 }
440 }
441
5032b16d
LP
442 /* If the caller sets ret_tentative to NULL, then use this as
443 * indication to not return tentative entries */
444
445 if (!ret_tentative && tentative)
446 goto return_empty;
447
8bf52d3d 448 *ret_answer = answer;
623a4c97
LP
449 answer = NULL;
450
5032b16d
LP
451 if (ret_soa) {
452 *ret_soa = soa;
453 soa = NULL;
454 }
8bf52d3d 455
ec2c5e43
LP
456 if (ret_tentative)
457 *ret_tentative = tentative;
458
623a4c97 459 return 1;
5032b16d
LP
460
461return_empty:
462 *ret_answer = NULL;
463
464 if (ret_soa)
465 *ret_soa = NULL;
466
467 if (ret_tentative)
468 *ret_tentative = false;
469
470 return 0;
623a4c97 471}
ec2c5e43
LP
472
473void dns_zone_item_conflict(DnsZoneItem *i) {
474 _cleanup_free_ char *pretty = NULL;
475
476 assert(i);
477
a4076574
LP
478 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
479 return;
480
ec2c5e43
LP
481 dns_resource_record_to_string(i->rr, &pretty);
482 log_info("Detected conflict on %s", strna(pretty));
483
d84b686f
LP
484 dns_zone_item_probe_stop(i);
485
ec2c5e43
LP
486 /* Withdraw the conflict item */
487 i->state = DNS_ZONE_ITEM_WITHDRAWN;
488
489 /* Maybe change the hostname */
78c6a153 490 if (manager_is_own_hostname(i->scope->manager, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0)
ec2c5e43
LP
491 manager_next_hostname(i->scope->manager);
492}
493
494void dns_zone_item_ready(DnsZoneItem *i) {
a4076574
LP
495 _cleanup_free_ char *pretty = NULL;
496
ec2c5e43
LP
497 assert(i);
498 assert(i->probe_transaction);
499
500 if (i->block_ready > 0)
501 return;
502
503 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING))
504 return;
505
a4076574
LP
506 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
507 bool we_lost = false;
ec2c5e43 508
a4076574
LP
509 /* The probe got a successful reply. If we so far
510 * weren't established we just give up. If we already
511 * were established, and the peer has the
4d91eec4 512 * lexicographically larger IP address we continue
a4076574
LP
513 * and defend it. */
514
2fb3034c
LP
515 if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
516 log_debug("Got a successful probe for not yet established RR, we lost.");
a4076574 517 we_lost = true;
2fb3034c 518 } else {
a4076574 519 assert(i->probe_transaction->received);
4d91eec4 520 we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
2fb3034c 521 if (we_lost)
4d91eec4 522 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
a4076574
LP
523 }
524
525 if (we_lost) {
526 dns_zone_item_conflict(i);
527 return;
528 }
529
530 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
531 }
532
533 dns_resource_record_to_string(i->rr, &pretty);
534 log_debug("Record %s successfully probed.", strna(pretty));
ec2c5e43 535
a4076574
LP
536 dns_zone_item_probe_stop(i);
537 i->state = DNS_ZONE_ITEM_ESTABLISHED;
538}
539
540static int dns_zone_item_verify(DnsZoneItem *i) {
2fb3034c 541 _cleanup_free_ char *pretty = NULL;
a4076574
LP
542 int r;
543
544 assert(i);
545
546 if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
547 return 0;
548
2fb3034c
LP
549 dns_resource_record_to_string(i->rr, &pretty);
550 log_debug("Verifying RR %s", strna(pretty));
551
a4076574
LP
552 i->state = DNS_ZONE_ITEM_VERIFYING;
553 r = dns_zone_item_probe_start(i);
554 if (r < 0) {
da927ba9 555 log_error_errno(r, "Failed to start probing for verifying RR: %m");
ec2c5e43 556 i->state = DNS_ZONE_ITEM_ESTABLISHED;
a4076574
LP
557 return r;
558 }
559
560 return 0;
561}
562
563int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
564 DnsZoneItem *i, *first;
bf1594f5 565 int c = 0;
a4076574
LP
566
567 assert(zone);
568 assert(rr);
569
570 /* This checks whether a response RR we received from somebody
571 * else is one that we actually thought was uniquely ours. If
572 * so, we'll verify our RRs. */
573
574 /* No conflict if we don't have the name at all. */
575 first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(rr->key));
576 if (!first)
577 return 0;
578
579 /* No conflict if we have the exact same RR */
580 if (dns_zone_get(zone, rr))
581 return 0;
582
583 /* OK, somebody else has RRs for the same name. Yuck! Let's
584 * start probing again */
585
586 LIST_FOREACH(by_name, i, first) {
587 if (dns_resource_record_equal(i->rr, rr))
588 continue;
589
590 dns_zone_item_verify(i);
591 c++;
592 }
593
594 return c;
595}
596
597int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
598 DnsZoneItem *i, *first;
bf1594f5 599 int c = 0;
a4076574
LP
600
601 assert(zone);
602
603 /* Somebody else notified us about a possible conflict. Let's
604 * verify if that's true. */
605
606 first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(key));
607 if (!first)
608 return 0;
609
610 LIST_FOREACH(by_name, i, first) {
611 dns_zone_item_verify(i);
612 c++;
613 }
614
615 return c;
ec2c5e43 616}
902bb5d8
LP
617
618void dns_zone_verify_all(DnsZone *zone) {
619 DnsZoneItem *i;
620 Iterator iterator;
621
622 assert(zone);
623
624 HASHMAP_FOREACH(i, zone->by_key, iterator) {
625 DnsZoneItem *j;
626
627 LIST_FOREACH(by_key, j, i)
628 dns_zone_item_verify(j);
629 }
630}
4d506d6b
LP
631
632void dns_zone_dump(DnsZone *zone, FILE *f) {
633 Iterator iterator;
634 DnsZoneItem *i;
635 int r;
636
637 if (!zone)
638 return;
639
640 if (!f)
641 f = stdout;
642
643 HASHMAP_FOREACH(i, zone->by_key, iterator) {
644 DnsZoneItem *j;
645
646 LIST_FOREACH(by_key, j, i) {
647 _cleanup_free_ char *t = NULL;
648
649 r = dns_resource_record_to_string(j->rr, &t);
650 if (r < 0) {
651 log_oom();
652 continue;
653 }
654
655 fputc('\t', f);
656 fputs(t, f);
657 fputc('\n', f);
658 }
659 }
660}
661
662bool dns_zone_is_empty(DnsZone *zone) {
663 if (!zone)
664 return true;
665
666 return hashmap_isempty(zone->by_key);
667}