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