2012-12-07 00:04:48 +00:00
|
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/igmp.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/netdevice.h>
|
|
|
|
#include <linux/rculist.h>
|
|
|
|
#include <linux/skbuff.h>
|
2012-12-11 22:23:08 +00:00
|
|
|
#include <linux/if_ether.h>
|
2012-12-07 00:04:48 +00:00
|
|
|
#include <net/ip.h>
|
|
|
|
#include <net/netlink.h>
|
2016-01-10 20:06:23 +00:00
|
|
|
#include <net/switchdev.h>
|
2012-12-07 00:04:48 +00:00
|
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
|
|
#include <net/ipv6.h>
|
2013-09-04 00:13:39 +00:00
|
|
|
#include <net/addrconf.h>
|
2012-12-07 00:04:48 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "br_private.h"
|
|
|
|
|
|
|
|
static int br_rports_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
|
|
|
|
struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct net_bridge *br = netdev_priv(dev);
|
|
|
|
struct net_bridge_port *p;
|
2016-02-26 20:20:04 +00:00
|
|
|
struct nlattr *nest, *port_nest;
|
2012-12-07 00:04:48 +00:00
|
|
|
|
|
|
|
if (!br->multicast_router || hlist_empty(&br->router_list))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
nest = nla_nest_start(skb, MDBA_ROUTER);
|
|
|
|
if (nest == NULL)
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
hlist: drop the node parameter from iterators
I'm not sure why, but the hlist for each entry iterators were conceived
list_for_each_entry(pos, head, member)
The hlist ones were greedy and wanted an extra parameter:
hlist_for_each_entry(tpos, pos, head, member)
Why did they need an extra pos parameter? I'm not quite sure. Not only
they don't really need it, it also prevents the iterator from looking
exactly like the list iterator, which is unfortunate.
Besides the semantic patch, there was some manual work required:
- Fix up the actual hlist iterators in linux/list.h
- Fix up the declaration of other iterators based on the hlist ones.
- A very small amount of places were using the 'node' parameter, this
was modified to use 'obj->member' instead.
- Coccinelle didn't handle the hlist_for_each_entry_safe iterator
properly, so those had to be fixed up manually.
The semantic patch which is mostly the work of Peter Senna Tschudin is here:
@@
iterator name hlist_for_each_entry, hlist_for_each_entry_continue, hlist_for_each_entry_from, hlist_for_each_entry_rcu, hlist_for_each_entry_rcu_bh, hlist_for_each_entry_continue_rcu_bh, for_each_busy_worker, ax25_uid_for_each, ax25_for_each, inet_bind_bucket_for_each, sctp_for_each_hentry, sk_for_each, sk_for_each_rcu, sk_for_each_from, sk_for_each_safe, sk_for_each_bound, hlist_for_each_entry_safe, hlist_for_each_entry_continue_rcu, nr_neigh_for_each, nr_neigh_for_each_safe, nr_node_for_each, nr_node_for_each_safe, for_each_gfn_indirect_valid_sp, for_each_gfn_sp, for_each_host;
type T;
expression a,c,d,e;
identifier b;
statement S;
@@
-T b;
<+... when != b
(
hlist_for_each_entry(a,
- b,
c, d) S
|
hlist_for_each_entry_continue(a,
- b,
c) S
|
hlist_for_each_entry_from(a,
- b,
c) S
|
hlist_for_each_entry_rcu(a,
- b,
c, d) S
|
hlist_for_each_entry_rcu_bh(a,
- b,
c, d) S
|
hlist_for_each_entry_continue_rcu_bh(a,
- b,
c) S
|
for_each_busy_worker(a, c,
- b,
d) S
|
ax25_uid_for_each(a,
- b,
c) S
|
ax25_for_each(a,
- b,
c) S
|
inet_bind_bucket_for_each(a,
- b,
c) S
|
sctp_for_each_hentry(a,
- b,
c) S
|
sk_for_each(a,
- b,
c) S
|
sk_for_each_rcu(a,
- b,
c) S
|
sk_for_each_from
-(a, b)
+(a)
S
+ sk_for_each_from(a) S
|
sk_for_each_safe(a,
- b,
c, d) S
|
sk_for_each_bound(a,
- b,
c) S
|
hlist_for_each_entry_safe(a,
- b,
c, d, e) S
|
hlist_for_each_entry_continue_rcu(a,
- b,
c) S
|
nr_neigh_for_each(a,
- b,
c) S
|
nr_neigh_for_each_safe(a,
- b,
c, d) S
|
nr_node_for_each(a,
- b,
c) S
|
nr_node_for_each_safe(a,
- b,
c, d) S
|
- for_each_gfn_sp(a, c, d, b) S
+ for_each_gfn_sp(a, c, d) S
|
- for_each_gfn_indirect_valid_sp(a, c, d, b) S
+ for_each_gfn_indirect_valid_sp(a, c, d) S
|
for_each_host(a,
- b,
c) S
|
for_each_host_safe(a,
- b,
c, d) S
|
for_each_mesh_entry(a,
- b,
c, d) S
)
...+>
[akpm@linux-foundation.org: drop bogus change from net/ipv4/raw.c]
[akpm@linux-foundation.org: drop bogus hunk from net/ipv6/raw.c]
[akpm@linux-foundation.org: checkpatch fixes]
[akpm@linux-foundation.org: fix warnings]
[akpm@linux-foudnation.org: redo intrusive kvm changes]
Tested-by: Peter Senna Tschudin <peter.senna@gmail.com>
Acked-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Signed-off-by: Sasha Levin <sasha.levin@oracle.com>
Cc: Wu Fengguang <fengguang.wu@intel.com>
Cc: Marcelo Tosatti <mtosatti@redhat.com>
Cc: Gleb Natapov <gleb@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2013-02-28 01:06:00 +00:00
|
|
|
hlist_for_each_entry_rcu(p, &br->router_list, rlist) {
|
2016-02-26 20:20:04 +00:00
|
|
|
if (!p)
|
|
|
|
continue;
|
|
|
|
port_nest = nla_nest_start(skb, MDBA_ROUTER_PORT);
|
|
|
|
if (!port_nest)
|
2012-12-07 00:04:48 +00:00
|
|
|
goto fail;
|
2016-02-26 20:20:04 +00:00
|
|
|
if (nla_put_nohdr(skb, sizeof(u32), &p->dev->ifindex) ||
|
|
|
|
nla_put_u32(skb, MDBA_ROUTER_PATTR_TIMER,
|
|
|
|
br_timer_value(&p->multicast_router_timer)) ||
|
|
|
|
nla_put_u8(skb, MDBA_ROUTER_PATTR_TYPE,
|
|
|
|
p->multicast_router)) {
|
|
|
|
nla_nest_cancel(skb, port_nest);
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
nla_nest_end(skb, port_nest);
|
2012-12-07 00:04:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
nla_nest_end(skb, nest);
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
|
|
nla_nest_cancel(skb, nest);
|
|
|
|
return -EMSGSIZE;
|
|
|
|
}
|
|
|
|
|
2016-02-03 08:57:05 +00:00
|
|
|
static void __mdb_entry_fill_flags(struct br_mdb_entry *e, unsigned char flags)
|
|
|
|
{
|
|
|
|
e->state = flags & MDB_PG_FLAGS_PERMANENT;
|
|
|
|
e->flags = 0;
|
|
|
|
if (flags & MDB_PG_FLAGS_OFFLOAD)
|
|
|
|
e->flags |= MDB_FLAGS_OFFLOAD;
|
|
|
|
}
|
|
|
|
|
2016-04-21 10:52:44 +00:00
|
|
|
static void __mdb_entry_to_br_ip(struct br_mdb_entry *entry, struct br_ip *ip)
|
|
|
|
{
|
|
|
|
memset(ip, 0, sizeof(struct br_ip));
|
|
|
|
ip->vid = entry->vid;
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2012-12-07 00:04:48 +00:00
|
|
|
static int br_mdb_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
|
|
|
|
struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct net_bridge *br = netdev_priv(dev);
|
|
|
|
struct net_bridge_mdb_htable *mdb;
|
|
|
|
struct nlattr *nest, *nest2;
|
|
|
|
int i, err = 0;
|
|
|
|
int idx = 0, s_idx = cb->args[1];
|
|
|
|
|
|
|
|
if (br->multicast_disabled)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
mdb = rcu_dereference(br->mdb);
|
|
|
|
if (!mdb)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
nest = nla_nest_start(skb, MDBA_MDB);
|
|
|
|
if (nest == NULL)
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
|
|
|
for (i = 0; i < mdb->max; i++) {
|
|
|
|
struct net_bridge_mdb_entry *mp;
|
2013-08-05 00:19:38 +00:00
|
|
|
struct net_bridge_port_group *p;
|
|
|
|
struct net_bridge_port_group __rcu **pp;
|
2012-12-07 00:04:48 +00:00
|
|
|
struct net_bridge_port *port;
|
|
|
|
|
hlist: drop the node parameter from iterators
I'm not sure why, but the hlist for each entry iterators were conceived
list_for_each_entry(pos, head, member)
The hlist ones were greedy and wanted an extra parameter:
hlist_for_each_entry(tpos, pos, head, member)
Why did they need an extra pos parameter? I'm not quite sure. Not only
they don't really need it, it also prevents the iterator from looking
exactly like the list iterator, which is unfortunate.
Besides the semantic patch, there was some manual work required:
- Fix up the actual hlist iterators in linux/list.h
- Fix up the declaration of other iterators based on the hlist ones.
- A very small amount of places were using the 'node' parameter, this
was modified to use 'obj->member' instead.
- Coccinelle didn't handle the hlist_for_each_entry_safe iterator
properly, so those had to be fixed up manually.
The semantic patch which is mostly the work of Peter Senna Tschudin is here:
@@
iterator name hlist_for_each_entry, hlist_for_each_entry_continue, hlist_for_each_entry_from, hlist_for_each_entry_rcu, hlist_for_each_entry_rcu_bh, hlist_for_each_entry_continue_rcu_bh, for_each_busy_worker, ax25_uid_for_each, ax25_for_each, inet_bind_bucket_for_each, sctp_for_each_hentry, sk_for_each, sk_for_each_rcu, sk_for_each_from, sk_for_each_safe, sk_for_each_bound, hlist_for_each_entry_safe, hlist_for_each_entry_continue_rcu, nr_neigh_for_each, nr_neigh_for_each_safe, nr_node_for_each, nr_node_for_each_safe, for_each_gfn_indirect_valid_sp, for_each_gfn_sp, for_each_host;
type T;
expression a,c,d,e;
identifier b;
statement S;
@@
-T b;
<+... when != b
(
hlist_for_each_entry(a,
- b,
c, d) S
|
hlist_for_each_entry_continue(a,
- b,
c) S
|
hlist_for_each_entry_from(a,
- b,
c) S
|
hlist_for_each_entry_rcu(a,
- b,
c, d) S
|
hlist_for_each_entry_rcu_bh(a,
- b,
c, d) S
|
hlist_for_each_entry_continue_rcu_bh(a,
- b,
c) S
|
for_each_busy_worker(a, c,
- b,
d) S
|
ax25_uid_for_each(a,
- b,
c) S
|
ax25_for_each(a,
- b,
c) S
|
inet_bind_bucket_for_each(a,
- b,
c) S
|
sctp_for_each_hentry(a,
- b,
c) S
|
sk_for_each(a,
- b,
c) S
|
sk_for_each_rcu(a,
- b,
c) S
|
sk_for_each_from
-(a, b)
+(a)
S
+ sk_for_each_from(a) S
|
sk_for_each_safe(a,
- b,
c, d) S
|
sk_for_each_bound(a,
- b,
c) S
|
hlist_for_each_entry_safe(a,
- b,
c, d, e) S
|
hlist_for_each_entry_continue_rcu(a,
- b,
c) S
|
nr_neigh_for_each(a,
- b,
c) S
|
nr_neigh_for_each_safe(a,
- b,
c, d) S
|
nr_node_for_each(a,
- b,
c) S
|
nr_node_for_each_safe(a,
- b,
c, d) S
|
- for_each_gfn_sp(a, c, d, b) S
+ for_each_gfn_sp(a, c, d) S
|
- for_each_gfn_indirect_valid_sp(a, c, d, b) S
+ for_each_gfn_indirect_valid_sp(a, c, d) S
|
for_each_host(a,
- b,
c) S
|
for_each_host_safe(a,
- b,
c, d) S
|
for_each_mesh_entry(a,
- b,
c, d) S
)
...+>
[akpm@linux-foundation.org: drop bogus change from net/ipv4/raw.c]
[akpm@linux-foundation.org: drop bogus hunk from net/ipv6/raw.c]
[akpm@linux-foundation.org: checkpatch fixes]
[akpm@linux-foundation.org: fix warnings]
[akpm@linux-foudnation.org: redo intrusive kvm changes]
Tested-by: Peter Senna Tschudin <peter.senna@gmail.com>
Acked-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Signed-off-by: Sasha Levin <sasha.levin@oracle.com>
Cc: Wu Fengguang <fengguang.wu@intel.com>
Cc: Marcelo Tosatti <mtosatti@redhat.com>
Cc: Gleb Natapov <gleb@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2013-02-28 01:06:00 +00:00
|
|
|
hlist_for_each_entry_rcu(mp, &mdb->mhash[i], hlist[mdb->ver]) {
|
2012-12-07 00:04:48 +00:00
|
|
|
if (idx < s_idx)
|
|
|
|
goto skip;
|
|
|
|
|
|
|
|
nest2 = nla_nest_start(skb, MDBA_MDB_ENTRY);
|
|
|
|
if (nest2 == NULL) {
|
|
|
|
err = -EMSGSIZE;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (pp = &mp->ports;
|
|
|
|
(p = rcu_dereference(*pp)) != NULL;
|
|
|
|
pp = &p->next) {
|
2016-02-16 11:46:54 +00:00
|
|
|
struct nlattr *nest_ent;
|
2016-02-16 11:46:53 +00:00
|
|
|
struct br_mdb_entry e;
|
|
|
|
|
2012-12-07 00:04:48 +00:00
|
|
|
port = p->port;
|
2016-02-16 11:46:53 +00:00
|
|
|
if (!port)
|
|
|
|
continue;
|
2016-02-16 11:46:54 +00:00
|
|
|
|
2016-02-16 11:46:53 +00:00
|
|
|
memset(&e, 0, sizeof(e));
|
|
|
|
e.ifindex = port->dev->ifindex;
|
|
|
|
e.vid = p->addr.vid;
|
|
|
|
__mdb_entry_fill_flags(&e, p->flags);
|
|
|
|
if (p->addr.proto == htons(ETH_P_IP))
|
|
|
|
e.addr.u.ip4 = p->addr.u.ip4;
|
2012-12-07 00:04:48 +00:00
|
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
2016-02-16 11:46:53 +00:00
|
|
|
if (p->addr.proto == htons(ETH_P_IPV6))
|
|
|
|
e.addr.u.ip6 = p->addr.u.ip6;
|
2012-12-07 00:04:48 +00:00
|
|
|
#endif
|
2016-02-16 11:46:53 +00:00
|
|
|
e.addr.proto = p->addr.proto;
|
2016-02-16 11:46:54 +00:00
|
|
|
nest_ent = nla_nest_start(skb,
|
|
|
|
MDBA_MDB_ENTRY_INFO);
|
|
|
|
if (!nest_ent) {
|
|
|
|
nla_nest_cancel(skb, nest2);
|
|
|
|
err = -EMSGSIZE;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
if (nla_put_nohdr(skb, sizeof(e), &e) ||
|
|
|
|
nla_put_u32(skb,
|
|
|
|
MDBA_MDB_EATTR_TIMER,
|
|
|
|
br_timer_value(&p->timer))) {
|
|
|
|
nla_nest_cancel(skb, nest_ent);
|
2016-02-16 11:46:53 +00:00
|
|
|
nla_nest_cancel(skb, nest2);
|
|
|
|
err = -EMSGSIZE;
|
|
|
|
goto out;
|
2012-12-07 00:04:48 +00:00
|
|
|
}
|
2016-02-16 11:46:54 +00:00
|
|
|
nla_nest_end(skb, nest_ent);
|
2012-12-07 00:04:48 +00:00
|
|
|
}
|
|
|
|
nla_nest_end(skb, nest2);
|
|
|
|
skip:
|
|
|
|
idx++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
cb->args[1] = idx;
|
|
|
|
nla_nest_end(skb, nest);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int br_mdb_dump(struct sk_buff *skb, struct netlink_callback *cb)
|
|
|
|
{
|
|
|
|
struct net_device *dev;
|
|
|
|
struct net *net = sock_net(skb->sk);
|
|
|
|
struct nlmsghdr *nlh = NULL;
|
|
|
|
int idx = 0, s_idx;
|
|
|
|
|
|
|
|
s_idx = cb->args[0];
|
|
|
|
|
|
|
|
rcu_read_lock();
|
|
|
|
|
2012-12-10 02:15:35 +00:00
|
|
|
/* In theory this could be wrapped to 0... */
|
|
|
|
cb->seq = net->dev_base_seq + br_mdb_rehash_seq;
|
2012-12-07 00:04:48 +00:00
|
|
|
|
|
|
|
for_each_netdev_rcu(net, dev) {
|
|
|
|
if (dev->priv_flags & IFF_EBRIDGE) {
|
|
|
|
struct br_port_msg *bpm;
|
|
|
|
|
|
|
|
if (idx < s_idx)
|
|
|
|
goto skip;
|
|
|
|
|
|
|
|
nlh = nlmsg_put(skb, NETLINK_CB(cb->skb).portid,
|
|
|
|
cb->nlh->nlmsg_seq, RTM_GETMDB,
|
|
|
|
sizeof(*bpm), NLM_F_MULTI);
|
|
|
|
if (nlh == NULL)
|
|
|
|
break;
|
|
|
|
|
|
|
|
bpm = nlmsg_data(nlh);
|
2013-03-09 05:52:19 +00:00
|
|
|
memset(bpm, 0, sizeof(*bpm));
|
2012-12-07 00:04:48 +00:00
|
|
|
bpm->ifindex = dev->ifindex;
|
|
|
|
if (br_mdb_fill_info(skb, cb, dev) < 0)
|
|
|
|
goto out;
|
|
|
|
if (br_rports_fill_info(skb, cb, dev) < 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
cb->args[1] = 0;
|
|
|
|
nlmsg_end(skb, nlh);
|
|
|
|
skip:
|
|
|
|
idx++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
if (nlh)
|
|
|
|
nlmsg_end(skb, nlh);
|
|
|
|
rcu_read_unlock();
|
|
|
|
cb->args[0] = idx;
|
|
|
|
return skb->len;
|
|
|
|
}
|
|
|
|
|
2012-12-11 22:23:07 +00:00
|
|
|
static int nlmsg_populate_mdb_fill(struct sk_buff *skb,
|
|
|
|
struct net_device *dev,
|
|
|
|
struct br_mdb_entry *entry, u32 pid,
|
|
|
|
u32 seq, int type, unsigned int flags)
|
|
|
|
{
|
|
|
|
struct nlmsghdr *nlh;
|
|
|
|
struct br_port_msg *bpm;
|
|
|
|
struct nlattr *nest, *nest2;
|
|
|
|
|
2015-04-28 16:33:48 +00:00
|
|
|
nlh = nlmsg_put(skb, pid, seq, type, sizeof(*bpm), 0);
|
2012-12-11 22:23:07 +00:00
|
|
|
if (!nlh)
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
|
|
|
bpm = nlmsg_data(nlh);
|
2013-03-09 05:52:19 +00:00
|
|
|
memset(bpm, 0, sizeof(*bpm));
|
2012-12-11 22:23:07 +00:00
|
|
|
bpm->family = AF_BRIDGE;
|
|
|
|
bpm->ifindex = dev->ifindex;
|
|
|
|
nest = nla_nest_start(skb, MDBA_MDB);
|
|
|
|
if (nest == NULL)
|
|
|
|
goto cancel;
|
|
|
|
nest2 = nla_nest_start(skb, MDBA_MDB_ENTRY);
|
|
|
|
if (nest2 == NULL)
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
if (nla_put(skb, MDBA_MDB_ENTRY_INFO, sizeof(*entry), entry))
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
nla_nest_end(skb, nest2);
|
|
|
|
nla_nest_end(skb, nest);
|
2015-01-16 21:09:00 +00:00
|
|
|
nlmsg_end(skb, nlh);
|
|
|
|
return 0;
|
2012-12-11 22:23:07 +00:00
|
|
|
|
|
|
|
end:
|
|
|
|
nla_nest_end(skb, nest);
|
|
|
|
cancel:
|
|
|
|
nlmsg_cancel(skb, nlh);
|
|
|
|
return -EMSGSIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline size_t rtnl_mdb_nlmsg_size(void)
|
|
|
|
{
|
|
|
|
return NLMSG_ALIGN(sizeof(struct br_port_msg))
|
|
|
|
+ nla_total_size(sizeof(struct br_mdb_entry));
|
|
|
|
}
|
|
|
|
|
2016-04-21 10:52:45 +00:00
|
|
|
struct br_mdb_complete_info {
|
|
|
|
struct net_bridge_port *port;
|
|
|
|
struct br_ip ip;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void br_mdb_complete(struct net_device *dev, int err, void *priv)
|
2012-12-11 22:23:07 +00:00
|
|
|
{
|
2016-04-21 10:52:45 +00:00
|
|
|
struct br_mdb_complete_info *data = priv;
|
|
|
|
struct net_bridge_port_group __rcu **pp;
|
|
|
|
struct net_bridge_port_group *p;
|
|
|
|
struct net_bridge_mdb_htable *mdb;
|
|
|
|
struct net_bridge_mdb_entry *mp;
|
|
|
|
struct net_bridge_port *port = data->port;
|
|
|
|
struct net_bridge *br = port->br;
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
spin_lock_bh(&br->multicast_lock);
|
|
|
|
mdb = mlock_dereference(br->mdb, br);
|
|
|
|
mp = br_mdb_ip_get(mdb, &data->ip);
|
|
|
|
if (!mp)
|
|
|
|
goto out;
|
|
|
|
for (pp = &mp->ports; (p = mlock_dereference(*pp, br)) != NULL;
|
|
|
|
pp = &p->next) {
|
|
|
|
if (p->port != port)
|
|
|
|
continue;
|
|
|
|
p->flags |= MDB_PG_FLAGS_OFFLOAD;
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
spin_unlock_bh(&br->multicast_lock);
|
|
|
|
err:
|
|
|
|
kfree(priv);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __br_mdb_notify(struct net_device *dev, struct net_bridge_port *p,
|
|
|
|
struct br_mdb_entry *entry, int type)
|
|
|
|
{
|
|
|
|
struct br_mdb_complete_info *complete_info;
|
2016-01-10 20:06:23 +00:00
|
|
|
struct switchdev_obj_port_mdb mdb = {
|
|
|
|
.obj = {
|
|
|
|
.id = SWITCHDEV_OBJ_ID_PORT_MDB,
|
|
|
|
.flags = SWITCHDEV_F_DEFER,
|
|
|
|
},
|
|
|
|
.vid = entry->vid,
|
|
|
|
};
|
|
|
|
struct net_device *port_dev;
|
2012-12-11 22:23:07 +00:00
|
|
|
struct net *net = dev_net(dev);
|
|
|
|
struct sk_buff *skb;
|
|
|
|
int err = -ENOBUFS;
|
|
|
|
|
2016-01-10 20:06:23 +00:00
|
|
|
port_dev = __dev_get_by_index(net, entry->ifindex);
|
|
|
|
if (entry->addr.proto == htons(ETH_P_IP))
|
|
|
|
ip_eth_mc_map(entry->addr.u.ip4, mdb.addr);
|
|
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
|
|
else
|
|
|
|
ipv6_eth_mc_map(&entry->addr.u.ip6, mdb.addr);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
mdb.obj.orig_dev = port_dev;
|
2016-02-03 08:57:06 +00:00
|
|
|
if (port_dev && type == RTM_NEWMDB) {
|
2016-04-21 10:52:45 +00:00
|
|
|
complete_info = kmalloc(sizeof(*complete_info), GFP_ATOMIC);
|
|
|
|
if (complete_info) {
|
|
|
|
complete_info->port = p;
|
|
|
|
__mdb_entry_to_br_ip(entry, &complete_info->ip);
|
|
|
|
mdb.obj.complete_priv = complete_info;
|
|
|
|
mdb.obj.complete = br_mdb_complete;
|
|
|
|
switchdev_port_obj_add(port_dev, &mdb.obj);
|
|
|
|
}
|
2016-02-03 08:57:06 +00:00
|
|
|
} else if (port_dev && type == RTM_DELMDB) {
|
2016-01-10 20:06:23 +00:00
|
|
|
switchdev_port_obj_del(port_dev, &mdb.obj);
|
2016-02-03 08:57:06 +00:00
|
|
|
}
|
2016-01-10 20:06:23 +00:00
|
|
|
|
2012-12-11 22:23:07 +00:00
|
|
|
skb = nlmsg_new(rtnl_mdb_nlmsg_size(), GFP_ATOMIC);
|
|
|
|
if (!skb)
|
|
|
|
goto errout;
|
|
|
|
|
|
|
|
err = nlmsg_populate_mdb_fill(skb, dev, entry, 0, 0, type, NTF_SELF);
|
|
|
|
if (err < 0) {
|
|
|
|
kfree_skb(skb);
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
|
|
|
rtnl_notify(skb, net, 0, RTNLGRP_MDB, NULL, GFP_ATOMIC);
|
|
|
|
return;
|
|
|
|
errout:
|
|
|
|
rtnl_set_sk_err(net, RTNLGRP_MDB, err);
|
|
|
|
}
|
|
|
|
|
2016-04-21 10:52:45 +00:00
|
|
|
void br_mdb_notify(struct net_device *dev, struct net_bridge_port *port,
|
|
|
|
struct br_ip *group, int type, u8 flags)
|
2012-12-11 22:23:07 +00:00
|
|
|
{
|
|
|
|
struct br_mdb_entry entry;
|
|
|
|
|
2013-03-09 05:52:19 +00:00
|
|
|
memset(&entry, 0, sizeof(entry));
|
2016-04-21 10:52:45 +00:00
|
|
|
entry.ifindex = port->dev->ifindex;
|
|
|
|
entry.addr.proto = group->proto;
|
|
|
|
entry.addr.u.ip4 = group->u.ip4;
|
2012-12-11 22:23:07 +00:00
|
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
2016-04-21 10:52:45 +00:00
|
|
|
entry.addr.u.ip6 = group->u.ip6;
|
2012-12-11 22:23:07 +00:00
|
|
|
#endif
|
2016-04-21 10:52:45 +00:00
|
|
|
entry.vid = group->vid;
|
|
|
|
__mdb_entry_fill_flags(&entry, flags);
|
|
|
|
__br_mdb_notify(dev, port, &entry, type);
|
2012-12-11 22:23:07 +00:00
|
|
|
}
|
|
|
|
|
2015-07-23 12:00:53 +00:00
|
|
|
static int nlmsg_populate_rtr_fill(struct sk_buff *skb,
|
|
|
|
struct net_device *dev,
|
|
|
|
int ifindex, u32 pid,
|
|
|
|
u32 seq, int type, unsigned int flags)
|
|
|
|
{
|
|
|
|
struct br_port_msg *bpm;
|
|
|
|
struct nlmsghdr *nlh;
|
|
|
|
struct nlattr *nest;
|
|
|
|
|
|
|
|
nlh = nlmsg_put(skb, pid, seq, type, sizeof(*bpm), NLM_F_MULTI);
|
|
|
|
if (!nlh)
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
|
|
|
bpm = nlmsg_data(nlh);
|
|
|
|
memset(bpm, 0, sizeof(*bpm));
|
|
|
|
bpm->family = AF_BRIDGE;
|
|
|
|
bpm->ifindex = dev->ifindex;
|
|
|
|
nest = nla_nest_start(skb, MDBA_ROUTER);
|
|
|
|
if (!nest)
|
|
|
|
goto cancel;
|
|
|
|
|
|
|
|
if (nla_put_u32(skb, MDBA_ROUTER_PORT, ifindex))
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
nla_nest_end(skb, nest);
|
|
|
|
nlmsg_end(skb, nlh);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
nla_nest_end(skb, nest);
|
|
|
|
cancel:
|
|
|
|
nlmsg_cancel(skb, nlh);
|
|
|
|
return -EMSGSIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline size_t rtnl_rtr_nlmsg_size(void)
|
|
|
|
{
|
|
|
|
return NLMSG_ALIGN(sizeof(struct br_port_msg))
|
|
|
|
+ nla_total_size(sizeof(__u32));
|
|
|
|
}
|
|
|
|
|
|
|
|
void br_rtr_notify(struct net_device *dev, struct net_bridge_port *port,
|
|
|
|
int type)
|
|
|
|
{
|
|
|
|
struct net *net = dev_net(dev);
|
|
|
|
struct sk_buff *skb;
|
|
|
|
int err = -ENOBUFS;
|
|
|
|
int ifindex;
|
|
|
|
|
|
|
|
ifindex = port ? port->dev->ifindex : 0;
|
|
|
|
skb = nlmsg_new(rtnl_rtr_nlmsg_size(), GFP_ATOMIC);
|
|
|
|
if (!skb)
|
|
|
|
goto errout;
|
|
|
|
|
|
|
|
err = nlmsg_populate_rtr_fill(skb, dev, ifindex, 0, 0, type, NTF_SELF);
|
|
|
|
if (err < 0) {
|
|
|
|
kfree_skb(skb);
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
|
|
|
rtnl_notify(skb, net, 0, RTNLGRP_MDB, NULL, GFP_ATOMIC);
|
|
|
|
return;
|
|
|
|
|
|
|
|
errout:
|
|
|
|
rtnl_set_sk_err(net, RTNLGRP_MDB, err);
|
|
|
|
}
|
|
|
|
|
2012-12-11 22:23:08 +00:00
|
|
|
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)) {
|
2013-09-04 00:13:39 +00:00
|
|
|
if (ipv6_addr_is_ll_all_nodes(&entry->addr.u.ip6))
|
2012-12-11 22:23:08 +00:00
|
|
|
return false;
|
|
|
|
#endif
|
|
|
|
} else
|
|
|
|
return false;
|
2012-12-14 22:09:51 +00:00
|
|
|
if (entry->state != MDB_PERMANENT && entry->state != MDB_TEMPORARY)
|
|
|
|
return false;
|
2015-07-10 15:02:08 +00:00
|
|
|
if (entry->vid >= VLAN_VID_MASK)
|
|
|
|
return false;
|
2012-12-11 22:23:08 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2015-01-15 15:29:12 +00:00
|
|
|
err = nlmsg_parse(nlh, sizeof(*bpm), tb, MDBA_SET_ENTRY_MAX, NULL);
|
2012-12-11 22:23:08 +00:00
|
|
|
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,
|
2016-04-21 10:52:45 +00:00
|
|
|
struct br_ip *group, unsigned char state)
|
2012-12-11 22:23:08 +00:00
|
|
|
{
|
|
|
|
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;
|
2015-07-06 12:53:35 +00:00
|
|
|
unsigned long now = jiffies;
|
2012-12-11 22:23:08 +00:00
|
|
|
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);
|
2016-02-10 15:09:02 +00:00
|
|
|
err = PTR_ERR_OR_ZERO(mp);
|
|
|
|
if (err)
|
2012-12-11 22:23:08 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-12-14 22:09:51 +00:00
|
|
|
p = br_multicast_new_port_group(port, group, *pp, state);
|
2012-12-11 22:23:08 +00:00
|
|
|
if (unlikely(!p))
|
|
|
|
return -ENOMEM;
|
|
|
|
rcu_assign_pointer(*pp, p);
|
2015-07-06 12:53:35 +00:00
|
|
|
if (state == MDB_TEMPORARY)
|
|
|
|
mod_timer(&p->timer, now + br->multicast_membership_interval);
|
2012-12-11 22:23:08 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __br_mdb_add(struct net *net, struct net_bridge *br,
|
2016-04-21 10:52:45 +00:00
|
|
|
struct br_mdb_entry *entry)
|
2012-12-11 22:23:08 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
2016-04-21 10:52:44 +00:00
|
|
|
__mdb_entry_to_br_ip(entry, &ip);
|
2012-12-11 22:23:08 +00:00
|
|
|
|
|
|
|
spin_lock_bh(&br->multicast_lock);
|
2016-04-21 10:52:45 +00:00
|
|
|
ret = br_mdb_add_group(br, p, &ip, entry->state);
|
2012-12-11 22:23:08 +00:00
|
|
|
spin_unlock_bh(&br->multicast_lock);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2013-03-21 07:45:29 +00:00
|
|
|
static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh)
|
2012-12-11 22:23:08 +00:00
|
|
|
{
|
|
|
|
struct net *net = sock_net(skb->sk);
|
bridge: vlan: add per-vlan struct and move to rhashtables
This patch changes the bridge vlan implementation to use rhashtables
instead of bitmaps. The main motivation behind this change is that we
need extensible per-vlan structures (both per-port and global) so more
advanced features can be introduced and the vlan support can be
extended. I've tried to break this up but the moment net_port_vlans is
changed and the whole API goes away, thus this is a larger patch.
A few short goals of this patch are:
- Extensible per-vlan structs stored in rhashtables and a sorted list
- Keep user-visible behaviour (compressed vlans etc)
- Keep fastpath ingress/egress logic the same (optimizations to come
later)
Here's a brief list of some of the new features we'd like to introduce:
- per-vlan counters
- vlan ingress/egress mapping
- per-vlan igmp configuration
- vlan priorities
- avoid fdb entries replication (e.g. local fdb scaling issues)
The structure is kept single for both global and per-port entries so to
avoid code duplication where possible and also because we'll soon introduce
"port0 / aka bridge as port" which should simplify things further
(thanks to Vlad for the suggestion!).
Now we have per-vlan global rhashtable (bridge-wide) and per-vlan port
rhashtable, if an entry is added to a port it'll get a pointer to its
global context so it can be quickly accessed later. There's also a
sorted vlan list which is used for stable walks and some user-visible
behaviour such as the vlan ranges, also for error paths.
VLANs are stored in a "vlan group" which currently contains the
rhashtable, sorted vlan list and the number of "real" vlan entries.
A good side-effect of this change is that it resembles how hw keeps
per-vlan data.
One important note after this change is that if a VLAN is being looked up
in the bridge's rhashtable for filtering purposes (or to check if it's an
existing usable entry, not just a global context) then the new helper
br_vlan_should_use() needs to be used if the vlan is found. In case the
lookup is done only with a port's vlan group, then this check can be
skipped.
Things tested so far:
- basic vlan ingress/egress
- pvids
- untagged vlans
- undef CONFIG_BRIDGE_VLAN_FILTERING
- adding/deleting vlans in different scenarios (with/without global ctx,
while transmitting traffic, in ranges etc)
- loading/removing the module while having/adding/deleting vlans
- extracting bridge vlan information (user ABI), compressed requests
- adding/deleting fdbs on vlans
- bridge mac change, promisc mode
- default pvid change
- kmemleak ON during the whole time
Signed-off-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-09-25 17:00:11 +00:00
|
|
|
struct net_bridge_vlan_group *vg;
|
2015-08-03 11:29:16 +00:00
|
|
|
struct net_device *dev, *pdev;
|
2012-12-11 22:23:08 +00:00
|
|
|
struct br_mdb_entry *entry;
|
2015-08-03 11:29:16 +00:00
|
|
|
struct net_bridge_port *p;
|
bridge: vlan: add per-vlan struct and move to rhashtables
This patch changes the bridge vlan implementation to use rhashtables
instead of bitmaps. The main motivation behind this change is that we
need extensible per-vlan structures (both per-port and global) so more
advanced features can be introduced and the vlan support can be
extended. I've tried to break this up but the moment net_port_vlans is
changed and the whole API goes away, thus this is a larger patch.
A few short goals of this patch are:
- Extensible per-vlan structs stored in rhashtables and a sorted list
- Keep user-visible behaviour (compressed vlans etc)
- Keep fastpath ingress/egress logic the same (optimizations to come
later)
Here's a brief list of some of the new features we'd like to introduce:
- per-vlan counters
- vlan ingress/egress mapping
- per-vlan igmp configuration
- vlan priorities
- avoid fdb entries replication (e.g. local fdb scaling issues)
The structure is kept single for both global and per-port entries so to
avoid code duplication where possible and also because we'll soon introduce
"port0 / aka bridge as port" which should simplify things further
(thanks to Vlad for the suggestion!).
Now we have per-vlan global rhashtable (bridge-wide) and per-vlan port
rhashtable, if an entry is added to a port it'll get a pointer to its
global context so it can be quickly accessed later. There's also a
sorted vlan list which is used for stable walks and some user-visible
behaviour such as the vlan ranges, also for error paths.
VLANs are stored in a "vlan group" which currently contains the
rhashtable, sorted vlan list and the number of "real" vlan entries.
A good side-effect of this change is that it resembles how hw keeps
per-vlan data.
One important note after this change is that if a VLAN is being looked up
in the bridge's rhashtable for filtering purposes (or to check if it's an
existing usable entry, not just a global context) then the new helper
br_vlan_should_use() needs to be used if the vlan is found. In case the
lookup is done only with a port's vlan group, then this check can be
skipped.
Things tested so far:
- basic vlan ingress/egress
- pvids
- untagged vlans
- undef CONFIG_BRIDGE_VLAN_FILTERING
- adding/deleting vlans in different scenarios (with/without global ctx,
while transmitting traffic, in ranges etc)
- loading/removing the module while having/adding/deleting vlans
- extracting bridge vlan information (user ABI), compressed requests
- adding/deleting fdbs on vlans
- bridge mac change, promisc mode
- default pvid change
- kmemleak ON during the whole time
Signed-off-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-09-25 17:00:11 +00:00
|
|
|
struct net_bridge_vlan *v;
|
2012-12-11 22:23:08 +00:00
|
|
|
struct net_bridge *br;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = br_mdb_parse(skb, nlh, &dev, &entry);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
br = netdev_priv(dev);
|
|
|
|
|
2015-08-03 11:29:16 +00:00
|
|
|
/* If vlan filtering is enabled and VLAN is not specified
|
|
|
|
* install mdb entry on all vlans configured on the port.
|
|
|
|
*/
|
|
|
|
pdev = __dev_get_by_index(net, entry->ifindex);
|
|
|
|
if (!pdev)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
p = br_port_get_rtnl(pdev);
|
|
|
|
if (!p || p->br != br || p->state == BR_STATE_DISABLED)
|
|
|
|
return -EINVAL;
|
|
|
|
|
bridge: vlan: add per-vlan struct and move to rhashtables
This patch changes the bridge vlan implementation to use rhashtables
instead of bitmaps. The main motivation behind this change is that we
need extensible per-vlan structures (both per-port and global) so more
advanced features can be introduced and the vlan support can be
extended. I've tried to break this up but the moment net_port_vlans is
changed and the whole API goes away, thus this is a larger patch.
A few short goals of this patch are:
- Extensible per-vlan structs stored in rhashtables and a sorted list
- Keep user-visible behaviour (compressed vlans etc)
- Keep fastpath ingress/egress logic the same (optimizations to come
later)
Here's a brief list of some of the new features we'd like to introduce:
- per-vlan counters
- vlan ingress/egress mapping
- per-vlan igmp configuration
- vlan priorities
- avoid fdb entries replication (e.g. local fdb scaling issues)
The structure is kept single for both global and per-port entries so to
avoid code duplication where possible and also because we'll soon introduce
"port0 / aka bridge as port" which should simplify things further
(thanks to Vlad for the suggestion!).
Now we have per-vlan global rhashtable (bridge-wide) and per-vlan port
rhashtable, if an entry is added to a port it'll get a pointer to its
global context so it can be quickly accessed later. There's also a
sorted vlan list which is used for stable walks and some user-visible
behaviour such as the vlan ranges, also for error paths.
VLANs are stored in a "vlan group" which currently contains the
rhashtable, sorted vlan list and the number of "real" vlan entries.
A good side-effect of this change is that it resembles how hw keeps
per-vlan data.
One important note after this change is that if a VLAN is being looked up
in the bridge's rhashtable for filtering purposes (or to check if it's an
existing usable entry, not just a global context) then the new helper
br_vlan_should_use() needs to be used if the vlan is found. In case the
lookup is done only with a port's vlan group, then this check can be
skipped.
Things tested so far:
- basic vlan ingress/egress
- pvids
- untagged vlans
- undef CONFIG_BRIDGE_VLAN_FILTERING
- adding/deleting vlans in different scenarios (with/without global ctx,
while transmitting traffic, in ranges etc)
- loading/removing the module while having/adding/deleting vlans
- extracting bridge vlan information (user ABI), compressed requests
- adding/deleting fdbs on vlans
- bridge mac change, promisc mode
- default pvid change
- kmemleak ON during the whole time
Signed-off-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-09-25 17:00:11 +00:00
|
|
|
vg = nbp_vlan_group(p);
|
|
|
|
if (br_vlan_enabled(br) && vg && entry->vid == 0) {
|
|
|
|
list_for_each_entry(v, &vg->vlan_list, vlist) {
|
|
|
|
entry->vid = v->vid;
|
2016-04-21 10:52:45 +00:00
|
|
|
err = __br_mdb_add(net, br, entry);
|
2015-08-03 11:29:16 +00:00
|
|
|
if (err)
|
|
|
|
break;
|
2016-04-21 10:52:45 +00:00
|
|
|
__br_mdb_notify(dev, p, entry, RTM_NEWMDB);
|
2015-08-03 11:29:16 +00:00
|
|
|
}
|
|
|
|
} else {
|
2016-04-21 10:52:45 +00:00
|
|
|
err = __br_mdb_add(net, br, entry);
|
2015-08-03 11:29:16 +00:00
|
|
|
if (!err)
|
2016-04-21 10:52:45 +00:00
|
|
|
__br_mdb_notify(dev, p, entry, RTM_NEWMDB);
|
2015-08-03 11:29:16 +00:00
|
|
|
}
|
|
|
|
|
2012-12-11 22:23:08 +00:00
|
|
|
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;
|
|
|
|
|
2016-04-21 10:52:44 +00:00
|
|
|
__mdb_entry_to_br_ip(entry, &ip);
|
2012-12-11 22:23:08 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2016-02-03 08:57:05 +00:00
|
|
|
__mdb_entry_fill_flags(entry, p->flags);
|
2012-12-11 22:23:08 +00:00
|
|
|
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;
|
|
|
|
|
2013-10-19 22:58:57 +00:00
|
|
|
if (!mp->ports && !mp->mglist &&
|
2012-12-11 22:23:08 +00:00
|
|
|
netif_running(br->dev))
|
|
|
|
mod_timer(&mp->timer, jiffies);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
unlock:
|
|
|
|
spin_unlock_bh(&br->multicast_lock);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2013-03-21 07:45:29 +00:00
|
|
|
static int br_mdb_del(struct sk_buff *skb, struct nlmsghdr *nlh)
|
2012-12-11 22:23:08 +00:00
|
|
|
{
|
2015-08-03 11:29:16 +00:00
|
|
|
struct net *net = sock_net(skb->sk);
|
bridge: vlan: add per-vlan struct and move to rhashtables
This patch changes the bridge vlan implementation to use rhashtables
instead of bitmaps. The main motivation behind this change is that we
need extensible per-vlan structures (both per-port and global) so more
advanced features can be introduced and the vlan support can be
extended. I've tried to break this up but the moment net_port_vlans is
changed and the whole API goes away, thus this is a larger patch.
A few short goals of this patch are:
- Extensible per-vlan structs stored in rhashtables and a sorted list
- Keep user-visible behaviour (compressed vlans etc)
- Keep fastpath ingress/egress logic the same (optimizations to come
later)
Here's a brief list of some of the new features we'd like to introduce:
- per-vlan counters
- vlan ingress/egress mapping
- per-vlan igmp configuration
- vlan priorities
- avoid fdb entries replication (e.g. local fdb scaling issues)
The structure is kept single for both global and per-port entries so to
avoid code duplication where possible and also because we'll soon introduce
"port0 / aka bridge as port" which should simplify things further
(thanks to Vlad for the suggestion!).
Now we have per-vlan global rhashtable (bridge-wide) and per-vlan port
rhashtable, if an entry is added to a port it'll get a pointer to its
global context so it can be quickly accessed later. There's also a
sorted vlan list which is used for stable walks and some user-visible
behaviour such as the vlan ranges, also for error paths.
VLANs are stored in a "vlan group" which currently contains the
rhashtable, sorted vlan list and the number of "real" vlan entries.
A good side-effect of this change is that it resembles how hw keeps
per-vlan data.
One important note after this change is that if a VLAN is being looked up
in the bridge's rhashtable for filtering purposes (or to check if it's an
existing usable entry, not just a global context) then the new helper
br_vlan_should_use() needs to be used if the vlan is found. In case the
lookup is done only with a port's vlan group, then this check can be
skipped.
Things tested so far:
- basic vlan ingress/egress
- pvids
- untagged vlans
- undef CONFIG_BRIDGE_VLAN_FILTERING
- adding/deleting vlans in different scenarios (with/without global ctx,
while transmitting traffic, in ranges etc)
- loading/removing the module while having/adding/deleting vlans
- extracting bridge vlan information (user ABI), compressed requests
- adding/deleting fdbs on vlans
- bridge mac change, promisc mode
- default pvid change
- kmemleak ON during the whole time
Signed-off-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-09-25 17:00:11 +00:00
|
|
|
struct net_bridge_vlan_group *vg;
|
2015-08-03 11:29:16 +00:00
|
|
|
struct net_device *dev, *pdev;
|
2012-12-11 22:23:08 +00:00
|
|
|
struct br_mdb_entry *entry;
|
2015-08-03 11:29:16 +00:00
|
|
|
struct net_bridge_port *p;
|
bridge: vlan: add per-vlan struct and move to rhashtables
This patch changes the bridge vlan implementation to use rhashtables
instead of bitmaps. The main motivation behind this change is that we
need extensible per-vlan structures (both per-port and global) so more
advanced features can be introduced and the vlan support can be
extended. I've tried to break this up but the moment net_port_vlans is
changed and the whole API goes away, thus this is a larger patch.
A few short goals of this patch are:
- Extensible per-vlan structs stored in rhashtables and a sorted list
- Keep user-visible behaviour (compressed vlans etc)
- Keep fastpath ingress/egress logic the same (optimizations to come
later)
Here's a brief list of some of the new features we'd like to introduce:
- per-vlan counters
- vlan ingress/egress mapping
- per-vlan igmp configuration
- vlan priorities
- avoid fdb entries replication (e.g. local fdb scaling issues)
The structure is kept single for both global and per-port entries so to
avoid code duplication where possible and also because we'll soon introduce
"port0 / aka bridge as port" which should simplify things further
(thanks to Vlad for the suggestion!).
Now we have per-vlan global rhashtable (bridge-wide) and per-vlan port
rhashtable, if an entry is added to a port it'll get a pointer to its
global context so it can be quickly accessed later. There's also a
sorted vlan list which is used for stable walks and some user-visible
behaviour such as the vlan ranges, also for error paths.
VLANs are stored in a "vlan group" which currently contains the
rhashtable, sorted vlan list and the number of "real" vlan entries.
A good side-effect of this change is that it resembles how hw keeps
per-vlan data.
One important note after this change is that if a VLAN is being looked up
in the bridge's rhashtable for filtering purposes (or to check if it's an
existing usable entry, not just a global context) then the new helper
br_vlan_should_use() needs to be used if the vlan is found. In case the
lookup is done only with a port's vlan group, then this check can be
skipped.
Things tested so far:
- basic vlan ingress/egress
- pvids
- untagged vlans
- undef CONFIG_BRIDGE_VLAN_FILTERING
- adding/deleting vlans in different scenarios (with/without global ctx,
while transmitting traffic, in ranges etc)
- loading/removing the module while having/adding/deleting vlans
- extracting bridge vlan information (user ABI), compressed requests
- adding/deleting fdbs on vlans
- bridge mac change, promisc mode
- default pvid change
- kmemleak ON during the whole time
Signed-off-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-09-25 17:00:11 +00:00
|
|
|
struct net_bridge_vlan *v;
|
2012-12-11 22:23:08 +00:00
|
|
|
struct net_bridge *br;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = br_mdb_parse(skb, nlh, &dev, &entry);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
br = netdev_priv(dev);
|
|
|
|
|
2015-08-03 11:29:16 +00:00
|
|
|
/* If vlan filtering is enabled and VLAN is not specified
|
|
|
|
* delete mdb entry on all vlans configured on the port.
|
|
|
|
*/
|
|
|
|
pdev = __dev_get_by_index(net, entry->ifindex);
|
|
|
|
if (!pdev)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
p = br_port_get_rtnl(pdev);
|
|
|
|
if (!p || p->br != br || p->state == BR_STATE_DISABLED)
|
|
|
|
return -EINVAL;
|
|
|
|
|
bridge: vlan: add per-vlan struct and move to rhashtables
This patch changes the bridge vlan implementation to use rhashtables
instead of bitmaps. The main motivation behind this change is that we
need extensible per-vlan structures (both per-port and global) so more
advanced features can be introduced and the vlan support can be
extended. I've tried to break this up but the moment net_port_vlans is
changed and the whole API goes away, thus this is a larger patch.
A few short goals of this patch are:
- Extensible per-vlan structs stored in rhashtables and a sorted list
- Keep user-visible behaviour (compressed vlans etc)
- Keep fastpath ingress/egress logic the same (optimizations to come
later)
Here's a brief list of some of the new features we'd like to introduce:
- per-vlan counters
- vlan ingress/egress mapping
- per-vlan igmp configuration
- vlan priorities
- avoid fdb entries replication (e.g. local fdb scaling issues)
The structure is kept single for both global and per-port entries so to
avoid code duplication where possible and also because we'll soon introduce
"port0 / aka bridge as port" which should simplify things further
(thanks to Vlad for the suggestion!).
Now we have per-vlan global rhashtable (bridge-wide) and per-vlan port
rhashtable, if an entry is added to a port it'll get a pointer to its
global context so it can be quickly accessed later. There's also a
sorted vlan list which is used for stable walks and some user-visible
behaviour such as the vlan ranges, also for error paths.
VLANs are stored in a "vlan group" which currently contains the
rhashtable, sorted vlan list and the number of "real" vlan entries.
A good side-effect of this change is that it resembles how hw keeps
per-vlan data.
One important note after this change is that if a VLAN is being looked up
in the bridge's rhashtable for filtering purposes (or to check if it's an
existing usable entry, not just a global context) then the new helper
br_vlan_should_use() needs to be used if the vlan is found. In case the
lookup is done only with a port's vlan group, then this check can be
skipped.
Things tested so far:
- basic vlan ingress/egress
- pvids
- untagged vlans
- undef CONFIG_BRIDGE_VLAN_FILTERING
- adding/deleting vlans in different scenarios (with/without global ctx,
while transmitting traffic, in ranges etc)
- loading/removing the module while having/adding/deleting vlans
- extracting bridge vlan information (user ABI), compressed requests
- adding/deleting fdbs on vlans
- bridge mac change, promisc mode
- default pvid change
- kmemleak ON during the whole time
Signed-off-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-09-25 17:00:11 +00:00
|
|
|
vg = nbp_vlan_group(p);
|
|
|
|
if (br_vlan_enabled(br) && vg && entry->vid == 0) {
|
|
|
|
list_for_each_entry(v, &vg->vlan_list, vlist) {
|
|
|
|
entry->vid = v->vid;
|
2015-08-03 11:29:16 +00:00
|
|
|
err = __br_mdb_del(br, entry);
|
|
|
|
if (!err)
|
2016-04-21 10:52:45 +00:00
|
|
|
__br_mdb_notify(dev, p, entry, RTM_DELMDB);
|
2015-08-03 11:29:16 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
err = __br_mdb_del(br, entry);
|
|
|
|
if (!err)
|
2016-04-21 10:52:45 +00:00
|
|
|
__br_mdb_notify(dev, p, entry, RTM_DELMDB);
|
2015-08-03 11:29:16 +00:00
|
|
|
}
|
|
|
|
|
2012-12-11 22:23:08 +00:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2012-12-07 00:04:48 +00:00
|
|
|
void br_mdb_init(void)
|
|
|
|
{
|
|
|
|
rtnl_register(PF_BRIDGE, RTM_GETMDB, NULL, br_mdb_dump, NULL);
|
2012-12-11 22:23:08 +00:00
|
|
|
rtnl_register(PF_BRIDGE, RTM_NEWMDB, br_mdb_add, NULL, NULL);
|
|
|
|
rtnl_register(PF_BRIDGE, RTM_DELMDB, br_mdb_del, NULL, NULL);
|
2012-12-07 00:04:48 +00:00
|
|
|
}
|
2012-12-19 09:13:48 +00:00
|
|
|
|
|
|
|
void br_mdb_uninit(void)
|
|
|
|
{
|
|
|
|
rtnl_unregister(PF_BRIDGE, RTM_GETMDB);
|
|
|
|
rtnl_unregister(PF_BRIDGE, RTM_NEWMDB);
|
|
|
|
rtnl_unregister(PF_BRIDGE, RTM_DELMDB);
|
|
|
|
}
|