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