networkd: add support to configure VLAN on bridge ports

This commit is contained in:
Tobias Jungel 2016-06-01 15:18:21 +02:00
parent 6cad256dbe
commit 13b498f967
10 changed files with 466 additions and 1 deletions

View File

@ -5480,6 +5480,8 @@ libnetworkd_core_la_SOURCES = \
src/network/networkd-manager-bus.c \
src/network/networkd-fdb.h \
src/network/networkd-fdb.c \
src/network/networkd-brvlan.h \
src/network/networkd-brvlan.c \
src/network/networkd-address-pool.h \
src/network/networkd-address-pool.c \
src/network/networkd-util.h \

View File

@ -1130,6 +1130,39 @@
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>[BridgeVLAN] Section Options</title>
<para>The <literal>[BridgeVLAN]</literal> section manages the VLAN ID configuration of a bridge port and accepts
the following keys. Specify several <literal>[BridgeVLAN]</literal> sections to configure several VLAN entries.
The <varname>VLANFiltering=</varname> option has to be enabled, see <literal>[Bridge]</literal> section in
<citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
<variablelist class='network-directives'>
<varlistentry>
<term><varname>VLAN=</varname></term>
<listitem>
<para>The VLAN ID allowed on the port. This can be either a single ID or a range M-N. VLAN IDs are valid
from 1 to 4094.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>EgressUntagged=</varname></term>
<listitem>
<para>The VLAN ID specified here will be used to untag frames on egress. Configuring
<varname>EgressUntagged=</varname> implicates the use of <varname>VLAN=</varname> above and will enable the
VLAN ID for ingress as well. This can be either a single ID or a range M-N.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>PVID=</varname></term>
<listitem>
<para>The Port VLAN ID specified here is assigned to all untagged frames at ingress.
<varname>PVID=</varname> can be used only once. Configuring <varname>PVID=</varname> implicates the use of
<varname>VLAN=</varname> above and will enable the VLAN ID for ingress as well.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Example</title>
@ -1174,6 +1207,26 @@ Name=enp2s0
[Network]
Bridge=bridge0</programlisting>
</example>
<example>
<title>/etc/systemd/network/25-bridge-slave-interface-vlan.network</title>
<programlisting>[Match]
Name=enp2s0
[Network]
Bridge=bridge0
[BridgeVLAN]
VLAN=1-32
PVID=42
EgressUntagged=42
[BridgeVLAN]
VLAN=100-200
[BridgeVLAN]
EgressUntagged=300-400</programlisting>
</example>
<example>
<title>/etc/systemd/network/25-ipip.network</title>

View File

@ -89,6 +89,15 @@
#define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq))
#define UNIQ __COUNTER__
/* builtins */
#if __SIZEOF_INT__ == 4
#define BUILTIN_FFS_U32(x) __builtin_ffs(x);
#elif __SIZEOF_LONG__ == 4
#define BUILTIN_FFS_U32(x) __builtin_ffsl(x);
#else
#error "neither int nor long are four bytes long?!?"
#endif
/* Rounds up */
#define ALIGN4(l) (((l) + 3) & ~3)

View File

