Commit 23f4eacd authored by David S. Miller's avatar David S. Miller

Merge branch 'net-bridge-add-per-vlan-state-option'

Nikolay Aleksandrov says:

====================
net: bridge: add per-vlan state option

This set adds the first per-vlan option - state, which uses the new vlan
infrastructure that was recently added. It gives us forwarding control on
per-vlan basis. The first 3 patches prepare the vlan code to support option
dumping and modification. We still compress vlan ranges which have equal
options, each new option will have to add its own equality check to
br_vlan_opts_eq(). The vlans are created in forwarding state by default to
be backwards compatible and vlan state is considered only when the port
state is forwarding (more info in patch 4).
I'll send the selftest for the vlan state with the iproute2 patch-set.

v2: patch 3: do full (all-vlan) notification only on vlan
    create/delete, otherwise use the per-vlan notifications only,
    rework how option change ranges are detected, add more verbose error
    messages when setting options and add checks if a vlan should be used.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 9bbc8be2 a580c76d
......@@ -130,6 +130,7 @@ enum {
#define BRIDGE_VLAN_INFO_RANGE_BEGIN (1<<3) /* VLAN is start of vlan range */
#define BRIDGE_VLAN_INFO_RANGE_END (1<<4) /* VLAN is end of vlan range */
#define BRIDGE_VLAN_INFO_BRENTRY (1<<5) /* Global bridge VLAN entry */
#define BRIDGE_VLAN_INFO_ONLY_OPTS (1<<6) /* Skip create/delete/flags */
struct bridge_vlan_info {
__u16 flags;
......@@ -190,6 +191,7 @@ enum {
BRIDGE_VLANDB_ENTRY_UNSPEC,
BRIDGE_VLANDB_ENTRY_INFO,
BRIDGE_VLANDB_ENTRY_RANGE,
BRIDGE_VLANDB_ENTRY_STATE,
__BRIDGE_VLANDB_ENTRY_MAX,
};
#define BRIDGE_VLANDB_ENTRY_MAX (__BRIDGE_VLANDB_ENTRY_MAX - 1)
......
......@@ -20,7 +20,7 @@ obj-$(CONFIG_BRIDGE_NETFILTER) += br_netfilter.o
bridge-$(CONFIG_BRIDGE_IGMP_SNOOPING) += br_multicast.o br_mdb.o
bridge-$(CONFIG_BRIDGE_VLAN_FILTERING) += br_vlan.o br_vlan_tunnel.o
bridge-$(CONFIG_BRIDGE_VLAN_FILTERING) += br_vlan.o br_vlan_tunnel.o br_vlan_options.o
bridge-$(CONFIG_NET_SWITCHDEV) += br_switchdev.o
......
......@@ -32,6 +32,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
struct net_bridge_mdb_entry *mdst;
struct pcpu_sw_netstats *brstats = this_cpu_ptr(br->stats);
const struct nf_br_ops *nf_ops;
u8 state = BR_STATE_FORWARDING;
const unsigned char *dest;
struct ethhdr *eth;
u16 vid = 0;
......@@ -56,7 +57,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
eth = eth_hdr(skb);
skb_pull(skb, ETH_HLEN);
if (!br_allowed_ingress(br, br_vlan_group_rcu(br), skb, &vid))
if (!br_allowed_ingress(br, br_vlan_group_rcu(br), skb, &vid, &state))
goto out;
if (IS_ENABLED(CONFIG_INET) &&
......
......@@ -25,7 +25,7 @@ static inline int should_deliver(const struct net_bridge_port *p,
vg = nbp_vlan_group_rcu(p);
return ((p->flags & BR_HAIRPIN_MODE) || skb->dev != p->dev) &&
br_allowed_egress(vg, skb) && p->state == BR_STATE_FORWARDING &&
p->state == BR_STATE_FORWARDING && br_allowed_egress(vg, skb) &&
nbp_switchdev_allowed_egress(p, skb) &&
!br_skb_isolated(p, skb);
}
......
......@@ -76,11 +76,14 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
bool local_rcv, mcast_hit = false;
struct net_bridge *br;
u16 vid = 0;
u8 state;
if (!p || p->state == BR_STATE_DISABLED)
goto drop;
if (!br_allowed_ingress(p->br, nbp_vlan_group_rcu(p), skb, &vid))
state = p->state;
if (!br_allowed_ingress(p->br, nbp_vlan_group_rcu(p), skb, &vid,
&state))
goto out;
nbp_switchdev_frame_mark(p, skb);
......@@ -103,7 +106,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
}
}
if (p->state == BR_STATE_LEARNING)
if (state == BR_STATE_LEARNING)
goto drop;
BR_INPUT_SKB_CB(skb)->brdev = br->dev;
......
......@@ -113,6 +113,7 @@ enum {
* @vid: VLAN id
* @flags: bridge vlan flags
* @priv_flags: private (in-kernel) bridge vlan flags
* @state: STP state (e.g. blocking, learning, forwarding)
* @stats: per-cpu VLAN statistics
* @br: if MASTER flag set, this points to a bridge struct
* @port: if MASTER flag unset, this points to a port struct
......@@ -133,6 +134,7 @@ struct net_bridge_vlan {
u16 vid;
u16 flags;
u16 priv_flags;
u8 state;
struct br_vlan_stats __percpu *stats;
union {
struct net_bridge *br;
......@@ -157,6 +159,7 @@ struct net_bridge_vlan {
* @vlan_list: sorted VLAN entry list
* @num_vlans: number of total VLAN entries
* @pvid: PVID VLAN id
* @pvid_state: PVID's STP state (e.g. forwarding, learning, blocking)
*
* IMPORTANT: Be careful when checking if there're VLAN entries using list
* primitives because the bridge can have entries in its list which
......@@ -170,6 +173,7 @@ struct net_bridge_vlan_group {
struct list_head vlan_list;
u16 num_vlans;
u16 pvid;
u8 pvid_state;
};
/* bridge fdb flags */
......@@ -935,7 +939,7 @@ static inline int br_multicast_igmp_type(const struct sk_buff *skb)
#ifdef CONFIG_BRIDGE_VLAN_FILTERING
bool br_allowed_ingress(const struct net_bridge *br,
struct net_bridge_vlan_group *vg, struct sk_buff *skb,
u16 *vid);
u16 *vid, u8 *state);
bool br_allowed_egress(struct net_bridge_vlan_group *vg,
const struct sk_buff *skb);
bool br_should_learn(struct net_bridge_port *p, struct sk_buff *skb, u16 *vid);
......@@ -976,6 +980,8 @@ void br_vlan_notify(const struct net_bridge *br,
const struct net_bridge_port *p,
u16 vid, u16 vid_range,
int cmd);
bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
const struct net_bridge_vlan *range_end);
static inline struct net_bridge_vlan_group *br_vlan_group(
const struct net_bridge *br)
......@@ -1035,7 +1041,7 @@ static inline u16 br_vlan_flags(const struct net_bridge_vlan *v, u16 pvid)
static inline bool br_allowed_ingress(const struct net_bridge *br,
struct net_bridge_vlan_group *vg,
struct sk_buff *skb,
u16 *vid)
u16 *vid, u8 *state)
{
return true;
}
......@@ -1191,6 +1197,55 @@ static inline void br_vlan_notify(const struct net_bridge *br,
}
#endif
/* br_vlan_options.c */
#ifdef CONFIG_BRIDGE_VLAN_FILTERING
bool br_vlan_opts_eq(const struct net_bridge_vlan *v1,
const struct net_bridge_vlan *v2);
bool br_vlan_opts_fill(struct sk_buff *skb, const struct net_bridge_vlan *v);
size_t br_vlan_opts_nl_size(void);
int br_vlan_process_options(const struct net_bridge *br,
const struct net_bridge_port *p,
struct net_bridge_vlan *range_start,
struct net_bridge_vlan *range_end,
struct nlattr **tb,
struct netlink_ext_ack *extack);
/* vlan state manipulation helpers using *_ONCE to annotate lock-free access */
static inline u8 br_vlan_get_state(const struct net_bridge_vlan *v)
{
return READ_ONCE(v->state);
}
static inline void br_vlan_set_state(struct net_bridge_vlan *v, u8 state)
{
WRITE_ONCE(v->state, state);
}
static inline u8 br_vlan_get_pvid_state(const struct net_bridge_vlan_group *vg)
{
return READ_ONCE(vg->pvid_state);
}
static inline void br_vlan_set_pvid_state(struct net_bridge_vlan_group *vg,
u8 state)
{
WRITE_ONCE(vg->pvid_state, state);
}
/* learn_allow is true at ingress and false at egress */
static inline bool br_vlan_state_allowed(u8 state, bool learn_allow)
{
switch (state) {
case BR_STATE_LEARNING:
return learn_allow;
case BR_STATE_FORWARDING:
return true;
default:
return false;
}
}
#endif
struct nf_br_ops {
int (*br_dev_xmit_hook)(struct sk_buff *skb);
};
......
......@@ -34,13 +34,15 @@ static struct net_bridge_vlan *br_vlan_lookup(struct rhashtable *tbl, u16 vid)
return rhashtable_lookup_fast(tbl, &vid, br_vlan_rht_params);
}
static bool __vlan_add_pvid(struct net_bridge_vlan_group *vg, u16 vid)
static bool __vlan_add_pvid(struct net_bridge_vlan_group *vg,
const struct net_bridge_vlan *v)
{
if (vg->pvid == vid)
if (vg->pvid == v->vid)
return false;
smp_wmb();
vg->pvid = vid;
br_vlan_set_pvid_state(vg, v->state);
vg->pvid = v->vid;
return true;
}
......@@ -69,7 +71,7 @@ static bool __vlan_add_flags(struct net_bridge_vlan *v, u16 flags)
vg = nbp_vlan_group(v->port);
if (flags & BRIDGE_VLAN_INFO_PVID)
ret = __vlan_add_pvid(vg, v->vid);
ret = __vlan_add_pvid(vg, v);
else
ret = __vlan_delete_pvid(vg, v->vid);
......@@ -293,6 +295,9 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
vg->num_vlans++;
}
/* set the state before publishing */
v->state = BR_STATE_FORWARDING;
err = rhashtable_lookup_insert_fast(&vg->vlan_hash, &v->vnode,
br_vlan_rht_params);
if (err)
......@@ -466,7 +471,8 @@ struct sk_buff *br_handle_vlan(struct net_bridge *br,
/* Called under RCU */
static bool __allowed_ingress(const struct net_bridge *br,
struct net_bridge_vlan_group *vg,
struct sk_buff *skb, u16 *vid)
struct sk_buff *skb, u16 *vid,
u8 *state)
{
struct br_vlan_stats *stats;
struct net_bridge_vlan *v;
......@@ -532,13 +538,25 @@ static bool __allowed_ingress(const struct net_bridge *br,
skb->vlan_tci |= pvid;
/* if stats are disabled we can avoid the lookup */
if (!br_opt_get(br, BROPT_VLAN_STATS_ENABLED))
if (!br_opt_get(br, BROPT_VLAN_STATS_ENABLED)) {
if (*state == BR_STATE_FORWARDING) {
*state = br_vlan_get_pvid_state(vg);
return br_vlan_state_allowed(*state, true);
} else {
return true;
}
}
}
v = br_vlan_find(vg, *vid);
if (!v || !br_vlan_should_use(v))
goto drop;
if (*state == BR_STATE_FORWARDING) {
*state = br_vlan_get_state(v);
if (!br_vlan_state_allowed(*state, true))
goto drop;
}
if (br_opt_get(br, BROPT_VLAN_STATS_ENABLED)) {
stats = this_cpu_ptr(v->stats);
u64_stats_update_begin(&stats->syncp);
......@@ -556,7 +574,7 @@ static bool __allowed_ingress(const struct net_bridge *br,
bool br_allowed_ingress(const struct net_bridge *br,
struct net_bridge_vlan_group *vg, struct sk_buff *skb,
u16 *vid)
u16 *vid, u8 *state)
{
/* If VLAN filtering is disabled on the bridge, all packets are
* permitted.
......@@ -566,7 +584,7 @@ bool br_allowed_ingress(const struct net_bridge *br,
return true;
}
return __allowed_ingress(br, vg, skb, vid);
return __allowed_ingress(br, vg, skb, vid, state);
}
/* Called under RCU. */
......@@ -582,7 +600,8 @@ bool br_allowed_egress(struct net_bridge_vlan_group *vg,
br_vlan_get_tag(skb, &vid);
v = br_vlan_find(vg, vid);
if (v && br_vlan_should_use(v))
if (v && br_vlan_should_use(v) &&
br_vlan_state_allowed(br_vlan_get_state(v), false))
return true;
return false;
......@@ -593,6 +612,7 @@ bool br_should_learn(struct net_bridge_port *p, struct sk_buff *skb, u16 *vid)
{
struct net_bridge_vlan_group *vg;
struct net_bridge *br = p->br;
struct net_bridge_vlan *v;
/* If filtering was disabled at input, let it pass. */
if (!br_opt_get(br, BROPT_VLAN_ENABLED))
......@@ -607,13 +627,15 @@ bool br_should_learn(struct net_bridge_port *p, struct sk_buff *skb, u16 *vid)
if (!*vid) {
*vid = br_get_pvid(vg);
if (!*vid)
if (!*vid ||
!br_vlan_state_allowed(br_vlan_get_pvid_state(vg), true))
return false;
return true;
}
if (br_vlan_find(vg, *vid))
v = br_vlan_find(vg, *vid);
if (v && br_vlan_state_allowed(br_vlan_get_state(v), true))
return true;
return false;
......@@ -1547,7 +1569,9 @@ void br_vlan_port_event(struct net_bridge_port *p, unsigned long event)
}
}
/* v_opts is used to dump the options which must be equal in the whole range */
static bool br_vlan_fill_vids(struct sk_buff *skb, u16 vid, u16 vid_range,
const struct net_bridge_vlan *v_opts,
u16 flags)
{
struct bridge_vlan_info info;
......@@ -1572,6 +1596,9 @@ static bool br_vlan_fill_vids(struct sk_buff *skb, u16 vid, u16 vid_range,
nla_put_u16(skb, BRIDGE_VLANDB_ENTRY_RANGE, vid_range))
goto out_err;
if (v_opts && !br_vlan_opts_fill(skb, v_opts))
goto out_err;
nla_nest_end(skb, nest);
return true;
......@@ -1586,7 +1613,8 @@ static size_t rtnl_vlan_nlmsg_size(void)
return NLMSG_ALIGN(sizeof(struct br_vlan_msg))
+ nla_total_size(0) /* BRIDGE_VLANDB_ENTRY */
+ nla_total_size(sizeof(u16)) /* BRIDGE_VLANDB_ENTRY_RANGE */
+ nla_total_size(sizeof(struct bridge_vlan_info)); /* BRIDGE_VLANDB_ENTRY_INFO */
+ nla_total_size(sizeof(struct bridge_vlan_info)) /* BRIDGE_VLANDB_ENTRY_INFO */
+ br_vlan_opts_nl_size(); /* bridge vlan options */
}
void br_vlan_notify(const struct net_bridge *br,
......@@ -1595,7 +1623,7 @@ void br_vlan_notify(const struct net_bridge *br,
int cmd)
{
struct net_bridge_vlan_group *vg;
struct net_bridge_vlan *v;
struct net_bridge_vlan *v = NULL;
struct br_vlan_msg *bvm;
struct nlmsghdr *nlh;
struct sk_buff *skb;
......@@ -1647,7 +1675,7 @@ void br_vlan_notify(const struct net_bridge *br,
goto out_kfree;
}
if (!br_vlan_fill_vids(skb, vid, vid_range, flags))
if (!br_vlan_fill_vids(skb, vid, vid_range, v, flags))
goto out_err;
nlmsg_end(skb, nlh);
......@@ -1661,11 +1689,12 @@ void br_vlan_notify(const struct net_bridge *br,
}
/* check if v_curr can enter a range ending in range_end */
static bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
const struct net_bridge_vlan *range_end)
{
return v_curr->vid - range_end->vid == 1 &&
range_end->flags == v_curr->flags;
range_end->flags == v_curr->flags &&
br_vlan_opts_eq(v_curr, range_end);
}
static int br_vlan_dump_dev(const struct net_device *dev,
......@@ -1729,7 +1758,8 @@ static int br_vlan_dump_dev(const struct net_device *dev,
u16 flags = br_vlan_flags(range_start, pvid);
if (!br_vlan_fill_vids(skb, range_start->vid,
range_end->vid, flags)) {
range_end->vid, range_start,
flags)) {
err = -EMSGSIZE;
break;
}
......@@ -1748,7 +1778,7 @@ static int br_vlan_dump_dev(const struct net_device *dev,
*/
if (!err && range_start &&
!br_vlan_fill_vids(skb, range_start->vid, range_end->vid,
br_vlan_flags(range_start, pvid)))
range_start, br_vlan_flags(range_start, pvid)))
err = -EMSGSIZE;
cb->args[1] = err ? idx : 0;
......@@ -1808,6 +1838,7 @@ static const struct nla_policy br_vlan_db_policy[BRIDGE_VLANDB_ENTRY_MAX + 1] =
[BRIDGE_VLANDB_ENTRY_INFO] = { .type = NLA_EXACT_LEN,
.len = sizeof(struct bridge_vlan_info) },
[BRIDGE_VLANDB_ENTRY_RANGE] = { .type = NLA_U16 },
[BRIDGE_VLANDB_ENTRY_STATE] = { .type = NLA_U8 },
};
static int br_vlan_rtm_process_one(struct net_device *dev,
......@@ -1816,11 +1847,11 @@ static int br_vlan_rtm_process_one(struct net_device *dev,
{
struct bridge_vlan_info *vinfo, vrange_end, *vinfo_last = NULL;
struct nlattr *tb[BRIDGE_VLANDB_ENTRY_MAX + 1];
bool changed = false, skip_processing = false;
struct net_bridge_vlan_group *vg;
struct net_bridge_port *p = NULL;
int err = 0, cmdmap = 0;
struct net_bridge *br;
bool changed = false;
if (netif_is_bridge_master(dev)) {
br = netdev_priv(dev);
......@@ -1874,17 +1905,44 @@ static int br_vlan_rtm_process_one(struct net_device *dev,
switch (cmd) {
case RTM_NEWVLAN:
cmdmap = RTM_SETLINK;
skip_processing = !!(vinfo->flags & BRIDGE_VLAN_INFO_ONLY_OPTS);
break;
case RTM_DELVLAN:
cmdmap = RTM_DELLINK;
break;
}
err = br_process_vlan_info(br, p, cmdmap, vinfo, &vinfo_last, &changed,
extack);
if (!skip_processing) {
struct bridge_vlan_info *tmp_last = vinfo_last;
/* br_process_vlan_info may overwrite vinfo_last */
err = br_process_vlan_info(br, p, cmdmap, vinfo, &tmp_last,
&changed, extack);
/* notify first if anything changed */
if (changed)
br_ifinfo_notify(cmdmap, br, p);
if (err)
return err;
}
/* deal with options */
if (cmd == RTM_NEWVLAN) {
struct net_bridge_vlan *range_start, *range_end;
if (vinfo_last) {
range_start = br_vlan_find(vg, vinfo_last->vid);
range_end = br_vlan_find(vg, vinfo->vid);
} else {
range_start = br_vlan_find(vg, vinfo->vid);
range_end = range_start;
}
err = br_vlan_process_options(br, p, range_start, range_end,
tb, extack);
}
return err;
}
......
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2020, Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
#include <linux/slab.h>
#include "br_private.h"
/* check if the options between two vlans are equal */
bool br_vlan_opts_eq(const struct net_bridge_vlan *v1,
const struct net_bridge_vlan *v2)
{
return v1->state == v2->state;
}
bool br_vlan_opts_fill(struct sk_buff *skb, const struct net_bridge_vlan *v)
{
return !nla_put_u8(skb, BRIDGE_VLANDB_ENTRY_STATE,
br_vlan_get_state(v));
}
size_t br_vlan_opts_nl_size(void)
{
return nla_total_size(sizeof(u8)); /* BRIDGE_VLANDB_ENTRY_STATE */
}
static int br_vlan_modify_state(struct net_bridge_vlan_group *vg,
struct net_bridge_vlan *v,
u8 state,
bool *changed,
struct netlink_ext_ack *extack)
{
struct net_bridge *br;
ASSERT_RTNL();
if (state > BR_STATE_BLOCKING) {
NL_SET_ERR_MSG_MOD(extack, "Invalid vlan state");
return -EINVAL;
}
if (br_vlan_is_brentry(v))
br = v->br;
else
br = v->port->br;
if (br->stp_enabled == BR_KERNEL_STP) {
NL_SET_ERR_MSG_MOD(extack, "Can't modify vlan state when using kernel STP");
return -EBUSY;
}
if (v->state == state)
return 0;
if (v->vid == br_get_pvid(vg))
br_vlan_set_pvid_state(vg, state);
br_vlan_set_state(v, state);
*changed = true;
return 0;
}
static int br_vlan_process_one_opts(const struct net_bridge *br,
const struct net_bridge_port *p,
struct net_bridge_vlan_group *vg,
struct net_bridge_vlan *v,
struct nlattr **tb,
bool *changed,
struct netlink_ext_ack *extack)
{
int err;
*changed = false;
if (tb[BRIDGE_VLANDB_ENTRY_STATE]) {
u8 state = nla_get_u8(tb[BRIDGE_VLANDB_ENTRY_STATE]);
err = br_vlan_modify_state(vg, v, state, changed, extack);
if (err)
return err;
}
return 0;
}
int br_vlan_process_options(const struct net_bridge *br,
const struct net_bridge_port *p,
struct net_bridge_vlan *range_start,
struct net_bridge_vlan *range_end,
struct nlattr **tb,
struct netlink_ext_ack *extack)
{
struct net_bridge_vlan *v, *curr_start = NULL, *curr_end = NULL;
struct net_bridge_vlan_group *vg;
int vid, err = 0;
u16 pvid;
if (p)
vg = nbp_vlan_group(p);
else
vg = br_vlan_group(br);
if (!range_start || !br_vlan_should_use(range_start)) {
NL_SET_ERR_MSG_MOD(extack, "Vlan range start doesn't exist, can't process options");
return -ENOENT;
}
if (!range_end || !br_vlan_should_use(range_end)) {
NL_SET_ERR_MSG_MOD(extack, "Vlan range end doesn't exist, can't process options");
return -ENOENT;
}
pvid = br_get_pvid(vg);
for (vid = range_start->vid; vid <= range_end->vid; vid++) {
bool changed = false;
v = br_vlan_find(vg, vid);
if (!v || !br_vlan_should_use(v)) {
NL_SET_ERR_MSG_MOD(extack, "Vlan in range doesn't exist, can't process options");
err = -ENOENT;
break;
}
err = br_vlan_process_one_opts(br, p, vg, v, tb, &changed,
extack);
if (err)
break;
if (changed) {
/* vlan options changed, check for range */
if (!curr_start) {
curr_start = v;
curr_end = v;
continue;
}
if (v->vid == pvid ||
!br_vlan_can_enter_range(v, curr_end)) {
br_vlan_notify(br, p, curr_start->vid,
curr_end->vid, RTM_NEWVLAN);
curr_start = v;
}
curr_end = v;
} else {
/* nothing changed and nothing to notify yet */
if (!curr_start)
continue;
br_vlan_notify(br, p, curr_start->vid, curr_end->vid,
RTM_NEWVLAN);
curr_start = NULL;
curr_end = NULL;
}
}
if (curr_start)
br_vlan_notify(br, p, curr_start->vid, curr_end->vid,
RTM_NEWVLAN);
return err;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment