]>
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" |
f2f1dbe5 | 26 | #include "string-util.h" |
74b2466e | 27 | |
9df3ba6c TG |
28 | /* After how much time to repeat classic DNS requests */ |
29 | #define DNS_TIMEOUT_MIN_USEC (500 * USEC_PER_MSEC) | |
30 | #define DNS_TIMEOUT_MAX_USEC (5 * USEC_PER_SEC) | |
31 | ||
74b2466e LP |
32 | int dns_server_new( |
33 | Manager *m, | |
34 | DnsServer **ret, | |
4e945a6f | 35 | DnsServerType type, |
74b2466e | 36 | Link *l, |
0dd25fb9 | 37 | int family, |
3c0cf502 | 38 | const union in_addr_union *in_addr) { |
74b2466e LP |
39 | |
40 | DnsServer *s, *tail; | |
41 | ||
42 | assert(m); | |
4e945a6f | 43 | assert((type == DNS_SERVER_LINK) == !!l); |
74b2466e | 44 | assert(in_addr); |
74b2466e LP |
45 | |
46 | s = new0(DnsServer, 1); | |
47 | if (!s) | |
48 | return -ENOMEM; | |
49 | ||
91b14d6f | 50 | s->n_ref = 1; |
4e945a6f | 51 | s->type = type; |
74b2466e LP |
52 | s->family = family; |
53 | s->address = *in_addr; | |
9df3ba6c | 54 | s->resend_timeout = DNS_TIMEOUT_MIN_USEC; |
74b2466e | 55 | |
4e945a6f | 56 | if (type == DNS_SERVER_LINK) { |
6073b6f2 TG |
57 | LIST_FIND_TAIL(servers, l->dns_servers, tail); |
58 | LIST_INSERT_AFTER(servers, l->dns_servers, tail, s); | |
74b2466e | 59 | s->link = l; |
4e945a6f | 60 | } else if (type == DNS_SERVER_SYSTEM) { |
74b2466e LP |
61 | LIST_FIND_TAIL(servers, m->dns_servers, tail); |
62 | LIST_INSERT_AFTER(servers, m->dns_servers, tail, s); | |
4e945a6f LP |
63 | } else if (type == DNS_SERVER_FALLBACK) { |
64 | LIST_FIND_TAIL(servers, m->fallback_dns_servers, tail); | |
65 | LIST_INSERT_AFTER(servers, m->fallback_dns_servers, tail, s); | |
66 | } else | |
67 | assert_not_reached("Unknown server type"); | |
74b2466e LP |
68 | |
69 | s->manager = m; | |
0eac4623 | 70 | s->linked = true; |
74b2466e | 71 | |
4e945a6f LP |
72 | /* A new DNS server that isn't fallback is added and the one |
73 | * we used so far was a fallback one? Then let's try to pick | |
74 | * the new one */ | |
75 | if (type != DNS_SERVER_FALLBACK && | |
3e684349 LP |
76 | m->current_dns_server && |
77 | m->current_dns_server->type == DNS_SERVER_FALLBACK) | |
78 | manager_set_dns_server(m, NULL); | |
4e945a6f | 79 | |
74b2466e LP |
80 | if (ret) |
81 | *ret = s; | |
82 | ||
83 | return 0; | |
84 | } | |
85 | ||
91b14d6f | 86 | DnsServer* dns_server_ref(DnsServer *s) { |
74b2466e LP |
87 | if (!s) |
88 | return NULL; | |
89 | ||
91b14d6f | 90 | assert(s->n_ref > 0); |
91b14d6f | 91 | s->n_ref ++; |
cab5b059 | 92 | |
91b14d6f TG |
93 | return s; |
94 | } | |
95 | ||
0eac4623 | 96 | DnsServer* dns_server_unref(DnsServer *s) { |
91b14d6f TG |
97 | if (!s) |
98 | return NULL; | |
3e684349 | 99 | |
0eac4623 LP |
100 | assert(s->n_ref > 0); |
101 | s->n_ref --; | |
91b14d6f | 102 | |
0eac4623 LP |
103 | if (s->n_ref > 0) |
104 | return NULL; | |
74b2466e | 105 | |
74b2466e | 106 | free(s); |
74b2466e LP |
107 | return NULL; |
108 | } | |
87f5a193 | 109 | |
0eac4623 LP |
110 | void dns_server_unlink(DnsServer *s) { |
111 | assert(s); | |
112 | assert(s->manager); | |
91b14d6f | 113 | |
0eac4623 LP |
114 | /* This removes the specified server from the linked list of |
115 | * servers, but any server might still stay around if it has | |
116 | * refs, for example from an ongoing transaction. */ | |
91b14d6f | 117 | |
0eac4623 LP |
118 | if (!s->linked) |
119 | return; | |
91b14d6f | 120 | |
0eac4623 LP |
121 | switch (s->type) { |
122 | ||
123 | case DNS_SERVER_LINK: | |
124 | assert(s->link); | |
125 | LIST_REMOVE(servers, s->link->dns_servers, s); | |
126 | break; | |
127 | ||
128 | case DNS_SERVER_SYSTEM: | |
129 | LIST_REMOVE(servers, s->manager->dns_servers, s); | |
130 | break; | |
131 | ||
132 | case DNS_SERVER_FALLBACK: | |
133 | LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); | |
134 | break; | |
135 | } | |
136 | ||
137 | s->linked = false; | |
138 | ||
139 | if (s->link && s->link->current_dns_server == s) | |
140 | link_set_dns_server(s->link, NULL); | |
141 | ||
142 | if (s->manager->current_dns_server == s) | |
143 | manager_set_dns_server(s->manager, NULL); | |
144 | ||
145 | dns_server_unref(s); | |
91b14d6f TG |
146 | } |
147 | ||
9df3ba6c TG |
148 | void dns_server_packet_received(DnsServer *s, usec_t rtt) { |
149 | assert(s); | |
150 | ||
84129d46 LP |
151 | if (rtt <= s->max_rtt) |
152 | return; | |
153 | ||
154 | s->max_rtt = rtt; | |
155 | s->resend_timeout = MIN(MAX(DNS_TIMEOUT_MIN_USEC, s->max_rtt * 2), DNS_TIMEOUT_MAX_USEC); | |
9df3ba6c TG |
156 | } |
157 | ||
158 | void dns_server_packet_lost(DnsServer *s, usec_t usec) { | |
159 | assert(s); | |
160 | ||
84129d46 LP |
161 | if (s->resend_timeout > usec) |
162 | return; | |
163 | ||
164 | s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC); | |
9df3ba6c TG |
165 | } |
166 | ||
b826ab58 | 167 | static void dns_server_hash_func(const void *p, struct siphash *state) { |
87f5a193 | 168 | const DnsServer *s = p; |
87f5a193 | 169 | |
b826ab58 | 170 | assert(s); |
87f5a193 | 171 | |
b826ab58 TG |
172 | siphash24_compress(&s->family, sizeof(s->family), state); |
173 | siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state); | |
87f5a193 LP |
174 | } |
175 | ||
d5099efc | 176 | static int dns_server_compare_func(const void *a, const void *b) { |
87f5a193 LP |
177 | const DnsServer *x = a, *y = b; |
178 | ||
179 | if (x->family < y->family) | |
180 | return -1; | |
181 | if (x->family > y->family) | |
182 | return 1; | |
183 | ||
184 | return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family)); | |
185 | } | |
d5099efc MS |
186 | |
187 | const struct hash_ops dns_server_hash_ops = { | |
188 | .hash = dns_server_hash_func, | |
189 | .compare = dns_server_compare_func | |
190 | }; | |
636e813d | 191 | |
0eac4623 LP |
192 | DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t) { |
193 | assert(m); | |
194 | ||
195 | switch (t) { | |
196 | ||
197 | case DNS_SERVER_SYSTEM: | |
198 | return m->dns_servers; | |
199 | ||
200 | case DNS_SERVER_FALLBACK: | |
201 | return m->fallback_dns_servers; | |
636e813d | 202 | |
0eac4623 LP |
203 | default: |
204 | return NULL; | |
205 | } | |
206 | } | |
207 | ||
208 | void manager_flush_dns_servers(Manager *m, DnsServerType type) { | |
636e813d LP |
209 | assert(m); |
210 | ||
0eac4623 LP |
211 | for (;;) { |
212 | DnsServer *first; | |
636e813d | 213 | |
0eac4623 LP |
214 | first = manager_get_first_dns_server(m, type); |
215 | if (!first) | |
216 | break; | |
636e813d | 217 | |
0eac4623 | 218 | dns_server_unlink(first); |
636e813d LP |
219 | } |
220 | } | |
221 | ||
222 | void manager_flush_marked_dns_servers(Manager *m, DnsServerType type) { | |
0eac4623 | 223 | DnsServer *first, *s, *next; |
636e813d LP |
224 | |
225 | assert(m); | |
226 | ||
0eac4623 | 227 | first = manager_get_first_dns_server(m, type); |
636e813d | 228 | |
0eac4623 | 229 | LIST_FOREACH_SAFE(servers, s, next, first) { |
636e813d LP |
230 | if (!s->marked) |
231 | continue; | |
232 | ||
0eac4623 | 233 | dns_server_unlink(s); |
636e813d LP |
234 | } |
235 | } | |
236 | ||
237 | void manager_mark_dns_servers(Manager *m, DnsServerType type) { | |
238 | DnsServer *first, *s; | |
239 | ||
240 | assert(m); | |
241 | ||
0eac4623 | 242 | first = manager_get_first_dns_server(m, type); |
636e813d LP |
243 | LIST_FOREACH(servers, s, first) |
244 | s->marked = true; | |
245 | } | |
f2f1dbe5 | 246 | |
0eac4623 LP |
247 | DnsServer* manager_find_dns_server(Manager *m, DnsServerType type, int family, const union in_addr_union *in_addr) { |
248 | DnsServer *first, *s; | |
f2f1dbe5 LP |
249 | |
250 | assert(m); | |
251 | assert(in_addr); | |
252 | ||
0eac4623 | 253 | first = manager_get_first_dns_server(m, type); |
f2f1dbe5 | 254 | |
0eac4623 | 255 | LIST_FOREACH(servers, s, first) |
f2f1dbe5 LP |
256 | if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0) |
257 | return s; | |
258 | ||
259 | return NULL; | |
260 | } | |
261 | ||
262 | DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { | |
263 | assert(m); | |
264 | ||
265 | if (m->current_dns_server == s) | |
266 | return s; | |
267 | ||
268 | if (s) { | |
269 | _cleanup_free_ char *ip = NULL; | |
270 | ||
271 | in_addr_to_string(s->family, &s->address, &ip); | |
272 | log_info("Switching to system DNS server %s.", strna(ip)); | |
273 | } | |
274 | ||
0eac4623 LP |
275 | dns_server_unref(m->current_dns_server); |
276 | m->current_dns_server = dns_server_ref(s); | |
f2f1dbe5 LP |
277 | |
278 | if (m->unicast_scope) | |
279 | dns_cache_flush(&m->unicast_scope->cache); | |
280 | ||
281 | return s; | |
282 | } | |
283 | ||
284 | DnsServer *manager_get_dns_server(Manager *m) { | |
285 | Link *l; | |
286 | assert(m); | |
287 | ||
288 | /* Try to read updates resolv.conf */ | |
289 | manager_read_resolv_conf(m); | |
290 | ||
291 | /* If no DNS server was chose so far, pick the first one */ | |
292 | if (!m->current_dns_server) | |
293 | manager_set_dns_server(m, m->dns_servers); | |
294 | ||
295 | if (!m->current_dns_server) { | |
296 | bool found = false; | |
297 | Iterator i; | |
298 | ||
299 | /* No DNS servers configured, let's see if there are | |
300 | * any on any links. If not, we use the fallback | |
301 | * servers */ | |
302 | ||
303 | HASHMAP_FOREACH(l, m->links, i) | |
304 | if (l->dns_servers) { | |
305 | found = true; | |
306 | break; | |
307 | } | |
308 | ||
309 | if (!found) | |
310 | manager_set_dns_server(m, m->fallback_dns_servers); | |
311 | } | |
312 | ||
313 | return m->current_dns_server; | |
314 | } | |
315 | ||
316 | void manager_next_dns_server(Manager *m) { | |
317 | assert(m); | |
318 | ||
319 | /* If there's currently no DNS server set, then the next | |
320 | * manager_get_dns_server() will find one */ | |
321 | if (!m->current_dns_server) | |
322 | return; | |
323 | ||
0eac4623 LP |
324 | /* Change to the next one, but make sure to follow the linked |
325 | * list only if the server is still linked. */ | |
326 | if (m->current_dns_server->linked && m->current_dns_server->servers_next) { | |
f2f1dbe5 LP |
327 | manager_set_dns_server(m, m->current_dns_server->servers_next); |
328 | return; | |
329 | } | |
330 | ||
331 | /* If there was no next one, then start from the beginning of | |
332 | * the list */ | |
333 | if (m->current_dns_server->type == DNS_SERVER_FALLBACK) | |
334 | manager_set_dns_server(m, m->fallback_dns_servers); | |
335 | else | |
336 | manager_set_dns_server(m, m->dns_servers); | |
337 | } |