tizen 2.4 release
[external/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 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) > 0)
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         if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
497                 return;
498
499         dns_resource_record_to_string(i->rr, &pretty);
500         log_info("Detected conflict on %s", strna(pretty));
501
502         dns_zone_item_probe_stop(i);
503
504         /* Withdraw the conflict item */
505         i->state = DNS_ZONE_ITEM_WITHDRAWN;
506
507         /* Maybe change the hostname */
508         if (dns_name_equal(i->scope->manager->hostname, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0)
509                 manager_next_hostname(i->scope->manager);
510 }
511
512 void dns_zone_item_ready(DnsZoneItem *i) {
513         _cleanup_free_ char *pretty = NULL;
514
515         assert(i);
516         assert(i->probe_transaction);
517
518         if (i->block_ready > 0)
519                 return;
520
521         if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING))
522                 return;
523
524         if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
525                 bool we_lost = false;
526
527                 /* The probe got a successful reply. If we so far
528                  * weren't established we just give up. If we already
529                  * were established, and the peer has the
530                  * lexicographically larger IP address we continue
531                  * and defend it. */
532
533                 if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
534                         log_debug("Got a successful probe for not yet established RR, we lost.");
535                         we_lost = true;
536                 } else {
537                         assert(i->probe_transaction->received);
538                         we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
539                         if (we_lost)
540                                 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
541                 }
542
543                 if (we_lost) {
544                         dns_zone_item_conflict(i);
545                         return;
546                 }
547
548                 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
549         }
550
551         dns_resource_record_to_string(i->rr, &pretty);
552         log_debug("Record %s successfully probed.", strna(pretty));
553
554         dns_zone_item_probe_stop(i);
555         i->state = DNS_ZONE_ITEM_ESTABLISHED;
556 }
557
558 static int dns_zone_item_verify(DnsZoneItem *i) {
559         _cleanup_free_ char *pretty = NULL;
560         int r;
561
562         assert(i);
563
564         if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
565                 return 0;
566
567         dns_resource_record_to_string(i->rr, &pretty);
568         log_debug("Verifying RR %s", strna(pretty));
569
570         i->state = DNS_ZONE_ITEM_VERIFYING;
571         r = dns_zone_item_probe_start(i);
572         if (r < 0) {
573                 log_error("Failed to start probing for verifying RR: %s", strerror(-r));
574                 i->state = DNS_ZONE_ITEM_ESTABLISHED;
575                 return r;
576         }
577
578         return 0;
579 }
580
581 int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
582         DnsZoneItem *i, *first;
583         int c = 0;
584
585         assert(zone);
586         assert(rr);
587
588         /* This checks whether a response RR we received from somebody
589          * else is one that we actually thought was uniquely ours. If
590          * so, we'll verify our RRs. */
591
592         /* No conflict if we don't have the name at all. */
593         first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(rr->key));
594         if (!first)
595                 return 0;
596
597         /* No conflict if we have the exact same RR */
598         if (dns_zone_get(zone, rr))
599                 return 0;
600
601         /* OK, somebody else has RRs for the same name. Yuck! Let's
602          * start probing again */
603
604         LIST_FOREACH(by_name, i, first) {
605                 if (dns_resource_record_equal(i->rr, rr))
606                         continue;
607
608                 dns_zone_item_verify(i);
609                 c++;
610         }
611
612         return c;
613 }
614
615 int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
616         DnsZoneItem *i, *first;
617         int c = 0;
618
619         assert(zone);
620
621         /* Somebody else notified us about a possible conflict. Let's
622          * verify if that's true. */
623
624         first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(key));
625         if (!first)
626                 return 0;
627
628         LIST_FOREACH(by_name, i, first) {
629                 dns_zone_item_verify(i);
630                 c++;
631         }
632
633         return c;
634 }
635
636 void dns_zone_verify_all(DnsZone *zone) {
637         DnsZoneItem *i;
638         Iterator iterator;
639
640         assert(zone);
641
642         HASHMAP_FOREACH(i, zone->by_key, iterator) {
643                 DnsZoneItem *j;
644
645                 LIST_FOREACH(by_key, j, i)
646                         dns_zone_item_verify(j);
647         }
648 }