]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-zone.c
core: add new RandomSec= setting for time units
[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
ec2c5e43 286int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
8bf52d3d
LP
287 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
288 unsigned i, n_answer = 0, n_soa = 0;
ec2c5e43 289 bool tentative = true;
d5323661 290 int r;
623a4c97
LP
291
292 assert(z);
293 assert(q);
8bf52d3d
LP
294 assert(ret_answer);
295 assert(ret_soa);
623a4c97
LP
296
297 if (q->n_keys <= 0) {
8bf52d3d
LP
298 *ret_answer = NULL;
299 *ret_soa = NULL;
ec2c5e43
LP
300
301 if (ret_tentative)
302 *ret_tentative = false;
303
623a4c97
LP
304 return 0;
305 }
306
8bf52d3d 307 /* First iteration, count what we have */
623a4c97 308 for (i = 0; i < q->n_keys; i++) {
ec2c5e43 309 DnsZoneItem *j, *first;
623a4c97 310
d5323661
LP
311 if (q->keys[i]->type == DNS_TYPE_ANY ||
312 q->keys[i]->class == DNS_CLASS_ANY) {
ec2c5e43 313 bool found = false, added = false;
d5323661
LP
314 int k;
315
316 /* If this is a generic match, then we have to
317 * go through the list by the name and look
318 * for everything manually */
319
ec2c5e43
LP
320 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
321 LIST_FOREACH(by_name, j, first) {
322 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
323 continue;
324
325 found = true;
326
d5323661
LP
327 k = dns_resource_key_match_rr(q->keys[i], j->rr);
328 if (k < 0)
329 return k;
ec2c5e43 330 if (k > 0) {
8bf52d3d 331 n_answer++;
ec2c5e43
LP
332 added = true;
333 }
334
d5323661
LP
335 }
336
ec2c5e43
LP
337 if (found && !added)
338 n_soa++;
339
d5323661 340 } else {
ec2c5e43
LP
341 bool found = false;
342
343 /* If this is a specific match, then look for
344 * the right key immediately */
345
346 first = hashmap_get(z->by_key, q->keys[i]);
347 LIST_FOREACH(by_key, j, first) {
348 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
349 continue;
350
351 found = true;
352 n_answer++;
353 }
354
355 if (!found) {
356 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
357 LIST_FOREACH(by_name, j, first) {
358 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
359 continue;
360
361 n_soa++;
362 break;
363 }
d5323661 364 }
d5323661 365 }
623a4c97
LP
366 }
367
8bf52d3d
LP
368 if (n_answer <= 0 && n_soa <= 0) {
369 *ret_answer = NULL;
370 *ret_soa = NULL;
ec2c5e43
LP
371
372 if (ret_tentative)
373 *ret_tentative = false;
374
8bf52d3d 375 return 0;
623a4c97
LP
376 }
377
8bf52d3d
LP
378 if (n_answer > 0) {
379 answer = dns_answer_new(n_answer);
380 if (!answer)
381 return -ENOMEM;
382 }
623a4c97 383
8bf52d3d
LP
384 if (n_soa > 0) {
385 soa = dns_answer_new(n_soa);
386 if (!soa)
387 return -ENOMEM;
388 }
389
390 /* Second iteration, actually add the RRs to the answers */
623a4c97 391 for (i = 0; i < q->n_keys; i++) {
ec2c5e43 392 DnsZoneItem *j, *first;
623a4c97 393
d5323661
LP
394 if (q->keys[i]->type == DNS_TYPE_ANY ||
395 q->keys[i]->class == DNS_CLASS_ANY) {
ec2c5e43 396 bool found = false, added = false;
d5323661
LP
397 int k;
398
ec2c5e43
LP
399 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
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
d5323661
LP
409 k = dns_resource_key_match_rr(q->keys[i], j->rr);
410 if (k < 0)
411 return k;
ec2c5e43 412 if (k > 0) {
78c6a153 413 r = dns_answer_add(answer, j->rr, 0);
ec2c5e43
LP
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(q->keys[i]), LLMNR_DEFAULT_TTL);
d5323661
LP
423 if (r < 0)
424 return r;
425 }
426 } else {
ec2c5e43 427 bool found = false;
d5323661 428
ec2c5e43
LP
429 first = hashmap_get(z->by_key, q->keys[i]);
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
78c6a153 439 r = dns_answer_add(answer, j->rr, 0);
ec2c5e43
LP
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(q->keys[i]));
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;
8bf52d3d 456 }
ec2c5e43
LP
457
458 if (add_soa) {
57f5ad31 459 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL);
8bf52d3d
LP
460 if (r < 0)
461 return r;
462 }
d5323661 463 }
623a4c97
LP
464 }
465 }
466
8bf52d3d 467 *ret_answer = answer;
623a4c97
LP
468 answer = NULL;
469
8bf52d3d
LP
470 *ret_soa = soa;
471 soa = NULL;
472
ec2c5e43
LP
473 if (ret_tentative)
474 *ret_tentative = tentative;
475
623a4c97
LP
476 return 1;
477}
ec2c5e43
LP
478
479void dns_zone_item_conflict(DnsZoneItem *i) {
480 _cleanup_free_ char *pretty = NULL;
481
482 assert(i);
483
a4076574
LP
484 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
485 return;
486
ec2c5e43
LP
487 dns_resource_record_to_string(i->rr, &pretty);
488 log_info("Detected conflict on %s", strna(pretty));
489
d84b686f
LP
490 dns_zone_item_probe_stop(i);
491
ec2c5e43
LP
492 /* Withdraw the conflict item */
493 i->state = DNS_ZONE_ITEM_WITHDRAWN;
494
495 /* Maybe change the hostname */
78c6a153 496 if (manager_is_own_hostname(i->scope->manager, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0)
ec2c5e43
LP
497 manager_next_hostname(i->scope->manager);
498}
499
500void dns_zone_item_ready(DnsZoneItem *i) {
a4076574
LP
501 _cleanup_free_ char *pretty = NULL;
502
ec2c5e43
LP
503 assert(i);
504 assert(i->probe_transaction);
505
506 if (i->block_ready > 0)
507 return;
508
509 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING))
510 return;
511
a4076574
LP
512 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
513 bool we_lost = false;
ec2c5e43 514
a4076574
LP
515 /* The probe got a successful reply. If we so far
516 * weren't established we just give up. If we already
517 * were established, and the peer has the
4d91eec4 518 * lexicographically larger IP address we continue
a4076574
LP
519 * and defend it. */
520
2fb3034c
LP
521 if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
522 log_debug("Got a successful probe for not yet established RR, we lost.");
a4076574 523 we_lost = true;
2fb3034c 524 } else {
a4076574 525 assert(i->probe_transaction->received);
4d91eec4 526 we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
2fb3034c 527 if (we_lost)
4d91eec4 528 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
a4076574
LP
529 }
530
531 if (we_lost) {
532 dns_zone_item_conflict(i);
533 return;
534 }
535
536 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
537 }
538
539 dns_resource_record_to_string(i->rr, &pretty);
540 log_debug("Record %s successfully probed.", strna(pretty));
ec2c5e43 541
a4076574
LP
542 dns_zone_item_probe_stop(i);
543 i->state = DNS_ZONE_ITEM_ESTABLISHED;
544}
545
546static int dns_zone_item_verify(DnsZoneItem *i) {
2fb3034c 547 _cleanup_free_ char *pretty = NULL;
a4076574
LP
548 int r;
549
550 assert(i);
551
552 if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
553 return 0;
554
2fb3034c
LP
555 dns_resource_record_to_string(i->rr, &pretty);
556 log_debug("Verifying RR %s", strna(pretty));
557
a4076574
LP
558 i->state = DNS_ZONE_ITEM_VERIFYING;
559 r = dns_zone_item_probe_start(i);
560 if (r < 0) {
da927ba9 561 log_error_errno(r, "Failed to start probing for verifying RR: %m");
ec2c5e43 562 i->state = DNS_ZONE_ITEM_ESTABLISHED;
a4076574
LP
563 return r;
564 }
565
566 return 0;
567}
568
569int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
570 DnsZoneItem *i, *first;
bf1594f5 571 int c = 0;
a4076574
LP
572
573 assert(zone);
574 assert(rr);
575
576 /* This checks whether a response RR we received from somebody
577 * else is one that we actually thought was uniquely ours. If
578 * so, we'll verify our RRs. */
579
580 /* No conflict if we don't have the name at all. */
581 first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(rr->key));
582 if (!first)
583 return 0;
584
585 /* No conflict if we have the exact same RR */
586 if (dns_zone_get(zone, rr))
587 return 0;
588
589 /* OK, somebody else has RRs for the same name. Yuck! Let's
590 * start probing again */
591
592 LIST_FOREACH(by_name, i, first) {
593 if (dns_resource_record_equal(i->rr, rr))
594 continue;
595
596 dns_zone_item_verify(i);
597 c++;
598 }
599
600 return c;
601}
602
603int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
604 DnsZoneItem *i, *first;
bf1594f5 605 int c = 0;
a4076574
LP
606
607 assert(zone);
608
609 /* Somebody else notified us about a possible conflict. Let's
610 * verify if that's true. */
611
612 first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(key));
613 if (!first)
614 return 0;
615
616 LIST_FOREACH(by_name, i, first) {
617 dns_zone_item_verify(i);
618 c++;
619 }
620
621 return c;
ec2c5e43 622}
902bb5d8
LP
623
624void dns_zone_verify_all(DnsZone *zone) {
625 DnsZoneItem *i;
626 Iterator iterator;
627
628 assert(zone);
629
630 HASHMAP_FOREACH(i, zone->by_key, iterator) {
631 DnsZoneItem *j;
632
633 LIST_FOREACH(by_key, j, i)
634 dns_zone_item_verify(j);
635 }
636}
4d506d6b
LP
637
638void dns_zone_dump(DnsZone *zone, FILE *f) {
639 Iterator iterator;
640 DnsZoneItem *i;
641 int r;
642
643 if (!zone)
644 return;
645
646 if (!f)
647 f = stdout;
648
649 HASHMAP_FOREACH(i, zone->by_key, iterator) {
650 DnsZoneItem *j;
651
652 LIST_FOREACH(by_key, j, i) {
653 _cleanup_free_ char *t = NULL;
654
655 r = dns_resource_record_to_string(j->rr, &t);
656 if (r < 0) {
657 log_oom();
658 continue;
659 }
660
661 fputc('\t', f);
662 fputs(t, f);
663 fputc('\n', f);
664 }
665 }
666}
667
668bool dns_zone_is_empty(DnsZone *zone) {
669 if (!zone)
670 return true;
671
672 return hashmap_isempty(zone->by_key);
673}