]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-zone.c
resolved: implement LLMNR uniqueness verification
[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);
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 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
274 i = NULL;
275 return 0;
276 }
277
278 int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
279 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
280 unsigned i, n_answer = 0, n_soa = 0;
281 bool tentative = true;
282 int r;
283
284 assert(z);
285 assert(q);
286 assert(ret_answer);
287 assert(ret_soa);
288
289 if (q->n_keys <= 0) {
290 *ret_answer = NULL;
291 *ret_soa = NULL;
292
293 if (ret_tentative)
294 *ret_tentative = false;
295
296 return 0;
297 }
298
299 /* First iteration, count what we have */
300 for (i = 0; i < q->n_keys; i++) {
301 DnsZoneItem *j, *first;
302
303 if (q->keys[i]->type == DNS_TYPE_ANY ||
304 q->keys[i]->class == DNS_CLASS_ANY) {
305 bool found = false, added = false;
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
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
319 k = dns_resource_key_match_rr(q->keys[i], j->rr);
320 if (k < 0)
321 return k;
322 if (k > 0) {
323 n_answer++;
324 added = true;
325 }
326
327 }
328
329 if (found && !added)
330 n_soa++;
331
332 } else {
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 }
356 }
357 }
358 }
359
360 if (n_answer <= 0 && n_soa <= 0) {
361 *ret_answer = NULL;
362 *ret_soa = NULL;
363
364 if (ret_tentative)
365 *ret_tentative = false;
366
367 return 0;
368 }
369
370 if (n_answer > 0) {
371 answer = dns_answer_new(n_answer);
372 if (!answer)
373 return -ENOMEM;
374 }
375
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 */
383 for (i = 0; i < q->n_keys; i++) {
384 DnsZoneItem *j, *first;
385
386 if (q->keys[i]->type == DNS_TYPE_ANY ||
387 q->keys[i]->class == DNS_CLASS_ANY) {
388 bool found = false, added = false;
389 int k;
390
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
401 k = dns_resource_key_match_rr(q->keys[i], j->rr);
402 if (k < 0)
403 return k;
404 if (k > 0) {
405 r = dns_answer_add(answer, j->rr);
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);
415 if (r < 0)
416 return r;
417 }
418 } else {
419 bool found = false;
420
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;
448 }
449
450 if (add_soa) {
451 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL);
452 if (r < 0)
453 return r;
454 }
455 }
456 }
457 }
458
459 *ret_answer = answer;
460 answer = NULL;
461
462 *ret_soa = soa;
463 soa = NULL;
464
465 if (ret_tentative)
466 *ret_tentative = tentative;
467
468 return 1;
469 }
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 }