diff options
Diffstat (limited to 'src/network/networkd-bridge-vlan.c')
-rw-r--r-- | src/network/networkd-bridge-vlan.c | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/src/network/networkd-bridge-vlan.c b/src/network/networkd-bridge-vlan.c new file mode 100644 index 0000000..36e3610 --- /dev/null +++ b/src/network/networkd-bridge-vlan.c @@ -0,0 +1,249 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2016 BISDN GmbH. All rights reserved. +***/ + +#include <netinet/in.h> +#include <linux/if_bridge.h> +#include <stdbool.h> + +#include "alloc-util.h" +#include "conf-parser.h" +#include "netlink-util.h" +#include "networkd-bridge-vlan.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "parse-util.h" +#include "vlan-util.h" + +static bool is_bit_set(unsigned bit, uint32_t scope) { + assert(bit < sizeof(scope)*8); + return scope & (UINT32_C(1) << bit); +} + +static void set_bit(unsigned nr, uint32_t *addr) { + if (nr < BRIDGE_VLAN_BITMAP_MAX) + addr[nr / 32] |= (UINT32_C(1) << (nr % 32)); +} + +static int find_next_bit(int i, uint32_t x) { + int j; + + if (i >= 32) + return -1; + + /* find first bit */ + if (i < 0) + return BUILTIN_FFS_U32(x); + + /* mask off prior finds to get next */ + j = __builtin_ffs(x >> i); + return j ? j + i : 0; +} + +int bridge_vlan_append_info( + const Link *link, + sd_netlink_message *req, + uint16_t pvid, + const uint32_t *br_vid_bitmap, + const uint32_t *br_untagged_bitmap) { + + struct bridge_vlan_info br_vlan; + bool done, untagged = false; + uint16_t begin, end; + int r, cnt; + + assert(link); + assert(req); + assert(br_vid_bitmap); + assert(br_untagged_bitmap); + + cnt = 0; + + begin = end = UINT16_MAX; + for (int k = 0; k < BRIDGE_VLAN_BITMAP_LEN; k++) { + uint32_t untagged_map = br_untagged_bitmap[k]; + uint32_t vid_map = br_vid_bitmap[k]; + unsigned base_bit = k * 32; + int i = -1; + + done = false; + do { + int j = find_next_bit(i, vid_map); + if (j > 0) { + /* first hit of any bit */ + if (begin == UINT16_MAX && end == UINT16_MAX) { + begin = end = j - 1 + base_bit; + untagged = is_bit_set(j - 1, untagged_map); + goto next; + } + + /* this bit is a continuation of prior bits */ + 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) { + end++; + goto next; + } + } else + done = true; + + if (begin != UINT16_MAX) { + cnt++; + if (done && k < BRIDGE_VLAN_BITMAP_LEN - 1) + break; + + br_vlan.flags = 0; + if (untagged) + br_vlan.flags |= BRIDGE_VLAN_INFO_UNTAGGED; + + if (begin == end) { + br_vlan.vid = begin; + + if (begin == pvid) + br_vlan.flags |= BRIDGE_VLAN_INFO_PVID; + + r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan)); + if (r < 0) + return r; + } else { + br_vlan.vid = begin; + br_vlan.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN; + + r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan)); + if (r < 0) + return r; + + br_vlan.vid = end; + br_vlan.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN; + br_vlan.flags |= BRIDGE_VLAN_INFO_RANGE_END; + + r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan)); + if (r < 0) + return r; + } + + if (done) + break; + } + if (j > 0) { + begin = end = j - 1 + base_bit; + untagged = is_bit_set(j - 1, untagged_map); + } + + next: + i = j; + } while (!done); + } + + assert(cnt > 0); + return cnt; +} + +void network_adjust_bridge_vlan(Network *network) { + assert(network); + + if (!network->use_br_vlan) + return; + + /* pvid might not be in br_vid_bitmap yet */ + if (network->pvid) + set_bit(network->pvid, network->br_vid_bitmap); +} + +int config_parse_brvlan_pvid( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + uint16_t pvid; + int r; + + r = parse_vlanid(rvalue, &pvid); + if (r < 0) + return r; + + network->pvid = pvid; + network->use_br_vlan = true; + + return 0; +} + +int config_parse_brvlan_vlan( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + uint16_t vid, vid_end; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_vid_range(rvalue, &vid, &vid_end); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse VLAN, ignoring: %s", rvalue); + return 0; + } + + for (; vid <= vid_end; vid++) + set_bit(vid, network->br_vid_bitmap); + + network->use_br_vlan = true; + return 0; +} + +int config_parse_brvlan_untagged( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Network *network = userdata; + uint16_t vid, vid_end; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_vid_range(rvalue, &vid, &vid_end); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Could not parse VLAN: %s", rvalue); + return 0; + } + + for (; vid <= vid_end; vid++) { + set_bit(vid, network->br_vid_bitmap); + set_bit(vid, network->br_untagged_bitmap); + } + + network->use_br_vlan = true; + return 0; +} |