]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-zone.c
resolved: when sending fails, don't try connecting to the next DNS server if we actua...
[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
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
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 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 298int 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
491void 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
507void 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}