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