chiark / gitweb /
udev: link-config - move naming policy from udev rules
[elogind.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 }