/***
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 .
***/
#include
#include
#include
#include "alloc-util.h"
#include "conf-parser.h"
#include "netlink-util.h"
#include "networkd-brvlan.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 & (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;
}