]>
Commit | Line | Data |
---|---|---|
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" | |
25 | #include "resolved-dns-domain.h" | |
26 | #include "resolved-dns-packet.h" | |
27 | ||
28 | /* Never allow more than 1K entries */ | |
29 | #define ZONE_MAX 1024 | |
30 | ||
ec2c5e43 LP |
31 | static void dns_zone_item_probe_stop(DnsZoneItem *i) { |
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 | |
45 | static 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 | ||
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)) | |
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_func, dns_resource_key_compare_func); | |
130 | if (r < 0) | |
131 | return r; | |
132 | ||
133 | r = hashmap_ensure_allocated(&z->by_name, dns_name_hash_func, dns_name_compare_func); | |
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 | ||
ec2c5e43 LP |
167 | static 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 | ||
197 | r = set_ensure_allocated(&t->zone_items, NULL, NULL); | |
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 | ||
223 | gc: | |
224 | dns_transaction_gc(t); | |
225 | return r; | |
226 | } | |
227 | ||
228 | int 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 { | |
282 | r = dns_zone_item_probe_start(i); | |
283 | if (r < 0) { | |
284 | dns_zone_item_remove_and_free(z, i); | |
285 | i = NULL; | |
286 | return r; | |
287 | } | |
288 | ||
289 | i->state = DNS_ZONE_ITEM_PROBING; | |
290 | } | |
ec2c5e43 LP |
291 | } else |
292 | i->state = DNS_ZONE_ITEM_ESTABLISHED; | |
293 | ||
623a4c97 LP |
294 | i = NULL; |
295 | return 0; | |
296 | } | |
297 | ||
ec2c5e43 | 298 | int 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 | |
491 | void dns_zone_item_conflict(DnsZoneItem *i) { | |
492 | _cleanup_free_ char *pretty = NULL; | |
493 | ||
494 | assert(i); | |
495 | ||
496 | dns_resource_record_to_string(i->rr, &pretty); | |
497 | log_info("Detected conflict on %s", strna(pretty)); | |
498 | ||
499 | /* Withdraw the conflict item */ | |
500 | i->state = DNS_ZONE_ITEM_WITHDRAWN; | |
501 | ||
502 | /* Maybe change the hostname */ | |
503 | if (dns_name_equal(i->scope->manager->hostname, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0) | |
504 | manager_next_hostname(i->scope->manager); | |
505 | } | |
506 | ||
507 | void dns_zone_item_ready(DnsZoneItem *i) { | |
508 | assert(i); | |
509 | assert(i->probe_transaction); | |
510 | ||
511 | if (i->block_ready > 0) | |
512 | return; | |
513 | ||
514 | if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)) | |
515 | return; | |
516 | ||
517 | if (i->probe_transaction->state != DNS_TRANSACTION_SUCCESS) { | |
518 | _cleanup_free_ char *pretty = NULL; | |
519 | ||
520 | dns_resource_record_to_string(i->rr, &pretty); | |
521 | log_debug("Record %s successfully probed.", strna(pretty)); | |
522 | ||
523 | dns_zone_item_probe_stop(i); | |
524 | i->state = DNS_ZONE_ITEM_ESTABLISHED; | |
525 | ||
526 | } else | |
527 | dns_zone_item_conflict(i); | |
528 | } |