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