@ -759,6 +759,14 @@ struct btrfs_ioctl_quota_ctl_args {
#define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1)
#endif
#ifndef BRIDGE_VLAN_INFO_RANGE_BEGIN
#define BRIDGE_VLAN_INFO_RANGE_BEGIN (1<<3) /* VLAN is start of vlan range */
#endif
#ifndef BRIDGE_VLAN_INFO_RANGE_END
#define BRIDGE_VLAN_INFO_RANGE_END (1<<4) /* VLAN is end of vlan range */
#endif
#if !HAVE_DECL_IFLA_BR_VLAN_DEFAULT_PVID
#define IFLA_BR_UNSPEC 0
#define IFLA_BR_FORWARD_DELAY 1

View File

@ -0,0 +1,335 @@
/***
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 inline int is_vid_valid(unsigned vid) {
if (vid > VLANID_MAX || vid == 0)
return -EINVAL;
return 0;
}
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;
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)
return -ERANGE;
r = parse_vlanid(p, &_vid_end);
if (r < 0)
return r;
if (!_vid_end)
return -ERANGE;
} else {
r = parse_vlanid(_rvalue, &_vid);
if (r < 0)
return r;
if (!_vid)
return -ERANGE;
}
*vid = _vid;
*vid_end = _vid_end;
return r;
}
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);
}
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);
}
}
return 0;
}

View File

@ -0,0 +1,29 @@
#pragma once
/***
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 <stdint.h>
typedef struct Link Link;
int br_vlan_configure(Link *link, uint16_t pvid, uint32_t *br_vid_bitmap, uint32_t *br_untagged_bitmap);
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);
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);

View File

@ -1114,6 +1114,16 @@ int link_address_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, void *u
return 1;
}
static int link_set_bridge_vlan(Link *link) {
int r = 0;
r = br_vlan_configure(link, link->network->pvid, link->network->br_vid_bitmap, link->network->br_untagged_bitmap);
if (r < 0)
log_link_error_errno(link, r, "Failed to assign VLANs to bridge port: %m");
return r;
}
static int link_set_bridge_fdb(Link *link) {
FdbEntry *fdb_entry;
int r = 0;
@ -1996,6 +2006,12 @@ static int link_joined(Link *link) {
log_link_error_errno(link, r, "Could not set bridge message: %m");
}
if (link->network->bridge || NETDEV_KIND_BRIDGE == netdev_kind_from_string(link->kind)) {
r = link_set_bridge_vlan(link);
if (r < 0)
log_link_error_errno(link, r, "Could not set bridge vlan: %m");
}
return link_enter_set_addresses(link);
}

View File

@ -4,6 +4,7 @@
#include "networkd.h"
#include "networkd-conf.h"
#include "network-internal.h"
#include "vlan-util.h"
%}
struct ConfigPerfItem;
%null_strings
@ -112,6 +113,9 @@ Bridge.AllowPortToBeRoot, config_parse_bool,
Bridge.UnicastFlood, config_parse_bool, 0, offsetof(Network, unicast_flood)
BridgeFDB.MACAddress, config_parse_fdb_hwaddr, 0, 0
BridgeFDB.VLANId, config_parse_fdb_vlan_id, 0, 0
BridgeVLAN.PVID, config_parse_vlanid, 0, offsetof(Network, pvid)
BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0
BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0
/* backwards compatibility: do not add new entries to this section */
Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local)
DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns)

View File

@ -147,7 +147,8 @@ static int network_load_one(Manager *manager, const char *filename) {
"DHCPServer\0"
"IPv6AcceptRA\0"
"Bridge\0"
"BridgeFDB\0",
"BridgeFDB\0"
"BridgeVLAN\0",
config_item_perf_lookup, network_network_gperf_lookup,
false, false, true, network);
if (r < 0)

View File

@ -28,6 +28,7 @@
#include "resolve-util.h"
#include "networkd-address.h"
#include "networkd-brvlan.h"
#include "networkd-fdb.h"
#include "networkd-lldp-tx.h"
#include "networkd-netdev.h"
@ -37,6 +38,9 @@
#define DHCP_ROUTE_METRIC 1024
#define IPV4LL_ROUTE_METRIC 2048
#define BRIDGE_VLAN_BITMAP_MAX 4096
#define BRIDGE_VLAN_BITMAP_LEN (BRIDGE_VLAN_BITMAP_MAX / 32)
typedef enum DCHPClientIdentifier {
DHCP_CLIENT_ID_MAC,
DHCP_CLIENT_ID_DUID,
@ -146,6 +150,10 @@ struct Network {
bool unicast_flood;
unsigned cost;
uint16_t pvid;
uint32_t br_vid_bitmap[BRIDGE_VLAN_BITMAP_LEN];
uint32_t br_untagged_bitmap[BRIDGE_VLAN_BITMAP_LEN];
AddressFamilyBoolean ip_forward;
bool ip_masquerade;