]>
Commit | Line | Data |
---|---|---|
52ad194e | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
5eef597e MP |
2 | /*** |
3 | This file is part of systemd. | |
4 | ||
5 | Copyright 2014 Lennart Poettering | |
6 | ||
7 | systemd is free software; you can redistribute it and/or modify it | |
8 | under the terms of the GNU Lesser General Public License as published by | |
9 | the Free Software Foundation; either version 2.1 of the License, or | |
10 | (at your option) any later version. | |
11 | ||
12 | systemd is distributed in the hope that it will be useful, but | |
13 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | Lesser General Public License for more details. | |
16 | ||
17 | You should have received a copy of the GNU Lesser General Public License | |
18 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
19 | ***/ | |
20 | ||
db2df898 | 21 | #include "alloc-util.h" |
86f210e9 | 22 | #include "dns-domain.h" |
db2df898 | 23 | #include "list.h" |
5eef597e | 24 | #include "resolved-dns-packet.h" |
db2df898 | 25 | #include "resolved-dns-zone.h" |
52ad194e | 26 | #include "resolved-dnssd.h" |
db2df898 | 27 | #include "string-util.h" |
5eef597e MP |
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 | ||
4c89c718 | 42 | set_remove(t->notify_zone_items, i); |
aa27b158 | 43 | set_remove(t->notify_zone_items_done, i); |
5eef597e MP |
44 | dns_transaction_gc(t); |
45 | } | |
46 | ||
47 | static void dns_zone_item_free(DnsZoneItem *i) { | |
48 | if (!i) | |
49 | return; | |
50 | ||
51 | dns_zone_item_probe_stop(i); | |
52 | dns_resource_record_unref(i->rr); | |
53 | ||
54 | free(i); | |
55 | } | |
56 | ||
57 | DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free); | |
58 | ||
59 | static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) { | |
60 | DnsZoneItem *first; | |
61 | ||
62 | assert(z); | |
63 | ||
64 | if (!i) | |
65 | return; | |
66 | ||
67 | first = hashmap_get(z->by_key, i->rr->key); | |
68 | LIST_REMOVE(by_key, first, i); | |
69 | if (first) | |
70 | assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); | |
71 | else | |
72 | hashmap_remove(z->by_key, i->rr->key); | |
73 | ||
aa27b158 | 74 | first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key)); |
5eef597e MP |
75 | LIST_REMOVE(by_name, first, i); |
76 | if (first) | |
aa27b158 | 77 | assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0); |
5eef597e | 78 | else |
aa27b158 | 79 | hashmap_remove(z->by_name, dns_resource_key_name(i->rr->key)); |
5eef597e MP |
80 | |
81 | dns_zone_item_free(i); | |
82 | } | |
83 | ||
84 | void dns_zone_flush(DnsZone *z) { | |
85 | DnsZoneItem *i; | |
86 | ||
87 | assert(z); | |
88 | ||
89 | while ((i = hashmap_first(z->by_key))) | |
90 | dns_zone_item_remove_and_free(z, i); | |
91 | ||
92 | assert(hashmap_size(z->by_key) == 0); | |
93 | assert(hashmap_size(z->by_name) == 0); | |
94 | ||
6300502b MP |
95 | z->by_key = hashmap_free(z->by_key); |
96 | z->by_name = hashmap_free(z->by_name); | |
5eef597e MP |
97 | } |
98 | ||
52ad194e | 99 | DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) { |
5eef597e MP |
100 | DnsZoneItem *i; |
101 | ||
102 | assert(z); | |
103 | assert(rr); | |
104 | ||
105 | LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key)) | |
106 | if (dns_resource_record_equal(i->rr, rr) > 0) | |
107 | return i; | |
108 | ||
109 | return NULL; | |
110 | } | |
111 | ||
112 | void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) { | |
113 | DnsZoneItem *i; | |
114 | ||
115 | assert(z); | |
116 | assert(rr); | |
117 | ||
118 | i = dns_zone_get(z, rr); | |
119 | if (i) | |
120 | dns_zone_item_remove_and_free(z, i); | |
121 | } | |
122 | ||
52ad194e MB |
123 | int dns_zone_remove_rrs_by_key(DnsZone *z, DnsResourceKey *key) { |
124 | _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; | |
125 | DnsResourceRecord *rr; | |
126 | bool tentative; | |
127 | int r; | |
128 | ||
129 | r = dns_zone_lookup(z, key, 0, &answer, &soa, &tentative); | |
130 | if (r < 0) | |
131 | return r; | |
132 | ||
133 | DNS_ANSWER_FOREACH(rr, answer) | |
134 | dns_zone_remove_rr(z, rr); | |
135 | ||
136 | return 0; | |
137 | } | |
138 | ||
5eef597e MP |
139 | static int dns_zone_init(DnsZone *z) { |
140 | int r; | |
141 | ||
142 | assert(z); | |
143 | ||
144 | r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops); | |
145 | if (r < 0) | |
146 | return r; | |
147 | ||
148 | r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops); | |
149 | if (r < 0) | |
150 | return r; | |
151 | ||
152 | return 0; | |
153 | } | |
154 | ||
155 | static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) { | |
156 | DnsZoneItem *first; | |
157 | int r; | |
158 | ||
159 | first = hashmap_get(z->by_key, i->rr->key); | |
160 | if (first) { | |
161 | LIST_PREPEND(by_key, first, i); | |
162 | assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); | |
163 | } else { | |
164 | r = hashmap_put(z->by_key, i->rr->key, i); | |
165 | if (r < 0) | |
166 | return r; | |
167 | } | |
168 | ||
aa27b158 | 169 | first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key)); |
5eef597e MP |
170 | if (first) { |
171 | LIST_PREPEND(by_name, first, i); | |
aa27b158 | 172 | assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0); |
5eef597e | 173 | } else { |
aa27b158 | 174 | r = hashmap_put(z->by_name, dns_resource_key_name(i->rr->key), i); |
5eef597e MP |
175 | if (r < 0) |
176 | return r; | |
177 | } | |
178 | ||
179 | return 0; | |
180 | } | |
181 | ||
182 | static int dns_zone_item_probe_start(DnsZoneItem *i) { | |
5eef597e MP |
183 | DnsTransaction *t; |
184 | int r; | |
185 | ||
186 | assert(i); | |
187 | ||
188 | if (i->probe_transaction) | |
189 | return 0; | |
190 | ||
aa27b158 | 191 | 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); |
5eef597e | 192 | if (!t) { |
4c89c718 MP |
193 | _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; |
194 | ||
aa27b158 | 195 | key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)); |
4c89c718 MP |
196 | if (!key) |
197 | return -ENOMEM; | |
198 | ||
13d276d0 | 199 | r = dns_transaction_new(&t, i->scope, key); |
5eef597e MP |
200 | if (r < 0) |
201 | return r; | |
202 | } | |
203 | ||
4c89c718 | 204 | r = set_ensure_allocated(&t->notify_zone_items, NULL); |
5eef597e MP |
205 | if (r < 0) |
206 | goto gc; | |
207 | ||
aa27b158 MP |
208 | r = set_ensure_allocated(&t->notify_zone_items_done, NULL); |
209 | if (r < 0) | |
210 | goto gc; | |
211 | ||
4c89c718 | 212 | r = set_put(t->notify_zone_items, i); |
5eef597e MP |
213 | if (r < 0) |
214 | goto gc; | |
215 | ||
216 | i->probe_transaction = t; | |
2897b343 | 217 | t->probing = true; |
5eef597e MP |
218 | |
219 | if (t->state == DNS_TRANSACTION_NULL) { | |
220 | ||
221 | i->block_ready++; | |
222 | r = dns_transaction_go(t); | |
223 | i->block_ready--; | |
224 | ||
225 | if (r < 0) { | |
226 | dns_zone_item_probe_stop(i); | |
227 | return r; | |
228 | } | |
229 | } | |
230 | ||
4c89c718 | 231 | dns_zone_item_notify(i); |
5eef597e MP |
232 | return 0; |
233 | ||
234 | gc: | |
235 | dns_transaction_gc(t); | |
236 | return r; | |
237 | } | |
238 | ||
239 | int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) { | |
240 | _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL; | |
241 | DnsZoneItem *existing; | |
242 | int r; | |
243 | ||
244 | assert(z); | |
245 | assert(s); | |
246 | assert(rr); | |
247 | ||
4c89c718 | 248 | if (dns_class_is_pseudo(rr->key->class)) |
5eef597e | 249 | return -EINVAL; |
4c89c718 | 250 | if (dns_type_is_pseudo(rr->key->type)) |
5eef597e MP |
251 | return -EINVAL; |
252 | ||
253 | existing = dns_zone_get(z, rr); | |
254 | if (existing) | |
255 | return 0; | |
256 | ||
257 | r = dns_zone_init(z); | |
258 | if (r < 0) | |
259 | return r; | |
260 | ||
261 | i = new0(DnsZoneItem, 1); | |
262 | if (!i) | |
263 | return -ENOMEM; | |
264 | ||
265 | i->scope = s; | |
266 | i->rr = dns_resource_record_ref(rr); | |
267 | i->probing_enabled = probe; | |
268 | ||
269 | r = dns_zone_link_item(z, i); | |
270 | if (r < 0) | |
271 | return r; | |
272 | ||
273 | if (probe) { | |
274 | DnsZoneItem *first, *j; | |
275 | bool established = false; | |
276 | ||
277 | /* Check if there's already an RR with the same name | |
278 | * established. If so, it has been probed already, and | |
279 | * we don't ned to probe again. */ | |
280 | ||
281 | LIST_FIND_HEAD(by_name, i, first); | |
282 | LIST_FOREACH(by_name, j, first) { | |
283 | if (i == j) | |
284 | continue; | |
285 | ||
286 | if (j->state == DNS_ZONE_ITEM_ESTABLISHED) | |
287 | established = true; | |
288 | } | |
289 | ||
290 | if (established) | |
291 | i->state = DNS_ZONE_ITEM_ESTABLISHED; | |
292 | else { | |
293 | i->state = DNS_ZONE_ITEM_PROBING; | |
294 | ||
295 | r = dns_zone_item_probe_start(i); | |
296 | if (r < 0) { | |
297 | dns_zone_item_remove_and_free(z, i); | |
298 | i = NULL; | |
299 | return r; | |
300 | } | |
301 | } | |
302 | } else | |
303 | i->state = DNS_ZONE_ITEM_ESTABLISHED; | |
304 | ||
305 | i = NULL; | |
306 | return 0; | |
307 | } | |
308 | ||
52ad194e MB |
309 | static int dns_zone_add_authenticated_answer(DnsAnswer *a, DnsZoneItem *i, int ifindex) { |
310 | DnsAnswerFlags flags; | |
311 | ||
312 | /* From RFC 6762, Section 10.2 | |
313 | * "They (the rules about when to set the cache-flush bit) apply to | |
314 | * startup announcements as described in Section 8.3, "Announcing", | |
315 | * and to responses generated as a result of receiving query messages." | |
316 | * So, set the cache-flush bit for mDNS answers except for DNS-SD | |
317 | * service enumeration PTRs described in RFC 6763, Section 4.1. */ | |
318 | if (i->scope->protocol == DNS_PROTOCOL_MDNS && | |
319 | !dns_resource_key_is_dnssd_ptr(i->rr->key)) | |
320 | flags = DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHE_FLUSH; | |
321 | else | |
322 | flags = DNS_ANSWER_AUTHENTICATED; | |
323 | ||
324 | return dns_answer_add(a, i->rr, ifindex, flags); | |
325 | } | |
326 | ||
5a920b42 | 327 | int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) { |
5eef597e | 328 | _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; |
4c89c718 MP |
329 | unsigned n_answer = 0; |
330 | DnsZoneItem *j, *first; | |
331 | bool tentative = true, need_soa = false; | |
5eef597e MP |
332 | int r; |
333 | ||
5a920b42 MP |
334 | /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the |
335 | * ifindex field in the answer with it */ | |
336 | ||
5eef597e | 337 | assert(z); |
4c89c718 | 338 | assert(key); |
5eef597e | 339 | assert(ret_answer); |
5eef597e | 340 | |
4c89c718 | 341 | /* First iteration, count what we have */ |
5eef597e | 342 | |
4c89c718 MP |
343 | if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { |
344 | bool found = false, added = false; | |
345 | int k; | |
5eef597e | 346 | |
4c89c718 MP |
347 | /* If this is a generic match, then we have to |
348 | * go through the list by the name and look | |
349 | * for everything manually */ | |
5eef597e | 350 | |
aa27b158 | 351 | first = hashmap_get(z->by_name, dns_resource_key_name(key)); |
4c89c718 MP |
352 | LIST_FOREACH(by_name, j, first) { |
353 | if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) | |
354 | continue; | |
5eef597e | 355 | |
4c89c718 | 356 | found = true; |
5eef597e | 357 | |
4c89c718 MP |
358 | k = dns_resource_key_match_rr(key, j->rr, NULL); |
359 | if (k < 0) | |
360 | return k; | |
361 | if (k > 0) { | |
362 | n_answer++; | |
363 | added = true; | |
364 | } | |
5eef597e | 365 | |
4c89c718 | 366 | } |
5eef597e | 367 | |
4c89c718 MP |
368 | if (found && !added) |
369 | need_soa = true; | |
5eef597e | 370 | |
4c89c718 MP |
371 | } else { |
372 | bool found = false; | |
5eef597e | 373 | |
4c89c718 MP |
374 | /* If this is a specific match, then look for |
375 | * the right key immediately */ | |
5eef597e | 376 | |
4c89c718 MP |
377 | first = hashmap_get(z->by_key, key); |
378 | LIST_FOREACH(by_key, j, first) { | |
379 | if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) | |
380 | continue; | |
5eef597e | 381 | |
4c89c718 MP |
382 | found = true; |
383 | n_answer++; | |
384 | } | |
5eef597e | 385 | |
4c89c718 | 386 | if (!found) { |
aa27b158 | 387 | first = hashmap_get(z->by_name, dns_resource_key_name(key)); |
4c89c718 | 388 | LIST_FOREACH(by_name, j, first) { |
5eef597e MP |
389 | if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) |
390 | continue; | |
391 | ||
4c89c718 MP |
392 | need_soa = true; |
393 | break; | |
5eef597e MP |
394 | } |
395 | } | |
396 | } | |
397 | ||
4c89c718 MP |
398 | if (n_answer <= 0 && !need_soa) |
399 | goto return_empty; | |
5eef597e MP |
400 | |
401 | if (n_answer > 0) { | |
402 | answer = dns_answer_new(n_answer); | |
403 | if (!answer) | |
404 | return -ENOMEM; | |
405 | } | |
406 | ||
4c89c718 MP |
407 | if (need_soa) { |
408 | soa = dns_answer_new(1); | |
5eef597e MP |
409 | if (!soa) |
410 | return -ENOMEM; | |
411 | } | |
412 | ||
413 | /* Second iteration, actually add the RRs to the answers */ | |
4c89c718 MP |
414 | if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { |
415 | bool found = false, added = false; | |
416 | int k; | |
5eef597e | 417 | |
aa27b158 | 418 | first = hashmap_get(z->by_name, dns_resource_key_name(key)); |
4c89c718 MP |
419 | LIST_FOREACH(by_name, j, first) { |
420 | if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) | |
421 | continue; | |
5eef597e | 422 | |
4c89c718 | 423 | found = true; |
5eef597e | 424 | |
4c89c718 MP |
425 | if (j->state != DNS_ZONE_ITEM_PROBING) |
426 | tentative = false; | |
5eef597e | 427 | |
4c89c718 MP |
428 | k = dns_resource_key_match_rr(key, j->rr, NULL); |
429 | if (k < 0) | |
430 | return k; | |
431 | if (k > 0) { | |
52ad194e | 432 | r = dns_zone_add_authenticated_answer(answer, j, ifindex); |
5eef597e MP |
433 | if (r < 0) |
434 | return r; | |
4c89c718 MP |
435 | |
436 | added = true; | |
5eef597e | 437 | } |
4c89c718 | 438 | } |
5eef597e | 439 | |
4c89c718 | 440 | if (found && !added) { |
5a920b42 | 441 | r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex); |
4c89c718 MP |
442 | if (r < 0) |
443 | return r; | |
444 | } | |
445 | } else { | |
446 | bool found = false; | |
5eef597e | 447 | |
4c89c718 MP |
448 | first = hashmap_get(z->by_key, key); |
449 | LIST_FOREACH(by_key, j, first) { | |
450 | if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) | |
451 | continue; | |
5eef597e | 452 | |
4c89c718 | 453 | found = true; |
5eef597e | 454 | |
4c89c718 MP |
455 | if (j->state != DNS_ZONE_ITEM_PROBING) |
456 | tentative = false; | |
5eef597e | 457 | |
52ad194e | 458 | r = dns_zone_add_authenticated_answer(answer, j, ifindex); |
4c89c718 MP |
459 | if (r < 0) |
460 | return r; | |
461 | } | |
5eef597e | 462 | |
4c89c718 MP |
463 | if (!found) { |
464 | bool add_soa = false; | |
465 | ||
aa27b158 | 466 | first = hashmap_get(z->by_name, dns_resource_key_name(key)); |
4c89c718 MP |
467 | LIST_FOREACH(by_name, j, first) { |
468 | if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) | |
469 | continue; | |
5eef597e | 470 | |
4c89c718 MP |
471 | if (j->state != DNS_ZONE_ITEM_PROBING) |
472 | tentative = false; | |
5eef597e | 473 | |
4c89c718 MP |
474 | add_soa = true; |
475 | } | |
5eef597e | 476 | |
4c89c718 | 477 | if (add_soa) { |
5a920b42 | 478 | r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex); |
4c89c718 MP |
479 | if (r < 0) |
480 | return r; | |
5eef597e MP |
481 | } |
482 | } | |
483 | } | |
484 | ||
4c89c718 MP |
485 | /* If the caller sets ret_tentative to NULL, then use this as |
486 | * indication to not return tentative entries */ | |
487 | ||
488 | if (!ret_tentative && tentative) | |
489 | goto return_empty; | |
490 | ||
5eef597e MP |
491 | *ret_answer = answer; |
492 | answer = NULL; | |
493 | ||
4c89c718 MP |
494 | if (ret_soa) { |
495 | *ret_soa = soa; | |
496 | soa = NULL; | |
497 | } | |
5eef597e MP |
498 | |
499 | if (ret_tentative) | |
500 | *ret_tentative = tentative; | |
501 | ||
502 | return 1; | |
4c89c718 MP |
503 | |
504 | return_empty: | |
505 | *ret_answer = NULL; | |
506 | ||
507 | if (ret_soa) | |
508 | *ret_soa = NULL; | |
509 | ||
510 | if (ret_tentative) | |
511 | *ret_tentative = false; | |
512 | ||
513 | return 0; | |
5eef597e MP |
514 | } |
515 | ||
516 | void dns_zone_item_conflict(DnsZoneItem *i) { | |
5eef597e MP |
517 | assert(i); |
518 | ||
519 | if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED)) | |
520 | return; | |
521 | ||
4c89c718 | 522 | log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr))); |
5eef597e MP |
523 | |
524 | dns_zone_item_probe_stop(i); | |
525 | ||
526 | /* Withdraw the conflict item */ | |
527 | i->state = DNS_ZONE_ITEM_WITHDRAWN; | |
528 | ||
52ad194e MB |
529 | dnssd_signal_conflict(i->scope->manager, dns_resource_key_name(i->rr->key)); |
530 | ||
5eef597e | 531 | /* Maybe change the hostname */ |
aa27b158 | 532 | if (manager_is_own_hostname(i->scope->manager, dns_resource_key_name(i->rr->key)) > 0) |
5eef597e MP |
533 | manager_next_hostname(i->scope->manager); |
534 | } | |
535 | ||
4c89c718 | 536 | void dns_zone_item_notify(DnsZoneItem *i) { |
5eef597e MP |
537 | assert(i); |
538 | assert(i->probe_transaction); | |
539 | ||
540 | if (i->block_ready > 0) | |
541 | return; | |
542 | ||
4c89c718 | 543 | if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)) |
5eef597e MP |
544 | return; |
545 | ||
546 | if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) { | |
547 | bool we_lost = false; | |
548 | ||
549 | /* The probe got a successful reply. If we so far | |
52ad194e MB |
550 | * weren't established we just give up. |
551 | * | |
552 | * In LLMNR case if we already | |
5eef597e MP |
553 | * were established, and the peer has the |
554 | * lexicographically larger IP address we continue | |
555 | * and defend it. */ | |
556 | ||
557 | if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) { | |
558 | log_debug("Got a successful probe for not yet established RR, we lost."); | |
559 | we_lost = true; | |
52ad194e | 560 | } else if (i->probe_transaction->scope->protocol == DNS_PROTOCOL_LLMNR) { |
5eef597e MP |
561 | assert(i->probe_transaction->received); |
562 | we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0; | |
563 | if (we_lost) | |
564 | log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost."); | |
565 | } | |
566 | ||
567 | if (we_lost) { | |
568 | dns_zone_item_conflict(i); | |
569 | return; | |
570 | } | |
571 | ||
572 | log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost."); | |
573 | } | |
574 | ||
4c89c718 | 575 | log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr))); |
5eef597e MP |
576 | |
577 | dns_zone_item_probe_stop(i); | |
578 | i->state = DNS_ZONE_ITEM_ESTABLISHED; | |
579 | } | |
580 | ||
581 | static int dns_zone_item_verify(DnsZoneItem *i) { | |
5eef597e MP |
582 | int r; |
583 | ||
584 | assert(i); | |
585 | ||
586 | if (i->state != DNS_ZONE_ITEM_ESTABLISHED) | |
587 | return 0; | |
588 | ||
4c89c718 | 589 | log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr))); |
5eef597e MP |
590 | |
591 | i->state = DNS_ZONE_ITEM_VERIFYING; | |
592 | r = dns_zone_item_probe_start(i); | |
593 | if (r < 0) { | |
f47781d8 | 594 | log_error_errno(r, "Failed to start probing for verifying RR: %m"); |
5eef597e MP |
595 | i->state = DNS_ZONE_ITEM_ESTABLISHED; |
596 | return r; | |
597 | } | |
598 | ||
599 | return 0; | |
600 | } | |
601 | ||
602 | int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) { | |
603 | DnsZoneItem *i, *first; | |
604 | int c = 0; | |
605 | ||
606 | assert(zone); | |
607 | assert(rr); | |
608 | ||
609 | /* This checks whether a response RR we received from somebody | |
610 | * else is one that we actually thought was uniquely ours. If | |
611 | * so, we'll verify our RRs. */ | |
612 | ||
613 | /* No conflict if we don't have the name at all. */ | |
aa27b158 | 614 | first = hashmap_get(zone->by_name, dns_resource_key_name(rr->key)); |
5eef597e MP |
615 | if (!first) |
616 | return 0; | |
617 | ||
618 | /* No conflict if we have the exact same RR */ | |
619 | if (dns_zone_get(zone, rr)) | |
620 | return 0; | |
621 | ||
52ad194e MB |
622 | /* No conflict if it is DNS-SD RR used for service enumeration. */ |
623 | if (dns_resource_key_is_dnssd_ptr(rr->key)) | |
624 | return 0; | |
625 | ||
5eef597e MP |
626 | /* OK, somebody else has RRs for the same name. Yuck! Let's |
627 | * start probing again */ | |
628 | ||
629 | LIST_FOREACH(by_name, i, first) { | |
630 | if (dns_resource_record_equal(i->rr, rr)) | |
631 | continue; | |
632 | ||
633 | dns_zone_item_verify(i); | |
634 | c++; | |
635 | } | |
636 | ||
637 | return c; | |
638 | } | |
639 | ||
640 | int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) { | |
641 | DnsZoneItem *i, *first; | |
642 | int c = 0; | |
643 | ||
644 | assert(zone); | |
645 | ||
646 | /* Somebody else notified us about a possible conflict. Let's | |
647 | * verify if that's true. */ | |
648 | ||
aa27b158 | 649 | first = hashmap_get(zone->by_name, dns_resource_key_name(key)); |
5eef597e MP |
650 | if (!first) |
651 | return 0; | |
652 | ||
653 | LIST_FOREACH(by_name, i, first) { | |
654 | dns_zone_item_verify(i); | |
655 | c++; | |
656 | } | |
657 | ||
658 | return c; | |
659 | } | |
660 | ||
661 | void dns_zone_verify_all(DnsZone *zone) { | |
662 | DnsZoneItem *i; | |
663 | Iterator iterator; | |
664 | ||
665 | assert(zone); | |
666 | ||
667 | HASHMAP_FOREACH(i, zone->by_key, iterator) { | |
668 | DnsZoneItem *j; | |
669 | ||
670 | LIST_FOREACH(by_key, j, i) | |
671 | dns_zone_item_verify(j); | |
672 | } | |
673 | } | |
13d276d0 MP |
674 | |
675 | void dns_zone_dump(DnsZone *zone, FILE *f) { | |
676 | Iterator iterator; | |
677 | DnsZoneItem *i; | |
13d276d0 MP |
678 | |
679 | if (!zone) | |
680 | return; | |
681 | ||
682 | if (!f) | |
683 | f = stdout; | |
684 | ||
685 | HASHMAP_FOREACH(i, zone->by_key, iterator) { | |
686 | DnsZoneItem *j; | |
687 | ||
688 | LIST_FOREACH(by_key, j, i) { | |
4c89c718 | 689 | const char *t; |
13d276d0 | 690 | |
4c89c718 MP |
691 | t = dns_resource_record_to_string(j->rr); |
692 | if (!t) { | |
13d276d0 MP |
693 | log_oom(); |
694 | continue; | |
695 | } | |
696 | ||
697 | fputc('\t', f); | |
698 | fputs(t, f); | |
699 | fputc('\n', f); | |
700 | } | |
701 | } | |
702 | } | |
703 | ||
704 | bool dns_zone_is_empty(DnsZone *zone) { | |
705 | if (!zone) | |
706 | return true; | |
707 | ||
708 | return hashmap_isempty(zone->by_key); | |
709 | } | |
52ad194e MB |
710 | |
711 | bool dns_zone_contains_name(DnsZone *z, const char *name) { | |
712 | DnsZoneItem *i, *first; | |
713 | ||
714 | first = hashmap_get(z->by_name, name); | |
715 | if (!first) | |
716 | return false; | |
717 | ||
718 | LIST_FOREACH(by_name, i, first) { | |
719 | if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) | |
720 | continue; | |
721 | ||
722 | return true; | |
723 | } | |
724 | ||
725 | return false; | |
726 | } |