]> git.ipfire.org Git - thirdparty/systemd.git/blame - 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
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
4ad7f276 22#include "dns-domain.h"
07630cea 23#include "list.h"
623a4c97 24#include "resolved-dns-packet.h"
07630cea
LP
25#include "resolved-dns-zone.h"
26#include "string-util.h"
623a4c97
LP
27
28/* Never allow more than 1K entries */
29#define ZONE_MAX 1024
30
3ef64445 31void dns_zone_item_probe_stop(DnsZoneItem *i) {
ec2c5e43
LP
32 DnsTransaction *t;
33 assert(i);
623a4c97 34
ec2c5e43
LP
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}
623a4c97
LP
44
45static void dns_zone_item_free(DnsZoneItem *i) {
46 if (!i)
47 return;
48
ec2c5e43 49 dns_zone_item_probe_stop(i);
623a4c97 50 dns_resource_record_unref(i->rr);
ec2c5e43 51
623a4c97
LP
52 free(i);
53}
54
55DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
56
57static 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
82void 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
525d3cc7
LP
93 z->by_key = hashmap_free(z->by_key);
94 z->by_name = hashmap_free(z->by_name);
623a4c97
LP
95}
96
97static 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))
3ef77d04 104 if (dns_resource_record_equal(i->rr, rr) > 0)
623a4c97
LP
105 return i;
106
107 return NULL;
108}
109
110void 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
121static int dns_zone_init(DnsZone *z) {
122 int r;
123
124 assert(z);
125
d5099efc 126 r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops);
623a4c97
LP
127 if (r < 0)
128 return r;
129
d5099efc 130 r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops);
623a4c97
LP
131 if (r < 0)
132 return r;
133
134 return 0;
135}
136
137static 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
ec2c5e43
LP
164static int dns_zone_item_probe_start(DnsZoneItem *i) {
165 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
ec2c5e43
LP
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
f52e61da 178 t = dns_scope_find_transaction(i->scope, key, false);
ec2c5e43 179 if (!t) {
f52e61da 180 r = dns_transaction_new(&t, i->scope, key);
ec2c5e43
LP
181 if (r < 0)
182 return r;
183 }
184
d5099efc 185 r = set_ensure_allocated(&t->zone_items, NULL);
ec2c5e43
LP
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);
ec2c5e43
LP
208 return 0;
209
210gc:
211 dns_transaction_gc(t);
212 return r;
213}
214
215int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
623a4c97
LP
216 _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
217 DnsZoneItem *existing;
218 int r;
219
220 assert(z);
ec2c5e43 221 assert(s);
623a4c97
LP
222 assert(rr);
223
1d3b690f
LP
224 if (rr->key->class == DNS_CLASS_ANY)
225 return -EINVAL;
226 if (rr->key->type == DNS_TYPE_ANY)
227 return -EINVAL;
228
623a4c97
LP
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
ec2c5e43 241 i->scope = s;
623a4c97 242 i->rr = dns_resource_record_ref(rr);
ec2c5e43 243 i->probing_enabled = probe;
623a4c97
LP
244
245 r = dns_zone_link_item(z, i);
246 if (r < 0)
247 return r;
248
ec2c5e43 249 if (probe) {
cd1b20f9
LP
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;
ec2c5e43
LP
264 }
265
cd1b20f9
LP
266 if (established)
267 i->state = DNS_ZONE_ITEM_ESTABLISHED;
268 else {
60eb3f7c
LP
269 i->state = DNS_ZONE_ITEM_PROBING;
270
cd1b20f9
LP
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 }
cd1b20f9 277 }
ec2c5e43
LP
278 } else
279 i->state = DNS_ZONE_ITEM_ESTABLISHED;
280
623a4c97
LP
281 i = NULL;
282 return 0;
283}
284
ec2c5e43 285int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
8bf52d3d
LP
286 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
287 unsigned i, n_answer = 0, n_soa = 0;
ec2c5e43 288 bool tentative = true;
d5323661 289 int r;
623a4c97
LP
290
291 assert(z);
292 assert(q);
8bf52d3d
LP
293 assert(ret_answer);
294 assert(ret_soa);
623a4c97
LP
295
296 if (q->n_keys <= 0) {
8bf52d3d
LP
297 *ret_answer = NULL;
298 *ret_soa = NULL;
ec2c5e43
LP
299
300 if (ret_tentative)
301 *ret_tentative = false;
302
623a4c97
LP
303 return 0;
304 }
305
8bf52d3d 306 /* First iteration, count what we have */
623a4c97 307 for (i = 0; i < q->n_keys; i++) {
ec2c5e43 308 DnsZoneItem *j, *first;
623a4c97 309
d5323661
LP
310 if (q->keys[i]->type == DNS_TYPE_ANY ||
311 q->keys[i]->class == DNS_CLASS_ANY) {
ec2c5e43 312 bool found = false, added = false;
d5323661
LP
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
ec2c5e43
LP
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
d5323661
LP
326 k = dns_resource_key_match_rr(q->keys[i], j->rr);
327 if (k < 0)
328 return k;
ec2c5e43 329 if (k > 0) {
8bf52d3d 330 n_answer++;
ec2c5e43
LP
331 added = true;
332 }
333
d5323661
LP
334 }
335
ec2c5e43
LP
336 if (found && !added)
337 n_soa++;
338
d5323661 339 } else {
ec2c5e43
LP
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 }
d5323661 363 }
d5323661 364 }
623a4c97
LP
365 }
366
8bf52d3d
LP
367 if (n_answer <= 0 && n_soa <= 0) {
368 *ret_answer = NULL;
369 *ret_soa = NULL;
ec2c5e43
LP
370
371 if (ret_tentative)
372 *ret_tentative = false;
373
8bf52d3d 374 return 0;
623a4c97
LP
375 }
376
8bf52d3d
LP
377 if (n_answer > 0) {
378 answer = dns_answer_new(n_answer);
379 if (!answer)
380 return -ENOMEM;
381 }
623a4c97 382
8bf52d3d
LP
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 */
623a4c97 390 for (i = 0; i < q->n_keys; i++) {
ec2c5e43 391 DnsZoneItem *j, *first;
623a4c97 392
d5323661
LP
393 if (q->keys[i]->type == DNS_TYPE_ANY ||
394 q->keys[i]->class == DNS_CLASS_ANY) {
ec2c5e43 395 bool found = false, added = false;
d5323661
LP
396 int k;
397
ec2c5e43
LP
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
d5323661
LP
408 k = dns_resource_key_match_rr(q->keys[i], j->rr);
409 if (k < 0)
410 return k;
ec2c5e43 411 if (k > 0) {
78c6a153 412 r = dns_answer_add(answer, j->rr, 0);
ec2c5e43
LP
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);
d5323661
LP
422 if (r < 0)
423 return r;
424 }
425 } else {
ec2c5e43 426 bool found = false;
d5323661 427
ec2c5e43
LP
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
78c6a153 438 r = dns_answer_add(answer, j->rr, 0);
ec2c5e43
LP
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;
8bf52d3d 455 }
ec2c5e43
LP
456
457 if (add_soa) {
57f5ad31 458 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL);
8bf52d3d
LP
459 if (r < 0)
460 return r;
461 }
d5323661 462 }
623a4c97
LP
463 }
464 }
465
8bf52d3d 466 *ret_answer = answer;
623a4c97
LP
467 answer = NULL;
468
8bf52d3d
LP
469 *ret_soa = soa;
470 soa = NULL;
471
ec2c5e43
LP
472 if (ret_tentative)
473 *ret_tentative = tentative;
474
623a4c97
LP
475 return 1;
476}
ec2c5e43
LP
477
478void dns_zone_item_conflict(DnsZoneItem *i) {
479 _cleanup_free_ char *pretty = NULL;
480
481 assert(i);
482
a4076574
LP
483 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
484 return;
485
ec2c5e43
LP
486 dns_resource_record_to_string(i->rr, &pretty);
487 log_info("Detected conflict on %s", strna(pretty));
488
d84b686f
LP
489 dns_zone_item_probe_stop(i);
490
ec2c5e43
LP
491 /* Withdraw the conflict item */
492 i->state = DNS_ZONE_ITEM_WITHDRAWN;
493
494 /* Maybe change the hostname */
78c6a153 495 if (manager_is_own_hostname(i->scope->manager, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0)
ec2c5e43
LP
496 manager_next_hostname(i->scope->manager);
497}
498
499void dns_zone_item_ready(DnsZoneItem *i) {
a4076574
LP
500 _cleanup_free_ char *pretty = NULL;
501
ec2c5e43
LP
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
a4076574
LP
511 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
512 bool we_lost = false;
ec2c5e43 513
a4076574
LP
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
4d91eec4 517 * lexicographically larger IP address we continue
a4076574
LP
518 * and defend it. */
519
2fb3034c
LP
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.");
a4076574 522 we_lost = true;
2fb3034c 523 } else {
a4076574 524 assert(i->probe_transaction->received);
4d91eec4 525 we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
2fb3034c 526 if (we_lost)
4d91eec4 527 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
a4076574
LP
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));
ec2c5e43 540
a4076574
LP
541 dns_zone_item_probe_stop(i);
542 i->state = DNS_ZONE_ITEM_ESTABLISHED;
543}
544
545static int dns_zone_item_verify(DnsZoneItem *i) {
2fb3034c 546 _cleanup_free_ char *pretty = NULL;
a4076574
LP
547 int r;
548
549 assert(i);
550
551 if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
552 return 0;
553
2fb3034c
LP
554 dns_resource_record_to_string(i->rr, &pretty);
555 log_debug("Verifying RR %s", strna(pretty));
556
a4076574
LP
557 i->state = DNS_ZONE_ITEM_VERIFYING;
558 r = dns_zone_item_probe_start(i);
559 if (r < 0) {
da927ba9 560 log_error_errno(r, "Failed to start probing for verifying RR: %m");
ec2c5e43 561 i->state = DNS_ZONE_ITEM_ESTABLISHED;
a4076574
LP
562 return r;
563 }
564
565 return 0;
566}
567
568int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
569 DnsZoneItem *i, *first;
bf1594f5 570 int c = 0;
a4076574
LP
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
602int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
603 DnsZoneItem *i, *first;
bf1594f5 604 int c = 0;
a4076574
LP
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;
ec2c5e43 621}
902bb5d8
LP
622
623void 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}
4d506d6b
LP
636
637void 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
667bool dns_zone_is_empty(DnsZone *zone) {
668 if (!zone)
669 return true;
670
671 return hashmap_isempty(zone->by_key);
672}