chiark / gitweb /
bb0640589be5160e26b6e66ee45219727e594265
[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
38 struct link_config_ctx {
39         LIST_HEAD(link_config, links);
40
41         int ethtool_fd;
42
43         sd_rtnl *rtnl;
44
45         char **link_dirs;
46         usec_t *link_dirs_ts_usec;
47 };
48
49 int link_config_ctx_new(link_config_ctx **ret) {
50         link_config_ctx *ctx;
51         int r;
52
53         if (!ret)
54                 return -EINVAL;
55
56         ctx = new0(link_config_ctx, 1);
57         if (!ctx)
58                 return -ENOMEM;
59
60         r = ethtool_connect(&ctx->ethtool_fd);
61         if (r < 0) {
62                 link_config_ctx_free(ctx);
63                 return r;
64         }
65
66         r = sd_rtnl_open(0, &ctx->rtnl);
67         if (r < 0) {
68                 link_config_ctx_free(ctx);
69                 return r;
70         }
71
72         LIST_HEAD_INIT(ctx->links);
73
74         ctx->link_dirs = strv_new("/etc/net/links",
75                                   "/run/net/links",
76                                   "/usr/lib/net/links",
77                                   NULL);
78         if (!ctx->link_dirs) {
79                 log_error("failed to build link config directory array");
80                 link_config_ctx_free(ctx);
81                 return -ENOMEM;
82         }
83         if (!path_strv_canonicalize_uniq(ctx->link_dirs)) {
84                 log_error("failed to canonicalize link config directories\n");
85                 link_config_ctx_free(ctx);
86                 return -ENOMEM;
87         }
88
89         ctx->link_dirs_ts_usec = calloc(strv_length(ctx->link_dirs), sizeof(usec_t));
90         if(!ctx->link_dirs_ts_usec) {
91                 link_config_ctx_free(ctx);
92                 return -ENOMEM;
93         }
94
95         *ret = ctx;
96         return 0;
97 }
98
99 static void link_configs_free(link_config_ctx *ctx) {
100         link_config *link, *link_next;
101
102         if (!ctx)
103                 return;
104
105         LIST_FOREACH_SAFE(links, link, link_next, ctx->links) {
106                 free(link->filename);
107                 free(link->match_path);
108                 free(link->match_driver);
109                 free(link->match_type);
110                 free(link->description);
111
112                 free(link);
113         }
114 }
115
116 void link_config_ctx_free(link_config_ctx *ctx) {
117         if (!ctx)
118                 return;
119
120         if (ctx->ethtool_fd >= 0)
121                 close_nointr_nofail(ctx->ethtool_fd);
122
123         sd_rtnl_unref(ctx->rtnl);
124
125         strv_free(ctx->link_dirs);
126         free(ctx->link_dirs_ts_usec);
127         link_configs_free(ctx);
128
129         free(ctx);
130
131         return;
132 }
133
134 static int load_link(link_config_ctx *ctx, const char *filename) {
135         link_config *link;
136         FILE *file;
137         int r;
138
139         file = fopen(filename, "re");
140         if (!file) {
141                 if (errno == ENOENT)
142                         return 0;
143                 else
144                         return errno;
145         }
146
147         link = new0(link_config, 1);
148         if (!link) {
149                 r = log_oom();
150                 goto failure;
151         }
152
153         r = config_parse(NULL, filename, file, "Match\0Link\0Ethernet\0", config_item_perf_lookup,
154                          (void*) link_config_gperf_lookup, false, false, link);
155         if (r < 0) {
156                 log_warning("Colud not parse config file %s: %s", filename, strerror(-r));
157                 goto failure;
158         } else
159                 log_info("Parsed configuration file %s", filename);
160
161         link->filename = strdup(filename);
162
163         LIST_PREPEND(links, ctx->links, link);
164
165         return 0;
166
167 failure:
168         free(link);
169         return r;
170 }
171
172 int link_config_load(link_config_ctx *ctx) {
173         int r;
174         char **files, **f;
175
176         link_configs_free(ctx);
177
178         /* update timestamps */
179         paths_check_timestamp(ctx->link_dirs, ctx->link_dirs_ts_usec, true);
180
181         r = conf_files_list_strv(&files, ".link", NULL, (const char **)ctx->link_dirs);
182         if (r < 0) {
183                 log_error("failed to enumerate link files: %s", strerror(-r));
184                 return r;
185         }
186
187         STRV_FOREACH_BACKWARDS(f, files) {
188                 r = load_link(ctx, *f);
189                 if (r < 0)
190                         return r;
191         }
192
193         return 0;
194 }
195
196 bool link_config_should_reload(link_config_ctx *ctx) {
197         return paths_check_timestamp(ctx->link_dirs, ctx->link_dirs_ts_usec, false);
198 }
199
200 static bool match_config(link_config *match, struct udev_device *device) {
201         const char *property;
202
203         if (match->match_mac) {
204                 property = udev_device_get_sysattr_value(device, "address");
205                 if (!property || !streq(match->match_mac, property)) {
206                         log_debug("Device MAC address (%s) did not match MACAddress=%s", property, match->match_mac);
207                         return 0;
208                 }
209         }
210
211         if (match->match_path) {
212                 property = udev_device_get_property_value(device, "ID_PATH");
213                 if (!property || !streq(match->match_path, property)) {
214                         log_debug("Device's persistent path (%s) did not match Path=%s", property, match->match_path);
215                         return 0;
216                 }
217         }
218
219         if (match->match_driver) {
220                 property = udev_device_get_driver(device);
221                 if (!property || !streq(match->match_driver, property)) {
222                         log_debug("Device driver (%s) did not match Driver=%s", property, match->match_driver);
223                         return 0;
224                 }
225         }
226
227         if (match->match_type) {
228                 property = udev_device_get_devtype(device);
229                 if (!property || !streq(match->match_type, property)) {
230                         log_debug("Device type (%s) did not match Type=%s", property, match->match_type);
231                         return 0;
232                 }
233         }
234
235         return 1;
236 }
237
238 int link_config_get(link_config_ctx *ctx, struct udev_device *device, link_config **ret) {
239         link_config *link;
240
241         LIST_FOREACH(links, link, ctx->links) {
242                 if (!match_config(link, device)) {
243                         log_info("Config file %s does not apply to device %s", link->filename, udev_device_get_sysname(device));
244                 } else {
245                         log_info("Config file %s applies to device %s", link->filename, udev_device_get_sysname(device));
246                         *ret = link;
247                         return 0;
248                 }
249         }
250
251         return -ENOENT;
252 }
253
254 static int rtnl_set_properties(sd_rtnl *rtnl, int ifindex, const char *name, const char *mac, unsigned int mtu) {
255         _cleanup_sd_rtnl_message_unref_ sd_rtnl_message *message;
256         char new_name[IFNAMSIZ];
257         struct ether_addr new_mac;
258         bool need_update;
259         int r;
260
261         assert(rtnl);
262         assert(ifindex > 0);
263
264         r = sd_rtnl_message_link_new(RTM_NEWLINK, ifindex, 0, 0, &message);
265         if (r < 0)
266                 return r;
267
268         if (name) {
269                 strscpy(new_name, IFNAMSIZ, name);
270                 r = sd_rtnl_message_append(message, IFLA_IFNAME, new_name);
271                 if (r < 0)
272                         return r;
273
274                 need_update = true;
275         }
276
277         if (mac) {
278                 r = sscanf(mac, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
279                            &new_mac.ether_addr_octet[0],
280                            &new_mac.ether_addr_octet[1],
281                            &new_mac.ether_addr_octet[2],
282                            &new_mac.ether_addr_octet[3],
283                            &new_mac.ether_addr_octet[4],
284                            &new_mac.ether_addr_octet[5]);
285                 if (r != 6)
286                         return -EINVAL;
287                 r = sd_rtnl_message_append(message, IFLA_ADDRESS, &new_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, 250 * USEC_PER_MSEC, NULL);
304                 if (r < 0)
305                         return r;
306         }
307
308         return 0;
309 }
310
311 int link_config_apply(link_config_ctx *ctx, link_config *config, struct udev_device *device) {
312         const char *name;
313         int r, ifindex;
314
315         name = udev_device_get_sysname(device);
316         if (!name)
317                 return -EINVAL;
318
319         log_info("Configuring %s", name);
320
321         if (config->description) {
322                 r = udev_device_set_sysattr_value(device, "ifalias",
323                                                   config->description);
324                 if (r < 0)
325                         log_warning("Could not set description of %s to '%s': %s",
326                                     name, config->description, strerror(-r));
327                 else
328                         log_info("Set link description of %s to '%s'", name,
329                                  config->description);
330         }
331
332         if (config->speed || config->duplex) {
333                 r = ethtool_set_speed(ctx->ethtool_fd, name,
334                                       config->speed, config->duplex);
335                 if (r < 0)
336                         log_warning("Could not set speed or duplex of %s to %u Mbytes (%s): %s",
337                                     name, config->speed, config->duplex, strerror(-r));
338                 else
339                         log_info("Set speed or duplex of %s to %u Mbytes (%s)", name,
340                                  config->speed, config->duplex);
341         }
342
343         if (config->wol) {
344                 r = ethtool_set_wol(ctx->ethtool_fd, name, config->wol);
345                 if (r < 0)
346                         log_warning("Could not set WakeOnLan of %s to %s: %s",
347                                     name, config->wol, strerror(-r));
348                 else
349                         log_info("Set WakeOnLan of %s to %s", name, config->wol);
350         }
351
352         ifindex = udev_device_get_ifindex(device);
353         if (ifindex <= 0) {
354                 log_warning("Could not find ifindex");
355                 return -ENODEV;
356         }
357
358         r = rtnl_set_properties(ctx->rtnl, ifindex, config->name, config->mac, config->mtu);
359         if (r < 0) {
360                 log_warning("Could not set Name, MACAddress or MTU on %s", name);
361                 return r;
362         }
363
364         return 0;
365 }