chiark / gitweb /
1a6eebe1a71182ba1727b746fb123f9ba0eaef81
[elogind.git] / src / network / networkd-netdev.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 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 "networkd.h"
23 #include "net-util.h"
24 #include "path-util.h"
25 #include "conf-files.h"
26 #include "conf-parser.h"
27 #include "list.h"
28
29 static const char* const netdev_kind_table[] = {
30         [NETDEV_KIND_BRIDGE] = "bridge",
31         [NETDEV_KIND_BOND] = "bond",
32         [NETDEV_KIND_VLAN] = "vlan",
33 };
34
35 DEFINE_STRING_TABLE_LOOKUP(netdev_kind, NetdevKind);
36 DEFINE_CONFIG_PARSE_ENUM(config_parse_netdev_kind, netdev_kind, NetdevKind, "Failed to parse netdev kind");
37
38 void netdev_free(Netdev *netdev) {
39         netdev_enslave_callback *callback;
40
41         if (!netdev)
42                 return;
43
44         while ((callback = netdev->callbacks)) {
45                 LIST_REMOVE(callbacks, netdev->callbacks, callback);
46                 free(callback);
47         }
48
49         if (netdev->name)
50                 hashmap_remove(netdev->manager->netdevs, netdev->name);
51
52         free(netdev->filename);
53
54         free(netdev->description);
55         free(netdev->name);
56
57         free(netdev);
58 }
59
60 int netdev_get(Manager *manager, const char *name, Netdev **ret) {
61         Netdev *netdev;
62
63         assert(manager);
64         assert(name);
65         assert(ret);
66
67         netdev = hashmap_get(manager->netdevs, name);
68         if (!netdev) {
69                 *ret = NULL;
70                 return -ENOENT;
71         }
72
73         *ret = netdev;
74
75         return 0;
76 }
77
78 static int netdev_enter_failed(Netdev *netdev) {
79         netdev->state = NETDEV_STATE_FAILED;
80
81         return 0;
82 }
83
84 static int netdev_enslave_ready(Netdev *netdev, Link* link, sd_rtnl_message_handler_t callback) {
85         _cleanup_sd_rtnl_message_unref_ sd_rtnl_message *req = NULL;
86         int r;
87
88         assert(netdev);
89         assert(netdev->state == NETDEV_STATE_READY);
90         assert(link);
91         assert(callback);
92
93         r = sd_rtnl_message_link_new(RTM_SETLINK, link->ifindex, &req);
94         if (r < 0) {
95                 log_error_netdev(netdev,
96                                  "Could not allocate RTM_SETLINK message: %s",
97                                  strerror(-r));
98                 return r;
99         }
100
101         r = sd_rtnl_message_append_u32(req, IFLA_MASTER, netdev->link->ifindex);
102         if (r < 0) {
103                 log_error_netdev(netdev,
104                                  "Could not append IFLA_MASTER attribute: %s",
105                                  strerror(-r));
106                 return r;
107         }
108
109         r = sd_rtnl_call_async(netdev->manager->rtnl, req, callback, link, 0, NULL);
110         if (r < 0) {
111                 log_error_netdev(netdev,
112                                  "Could not send rtnetlink message: %s",
113                                  strerror(-r));
114                 return r;
115         }
116
117         log_debug_netdev(netdev, "enslaving link '%s'", link->ifname);
118
119         return 0;
120 }
121
122 static int netdev_enter_ready(Netdev *netdev) {
123         netdev_enslave_callback *callback;
124
125         assert(netdev);
126         assert(netdev->name);
127
128         netdev->state = NETDEV_STATE_READY;
129
130         log_info_netdev(netdev, "netdev ready");
131
132         LIST_FOREACH(callbacks, callback, netdev->callbacks) {
133                 /* enslave the links that were attempted to be enslaved befor the
134                  * link was ready */
135                 netdev_enslave_ready(netdev, callback->link, callback->callback);
136         }
137
138         return 0;
139 }
140
141 static int netdev_create_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
142         Netdev *netdev = userdata;
143         int r;
144
145         assert(netdev->state != _NETDEV_STATE_INVALID);
146
147         r = sd_rtnl_message_get_errno(m);
148         if (r < 0) {
149                 log_warning_netdev(netdev, "netdev failed: %s", strerror(-r));
150                 netdev_enter_failed(netdev);
151
152                 return 1;
153         }
154
155         return 1;
156 }
157
158 static int netdev_create(Netdev *netdev, Link *link, sd_rtnl_message_handler_t callback) {
159         _cleanup_sd_rtnl_message_unref_ sd_rtnl_message *req = NULL;
160         const char *kind;
161         int r;
162
163         assert(netdev);
164         assert(!(netdev->kind == NETDEV_KIND_VLAN) || (link && callback && netdev->vlanid >= 0));
165         assert(netdev->name);
166         assert(netdev->manager);
167         assert(netdev->manager->rtnl);
168
169         r = sd_rtnl_message_link_new(RTM_NEWLINK, 0, &req);
170         if (r < 0) {
171                 log_error_netdev(netdev,
172                                  "Could not allocate RTM_NEWLINK message: %s",
173                                  strerror(-r));
174                 return r;
175         }
176
177         if (link) {
178                 r = sd_rtnl_message_append_u32(req, IFLA_LINK, link->ifindex);
179                 if (r < 0) {
180                         log_error_netdev(netdev,
181                                          "Could not append IFLA_LINK attribute: %s",
182                                          strerror(-r));
183                         return r;
184                 }
185         }
186
187         r = sd_rtnl_message_append_string(req, IFLA_IFNAME, netdev->name);
188         if (r < 0) {
189                 log_error_netdev(netdev,
190                                  "Could not append IFLA_IFNAME attribute: %s",
191                                  strerror(-r));
192                 return r;
193         }
194
195         r = sd_rtnl_message_open_container(req, IFLA_LINKINFO);
196         if (r < 0) {
197                 log_error_netdev(netdev,
198                                  "Could not open IFLA_LINKINFO container: %s",
199                                  strerror(-r));
200                 return r;
201         }
202
203         kind = netdev_kind_to_string(netdev->kind);
204         if (!kind) {
205                 log_error_netdev(netdev, "Invalid kind");
206                 return -EINVAL;
207         }
208
209         r = sd_rtnl_message_append_string(req, IFLA_INFO_KIND, kind);
210         if (r < 0) {
211                 log_error_netdev(netdev,
212                                  "Could not append IFLA_INFO_KIND attribute: %s",
213                                  strerror(-r));
214                 return r;
215         }
216
217         if (netdev->vlanid >= 0) {
218                 r = sd_rtnl_message_open_container(req, IFLA_INFO_DATA);
219                 if (r < 0) {
220                         log_error_netdev(netdev,
221                                          "Could not open IFLA_INFO_DATA container: %s",
222                                          strerror(-r));
223                         return r;
224                 }
225
226                 r = sd_rtnl_message_append_u16(req, IFLA_VLAN_ID, netdev->vlanid);
227                 if (r < 0) {
228                         log_error_netdev(netdev,
229                                          "Could not append IFLA_VLAN_ID attribute: %s",
230                                          strerror(-r));
231                         return r;
232                 }
233
234                 r = sd_rtnl_message_close_container(req);
235                 if (r < 0) {
236                         log_error_netdev(netdev,
237                                          "Could not close IFLA_INFO_DATA container %s",
238                                          strerror(-r));
239                         return r;
240                 }
241         }
242
243         r = sd_rtnl_message_close_container(req);
244         if (r < 0) {
245                 log_error_netdev(netdev,
246                                  "Could not close IFLA_LINKINFO container %s",
247                                  strerror(-r));
248                 return r;
249         }
250
251         if (link)
252                 r = sd_rtnl_call_async(netdev->manager->rtnl, req, callback, link, 0, NULL);
253         else
254                 r = sd_rtnl_call_async(netdev->manager->rtnl, req, &netdev_create_handler, netdev, 0, NULL);
255         if (r < 0) {
256                 log_error_netdev(netdev,
257                                  "Could not send rtnetlink message: %s", strerror(-r));
258                 return r;
259         }
260
261         log_debug_netdev(netdev, "creating netdev");
262
263         netdev->state = NETDEV_STATE_CREATING;
264
265         return 0;
266 }
267
268 int netdev_enslave(Netdev *netdev, Link *link, sd_rtnl_message_handler_t callback) {
269         if (netdev->kind == NETDEV_KIND_VLAN)
270                 return netdev_create(netdev, link, callback);
271
272         if (netdev->state == NETDEV_STATE_READY) {
273                 netdev_enslave_ready(netdev, link, callback);
274         } else {
275                 /* the netdev is not yet read, save this request for when it is*/
276                 netdev_enslave_callback *cb;
277
278                 cb = new0(netdev_enslave_callback, 1);
279                 if (!cb)
280                         return log_oom();
281
282                 cb->callback = callback;
283                 cb->link = link;
284
285                 LIST_PREPEND(callbacks, netdev->callbacks, cb);
286         }
287
288         return 0;
289 }
290
291 int netdev_set_link(Manager *m, NetdevKind kind, Link *link) {
292         Netdev *netdev;
293         int r;
294
295         r = netdev_get(m, link->ifname, &netdev);
296         if (r < 0)
297                 return r;
298
299         if (netdev->link && netdev->link != link)
300                 return -EEXIST;
301
302         if (netdev->kind != kind)
303                 return -EINVAL;
304
305         netdev->link = link;
306
307         netdev_enter_ready(netdev);
308
309         return 0;
310 }
311
312 static int netdev_load_one(Manager *manager, const char *filename) {
313         _cleanup_netdev_free_ Netdev *netdev = NULL;
314         _cleanup_fclose_ FILE *file = NULL;
315         int r;
316
317         assert(manager);
318         assert(filename);
319
320         file = fopen(filename, "re");
321         if (!file) {
322                 if (errno == ENOENT)
323                         return 0;
324                 else
325                         return errno;
326         }
327
328         netdev = new0(Netdev, 1);
329         if (!netdev)
330                 return log_oom();
331
332         netdev->manager = manager;
333         netdev->state = _NETDEV_STATE_INVALID;
334         netdev->kind = _NETDEV_KIND_INVALID;
335         netdev->vlanid = -1;
336
337         r = config_parse(NULL, filename, file, "Netdev\0VLAN\0", config_item_perf_lookup,
338                         (void*) network_gperf_lookup, false, false, netdev);
339         if (r < 0) {
340                 log_warning("Could not parse config file %s: %s", filename, strerror(-r));
341                 return r;
342         }
343
344         if (netdev->kind == _NETDEV_KIND_INVALID) {
345                 log_warning("Netdev without Kind configured in %s. Ignoring", filename);
346                 return 0;
347         }
348
349         if (!netdev->name) {
350                 log_warning("Netdev without Name configured in %s. Ignoring", filename);
351                 return 0;
352         }
353
354         if (netdev->kind == NETDEV_KIND_VLAN && netdev->vlanid < 0) {
355                 log_warning("VLAN without Id configured in %s. Ignoring", filename);
356                 return 0;
357         }
358
359         netdev->filename = strdup(filename);
360         if (!netdev->filename)
361                 return log_oom();
362
363         r = hashmap_put(netdev->manager->netdevs, netdev->name, netdev);
364         if (r < 0)
365                 return r;
366
367         LIST_HEAD_INIT(netdev->callbacks);
368
369         if (netdev->kind != NETDEV_KIND_VLAN) {
370                 r = netdev_create(netdev, NULL, NULL);
371                 if (r < 0)
372                         return r;
373         }
374
375         netdev = NULL;
376
377         return 0;
378 }
379
380 int netdev_load(Manager *manager) {
381         Netdev *netdev;
382         char **files, **f;
383         int r;
384
385         assert(manager);
386
387         while ((netdev = hashmap_first(manager->netdevs)))
388                 netdev_free(netdev);
389
390         r = conf_files_list_strv(&files, ".netdev", NULL, network_dirs);
391         if (r < 0) {
392                 log_error("Failed to enumerate netdev files: %s", strerror(-r));
393                 return r;
394         }
395
396         STRV_FOREACH_BACKWARDS(f, files) {
397                 r = netdev_load_one(manager, *f);
398                 if (r < 0)
399                         return r;
400         }
401
402         strv_free(files);
403
404         return 0;
405 }