resolved: add daemon to manage resolv.conf
[thirdparty/systemd.git] / src / resolve / resolved-manager.c
CommitLineData
091a364c
TG
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2014 Tom Gundersen <teg@jklm.no>
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 <arpa/inet.h>
23#include <resolv.h>
24#include <linux/if.h>
25
26#include "resolved.h"
27#include "event-util.h"
28#include "network-util.h"
29#include "sd-dhcp-lease.h"
30#include "dhcp-lease-internal.h"
31#include "network-internal.h"
32#include "conf-parser.h"
33#include "mkdir.h"
34
35static int set_fallback_dns(Manager *m, const char *string) {
36 char *word, *state;
37 size_t length;
38 int r;
39
40 assert(m);
41 assert(string);
42
43 FOREACH_WORD_QUOTED(word, length, string, state) {
44 _cleanup_free_ Address *address = NULL;
45 Address *tail;
46 _cleanup_free_ char *addrstr = NULL;
47
48 address = new0(Address, 1);
49 if (!address)
50 return -ENOMEM;
51
52 addrstr = strndup(word, length);
53 if (!addrstr)
54 return -ENOMEM;
55
56 r = net_parse_inaddr(addrstr, &address->family, &address->in_addr);
57 if (r < 0) {
58 log_debug("Ignoring invalid DNS address '%s'", addrstr);
59 continue;
60 }
61
62 LIST_FIND_TAIL(addresses, m->fallback_dns, tail);
63 LIST_INSERT_AFTER(addresses, m->fallback_dns, tail, address);
64 address = NULL;
65 }
66
67 return 0;
68}
69
70int config_parse_dnsv(
71 const char *unit,
72 const char *filename,
73 unsigned line,
74 const char *section,
75 unsigned section_line,
76 const char *lvalue,
77 int ltype,
78 const char *rvalue,
79 void *data,
80 void *userdata) {
81
82 Manager *m = userdata;
83 Address *address;
84
85 assert(filename);
86 assert(lvalue);
87 assert(rvalue);
88 assert(m);
89
90 while ((address = m->fallback_dns)) {
91 LIST_REMOVE(addresses, m->fallback_dns, address);
92 free(address);
93 }
94
95 set_fallback_dns(m, rvalue);
96
97 return 0;
98}
99
100static int manager_parse_config_file(Manager *m) {
101 static const char fn[] = "/etc/systemd/resolved.conf";
102 _cleanup_fclose_ FILE *f = NULL;
103 int r;
104
105 assert(m);
106
107 f = fopen(fn, "re");
108 if (!f) {
109 if (errno == ENOENT)
110 return 0;
111
112 log_warning("Failed to open configuration file %s: %m", fn);
113 return -errno;
114 }
115
116 r = config_parse(NULL, fn, f, "Resolve\0", config_item_perf_lookup,
117 (void*) resolved_gperf_lookup, false, false, m);
118 if (r < 0)
119 log_warning("Failed to parse configuration file: %s", strerror(-r));
120
121 return r;
122}
123
124int manager_new(Manager **ret) {
125 _cleanup_manager_free_ Manager *m = NULL;
126 int r;
127
128 m = new0(Manager, 1);
129 if (!m)
130 return -ENOMEM;
131
132 r = set_fallback_dns(m, DNS_SERVERS);
133 if (r < 0)
134 return r;
135
136 r = manager_parse_config_file(m);
137 if (r < 0)
138 return r;
139
140 r = sd_event_default(&m->event);
141 if (r < 0)
142 return r;
143
144 sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
145 sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
146
147 sd_event_set_watchdog(m->event, true);
148
149 *ret = m;
150 m = NULL;
151
152 return 0;
153}
154
155void manager_free(Manager *m) {
156 Address *address;
157
158 if (!m)
159 return;
160
161 sd_event_unref(m->event);
162
163 while ((address = m->fallback_dns)) {
164 LIST_REMOVE(addresses, m->fallback_dns, address);
165 free(address);
166 }
167
168 free(m);
169}
170
171static void append_dns(FILE *f, void *dns, unsigned char family, unsigned *count) {
172 char buf[INET6_ADDRSTRLEN];
173 const char *address;
174
175 assert(f);
176 assert(dns);
177 assert(count);
178
179 address = inet_ntop(family, dns, buf, INET6_ADDRSTRLEN);
180 if (!address) {
181 log_warning("Invalid DNS address. Ignoring.");
182 return;
183 }
184
185 if (*count == MAXNS)
186 fputs("# Too many DNS servers configured, the following entries "
187 "may be ignored\n", f);
188
189 fprintf(f, "nameserver %s\n", address);
190
191 (*count) ++;
192}
193
194int manager_update_resolv_conf(Manager *m) {
195 _cleanup_free_ char *temp_path = NULL;
196 _cleanup_fclose_ FILE *f = NULL;
197 _cleanup_free_ unsigned *indices = NULL;
198 Address *address;
199 unsigned count = 0;
200 int n, r, i;
201
202 assert(m);
203
204 r = fopen_temporary("/run/systemd/network/resolv.conf", &f, &temp_path);
205 if (r < 0)
206 return r;
207
208 fchmod(fileno(f), 0644);
209
210 fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n"
211 "# Third party programs must not access this file directly, but\n"
212 "# only through the symlink at /etc/resolv.conf. To manage\n"
213 "# resolv.conf(5) in a different way, replace the symlink by a\n"
214 "# static file or a different symlink.\n\n", f);
215
216 n = sd_network_get_ifindices(&indices);
217 if (n < 0)
218 n = 0;
219
220 for (i = 0; i < n; i++) {
221 _cleanup_dhcp_lease_unref_ sd_dhcp_lease *lease = NULL;
222 struct in_addr *nameservers;
223 struct in6_addr *nameservers6;
224 size_t nameservers_size;
225
226 r = sd_network_dhcp_use_dns(indices[i]);
227 if (r > 0) {
228 r = sd_network_get_dhcp_lease(indices[i], &lease);
229 if (r >= 0) {
230 r = sd_dhcp_lease_get_dns(lease, &nameservers, &nameservers_size);
231 if (r >= 0) {
232 unsigned j;
233
234 for (j = 0; j < nameservers_size; j++)
235 append_dns(f, &nameservers[j], AF_INET, &count);
236 }
237 }
238 }
239
240 r = sd_network_get_dns(indices[i], &nameservers, &nameservers_size);
241 if (r >= 0) {
242 unsigned j;
243
244 for (j = 0; j < nameservers_size; j++)
245 append_dns(f, &nameservers[j], AF_INET, &count);
246
247 free(nameservers);
248 }
249
250 r = sd_network_get_dns6(indices[i], &nameservers6, &nameservers_size);
251 if (r >= 0) {
252 unsigned j;
253
254 for (j = 0; j < nameservers_size; j++)
255 append_dns(f, &nameservers6[j], AF_INET6, &count);
256
257 free(nameservers6);
258 }
259 }
260
261 LIST_FOREACH(addresses, address, m->fallback_dns)
262 append_dns(f, &address->in_addr, address->family, &count);
263
264 fflush(f);
265
266 if (ferror(f) || rename(temp_path, "/run/systemd/network/resolv.conf") < 0) {
267 r = -errno;
268 unlink("/run/systemd/network/resolv.conf");
269 unlink(temp_path);
270 return r;
271 }
272
273 return 0;
274}
275
276static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents,
277 void *userdata) {
278 Manager *m = userdata;
279 int r;
280
281 assert(m);
282
283 r = manager_update_resolv_conf(m);
284 if (r < 0)
285 log_warning("Could not update resolv.conf: %s", strerror(-r));
286
287 sd_network_monitor_flush(m->network_monitor);
288
289 return 0;
290}
291
292int manager_network_monitor_listen(Manager *m) {
293 _cleanup_event_source_unref_ sd_event_source *event_source = NULL;
294 _cleanup_network_monitor_unref_ sd_network_monitor *monitor = NULL;
295 int r, fd, events;
296
297 r = sd_network_monitor_new(NULL, &monitor);
298 if (r < 0)
299 return r;
300
301 fd = sd_network_monitor_get_fd(monitor);
302 if (fd < 0)
303 return fd;
304
305 events = sd_network_monitor_get_events(monitor);
306 if (events < 0)
307 return events;
308
309 r = sd_event_add_io(m->event, &event_source, fd, events,
310 &manager_network_event_handler, m);
311 if (r < 0)
312 return r;
313
314 m->network_monitor = monitor;
315 m->network_event_source = event_source;
316 monitor = NULL;
317 event_source = NULL;
318
319 return 0;
320}