chiark / gitweb /
networkd: netdev - reduce chance of race when receiving netdev's ifindex
[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->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, ifindex;
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         r = sd_rtnl_message_link_get_ifindex(m, &ifindex);
156         if (r < 0)
157                 log_warning_netdev(netdev, "created netdev with unknown ifindex: %s", strerror(-r));
158         else {
159                 log_info_netdev(netdev, "created netdev with ifindex %d", ifindex);
160                 netdev_set_ifindex(netdev, ifindex);
161         }
162
163         return 1;
164 }
165
166 static int netdev_create(Netdev *netdev, Link *link, sd_rtnl_message_handler_t callback) {
167         _cleanup_sd_rtnl_message_unref_ sd_rtnl_message *req = NULL;
168         const char *kind;
169         int r;
170
171         assert(netdev);
172         assert(!(netdev->kind == NETDEV_KIND_VLAN) || (link && callback && netdev->vlanid >= 0));
173         assert(netdev->name);
174         assert(netdev->manager);
175         assert(netdev->manager->rtnl);
176
177         r = sd_rtnl_message_link_new(RTM_NEWLINK, 0, &req);
178         if (r < 0) {
179                 log_error_netdev(netdev,
180                                  "Could not allocate RTM_NEWLINK message: %s",
181                                  strerror(-r));
182                 return r;
183         }
184
185         if (link) {
186                 r = sd_rtnl_message_append_u32(req, IFLA_LINK, link->ifindex);
187                 if (r < 0) {
188                         log_error_netdev(netdev,
189                                          "Could not append IFLA_LINK attribute: %s",
190                                          strerror(-r));
191                         return r;
192                 }
193         }
194
195         r = sd_rtnl_message_append_string(req, IFLA_IFNAME, netdev->name);
196         if (r < 0) {
197                 log_error_netdev(netdev,
198                                  "Could not append IFLA_IFNAME attribute: %s",
199                                  strerror(-r));
200                 return r;
201         }
202
203         r = sd_rtnl_message_open_container(req, IFLA_LINKINFO);
204         if (r < 0) {
205                 log_error_netdev(netdev,
206                                  "Could not open IFLA_LINKINFO container: %s",
207                                  strerror(-r));
208                 return r;
209         }
210
211         kind = netdev_kind_to_string(netdev->kind);
212         if (!kind) {
213                 log_error_netdev(netdev, "Invalid kind");
214                 return -EINVAL;
215         }
216
217         r = sd_rtnl_message_append_string(req, IFLA_INFO_KIND, kind);
218         if (r < 0) {
219                 log_error_netdev(netdev,
220                                  "Could not append IFLA_INFO_KIND attribute: %s",
221                                  strerror(-r));
222                 return r;
223         }
224
225         if (netdev->vlanid >= 0) {
226                 r = sd_rtnl_message_open_container(req, IFLA_INFO_DATA);
227                 if (r < 0) {
228                         log_error_netdev(netdev,
229                                          "Could not open IFLA_INFO_DATA container: %s",
230                                          strerror(-r));
231                         return r;
232                 }
233
234                 r = sd_rtnl_message_append_u16(req, IFLA_VLAN_ID, netdev->vlanid);
235                 if (r < 0) {
236                         log_error_netdev(netdev,
237                                          "Could not append IFLA_VLAN_ID attribute: %s",
238                                          strerror(-r));
239                         return r;
240                 }
241
242                 r = sd_rtnl_message_close_container(req);
243                 if (r < 0) {
244                         log_error_netdev(netdev,
245                                          "Could not close IFLA_INFO_DATA container %s",
246                                          strerror(-r));
247                         return r;
248                 }
249         }
250
251         r = sd_rtnl_message_close_container(req);
252         if (r < 0) {
253                 log_error_netdev(netdev,
254                                  "Could not close IFLA_LINKINFO container %s",
255                                  strerror(-r));
256                 return r;
257         }
258
259         if (link)
260                 r = sd_rtnl_call_async(netdev->manager->rtnl, req, callback, link, 0, NULL);
261         else
262                 r = sd_rtnl_call_async(netdev->manager->rtnl, req, &netdev_create_handler, netdev, 0, NULL);
263         if (r < 0) {
264                 log_error_netdev(netdev,
265                                  "Could not send rtnetlink message: %s", strerror(-r));
266                 return r;
267         }
268
269         log_debug_netdev(netdev, "creating netdev");
270
271         netdev->state = NETDEV_STATE_CREATING;
272
273         return 0;
274 }
275
276 int netdev_enslave(Netdev *netdev, Link *link, sd_rtnl_message_handler_t callback) {
277         if (netdev->kind == NETDEV_KIND_VLAN)
278                 return netdev_create(netdev, link, callback);
279
280         if (netdev->state == NETDEV_STATE_READY) {
281                 netdev_enslave_ready(netdev, link, callback);
282         } else {
283                 /* the netdev is not yet read, save this request for when it is*/
284                 netdev_enslave_callback *cb;
285
286                 cb = new0(netdev_enslave_callback, 1);
287                 if (!cb)
288                         return log_oom();
289
290                 cb->callback = callback;
291                 cb->link = link;
292
293                 LIST_PREPEND(callbacks, netdev->callbacks, cb);
294         }
295
296         return 0;
297 }
298
299 int netdev_set_ifindex(Netdev *netdev, int ifindex) {
300         assert(netdev);
301         assert(ifindex > 0);
302
303         if (netdev->ifindex > 0) {
304                 if (netdev->ifindex == ifindex)
305                         return 0;
306                 else
307                         return -EEXIST;
308         }
309
310         netdev->ifindex = ifindex;
311
312         netdev_enter_ready(netdev);
313
314         return 0;
315 }
316
317 static int netdev_load_one(Manager *manager, const char *filename) {
318         _cleanup_netdev_free_ Netdev *netdev = NULL;
319         _cleanup_fclose_ FILE *file = NULL;
320         int r;
321
322         assert(manager);
323         assert(filename);
324
325         file = fopen(filename, "re");
326         if (!file) {
327                 if (errno == ENOENT)
328                         return 0;
329                 else
330                         return errno;
331         }
332
333         netdev = new0(Netdev, 1);
334         if (!netdev)
335                 return log_oom();
336
337         netdev->manager = manager;
338         netdev->state = _NETDEV_STATE_INVALID;
339         netdev->kind = _NETDEV_KIND_INVALID;
340         netdev->vlanid = -1;
341
342         r = config_parse(NULL, filename, file, "Netdev\0VLAN\0", config_item_perf_lookup,
343                         (void*) network_gperf_lookup, false, false, netdev);
344         if (r < 0) {
345                 log_warning("Could not parse config file %s: %s", filename, strerror(-r));
346                 return r;
347         }
348
349         if (netdev->kind == _NETDEV_KIND_INVALID) {
350                 log_warning("Netdev without Kind configured in %s. Ignoring", filename);
351                 return 0;
352         }
353
354         if (!netdev->name) {
355                 log_warning("Netdev without Name configured in %s. Ignoring", filename);
356                 return 0;
357         }
358
359         if (netdev->kind == NETDEV_KIND_VLAN && netdev->vlanid < 0) {
360                 log_warning("VLAN without Id configured in %s. Ignoring", filename);
361                 return 0;
362         }
363
364         netdev->filename = strdup(filename);
365         if (!netdev->filename)
366                 return log_oom();
367
368         r = hashmap_put(netdev->manager->netdevs, netdev->name, netdev);
369         if (r < 0)
370                 return r;
371
372         LIST_HEAD_INIT(netdev->callbacks);
373
374         if (netdev->kind != NETDEV_KIND_VLAN) {
375                 r = netdev_create(netdev, NULL, NULL);
376                 if (r < 0)
377                         return r;
378         }
379
380         netdev = NULL;
381
382         return 0;
383 }
384
385 int netdev_load(Manager *manager) {
386         Netdev *netdev;
387         char **files, **f;
388         int r;
389
390         assert(manager);
391
392         while ((netdev = hashmap_first(manager->netdevs)))
393                 netdev_free(netdev);
394
395         r = conf_files_list_strv(&files, ".netdev", NULL, network_dirs);
396         if (r < 0) {
397                 log_error("Failed to enumerate netdev files: %s", strerror(-r));
398                 return r;
399         }
400
401         STRV_FOREACH_BACKWARDS(f, files) {
402                 r = netdev_load_one(manager, *f);
403                 if (r < 0)
404                         return r;
405         }
406
407         strv_free(files);
408
409         return 0;
410 }