Skip to content

Commit

Permalink
net: bridge: vlan: add basic option setting support
Browse files Browse the repository at this point in the history
This patch adds support for option modification of single vlans and
ranges. It allows to only modify options, i.e. skip create/delete by
using the BRIDGE_VLAN_INFO_ONLY_OPTS flag. When working with a range
option changes we try to pack the notifications as much as possible.

v2: do full port (all vlans) notification only when creating/deleting
    vlans for compatibility, rework the range detection when changing
    options, add more verbose extack errors and check if a vlan should
    be used (br_vlan_should_use checks)

Signed-off-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Nikolay Aleksandrov authored and David S. Miller committed Jan 24, 2020
1 parent 7a53e71 commit a5d29ae
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 7 deletions.
1 change: 1 addition & 0 deletions include/uapi/linux/if_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions net/bridge/br_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,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)
Expand Down Expand Up @@ -1197,6 +1199,12 @@ 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);
#endif

struct nf_br_ops {
Expand Down
41 changes: 34 additions & 7 deletions net/bridge/br_vlan.c
Original file line number Diff line number Diff line change
Expand Up @@ -1667,8 +1667,8 @@ 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,
const struct net_bridge_vlan *range_end)
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 &&
Expand Down Expand Up @@ -1824,11 +1824,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);
Expand Down Expand Up @@ -1882,16 +1882,43 @@ 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 (changed)
br_ifinfo_notify(cmdmap, br, p);
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;
}
Expand Down
87 changes: 87 additions & 0 deletions net/bridge/br_vlan_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,90 @@ size_t br_vlan_opts_nl_size(void)
{
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)
{
*changed = false;
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;
}

0 comments on commit a5d29ae

Please sign in to comment.