]>
Commit | Line | Data |
---|---|---|
74b2466e 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 | ||
b5efdb8a | 22 | #include "alloc-util.h" |
74b2466e | 23 | #include "resolved-dns-server.h" |
f2f1dbe5 | 24 | #include "resolved-resolv-conf.h" |
b5efdb8a | 25 | #include "siphash24.h" |
be808ea0 | 26 | #include "string-table.h" |
f2f1dbe5 | 27 | #include "string-util.h" |
74b2466e | 28 | |
9df3ba6c TG |
29 | /* After how much time to repeat classic DNS requests */ |
30 | #define DNS_TIMEOUT_MIN_USEC (500 * USEC_PER_MSEC) | |
31 | #define DNS_TIMEOUT_MAX_USEC (5 * USEC_PER_SEC) | |
32 | ||
be808ea0 TG |
33 | /* The amount of time to wait before retrying with a full feature set */ |
34 | #define DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC (6 * USEC_PER_HOUR) | |
35 | #define DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC (5 * USEC_PER_MINUTE) | |
36 | ||
37 | /* The number of times we will attempt a certain feature set before degrading */ | |
38 | #define DNS_SERVER_FEATURE_RETRY_ATTEMPTS 3 | |
39 | ||
74b2466e LP |
40 | int dns_server_new( |
41 | Manager *m, | |
42 | DnsServer **ret, | |
4e945a6f | 43 | DnsServerType type, |
74b2466e | 44 | Link *l, |
0dd25fb9 | 45 | int family, |
3c0cf502 | 46 | const union in_addr_union *in_addr) { |
74b2466e | 47 | |
eed857b7 | 48 | DnsServer *s; |
74b2466e LP |
49 | |
50 | assert(m); | |
4e945a6f | 51 | assert((type == DNS_SERVER_LINK) == !!l); |
74b2466e | 52 | assert(in_addr); |
74b2466e | 53 | |
eed857b7 LP |
54 | if (!IN_SET(family, AF_INET, AF_INET6)) |
55 | return -EAFNOSUPPORT; | |
56 | ||
57 | if (l) { | |
58 | if (l->n_dns_servers >= LINK_DNS_SERVERS_MAX) | |
59 | return -E2BIG; | |
60 | } else { | |
61 | if (m->n_dns_servers >= MANAGER_DNS_SERVERS_MAX) | |
62 | return -E2BIG; | |
63 | } | |
64 | ||
74b2466e LP |
65 | s = new0(DnsServer, 1); |
66 | if (!s) | |
67 | return -ENOMEM; | |
68 | ||
91b14d6f | 69 | s->n_ref = 1; |
0b58db65 | 70 | s->manager = m; |
be808ea0 TG |
71 | s->verified_features = _DNS_SERVER_FEATURE_LEVEL_INVALID; |
72 | s->possible_features = DNS_SERVER_FEATURE_LEVEL_BEST; | |
73 | s->features_grace_period_usec = DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC; | |
4e945a6f | 74 | s->type = type; |
74b2466e LP |
75 | s->family = family; |
76 | s->address = *in_addr; | |
9df3ba6c | 77 | s->resend_timeout = DNS_TIMEOUT_MIN_USEC; |
74b2466e | 78 | |
0b58db65 LP |
79 | switch (type) { |
80 | ||
81 | case DNS_SERVER_LINK: | |
82 | s->link = l; | |
eed857b7 LP |
83 | LIST_APPEND(servers, l->dns_servers, s); |
84 | l->n_dns_servers++; | |
0b58db65 LP |
85 | break; |
86 | ||
87 | case DNS_SERVER_SYSTEM: | |
eed857b7 LP |
88 | LIST_APPEND(servers, m->dns_servers, s); |
89 | m->n_dns_servers++; | |
0b58db65 LP |
90 | break; |
91 | ||
92 | case DNS_SERVER_FALLBACK: | |
eed857b7 LP |
93 | LIST_APPEND(servers, m->fallback_dns_servers, s); |
94 | m->n_dns_servers++; | |
0b58db65 LP |
95 | break; |
96 | ||
97 | default: | |
4e945a6f | 98 | assert_not_reached("Unknown server type"); |
0b58db65 | 99 | } |
74b2466e | 100 | |
0eac4623 | 101 | s->linked = true; |
74b2466e | 102 | |
4e945a6f LP |
103 | /* A new DNS server that isn't fallback is added and the one |
104 | * we used so far was a fallback one? Then let's try to pick | |
105 | * the new one */ | |
106 | if (type != DNS_SERVER_FALLBACK && | |
3e684349 LP |
107 | m->current_dns_server && |
108 | m->current_dns_server->type == DNS_SERVER_FALLBACK) | |
109 | manager_set_dns_server(m, NULL); | |
4e945a6f | 110 | |
74b2466e LP |
111 | if (ret) |
112 | *ret = s; | |
113 | ||
114 | return 0; | |
115 | } | |
116 | ||
91b14d6f | 117 | DnsServer* dns_server_ref(DnsServer *s) { |
74b2466e LP |
118 | if (!s) |
119 | return NULL; | |
120 | ||
91b14d6f | 121 | assert(s->n_ref > 0); |
91b14d6f | 122 | s->n_ref ++; |
cab5b059 | 123 | |
91b14d6f TG |
124 | return s; |
125 | } | |
126 | ||
0eac4623 | 127 | DnsServer* dns_server_unref(DnsServer *s) { |
91b14d6f TG |
128 | if (!s) |
129 | return NULL; | |
3e684349 | 130 | |
0eac4623 LP |
131 | assert(s->n_ref > 0); |
132 | s->n_ref --; | |
91b14d6f | 133 | |
0eac4623 LP |
134 | if (s->n_ref > 0) |
135 | return NULL; | |
74b2466e | 136 | |
74b2466e | 137 | free(s); |
74b2466e LP |
138 | return NULL; |
139 | } | |
87f5a193 | 140 | |
0eac4623 LP |
141 | void dns_server_unlink(DnsServer *s) { |
142 | assert(s); | |
143 | assert(s->manager); | |
91b14d6f | 144 | |
0eac4623 LP |
145 | /* This removes the specified server from the linked list of |
146 | * servers, but any server might still stay around if it has | |
147 | * refs, for example from an ongoing transaction. */ | |
91b14d6f | 148 | |
0eac4623 LP |
149 | if (!s->linked) |
150 | return; | |
91b14d6f | 151 | |
0eac4623 LP |
152 | switch (s->type) { |
153 | ||
154 | case DNS_SERVER_LINK: | |
155 | assert(s->link); | |
eed857b7 | 156 | assert(s->link->n_dns_servers > 0); |
0eac4623 LP |
157 | LIST_REMOVE(servers, s->link->dns_servers, s); |
158 | break; | |
159 | ||
160 | case DNS_SERVER_SYSTEM: | |
eed857b7 | 161 | assert(s->manager->n_dns_servers > 0); |
0eac4623 | 162 | LIST_REMOVE(servers, s->manager->dns_servers, s); |
eed857b7 | 163 | s->manager->n_dns_servers--; |
0eac4623 LP |
164 | break; |
165 | ||
166 | case DNS_SERVER_FALLBACK: | |
eed857b7 | 167 | assert(s->manager->n_dns_servers > 0); |
0eac4623 | 168 | LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); |
eed857b7 | 169 | s->manager->n_dns_servers--; |
0eac4623 LP |
170 | break; |
171 | } | |
172 | ||
173 | s->linked = false; | |
174 | ||
175 | if (s->link && s->link->current_dns_server == s) | |
176 | link_set_dns_server(s->link, NULL); | |
177 | ||
178 | if (s->manager->current_dns_server == s) | |
179 | manager_set_dns_server(s->manager, NULL); | |
180 | ||
181 | dns_server_unref(s); | |
91b14d6f TG |
182 | } |
183 | ||
0b58db65 LP |
184 | void dns_server_move_back_and_unmark(DnsServer *s) { |
185 | DnsServer *tail; | |
186 | ||
187 | assert(s); | |
188 | ||
189 | if (!s->marked) | |
190 | return; | |
191 | ||
192 | s->marked = false; | |
193 | ||
194 | if (!s->linked || !s->servers_next) | |
195 | return; | |
196 | ||
197 | /* Move us to the end of the list, so that the order is | |
198 | * strictly kept, if we are not at the end anyway. */ | |
199 | ||
200 | switch (s->type) { | |
201 | ||
202 | case DNS_SERVER_LINK: | |
203 | assert(s->link); | |
204 | LIST_FIND_TAIL(servers, s, tail); | |
205 | LIST_REMOVE(servers, s->link->dns_servers, s); | |
206 | LIST_INSERT_AFTER(servers, s->link->dns_servers, tail, s); | |
207 | break; | |
208 | ||
209 | case DNS_SERVER_SYSTEM: | |
210 | LIST_FIND_TAIL(servers, s, tail); | |
211 | LIST_REMOVE(servers, s->manager->dns_servers, s); | |
212 | LIST_INSERT_AFTER(servers, s->manager->dns_servers, tail, s); | |
213 | break; | |
214 | ||
215 | case DNS_SERVER_FALLBACK: | |
216 | LIST_FIND_TAIL(servers, s, tail); | |
217 | LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); | |
218 | LIST_INSERT_AFTER(servers, s->manager->fallback_dns_servers, tail, s); | |
219 | break; | |
220 | ||
221 | default: | |
222 | assert_not_reached("Unknown server type"); | |
223 | } | |
224 | } | |
225 | ||
be808ea0 | 226 | void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt) { |
9df3ba6c TG |
227 | assert(s); |
228 | ||
be808ea0 TG |
229 | if (s->verified_features < features) { |
230 | s->verified_features = features; | |
231 | assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0); | |
232 | } | |
233 | ||
234 | if (s->possible_features == features) | |
235 | s->n_failed_attempts = 0; | |
84129d46 | 236 | |
be808ea0 TG |
237 | if (s->max_rtt < rtt) { |
238 | s->max_rtt = rtt; | |
239 | s->resend_timeout = MIN(MAX(DNS_TIMEOUT_MIN_USEC, s->max_rtt * 2), DNS_TIMEOUT_MAX_USEC); | |
240 | } | |
9df3ba6c TG |
241 | } |
242 | ||
be808ea0 | 243 | void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t usec) { |
9df3ba6c | 244 | assert(s); |
be808ea0 TG |
245 | assert(s->manager); |
246 | ||
247 | if (s->possible_features == features) | |
248 | s->n_failed_attempts ++; | |
9df3ba6c | 249 | |
84129d46 LP |
250 | if (s->resend_timeout > usec) |
251 | return; | |
252 | ||
253 | s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC); | |
9df3ba6c TG |
254 | } |
255 | ||
be808ea0 TG |
256 | static bool dns_server_grace_period_expired(DnsServer *s) { |
257 | usec_t ts; | |
258 | ||
259 | assert(s); | |
260 | assert(s->manager); | |
261 | ||
262 | if (s->verified_usec == 0) | |
263 | return false; | |
264 | ||
265 | assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); | |
266 | ||
267 | if (s->verified_usec + s->features_grace_period_usec > ts) | |
268 | return false; | |
269 | ||
270 | s->features_grace_period_usec = MIN(s->features_grace_period_usec * 2, DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC); | |
271 | ||
272 | return true; | |
273 | } | |
274 | ||
275 | DnsServerFeatureLevel dns_server_possible_features(DnsServer *s) { | |
276 | assert(s); | |
277 | ||
278 | if (s->possible_features != DNS_SERVER_FEATURE_LEVEL_BEST && | |
279 | dns_server_grace_period_expired(s)) { | |
280 | _cleanup_free_ char *ip = NULL; | |
281 | ||
282 | s->possible_features = DNS_SERVER_FEATURE_LEVEL_BEST; | |
283 | s->n_failed_attempts = 0; | |
284 | s->verified_usec = 0; | |
285 | ||
286 | in_addr_to_string(s->family, &s->address, &ip); | |
287 | log_info("Grace period over, resuming full feature set for DNS server %s", strna(ip)); | |
288 | } else if (s->possible_features <= s->verified_features) | |
289 | s->possible_features = s->verified_features; | |
290 | else if (s->n_failed_attempts >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && | |
291 | s->possible_features > DNS_SERVER_FEATURE_LEVEL_WORST) { | |
292 | _cleanup_free_ char *ip = NULL; | |
293 | ||
294 | s->possible_features --; | |
295 | s->n_failed_attempts = 0; | |
296 | s->verified_usec = 0; | |
297 | ||
298 | in_addr_to_string(s->family, &s->address, &ip); | |
299 | log_warning("Using degraded feature set (%s) for DNS server %s", | |
300 | dns_server_feature_level_to_string(s->possible_features), strna(ip)); | |
301 | } | |
302 | ||
303 | return s->possible_features; | |
304 | } | |
305 | ||
b826ab58 | 306 | static void dns_server_hash_func(const void *p, struct siphash *state) { |
87f5a193 | 307 | const DnsServer *s = p; |
87f5a193 | 308 | |
b826ab58 | 309 | assert(s); |
87f5a193 | 310 | |
b826ab58 TG |
311 | siphash24_compress(&s->family, sizeof(s->family), state); |
312 | siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state); | |
87f5a193 LP |
313 | } |
314 | ||
d5099efc | 315 | static int dns_server_compare_func(const void *a, const void *b) { |
87f5a193 LP |
316 | const DnsServer *x = a, *y = b; |
317 | ||
318 | if (x->family < y->family) | |
319 | return -1; | |
320 | if (x->family > y->family) | |
321 | return 1; | |
322 | ||
323 | return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family)); | |
324 | } | |
d5099efc MS |
325 | |
326 | const struct hash_ops dns_server_hash_ops = { | |
327 | .hash = dns_server_hash_func, | |
328 | .compare = dns_server_compare_func | |
329 | }; | |
636e813d | 330 | |
4b95f179 LP |
331 | void dns_server_unlink_all(DnsServer *first) { |
332 | DnsServer *next; | |
0eac4623 | 333 | |
4b95f179 LP |
334 | if (!first) |
335 | return; | |
0eac4623 | 336 | |
4b95f179 LP |
337 | next = first->servers_next; |
338 | dns_server_unlink(first); | |
636e813d | 339 | |
4b95f179 | 340 | dns_server_unlink_all(next); |
0eac4623 LP |
341 | } |
342 | ||
4b95f179 LP |
343 | void dns_server_unlink_marked(DnsServer *first) { |
344 | DnsServer *next; | |
636e813d | 345 | |
4b95f179 LP |
346 | if (!first) |
347 | return; | |
636e813d | 348 | |
4b95f179 | 349 | next = first->servers_next; |
636e813d | 350 | |
4b95f179 | 351 | if (first->marked) |
0eac4623 | 352 | dns_server_unlink(first); |
636e813d | 353 | |
4b95f179 LP |
354 | dns_server_unlink_marked(next); |
355 | } | |
636e813d | 356 | |
4b95f179 LP |
357 | void dns_server_mark_all(DnsServer *first) { |
358 | if (!first) | |
359 | return; | |
636e813d | 360 | |
4b95f179 LP |
361 | first->marked = true; |
362 | dns_server_mark_all(first->servers_next); | |
636e813d LP |
363 | } |
364 | ||
4b95f179 LP |
365 | DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr) { |
366 | DnsServer *s; | |
636e813d | 367 | |
636e813d | 368 | LIST_FOREACH(servers, s, first) |
4b95f179 LP |
369 | if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0) |
370 | return s; | |
f2f1dbe5 | 371 | |
4b95f179 LP |
372 | return NULL; |
373 | } | |
f2f1dbe5 | 374 | |
4b95f179 | 375 | DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t) { |
f2f1dbe5 | 376 | assert(m); |
f2f1dbe5 | 377 | |
4b95f179 | 378 | switch (t) { |
f2f1dbe5 | 379 | |
4b95f179 LP |
380 | case DNS_SERVER_SYSTEM: |
381 | return m->dns_servers; | |
f2f1dbe5 | 382 | |
4b95f179 LP |
383 | case DNS_SERVER_FALLBACK: |
384 | return m->fallback_dns_servers; | |
385 | ||
386 | default: | |
387 | return NULL; | |
388 | } | |
f2f1dbe5 LP |
389 | } |
390 | ||
391 | DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { | |
392 | assert(m); | |
393 | ||
394 | if (m->current_dns_server == s) | |
395 | return s; | |
396 | ||
397 | if (s) { | |
398 | _cleanup_free_ char *ip = NULL; | |
399 | ||
400 | in_addr_to_string(s->family, &s->address, &ip); | |
401 | log_info("Switching to system DNS server %s.", strna(ip)); | |
402 | } | |
403 | ||
0eac4623 LP |
404 | dns_server_unref(m->current_dns_server); |
405 | m->current_dns_server = dns_server_ref(s); | |
f2f1dbe5 LP |
406 | |
407 | if (m->unicast_scope) | |
408 | dns_cache_flush(&m->unicast_scope->cache); | |
409 | ||
410 | return s; | |
411 | } | |
412 | ||
413 | DnsServer *manager_get_dns_server(Manager *m) { | |
414 | Link *l; | |
415 | assert(m); | |
416 | ||
417 | /* Try to read updates resolv.conf */ | |
418 | manager_read_resolv_conf(m); | |
419 | ||
420 | /* If no DNS server was chose so far, pick the first one */ | |
421 | if (!m->current_dns_server) | |
422 | manager_set_dns_server(m, m->dns_servers); | |
423 | ||
424 | if (!m->current_dns_server) { | |
425 | bool found = false; | |
426 | Iterator i; | |
427 | ||
428 | /* No DNS servers configured, let's see if there are | |
429 | * any on any links. If not, we use the fallback | |
430 | * servers */ | |
431 | ||
432 | HASHMAP_FOREACH(l, m->links, i) | |
433 | if (l->dns_servers) { | |
434 | found = true; | |
435 | break; | |
436 | } | |
437 | ||
438 | if (!found) | |
439 | manager_set_dns_server(m, m->fallback_dns_servers); | |
440 | } | |
441 | ||
442 | return m->current_dns_server; | |
443 | } | |
444 | ||
445 | void manager_next_dns_server(Manager *m) { | |
446 | assert(m); | |
447 | ||
448 | /* If there's currently no DNS server set, then the next | |
449 | * manager_get_dns_server() will find one */ | |
450 | if (!m->current_dns_server) | |
451 | return; | |
452 | ||
0eac4623 LP |
453 | /* Change to the next one, but make sure to follow the linked |
454 | * list only if the server is still linked. */ | |
455 | if (m->current_dns_server->linked && m->current_dns_server->servers_next) { | |
f2f1dbe5 LP |
456 | manager_set_dns_server(m, m->current_dns_server->servers_next); |
457 | return; | |
458 | } | |
459 | ||
460 | /* If there was no next one, then start from the beginning of | |
461 | * the list */ | |
462 | if (m->current_dns_server->type == DNS_SERVER_FALLBACK) | |
463 | manager_set_dns_server(m, m->fallback_dns_servers); | |
464 | else | |
465 | manager_set_dns_server(m, m->dns_servers); | |
466 | } | |
be808ea0 TG |
467 | |
468 | static const char* const dns_server_feature_level_table[_DNS_SERVER_FEATURE_LEVEL_MAX] = { | |
469 | [DNS_SERVER_FEATURE_LEVEL_TCP] = "TCP", | |
470 | [DNS_SERVER_FEATURE_LEVEL_UDP] = "UDP", | |
471 | }; | |
472 | DEFINE_STRING_TABLE_LOOKUP(dns_server_feature_level, DnsServerFeatureLevel); |