]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-zone.c
Merge pull request #2138 from stefwalter/journal-combine
[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 (dns_class_is_pseudo(rr->key->class))
227 return -EINVAL;
228 if (dns_type_is_pseudo(rr->key->type))
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, DNS_ANSWER_AUTHENTICATED);
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, DNS_ANSWER_AUTHENTICATED);
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 assert(i);
475
476 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
477 return;
478
479 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr)));
480
481 dns_zone_item_probe_stop(i);
482
483 /* Withdraw the conflict item */
484 i->state = DNS_ZONE_ITEM_WITHDRAWN;
485
486 /* Maybe change the hostname */
487 if (manager_is_own_hostname(i->scope->manager, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0)
488 manager_next_hostname(i->scope->manager);
489 }
490
491 void dns_zone_item_notify(DnsZoneItem *i) {
492 assert(i);
493 assert(i->probe_transaction);
494
495 if (i->block_ready > 0)
496 return;
497
498 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
499 return;
500
501 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
502 bool we_lost = false;
503
504 /* The probe got a successful reply. If we so far
505 * weren't established we just give up. If we already
506 * were established, and the peer has the
507 * lexicographically larger IP address we continue
508 * and defend it. */
509
510 if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
511 log_debug("Got a successful probe for not yet established RR, we lost.");
512 we_lost = true;
513 } else {
514 assert(i->probe_transaction->received);
515 we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
516 if (we_lost)
517 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
518 }
519
520 if (we_lost) {
521 dns_zone_item_conflict(i);
522 return;
523 }
524
525 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
526 }
527
528 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr)));
529
530 dns_zone_item_probe_stop(i);
531 i->state = DNS_ZONE_ITEM_ESTABLISHED;
532 }
533
534 static int dns_zone_item_verify(DnsZoneItem *i) {
535 int r;
536
537 assert(i);
538
539 if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
540 return 0;
541
542 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr)));
543
544 i->state = DNS_ZONE_ITEM_VERIFYING;
545 r = dns_zone_item_probe_start(i);
546 if (r < 0) {
547 log_error_errno(r, "Failed to start probing for verifying RR: %m");
548 i->state = DNS_ZONE_ITEM_ESTABLISHED;
549 return r;
550 }
551
552 return 0;
553 }
554
555 int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
556 DnsZoneItem *i, *first;
557 int c = 0;
558
559 assert(zone);
560 assert(rr);
561
562 /* This checks whether a response RR we received from somebody
563 * else is one that we actually thought was uniquely ours. If
564 * so, we'll verify our RRs. */
565
566 /* No conflict if we don't have the name at all. */
567 first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(rr->key));
568 if (!first)
569 return 0;
570
571 /* No conflict if we have the exact same RR */
572 if (dns_zone_get(zone, rr))
573 return 0;
574
575 /* OK, somebody else has RRs for the same name. Yuck! Let's
576 * start probing again */
577
578 LIST_FOREACH(by_name, i, first) {
579 if (dns_resource_record_equal(i->rr, rr))
580 continue;
581
582 dns_zone_item_verify(i);
583 c++;
584 }
585
586 return c;
587 }
588
589 int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
590 DnsZoneItem *i, *first;
591 int c = 0;
592
593 assert(zone);
594
595 /* Somebody else notified us about a possible conflict. Let's
596 * verify if that's true. */
597
598 first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(key));
599 if (!first)
600 return 0;
601
602 LIST_FOREACH(by_name, i, first) {
603 dns_zone_item_verify(i);
604 c++;
605 }
606
607 return c;
608 }
609
610 void dns_zone_verify_all(DnsZone *zone) {
611 DnsZoneItem *i;
612 Iterator iterator;
613
614 assert(zone);
615
616 HASHMAP_FOREACH(i, zone->by_key, iterator) {
617 DnsZoneItem *j;
618
619 LIST_FOREACH(by_key, j, i)
620 dns_zone_item_verify(j);
621 }
622 }
623
624 void dns_zone_dump(DnsZone *zone, FILE *f) {
625 Iterator iterator;
626 DnsZoneItem *i;
627
628 if (!zone)
629 return;
630
631 if (!f)
632 f = stdout;
633
634 HASHMAP_FOREACH(i, zone->by_key, iterator) {
635 DnsZoneItem *j;
636
637 LIST_FOREACH(by_key, j, i) {
638 const char *t;
639
640 t = dns_resource_record_to_string(j->rr);
641 if (!t) {
642 log_oom();
643 continue;
644 }
645
646 fputc('\t', f);
647 fputs(t, f);
648 fputc('\n', f);
649 }
650 }
651 }
652
653 bool dns_zone_is_empty(DnsZone *zone) {
654 if (!zone)
655 return true;
656
657 return hashmap_isempty(zone->by_key);
658 }