]>
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 | ||
190 | t = dns_scope_find_transaction(i->scope, question); | |
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 LP |
262 | if (probe) { |
263 | r = dns_zone_item_probe_start(i); | |
264 | if (r < 0) { | |
265 | dns_zone_item_remove_and_free(z, i); | |
266 | i = NULL; | |
267 | return r; | |
268 | } | |
269 | ||
270 | i->state = DNS_ZONE_ITEM_PROBING; | |
271 | } else | |
272 | i->state = DNS_ZONE_ITEM_ESTABLISHED; | |
273 | ||
623a4c97 LP |
274 | i = NULL; |
275 | return 0; | |
276 | } | |
277 | ||
ec2c5e43 | 278 | int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) { |
8bf52d3d LP |
279 | _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; |
280 | unsigned i, n_answer = 0, n_soa = 0; | |
ec2c5e43 | 281 | bool tentative = true; |
d5323661 | 282 | int r; |
623a4c97 LP |
283 | |
284 | assert(z); | |
285 | assert(q); | |
8bf52d3d LP |
286 | assert(ret_answer); |
287 | assert(ret_soa); | |
623a4c97 LP |
288 | |
289 | if (q->n_keys <= 0) { | |
8bf52d3d LP |
290 | *ret_answer = NULL; |
291 | *ret_soa = NULL; | |
ec2c5e43 LP |
292 | |
293 | if (ret_tentative) | |
294 | *ret_tentative = false; | |
295 | ||
623a4c97 LP |
296 | return 0; |
297 | } | |
298 | ||
8bf52d3d | 299 | /* First iteration, count what we have */ |
623a4c97 | 300 | for (i = 0; i < q->n_keys; i++) { |
ec2c5e43 | 301 | DnsZoneItem *j, *first; |
623a4c97 | 302 | |
d5323661 LP |
303 | if (q->keys[i]->type == DNS_TYPE_ANY || |
304 | q->keys[i]->class == DNS_CLASS_ANY) { | |
ec2c5e43 | 305 | bool found = false, added = false; |
d5323661 LP |
306 | int k; |
307 | ||
308 | /* If this is a generic match, then we have to | |
309 | * go through the list by the name and look | |
310 | * for everything manually */ | |
311 | ||
ec2c5e43 LP |
312 | first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i])); |
313 | LIST_FOREACH(by_name, j, first) { | |
314 | if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) | |
315 | continue; | |
316 | ||
317 | found = true; | |
318 | ||
d5323661 LP |
319 | k = dns_resource_key_match_rr(q->keys[i], j->rr); |
320 | if (k < 0) | |
321 | return k; | |
ec2c5e43 | 322 | if (k > 0) { |
8bf52d3d | 323 | n_answer++; |
ec2c5e43 LP |
324 | added = true; |
325 | } | |
326 | ||
d5323661 LP |
327 | } |
328 | ||
ec2c5e43 LP |
329 | if (found && !added) |
330 | n_soa++; | |
331 | ||
d5323661 | 332 | } else { |
ec2c5e43 LP |
333 | bool found = false; |
334 | ||
335 | /* If this is a specific match, then look for | |
336 | * the right key immediately */ | |
337 | ||
338 | first = hashmap_get(z->by_key, q->keys[i]); | |
339 | LIST_FOREACH(by_key, j, first) { | |
340 | if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) | |
341 | continue; | |
342 | ||
343 | found = true; | |
344 | n_answer++; | |
345 | } | |
346 | ||
347 | if (!found) { | |
348 | first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i])); | |
349 | LIST_FOREACH(by_name, j, first) { | |
350 | if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) | |
351 | continue; | |
352 | ||
353 | n_soa++; | |
354 | break; | |
355 | } | |
d5323661 | 356 | } |
d5323661 | 357 | } |
623a4c97 LP |
358 | } |
359 | ||
8bf52d3d LP |
360 | if (n_answer <= 0 && n_soa <= 0) { |
361 | *ret_answer = NULL; | |
362 | *ret_soa = NULL; | |
ec2c5e43 LP |
363 | |
364 | if (ret_tentative) | |
365 | *ret_tentative = false; | |
366 | ||
8bf52d3d | 367 | return 0; |
623a4c97 LP |
368 | } |
369 | ||
8bf52d3d LP |
370 | if (n_answer > 0) { |
371 | answer = dns_answer_new(n_answer); | |
372 | if (!answer) | |
373 | return -ENOMEM; | |
374 | } | |
623a4c97 | 375 | |
8bf52d3d LP |
376 | if (n_soa > 0) { |
377 | soa = dns_answer_new(n_soa); | |
378 | if (!soa) | |
379 | return -ENOMEM; | |
380 | } | |
381 | ||
382 | /* Second iteration, actually add the RRs to the answers */ | |
623a4c97 | 383 | for (i = 0; i < q->n_keys; i++) { |
ec2c5e43 | 384 | DnsZoneItem *j, *first; |
623a4c97 | 385 | |
d5323661 LP |
386 | if (q->keys[i]->type == DNS_TYPE_ANY || |
387 | q->keys[i]->class == DNS_CLASS_ANY) { | |
ec2c5e43 | 388 | bool found = false, added = false; |
d5323661 LP |
389 | int k; |
390 | ||
ec2c5e43 LP |
391 | first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i])); |
392 | LIST_FOREACH(by_name, j, first) { | |
393 | if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) | |
394 | continue; | |
395 | ||
396 | found = true; | |
397 | ||
398 | if (j->state != DNS_ZONE_ITEM_PROBING) | |
399 | tentative = false; | |
400 | ||
d5323661 LP |
401 | k = dns_resource_key_match_rr(q->keys[i], j->rr); |
402 | if (k < 0) | |
403 | return k; | |
ec2c5e43 | 404 | if (k > 0) { |
8bf52d3d | 405 | r = dns_answer_add(answer, j->rr); |
ec2c5e43 LP |
406 | if (r < 0) |
407 | return r; | |
408 | ||
409 | added = true; | |
410 | } | |
411 | } | |
412 | ||
413 | if (found && !added) { | |
414 | r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL); | |
d5323661 LP |
415 | if (r < 0) |
416 | return r; | |
417 | } | |
418 | } else { | |
ec2c5e43 | 419 | bool found = false; |
d5323661 | 420 | |
ec2c5e43 LP |
421 | first = hashmap_get(z->by_key, q->keys[i]); |
422 | LIST_FOREACH(by_key, j, first) { | |
423 | if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) | |
424 | continue; | |
425 | ||
426 | found = true; | |
427 | ||
428 | if (j->state != DNS_ZONE_ITEM_PROBING) | |
429 | tentative = false; | |
430 | ||
431 | r = dns_answer_add(answer, j->rr); | |
432 | if (r < 0) | |
433 | return r; | |
434 | } | |
435 | ||
436 | if (!found) { | |
437 | bool add_soa = false; | |
438 | ||
439 | first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i])); | |
440 | LIST_FOREACH(by_name, j, first) { | |
441 | if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) | |
442 | continue; | |
443 | ||
444 | if (j->state != DNS_ZONE_ITEM_PROBING) | |
445 | tentative = false; | |
446 | ||
447 | add_soa = true; | |
8bf52d3d | 448 | } |
ec2c5e43 LP |
449 | |
450 | if (add_soa) { | |
57f5ad31 | 451 | r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL); |
8bf52d3d LP |
452 | if (r < 0) |
453 | return r; | |
454 | } | |
d5323661 | 455 | } |
623a4c97 LP |
456 | } |
457 | } | |
458 | ||
8bf52d3d | 459 | *ret_answer = answer; |
623a4c97 LP |
460 | answer = NULL; |
461 | ||
8bf52d3d LP |
462 | *ret_soa = soa; |
463 | soa = NULL; | |
464 | ||
ec2c5e43 LP |
465 | if (ret_tentative) |
466 | *ret_tentative = tentative; | |
467 | ||
623a4c97 LP |
468 | return 1; |
469 | } | |
ec2c5e43 LP |
470 | |
471 | void dns_zone_item_conflict(DnsZoneItem *i) { | |
472 | _cleanup_free_ char *pretty = NULL; | |
473 | ||
474 | assert(i); | |
475 | ||
476 | dns_resource_record_to_string(i->rr, &pretty); | |
477 | log_info("Detected conflict on %s", strna(pretty)); | |
478 | ||
479 | /* Withdraw the conflict item */ | |
480 | i->state = DNS_ZONE_ITEM_WITHDRAWN; | |
481 | ||
482 | /* Maybe change the hostname */ | |
483 | if (dns_name_equal(i->scope->manager->hostname, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0) | |
484 | manager_next_hostname(i->scope->manager); | |
485 | } | |
486 | ||
487 | void dns_zone_item_ready(DnsZoneItem *i) { | |
488 | assert(i); | |
489 | assert(i->probe_transaction); | |
490 | ||
491 | if (i->block_ready > 0) | |
492 | return; | |
493 | ||
494 | if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)) | |
495 | return; | |
496 | ||
497 | if (i->probe_transaction->state != DNS_TRANSACTION_SUCCESS) { | |
498 | _cleanup_free_ char *pretty = NULL; | |
499 | ||
500 | dns_resource_record_to_string(i->rr, &pretty); | |
501 | log_debug("Record %s successfully probed.", strna(pretty)); | |
502 | ||
503 | dns_zone_item_probe_stop(i); | |
504 | i->state = DNS_ZONE_ITEM_ESTABLISHED; | |
505 | ||
506 | } else | |
507 | dns_zone_item_conflict(i); | |
508 | } |