]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/udev/net/link-config.c
path_check_timestamp: only keep the most recent timestamp
[thirdparty/systemd.git] / src / udev / net / link-config.c
CommitLineData
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
41struct 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
52int 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
96static 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
113void 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
130static 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
168failure:
169 free(link);
170 return r;
171}
172
173int 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
197bool 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
201static 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
243int 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 259static 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
304static 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
323static 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
336static 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 349static 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 396int 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}