Commit 8dcea187 authored by Nikolay Aleksandrov's avatar Nikolay Aleksandrov Committed by David S. Miller
Browse files

net: bridge: vlan: add rtm definitions and dump support



This patch adds vlan rtm definitions:
 - NEWVLAN: to be used for creating vlans, setting options and
   notifications
 - DELVLAN: to be used for deleting vlans
 - GETVLAN: used for dumping vlan information

Dumping vlans which can span multiple messages is added now with basic
information (vid and flags). We use nlmsg_parse() to validate the header
length in order to be able to extend the message with filtering
attributes later.

Signed-off-by: default avatarNikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 8f4cc940
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
@@ -165,6 +165,34 @@ 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_MAX,
};
#define BRIDGE_VLANDB_ENTRY_MAX (__BRIDGE_VLANDB_ENTRY_MAX - 1)

/* Bridge multicast database attributes
 * [MDBA_MDB] = {
 *     [MDBA_MDB_ENTRY] = {
+7 −0
Original line number Diff line number Diff line
@@ -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)
};
+2 −0
Original line number Diff line number Diff line
@@ -1657,6 +1657,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);
@@ -1674,6 +1675,7 @@ out_af:
void br_netlink_fini(void)
{
	br_mdb_uninit();
	br_vlan_rtnl_uninit();
	rtnl_af_unregister(&br_af_ops);
	rtnl_link_unregister(&br_link_ops);
}
+14 −0
Original line number Diff line number Diff line
@@ -958,6 +958,8 @@ 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);

static inline struct net_bridge_vlan_group *br_vlan_group(
					const struct net_bridge *br)
@@ -1009,6 +1011,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,
@@ -1152,6 +1158,14 @@ 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)
{
}
#endif

struct nf_br_ops {
+148 −0
Original line number Diff line number Diff line
@@ -1505,3 +1505,151 @@ void br_vlan_port_event(struct net_bridge_port *p, unsigned long event)
		break;
	}
}

static bool br_vlan_fill_vids(struct sk_buff *skb, u16 vid, u16 flags)
{
	struct bridge_vlan_info info;
	struct nlattr *nest;

	nest = nla_nest_start(skb, BRIDGE_VLANDB_ENTRY);
	if (!nest)
		return false;

	memset(&info, 0, sizeof(info));
	info.vid = vid;
	if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
		info.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
	if (flags & BRIDGE_VLAN_INFO_PVID)
		info.flags |= BRIDGE_VLAN_INFO_PVID;

	if (nla_put(skb, BRIDGE_VLANDB_ENTRY_INFO, sizeof(info), &info))
		goto out_err;

	nla_nest_end(skb, nest);

	return true;

out_err:
	nla_nest_cancel(skb, nest);
	return false;
}

static int br_vlan_dump_dev(const struct net_device *dev,
			    struct sk_buff *skb,
			    struct netlink_callback *cb)
{
	struct net_bridge_vlan_group *vg;
	int idx = 0, s_idx = cb->args[1];
	struct nlmsghdr *nlh = NULL;
	struct net_bridge_vlan *v;
	struct net_bridge_port *p;
	struct br_vlan_msg *bvm;
	struct net_bridge *br;
	int err = 0;
	u16 pvid;

	if (!netif_is_bridge_master(dev) && !netif_is_bridge_port(dev))
		return -EINVAL;

	if (netif_is_bridge_master(dev)) {
		br = netdev_priv(dev);
		vg = br_vlan_group_rcu(br);
		p = NULL;
	} else {
		p = br_port_get_rcu(dev);
		if (WARN_ON(!p))
			return -EINVAL;
		vg = nbp_vlan_group_rcu(p);
		br = p->br;
	}

	if (!vg)
		return 0;

	nlh = nlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
			RTM_NEWVLAN, sizeof(*bvm), NLM_F_MULTI);
	if (!nlh)
		return -EMSGSIZE;
	bvm = nlmsg_data(nlh);
	memset(bvm, 0, sizeof(*bvm));
	bvm->family = PF_BRIDGE;
	bvm->ifindex = dev->ifindex;
	pvid = br_get_pvid(vg);

	list_for_each_entry_rcu(v, &vg->vlan_list, vlist) {
		if (!br_vlan_should_use(v))
			continue;
		if (idx < s_idx)
			goto skip;
		if (!br_vlan_fill_vids(skb, v->vid, br_vlan_flags(v, pvid))) {
			err = -EMSGSIZE;
			break;
		}
skip:
		idx++;
	}
	if (err)
		cb->args[1] = idx;
	else
		cb->args[1] = 0;
	nlmsg_end(skb, nlh);

	return err;
}

static int br_vlan_rtm_dump(struct sk_buff *skb, struct netlink_callback *cb)
{
	int idx = 0, err = 0, s_idx = cb->args[0];
	struct net *net = sock_net(skb->sk);
	struct br_vlan_msg *bvm;
	struct net_device *dev;

	err = nlmsg_parse(cb->nlh, sizeof(*bvm), NULL, 0, NULL, cb->extack);
	if (err < 0)
		return err;

	bvm = nlmsg_data(cb->nlh);

	rcu_read_lock();
	if (bvm->ifindex) {
		dev = dev_get_by_index_rcu(net, bvm->ifindex);
		if (!dev) {
			err = -ENODEV;
			goto out_err;
		}
		err = br_vlan_dump_dev(dev, skb, cb);
		if (err && err != -EMSGSIZE)
			goto out_err;
	} else {
		for_each_netdev_rcu(net, dev) {
			if (idx < s_idx)
				goto skip;

			err = br_vlan_dump_dev(dev, skb, cb);
			if (err == -EMSGSIZE)
				break;
skip:
			idx++;
		}
	}
	cb->args[0] = idx;
	rcu_read_unlock();

	return skb->len;

out_err:
	rcu_read_unlock();

	return err;
}

void br_vlan_rtnl_init(void)
{
	rtnl_register_module(THIS_MODULE, PF_BRIDGE, RTM_GETVLAN, NULL,
			     br_vlan_rtm_dump, 0);
}

void br_vlan_rtnl_uninit(void)
{
	rtnl_unregister(PF_BRIDGE, RTM_GETVLAN);
}
Loading