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