]>
Commit | Line | Data |
---|---|---|
02b59d57 TG |
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 | ||
52433f6b TG |
29 | static const char* const netdev_kind_table[] = { |
30 | [NETDEV_KIND_BRIDGE] = "bridge", | |
54abf461 TG |
31 | [NETDEV_KIND_BOND] = "bond", |
32 | [NETDEV_KIND_VLAN] = "vlan", | |
52433f6b | 33 | }; |
02b59d57 | 34 | |
52433f6b TG |
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) | |
02b59d57 TG |
42 | return; |
43 | ||
52433f6b TG |
44 | while ((callback = netdev->callbacks)) { |
45 | LIST_REMOVE(callbacks, netdev->callbacks, callback); | |
02b59d57 TG |
46 | free(callback); |
47 | } | |
48 | ||
52433f6b TG |
49 | if (netdev->name) |
50 | hashmap_remove(netdev->manager->netdevs, netdev->name); | |
02b59d57 | 51 | |
52433f6b | 52 | free(netdev->filename); |
02b59d57 | 53 | |
52433f6b TG |
54 | free(netdev->description); |
55 | free(netdev->name); | |
02b59d57 | 56 | |
52433f6b | 57 | free(netdev); |
02b59d57 TG |
58 | } |
59 | ||
52433f6b TG |
60 | int netdev_get(Manager *manager, const char *name, Netdev **ret) { |
61 | Netdev *netdev; | |
02b59d57 TG |
62 | |
63 | assert(manager); | |
64 | assert(name); | |
65 | assert(ret); | |
66 | ||
52433f6b TG |
67 | netdev = hashmap_get(manager->netdevs, name); |
68 | if (!netdev) { | |
02b59d57 TG |
69 | *ret = NULL; |
70 | return -ENOENT; | |
71 | } | |
72 | ||
52433f6b | 73 | *ret = netdev; |
02b59d57 TG |
74 | |
75 | return 0; | |
76 | } | |
77 | ||
52433f6b TG |
78 | static int netdev_enter_failed(Netdev *netdev) { |
79 | netdev->state = NETDEV_STATE_FAILED; | |
02b59d57 TG |
80 | |
81 | return 0; | |
82 | } | |
83 | ||
52433f6b | 84 | static int netdev_enslave_ready(Netdev *netdev, Link* link, sd_rtnl_message_handler_t callback) { |
02b59d57 TG |
85 | _cleanup_sd_rtnl_message_unref_ sd_rtnl_message *req = NULL; |
86 | int r; | |
87 | ||
52433f6b TG |
88 | assert(netdev); |
89 | assert(netdev->state == NETDEV_STATE_READY); | |
02b59d57 TG |
90 | assert(link); |
91 | assert(callback); | |
92 | ||
fc25d7f8 | 93 | r = sd_rtnl_message_link_new(RTM_SETLINK, link->ifindex, &req); |
02b59d57 | 94 | if (r < 0) { |
52433f6b | 95 | log_error_netdev(netdev, |
3333d748 ZJS |
96 | "Could not allocate RTM_SETLINK message: %s", |
97 | strerror(-r)); | |
02b59d57 TG |
98 | return r; |
99 | } | |
100 | ||
52433f6b | 101 | r = sd_rtnl_message_append_u32(req, IFLA_MASTER, netdev->link->ifindex); |
02b59d57 | 102 | if (r < 0) { |
52433f6b | 103 | log_error_netdev(netdev, |
3333d748 ZJS |
104 | "Could not append IFLA_MASTER attribute: %s", |
105 | strerror(-r)); | |
02b59d57 TG |
106 | return r; |
107 | } | |
108 | ||
52433f6b | 109 | r = sd_rtnl_call_async(netdev->manager->rtnl, req, callback, link, 0, NULL); |
02b59d57 | 110 | if (r < 0) { |
52433f6b | 111 | log_error_netdev(netdev, |
3333d748 ZJS |
112 | "Could not send rtnetlink message: %s", |
113 | strerror(-r)); | |
02b59d57 TG |
114 | return r; |
115 | } | |
116 | ||
52433f6b | 117 | log_debug_netdev(netdev, "enslaving link '%s'", link->ifname); |
ab47d620 | 118 | |
02b59d57 TG |
119 | return 0; |
120 | } | |
121 | ||
52433f6b TG |
122 | static int netdev_enter_ready(Netdev *netdev) { |
123 | netdev_enslave_callback *callback; | |
02b59d57 | 124 | |
52433f6b TG |
125 | assert(netdev); |
126 | assert(netdev->name); | |
924fe430 | 127 | |
52433f6b | 128 | netdev->state = NETDEV_STATE_READY; |
02b59d57 | 129 | |
52433f6b | 130 | log_info_netdev(netdev, "netdev ready"); |
02b59d57 | 131 | |
52433f6b TG |
132 | LIST_FOREACH(callbacks, callback, netdev->callbacks) { |
133 | /* enslave the links that were attempted to be enslaved befor the | |
02b59d57 | 134 | * link was ready */ |
52433f6b | 135 | netdev_enslave_ready(netdev, callback->link, callback->callback); |
02b59d57 TG |
136 | } |
137 | ||
138 | return 0; | |
139 | } | |
140 | ||
52433f6b TG |
141 | static int netdev_create_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) { |
142 | Netdev *netdev = userdata; | |
02b59d57 TG |
143 | int r; |
144 | ||
52433f6b | 145 | assert(netdev->state != _NETDEV_STATE_INVALID); |
02b59d57 TG |
146 | |
147 | r = sd_rtnl_message_get_errno(m); | |
148 | if (r < 0) { | |
52433f6b TG |
149 | log_warning_netdev(netdev, "netdev failed: %s", strerror(-r)); |
150 | netdev_enter_failed(netdev); | |
dd3efc09 TG |
151 | |
152 | return 1; | |
02b59d57 TG |
153 | } |
154 | ||
dd3efc09 | 155 | return 1; |
02b59d57 TG |
156 | } |
157 | ||
54abf461 | 158 | static int netdev_create(Netdev *netdev, Link *link, sd_rtnl_message_handler_t callback) { |
02b59d57 | 159 | _cleanup_sd_rtnl_message_unref_ sd_rtnl_message *req = NULL; |
52433f6b | 160 | const char *kind; |
02b59d57 TG |
161 | int r; |
162 | ||
52433f6b | 163 | assert(netdev); |
54abf461 | 164 | assert(!(netdev->kind == NETDEV_KIND_VLAN) || (link && callback && netdev->vlanid >= 0)); |
52433f6b TG |
165 | assert(netdev->name); |
166 | assert(netdev->manager); | |
167 | assert(netdev->manager->rtnl); | |
02b59d57 | 168 | |
fc25d7f8 | 169 | r = sd_rtnl_message_link_new(RTM_NEWLINK, 0, &req); |
02b59d57 | 170 | if (r < 0) { |
52433f6b | 171 | log_error_netdev(netdev, |
3333d748 ZJS |
172 | "Could not allocate RTM_NEWLINK message: %s", |
173 | strerror(-r)); | |
02b59d57 TG |
174 | return r; |
175 | } | |
176 | ||
54abf461 TG |
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 | ||
52433f6b | 187 | r = sd_rtnl_message_append_string(req, IFLA_IFNAME, netdev->name); |
02b59d57 | 188 | if (r < 0) { |
52433f6b | 189 | log_error_netdev(netdev, |
3333d748 ZJS |
190 | "Could not append IFLA_IFNAME attribute: %s", |
191 | strerror(-r)); | |
02b59d57 TG |
192 | return r; |
193 | } | |
194 | ||
195 | r = sd_rtnl_message_open_container(req, IFLA_LINKINFO); | |
196 | if (r < 0) { | |
52433f6b | 197 | log_error_netdev(netdev, |
3333d748 ZJS |
198 | "Could not open IFLA_LINKINFO container: %s", |
199 | strerror(-r)); | |
02b59d57 TG |
200 | return r; |
201 | } | |
202 | ||
52433f6b TG |
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); | |
02b59d57 | 210 | if (r < 0) { |
52433f6b | 211 | log_error_netdev(netdev, |
3333d748 ZJS |
212 | "Could not append IFLA_INFO_KIND attribute: %s", |
213 | strerror(-r)); | |
02b59d57 TG |
214 | return r; |
215 | } | |
216 | ||
54abf461 TG |
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 | ||
02b59d57 TG |
243 | r = sd_rtnl_message_close_container(req); |
244 | if (r < 0) { | |
52433f6b | 245 | log_error_netdev(netdev, |
3333d748 ZJS |
246 | "Could not close IFLA_LINKINFO container %s", |
247 | strerror(-r)); | |
02b59d57 TG |
248 | return r; |
249 | } | |
250 | ||
54abf461 TG |
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); | |
02b59d57 | 255 | if (r < 0) { |
52433f6b | 256 | log_error_netdev(netdev, |
3333d748 | 257 | "Could not send rtnetlink message: %s", strerror(-r)); |
02b59d57 TG |
258 | return r; |
259 | } | |
260 | ||
52433f6b | 261 | log_debug_netdev(netdev, "creating netdev"); |
02b59d57 | 262 | |
52433f6b | 263 | netdev->state = NETDEV_STATE_CREATING; |
02b59d57 TG |
264 | |
265 | return 0; | |
266 | } | |
267 | ||
52433f6b | 268 | int netdev_enslave(Netdev *netdev, Link *link, sd_rtnl_message_handler_t callback) { |
54abf461 TG |
269 | if (netdev->kind == NETDEV_KIND_VLAN) |
270 | return netdev_create(netdev, link, callback); | |
271 | ||
52433f6b TG |
272 | if (netdev->state == NETDEV_STATE_READY) { |
273 | netdev_enslave_ready(netdev, link, callback); | |
02b59d57 | 274 | } else { |
52433f6b TG |
275 | /* the netdev is not yet read, save this request for when it is*/ |
276 | netdev_enslave_callback *cb; | |
02b59d57 | 277 | |
52433f6b | 278 | cb = new0(netdev_enslave_callback, 1); |
02b59d57 TG |
279 | if (!cb) |
280 | return log_oom(); | |
281 | ||
282 | cb->callback = callback; | |
283 | cb->link = link; | |
284 | ||
52433f6b | 285 | LIST_PREPEND(callbacks, netdev->callbacks, cb); |
02b59d57 TG |
286 | } |
287 | ||
288 | return 0; | |
289 | } | |
290 | ||
52433f6b TG |
291 | int netdev_set_link(Manager *m, NetdevKind kind, Link *link) { |
292 | Netdev *netdev; | |
924fe430 | 293 | int r; |
02b59d57 | 294 | |
52433f6b | 295 | r = netdev_get(m, link->ifname, &netdev); |
924fe430 TG |
296 | if (r < 0) |
297 | return r; | |
02b59d57 | 298 | |
52433f6b | 299 | if (netdev->link && netdev->link != link) |
02b59d57 TG |
300 | return -EEXIST; |
301 | ||
52433f6b TG |
302 | if (netdev->kind != kind) |
303 | return -EINVAL; | |
02b59d57 | 304 | |
52433f6b TG |
305 | netdev->link = link; |
306 | ||
307 | netdev_enter_ready(netdev); | |
02b59d57 TG |
308 | |
309 | return 0; | |
310 | } | |
311 | ||
52433f6b TG |
312 | static int netdev_load_one(Manager *manager, const char *filename) { |
313 | _cleanup_netdev_free_ Netdev *netdev = NULL; | |
02b59d57 TG |
314 | _cleanup_fclose_ FILE *file = NULL; |
315 | int r; | |
316 | ||
bf1bc670 TA |
317 | assert(manager); |
318 | assert(filename); | |
319 | ||
02b59d57 TG |
320 | file = fopen(filename, "re"); |
321 | if (!file) { | |
322 | if (errno == ENOENT) | |
323 | return 0; | |
324 | else | |
325 | return errno; | |
326 | } | |
327 | ||
52433f6b TG |
328 | netdev = new0(Netdev, 1); |
329 | if (!netdev) | |
02b59d57 TG |
330 | return log_oom(); |
331 | ||
52433f6b TG |
332 | netdev->manager = manager; |
333 | netdev->state = _NETDEV_STATE_INVALID; | |
334 | netdev->kind = _NETDEV_KIND_INVALID; | |
54abf461 | 335 | netdev->vlanid = -1; |
02b59d57 | 336 | |
54abf461 | 337 | r = config_parse(NULL, filename, file, "Netdev\0VLAN\0", config_item_perf_lookup, |
52433f6b | 338 | (void*) network_gperf_lookup, false, false, netdev); |
02b59d57 TG |
339 | if (r < 0) { |
340 | log_warning("Could not parse config file %s: %s", filename, strerror(-r)); | |
341 | return r; | |
449f7554 | 342 | } |
02b59d57 | 343 | |
52433f6b TG |
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); | |
02b59d57 TG |
351 | return 0; |
352 | } | |
353 | ||
54abf461 TG |
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 | ||
52433f6b TG |
359 | netdev->filename = strdup(filename); |
360 | if (!netdev->filename) | |
02b59d57 TG |
361 | return log_oom(); |
362 | ||
52433f6b | 363 | r = hashmap_put(netdev->manager->netdevs, netdev->name, netdev); |
02b59d57 TG |
364 | if (r < 0) |
365 | return r; | |
366 | ||
52433f6b | 367 | LIST_HEAD_INIT(netdev->callbacks); |
02b59d57 | 368 | |
54abf461 TG |
369 | if (netdev->kind != NETDEV_KIND_VLAN) { |
370 | r = netdev_create(netdev, NULL, NULL); | |
371 | if (r < 0) | |
372 | return r; | |
373 | } | |
02b59d57 | 374 | |
52433f6b | 375 | netdev = NULL; |
02b59d57 TG |
376 | |
377 | return 0; | |
378 | } | |
379 | ||
52433f6b TG |
380 | int netdev_load(Manager *manager) { |
381 | Netdev *netdev; | |
02b59d57 TG |
382 | char **files, **f; |
383 | int r; | |
384 | ||
385 | assert(manager); | |
386 | ||
52433f6b TG |
387 | while ((netdev = hashmap_first(manager->netdevs))) |
388 | netdev_free(netdev); | |
02b59d57 | 389 | |
2ad8416d | 390 | r = conf_files_list_strv(&files, ".netdev", NULL, network_dirs); |
02b59d57 TG |
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) { | |
52433f6b | 397 | r = netdev_load_one(manager, *f); |
02b59d57 TG |
398 | if (r < 0) |
399 | return r; | |
400 | } | |
401 | ||
402 | strv_free(files); | |
403 | ||
404 | return 0; | |
405 | } |