Commit 407af329 authored by Vlad Yasevich's avatar Vlad Yasevich Committed by David S. Miller

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: default avatarVlad Yasevich <vyasevic@redhat.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 85f46c6b
......@@ -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);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment