]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-zone.c
resolved: stop the prober when we detect a conflict in LLMNR
[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 "resolved-dns-domain.h"
26 #include "resolved-dns-packet.h"
27
28 /* Never allow more than 1K entries */
29 #define ZONE_MAX 1024
30
31 static 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))
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
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, false);
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) {
229 _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
230 DnsZoneItem *existing;
231 int r;
232
233 assert(z);
234 assert(s);
235 assert(rr);
236
237 if (rr->key->class == DNS_CLASS_ANY)
238 return -EINVAL;
239 if (rr->key->type == DNS_TYPE_ANY)
240 return -EINVAL;
241
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
254 i->scope = s;
255 i->rr = dns_resource_record_ref(rr);
256 i->probing_enabled = probe;
257
258 r = dns_zone_link_item(z, i);
259 if (r < 0)
260 return r;
261
262 if (probe) {
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;
277 }
278
279 if (established)
280 i->state = DNS_ZONE_ITEM_ESTABLISHED;
281 else {
282 i->state = DNS_ZONE_ITEM_PROBING;
283
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 }
290 }
291 } else
292 i->state = DNS_ZONE_ITEM_ESTABLISHED;
293
294 i = NULL;
295 return 0;
296 }
297
298 int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
299 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
300 unsigned i, n_answer = 0, n_soa = 0;
301 bool tentative = true;
302 int r;
303
304 assert(z);
305 assert(q);
306 assert(ret_answer);
307 assert(ret_soa);
308
309 if (q->n_keys <= 0) {
310 *ret_answer = NULL;
311 *ret_soa = NULL;
312
313 if (ret_tentative)
314 *ret_tentative = false;
315
316 return 0;
317 }
318
319 /* First iteration, count what we have */
320 for (i = 0; i < q->n_keys; i++) {
321 DnsZoneItem *j, *first;
322
323 if (q->keys[i]->type == DNS_TYPE_ANY ||
324 q->keys[i]->class == DNS_CLASS_ANY) {
325 bool found = false, added = false;
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
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
339 k = dns_resource_key_match_rr(q->keys[i], j->rr);
340 if (k < 0)
341 return k;
342 if (k > 0) {
343 n_answer++;
344 added = true;
345 }
346
347 }
348
349 if (found && !added)
350 n_soa++;
351
352 } else {
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 }
376 }
377 }
378 }
379
380 if (n_answer <= 0 && n_soa <= 0) {
381 *ret_answer = NULL;
382 *ret_soa = NULL;
383
384 if (ret_tentative)
385 *ret_tentative = false;
386
387 return 0;
388 }
389
390 if (n_answer > 0) {
391 answer = dns_answer_new(n_answer);
392 if (!answer)
393 return -ENOMEM;
394 }
395
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 */
403 for (i = 0; i < q->n_keys; i++) {
404 DnsZoneItem *j, *first;
405
406 if (q->keys[i]->type == DNS_TYPE_ANY ||
407 q->keys[i]->class == DNS_CLASS_ANY) {
408 bool found = false, added = false;
409 int k;
410
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
421 k = dns_resource_key_match_rr(q->keys[i], j->rr);
422 if (k < 0)
423 return k;
424 if (k > 0) {
425 r = dns_answer_add(answer, j->rr);
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);
435 if (r < 0)
436 return r;
437 }
438 } else {
439 bool found = false;
440
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;
468 }
469
470 if (add_soa) {
471 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL);
472 if (r < 0)
473 return r;
474 }
475 }
476 }
477 }
478
479 *ret_answer = answer;
480 answer = NULL;
481
482 *ret_soa = soa;
483 soa = NULL;
484
485 if (ret_tentative)
486 *ret_tentative = tentative;
487
488 return 1;
489 }
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 dns_zone_item_probe_stop(i);
500
501 /* Withdraw the conflict item */
502 i->state = DNS_ZONE_ITEM_WITHDRAWN;
503
504 /* Maybe change the hostname */
505 if (dns_name_equal(i->scope->manager->hostname, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0)
506 manager_next_hostname(i->scope->manager);
507 }
508
509 void dns_zone_item_ready(DnsZoneItem *i) {
510 assert(i);
511 assert(i->probe_transaction);
512
513 if (i->block_ready > 0)
514 return;
515
516 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING))
517 return;
518
519 if (i->probe_transaction->state != DNS_TRANSACTION_SUCCESS) {
520 _cleanup_free_ char *pretty = NULL;
521
522 dns_resource_record_to_string(i->rr, &pretty);
523 log_debug("Record %s successfully probed.", strna(pretty));
524
525 dns_zone_item_probe_stop(i);
526 i->state = DNS_ZONE_ITEM_ESTABLISHED;
527 } else
528 dns_zone_item_conflict(i);
529 }