Skip to content

Commit

Permalink
Merge branch 'bridge-add-vlan-notifications-and-rtm-support'
Browse files Browse the repository at this point in the history
Nikolay Aleksandrov says:

====================
net: bridge: add vlan notifications and rtm support

This patch-set is a prerequisite for adding per-vlan options support
because we need to be able to send vlan-only notifications and do larger
vlan netlink dumps. Per-vlan options are needed as we move the control
more to vlans and would like to add per-vlan state (needed for per-vlan
STP and EVPN), per-vlan multicast options and control, and I'm sure
there would be many more per-vlan options coming.
Now we create/delete/dump vlans with the device AF_SPEC attribute which is
fine since we support vlan ranges or use a compact bridge_vlan_info
structure, but that cannot really be extended to support per-vlan options
well. The biggest issue is dumping them - we tried using the af_spec with
a new vlan option attribute but that led to insufficient message size
quickly, also another minor problem with that is we have to dump all vlans
always when notifying which, with options present, can be huge if they have
different options set, so we decided to add new rtm message types
specifically for vlans and register handlers for them and a new bridge vlan
notification nl group for vlan-only notifications.
The new RTM NEW/DEL/GETVLAN types introduced match the current af spec
bridge functionality and in fact use the same helpers.
The new nl format is:
 [BRIDGE_VLANDB_ENTRY]
    [BRIDGE_VLANDB_ENTRY_INFO] - bridge_vlan_info (either 1 vlan or
                                                   range start)
    [BRIDGE_VLANDB_ENTRY_RANGE] - range end

This allows to encapsulate a range in a single attribute and also to
create vlans and immediately set options on all of them with a single
attribute. The GETVLAN dump can span multiple messages and dump all the
necessary information. The vlan-only notifications are sent on
NEW/DELVLAN events or when vlan options change (currently only flags),
we try hard to compress the vlans into ranges in the notifications as
well. When the per-vlan options are added we'll add helpers to check for
option equality between neighbor vlans and will keep compressing them
when possible.

Note patch 02 is not really required, it's just a nice addition to have
human-readable error messages from the different vlan checks.

iproute2 changes and selftests will be sent with the next set which adds
the first per-vlan option - per-vlan state similar to the port state.

v2: changed patch 03 and patch 04 to use nlmsg_parse() in order to
    strictly validate the msg and make sure there are no remaining bytes
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed Jan 15, 2020
2 parents f6310b6 + f545923 commit 4e2fa6b
Show file tree
Hide file tree
Showing 6 changed files with 632 additions and 35 deletions.
29 changes: 29 additions & 0 deletions include/uapi/linux/if_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,35 @@ struct bridge_stp_xstats {
__u64 tx_tcn;
};

/* Bridge vlan RTM header */
struct br_vlan_msg {
__u8 family;
__u8 reserved1;
__u16 reserved2;
__u32 ifindex;
};

/* Bridge vlan RTM attributes
* [BRIDGE_VLANDB_ENTRY] = {
* [BRIDGE_VLANDB_ENTRY_INFO]
* ...
* }
*/
enum {
BRIDGE_VLANDB_UNSPEC,
BRIDGE_VLANDB_ENTRY,
__BRIDGE_VLANDB_MAX,
};
#define BRIDGE_VLANDB_MAX (__BRIDGE_VLANDB_MAX - 1)

enum {
BRIDGE_VLANDB_ENTRY_UNSPEC,
BRIDGE_VLANDB_ENTRY_INFO,
BRIDGE_VLANDB_ENTRY_RANGE,
__BRIDGE_VLANDB_ENTRY_MAX,
};
#define BRIDGE_VLANDB_ENTRY_MAX (__BRIDGE_VLANDB_ENTRY_MAX - 1)

