chiark / gitweb /
72be726e0d547dee708e7907a88acdb7858b4d31
[elogind.git] / src / network / networkd-brvlan.c
1 /***
2   This file is part of elogind.
3
4   Copyright (C) 2016 BISDN GmbH. All rights reserved.
5
6   elogind is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10
11   elogind is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with elogind; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include <netinet/in.h>
21 #include <linux/if_bridge.h>
22 #include <stdbool.h>
23
24 #include "alloc-util.h"
25 #include "conf-parser.h"
26 #include "netlink-util.h"
27 #include "networkd-brvlan.h"
28 #include "networkd.h"
29 #include "parse-util.h"
30 #include "vlan-util.h"
31
32 static bool is_bit_set(unsigned bit, uint32_t scope) {
33         assert(bit < sizeof(scope)*8);
34         return scope & (1 << bit);
35 }
36
37 static inline void set_bit(unsigned nr, uint32_t *addr) {
38         if (nr < BRIDGE_VLAN_BITMAP_MAX)
39                 addr[nr / 32] |= (((uint32_t) 1) << (nr % 32));
40 }
41
42 static inline int is_vid_valid(unsigned vid) {
43         if (vid > VLANID_MAX || vid == 0)
44                 return -EINVAL;
45         return 0;
46 }
47
48 static int find_next_bit(int i, uint32_t x) {
49         int j;
50
51         if (i >= 32)
52                 return -1;
53
54         /* find first bit */
55         if (i < 0)
56                 return BUILTIN_FFS_U32(x);
57
58         /* mask off prior finds to get next */
59         j = __builtin_ffs(x >> i);
60         return j ? j + i : 0;
61 }
62
63 static int append_vlan_info_data(Link *const link, sd_netlink_message *req, uint16_t pvid, const uint32_t *br_vid_bitmap, const uint32_t *br_untagged_bitmap) {
64         struct bridge_vlan_info br_vlan;
65         int i, j, k, r, done, cnt;
66         uint16_t begin, end;
67         bool untagged = false;
68
69         assert(link);
70         assert(req);
71         assert(br_vid_bitmap);
72         assert(br_untagged_bitmap);
73
74         i = cnt = -1;
75
76         begin = end = UINT16_MAX;
77         for (k = 0; k < BRIDGE_VLAN_BITMAP_LEN; k++) {
78                 unsigned base_bit;
79                 uint32_t vid_map = br_vid_bitmap[k];
80                 uint32_t untagged_map = br_untagged_bitmap[k];
81
82                 base_bit = k * 32;
83                 i = -1;
84                 done = 0;
85                 do {
86                         j = find_next_bit(i, vid_map);
87                         if (j > 0) {
88                                 /* first hit of any bit */
89                                 if (begin == UINT16_MAX && end == UINT16_MAX) {
90                                         begin = end = j - 1 + base_bit;
91                                         untagged = is_bit_set(j - 1, untagged_map);
92                                         goto next;
93                                 }
94
95                                 /* this bit is a continuation of prior bits */
96                                 if (j - 2 + base_bit == end && untagged == is_bit_set(j - 1, untagged_map) && (uint16_t)j - 1 + base_bit != pvid && (uint16_t)begin != pvid) {
97                                         end++;
98                                         goto next;
99                                 }
100                         } else
101                                 done = 1;
102
103                         if (begin != UINT16_MAX) {
104                                 cnt++;
105                                 if (done && k < BRIDGE_VLAN_BITMAP_LEN - 1)
106                                         break;
107
108                                 br_vlan.flags = 0;
109                                 if (untagged)
110                                         br_vlan.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
111
112                                 if (begin == end) {
113                                         br_vlan.vid = begin;
114
115                                         if (begin == pvid)
116                                                 br_vlan.flags |= BRIDGE_VLAN_INFO_PVID;
117
118                                         r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan));
119                                         if (r < 0)
120                                                 return log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m");
121                                 } else {
122                                         br_vlan.vid = begin;
123                                         br_vlan.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN;
124
125                                         r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan));
126                                         if (r < 0)
127                                                 return log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m");
128
129                                         br_vlan.vid = end;
130                                         br_vlan.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN;
131                                         br_vlan.flags |= BRIDGE_VLAN_INFO_RANGE_END;
132
133                                         r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan));
134                                         if (r < 0)
135                                                 return log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m");
136                                 }
137
138                                 if (done)
139                                         break;
140                         }
141                         if (j > 0) {
142                                 begin = end = j - 1 + base_bit;
143                                 untagged = is_bit_set(j - 1, untagged_map);
144                         }
145
146                 next:
147                         i = j;
148                 } while(!done);
149         }
150         if (!cnt)
151                 return -EINVAL;
152
153         return cnt;
154 }
155
156 static int set_brvlan_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) {
157         Link *link = userdata;
158         int r;
159
160         assert(link);
161
162         r = sd_netlink_message_get_errno(m);
163         if (r < 0 && r != -EEXIST)
164                 log_link_error_errno(link, r, "Could not add VLAN to bridge port: %m");
165
166         return 1;
167 }
168
169 int br_vlan_configure(Link *link, uint16_t pvid, uint32_t *br_vid_bitmap, uint32_t *br_untagged_bitmap) {
170         _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
171         int r;
172         uint16_t flags;
173         sd_netlink *rtnl;
174
175         assert(link);
176         assert(link->manager);
177         assert(br_vid_bitmap);
178         assert(br_untagged_bitmap);
179         assert(link->network);
180
181         /* pvid might not be in br_vid_bitmap yet */
182         if (pvid)
183                 set_bit(pvid, br_vid_bitmap);
184
185         rtnl = link->manager->rtnl;
186
187         /* create new RTM message */
188         r = sd_rtnl_message_new_link(rtnl, &req, RTM_SETLINK, link->ifindex);
189         if (r < 0)
190                 return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
191
192         r = sd_rtnl_message_link_set_family(req, PF_BRIDGE);
193         if (r < 0)
194                 return log_link_error_errno(link, r, "Could not set message family: %m");
195
196         r = sd_netlink_message_open_container(req, IFLA_AF_SPEC);
197         if (r < 0)
198                 return log_link_error_errno(link, r, "Could not open IFLA_AF_SPEC container: %m");
199
200         /* master needs flag self */
201         if (!link->network->bridge) {
202                 flags = BRIDGE_FLAGS_SELF;
203                 sd_netlink_message_append_data(req, IFLA_BRIDGE_FLAGS, &flags, sizeof(uint16_t));
204         }
205
206         /* add vlan info */
207         r = append_vlan_info_data(link, req, pvid, br_vid_bitmap, br_untagged_bitmap);
208         if (r < 0)
209                 return log_link_error_errno(link, r, "Could not append VLANs: %m");
210
211         r = sd_netlink_message_close_container(req);
212         if (r < 0)
213                 return log_link_error_errno(link, r, "Could not close IFLA_AF_SPEC container: %m");
214
215         /* send message to the kernel */
216         r = sd_netlink_call_async(rtnl, req, set_brvlan_handler, link, 0, NULL);
217         if (r < 0)
218                 return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
219
220         return 0;
221 }
222
223 static int parse_vid_range(const char *rvalue, uint16_t *vid, uint16_t *vid_end) {
224         int r;
225         char *p;
226         char *_rvalue = NULL;
227         uint16_t _vid = UINT16_MAX;
228         uint16_t _vid_end = UINT16_MAX;
229
230         assert(rvalue);
231         assert(vid);
232         assert(vid_end);
233
234         _rvalue = strdupa(rvalue);
235         p = strchr(_rvalue, '-');
236         if (p) {
237                 *p = '\0';
238                 p++;
239                 r = parse_vlanid(_rvalue, &_vid);
240                 if (r < 0)
241                         return r;
242
243                 if (!_vid)
244                         return -ERANGE;
245
246                 r = parse_vlanid(p, &_vid_end);
247                 if (r < 0)
248                         return r;
249
250                 if (!_vid_end)
251                         return -ERANGE;
252         } else {
253                 r = parse_vlanid(_rvalue, &_vid);
254                 if (r < 0)
255                         return r;
256
257                 if (!_vid)
258                         return -ERANGE;
259         }
260
261         *vid = _vid;
262         *vid_end = _vid_end;
263         return r;
264 }
265
266 int config_parse_brvlan_vlan(const char *unit, const char *filename,
267                              unsigned line, const char *section,
268                              unsigned section_line, const char *lvalue,
269                              int ltype, const char *rvalue, void *data,
270                              void *userdata) {
271         Network *network = userdata;
272         int r;
273         uint16_t vid, vid_end;
274
275         assert(filename);
276         assert(section);
277         assert(lvalue);
278         assert(rvalue);
279         assert(data);
280
281         r = parse_vid_range(rvalue, &vid, &vid_end);
282         if (r < 0) {
283                 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse VLAN, ignoring: %s", rvalue);
284                 return 0;
285         }
286
287         if (UINT16_MAX == vid_end)
288                 set_bit(vid++, network->br_vid_bitmap);
289         else {
290                 if (vid >= vid_end) {
291                         log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid VLAN range, ignoring %s", rvalue);
292                         return 0;
293                 }
294                 for (; vid <= vid_end; vid++)
295                         set_bit(vid, network->br_vid_bitmap);
296         }
297         return 0;
298 }
299
300 int config_parse_brvlan_untagged(const char *unit, const char *filename,
301                                  unsigned line, const char *section,
302                                  unsigned section_line, const char *lvalue,
303                                  int ltype, const char *rvalue, void *data,
304                                  void *userdata) {
305         Network *network = userdata;
306         int r;
307         uint16_t vid, vid_end;
308
309         assert(filename);
310         assert(section);
311         assert(lvalue);
312         assert(rvalue);
313         assert(data);
314
315         r = parse_vid_range(rvalue, &vid, &vid_end);
316         if (r < 0) {
317                 log_syntax(unit, LOG_ERR, filename, line, r, "Could not parse VLAN: %s", rvalue);
318                 return 0;
319         }
320
321         if (UINT16_MAX == vid_end) {
322                 set_bit(vid, network->br_vid_bitmap);
323                 set_bit(vid, network->br_untagged_bitmap);
324         } else {
325                 if (vid >= vid_end) {
326                         log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid VLAN range, ignoring %s", rvalue);
327                         return 0;
328                 }
329                 for (; vid <= vid_end; vid++) {
330                         set_bit(vid, network->br_vid_bitmap);
331                         set_bit(vid, network->br_untagged_bitmap);
332                 }
333         }
334         return 0;
335 }