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