]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-zone.c
systemd-resolved: split out inner loop
[thirdparty/systemd.git] / src / resolve / resolved-dns-zone.c
CommitLineData
623a4c97
LP
1/***
2 This file is part of systemd.
3
4 Copyright 2014 Lennart Poettering
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18***/
19
b5efdb8a 20#include "alloc-util.h"
4ad7f276 21#include "dns-domain.h"
07630cea 22#include "list.h"
623a4c97 23#include "resolved-dns-packet.h"
07630cea
LP
24#include "resolved-dns-zone.h"
25#include "string-util.h"
623a4c97
LP
26
27/* Never allow more than 1K entries */
28#define ZONE_MAX 1024
29
3ef64445 30void dns_zone_item_probe_stop(DnsZoneItem *i) {
ec2c5e43
LP
31 DnsTransaction *t;
32 assert(i);
623a4c97 33
ec2c5e43
LP
34 if (!i->probe_transaction)
35 return;
36
37 t = i->probe_transaction;
38 i->probe_transaction = NULL;
39
547973de 40 set_remove(t->notify_zone_items, i);
ec2c5e43
LP
41 dns_transaction_gc(t);
42}
623a4c97
LP
43
44static void dns_zone_item_free(DnsZoneItem *i) {
45 if (!i)
46 return;
47
ec2c5e43 48 dns_zone_item_probe_stop(i);
623a4c97 49 dns_resource_record_unref(i->rr);
ec2c5e43 50
623a4c97
LP
51 free(i);
52}
53
54DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
55
56static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) {
57 DnsZoneItem *first;
58
59 assert(z);
60
61 if (!i)
62 return;
63
64 first = hashmap_get(z->by_key, i->rr->key);
65 LIST_REMOVE(by_key, first, i);
66 if (first)
67 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
68 else
69 hashmap_remove(z->by_key, i->rr->key);
70
71 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
72 LIST_REMOVE(by_name, first, i);
73 if (first)
74 assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0);
75 else
76 hashmap_remove(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
77
78 dns_zone_item_free(i);
79}
80
81void dns_zone_flush(DnsZone *z) {
82 DnsZoneItem *i;
83
84 assert(z);
85
86 while ((i = hashmap_first(z->by_key)))
87 dns_zone_item_remove_and_free(z, i);
88
89 assert(hashmap_size(z->by_key) == 0);
90 assert(hashmap_size(z->by_name) == 0);
91
525d3cc7
LP
92 z->by_key = hashmap_free(z->by_key);
93 z->by_name = hashmap_free(z->by_name);
623a4c97
LP
94}
95
96static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
97 DnsZoneItem *i;
98
99 assert(z);
100 assert(rr);
101
102 LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key))
3ef77d04 103 if (dns_resource_record_equal(i->rr, rr) > 0)
623a4c97
LP
104 return i;
105
106 return NULL;
107}
108
109void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
110 DnsZoneItem *i;
111
112 assert(z);
113 assert(rr);
114
115 i = dns_zone_get(z, rr);
116 if (i)
117 dns_zone_item_remove_and_free(z, i);
118}
119
120static int dns_zone_init(DnsZone *z) {
121 int r;
122
123 assert(z);
124
d5099efc 125 r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops);
623a4c97
LP
126 if (r < 0)
127 return r;
128
d5099efc 129 r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops);
623a4c97
LP
130 if (r < 0)
131 return r;
132
133 return 0;
134}
135
136static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
137 DnsZoneItem *first;
138 int r;
139
140 first = hashmap_get(z->by_key, i->rr->key);
141 if (first) {
142 LIST_PREPEND(by_key, first, i);
143 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
144 } else {
145 r = hashmap_put(z->by_key, i->rr->key, i);
146 if (r < 0)
147 return r;
148 }
149
150 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
151 if (first) {
152 LIST_PREPEND(by_name, first, i);
153 assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0);
154 } else {
155 r = hashmap_put(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key), i);
156 if (r < 0)
157 return r;
158 }
159
160 return 0;
161}
162
ec2c5e43 163static int dns_zone_item_probe_start(DnsZoneItem *i) {
ec2c5e43
LP
164 DnsTransaction *t;
165 int r;
166
167 assert(i);
168
169 if (i->probe_transaction)
170 return 0;
171
1b4f6e79 172 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 173 if (!t) {
1b4f6e79
LP
174 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
175
176 key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key));
177 if (!key)
178 return -ENOMEM;
179
f52e61da 180 r = dns_transaction_new(&t, i->scope, key);
ec2c5e43
LP
181 if (r < 0)
182 return r;
183 }
184
547973de 185 r = set_ensure_allocated(&t->notify_zone_items, NULL);
ec2c5e43
LP
186 if (r < 0)
187 goto gc;
188
547973de 189 r = set_put(t->notify_zone_items, i);
ec2c5e43
LP
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
547973de 207 dns_zone_item_notify(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
222148b6 224 if (dns_class_is_pseudo(rr->key->class))
1d3b690f 225 return -EINVAL;
222148b6 226 if (dns_type_is_pseudo(rr->key->type))
1d3b690f
LP
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
5032b16d 285int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
8bf52d3d 286 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
5032b16d
LP
287 unsigned n_answer = 0;
288 DnsZoneItem *j, *first;
289 bool tentative = true, need_soa = false;
d5323661 290 int r;
623a4c97
LP
291
292 assert(z);
5032b16d 293 assert(key);
8bf52d3d 294 assert(ret_answer);
623a4c97 295
5032b16d 296 /* First iteration, count what we have */
ec2c5e43 297
5032b16d
LP
298 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
299 bool found = false, added = false;
300 int k;
623a4c97 301
5032b16d
LP
302 /* If this is a generic match, then we have to
303 * go through the list by the name and look
304 * for everything manually */
623a4c97 305
5032b16d
LP
306 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key));
307 LIST_FOREACH(by_name, j, first) {
308 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
309 continue;
d5323661 310
5032b16d 311 found = true;
d5323661 312
801ad6a6 313 k = dns_resource_key_match_rr(key, j->rr, NULL);
5032b16d
LP
314 if (k < 0)
315 return k;
316 if (k > 0) {
317 n_answer++;
318 added = true;
319 }
ec2c5e43 320
5032b16d 321 }
ec2c5e43 322
5032b16d
LP
323 if (found && !added)
324 need_soa = true;
ec2c5e43 325
5032b16d
LP
326 } else {
327 bool found = false;
d5323661 328
5032b16d
LP
329 /* If this is a specific match, then look for
330 * the right key immediately */
ec2c5e43 331
5032b16d
LP
332 first = hashmap_get(z->by_key, key);
333 LIST_FOREACH(by_key, j, first) {
334 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
335 continue;
ec2c5e43 336
5032b16d
LP
337 found = true;
338 n_answer++;
339 }
ec2c5e43 340
5032b16d
LP
341 if (!found) {
342 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key));
343 LIST_FOREACH(by_name, j, first) {
ec2c5e43
LP
344 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
345 continue;
346
5032b16d
LP
347 need_soa = true;
348 break;
d5323661 349 }
d5323661 350 }
623a4c97
LP
351 }
352
5032b16d
LP
353 if (n_answer <= 0 && !need_soa)
354 goto return_empty;
623a4c97 355
8bf52d3d
LP
356 if (n_answer > 0) {
357 answer = dns_answer_new(n_answer);
358 if (!answer)
359 return -ENOMEM;
360 }
623a4c97 361
5032b16d
LP
362 if (need_soa) {
363 soa = dns_answer_new(1);
8bf52d3d
LP
364 if (!soa)
365 return -ENOMEM;
366 }
367
368 /* Second iteration, actually add the RRs to the answers */
5032b16d
LP
369 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
370 bool found = false, added = false;
371 int k;
d5323661 372
5032b16d
LP
373 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key));
374 LIST_FOREACH(by_name, j, first) {
375 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
376 continue;
ec2c5e43 377
5032b16d 378 found = true;
ec2c5e43 379
5032b16d
LP
380 if (j->state != DNS_ZONE_ITEM_PROBING)
381 tentative = false;
ec2c5e43 382
801ad6a6 383 k = dns_resource_key_match_rr(key, j->rr, NULL);
5032b16d
LP
384 if (k < 0)
385 return k;
386 if (k > 0) {
105e1512 387 r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED);
d5323661
LP
388 if (r < 0)
389 return r;
5032b16d
LP
390
391 added = true;
d5323661 392 }
5032b16d 393 }
d5323661 394
5032b16d
LP
395 if (found && !added) {
396 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(key), LLMNR_DEFAULT_TTL);
397 if (r < 0)
398 return r;
399 }
400 } else {
401 bool found = false;
ec2c5e43 402
5032b16d
LP
403 first = hashmap_get(z->by_key, key);
404 LIST_FOREACH(by_key, j, first) {
405 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
406 continue;
ec2c5e43 407
5032b16d 408 found = true;
ec2c5e43 409
5032b16d
LP
410 if (j->state != DNS_ZONE_ITEM_PROBING)
411 tentative = false;
ec2c5e43 412
105e1512 413 r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED);
5032b16d
LP
414 if (r < 0)
415 return r;
416 }
ec2c5e43 417
5032b16d
LP
418 if (!found) {
419 bool add_soa = false;
ec2c5e43 420
5032b16d
LP
421 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key));
422 LIST_FOREACH(by_name, j, first) {
423 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
424 continue;
ec2c5e43 425
5032b16d
LP
426 if (j->state != DNS_ZONE_ITEM_PROBING)
427 tentative = false;
ec2c5e43 428
5032b16d
LP
429 add_soa = true;
430 }
431
432 if (add_soa) {
433 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(key), LLMNR_DEFAULT_TTL);
434 if (r < 0)
435 return r;
d5323661 436 }
623a4c97
LP
437 }
438 }
439
5032b16d
LP
440 /* If the caller sets ret_tentative to NULL, then use this as
441 * indication to not return tentative entries */
442
443 if (!ret_tentative && tentative)
444 goto return_empty;
445
8bf52d3d 446 *ret_answer = answer;
623a4c97
LP
447 answer = NULL;
448
5032b16d
LP
449 if (ret_soa) {
450 *ret_soa = soa;
451 soa = NULL;
452 }
8bf52d3d 453
ec2c5e43
LP
454 if (ret_tentative)
455 *ret_tentative = tentative;
456
623a4c97 457 return 1;
5032b16d
LP
458
459return_empty:
460 *ret_answer = NULL;
461
462 if (ret_soa)
463 *ret_soa = NULL;
464
465 if (ret_tentative)
466 *ret_tentative = false;
467
468 return 0;
623a4c97 469}
ec2c5e43
LP
470
471void dns_zone_item_conflict(DnsZoneItem *i) {
ec2c5e43
LP
472 assert(i);
473
a4076574
LP
474 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
475 return;
476
7b50eb2e 477 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr)));
ec2c5e43 478
d84b686f
LP
479 dns_zone_item_probe_stop(i);
480
ec2c5e43
LP
481 /* Withdraw the conflict item */
482 i->state = DNS_ZONE_ITEM_WITHDRAWN;
483
484 /* Maybe change the hostname */
78c6a153 485 if (manager_is_own_hostname(i->scope->manager, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0)
ec2c5e43
LP
486 manager_next_hostname(i->scope->manager);
487}
488
547973de 489void dns_zone_item_notify(DnsZoneItem *i) {
ec2c5e43
LP
490 assert(i);
491 assert(i->probe_transaction);
492
493 if (i->block_ready > 0)
494 return;
495
547973de 496 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
ec2c5e43
LP
497 return;
498
a4076574
LP
499 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
500 bool we_lost = false;
ec2c5e43 501
a4076574
LP
502 /* The probe got a successful reply. If we so far
503 * weren't established we just give up. If we already
504 * were established, and the peer has the
4d91eec4 505 * lexicographically larger IP address we continue
a4076574
LP
506 * and defend it. */
507
2fb3034c
LP
508 if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
509 log_debug("Got a successful probe for not yet established RR, we lost.");
a4076574 510 we_lost = true;
2fb3034c 511 } else {
a4076574 512 assert(i->probe_transaction->received);
4d91eec4 513 we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
2fb3034c 514 if (we_lost)
4d91eec4 515 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
a4076574
LP
516 }
517
518 if (we_lost) {
519 dns_zone_item_conflict(i);
520 return;
521 }
522
523 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
524 }
525
7b50eb2e 526 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr)));
ec2c5e43 527
a4076574
LP
528 dns_zone_item_probe_stop(i);
529 i->state = DNS_ZONE_ITEM_ESTABLISHED;
530}
531
532static int dns_zone_item_verify(DnsZoneItem *i) {
533 int r;
534
535 assert(i);
536
537 if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
538 return 0;
539
7b50eb2e 540 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr)));
2fb3034c 541
a4076574
LP
542 i->state = DNS_ZONE_ITEM_VERIFYING;
543 r = dns_zone_item_probe_start(i);
544 if (r < 0) {
da927ba9 545 log_error_errno(r, "Failed to start probing for verifying RR: %m");
ec2c5e43 546 i->state = DNS_ZONE_ITEM_ESTABLISHED;
a4076574
LP
547 return r;
548 }
549
550 return 0;
551}
552
553int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
554 DnsZoneItem *i, *first;
bf1594f5 555 int c = 0;
a4076574
LP
556
557 assert(zone);
558 assert(rr);
559
560 /* This checks whether a response RR we received from somebody
561 * else is one that we actually thought was uniquely ours. If
562 * so, we'll verify our RRs. */
563
564 /* No conflict if we don't have the name at all. */
565 first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(rr->key));
566 if (!first)
567 return 0;
568
569 /* No conflict if we have the exact same RR */
570 if (dns_zone_get(zone, rr))
571 return 0;
572
573 /* OK, somebody else has RRs for the same name. Yuck! Let's
574 * start probing again */
575
576 LIST_FOREACH(by_name, i, first) {
577 if (dns_resource_record_equal(i->rr, rr))
578 continue;
579
580 dns_zone_item_verify(i);
581 c++;
582 }
583
584 return c;
585}
586
587int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
588 DnsZoneItem *i, *first;
bf1594f5 589 int c = 0;
a4076574
LP
590
591 assert(zone);
592
593 /* Somebody else notified us about a possible conflict. Let's
594 * verify if that's true. */
595
596 first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(key));
597 if (!first)
598 return 0;
599
600 LIST_FOREACH(by_name, i, first) {
601 dns_zone_item_verify(i);
602 c++;
603 }
604
605 return c;
ec2c5e43 606}
902bb5d8
LP
607
608void dns_zone_verify_all(DnsZone *zone) {
609 DnsZoneItem *i;
610 Iterator iterator;
611
612 assert(zone);
613
614 HASHMAP_FOREACH(i, zone->by_key, iterator) {
615 DnsZoneItem *j;
616
617 LIST_FOREACH(by_key, j, i)
618 dns_zone_item_verify(j);
619 }
620}
4d506d6b
LP
621
622void dns_zone_dump(DnsZone *zone, FILE *f) {
623 Iterator iterator;
624 DnsZoneItem *i;
4d506d6b
LP
625
626 if (!zone)
627 return;
628
629 if (!f)
630 f = stdout;
631
632 HASHMAP_FOREACH(i, zone->by_key, iterator) {
633 DnsZoneItem *j;
634
635 LIST_FOREACH(by_key, j, i) {
7b50eb2e 636 const char *t;
4d506d6b 637
7b50eb2e
LP
638 t = dns_resource_record_to_string(j->rr);
639 if (!t) {
4d506d6b
LP
640 log_oom();
641 continue;
642 }
643
644 fputc('\t', f);
645 fputs(t, f);
646 fputc('\n', f);
647 }
648 }
649}
650
651bool dns_zone_is_empty(DnsZone *zone) {
652 if (!zone)
653 return true;
654
655 return hashmap_isempty(zone->by_key);
656}