]>
Commit | Line | Data |
---|---|---|
f579559b TG |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2013 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 | ||
3bef724f TG |
22 | #include <resolv.h> |
23 | ||
f579559b TG |
24 | #include "path-util.h" |
25 | #include "networkd.h" | |
26 | #include "libudev-private.h" | |
7b77ed8c | 27 | #include "udev-util.h" |
50add290 | 28 | #include "rtnl-util.h" |
3bef724f | 29 | #include "mkdir.h" |
60ad0c85 | 30 | #include "virt.h" |
f579559b | 31 | |
2ad8416d ZJS |
32 | const char* const network_dirs[] = { |
33 | "/etc/systemd/network", | |
34 | "/run/systemd/network", | |
35 | "/usr/lib/systemd/network", | |
eed0eee8 | 36 | #ifdef HAVE_SPLIT_USR |
2ad8416d ZJS |
37 | "/lib/systemd/network", |
38 | #endif | |
39 | NULL}; | |
40 | ||
0c2f9b84 TG |
41 | static int dispatch_sigterm(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) { |
42 | Manager *m = userdata; | |
43 | ||
44 | assert(m); | |
45 | ||
46 | log_received_signal(LOG_INFO, si); | |
47 | ||
48 | sd_event_exit(m->event, 0); | |
49 | return 0; | |
50 | } | |
51 | ||
52 | static int setup_signals(Manager *m) { | |
53 | sigset_t mask; | |
54 | int r; | |
55 | ||
56 | assert(m); | |
57 | ||
58 | assert_se(sigemptyset(&mask) == 0); | |
59 | sigset_add_many(&mask, SIGINT, SIGTERM, -1); | |
60 | assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); | |
61 | ||
62 | r = sd_event_add_signal(m->event, &m->sigterm_event_source, SIGTERM, dispatch_sigterm, m); | |
63 | if (r < 0) | |
64 | return r; | |
65 | ||
66 | r = sd_event_add_signal(m->event, &m->sigint_event_source, SIGINT, dispatch_sigterm, m); | |
67 | if (r < 0) | |
68 | return r; | |
69 | ||
70 | return 0; | |
71 | } | |
72 | ||
f579559b TG |
73 | int manager_new(Manager **ret) { |
74 | _cleanup_manager_free_ Manager *m = NULL; | |
75 | int r; | |
76 | ||
77 | m = new0(Manager, 1); | |
78 | if (!m) | |
79 | return -ENOMEM; | |
80 | ||
afc6adb5 | 81 | r = sd_event_default(&m->event); |
f579559b TG |
82 | if (r < 0) |
83 | return r; | |
84 | ||
cde93897 LP |
85 | sd_event_set_watchdog(m->event, true); |
86 | ||
151b9b96 | 87 | r = sd_rtnl_open(&m->rtnl, RTMGRP_LINK | RTMGRP_IPV4_IFADDR); |
f579559b TG |
88 | if (r < 0) |
89 | return r; | |
90 | ||
1346b1f0 | 91 | r = sd_bus_default_system(&m->bus); |
bcbca829 | 92 | if (r < 0 && r != -ENOENT) /* TODO: drop when we can rely on kdbus */ |
1346b1f0 TG |
93 | return r; |
94 | ||
0c2f9b84 TG |
95 | r = setup_signals(m); |
96 | if (r < 0) | |
97 | return r; | |
98 | ||
f579559b TG |
99 | m->udev = udev_new(); |
100 | if (!m->udev) | |
101 | return -ENOMEM; | |
102 | ||
60ad0c85 TG |
103 | /* udev does not initialize devices inside containers, |
104 | * so we rely on them being already initialized before | |
105 | * entering the container */ | |
106 | if (detect_container(NULL) > 0) { | |
107 | m->udev_monitor = udev_monitor_new_from_netlink(m->udev, "kernel"); | |
108 | if (!m->udev_monitor) | |
109 | return -ENOMEM; | |
110 | } else { | |
111 | m->udev_monitor = udev_monitor_new_from_netlink(m->udev, "udev"); | |
112 | if (!m->udev_monitor) | |
113 | return -ENOMEM; | |
114 | } | |
f579559b TG |
115 | |
116 | m->links = hashmap_new(uint64_hash_func, uint64_compare_func); | |
117 | if (!m->links) | |
118 | return -ENOMEM; | |
119 | ||
52433f6b TG |
120 | m->netdevs = hashmap_new(string_hash_func, string_compare_func); |
121 | if (!m->netdevs) | |
02b59d57 TG |
122 | return -ENOMEM; |
123 | ||
f579559b TG |
124 | LIST_HEAD_INIT(m->networks); |
125 | ||
f579559b TG |
126 | *ret = m; |
127 | m = NULL; | |
128 | ||
129 | return 0; | |
130 | } | |
131 | ||
132 | void manager_free(Manager *m) { | |
0617ffab | 133 | Network *network; |
1a436809 | 134 | NetDev *netdev; |
0617ffab TG |
135 | Link *link; |
136 | ||
624b5a63 TG |
137 | if (!m) |
138 | return; | |
139 | ||
f579559b TG |
140 | udev_monitor_unref(m->udev_monitor); |
141 | udev_unref(m->udev); | |
1346b1f0 | 142 | sd_bus_unref(m->bus); |
f579559b | 143 | sd_event_source_unref(m->udev_event_source); |
0c2f9b84 TG |
144 | sd_event_source_unref(m->sigterm_event_source); |
145 | sd_event_source_unref(m->sigint_event_source); | |
f579559b | 146 | sd_event_unref(m->event); |
0617ffab TG |
147 | |
148 | while ((network = m->networks)) | |
149 | network_free(network); | |
150 | ||
151 | while ((link = hashmap_first(m->links))) | |
152 | link_free(link); | |
f579559b | 153 | hashmap_free(m->links); |
0617ffab | 154 | |
52433f6b TG |
155 | while ((netdev = hashmap_first(m->netdevs))) |
156 | netdev_free(netdev); | |
157 | hashmap_free(m->netdevs); | |
02b59d57 | 158 | |
f579559b TG |
159 | sd_rtnl_unref(m->rtnl); |
160 | ||
161 | free(m); | |
162 | } | |
163 | ||
02b59d57 TG |
164 | int manager_load_config(Manager *m) { |
165 | int r; | |
166 | ||
167 | /* update timestamp */ | |
2ad8416d | 168 | paths_check_timestamp(network_dirs, &m->network_dirs_ts_usec, true); |
02b59d57 | 169 | |
52433f6b | 170 | r = netdev_load(m); |
02b59d57 TG |
171 | if (r < 0) |
172 | return r; | |
173 | ||
174 | r = network_load(m); | |
175 | if (r < 0) | |
176 | return r; | |
177 | ||
178 | return 0; | |
179 | } | |
180 | ||
181 | bool manager_should_reload(Manager *m) { | |
2ad8416d | 182 | return paths_check_timestamp(network_dirs, &m->network_dirs_ts_usec, false); |
02b59d57 TG |
183 | } |
184 | ||
f579559b | 185 | static int manager_process_link(Manager *m, struct udev_device *device) { |
11a7f229 | 186 | Link *link = NULL; |
f579559b TG |
187 | int r; |
188 | ||
11a7f229 TG |
189 | assert(m); |
190 | assert(device); | |
f579559b | 191 | |
11a7f229 TG |
192 | link_get(m, udev_device_get_ifindex(device), &link); |
193 | ||
194 | if (streq_ptr(udev_device_get_action(device), "remove")) { | |
449f7554 | 195 | log_debug("%s: link removed", udev_device_get_sysname(device)); |
002f5de9 | 196 | |
11a7f229 TG |
197 | if (link) |
198 | link_free(link); | |
199 | } else { | |
200 | if (link) { | |
201 | log_debug("%s: link already exists, ignoring", | |
202 | link->ifname); | |
f579559b | 203 | return 0; |
11a7f229 | 204 | } |
f579559b | 205 | |
aa3437a5 | 206 | r = link_add(m, device, &link); |
f579559b | 207 | if (r < 0) { |
11a7f229 TG |
208 | log_error("%s: could not handle link: %s", |
209 | udev_device_get_sysname(device), | |
210 | strerror(-r)); | |
2672953b | 211 | } else |
aa3437a5 TG |
212 | log_debug("%s: link (with ifindex %" PRIu64") added", |
213 | link->ifname, link->ifindex); | |
f579559b TG |
214 | } |
215 | ||
216 | return 0; | |
217 | } | |
218 | ||
219 | int manager_udev_enumerate_links(Manager *m) { | |
bf5332d2 | 220 | _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; |
f579559b | 221 | struct udev_list_entry *item = NULL, *first = NULL; |
f579559b TG |
222 | int r; |
223 | ||
224 | assert(m); | |
225 | ||
226 | e = udev_enumerate_new(m->udev); | |
bf5332d2 LP |
227 | if (!e) |
228 | return -ENOMEM; | |
f579559b TG |
229 | |
230 | r = udev_enumerate_add_match_subsystem(e, "net"); | |
231 | if (r < 0) | |
bf5332d2 | 232 | return r; |
f579559b | 233 | |
60ad0c85 TG |
234 | /* udev does not initialize devices inside containers, |
235 | * so we rely on them being already initialized before | |
236 | * entering the container */ | |
237 | if (detect_container(NULL) <= 0) { | |
238 | r = udev_enumerate_add_match_is_initialized(e); | |
239 | if (r < 0) | |
240 | return r; | |
241 | } | |
e1202047 | 242 | |
f579559b TG |
243 | r = udev_enumerate_scan_devices(e); |
244 | if (r < 0) | |
bf5332d2 | 245 | return r; |
f579559b TG |
246 | |
247 | first = udev_enumerate_get_list_entry(e); | |
248 | udev_list_entry_foreach(item, first) { | |
bf5332d2 | 249 | _cleanup_udev_device_unref_ struct udev_device *d = NULL; |
f579559b TG |
250 | int k; |
251 | ||
252 | d = udev_device_new_from_syspath(m->udev, udev_list_entry_get_name(item)); | |
bf5332d2 LP |
253 | if (!d) |
254 | return -ENOMEM; | |
f579559b | 255 | |
bf5332d2 | 256 | k = manager_process_link(m, d); |
f579559b TG |
257 | if (k < 0) |
258 | r = k; | |
259 | } | |
260 | ||
f579559b TG |
261 | return r; |
262 | } | |
263 | ||
264 | static int manager_dispatch_link_udev(sd_event_source *source, int fd, uint32_t revents, void *userdata) { | |
265 | Manager *m = userdata; | |
266 | struct udev_monitor *monitor = m->udev_monitor; | |
7b77ed8c | 267 | _cleanup_udev_device_unref_ struct udev_device *device = NULL; |
f579559b TG |
268 | |
269 | device = udev_monitor_receive_device(monitor); | |
270 | if (!device) | |
271 | return -ENOMEM; | |
272 | ||
7b77ed8c | 273 | manager_process_link(m, device); |
f579559b TG |
274 | return 0; |
275 | } | |
276 | ||
277 | int manager_udev_listen(Manager *m) { | |
278 | int r; | |
279 | ||
280 | r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_monitor, "net", NULL); | |
281 | if (r < 0) { | |
282 | log_error("Could not add udev monitor filter: %s", strerror(-r)); | |
283 | return r; | |
284 | } | |
285 | ||
f579559b TG |
286 | r = udev_monitor_enable_receiving(m->udev_monitor); |
287 | if (r < 0) { | |
288 | log_error("Could not enable udev monitor"); | |
289 | return r; | |
290 | } | |
291 | ||
292 | r = sd_event_add_io(m->event, | |
151b9b96 | 293 | &m->udev_event_source, |
f579559b TG |
294 | udev_monitor_get_fd(m->udev_monitor), |
295 | EPOLLIN, manager_dispatch_link_udev, | |
151b9b96 | 296 | m); |
f579559b TG |
297 | if (r < 0) |
298 | return r; | |
299 | ||
300 | return 0; | |
301 | } | |
f882c247 | 302 | |
dd3efc09 TG |
303 | static int manager_rtnl_process_link(sd_rtnl *rtnl, sd_rtnl_message *message, void *userdata) { |
304 | Manager *m = userdata; | |
305 | Link *link; | |
d22f1006 | 306 | char *name; |
dd3efc09 TG |
307 | int r, ifindex; |
308 | ||
11a7f229 TG |
309 | assert(rtnl); |
310 | assert(message); | |
311 | assert(m); | |
312 | ||
dd3efc09 | 313 | r = sd_rtnl_message_link_get_ifindex(message, &ifindex); |
50add290 | 314 | if (r < 0 || ifindex <= 0) { |
d0d311d6 | 315 | log_debug("received RTM_NEWLINK message without valid ifindex"); |
dd3efc09 | 316 | return 0; |
d0d311d6 | 317 | } |
dd3efc09 | 318 | |
d22f1006 | 319 | r = sd_rtnl_message_read_string(message, IFLA_IFNAME, &name); |
50add290 TG |
320 | if (r < 0) |
321 | log_debug("received RTM_NEWLINK message without valid IFLA_IFNAME"); | |
322 | else { | |
1a436809 | 323 | NetDev *netdev; |
50add290 TG |
324 | |
325 | r = netdev_get(m, name, &netdev); | |
326 | if (r >= 0) { | |
327 | r = netdev_set_ifindex(netdev, ifindex); | |
328 | if (r < 0) | |
329 | log_debug("could not set ifindex of netdev '%s' to %d: %s", | |
330 | name, ifindex, strerror(-r)); | |
331 | } | |
332 | } | |
333 | ||
11a7f229 TG |
334 | r = link_get(m, ifindex, &link); |
335 | if (r < 0) { | |
8b264404 | 336 | log_debug("received RTM_NEWLINK message for untracked ifindex %d", ifindex); |
dd3efc09 | 337 | return 0; |
d0d311d6 | 338 | } |
dd3efc09 | 339 | |
06a6e593 TG |
340 | /* only track the status of links we want to manage */ |
341 | if (link->network) { | |
342 | r = link_update(link, message); | |
343 | if (r < 0) | |
344 | return 0; | |
d0d311d6 | 345 | } else |
8b264404 | 346 | log_debug("%s: received RTM_NEWLINK message for unmanaged link", link->ifname); |
dd3efc09 TG |
347 | |
348 | return 1; | |
349 | } | |
350 | ||
f882c247 TG |
351 | int manager_rtnl_listen(Manager *m) { |
352 | int r; | |
353 | ||
354 | r = sd_rtnl_attach_event(m->rtnl, m->event, 0); | |
355 | if (r < 0) | |
356 | return r; | |
357 | ||
dd3efc09 TG |
358 | r = sd_rtnl_add_match(m->rtnl, RTM_NEWLINK, &manager_rtnl_process_link, m); |
359 | if (r < 0) | |
360 | return r; | |
361 | ||
f882c247 TG |
362 | return 0; |
363 | } | |
3bef724f | 364 | |
1346b1f0 TG |
365 | int manager_bus_listen(Manager *m) { |
366 | int r; | |
367 | ||
bcbca829 TG |
368 | assert(m->event); |
369 | ||
370 | if (!m->bus) /* TODO: drop when we can rely on kdbus */ | |
371 | return 0; | |
372 | ||
1346b1f0 TG |
373 | r = sd_bus_attach_event(m->bus, m->event, 0); |
374 | if (r < 0) | |
375 | return r; | |
376 | ||
377 | return 0; | |
378 | } | |
379 | ||
3bef724f TG |
380 | static void append_dns(FILE *f, struct in_addr *dns, unsigned char family, unsigned *count) { |
381 | char buf[INET6_ADDRSTRLEN]; | |
382 | const char *address; | |
383 | ||
384 | address = inet_ntop(family, dns, buf, INET6_ADDRSTRLEN); | |
385 | if (!address) { | |
386 | log_warning("Invalid DNS address. Ignoring."); | |
387 | return; | |
388 | } | |
389 | ||
390 | if (*count == MAXNS) | |
f3621dec TG |
391 | fputs("# Too many DNS servers configured, the following entries " |
392 | "will be ignored\n", f); | |
3bef724f TG |
393 | |
394 | fprintf(f, "nameserver %s\n", address); | |
395 | ||
396 | (*count) ++; | |
397 | } | |
398 | ||
399 | int manager_update_resolv_conf(Manager *m) { | |
400 | _cleanup_free_ char *temp_path = NULL; | |
401 | _cleanup_fclose_ FILE *f = NULL; | |
402 | Link *link; | |
403 | Iterator i; | |
404 | unsigned count = 0; | |
039ebe6a | 405 | const char *domainname = NULL; |
3bef724f TG |
406 | int r; |
407 | ||
408 | assert(m); | |
409 | ||
3bef724f TG |
410 | r = fopen_temporary("/run/systemd/network/resolv.conf", &f, &temp_path); |
411 | if (r < 0) | |
412 | return r; | |
413 | ||
414 | fchmod(fileno(f), 0644); | |
415 | ||
b2ad8a16 TG |
416 | fputs("# This file is managed by systemd-networkd(8). Do not edit.\n#\n" |
417 | "# Third party programs must not access this file directly, but\n" | |
418 | "# only through the symlink at /etc/resolv.conf. To manage\n" | |
419 | "# resolv.conf(5) in a different way, replace the symlink by a\n" | |
420 | "# static file or a different symlink.\n\n", f); | |
3bef724f TG |
421 | |
422 | HASHMAP_FOREACH(link, m->links, i) { | |
a6cc569e | 423 | if (link->dhcp_lease) { |
7ae4ef6d TG |
424 | struct in_addr *nameservers; |
425 | size_t nameservers_size; | |
3bef724f | 426 | |
039ebe6a | 427 | if (link->network->dhcp_dns) { |
a6cc569e | 428 | r = sd_dhcp_lease_get_dns(link->dhcp_lease, &nameservers, &nameservers_size); |
039ebe6a TG |
429 | if (r >= 0) { |
430 | unsigned j; | |
3bef724f | 431 | |
039ebe6a TG |
432 | for (j = 0; j < nameservers_size; j++) |
433 | append_dns(f, &nameservers[j], AF_INET, &count); | |
434 | } | |
435 | } | |
436 | ||
437 | if (link->network->dhcp_domainname && !domainname) { | |
a6cc569e | 438 | r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname); |
039ebe6a TG |
439 | if (r >= 0) |
440 | fprintf(f, "domain %s\n", domainname); | |
3bef724f TG |
441 | } |
442 | } | |
443 | } | |
444 | ||
06f021a8 TG |
445 | HASHMAP_FOREACH(link, m->links, i) { |
446 | if (link->network && link->network->dns) { | |
447 | Address *address; | |
448 | Iterator j; | |
449 | ||
450 | SET_FOREACH(address, link->network->dns, j) { | |
451 | append_dns(f, &address->in_addr.in, | |
452 | address->family, &count); | |
453 | } | |
454 | } | |
455 | } | |
3bef724f TG |
456 | |
457 | fflush(f); | |
458 | ||
459 | if (ferror(f) || rename(temp_path, "/run/systemd/network/resolv.conf") < 0) { | |
460 | r = -errno; | |
461 | unlink("/run/systemd/network/resolv.conf"); | |
462 | unlink(temp_path); | |
463 | return r; | |
464 | } | |
465 | ||
466 | return 0; | |
467 | } |