forked from mirrors/linux
		
	bridge: Add netlink interface to configure vlans on bridge ports
Add a netlink interface to add and remove vlan configuration on bridge port. The interface uses the RTM_SETLINK message and encodes the vlan configuration inside the IFLA_AF_SPEC. It is possble to include multiple vlans to either add or remove in a single message. Signed-off-by: Vlad Yasevich <vyasevic@redhat.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
		
							parent
							
								
									85f46c6bae
								
							
						
					
					
						commit
						407af3299e
					
				
					 7 changed files with 207 additions and 18 deletions
				
			
		| 
						 | 
				
			
			@ -1020,6 +1020,8 @@ struct net_device_ops {
 | 
			
		|||
	int			(*ndo_bridge_getlink)(struct sk_buff *skb,
 | 
			
		||||
						      u32 pid, u32 seq,
 | 
			
		||||
						      struct net_device *dev);
 | 
			
		||||
	int			(*ndo_bridge_dellink)(struct net_device *dev,
 | 
			
		||||
						      struct nlmsghdr *nlh);
 | 
			
		||||
	int			(*ndo_change_carrier)(struct net_device *dev,
 | 
			
		||||
						      bool new_carrier);
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -108,15 +108,24 @@ struct __fdb_entry {
 | 
			
		|||
 * [IFLA_AF_SPEC] = {
 | 
			
		||||
 *     [IFLA_BRIDGE_FLAGS]
 | 
			
		||||
 *     [IFLA_BRIDGE_MODE]
 | 
			
		||||
 *     [IFLA_BRIDGE_VLAN_INFO]
 | 
			
		||||
 * }
 | 
			
		||||
 */
 | 
			
		||||
enum {
 | 
			
		||||
	IFLA_BRIDGE_FLAGS,
 | 
			
		||||
	IFLA_BRIDGE_MODE,
 | 
			
		||||
	IFLA_BRIDGE_VLAN_INFO,
 | 
			
		||||
	__IFLA_BRIDGE_MAX,
 | 
			
		||||
};
 | 
			
		||||
#define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1)
 | 
			
		||||
 | 
			
		||||
#define BRIDGE_VLAN_INFO_MASTER	(1<<0)	/* Operate on Bridge device as well */
 | 
			
		||||
 | 
			
		||||
struct bridge_vlan_info {
 | 
			
		||||
	u16 flags;
 | 
			
		||||
	u16 vid;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Bridge multicast database attributes
 | 
			
		||||
 * [MDBA_MDB] = {
 | 
			
		||||
 *     [MDBA_MDB_ENTRY] = {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -316,6 +316,7 @@ static const struct net_device_ops br_netdev_ops = {
 | 
			
		|||
	.ndo_fdb_dump		 = br_fdb_dump,
 | 
			
		||||
	.ndo_bridge_getlink	 = br_getlink,
 | 
			
		||||
	.ndo_bridge_setlink	 = br_setlink,
 | 
			
		||||
	.ndo_bridge_dellink	 = br_dellink,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void br_dev_free(struct net_device *dev)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,7 @@
 | 
			
		|||
#include <linux/if_ether.h>
 | 
			
		||||
#include <linux/slab.h>
 | 
			
		||||
#include <net/sock.h>
 | 
			
		||||
#include <linux/if_vlan.h>
 | 
			
		||||
 | 
			
		||||
#include "br_private.h"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,7 @@
 | 
			
		|||
#include <net/rtnetlink.h>
 | 
			
		||||
#include <net/net_namespace.h>
 | 
			
		||||
#include <net/sock.h>
 | 
			
		||||
#include <uapi/linux/if_bridge.h>
 | 
			
		||||
 | 
			
		||||
#include "br_private.h"
 | 
			
		||||
#include "br_private_stp.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -119,10 +120,14 @@ static int br_fill_ifinfo(struct sk_buff *skb, const struct net_bridge_port *por
 | 
			
		|||
 */
 | 
			
		||||
void br_ifinfo_notify(int event, struct net_bridge_port *port)
 | 
			
		||||
{
 | 
			
		||||
	struct net *net = dev_net(port->dev);
 | 
			
		||||
	struct net *net;
 | 
			
		||||
	struct sk_buff *skb;
 | 
			
		||||
	int err = -ENOBUFS;
 | 
			
		||||
 | 
			
		||||
	if (!port)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	net = dev_net(port->dev);
 | 
			
		||||
	br_debug(port->br, "port %u(%s) event %d\n",
 | 
			
		||||
		 (unsigned int)port->port_no, port->dev->name, event);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -144,6 +149,7 @@ void br_ifinfo_notify(int event, struct net_bridge_port *port)
 | 
			
		|||
		rtnl_set_sk_err(net, RTNLGRP_LINK, err);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Dump information about all ports, in response to GETLINK
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -162,6 +168,64 @@ int br_getlink(struct sk_buff *skb, u32 pid, u32 seq,
 | 
			
		|||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const struct nla_policy ifla_br_policy[IFLA_MAX+1] = {
 | 
			
		||||
	[IFLA_BRIDGE_FLAGS]	= { .type = NLA_U16 },
 | 
			
		||||
	[IFLA_BRIDGE_MODE]	= { .type = NLA_U16 },
 | 
			
		||||
	[IFLA_BRIDGE_VLAN_INFO]	= { .type = NLA_BINARY,
 | 
			
		||||
				    .len = sizeof(struct bridge_vlan_info), },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int br_afspec(struct net_bridge *br,
 | 
			
		||||
		     struct net_bridge_port *p,
 | 
			
		||||
		     struct nlattr *af_spec,
 | 
			
		||||
		     int cmd)
 | 
			
		||||
{
 | 
			
		||||
	struct nlattr *tb[IFLA_BRIDGE_MAX+1];
 | 
			
		||||
	int err = 0;
 | 
			
		||||
 | 
			
		||||
	err = nla_parse_nested(tb, IFLA_BRIDGE_MAX, af_spec, ifla_br_policy);
 | 
			
		||||
	if (err)
 | 
			
		||||
		return err;
 | 
			
		||||
 | 
			
		||||
	if (tb[IFLA_BRIDGE_VLAN_INFO]) {
 | 
			
		||||
		struct bridge_vlan_info *vinfo;
 | 
			
		||||
 | 
			
		||||
		vinfo = nla_data(tb[IFLA_BRIDGE_VLAN_INFO]);
 | 
			
		||||
 | 
			
		||||
		if (vinfo->vid >= VLAN_N_VID)
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
 | 
			
		||||
		switch (cmd) {
 | 
			
		||||
		case RTM_SETLINK:
 | 
			
		||||
			if (p) {
 | 
			
		||||
				err = nbp_vlan_add(p, vinfo->vid);
 | 
			
		||||
				if (err)
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				if (vinfo->flags & BRIDGE_VLAN_INFO_MASTER)
 | 
			
		||||
					err = br_vlan_add(p->br, vinfo->vid);
 | 
			
		||||
			} else
 | 
			
		||||
				err = br_vlan_add(br, vinfo->vid);
 | 
			
		||||
 | 
			
		||||
			if (err)
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		case RTM_DELLINK:
 | 
			
		||||
			if (p) {
 | 
			
		||||
				nbp_vlan_delete(p, vinfo->vid);
 | 
			
		||||
				if (vinfo->flags & BRIDGE_VLAN_INFO_MASTER)
 | 
			
		||||
					br_vlan_delete(p->br, vinfo->vid);
 | 
			
		||||
			} else
 | 
			
		||||
				br_vlan_delete(br, vinfo->vid);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const struct nla_policy ifla_brport_policy[IFLA_BRPORT_MAX + 1] = {
 | 
			
		||||
	[IFLA_BRPORT_STATE]	= { .type = NLA_U8 },
 | 
			
		||||
	[IFLA_BRPORT_COST]	= { .type = NLA_U32 },
 | 
			
		||||
| 
						 | 
				
			
			@ -241,6 +305,7 @@ int br_setlink(struct net_device *dev, struct nlmsghdr *nlh)
 | 
			
		|||
{
 | 
			
		||||
	struct ifinfomsg *ifm;
 | 
			
		||||
	struct nlattr *protinfo;
 | 
			
		||||
	struct nlattr *afspec;
 | 
			
		||||
	struct net_bridge_port *p;
 | 
			
		||||
	struct nlattr *tb[IFLA_BRPORT_MAX + 1];
 | 
			
		||||
	int err;
 | 
			
		||||
| 
						 | 
				
			
			@ -248,13 +313,18 @@ int br_setlink(struct net_device *dev, struct nlmsghdr *nlh)
 | 
			
		|||
	ifm = nlmsg_data(nlh);
 | 
			
		||||
 | 
			
		||||
	protinfo = nlmsg_find_attr(nlh, sizeof(*ifm), IFLA_PROTINFO);
 | 
			
		||||
	if (!protinfo)
 | 
			
		||||
	afspec = nlmsg_find_attr(nlh, sizeof(*ifm), IFLA_AF_SPEC);
 | 
			
		||||
	if (!protinfo && !afspec)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	p = br_port_get_rtnl(dev);
 | 
			
		||||
	if (!p)
 | 
			
		||||
	/* We want to accept dev as bridge itself if the AF_SPEC
 | 
			
		||||
	 * is set to see if someone is setting vlan info on the brigde
 | 
			
		||||
	 */
 | 
			
		||||
	if (!p && ((dev->priv_flags & IFF_EBRIDGE) && !afspec))
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	if (p && protinfo) {
 | 
			
		||||
		if (protinfo->nla_type & NLA_F_NESTED) {
 | 
			
		||||
			err = nla_parse_nested(tb, IFLA_BRPORT_MAX,
 | 
			
		||||
					       protinfo, ifla_brport_policy);
 | 
			
		||||
| 
						 | 
				
			
			@ -273,13 +343,46 @@ int br_setlink(struct net_device *dev, struct nlmsghdr *nlh)
 | 
			
		|||
			err = br_set_port_state(p, nla_get_u8(protinfo));
 | 
			
		||||
			spin_unlock_bh(&p->br->lock);
 | 
			
		||||
		}
 | 
			
		||||
		if (err)
 | 
			
		||||
			goto out;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (afspec) {
 | 
			
		||||
		err = br_afspec((struct net_bridge *)netdev_priv(dev), p,
 | 
			
		||||
				afspec, RTM_SETLINK);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (err == 0)
 | 
			
		||||
		br_ifinfo_notify(RTM_NEWLINK, p);
 | 
			
		||||
 | 
			
		||||
out:
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Delete port information */
 | 
			
		||||
int br_dellink(struct net_device *dev, struct nlmsghdr *nlh)
 | 
			
		||||
{
 | 
			
		||||
	struct ifinfomsg *ifm;
 | 
			
		||||
	struct nlattr *afspec;
 | 
			
		||||
	struct net_bridge_port *p;
 | 
			
		||||
	int err;
 | 
			
		||||
 | 
			
		||||
	ifm = nlmsg_data(nlh);
 | 
			
		||||
 | 
			
		||||
	afspec = nlmsg_find_attr(nlh, sizeof(*ifm), IFLA_AF_SPEC);
 | 
			
		||||
	if (!afspec)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	p = br_port_get_rtnl(dev);
 | 
			
		||||
	/* We want to accept dev as bridge itself as well */
 | 
			
		||||
	if (!p && !(dev->priv_flags & IFF_EBRIDGE))
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	err = br_afspec((struct net_bridge *)netdev_priv(dev), p,
 | 
			
		||||
			afspec, RTM_DELLINK);
 | 
			
		||||
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
static int br_validate(struct nlattr *tb[], struct nlattr *data[])
 | 
			
		||||
{
 | 
			
		||||
	if (tb[IFLA_ADDRESS]) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -713,6 +713,7 @@ extern int br_netlink_init(void);
 | 
			
		|||
extern void br_netlink_fini(void);
 | 
			
		||||
extern void br_ifinfo_notify(int event, struct net_bridge_port *port);
 | 
			
		||||
extern int br_setlink(struct net_device *dev, struct nlmsghdr *nlmsg);
 | 
			
		||||
extern int br_dellink(struct net_device *dev, struct nlmsghdr *nlmsg);
 | 
			
		||||
extern int br_getlink(struct sk_buff *skb, u32 pid, u32 seq,
 | 
			
		||||
		      struct net_device *dev);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2464,6 +2464,77 @@ static int rtnl_bridge_setlink(struct sk_buff *skb, struct nlmsghdr *nlh,
 | 
			
		|||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int rtnl_bridge_dellink(struct sk_buff *skb, struct nlmsghdr *nlh,
 | 
			
		||||
			       void *arg)
 | 
			
		||||
{
 | 
			
		||||
	struct net *net = sock_net(skb->sk);
 | 
			
		||||
	struct ifinfomsg *ifm;
 | 
			
		||||
	struct net_device *dev;
 | 
			
		||||
	struct nlattr *br_spec, *attr = NULL;
 | 
			
		||||
	int rem, err = -EOPNOTSUPP;
 | 
			
		||||
	u16 oflags, flags = 0;
 | 
			
		||||
	bool have_flags = false;
 | 
			
		||||
 | 
			
		||||
	if (nlmsg_len(nlh) < sizeof(*ifm))
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	ifm = nlmsg_data(nlh);
 | 
			
		||||
	if (ifm->ifi_family != AF_BRIDGE)
 | 
			
		||||
		return -EPFNOSUPPORT;
 | 
			
		||||
 | 
			
		||||
	dev = __dev_get_by_index(net, ifm->ifi_index);
 | 
			
		||||
	if (!dev) {
 | 
			
		||||
		pr_info("PF_BRIDGE: RTM_SETLINK with unknown ifindex\n");
 | 
			
		||||
		return -ENODEV;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	br_spec = nlmsg_find_attr(nlh, sizeof(struct ifinfomsg), IFLA_AF_SPEC);
 | 
			
		||||
	if (br_spec) {
 | 
			
		||||
		nla_for_each_nested(attr, br_spec, rem) {
 | 
			
		||||
			if (nla_type(attr) == IFLA_BRIDGE_FLAGS) {
 | 
			
		||||
				have_flags = true;
 | 
			
		||||
				flags = nla_get_u16(attr);
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	oflags = flags;
 | 
			
		||||
 | 
			
		||||
	if (!flags || (flags & BRIDGE_FLAGS_MASTER)) {
 | 
			
		||||
		struct net_device *br_dev = netdev_master_upper_dev_get(dev);
 | 
			
		||||
 | 
			
		||||
		if (!br_dev || !br_dev->netdev_ops->ndo_bridge_dellink) {
 | 
			
		||||
			err = -EOPNOTSUPP;
 | 
			
		||||
			goto out;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = br_dev->netdev_ops->ndo_bridge_dellink(dev, nlh);
 | 
			
		||||
		if (err)
 | 
			
		||||
			goto out;
 | 
			
		||||
 | 
			
		||||
		flags &= ~BRIDGE_FLAGS_MASTER;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ((flags & BRIDGE_FLAGS_SELF)) {
 | 
			
		||||
		if (!dev->netdev_ops->ndo_bridge_dellink)
 | 
			
		||||
			err = -EOPNOTSUPP;
 | 
			
		||||
		else
 | 
			
		||||
			err = dev->netdev_ops->ndo_bridge_dellink(dev, nlh);
 | 
			
		||||
 | 
			
		||||
		if (!err)
 | 
			
		||||
			flags &= ~BRIDGE_FLAGS_SELF;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (have_flags)
 | 
			
		||||
		memcpy(nla_data(attr), &flags, sizeof(flags));
 | 
			
		||||
	/* Generate event to notify upper layer of bridge change */
 | 
			
		||||
	if (!err)
 | 
			
		||||
		err = rtnl_bridge_notify(dev, oflags);
 | 
			
		||||
out:
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Protected by RTNL sempahore.  */
 | 
			
		||||
static struct rtattr **rta_buf;
 | 
			
		||||
static int rtattr_max;
 | 
			
		||||
| 
						 | 
				
			
			@ -2647,6 +2718,7 @@ void __init rtnetlink_init(void)
 | 
			
		|||
	rtnl_register(PF_BRIDGE, RTM_GETNEIGH, NULL, rtnl_fdb_dump, NULL);
 | 
			
		||||
 | 
			
		||||
	rtnl_register(PF_BRIDGE, RTM_GETLINK, NULL, rtnl_bridge_getlink, NULL);
 | 
			
		||||
	rtnl_register(PF_BRIDGE, RTM_DELLINK, rtnl_bridge_dellink, NULL, NULL);
 | 
			
		||||
	rtnl_register(PF_BRIDGE, RTM_SETLINK, rtnl_bridge_setlink, NULL, NULL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue