/*** This file is part of systemd. Copyright (C) 2016 BISDN GmbH. All rights reserved. systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ #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-brvlan.h" #include "networkd.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 & (1 << bit); } static inline void set_bit(unsigned nr, uint32_t *addr) { if (nr < BRIDGE_VLAN_BITMAP_MAX) addr[nr / 32] |= (((uint32_t) 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; } 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) { struct bridge_vlan_info br_vlan; int i, j, k, r, done, cnt; uint16_t begin, end; bool untagged = false; assert(link); assert(req); assert(br_vid_bitmap); assert(br_untagged_bitmap); i = cnt = -1; begin = end = UINT16_MAX; for (k = 0; k < BRIDGE_VLAN_BITMAP_LEN; k++) { unsigned base_bit; uint32_t vid_map = br_vid_bitmap[k]; uint32_t untagged_map = br_untagged_bitmap[k]; base_bit = k * 32; i = -1; done = 0; do { 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 = 1; 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 log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m"); } 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 log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m"); 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 log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m"); } 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); } if (!cnt) return -EINVAL; return cnt; } static int set_brvlan_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { Link *link = userdata; int r; assert(link); r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) log_link_error_errno(link, r, "Could not add VLAN to bridge port: %m"); return 1; } int br_vlan_configure(Link *link, uint16_t pvid, uint32_t *br_vid_bitmap, uint32_t *br_untagged_bitmap) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; int r; uint16_t flags; sd_netlink *rtnl; assert(link); assert(link->manager); assert(br_vid_bitmap); assert(br_untagged_bitmap); assert(link->network); /* pvid might not be in br_vid_bitmap yet */ if (pvid) set_bit(pvid, br_vid_bitmap); rtnl = link->manager->rtnl; /* create new RTM message */ r = sd_rtnl_message_new_link(rtnl, &req, RTM_SETLINK, link->ifindex); if (r < 0) return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); r = sd_rtnl_message_link_set_family(req, PF_BRIDGE); if (r < 0) return log_link_error_errno(link, r, "Could not set message family: %m"); r = sd_netlink_message_open_container(req, IFLA_AF_SPEC); if (r < 0) return log_link_error_errno(link, r, "Could not open IFLA_AF_SPEC container: %m"); /* master needs flag self */ if (!link->network->bridge) { flags = BRIDGE_FLAGS_SELF; sd_netlink_message_append_data(req, IFLA_BRIDGE_FLAGS, &flags, sizeof(uint16_t)); } /* add vlan info */ r = append_vlan_info_data(link, req, pvid, br_vid_bitmap, br_untagged_bitmap); if (r < 0) return log_link_error_errno(link, r, "Could not append VLANs: %m"); r = sd_netlink_message_close_container(req); if (r < 0) return log_link_error_errno(link, r, "Could not close IFLA_AF_SPEC container: %m"); /* send message to the kernel */ r = sd_netlink_call_async(rtnl, req, set_brvlan_handler, link, 0, NULL); if (r < 0) return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); return 0; } static int parse_vid_range(const char *rvalue, uint16_t *vid, uint16_t *vid_end) { int r; char *p; char *_rvalue = NULL; uint16_t _vid = UINT16_MAX; uint16_t _vid_end = UINT16_MAX; assert(rvalue); assert(vid); assert(vid_end); _rvalue = strdupa(rvalue); p = strchr(_rvalue, '-'); if (p) { *p = '\0'; p++; r = parse_vlanid(_rvalue, &_vid); if (r < 0) return r; if (_vid == 0) return -ERANGE; r = parse_vlanid(p, &_vid_end); if (r < 0) return r; if (_vid_end == 0) return -ERANGE; } else { r = parse_vlanid(_rvalue, &_vid); if (r < 0) return r; if (_vid == 0) return -ERANGE; } *vid = _vid; *vid_end = _vid_end; return r; } 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; int r; uint16_t pvid; 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; int r; uint16_t vid, vid_end; 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_ERR, filename, line, r, "Failed to parse VLAN, ignoring: %s", rvalue); return 0; } if (UINT16_MAX == vid_end) set_bit(vid++, network->br_vid_bitmap); else { if (vid >= vid_end) { log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid VLAN range, 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; int r; uint16_t vid, vid_end; 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_ERR, filename, line, r, "Could not parse VLAN: %s", rvalue); return 0; } if (UINT16_MAX == vid_end) { set_bit(vid, network->br_vid_bitmap); set_bit(vid, network->br_untagged_bitmap); } else { if (vid >= vid_end) { log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid VLAN range, ignoring %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; }