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