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
					
				| @ -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 @@ nla_put_failure: | ||||
|  */ | ||||
| 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 @@ errout: | ||||
| 		rtnl_set_sk_err(net, RTNLGRP_LINK, err); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*
 | ||||
|  * Dump information about all ports, in response to GETLINK | ||||
|  */ | ||||
| @ -162,6 +168,64 @@ out: | ||||
| 	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 @@ out: | ||||
| 	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 New Issue
	
	Block a user