/* Bridge multicast database attributes
* [MDBA_MDB] = {
* [MDBA_MDB_ENTRY] = {
Expand Down
9 changes: 9 additions & 0 deletions include/uapi/linux/rtnetlink.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ enum {
RTM_GETLINKPROP,
#define RTM_GETLINKPROP RTM_GETLINKPROP

RTM_NEWVLAN = 112,
#define RTM_NEWNVLAN RTM_NEWVLAN
RTM_DELVLAN,
#define RTM_DELVLAN RTM_DELVLAN
RTM_GETVLAN,
#define RTM_GETVLAN RTM_GETVLAN

__RTM_MAX,
#define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1)
};
Expand Down Expand Up @@ -723,6 +730,8 @@ enum rtnetlink_groups {
#define RTNLGRP_IPV6_MROUTE_R RTNLGRP_IPV6_MROUTE_R
RTNLGRP_NEXTHOP,
#define RTNLGRP_NEXTHOP RTNLGRP_NEXTHOP
RTNLGRP_BRVLAN,
#define RTNLGRP_BRVLAN RTNLGRP_BRVLAN
__RTNLGRP_MAX
};
#define RTNLGRP_MAX (__RTNLGRP_MAX - 1)
Expand Down
61 changes: 42 additions & 19 deletions net/bridge/br_netlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -561,52 +561,73 @@ static int br_vlan_info(struct net_bridge *br, struct net_bridge_port *p,
return err;
}

static int br_process_vlan_info(struct net_bridge *br,
struct net_bridge_port *p, int cmd,
struct bridge_vlan_info *vinfo_curr,
struct bridge_vlan_info **vinfo_last,
bool *changed,
struct netlink_ext_ack *extack)
int br_process_vlan_info(struct net_bridge *br,
struct net_bridge_port *p, int cmd,
struct bridge_vlan_info *vinfo_curr,
struct bridge_vlan_info **vinfo_last,
bool *changed,
struct netlink_ext_ack *extack)
{
if (!vinfo_curr->vid || vinfo_curr->vid >= VLAN_VID_MASK)
int err, rtm_cmd;

if (!br_vlan_valid_id(vinfo_curr->vid, extack))
return -EINVAL;

/* needed for vlan-only NEWVLAN/DELVLAN notifications */
rtm_cmd = br_afspec_cmd_to_rtm(cmd);

if (vinfo_curr->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) {
/* check if we are already processing a range */
if (*vinfo_last)
if (!br_vlan_valid_range(vinfo_curr, *vinfo_last, extack))
return -EINVAL;
*vinfo_last = vinfo_curr;
/* don't allow range of pvids */
if ((*vinfo_last)->flags & BRIDGE_VLAN_INFO_PVID)
return -EINVAL;
return 0;
}

if (*vinfo_last) {
struct bridge_vlan_info tmp_vinfo;
int v, err;

if (!(vinfo_curr->flags & BRIDGE_VLAN_INFO_RANGE_END))
return -EINVAL;
int v, v_change_start = 0;

if (vinfo_curr->vid <= (*vinfo_last)->vid)
if (!br_vlan_valid_range(vinfo_curr, *vinfo_last, extack))
return -EINVAL;

memcpy(&tmp_vinfo, *vinfo_last,
sizeof(struct bridge_vlan_info));
for (v = (*vinfo_last)->vid; v <= vinfo_curr->vid; v++) {
bool curr_change = false;

tmp_vinfo.vid = v;
err = br_vlan_info(br, p, cmd, &tmp_vinfo, changed,
err = br_vlan_info(br, p, cmd, &tmp_vinfo, &curr_change,
extack);
if (err)
break;
if (curr_change) {
*changed = curr_change;
if (!v_change_start)
v_change_start = v;
} else {
/* nothing to notify yet */
if (!v_change_start)
continue;
br_vlan_notify(br, p, v_change_start,
v - 1, rtm_cmd);
v_change_start = 0;
}
}
/* v_change_start is set only if the last/whole range changed */
if (v_change_start)
br_vlan_notify(br, p, v_change_start,
v - 1, rtm_cmd);

*vinfo_last = NULL;

return err;
}

return br_vlan_info(br, p, cmd, vinfo_curr, changed, extack);
err = br_vlan_info(br, p, cmd, vinfo_curr, changed, extack);
if (*changed)
br_vlan_notify(br, p, vinfo_curr->vid, 0, rtm_cmd);

return err;
}

static int br_afspec(struct net_bridge *br,
Expand Down Expand Up @@ -1664,6 +1685,7 @@ int __init br_netlink_init(void)
int err;

br_mdb_init();
br_vlan_rtnl_init();
rtnl_af_register(&br_af_ops);

err = rtnl_link_register(&br_link_ops);
Expand All @@ -1681,6 +1703,7 @@ int __init br_netlink_init(void)
void br_netlink_fini(void)
{
br_mdb_uninit();
br_vlan_rtnl_uninit();
rtnl_af_unregister(&br_af_ops);
rtnl_link_unregister(&br_link_ops);
}
90 changes: 90 additions & 0 deletions net/bridge/br_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,65 @@ static inline bool nbp_state_should_learn(const struct net_bridge_port *p)
return p->state == BR_STATE_LEARNING || p->state == BR_STATE_FORWARDING;
}

