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