]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-zone.c
resolved: implement LLMNR uniqueness verification
[thirdparty/systemd.git] / src / resolve / resolved-dns-zone.c
CommitLineData
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
31static 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
45static 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
55DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
56
57static 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
82void 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
100static 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
113void 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
124static 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
140static 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
167static 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
223gc:
224 dns_transaction_gc(t);
225 return r;
226}
227
228int 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 278int 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
471void 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
487void 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}