|
@@ -33,6 +33,9 @@
|
|
|
|
|
|
#include "br_private.h"
|
|
|
|
|
|
+#define mlock_dereference(X, br) \
|
|
|
+ rcu_dereference_protected(X, lockdep_is_held(&br->multicast_lock))
|
|
|
+
|
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
|
static inline int ipv6_is_local_multicast(const struct in6_addr *addr)
|
|
|
{
|
|
@@ -135,7 +138,7 @@ static struct net_bridge_mdb_entry *br_mdb_ip6_get(
|
|
|
struct net_bridge_mdb_entry *br_mdb_get(struct net_bridge *br,
|
|
|
struct sk_buff *skb)
|
|
|
{
|
|
|
- struct net_bridge_mdb_htable *mdb = br->mdb;
|
|
|
+ struct net_bridge_mdb_htable *mdb = rcu_dereference(br->mdb);
|
|
|
struct br_ip ip;
|
|
|
|
|
|
if (br->multicast_disabled)
|
|
@@ -235,7 +238,8 @@ static void br_multicast_group_expired(unsigned long data)
|
|
|
if (mp->ports)
|
|
|
goto out;
|
|
|
|
|
|
- mdb = br->mdb;
|
|
|
+ mdb = mlock_dereference(br->mdb, br);
|
|
|
+
|
|
|
hlist_del_rcu(&mp->hlist[mdb->ver]);
|
|
|
mdb->size--;
|
|
|
|
|
@@ -249,16 +253,20 @@ out:
|
|
|
static void br_multicast_del_pg(struct net_bridge *br,
|
|
|
struct net_bridge_port_group *pg)
|
|
|
{
|
|
|
- struct net_bridge_mdb_htable *mdb = br->mdb;
|
|
|
+ struct net_bridge_mdb_htable *mdb;
|
|
|
struct net_bridge_mdb_entry *mp;
|
|
|
struct net_bridge_port_group *p;
|
|
|
- struct net_bridge_port_group **pp;
|
|
|
+ struct net_bridge_port_group __rcu **pp;
|
|
|
+
|
|
|
+ mdb = mlock_dereference(br->mdb, br);
|
|
|
|
|
|
mp = br_mdb_ip_get(mdb, &pg->addr);
|
|
|
if (WARN_ON(!mp))
|
|
|
return;
|
|
|
|
|
|
- for (pp = &mp->ports; (p = *pp); pp = &p->next) {
|
|
|
+ for (pp = &mp->ports;
|
|
|
+ (p = mlock_dereference(*pp, br)) != NULL;
|
|
|
+ pp = &p->next) {
|
|
|
if (p != pg)
|
|
|
continue;
|
|
|
|
|
@@ -294,10 +302,10 @@ out:
|
|
|
spin_unlock(&br->multicast_lock);
|
|
|
}
|
|
|
|
|
|
-static int br_mdb_rehash(struct net_bridge_mdb_htable **mdbp, int max,
|
|
|
+static int br_mdb_rehash(struct net_bridge_mdb_htable __rcu **mdbp, int max,
|
|
|
int elasticity)
|
|
|
{
|
|
|
- struct net_bridge_mdb_htable *old = *mdbp;
|
|
|
+ struct net_bridge_mdb_htable *old = rcu_dereference_protected(*mdbp, 1);
|
|
|
struct net_bridge_mdb_htable *mdb;
|
|
|
int err;
|
|
|
|
|
@@ -569,7 +577,7 @@ static struct net_bridge_mdb_entry *br_multicast_get_group(
|
|
|
struct net_bridge *br, struct net_bridge_port *port,
|
|
|
struct br_ip *group, int hash)
|
|
|
{
|
|
|
- struct net_bridge_mdb_htable *mdb = br->mdb;
|
|
|
+ struct net_bridge_mdb_htable *mdb;
|
|
|
struct net_bridge_mdb_entry *mp;
|
|
|
struct hlist_node *p;
|
|
|
unsigned count = 0;
|
|
@@ -577,6 +585,7 @@ static struct net_bridge_mdb_entry *br_multicast_get_group(
|
|
|
int elasticity;
|
|
|
int err;
|
|
|
|
|
|
+ mdb = rcu_dereference_protected(br->mdb, 1);
|
|
|
hlist_for_each_entry(mp, p, &mdb->mhash[hash], hlist[mdb->ver]) {
|
|
|
count++;
|
|
|
if (unlikely(br_ip_equal(group, &mp->addr)))
|
|
@@ -642,10 +651,11 @@ static struct net_bridge_mdb_entry *br_multicast_new_group(
|
|
|
struct net_bridge *br, struct net_bridge_port *port,
|
|
|
struct br_ip *group)
|
|
|
{
|
|
|
- struct net_bridge_mdb_htable *mdb = br->mdb;
|
|
|
+ struct net_bridge_mdb_htable *mdb;
|
|
|
struct net_bridge_mdb_entry *mp;
|
|
|
int hash;
|
|
|
|
|
|
+ mdb = rcu_dereference_protected(br->mdb, 1);
|
|
|
if (!mdb) {
|
|
|
if (br_mdb_rehash(&br->mdb, BR_HASH_SIZE, 0))
|
|
|
return NULL;
|
|
@@ -660,7 +670,7 @@ static struct net_bridge_mdb_entry *br_multicast_new_group(
|
|
|
|
|
|
case -EAGAIN:
|
|
|
rehash:
|
|
|
- mdb = br->mdb;
|
|
|
+ mdb = rcu_dereference_protected(br->mdb, 1);
|
|
|
hash = br_ip_hash(mdb, group);
|
|
|
break;
|
|
|
|
|
@@ -692,7 +702,7 @@ static int br_multicast_add_group(struct net_bridge *br,
|
|
|
{
|
|
|
struct net_bridge_mdb_entry *mp;
|
|
|
struct net_bridge_port_group *p;
|
|
|
- struct net_bridge_port_group **pp;
|
|
|
+ struct net_bridge_port_group __rcu **pp;
|
|
|
unsigned long now = jiffies;
|
|
|
int err;
|
|
|
|
|
@@ -712,7 +722,9 @@ static int br_multicast_add_group(struct net_bridge *br,
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
- for (pp = &mp->ports; (p = *pp); pp = &p->next) {
|
|
|
+ for (pp = &mp->ports;
|
|
|
+ (p = mlock_dereference(*pp, br)) != NULL;
|
|
|
+ pp = &p->next) {
|
|
|
if (p->port == port)
|
|
|
goto found;
|
|
|
if ((unsigned long)p->port < (unsigned long)port)
|
|
@@ -1106,7 +1118,7 @@ static int br_ip4_multicast_query(struct net_bridge *br,
|
|
|
struct net_bridge_mdb_entry *mp;
|
|
|
struct igmpv3_query *ih3;
|
|
|
struct net_bridge_port_group *p;
|
|
|
- struct net_bridge_port_group **pp;
|
|
|
+ struct net_bridge_port_group __rcu **pp;
|
|
|
unsigned long max_delay;
|
|
|
unsigned long now = jiffies;
|
|
|
__be32 group;
|
|
@@ -1145,7 +1157,7 @@ static int br_ip4_multicast_query(struct net_bridge *br,
|
|
|
if (!group)
|
|
|
goto out;
|
|
|
|
|
|
- mp = br_mdb_ip4_get(br->mdb, group);
|
|
|
+ mp = br_mdb_ip4_get(mlock_dereference(br->mdb, br), group);
|
|
|
if (!mp)
|
|
|
goto out;
|
|
|
|
|
@@ -1157,7 +1169,9 @@ static int br_ip4_multicast_query(struct net_bridge *br,
|
|
|
try_to_del_timer_sync(&mp->timer) >= 0))
|
|
|
mod_timer(&mp->timer, now + max_delay);
|
|
|
|
|
|
- for (pp = &mp->ports; (p = *pp); pp = &p->next) {
|
|
|
+ for (pp = &mp->ports;
|
|
|
+ (p = mlock_dereference(*pp, br)) != NULL;
|
|
|
+ pp = &p->next) {
|
|
|
if (timer_pending(&p->timer) ?
|
|
|
time_after(p->timer.expires, now + max_delay) :
|
|
|
try_to_del_timer_sync(&p->timer) >= 0)
|
|
@@ -1178,7 +1192,8 @@ static int br_ip6_multicast_query(struct net_bridge *br,
|
|
|
struct mld_msg *mld = (struct mld_msg *) icmp6_hdr(skb);
|
|
|
struct net_bridge_mdb_entry *mp;
|
|
|
struct mld2_query *mld2q;
|
|
|
- struct net_bridge_port_group *p, **pp;
|
|
|
+ struct net_bridge_port_group *p;
|
|
|
+ struct net_bridge_port_group __rcu **pp;
|
|
|
unsigned long max_delay;
|
|
|
unsigned long now = jiffies;
|
|
|
struct in6_addr *group = NULL;
|
|
@@ -1214,7 +1229,7 @@ static int br_ip6_multicast_query(struct net_bridge *br,
|
|
|
if (!group)
|
|
|
goto out;
|
|
|
|
|
|
- mp = br_mdb_ip6_get(br->mdb, group);
|
|
|
+ mp = br_mdb_ip6_get(mlock_dereference(br->mdb, br), group);
|
|
|
if (!mp)
|
|
|
goto out;
|
|
|
|
|
@@ -1225,7 +1240,9 @@ static int br_ip6_multicast_query(struct net_bridge *br,
|
|
|
try_to_del_timer_sync(&mp->timer) >= 0))
|
|
|
mod_timer(&mp->timer, now + max_delay);
|
|
|
|
|
|
- for (pp = &mp->ports; (p = *pp); pp = &p->next) {
|
|
|
+ for (pp = &mp->ports;
|
|
|
+ (p = mlock_dereference(*pp, br)) != NULL;
|
|
|
+ pp = &p->next) {
|
|
|
if (timer_pending(&p->timer) ?
|
|
|
time_after(p->timer.expires, now + max_delay) :
|
|
|
try_to_del_timer_sync(&p->timer) >= 0)
|
|
@@ -1254,7 +1271,7 @@ static void br_multicast_leave_group(struct net_bridge *br,
|
|
|
timer_pending(&br->multicast_querier_timer))
|
|
|
goto out;
|
|
|
|
|
|
- mdb = br->mdb;
|
|
|
+ mdb = mlock_dereference(br->mdb, br);
|
|
|
mp = br_mdb_ip_get(mdb, group);
|
|
|
if (!mp)
|
|
|
goto out;
|
|
@@ -1277,7 +1294,9 @@ static void br_multicast_leave_group(struct net_bridge *br,
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
- for (p = mp->ports; p; p = p->next) {
|
|
|
+ for (p = mlock_dereference(mp->ports, br);
|
|
|
+ p != NULL;
|
|
|
+ p = mlock_dereference(p->next, br)) {
|
|
|
if (p->port != port)
|
|
|
continue;
|
|
|
|
|
@@ -1625,7 +1644,7 @@ void br_multicast_stop(struct net_bridge *br)
|
|
|
del_timer_sync(&br->multicast_query_timer);
|
|
|
|
|
|
spin_lock_bh(&br->multicast_lock);
|
|
|
- mdb = br->mdb;
|
|
|
+ mdb = mlock_dereference(br->mdb, br);
|
|
|
if (!mdb)
|
|
|
goto out;
|
|
|
|
|
@@ -1729,6 +1748,7 @@ int br_multicast_toggle(struct net_bridge *br, unsigned long val)
|
|
|
{
|
|
|
struct net_bridge_port *port;
|
|
|
int err = 0;
|
|
|
+ struct net_bridge_mdb_htable *mdb;
|
|
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
|
if (br->multicast_disabled == !val)
|
|
@@ -1741,15 +1761,16 @@ int br_multicast_toggle(struct net_bridge *br, unsigned long val)
|
|
|
if (!netif_running(br->dev))
|
|
|
goto unlock;
|
|
|
|
|
|
- if (br->mdb) {
|
|
|
- if (br->mdb->old) {
|
|
|
+ mdb = mlock_dereference(br->mdb, br);
|
|
|
+ if (mdb) {
|
|
|
+ if (mdb->old) {
|
|
|
err = -EEXIST;
|
|
|
rollback:
|
|
|
br->multicast_disabled = !!val;
|
|
|
goto unlock;
|
|
|
}
|
|
|
|
|
|
- err = br_mdb_rehash(&br->mdb, br->mdb->max,
|
|
|
+ err = br_mdb_rehash(&br->mdb, mdb->max,
|
|
|
br->hash_elasticity);
|
|
|
if (err)
|
|
|
goto rollback;
|
|
@@ -1774,6 +1795,7 @@ int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val)
|
|
|
{
|
|
|
int err = -ENOENT;
|
|
|
u32 old;
|
|
|
+ struct net_bridge_mdb_htable *mdb;
|
|
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
|
if (!netif_running(br->dev))
|
|
@@ -1782,7 +1804,9 @@ int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val)
|
|
|
err = -EINVAL;
|
|
|
if (!is_power_of_2(val))
|
|
|
goto unlock;
|
|
|
- if (br->mdb && val < br->mdb->size)
|
|
|
+
|
|
|
+ mdb = mlock_dereference(br->mdb, br);
|
|
|
+ if (mdb && val < mdb->size)
|
|
|
goto unlock;
|
|
|
|
|
|
err = 0;
|
|
@@ -1790,8 +1814,8 @@ int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val)
|
|
|
old = br->hash_max;
|
|
|
br->hash_max = val;
|
|
|
|
|
|
- if (br->mdb) {
|
|
|
- if (br->mdb->old) {
|
|
|
+ if (mdb) {
|
|
|
+ if (mdb->old) {
|
|
|
err = -EEXIST;
|
|
|
rollback:
|
|
|
br->hash_max = old;
|