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