]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/net/link-config.c
udev: link-config - move naming policy from udev rules
[thirdparty/systemd.git] / src / udev / net / link-config.c
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
22 #include <netinet/ether.h>
23 #include <net/if.h>
24
25 #include "link-config.h"
26
27 #include "ethtool-util.h"
28
29 #include "libudev-private.h"
30 #include "sd-rtnl.h"
31 #include "util.h"
32 #include "log.h"
33 #include "strv.h"
34 #include "path-util.h"
35 #include "conf-parser.h"
36 #include "conf-files.h"
37 #include "fileio.h"
38
39 struct link_config_ctx {
40 LIST_HEAD(link_config, links);
41
42 int ethtool_fd;
43
44 sd_rtnl *rtnl;
45
46 char **link_dirs;
47 usec_t *link_dirs_ts_usec;
48 };
49
50 int link_config_ctx_new(link_config_ctx **ret) {
51 link_config_ctx *ctx;
52 int r;
53
54 if (!ret)
55 return -EINVAL;
56
57 ctx = new0(link_config_ctx, 1);
58 if (!ctx)
59 return -ENOMEM;
60
61 r = ethtool_connect(&ctx->ethtool_fd);
62 if (r < 0) {
63 link_config_ctx_free(ctx);
64 return r;
65 }
66
67 r = sd_rtnl_open(0, &ctx->rtnl);
68 if (r < 0) {
69 link_config_ctx_free(ctx);
70 return r;
71 }
72
73 LIST_HEAD_INIT(ctx->links);
74
75 ctx->link_dirs = strv_new("/etc/net/links",
76 "/run/net/links",
77 "/usr/lib/net/links",
78 NULL);
79 if (!ctx->link_dirs) {
80 log_error("failed to build link config directory array");
81 link_config_ctx_free(ctx);
82 return -ENOMEM;
83 }
84 if (!path_strv_canonicalize_uniq(ctx->link_dirs)) {
85 log_error("failed to canonicalize link config directories\n");
86 link_config_ctx_free(ctx);
87 return -ENOMEM;
88 }
89
90 ctx->link_dirs_ts_usec = calloc(strv_length(ctx->link_dirs), sizeof(usec_t));
91 if(!ctx->link_dirs_ts_usec) {
92 link_config_ctx_free(ctx);
93 return -ENOMEM;
94 }
95
96 *ret = ctx;
97 return 0;
98 }
99
100 static void link_configs_free(link_config_ctx *ctx) {
101 link_config *link, *link_next;
102
103 if (!ctx)
104 return;
105
106 LIST_FOREACH_SAFE(links, link, link_next, ctx->links) {
107 free(link->filename);
108 free(link->match_path);
109 free(link->match_driver);
110 free(link->match_type);
111 free(link->description);
112
113 free(link);
114 }
115 }
116
117 void link_config_ctx_free(link_config_ctx *ctx) {
118 if (!ctx)
119 return;
120
121 if (ctx->ethtool_fd >= 0)
122 close_nointr_nofail(ctx->ethtool_fd);
123
124 sd_rtnl_unref(ctx->rtnl);
125
126 strv_free(ctx->link_dirs);
127 free(ctx->link_dirs_ts_usec);
128 link_configs_free(ctx);
129
130 free(ctx);
131
132 return;
133 }
134
135 static int load_link(link_config_ctx *ctx, const char *filename) {
136 link_config *link;
137 FILE *file;
138 int r;
139
140 file = fopen(filename, "re");
141 if (!file) {
142 if (errno == ENOENT)
143 return 0;
144 else
145 return errno;
146 }
147
148 link = new0(link_config, 1);
149 if (!link) {
150 r = log_oom();
151 goto failure;
152 }
153
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
179 /* update timestamps */
180 paths_check_timestamp(ctx->link_dirs, ctx->link_dirs_ts_usec, true);
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) {
198 return paths_check_timestamp(ctx->link_dirs, ctx->link_dirs_ts_usec, false);
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");
206 if (!property || !streq(match->match_mac, property)) {
207 log_debug("Device MAC address (%s) did not match MACAddress=%s", property, match->match_mac);
208 return 0;
209 }
210 }
211
212 if (match->match_path) {
213 property = udev_device_get_property_value(device, "ID_PATH");
214 if (!property || !streq(match->match_path, property)) {
215 log_debug("Device's persistent path (%s) did not match Path=%s", property, match->match_path);
216 return 0;
217 }
218 }
219
220 if (match->match_driver) {
221 property = udev_device_get_driver(device);
222 if (!property || !streq(match->match_driver, property)) {
223 log_debug("Device driver (%s) did not match Driver=%s", property, match->match_driver);
224 return 0;
225 }
226 }
227
228 if (match->match_type) {
229 property = udev_device_get_devtype(device);
230 if (!property || !streq(match->match_type, property)) {
231 log_debug("Device type (%s) did not match Type=%s", property, match->match_type);
232 return 0;
233 }
234 }
235
236 return 1;
237 }
238
239 int link_config_get(link_config_ctx *ctx, struct udev_device *device, link_config **ret) {
240 link_config *link;
241
242 LIST_FOREACH(links, link, ctx->links) {
243 if (!match_config(link, device)) {
244 log_info("Config file %s does not apply to device %s", link->filename, udev_device_get_sysname(device));
245 } else {
246 log_info("Config file %s applies to device %s", link->filename, udev_device_get_sysname(device));
247 *ret = link;
248 return 0;
249 }
250 }
251
252 return -ENOENT;
253 }
254
255 static int rtnl_set_properties(sd_rtnl *rtnl, int ifindex, const char *name, const char *mac, unsigned int mtu) {
256 _cleanup_sd_rtnl_message_unref_ sd_rtnl_message *message;
257 char new_name[IFNAMSIZ];
258 struct ether_addr new_mac;
259 bool need_update;
260 int r;
261
262 assert(rtnl);
263 assert(ifindex > 0);
264
265 r = sd_rtnl_message_link_new(RTM_NEWLINK, ifindex, 0, 0, &message);
266 if (r < 0)
267 return r;
268
269 if (name) {
270 strscpy(new_name, IFNAMSIZ, name);
271 r = sd_rtnl_message_append(message, IFLA_IFNAME, new_name);
272 if (r < 0)
273 return r;
274
275 need_update = true;
276 }
277
278 if (mac) {
279 r = sscanf(mac, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
280 &new_mac.ether_addr_octet[0],
281 &new_mac.ether_addr_octet[1],
282 &new_mac.ether_addr_octet[2],
283 &new_mac.ether_addr_octet[3],
284 &new_mac.ether_addr_octet[4],
285 &new_mac.ether_addr_octet[5]);
286 if (r != 6)
287 return -EINVAL;
288 r = sd_rtnl_message_append(message, IFLA_ADDRESS, &new_mac);
289 if (r < 0)
290 return r;
291
292 need_update = true;
293 }
294
295 if (mtu > 0) {
296 r = sd_rtnl_message_append(message, IFLA_MTU, &mtu);
297 if (r < 0)
298 return r;
299
300 need_update = true;
301 }
302
303 if (need_update) {
304 r = sd_rtnl_send_with_reply_and_block(rtnl, message, 250 * USEC_PER_MSEC, NULL);
305 if (r < 0)
306 return r;
307 }
308
309 return 0;
310 }
311
312 static bool enable_name_policy(void) {
313 _cleanup_free_ char *line;
314 char *w, *state;
315 int r;
316 size_t l;
317
318 r = read_one_line_file("/proc/cmdline", &line);
319 if (r < 0) {
320 log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
321 return true; /* something is very wrong, let's not make it worse */
322 }
323
324 FOREACH_WORD_QUOTED(w, l, line, state)
325 if (strneq(w, "net.ifnames=0", l))
326 return false;
327
328 return true;
329 }
330
331 int link_config_apply(link_config_ctx *ctx, link_config *config, struct udev_device *device) {
332 const char *name, *new_name = NULL;
333 int r, ifindex;
334
335 name = udev_device_get_sysname(device);
336 if (!name)
337 return -EINVAL;
338
339 log_info("Configuring %s", name);
340
341 if (config->description) {
342 r = udev_device_set_sysattr_value(device, "ifalias",
343 config->description);
344 if (r < 0)
345 log_warning("Could not set description of %s to '%s': %s",
346 name, config->description, strerror(-r));
347 else
348 log_info("Set link description of %s to '%s'", name,
349 config->description);
350 }
351
352 if (config->speed || config->duplex) {
353 r = ethtool_set_speed(ctx->ethtool_fd, name,
354 config->speed, config->duplex);
355 if (r < 0)
356 log_warning("Could not set speed or duplex of %s to %u Mbytes (%s): %s",
357 name, config->speed, config->duplex, strerror(-r));
358 else
359 log_info("Set speed or duplex of %s to %u Mbytes (%s)", name,
360 config->speed, config->duplex);
361 }
362
363 if (config->wol) {
364 r = ethtool_set_wol(ctx->ethtool_fd, name, config->wol);
365 if (r < 0)
366 log_warning("Could not set WakeOnLan of %s to %s: %s",
367 name, config->wol, strerror(-r));
368 else
369 log_info("Set WakeOnLan of %s to %s", name, config->wol);
370 }
371
372 ifindex = udev_device_get_ifindex(device);
373 if (ifindex <= 0) {
374 log_warning("Could not find ifindex");
375 return -ENODEV;
376 }
377
378 if (config->name_policy && enable_name_policy()) {
379 char **policy;
380
381 STRV_FOREACH(policy, config->name_policy) {
382 if (streq(*policy, "onboard")) {
383 new_name = udev_device_get_property_value(device, "ID_NET_NAME_ONBOARD");
384 if (new_name)
385 break;
386 } else if (streq(*policy, "slot")) {
387 new_name = udev_device_get_property_value(device, "ID_NET_NAME_SLOT");
388 if (new_name)
389 break;
390 } else if (streq(*policy, "path")) {
391 new_name = udev_device_get_property_value(device, "ID_NET_NAME_PATH");
392 if (new_name)
393 break;
394 } else if (streq(*policy, "mac")) {
395 new_name = udev_device_get_property_value(device, "ID_NET_NAME_MAC");
396 if (new_name)
397 break;
398 } else
399 log_warning("Invalid link naming policy '%s', ignoring.", *policy);
400 }
401 }
402
403 if (!new_name && config->name)
404 new_name = config->name;
405
406 r = rtnl_set_properties(ctx->rtnl, ifindex, new_name, config->mac, config->mtu);
407 if (r < 0) {
408 log_warning("Could not set Name, MACAddress or MTU on %s", name);
409 return r;
410 }
411
412 return 0;
413 }