bridge: add support of adding and deleting mdb entries
This patch implents adding/deleting mdb entries via netlink. Currently all entries are temp, we probably need a flag to distinguish permanent entries too. Cc: Herbert Xu <herbert@gondor.apana.org.au> Cc: Stephen Hemminger <shemminger@vyatta.com> Cc: "David S. Miller" <davem@davemloft.net> Cc: Thomas Graf <tgraf@suug.ch> Signed-off-by: Cong Wang <amwang@redhat.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:

committed by
David S. Miller

szülő
37a393bc49
commit
cfd5675435
@@ -4,6 +4,7 @@
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/rculist.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <net/ip.h>
|
||||
#include <net/netlink.h>
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
@@ -235,7 +236,246 @@ void br_mdb_notify(struct net_device *dev, struct net_bridge_port *port,
|
||||
__br_mdb_notify(dev, &entry, type);
|
||||
}
|
||||
|
||||
static bool is_valid_mdb_entry(struct br_mdb_entry *entry)
|
||||
{
|
||||
if (entry->ifindex == 0)
|
||||
return false;
|
||||
|
||||
if (entry->addr.proto == htons(ETH_P_IP)) {
|
||||
if (!ipv4_is_multicast(entry->addr.u.ip4))
|
||||
return false;
|
||||
if (ipv4_is_local_multicast(entry->addr.u.ip4))
|
||||
return false;
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
} else if (entry->addr.proto == htons(ETH_P_IPV6)) {
|
||||
if (!ipv6_is_transient_multicast(&entry->addr.u.ip6))
|
||||
return false;
|
||||
#endif
|
||||
} else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int br_mdb_parse(struct sk_buff *skb, struct nlmsghdr *nlh,
|
||||
struct net_device **pdev, struct br_mdb_entry **pentry)
|
||||
{
|
||||
struct net *net = sock_net(skb->sk);
|
||||
struct br_mdb_entry *entry;
|
||||
struct br_port_msg *bpm;
|
||||
struct nlattr *tb[MDBA_SET_ENTRY_MAX+1];
|
||||
struct net_device *dev;
|
||||
int err;
|
||||
|
||||
if (!capable(CAP_NET_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
err = nlmsg_parse(nlh, sizeof(*bpm), tb, MDBA_SET_ENTRY, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
bpm = nlmsg_data(nlh);
|
||||
if (bpm->ifindex == 0) {
|
||||
pr_info("PF_BRIDGE: br_mdb_parse() with invalid ifindex\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev = __dev_get_by_index(net, bpm->ifindex);
|
||||
if (dev == NULL) {
|
||||
pr_info("PF_BRIDGE: br_mdb_parse() with unknown ifindex\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!(dev->priv_flags & IFF_EBRIDGE)) {
|
||||
pr_info("PF_BRIDGE: br_mdb_parse() with non-bridge\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
*pdev = dev;
|
||||
|
||||
if (!tb[MDBA_SET_ENTRY] ||
|
||||
nla_len(tb[MDBA_SET_ENTRY]) != sizeof(struct br_mdb_entry)) {
|
||||
pr_info("PF_BRIDGE: br_mdb_parse() with invalid attr\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
entry = nla_data(tb[MDBA_SET_ENTRY]);
|
||||
if (!is_valid_mdb_entry(entry)) {
|
||||
pr_info("PF_BRIDGE: br_mdb_parse() with invalid entry\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*pentry = entry;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port,
|
||||
struct br_ip *group)
|
||||
{
|
||||
struct net_bridge_mdb_entry *mp;
|
||||
struct net_bridge_port_group *p;
|
||||
struct net_bridge_port_group __rcu **pp;
|
||||
struct net_bridge_mdb_htable *mdb;
|
||||
int err;
|
||||
|
||||
mdb = mlock_dereference(br->mdb, br);
|
||||
mp = br_mdb_ip_get(mdb, group);
|
||||
if (!mp) {
|
||||
mp = br_multicast_new_group(br, port, group);
|
||||
err = PTR_ERR(mp);
|
||||
if (IS_ERR(mp))
|
||||
return err;
|
||||
}
|
||||
|
||||
for (pp = &mp->ports;
|
||||
(p = mlock_dereference(*pp, br)) != NULL;
|
||||
pp = &p->next) {
|
||||
if (p->port == port)
|
||||
return -EEXIST;
|
||||
if ((unsigned long)p->port < (unsigned long)port)
|
||||
break;
|
||||
}
|
||||
|
||||
p = br_multicast_new_port_group(port, group, *pp);
|
||||
if (unlikely(!p))
|
||||
return -ENOMEM;
|
||||
rcu_assign_pointer(*pp, p);
|
||||
|
||||
br_mdb_notify(br->dev, port, group, RTM_NEWMDB);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __br_mdb_add(struct net *net, struct net_bridge *br,
|
||||
struct br_mdb_entry *entry)
|
||||
{
|
||||
struct br_ip ip;
|
||||
struct net_device *dev;
|
||||
struct net_bridge_port *p;
|
||||
int ret;
|
||||
|
||||
if (!netif_running(br->dev) || br->multicast_disabled)
|
||||
return -EINVAL;
|
||||
|
||||
dev = __dev_get_by_index(net, entry->ifindex);
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
p = br_port_get_rtnl(dev);
|
||||
if (!p || p->br != br || p->state == BR_STATE_DISABLED)
|
||||
return -EINVAL;
|
||||
|
||||
ip.proto = entry->addr.proto;
|
||||
if (ip.proto == htons(ETH_P_IP))
|
||||
ip.u.ip4 = entry->addr.u.ip4;
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
else
|
||||
ip.u.ip6 = entry->addr.u.ip6;
|
||||
#endif
|
||||
|
||||
spin_lock_bh(&br->multicast_lock);
|
||||
ret = br_mdb_add_group(br, p, &ip);
|
||||
spin_unlock_bh(&br->multicast_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
|
||||
{
|
||||
struct net *net = sock_net(skb->sk);
|
||||
struct br_mdb_entry *entry;
|
||||
struct net_device *dev;
|
||||
struct net_bridge *br;
|
||||
int err;
|
||||
|
||||
err = br_mdb_parse(skb, nlh, &dev, &entry);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
br = netdev_priv(dev);
|
||||
|
||||
err = __br_mdb_add(net, br, entry);
|
||||
if (!err)
|
||||
__br_mdb_notify(dev, entry, RTM_NEWMDB);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __br_mdb_del(struct net_bridge *br, struct br_mdb_entry *entry)
|
||||
{
|
||||
struct net_bridge_mdb_htable *mdb;
|
||||
struct net_bridge_mdb_entry *mp;
|
||||
struct net_bridge_port_group *p;
|
||||
struct net_bridge_port_group __rcu **pp;
|
||||
struct br_ip ip;
|
||||
int err = -EINVAL;
|
||||
|
||||
if (!netif_running(br->dev) || br->multicast_disabled)
|
||||
return -EINVAL;
|
||||
|
||||
if (timer_pending(&br->multicast_querier_timer))
|
||||
return -EBUSY;
|
||||
|
||||
ip.proto = entry->addr.proto;
|
||||
if (ip.proto == htons(ETH_P_IP))
|
||||
ip.u.ip4 = entry->addr.u.ip4;
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
else
|
||||
ip.u.ip6 = entry->addr.u.ip6;
|
||||
#endif
|
||||
|
||||
spin_lock_bh(&br->multicast_lock);
|
||||
mdb = mlock_dereference(br->mdb, br);
|
||||
|
||||
mp = br_mdb_ip_get(mdb, &ip);
|
||||
if (!mp)
|
||||
goto unlock;
|
||||
|
||||
for (pp = &mp->ports;
|
||||
(p = mlock_dereference(*pp, br)) != NULL;
|
||||
pp = &p->next) {
|
||||
if (!p->port || p->port->dev->ifindex != entry->ifindex)
|
||||
continue;
|
||||
|
||||
if (p->port->state == BR_STATE_DISABLED)
|
||||
goto unlock;
|
||||
|
||||
rcu_assign_pointer(*pp, p->next);
|
||||
hlist_del_init(&p->mglist);
|
||||
del_timer(&p->timer);
|
||||
call_rcu_bh(&p->rcu, br_multicast_free_pg);
|
||||
err = 0;
|
||||
|
||||
if (!mp->ports && !mp->mglist &&
|
||||
netif_running(br->dev))
|
||||
mod_timer(&mp->timer, jiffies);
|
||||
break;
|
||||
}
|
||||
|
||||
unlock:
|
||||
spin_unlock_bh(&br->multicast_lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int br_mdb_del(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
|
||||
{
|
||||
struct net_device *dev;
|
||||
struct br_mdb_entry *entry;
|
||||
struct net_bridge *br;
|
||||
int err;
|
||||
|
||||
err = br_mdb_parse(skb, nlh, &dev, &entry);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
br = netdev_priv(dev);
|
||||
|
||||
err = __br_mdb_del(br, entry);
|
||||
if (!err)
|
||||
__br_mdb_notify(dev, entry, RTM_DELMDB);
|
||||
return err;
|
||||
}
|
||||
|
||||
void br_mdb_init(void)
|
||||
{
|
||||
rtnl_register(PF_BRIDGE, RTM_GETMDB, NULL, br_mdb_dump, NULL);
|
||||
rtnl_register(PF_BRIDGE, RTM_NEWMDB, br_mdb_add, NULL, NULL);
|
||||
rtnl_register(PF_BRIDGE, RTM_DELMDB, br_mdb_del, NULL, NULL);
|
||||
}
|
||||
|
Reference in New Issue
Block a user