]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-zone.c
Merge pull request #2589 from keszybz/resolve-tool-2
[thirdparty/systemd.git] / src / resolve / resolved-dns-zone.c
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
20 #include "alloc-util.h"
21 #include "dns-domain.h"
22 #include "list.h"
23 #include "resolved-dns-packet.h"
24 #include "resolved-dns-zone.h"
25 #include "string-util.h"
26
27 /* Never allow more than 1K entries */
28 #define ZONE_MAX 1024
29
30 void dns_zone_item_probe_stop(DnsZoneItem *i) {
31 DnsTransaction *t;
32 assert(i);
33
34 if (!i->probe_transaction)
35 return;
36
37 t = i->probe_transaction;
38 i->probe_transaction = NULL;
39
40 set_remove(t->notify_zone_items, i);
41 dns_transaction_gc(t);
42 }
43
44 static void dns_zone_item_free(DnsZoneItem *i) {
45 if (!i)
46 return;
47
48 dns_zone_item_probe_stop(i);
49 dns_resource_record_unref(i->rr);
50
51 free(i);
52 }
53
54 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
55
56 static 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
81 void 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
92 z->by_key = hashmap_free(z->by_key);
93 z->by_name = hashmap_free(z->by_name);
94 }
95
96 static 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))
103 if (dns_resource_record_equal(i->rr, rr) > 0)
104 return i;
105
106 return NULL;
107 }
108
109 void 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
120 static int dns_zone_init(DnsZone *z) {
121 int r;
122
123 assert(z);
124
125 r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops);
126 if (r < 0)
127 return r;
128
129 r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops);
130 if (r < 0)
131 return r;
132
133 return 0;
134 }
135
136 static 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
163 static int dns_zone_item_probe_start(DnsZoneItem *i) {
164 DnsTransaction *t;
165 int r;
166
167 assert(i);
168
169 if (i->probe_transaction)
170 return 0;
171
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);
173 if (!t) {
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
180 r = dns_transaction_new(&t, i->scope, key);
181 if (r < 0)
182 return r;
183 }
184
185 r = set_ensure_allocated(&t->notify_zone_items, NULL);
186 if (r < 0)
187 goto gc;
188
189 r = set_put(t->notify_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_notify(i);
208 return 0;
209
210 gc:
211 dns_transaction_gc(t);
212 return r;
213 }
214
215 int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
216 _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
217 DnsZoneItem *existing;
218 int r;
219
220 assert(z);
221 assert(s);
222 assert(rr);
223
224 if (dns_class_is_pseudo(rr->key->class))
225 return -EINVAL;
226 if (dns_type_is_pseudo(rr->key->type))
227 return -EINVAL;
228
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
241 i->scope = s;
242 i->rr = dns_resource_record_ref(rr);
243 i->probing_enabled = probe;
244
245 r = dns_zone_link_item(z, i);
246 if (r < 0)
247 return r;
248
249 if (probe) {
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;
264 }
265
266 if (established)
267 i->state = DNS_ZONE_ITEM_ESTABLISHED;
268 else {
269 i->state = DNS_ZONE_ITEM_PROBING;
270
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 }
277 }
278 } else
279 i->state = DNS_ZONE_ITEM_ESTABLISHED;
280
281 i = NULL;
282 return 0;
283 }
284
285 int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
286 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
287 unsigned n_answer = 0;
288 DnsZoneItem *j, *first;
289 bool tentative = true, need_soa = false;
290 int r;
291
292 assert(z);
293 assert(key);
294 assert(ret_answer);
295
296 /* First iteration, count what we have */
297
298 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
299 bool found = false, added = false;
300 int k;
301
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 */
305
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;
310
311 found = true;
312
313 k = dns_resource_key_match_rr(key, j->rr, NULL);
314 if (k < 0)
315 return k;
316 if (k > 0) {
317 n_answer++;
318 added = true;
319 }
320
321 }
322
323 if (found && !added)
324 need_soa = true;
325
326 } else {
327 bool found = false;
328
329 /* If this is a specific match, then look for
330 * the right key immediately */
331
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;
336
337 found = true;
338 n_answer++;
339 }
340
341 if (!found) {
342 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key));
343 LIST_FOREACH(by_name, j, first) {
344 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
345 continue;
346
347 need_soa = true;
348 break;
349 }
350 }
351 }
352
353 if (n_answer <= 0 && !need_soa)
354 goto return_empty;
355
356 if (n_answer > 0) {
357 answer = dns_answer_new(n_answer);
358 if (!answer)
359 return -ENOMEM;
360 }
361
362 if (need_soa) {
363 soa = dns_answer_new(1);
364 if (!soa)
365 return -ENOMEM;
366 }
367
368 /* Second iteration, actually add the RRs to the answers */
369 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
370 bool found = false, added = false;
371 int k;
372
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;
377
378 found = true;
379
380 if (j->state != DNS_ZONE_ITEM_PROBING)
381 tentative = false;
382
383 k = dns_resource_key_match_rr(key, j->rr, NULL);
384 if (k < 0)
385 return k;
386 if (k > 0) {
387 r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED);
388 if (r < 0)
389 return r;
390
391 added = true;
392 }
393 }
394
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;
402
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;
407
408 found = true;
409
410 if (j->state != DNS_ZONE_ITEM_PROBING)
411 tentative = false;
412
413 r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED);
414 if (r < 0)
415 return r;
416 }
417
418 if (!found) {
419 bool add_soa = false;
420
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;
425
426 if (j->state != DNS_ZONE_ITEM_PROBING)
427 tentative = false;
428
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;
436 }
437 }
438 }
439
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
446 *ret_answer = answer;
447 answer = NULL;
448
449 if (ret_soa) {
450 *ret_soa = soa;
451 soa = NULL;
452 }
453
454 if (ret_tentative)
455 *ret_tentative = tentative;
456
457 return 1;
458
459 return_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;
469 }
470
471 void dns_zone_item_conflict(DnsZoneItem *i) {
472 assert(i);
473
474 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
475 return;
476
477 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr)));
478
479 dns_zone_item_probe_stop(i);
480
481 /* Withdraw the conflict item */
482 i->state = DNS_ZONE_ITEM_WITHDRAWN;
483
484 /* Maybe change the hostname */
485 if (manager_is_own_hostname(i->scope->manager, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0)
486 manager_next_hostname(i->scope->manager);
487 }
488
489 void dns_zone_item_notify(DnsZoneItem *i) {
490 assert(i);
491 assert(i->probe_transaction);
492
493 if (i->block_ready > 0)
494 return;
495
496 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
497 return;
498
499 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
500 bool we_lost = false;
501
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
505 * lexicographically larger IP address we continue
506 * and defend it. */
507
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.");
510 we_lost = true;
511 } else {
512 assert(i->probe_transaction->received);
513 we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
514 if (we_lost)
515 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
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
526 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr)));
527
528 dns_zone_item_probe_stop(i);
529 i->state = DNS_ZONE_ITEM_ESTABLISHED;
530 }
531
532 static 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
540 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr)));
541
542 i->state = DNS_ZONE_ITEM_VERIFYING;
543 r = dns_zone_item_probe_start(i);
544 if (r < 0) {
545 log_error_errno(r, "Failed to start probing for verifying RR: %m");
546 i->state = DNS_ZONE_ITEM_ESTABLISHED;
547 return r;
548 }
549
550 return 0;
551 }
552
553 int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
554 DnsZoneItem *i, *first;
555 int c = 0;
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
587 int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
588 DnsZoneItem *i, *first;
589 int c = 0;
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;
606 }
607
608 void 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 }
621
622 void dns_zone_dump(DnsZone *zone, FILE *f) {
623 Iterator iterator;
624 DnsZoneItem *i;
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) {
636 const char *t;
637
638 t = dns_resource_record_to_string(j->rr);
639 if (!t) {
640 log_oom();
641 continue;
642 }
643
644 fputc('\t', f);
645 fputs(t, f);
646 fputc('\n', f);
647 }
648 }
649 }
650
651 bool dns_zone_is_empty(DnsZone *zone) {
652 if (!zone)
653 return true;
654
655 return hashmap_isempty(zone->by_key);
656 }