]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-server.c
resolved: rework dns server lifecycle logic
[thirdparty/systemd.git] / src / resolve / resolved-dns-server.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 "alloc-util.h"
23 #include "resolved-dns-server.h"
24 #include "resolved-resolv-conf.h"
25 #include "siphash24.h"
26 #include "string-util.h"
27
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
32 int dns_server_new(
33 Manager *m,
34 DnsServer **ret,
35 DnsServerType type,
36 Link *l,
37 int family,
38 const union in_addr_union *in_addr) {
39
40 DnsServer *s, *tail;
41
42 assert(m);
43 assert((type == DNS_SERVER_LINK) == !!l);
44 assert(in_addr);
45
46 s = new0(DnsServer, 1);
47 if (!s)
48 return -ENOMEM;
49
50 s->n_ref = 1;
51 s->type = type;
52 s->family = family;
53 s->address = *in_addr;
54 s->resend_timeout = DNS_TIMEOUT_MIN_USEC;
55
56 if (type == DNS_SERVER_LINK) {
57 LIST_FIND_TAIL(servers, l->dns_servers, tail);
58 LIST_INSERT_AFTER(servers, l->dns_servers, tail, s);
59 s->link = l;
60 } else if (type == DNS_SERVER_SYSTEM) {
61 LIST_FIND_TAIL(servers, m->dns_servers, tail);
62 LIST_INSERT_AFTER(servers, m->dns_servers, tail, s);
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");
68
69 s->manager = m;
70 s->linked = true;
71
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 &&
76 m->current_dns_server &&
77 m->current_dns_server->type == DNS_SERVER_FALLBACK)
78 manager_set_dns_server(m, NULL);
79
80 if (ret)
81 *ret = s;
82
83 return 0;
84 }
85
86 DnsServer* dns_server_ref(DnsServer *s) {
87 if (!s)
88 return NULL;
89
90 assert(s->n_ref > 0);
91 s->n_ref ++;
92
93 return s;
94 }
95
96 DnsServer* dns_server_unref(DnsServer *s) {
97 if (!s)
98 return NULL;
99
100 assert(s->n_ref > 0);
101 s->n_ref --;
102
103 if (s->n_ref > 0)
104 return NULL;
105
106 free(s);
107 return NULL;
108 }
109
110 void dns_server_unlink(DnsServer *s) {
111 assert(s);
112 assert(s->manager);
113
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. */
117
118 if (!s->linked)
119 return;
120
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);
146 }
147
148 void dns_server_packet_received(DnsServer *s, usec_t rtt) {
149 assert(s);
150
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);
156 }
157
158 void dns_server_packet_lost(DnsServer *s, usec_t usec) {
159 assert(s);
160
161 if (s->resend_timeout > usec)
162 return;
163
164 s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC);
165 }
166
167 static void dns_server_hash_func(const void *p, struct siphash *state) {
168 const DnsServer *s = p;
169
170 assert(s);
171
172 siphash24_compress(&s->family, sizeof(s->family), state);
173 siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state);
174 }
175
176 static int dns_server_compare_func(const void *a, const void *b) {
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 }
186
187 const struct hash_ops dns_server_hash_ops = {
188 .hash = dns_server_hash_func,
189 .compare = dns_server_compare_func
190 };
191
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;
202
203 default:
204 return NULL;
205 }
206 }
207
208 void manager_flush_dns_servers(Manager *m, DnsServerType type) {
209 assert(m);
210
211 for (;;) {
212 DnsServer *first;
213
214 first = manager_get_first_dns_server(m, type);
215 if (!first)
216 break;
217
218 dns_server_unlink(first);
219 }
220 }
221
222 void manager_flush_marked_dns_servers(Manager *m, DnsServerType type) {
223 DnsServer *first, *s, *next;
224
225 assert(m);
226
227 first = manager_get_first_dns_server(m, type);
228
229 LIST_FOREACH_SAFE(servers, s, next, first) {
230 if (!s->marked)
231 continue;
232
233 dns_server_unlink(s);
234 }
235 }
236
237 void manager_mark_dns_servers(Manager *m, DnsServerType type) {
238 DnsServer *first, *s;
239
240 assert(m);
241
242 first = manager_get_first_dns_server(m, type);
243 LIST_FOREACH(servers, s, first)
244 s->marked = true;
245 }
246
247 DnsServer* manager_find_dns_server(Manager *m, DnsServerType type, int family, const union in_addr_union *in_addr) {
248 DnsServer *first, *s;
249
250 assert(m);
251 assert(in_addr);
252
253 first = manager_get_first_dns_server(m, type);
254
255 LIST_FOREACH(servers, s, first)
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
275 dns_server_unref(m->current_dns_server);
276 m->current_dns_server = dns_server_ref(s);
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
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) {
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 }