static inline bool br_vlan_valid_id(u16 vid, struct netlink_ext_ack *extack)
{
bool ret = vid > 0 && vid < VLAN_VID_MASK;

if (!ret)
NL_SET_ERR_MSG_MOD(extack, "Vlan id is invalid");

return ret;
}

static inline bool br_vlan_valid_range(const struct bridge_vlan_info *cur,
const struct bridge_vlan_info *last,
struct netlink_ext_ack *extack)
{
/* pvid flag is not allowed in ranges */
if (cur->flags & BRIDGE_VLAN_INFO_PVID) {
NL_SET_ERR_MSG_MOD(extack, "Pvid isn't allowed in a range");
return false;
}

/* when cur is the range end, check if:
* - it has range start flag
* - range ids are invalid (end is equal to or before start)
*/
if (last) {
if (cur->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) {
NL_SET_ERR_MSG_MOD(extack, "Found a new vlan range start while processing one");
return false;
} else if (!(cur->flags & BRIDGE_VLAN_INFO_RANGE_END)) {
NL_SET_ERR_MSG_MOD(extack, "Vlan range end flag is missing");
return false;
} else if (cur->vid <= last->vid) {
NL_SET_ERR_MSG_MOD(extack, "End vlan id is less than or equal to start vlan id");
return false;
}
}

/* check for required range flags */
if (!(cur->flags & (BRIDGE_VLAN_INFO_RANGE_BEGIN |
BRIDGE_VLAN_INFO_RANGE_END))) {
NL_SET_ERR_MSG_MOD(extack, "Both vlan range flags are missing");
return false;
}

return true;
}

static inline int br_afspec_cmd_to_rtm(int cmd)
{
switch (cmd) {
case RTM_SETLINK:
return RTM_NEWVLAN;
case RTM_DELLINK:
return RTM_DELVLAN;
}

return 0;
}

static inline int br_opt_get(const struct net_bridge *br,
enum net_bridge_opts opt)
{
Expand Down Expand Up @@ -911,6 +970,12 @@ void br_vlan_get_stats(const struct net_bridge_vlan *v,
void br_vlan_port_event(struct net_bridge_port *p, unsigned long event);
int br_vlan_bridge_event(struct net_device *dev, unsigned long event,
void *ptr);
void br_vlan_rtnl_init(void);
void br_vlan_rtnl_uninit(void);
void br_vlan_notify(const struct net_bridge *br,
const struct net_bridge_port *p,
u16 vid, u16 vid_range,
int cmd);

static inline struct net_bridge_vlan_group *br_vlan_group(
const struct net_bridge *br)
Expand Down Expand Up @@ -962,6 +1027,10 @@ static inline u16 br_get_pvid(const struct net_bridge_vlan_group *vg)
return vg->pvid;
}

static inline u16 br_vlan_flags(const struct net_bridge_vlan *v, u16 pvid)
{
return v->vid == pvid ? v->flags | BRIDGE_VLAN_INFO_PVID : v->flags;
}
#else
static inline bool br_allowed_ingress(const struct net_bridge *br,
struct net_bridge_vlan_group *vg,
Expand Down Expand Up @@ -1105,6 +1174,21 @@ static inline int br_vlan_bridge_event(struct net_device *dev,
{
return 0;
}

static inline void br_vlan_rtnl_init(void)
{
}

static inline void br_vlan_rtnl_uninit(void)
{
}

static inline void br_vlan_notify(const struct net_bridge *br,
const struct net_bridge_port *p,
u16 vid, u16 vid_range,
int cmd)
{
}
#endif

struct nf_br_ops {
Expand Down Expand Up @@ -1176,6 +1260,12 @@ int br_setlink(struct net_device *dev, struct nlmsghdr *nlmsg, u16 flags,
int br_dellink(struct net_device *dev, struct nlmsghdr *nlmsg, u16 flags);
int br_getlink(struct sk_buff *skb, u32 pid, u32 seq, struct net_device *dev,
u32 filter_mask, int nlflags);
int br_process_vlan_info(struct net_bridge *br,
struct net_bridge_port *p, int cmd,
struct bridge_vlan_info *vinfo_curr,
struct bridge_vlan_info **vinfo_last,
bool *changed,
struct netlink_ext_ack *extack);

#ifdef CONFIG_SYSFS
/* br_sysfs_if.c */
Expand Down
Loading

0 comments on commit 4e2fa6b

Please sign in to comment.