]>
Commit | Line | Data |
---|---|---|
af6f0d42 TG |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright (C) 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 | ||
43b3a5ef | 22 | #include <netinet/ether.h> |
2a73e0d3 | 23 | #include <net/if.h> |
43b3a5ef | 24 | |
16b9b87a | 25 | #include "sd-id128.h" |
af6f0d42 | 26 | |
16b9b87a | 27 | #include "link-config.h" |
a5010333 TG |
28 | #include "ethtool-util.h" |
29 | ||
43b3a5ef TG |
30 | #include "libudev-private.h" |
31 | #include "sd-rtnl.h" | |
af6f0d42 TG |
32 | #include "util.h" |
33 | #include "log.h" | |
34 | #include "strv.h" | |
35 | #include "path-util.h" | |
36 | #include "conf-parser.h" | |
37 | #include "conf-files.h" | |
daeb71a3 | 38 | #include "fileio.h" |
16b9b87a | 39 | #include "hashmap.h" |
af6f0d42 TG |
40 | |
41 | struct link_config_ctx { | |
42 | LIST_HEAD(link_config, links); | |
43 | ||
a5010333 TG |
44 | int ethtool_fd; |
45 | ||
43b3a5ef TG |
46 | sd_rtnl *rtnl; |
47 | ||
af6f0d42 | 48 | char **link_dirs; |
97f2d76d | 49 | usec_t link_dirs_ts_usec; |
af6f0d42 TG |
50 | }; |
51 | ||
52 | int link_config_ctx_new(link_config_ctx **ret) { | |
53 | link_config_ctx *ctx; | |
a5010333 | 54 | int r; |
af6f0d42 TG |
55 | |
56 | if (!ret) | |
57 | return -EINVAL; | |
58 | ||
59 | ctx = new0(link_config_ctx, 1); | |
60 | if (!ctx) | |
61 | return -ENOMEM; | |
62 | ||
a5010333 TG |
63 | r = ethtool_connect(&ctx->ethtool_fd); |
64 | if (r < 0) { | |
65 | link_config_ctx_free(ctx); | |
66 | return r; | |
67 | } | |
68 | ||
43b3a5ef TG |
69 | r = sd_rtnl_open(0, &ctx->rtnl); |
70 | if (r < 0) { | |
71 | link_config_ctx_free(ctx); | |
72 | return r; | |
73 | } | |
74 | ||
af6f0d42 TG |
75 | LIST_HEAD_INIT(ctx->links); |
76 | ||
9dc670ea TG |
77 | ctx->link_dirs = strv_new("/etc/systemd/network", |
78 | "/run/systemd/network", | |
79 | "/usr/lib/systemd/network", | |
af6f0d42 TG |
80 | NULL); |
81 | if (!ctx->link_dirs) { | |
82 | log_error("failed to build link config directory array"); | |
83 | link_config_ctx_free(ctx); | |
84 | return -ENOMEM; | |
85 | } | |
86 | if (!path_strv_canonicalize_uniq(ctx->link_dirs)) { | |
87 | log_error("failed to canonicalize link config directories\n"); | |
88 | link_config_ctx_free(ctx); | |
89 | return -ENOMEM; | |
90 | } | |
91 | ||
af6f0d42 TG |
92 | *ret = ctx; |
93 | return 0; | |
94 | } | |
95 | ||
96 | static void link_configs_free(link_config_ctx *ctx) { | |
97 | link_config *link, *link_next; | |
98 | ||
99 | if (!ctx) | |
100 | return; | |
101 | ||
102 | LIST_FOREACH_SAFE(links, link, link_next, ctx->links) { | |
103 | free(link->filename); | |
104 | free(link->match_path); | |
105 | free(link->match_driver); | |
106 | free(link->match_type); | |
107 | free(link->description); | |
108 | ||
109 | free(link); | |
110 | } | |
111 | } | |
112 | ||
113 | void link_config_ctx_free(link_config_ctx *ctx) { | |
114 | if (!ctx) | |
115 | return; | |
116 | ||
43b3a5ef TG |
117 | if (ctx->ethtool_fd >= 0) |
118 | close_nointr_nofail(ctx->ethtool_fd); | |
119 | ||
120 | sd_rtnl_unref(ctx->rtnl); | |
121 | ||
af6f0d42 | 122 | strv_free(ctx->link_dirs); |
af6f0d42 TG |
123 | link_configs_free(ctx); |
124 | ||
125 | free(ctx); | |
126 | ||
127 | return; | |
128 | } | |
129 | ||
130 | static int load_link(link_config_ctx *ctx, const char *filename) { | |
131 | link_config *link; | |
132 | FILE *file; | |
133 | int r; | |
134 | ||
135 | file = fopen(filename, "re"); | |
136 | if (!file) { | |
137 | if (errno == ENOENT) | |
138 | return 0; | |
139 | else | |
140 | return errno; | |
141 | } | |
142 | ||
143 | link = new0(link_config, 1); | |
144 | if (!link) { | |
145 | r = log_oom(); | |
146 | goto failure; | |
147 | } | |
148 | ||
5fde13d7 TG |
149 | link->mac_policy = _MACPOLICY_INVALID; |
150 | link->wol = _WOL_INVALID; | |
151 | link->duplex = _DUP_INVALID; | |
152 | ||
153 | ||
af6f0d42 TG |
154 | r = config_parse(NULL, filename, file, "Match\0Link\0Ethernet\0", config_item_perf_lookup, |
155 | (void*) link_config_gperf_lookup, false, false, link); | |
156 | if (r < 0) { | |
157 | log_warning("Colud not parse config file %s: %s", filename, strerror(-r)); | |
158 | goto failure; | |
159 | } else | |
160 | log_info("Parsed configuration file %s", filename); | |
161 | ||
162 | link->filename = strdup(filename); | |
163 | ||
164 | LIST_PREPEND(links, ctx->links, link); | |
165 | ||
166 | return 0; | |
167 | ||
168 | failure: | |
169 | free(link); | |
170 | return r; | |
171 | } | |
172 | ||
173 | int link_config_load(link_config_ctx *ctx) { | |
174 | int r; | |
175 | char **files, **f; | |
176 | ||
177 | link_configs_free(ctx); | |
178 | ||
97f2d76d TG |
179 | /* update timestamp */ |
180 | paths_check_timestamp(ctx->link_dirs, &ctx->link_dirs_ts_usec, true); | |
af6f0d42 TG |
181 | |
182 | r = conf_files_list_strv(&files, ".link", NULL, (const char **)ctx->link_dirs); | |
183 | if (r < 0) { | |
184 | log_error("failed to enumerate link files: %s", strerror(-r)); | |
185 | return r; | |
186 | } | |
187 | ||
188 | STRV_FOREACH_BACKWARDS(f, files) { | |
189 | r = load_link(ctx, *f); | |
190 | if (r < 0) | |
191 | return r; | |
192 | } | |
193 | ||
194 | return 0; | |
195 | } | |
196 | ||
197 | bool link_config_should_reload(link_config_ctx *ctx) { | |
97f2d76d | 198 | return paths_check_timestamp(ctx->link_dirs, &ctx->link_dirs_ts_usec, false); |
af6f0d42 TG |
199 | } |
200 | ||
201 | static bool match_config(link_config *match, struct udev_device *device) { | |
202 | const char *property; | |
203 | ||
204 | if (match->match_mac) { | |
205 | property = udev_device_get_sysattr_value(device, "address"); | |
5fde13d7 TG |
206 | if (!property || memcmp(match->match_mac, ether_aton(property), ETH_ALEN)) { |
207 | log_debug("Device MAC address (%s) did not match MACAddress=%s", | |
208 | property, ether_ntoa(match->match_mac)); | |
af6f0d42 TG |
209 | return 0; |
210 | } | |
211 | } | |
212 | ||
213 | if (match->match_path) { | |
214 | property = udev_device_get_property_value(device, "ID_PATH"); | |
5fde13d7 TG |
215 | if (!streq_ptr(match->match_path, property)) { |
216 | log_debug("Device's persistent path (%s) did not match Path=%s", | |
217 | property, match->match_path); | |
af6f0d42 TG |
218 | return 0; |
219 | } | |
220 | } | |
221 | ||
222 | if (match->match_driver) { | |
223 | property = udev_device_get_driver(device); | |
5fde13d7 TG |
224 | if (!streq_ptr(match->match_driver, property)) { |
225 | log_debug("Device driver (%s) did not match Driver=%s", | |
226 | property, match->match_driver); | |
af6f0d42 TG |
227 | return 0; |
228 | } | |
229 | } | |
230 | ||
231 | if (match->match_type) { | |
232 | property = udev_device_get_devtype(device); | |
5fde13d7 TG |
233 | if (!streq_ptr(match->match_type, property)) { |
234 | log_debug("Device type (%s) did not match Type=%s", | |
235 | property, match->match_type); | |
af6f0d42 TG |
236 | return 0; |
237 | } | |
238 | } | |
239 | ||
240 | return 1; | |
241 | } | |
242 | ||
243 | int link_config_get(link_config_ctx *ctx, struct udev_device *device, link_config **ret) { | |
244 | link_config *link; | |
245 | ||
246 | LIST_FOREACH(links, link, ctx->links) { | |
247 | if (!match_config(link, device)) { | |
248 | log_info("Config file %s does not apply to device %s", link->filename, udev_device_get_sysname(device)); | |
249 | } else { | |
250 | log_info("Config file %s applies to device %s", link->filename, udev_device_get_sysname(device)); | |
251 | *ret = link; | |
252 | return 0; | |
253 | } | |
254 | } | |
255 | ||
256 | return -ENOENT; | |
257 | } | |
258 | ||
16b9b87a | 259 | static int rtnl_set_properties(sd_rtnl *rtnl, int ifindex, const char *name, const struct ether_addr *mac, unsigned int mtu) { |
43b3a5ef | 260 | _cleanup_sd_rtnl_message_unref_ sd_rtnl_message *message; |
16b9b87a | 261 | bool need_update = false; |
43b3a5ef TG |
262 | int r; |
263 | ||
264 | assert(rtnl); | |
265 | assert(ifindex > 0); | |
266 | ||
267 | r = sd_rtnl_message_link_new(RTM_NEWLINK, ifindex, 0, 0, &message); | |
268 | if (r < 0) | |
269 | return r; | |
270 | ||
271 | if (name) { | |
16b9b87a | 272 | r = sd_rtnl_message_append(message, IFLA_IFNAME, name); |
43b3a5ef TG |
273 | if (r < 0) |
274 | return r; | |
275 | ||
276 | need_update = true; | |
277 | } | |
278 | ||
279 | if (mac) { | |
16b9b87a | 280 | r = sd_rtnl_message_append(message, IFLA_ADDRESS, mac); |
43b3a5ef TG |
281 | if (r < 0) |
282 | return r; | |
283 | ||
284 | need_update = true; | |
285 | } | |
286 | ||
287 | if (mtu > 0) { | |
288 | r = sd_rtnl_message_append(message, IFLA_MTU, &mtu); | |
289 | if (r < 0) | |
290 | return r; | |
291 | ||
292 | need_update = true; | |
293 | } | |
294 | ||
295 | if (need_update) { | |
16b9b87a | 296 | r = sd_rtnl_send_with_reply_and_block(rtnl, message, 5 * USEC_PER_SEC, NULL); |
43b3a5ef TG |
297 | if (r < 0) |
298 | return r; | |
299 | } | |
300 | ||
301 | return 0; | |
302 | } | |
303 | ||
daeb71a3 TG |
304 | static bool enable_name_policy(void) { |
305 | _cleanup_free_ char *line; | |
306 | char *w, *state; | |
307 | int r; | |
308 | size_t l; | |
309 | ||
310 | r = read_one_line_file("/proc/cmdline", &line); | |
311 | if (r < 0) { | |
312 | log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r)); | |
313 | return true; /* something is very wrong, let's not make it worse */ | |
314 | } | |
315 | ||
316 | FOREACH_WORD_QUOTED(w, l, line, state) | |
317 | if (strneq(w, "net.ifnames=0", l)) | |
318 | return false; | |
319 | ||
320 | return true; | |
321 | } | |
322 | ||
16b9b87a TG |
323 | static bool mac_is_random(struct udev_device *device) { |
324 | const char *s; | |
325 | int type; | |
326 | ||
327 | s = udev_device_get_sysattr_value(device, "addr_assign_type"); | |
328 | if (!s) | |
329 | return -EINVAL; | |
330 | type = strtoul(s, NULL, 0); | |
331 | ||
332 | /* check for NET_ADDR_RANDOM */ | |
333 | return type == 1; | |
334 | } | |
335 | ||
336 | static bool mac_is_permanent(struct udev_device *device) { | |
337 | const char *s; | |
338 | int type; | |
339 | ||
340 | s = udev_device_get_sysattr_value(device, "addr_assign_type"); | |
341 | if (!s) | |
342 | return -EINVAL; | |
343 | type = strtoul(s, NULL, 0); | |
344 | ||
345 | /* check for NET_ADDR_PERM */ | |
346 | return type == 0; | |
347 | } | |
348 | ||
5fde13d7 | 349 | static int get_mac(struct udev_device *device, bool want_random, struct ether_addr *mac) { |
16b9b87a TG |
350 | unsigned int seed; |
351 | int r, i; | |
352 | ||
16b9b87a TG |
353 | if (want_random) |
354 | seed = random_u(); | |
355 | else { | |
356 | const char *name; | |
357 | sd_id128_t machine; | |
358 | char machineid_buf[33]; | |
359 | const char *seed_str; | |
360 | ||
361 | /* fetch some persistent data unique (on this machine) to this device */ | |
362 | name = udev_device_get_property_value(device, "ID_NET_NAME_ONBOARD"); | |
363 | if (!name) { | |
364 | name = udev_device_get_property_value(device, "ID_NET_NAME_SLOT"); | |
365 | if (!name) { | |
366 | name = udev_device_get_property_value(device, "ID_NET_NAME_PATH"); | |
367 | if (!name) | |
368 | return -1; | |
369 | } | |
370 | } | |
371 | /* fetch some persistent data unique to this machine */ | |
372 | r = sd_id128_get_machine(&machine); | |
373 | if (r < 0) | |
374 | return -1; | |
375 | ||
376 | /* combine the data */ | |
377 | seed_str = strappenda(name, sd_id128_to_string(machine, machineid_buf)); | |
378 | ||
379 | /* hash to get seed */ | |
380 | seed = string_hash_func(seed_str); | |
381 | } | |
382 | ||
383 | srandom(seed); | |
384 | ||
385 | for(i = 0; i < ETH_ALEN; i++) { | |
386 | mac->ether_addr_octet[i] = random(); | |
387 | } | |
388 | ||
389 | /* see eth_random_addr in the kernel */ | |
390 | mac->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ | |
391 | mac->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ | |
392 | ||
16b9b87a TG |
393 | return 0; |
394 | } | |
395 | ||
af6f0d42 | 396 | int link_config_apply(link_config_ctx *ctx, link_config *config, struct udev_device *device) { |
16b9b87a | 397 | const char *name; |
5fde13d7 TG |
398 | const char *new_name = NULL; |
399 | struct ether_addr generated_mac; | |
16b9b87a | 400 | struct ether_addr *mac = NULL; |
43b3a5ef | 401 | int r, ifindex; |
af6f0d42 TG |
402 | |
403 | name = udev_device_get_sysname(device); | |
404 | if (!name) | |
405 | return -EINVAL; | |
406 | ||
407 | log_info("Configuring %s", name); | |
408 | ||
409 | if (config->description) { | |
a5010333 TG |
410 | r = udev_device_set_sysattr_value(device, "ifalias", |
411 | config->description); | |
412 | if (r < 0) | |
413 | log_warning("Could not set description of %s to '%s': %s", | |
414 | name, config->description, strerror(-r)); | |
a5010333 TG |
415 | } |
416 | ||
5fde13d7 TG |
417 | r = ethtool_set_speed(ctx->ethtool_fd, name, config->speed, config->duplex); |
418 | if (r < 0) | |
419 | log_warning("Could not set speed or duplex of %s to %u Mbytes (%s): %s", | |
420 | name, config->speed, duplex_to_string(config->duplex), strerror(-r)); | |
a5010333 | 421 | |
5fde13d7 TG |
422 | r = ethtool_set_wol(ctx->ethtool_fd, name, config->wol); |
423 | if (r < 0) | |
424 | log_warning("Could not set WakeOnLan of %s to %s: %s", | |
425 | name, wol_to_string(config->wol), strerror(-r)); | |
af6f0d42 | 426 | |
43b3a5ef TG |
427 | ifindex = udev_device_get_ifindex(device); |
428 | if (ifindex <= 0) { | |
429 | log_warning("Could not find ifindex"); | |
430 | return -ENODEV; | |
431 | } | |
432 | ||
daeb71a3 | 433 | if (config->name_policy && enable_name_policy()) { |
5fde13d7 | 434 | NamePolicy *policy; |
daeb71a3 | 435 | |
5fde13d7 TG |
436 | for (policy = config->name_policy; !new_name && *policy != _NAMEPOLICY_INVALID; policy++) { |
437 | switch (*policy) { | |
438 | case NAMEPOLICY_ONBOARD: | |
439 | new_name = udev_device_get_property_value(device, "ID_NET_NAME_ONBOARD"); | |
daeb71a3 | 440 | break; |
5fde13d7 TG |
441 | case NAMEPOLICY_SLOT: |
442 | new_name = udev_device_get_property_value(device, "ID_NET_NAME_SLOT"); | |
daeb71a3 | 443 | break; |
5fde13d7 TG |
444 | case NAMEPOLICY_PATH: |
445 | new_name = udev_device_get_property_value(device, "ID_NET_NAME_PATH"); | |
daeb71a3 | 446 | break; |
5fde13d7 TG |
447 | case NAMEPOLICY_MAC: |
448 | new_name = udev_device_get_property_value(device, "ID_NET_NAME_MAC"); | |
daeb71a3 | 449 | break; |
5fde13d7 TG |
450 | default: |
451 | break; | |
452 | } | |
daeb71a3 TG |
453 | } |
454 | } | |
455 | ||
16b9b87a | 456 | if (!new_name && config->name) { |
5fde13d7 | 457 | new_name = config->name; |
16b9b87a | 458 | } |
daeb71a3 | 459 | |
5fde13d7 TG |
460 | switch (config->mac_policy) { |
461 | case MACPOLICY_PERSISTENT: | |
16b9b87a | 462 | if (!mac_is_permanent(device)) { |
5fde13d7 | 463 | r = get_mac(device, false, &generated_mac); |
16b9b87a TG |
464 | if (r < 0) |
465 | return r; | |
5fde13d7 | 466 | mac = &generated_mac; |
16b9b87a | 467 | } |
5fde13d7 TG |
468 | break; |
469 | case MACPOLICY_RANDOM: | |
16b9b87a | 470 | if (!mac_is_random(device)) { |
5fde13d7 | 471 | r = get_mac(device, true, &generated_mac); |
16b9b87a TG |
472 | if (r < 0) |
473 | return r; | |
5fde13d7 | 474 | mac = &generated_mac; |
16b9b87a | 475 | } |
5fde13d7 TG |
476 | break; |
477 | default: | |
478 | mac = config->mac; | |
16b9b87a TG |
479 | } |
480 | ||
481 | r = rtnl_set_properties(ctx->rtnl, ifindex, new_name, mac, config->mtu); | |
43b3a5ef | 482 | if (r < 0) { |
16b9b87a | 483 | log_warning("Could not set Name, MACAddress or MTU on %s: %s", name, strerror(-r)); |
5fde13d7 | 484 | return r; |
43b3a5ef TG |
485 | } |
486 | ||
af6f0d42 TG |
487 | return 0; |
488 | } |