mirror of
https://github.com/torvalds/linux.git
synced 2024-11-02 02:01:29 +00:00
b195167fcf
This patch allows the user to control the hash elasticity/max parameters. The elasticity setting does not take effect until the next new multicast group is added. At which point it is checked and if after rehashing it still can't be satisfied then snooping will be disabled. The max setting on the other hand takes effect immediately. It must be a power of two and cannot be set to a value less than the current number of multicast group entries. This is the only way to shrink the multicast hash. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Signed-off-by: David S. Miller <davem@davemloft.net>
1305 lines
28 KiB
C
1305 lines
28 KiB
C
/*
|
|
* Bridge multicast support.
|
|
*
|
|
* Copyright (c) 2010 Herbert Xu <herbert@gondor.apana.org.au>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/if_ether.h>
|
|
#include <linux/igmp.h>
|
|
#include <linux/jhash.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/log2.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/netfilter_bridge.h>
|
|
#include <linux/random.h>
|
|
#include <linux/rculist.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/timer.h>
|
|
#include <net/ip.h>
|
|
|
|
#include "br_private.h"
|
|
|
|
static inline int br_ip_hash(struct net_bridge_mdb_htable *mdb, __be32 ip)
|
|
{
|
|
return jhash_1word(mdb->secret, (u32)ip) & (mdb->max - 1);
|
|
}
|
|
|
|
static struct net_bridge_mdb_entry *__br_mdb_ip_get(
|
|
struct net_bridge_mdb_htable *mdb, __be32 dst, int hash)
|
|
{
|
|
struct net_bridge_mdb_entry *mp;
|
|
struct hlist_node *p;
|
|
|
|
hlist_for_each_entry(mp, p, &mdb->mhash[hash], hlist[mdb->ver]) {
|
|
if (dst == mp->addr)
|
|
return mp;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct net_bridge_mdb_entry *br_mdb_ip_get(
|
|
struct net_bridge_mdb_htable *mdb, __be32 dst)
|
|
{
|
|
return __br_mdb_ip_get(mdb, dst, br_ip_hash(mdb, dst));
|
|
}
|
|
|
|
struct net_bridge_mdb_entry *br_mdb_get(struct net_bridge *br,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct net_bridge_mdb_htable *mdb = br->mdb;
|
|
|
|
if (!mdb || br->multicast_disabled)
|
|
return NULL;
|
|
|
|
switch (skb->protocol) {
|
|
case htons(ETH_P_IP):
|
|
if (BR_INPUT_SKB_CB(skb)->igmp)
|
|
break;
|
|
return br_mdb_ip_get(mdb, ip_hdr(skb)->daddr);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void br_mdb_free(struct rcu_head *head)
|
|
{
|
|
struct net_bridge_mdb_htable *mdb =
|
|
container_of(head, struct net_bridge_mdb_htable, rcu);
|
|
struct net_bridge_mdb_htable *old = mdb->old;
|
|
|
|
mdb->old = NULL;
|
|
kfree(old->mhash);
|
|
kfree(old);
|
|
}
|
|
|
|
static int br_mdb_copy(struct net_bridge_mdb_htable *new,
|
|
struct net_bridge_mdb_htable *old,
|
|
int elasticity)
|
|
{
|
|
struct net_bridge_mdb_entry *mp;
|
|
struct hlist_node *p;
|
|
int maxlen;
|
|
int len;
|
|
int i;
|
|
|
|
for (i = 0; i < old->max; i++)
|
|
hlist_for_each_entry(mp, p, &old->mhash[i], hlist[old->ver])
|
|
hlist_add_head(&mp->hlist[new->ver],
|
|
&new->mhash[br_ip_hash(new, mp->addr)]);
|
|
|
|
if (!elasticity)
|
|
return 0;
|
|
|
|
maxlen = 0;
|
|
for (i = 0; i < new->max; i++) {
|
|
len = 0;
|
|
hlist_for_each_entry(mp, p, &new->mhash[i], hlist[new->ver])
|
|
len++;
|
|
if (len > maxlen)
|
|
maxlen = len;
|
|
}
|
|
|
|
return maxlen > elasticity ? -EINVAL : 0;
|
|
}
|
|
|
|
static void br_multicast_free_pg(struct rcu_head *head)
|
|
{
|
|
struct net_bridge_port_group *p =
|
|
container_of(head, struct net_bridge_port_group, rcu);
|
|
|
|
kfree(p);
|
|
}
|
|
|
|
static void br_multicast_free_group(struct rcu_head *head)
|
|
{
|
|
struct net_bridge_mdb_entry *mp =
|
|
container_of(head, struct net_bridge_mdb_entry, rcu);
|
|
|
|
kfree(mp);
|
|
}
|
|
|
|
static void br_multicast_group_expired(unsigned long data)
|
|
{
|
|
struct net_bridge_mdb_entry *mp = (void *)data;
|
|
struct net_bridge *br = mp->br;
|
|
struct net_bridge_mdb_htable *mdb;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
if (!netif_running(br->dev) || timer_pending(&mp->timer))
|
|
goto out;
|
|
|
|
if (!hlist_unhashed(&mp->mglist))
|
|
hlist_del_init(&mp->mglist);
|
|
|
|
if (mp->ports)
|
|
goto out;
|
|
|
|
mdb = br->mdb;
|
|
hlist_del_rcu(&mp->hlist[mdb->ver]);
|
|
mdb->size--;
|
|
|
|
del_timer(&mp->query_timer);
|
|
call_rcu_bh(&mp->rcu, br_multicast_free_group);
|
|
|
|
out:
|
|
spin_unlock(&br->multicast_lock);
|
|
}
|
|
|
|
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_entry *mp;
|
|
struct net_bridge_port_group *p;
|
|
struct net_bridge_port_group **pp;
|
|
|
|
mp = br_mdb_ip_get(mdb, pg->addr);
|
|
if (WARN_ON(!mp))
|
|
return;
|
|
|
|
for (pp = &mp->ports; (p = *pp); pp = &p->next) {
|
|
if (p != pg)
|
|
continue;
|
|
|
|
*pp = p->next;
|
|
hlist_del_init(&p->mglist);
|
|
del_timer(&p->timer);
|
|
del_timer(&p->query_timer);
|
|
call_rcu_bh(&p->rcu, br_multicast_free_pg);
|
|
|
|
if (!mp->ports && hlist_unhashed(&mp->mglist) &&
|
|
netif_running(br->dev))
|
|
mod_timer(&mp->timer, jiffies);
|
|
|
|
return;
|
|
}
|
|
|
|
WARN_ON(1);
|
|
}
|
|
|
|
static void br_multicast_port_group_expired(unsigned long data)
|
|
{
|
|
struct net_bridge_port_group *pg = (void *)data;
|
|
struct net_bridge *br = pg->port->br;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
if (!netif_running(br->dev) || timer_pending(&pg->timer) ||
|
|
hlist_unhashed(&pg->mglist))
|
|
goto out;
|
|
|
|
br_multicast_del_pg(br, pg);
|
|
|
|
out:
|
|
spin_unlock(&br->multicast_lock);
|
|
}
|
|
|
|
static int br_mdb_rehash(struct net_bridge_mdb_htable **mdbp, int max,
|
|
int elasticity)
|
|
{
|
|
struct net_bridge_mdb_htable *old = *mdbp;
|
|
struct net_bridge_mdb_htable *mdb;
|
|
int err;
|
|
|
|
mdb = kmalloc(sizeof(*mdb), GFP_ATOMIC);
|
|
if (!mdb)
|
|
return -ENOMEM;
|
|
|
|
mdb->max = max;
|
|
mdb->old = old;
|
|
|
|
mdb->mhash = kzalloc(max * sizeof(*mdb->mhash), GFP_ATOMIC);
|
|
if (!mdb->mhash) {
|
|
kfree(mdb);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
mdb->size = old ? old->size : 0;
|
|
mdb->ver = old ? old->ver ^ 1 : 0;
|
|
|
|
if (!old || elasticity)
|
|
get_random_bytes(&mdb->secret, sizeof(mdb->secret));
|
|
else
|
|
mdb->secret = old->secret;
|
|
|
|
if (!old)
|
|
goto out;
|
|
|
|
err = br_mdb_copy(mdb, old, elasticity);
|
|
if (err) {
|
|
kfree(mdb->mhash);
|
|
kfree(mdb);
|
|
return err;
|
|
}
|
|
|
|
call_rcu_bh(&mdb->rcu, br_mdb_free);
|
|
|
|
out:
|
|
rcu_assign_pointer(*mdbp, mdb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br,
|
|
__be32 group)
|
|
{
|
|
struct sk_buff *skb;
|
|
struct igmphdr *ih;
|
|
struct ethhdr *eth;
|
|
struct iphdr *iph;
|
|
|
|
skb = netdev_alloc_skb_ip_align(br->dev, sizeof(*eth) + sizeof(*iph) +
|
|
sizeof(*ih) + 4);
|
|
if (!skb)
|
|
goto out;
|
|
|
|
skb->protocol = htons(ETH_P_IP);
|
|
|
|
skb_reset_mac_header(skb);
|
|
eth = eth_hdr(skb);
|
|
|
|
memcpy(eth->h_source, br->dev->dev_addr, 6);
|
|
eth->h_dest[0] = 1;
|
|
eth->h_dest[1] = 0;
|
|
eth->h_dest[2] = 0x5e;
|
|
eth->h_dest[3] = 0;
|
|
eth->h_dest[4] = 0;
|
|
eth->h_dest[5] = 1;
|
|
eth->h_proto = htons(ETH_P_IP);
|
|
skb_put(skb, sizeof(*eth));
|
|
|
|
skb_set_network_header(skb, skb->len);
|
|
iph = ip_hdr(skb);
|
|
|
|
iph->version = 4;
|
|
iph->ihl = 6;
|
|
iph->tos = 0xc0;
|
|
iph->tot_len = htons(sizeof(*iph) + sizeof(*ih) + 4);
|
|
iph->id = 0;
|
|
iph->frag_off = htons(IP_DF);
|
|
iph->ttl = 1;
|
|
iph->protocol = IPPROTO_IGMP;
|
|
iph->saddr = 0;
|
|
iph->daddr = htonl(INADDR_ALLHOSTS_GROUP);
|
|
((u8 *)&iph[1])[0] = IPOPT_RA;
|
|
((u8 *)&iph[1])[1] = 4;
|
|
((u8 *)&iph[1])[2] = 0;
|
|
((u8 *)&iph[1])[3] = 0;
|
|
ip_send_check(iph);
|
|
skb_put(skb, 24);
|
|
|
|
skb_set_transport_header(skb, skb->len);
|
|
ih = igmp_hdr(skb);
|
|
ih->type = IGMP_HOST_MEMBERSHIP_QUERY;
|
|
ih->code = (group ? br->multicast_last_member_interval :
|
|
br->multicast_query_response_interval) /
|
|
(HZ / IGMP_TIMER_SCALE);
|
|
ih->group = group;
|
|
ih->csum = 0;
|
|
ih->csum = ip_compute_csum((void *)ih, sizeof(struct igmphdr));
|
|
skb_put(skb, sizeof(*ih));
|
|
|
|
__skb_pull(skb, sizeof(*eth));
|
|
|
|
out:
|
|
return skb;
|
|
}
|
|
|
|
static void br_multicast_send_group_query(struct net_bridge_mdb_entry *mp)
|
|
{
|
|
struct net_bridge *br = mp->br;
|
|
struct sk_buff *skb;
|
|
|
|
skb = br_multicast_alloc_query(br, mp->addr);
|
|
if (!skb)
|
|
goto timer;
|
|
|
|
netif_rx(skb);
|
|
|
|
timer:
|
|
if (++mp->queries_sent < br->multicast_last_member_count)
|
|
mod_timer(&mp->query_timer,
|
|
jiffies + br->multicast_last_member_interval);
|
|
}
|
|
|
|
static void br_multicast_group_query_expired(unsigned long data)
|
|
{
|
|
struct net_bridge_mdb_entry *mp = (void *)data;
|
|
struct net_bridge *br = mp->br;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
if (!netif_running(br->dev) || hlist_unhashed(&mp->mglist) ||
|
|
mp->queries_sent >= br->multicast_last_member_count)
|
|
goto out;
|
|
|
|
br_multicast_send_group_query(mp);
|
|
|
|
out:
|
|
spin_unlock(&br->multicast_lock);
|
|
}
|
|
|
|
static void br_multicast_send_port_group_query(struct net_bridge_port_group *pg)
|
|
{
|
|
struct net_bridge_port *port = pg->port;
|
|
struct net_bridge *br = port->br;
|
|
struct sk_buff *skb;
|
|
|
|
skb = br_multicast_alloc_query(br, pg->addr);
|
|
if (!skb)
|
|
goto timer;
|
|
|
|
br_deliver(port, skb);
|
|
|
|
timer:
|
|
if (++pg->queries_sent < br->multicast_last_member_count)
|
|
mod_timer(&pg->query_timer,
|
|
jiffies + br->multicast_last_member_interval);
|
|
}
|
|
|
|
static void br_multicast_port_group_query_expired(unsigned long data)
|
|
{
|
|
struct net_bridge_port_group *pg = (void *)data;
|
|
struct net_bridge_port *port = pg->port;
|
|
struct net_bridge *br = port->br;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
if (!netif_running(br->dev) || hlist_unhashed(&pg->mglist) ||
|
|
pg->queries_sent >= br->multicast_last_member_count)
|
|
goto out;
|
|
|
|
br_multicast_send_port_group_query(pg);
|
|
|
|
out:
|
|
spin_unlock(&br->multicast_lock);
|
|
}
|
|
|
|
static struct net_bridge_mdb_entry *br_multicast_get_group(
|
|
struct net_bridge *br, struct net_bridge_port *port, __be32 group,
|
|
int hash)
|
|
{
|
|
struct net_bridge_mdb_htable *mdb = br->mdb;
|
|
struct net_bridge_mdb_entry *mp;
|
|
struct hlist_node *p;
|
|
unsigned count = 0;
|
|
unsigned max;
|
|
int elasticity;
|
|
int err;
|
|
|
|
hlist_for_each_entry(mp, p, &mdb->mhash[hash], hlist[mdb->ver]) {
|
|
count++;
|
|
if (unlikely(group == mp->addr)) {
|
|
return mp;
|
|
}
|
|
}
|
|
|
|
elasticity = 0;
|
|
max = mdb->max;
|
|
|
|
if (unlikely(count > br->hash_elasticity && count)) {
|
|
if (net_ratelimit())
|
|
printk(KERN_INFO "%s: Multicast hash table "
|
|
"chain limit reached: %s\n",
|
|
br->dev->name, port ? port->dev->name :
|
|
br->dev->name);
|
|
|
|
elasticity = br->hash_elasticity;
|
|
}
|
|
|
|
if (mdb->size >= max) {
|
|
max *= 2;
|
|
if (unlikely(max >= br->hash_max)) {
|
|
printk(KERN_WARNING "%s: Multicast hash table maximum "
|
|
"reached, disabling snooping: %s, %d\n",
|
|
br->dev->name, port ? port->dev->name :
|
|
br->dev->name,
|
|
max);
|
|
err = -E2BIG;
|
|
disable:
|
|
br->multicast_disabled = 1;
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
if (max > mdb->max || elasticity) {
|
|
if (mdb->old) {
|
|
if (net_ratelimit())
|
|
printk(KERN_INFO "%s: Multicast hash table "
|
|
"on fire: %s\n",
|
|
br->dev->name, port ? port->dev->name :
|
|
br->dev->name);
|
|
err = -EEXIST;
|
|
goto err;
|
|
}
|
|
|
|
err = br_mdb_rehash(&br->mdb, max, elasticity);
|
|
if (err) {
|
|
printk(KERN_WARNING "%s: Cannot rehash multicast "
|
|
"hash table, disabling snooping: "
|
|
"%s, %d, %d\n",
|
|
br->dev->name, port ? port->dev->name :
|
|
br->dev->name,
|
|
mdb->size, err);
|
|
goto disable;
|
|
}
|
|
|
|
err = -EAGAIN;
|
|
goto err;
|
|
}
|
|
|
|
return NULL;
|
|
|
|
err:
|
|
mp = ERR_PTR(err);
|
|
return mp;
|
|
}
|
|
|
|
static struct net_bridge_mdb_entry *br_multicast_new_group(
|
|
struct net_bridge *br, struct net_bridge_port *port, __be32 group)
|
|
{
|
|
struct net_bridge_mdb_htable *mdb = br->mdb;
|
|
struct net_bridge_mdb_entry *mp;
|
|
int hash;
|
|
|
|
if (!mdb) {
|
|
if (br_mdb_rehash(&br->mdb, BR_HASH_SIZE, 0))
|
|
return NULL;
|
|
goto rehash;
|
|
}
|
|
|
|
hash = br_ip_hash(mdb, group);
|
|
mp = br_multicast_get_group(br, port, group, hash);
|
|
switch (PTR_ERR(mp)) {
|
|
case 0:
|
|
break;
|
|
|
|
case -EAGAIN:
|
|
rehash:
|
|
mdb = br->mdb;
|
|
hash = br_ip_hash(mdb, group);
|
|
break;
|
|
|
|
default:
|
|
goto out;
|
|
}
|
|
|
|
mp = kzalloc(sizeof(*mp), GFP_ATOMIC);
|
|
if (unlikely(!mp))
|
|
goto out;
|
|
|
|
mp->br = br;
|
|
mp->addr = group;
|
|
setup_timer(&mp->timer, br_multicast_group_expired,
|
|
(unsigned long)mp);
|
|
setup_timer(&mp->query_timer, br_multicast_group_query_expired,
|
|
(unsigned long)mp);
|
|
|
|
hlist_add_head_rcu(&mp->hlist[mdb->ver], &mdb->mhash[hash]);
|
|
mdb->size++;
|
|
|
|
out:
|
|
return mp;
|
|
}
|
|
|
|
static int br_multicast_add_group(struct net_bridge *br,
|
|
struct net_bridge_port *port, __be32 group)
|
|
{
|
|
struct net_bridge_mdb_entry *mp;
|
|
struct net_bridge_port_group *p;
|
|
struct net_bridge_port_group **pp;
|
|
unsigned long now = jiffies;
|
|
int err;
|
|
|
|
if (ipv4_is_local_multicast(group))
|
|
return 0;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
if (!netif_running(br->dev) ||
|
|
(port && port->state == BR_STATE_DISABLED))
|
|
goto out;
|
|
|
|
mp = br_multicast_new_group(br, port, group);
|
|
err = PTR_ERR(mp);
|
|
if (unlikely(IS_ERR(mp) || !mp))
|
|
goto err;
|
|
|
|
if (!port) {
|
|
hlist_add_head(&mp->mglist, &br->mglist);
|
|
mod_timer(&mp->timer, now + br->multicast_membership_interval);
|
|
goto out;
|
|
}
|
|
|
|
for (pp = &mp->ports; (p = *pp); pp = &p->next) {
|
|
if (p->port == port)
|
|
goto found;
|
|
if ((unsigned long)p->port < (unsigned long)port)
|
|
break;
|
|
}
|
|
|
|
p = kzalloc(sizeof(*p), GFP_ATOMIC);
|
|
err = -ENOMEM;
|
|
if (unlikely(!p))
|
|
goto err;
|
|
|
|
p->addr = group;
|
|
p->port = port;
|
|
p->next = *pp;
|
|
hlist_add_head(&p->mglist, &port->mglist);
|
|
setup_timer(&p->timer, br_multicast_port_group_expired,
|
|
(unsigned long)p);
|
|
setup_timer(&p->query_timer, br_multicast_port_group_query_expired,
|
|
(unsigned long)p);
|
|
|
|
rcu_assign_pointer(*pp, p);
|
|
|
|
found:
|
|
mod_timer(&p->timer, now + br->multicast_membership_interval);
|
|
out:
|
|
err = 0;
|
|
|
|
err:
|
|
spin_unlock(&br->multicast_lock);
|
|
return err;
|
|
}
|
|
|
|
static void br_multicast_router_expired(unsigned long data)
|
|
{
|
|
struct net_bridge_port *port = (void *)data;
|
|
struct net_bridge *br = port->br;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
if (port->multicast_router != 1 ||
|
|
timer_pending(&port->multicast_router_timer) ||
|
|
hlist_unhashed(&port->rlist))
|
|
goto out;
|
|
|
|
hlist_del_init_rcu(&port->rlist);
|
|
|
|
out:
|
|
spin_unlock(&br->multicast_lock);
|
|
}
|
|
|
|
static void br_multicast_local_router_expired(unsigned long data)
|
|
{
|
|
}
|
|
|
|
static void br_multicast_send_query(struct net_bridge *br,
|
|
struct net_bridge_port *port, u32 sent)
|
|
{
|
|
unsigned long time;
|
|
struct sk_buff *skb;
|
|
|
|
if (!netif_running(br->dev) || br->multicast_disabled ||
|
|
timer_pending(&br->multicast_querier_timer))
|
|
return;
|
|
|
|
skb = br_multicast_alloc_query(br, 0);
|
|
if (!skb)
|
|
goto timer;
|
|
|
|
if (port) {
|
|
__skb_push(skb, sizeof(struct ethhdr));
|
|
skb->dev = port->dev;
|
|
NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_OUT, skb, NULL, skb->dev,
|
|
dev_queue_xmit);
|
|
} else
|
|
netif_rx(skb);
|
|
|
|
timer:
|
|
time = jiffies;
|
|
time += sent < br->multicast_startup_query_count ?
|
|
br->multicast_startup_query_interval :
|
|
br->multicast_query_interval;
|
|
mod_timer(port ? &port->multicast_query_timer :
|
|
&br->multicast_query_timer, time);
|
|
}
|
|
|
|
static void br_multicast_port_query_expired(unsigned long data)
|
|
{
|
|
struct net_bridge_port *port = (void *)data;
|
|
struct net_bridge *br = port->br;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
if (port && (port->state == BR_STATE_DISABLED ||
|
|
port->state == BR_STATE_BLOCKING))
|
|
goto out;
|
|
|
|
if (port->multicast_startup_queries_sent <
|
|
br->multicast_startup_query_count)
|
|
port->multicast_startup_queries_sent++;
|
|
|
|
br_multicast_send_query(port->br, port,
|
|
port->multicast_startup_queries_sent);
|
|
|
|
out:
|
|
spin_unlock(&br->multicast_lock);
|
|
}
|
|
|
|
void br_multicast_add_port(struct net_bridge_port *port)
|
|
{
|
|
port->multicast_router = 1;
|
|
|
|
setup_timer(&port->multicast_router_timer, br_multicast_router_expired,
|
|
(unsigned long)port);
|
|
setup_timer(&port->multicast_query_timer,
|
|
br_multicast_port_query_expired, (unsigned long)port);
|
|
}
|
|
|
|
void br_multicast_del_port(struct net_bridge_port *port)
|
|
{
|
|
del_timer_sync(&port->multicast_router_timer);
|
|
}
|
|
|
|
static void __br_multicast_enable_port(struct net_bridge_port *port)
|
|
{
|
|
port->multicast_startup_queries_sent = 0;
|
|
|
|
if (try_to_del_timer_sync(&port->multicast_query_timer) >= 0 ||
|
|
del_timer(&port->multicast_query_timer))
|
|
mod_timer(&port->multicast_query_timer, jiffies);
|
|
}
|
|
|
|
void br_multicast_enable_port(struct net_bridge_port *port)
|
|
{
|
|
struct net_bridge *br = port->br;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
if (br->multicast_disabled || !netif_running(br->dev))
|
|
goto out;
|
|
|
|
__br_multicast_enable_port(port);
|
|
|
|
out:
|
|
spin_unlock(&br->multicast_lock);
|
|
}
|
|
|
|
void br_multicast_disable_port(struct net_bridge_port *port)
|
|
{
|
|
struct net_bridge *br = port->br;
|
|
struct net_bridge_port_group *pg;
|
|
struct hlist_node *p, *n;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
hlist_for_each_entry_safe(pg, p, n, &port->mglist, mglist)
|
|
br_multicast_del_pg(br, pg);
|
|
|
|
if (!hlist_unhashed(&port->rlist))
|
|
hlist_del_init_rcu(&port->rlist);
|
|
del_timer(&port->multicast_router_timer);
|
|
del_timer(&port->multicast_query_timer);
|
|
spin_unlock(&br->multicast_lock);
|
|
}
|
|
|
|
static int br_multicast_igmp3_report(struct net_bridge *br,
|
|
struct net_bridge_port *port,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct igmpv3_report *ih;
|
|
struct igmpv3_grec *grec;
|
|
int i;
|
|
int len;
|
|
int num;
|
|
int type;
|
|
int err = 0;
|
|
__be32 group;
|
|
|
|
if (!pskb_may_pull(skb, sizeof(*ih)))
|
|
return -EINVAL;
|
|
|
|
ih = igmpv3_report_hdr(skb);
|
|
num = ntohs(ih->ngrec);
|
|
len = sizeof(*ih);
|
|
|
|
for (i = 0; i < num; i++) {
|
|
len += sizeof(*grec);
|
|
if (!pskb_may_pull(skb, len))
|
|
return -EINVAL;
|
|
|
|
grec = (void *)(skb->data + len);
|
|
group = grec->grec_mca;
|
|
type = grec->grec_type;
|
|
|
|
len += grec->grec_nsrcs * 4;
|
|
if (!pskb_may_pull(skb, len))
|
|
return -EINVAL;
|
|
|
|
/* We treat this as an IGMPv2 report for now. */
|
|
switch (type) {
|
|
case IGMPV3_MODE_IS_INCLUDE:
|
|
case IGMPV3_MODE_IS_EXCLUDE:
|
|
case IGMPV3_CHANGE_TO_INCLUDE:
|
|
case IGMPV3_CHANGE_TO_EXCLUDE:
|
|
case IGMPV3_ALLOW_NEW_SOURCES:
|
|
case IGMPV3_BLOCK_OLD_SOURCES:
|
|
break;
|
|
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
err = br_multicast_add_group(br, port, group);
|
|
if (err)
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static void br_multicast_add_router(struct net_bridge *br,
|
|
struct net_bridge_port *port)
|
|
{
|
|
struct hlist_node *p;
|
|
struct hlist_node **h;
|
|
|
|
for (h = &br->router_list.first;
|
|
(p = *h) &&
|
|
(unsigned long)container_of(p, struct net_bridge_port, rlist) >
|
|
(unsigned long)port;
|
|
h = &p->next)
|
|
;
|
|
|
|
port->rlist.pprev = h;
|
|
port->rlist.next = p;
|
|
rcu_assign_pointer(*h, &port->rlist);
|
|
if (p)
|
|
p->pprev = &port->rlist.next;
|
|
}
|
|
|
|
static void br_multicast_mark_router(struct net_bridge *br,
|
|
struct net_bridge_port *port)
|
|
{
|
|
unsigned long now = jiffies;
|
|
|
|
if (!port) {
|
|
if (br->multicast_router == 1)
|
|
mod_timer(&br->multicast_router_timer,
|
|
now + br->multicast_querier_interval);
|
|
return;
|
|
}
|
|
|
|
if (port->multicast_router != 1)
|
|
return;
|
|
|
|
if (!hlist_unhashed(&port->rlist))
|
|
goto timer;
|
|
|
|
br_multicast_add_router(br, port);
|
|
|
|
timer:
|
|
mod_timer(&port->multicast_router_timer,
|
|
now + br->multicast_querier_interval);
|
|
}
|
|
|
|
static void br_multicast_query_received(struct net_bridge *br,
|
|
struct net_bridge_port *port,
|
|
__be32 saddr)
|
|
{
|
|
if (saddr)
|
|
mod_timer(&br->multicast_querier_timer,
|
|
jiffies + br->multicast_querier_interval);
|
|
else if (timer_pending(&br->multicast_querier_timer))
|
|
return;
|
|
|
|
br_multicast_mark_router(br, port);
|
|
}
|
|
|
|
static int br_multicast_query(struct net_bridge *br,
|
|
struct net_bridge_port *port,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct iphdr *iph = ip_hdr(skb);
|
|
struct igmphdr *ih = igmp_hdr(skb);
|
|
struct net_bridge_mdb_entry *mp;
|
|
struct igmpv3_query *ih3;
|
|
struct net_bridge_port_group *p;
|
|
struct net_bridge_port_group **pp;
|
|
unsigned long max_delay;
|
|
unsigned long now = jiffies;
|
|
__be32 group;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
if (!netif_running(br->dev) ||
|
|
(port && port->state == BR_STATE_DISABLED))
|
|
goto out;
|
|
|
|
br_multicast_query_received(br, port, iph->saddr);
|
|
|
|
group = ih->group;
|
|
|
|
if (skb->len == sizeof(*ih)) {
|
|
max_delay = ih->code * (HZ / IGMP_TIMER_SCALE);
|
|
|
|
if (!max_delay) {
|
|
max_delay = 10 * HZ;
|
|
group = 0;
|
|
}
|
|
} else {
|
|
if (!pskb_may_pull(skb, sizeof(struct igmpv3_query)))
|
|
return -EINVAL;
|
|
|
|
ih3 = igmpv3_query_hdr(skb);
|
|
if (ih3->nsrcs)
|
|
return 0;
|
|
|
|
max_delay = ih3->code ? 1 :
|
|
IGMPV3_MRC(ih3->code) * (HZ / IGMP_TIMER_SCALE);
|
|
}
|
|
|
|
if (!group)
|
|
goto out;
|
|
|
|
mp = br_mdb_ip_get(br->mdb, group);
|
|
if (!mp)
|
|
goto out;
|
|
|
|
max_delay *= br->multicast_last_member_count;
|
|
|
|
if (!hlist_unhashed(&mp->mglist) &&
|
|
(timer_pending(&mp->timer) ?
|
|
time_after(mp->timer.expires, now + max_delay) :
|
|
try_to_del_timer_sync(&mp->timer) >= 0))
|
|
mod_timer(&mp->timer, now + max_delay);
|
|
|
|
for (pp = &mp->ports; (p = *pp); pp = &p->next) {
|
|
if (timer_pending(&p->timer) ?
|
|
time_after(p->timer.expires, now + max_delay) :
|
|
try_to_del_timer_sync(&p->timer) >= 0)
|
|
mod_timer(&mp->timer, now + max_delay);
|
|
}
|
|
|
|
out:
|
|
spin_unlock(&br->multicast_lock);
|
|
return 0;
|
|
}
|
|
|
|
static void br_multicast_leave_group(struct net_bridge *br,
|
|
struct net_bridge_port *port,
|
|
__be32 group)
|
|
{
|
|
struct net_bridge_mdb_htable *mdb;
|
|
struct net_bridge_mdb_entry *mp;
|
|
struct net_bridge_port_group *p;
|
|
unsigned long now;
|
|
unsigned long time;
|
|
|
|
if (ipv4_is_local_multicast(group))
|
|
return;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
if (!netif_running(br->dev) ||
|
|
(port && port->state == BR_STATE_DISABLED) ||
|
|
timer_pending(&br->multicast_querier_timer))
|
|
goto out;
|
|
|
|
mdb = br->mdb;
|
|
mp = br_mdb_ip_get(mdb, group);
|
|
if (!mp)
|
|
goto out;
|
|
|
|
now = jiffies;
|
|
time = now + br->multicast_last_member_count *
|
|
br->multicast_last_member_interval;
|
|
|
|
if (!port) {
|
|
if (!hlist_unhashed(&mp->mglist) &&
|
|
(timer_pending(&mp->timer) ?
|
|
time_after(mp->timer.expires, time) :
|
|
try_to_del_timer_sync(&mp->timer) >= 0)) {
|
|
mod_timer(&mp->timer, time);
|
|
|
|
mp->queries_sent = 0;
|
|
mod_timer(&mp->query_timer, now);
|
|
}
|
|
|
|
goto out;
|
|
}
|
|
|
|
for (p = mp->ports; p; p = p->next) {
|
|
if (p->port != port)
|
|
continue;
|
|
|
|
if (!hlist_unhashed(&p->mglist) &&
|
|
(timer_pending(&p->timer) ?
|
|
time_after(p->timer.expires, time) :
|
|
try_to_del_timer_sync(&p->timer) >= 0)) {
|
|
mod_timer(&p->timer, time);
|
|
|
|
p->queries_sent = 0;
|
|
mod_timer(&p->query_timer, now);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
out:
|
|
spin_unlock(&br->multicast_lock);
|
|
}
|
|
|
|
static int br_multicast_ipv4_rcv(struct net_bridge *br,
|
|
struct net_bridge_port *port,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct sk_buff *skb2 = skb;
|
|
struct iphdr *iph;
|
|
struct igmphdr *ih;
|
|
unsigned len;
|
|
unsigned offset;
|
|
int err;
|
|
|
|
BR_INPUT_SKB_CB(skb)->igmp = 0;
|
|
BR_INPUT_SKB_CB(skb)->mrouters_only = 0;
|
|
|
|
/* We treat OOM as packet loss for now. */
|
|
if (!pskb_may_pull(skb, sizeof(*iph)))
|
|
return -EINVAL;
|
|
|
|
iph = ip_hdr(skb);
|
|
|
|
if (iph->ihl < 5 || iph->version != 4)
|
|
return -EINVAL;
|
|
|
|
if (!pskb_may_pull(skb, ip_hdrlen(skb)))
|
|
return -EINVAL;
|
|
|
|
iph = ip_hdr(skb);
|
|
|
|
if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
|
|
return -EINVAL;
|
|
|
|
if (iph->protocol != IPPROTO_IGMP)
|
|
return 0;
|
|
|
|
len = ntohs(iph->tot_len);
|
|
if (skb->len < len || len < ip_hdrlen(skb))
|
|
return -EINVAL;
|
|
|
|
if (skb->len > len) {
|
|
skb2 = skb_clone(skb, GFP_ATOMIC);
|
|
if (!skb2)
|
|
return -ENOMEM;
|
|
|
|
err = pskb_trim_rcsum(skb2, len);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
len -= ip_hdrlen(skb2);
|
|
offset = skb_network_offset(skb2) + ip_hdrlen(skb2);
|
|
__skb_pull(skb2, offset);
|
|
skb_reset_transport_header(skb2);
|
|
|
|
err = -EINVAL;
|
|
if (!pskb_may_pull(skb2, sizeof(*ih)))
|
|
goto out;
|
|
|
|
iph = ip_hdr(skb2);
|
|
|
|
switch (skb2->ip_summed) {
|
|
case CHECKSUM_COMPLETE:
|
|
if (!csum_fold(skb2->csum))
|
|
break;
|
|
/* fall through */
|
|
case CHECKSUM_NONE:
|
|
skb2->csum = 0;
|
|
if (skb_checksum_complete(skb2))
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = 0;
|
|
|
|
BR_INPUT_SKB_CB(skb)->igmp = 1;
|
|
ih = igmp_hdr(skb2);
|
|
|
|
switch (ih->type) {
|
|
case IGMP_HOST_MEMBERSHIP_REPORT:
|
|
case IGMPV2_HOST_MEMBERSHIP_REPORT:
|
|
BR_INPUT_SKB_CB(skb2)->mrouters_only = 1;
|
|
err = br_multicast_add_group(br, port, ih->group);
|
|
break;
|
|
case IGMPV3_HOST_MEMBERSHIP_REPORT:
|
|
err = br_multicast_igmp3_report(br, port, skb2);
|
|
break;
|
|
case IGMP_HOST_MEMBERSHIP_QUERY:
|
|
err = br_multicast_query(br, port, skb2);
|
|
break;
|
|
case IGMP_HOST_LEAVE_MESSAGE:
|
|
br_multicast_leave_group(br, port, ih->group);
|
|
break;
|
|
}
|
|
|
|
out:
|
|
__skb_push(skb2, offset);
|
|
if (skb2 != skb)
|
|
kfree_skb(skb2);
|
|
return err;
|
|
}
|
|
|
|
int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port,
|
|
struct sk_buff *skb)
|
|
{
|
|
if (br->multicast_disabled)
|
|
return 0;
|
|
|
|
switch (skb->protocol) {
|
|
case htons(ETH_P_IP):
|
|
return br_multicast_ipv4_rcv(br, port, skb);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void br_multicast_query_expired(unsigned long data)
|
|
{
|
|
struct net_bridge *br = (void *)data;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
if (br->multicast_startup_queries_sent <
|
|
br->multicast_startup_query_count)
|
|
br->multicast_startup_queries_sent++;
|
|
|
|
br_multicast_send_query(br, NULL, br->multicast_startup_queries_sent);
|
|
|
|
spin_unlock(&br->multicast_lock);
|
|
}
|
|
|
|
void br_multicast_init(struct net_bridge *br)
|
|
{
|
|
br->hash_elasticity = 4;
|
|
br->hash_max = 512;
|
|
|
|
br->multicast_router = 1;
|
|
br->multicast_last_member_count = 2;
|
|
br->multicast_startup_query_count = 2;
|
|
|
|
br->multicast_last_member_interval = HZ;
|
|
br->multicast_query_response_interval = 10 * HZ;
|
|
br->multicast_startup_query_interval = 125 * HZ / 4;
|
|
br->multicast_query_interval = 125 * HZ;
|
|
br->multicast_querier_interval = 255 * HZ;
|
|
br->multicast_membership_interval = 260 * HZ;
|
|
|
|
spin_lock_init(&br->multicast_lock);
|
|
setup_timer(&br->multicast_router_timer,
|
|
br_multicast_local_router_expired, 0);
|
|
setup_timer(&br->multicast_querier_timer,
|
|
br_multicast_local_router_expired, 0);
|
|
setup_timer(&br->multicast_query_timer, br_multicast_query_expired,
|
|
(unsigned long)br);
|
|
}
|
|
|
|
void br_multicast_open(struct net_bridge *br)
|
|
{
|
|
br->multicast_startup_queries_sent = 0;
|
|
|
|
if (br->multicast_disabled)
|
|
return;
|
|
|
|
mod_timer(&br->multicast_query_timer, jiffies);
|
|
}
|
|
|
|
void br_multicast_stop(struct net_bridge *br)
|
|
{
|
|
struct net_bridge_mdb_htable *mdb;
|
|
struct net_bridge_mdb_entry *mp;
|
|
struct hlist_node *p, *n;
|
|
u32 ver;
|
|
int i;
|
|
|
|
del_timer_sync(&br->multicast_router_timer);
|
|
del_timer_sync(&br->multicast_querier_timer);
|
|
del_timer_sync(&br->multicast_query_timer);
|
|
|
|
spin_lock_bh(&br->multicast_lock);
|
|
mdb = br->mdb;
|
|
if (!mdb)
|
|
goto out;
|
|
|
|
br->mdb = NULL;
|
|
|
|
ver = mdb->ver;
|
|
for (i = 0; i < mdb->max; i++) {
|
|
hlist_for_each_entry_safe(mp, p, n, &mdb->mhash[i],
|
|
hlist[ver]) {
|
|
del_timer(&mp->timer);
|
|
del_timer(&mp->query_timer);
|
|
call_rcu_bh(&mp->rcu, br_multicast_free_group);
|
|
}
|
|
}
|
|
|
|
if (mdb->old) {
|
|
spin_unlock_bh(&br->multicast_lock);
|
|
synchronize_rcu_bh();
|
|
spin_lock_bh(&br->multicast_lock);
|
|
WARN_ON(mdb->old);
|
|
}
|
|
|
|
mdb->old = mdb;
|
|
call_rcu_bh(&mdb->rcu, br_mdb_free);
|
|
|
|
out:
|
|
spin_unlock_bh(&br->multicast_lock);
|
|
}
|
|
|
|
int br_multicast_set_router(struct net_bridge *br, unsigned long val)
|
|
{
|
|
int err = -ENOENT;
|
|
|
|
spin_lock_bh(&br->multicast_lock);
|
|
if (!netif_running(br->dev))
|
|
goto unlock;
|
|
|
|
switch (val) {
|
|
case 0:
|
|
case 2:
|
|
del_timer(&br->multicast_router_timer);
|
|
/* fall through */
|
|
case 1:
|
|
br->multicast_router = val;
|
|
err = 0;
|
|
break;
|
|
|
|
default:
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
unlock:
|
|
spin_unlock_bh(&br->multicast_lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val)
|
|
{
|
|
struct net_bridge *br = p->br;
|
|
int err = -ENOENT;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
if (!netif_running(br->dev) || p->state == BR_STATE_DISABLED)
|
|
goto unlock;
|
|
|
|
switch (val) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
p->multicast_router = val;
|
|
err = 0;
|
|
|
|
if (val < 2 && !hlist_unhashed(&p->rlist))
|
|
hlist_del_init_rcu(&p->rlist);
|
|
|
|
if (val == 1)
|
|
break;
|
|
|
|
del_timer(&p->multicast_router_timer);
|
|
|
|
if (val == 0)
|
|
break;
|
|
|
|
br_multicast_add_router(br, p);
|
|
break;
|
|
|
|
default:
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
unlock:
|
|
spin_unlock(&br->multicast_lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
int br_multicast_toggle(struct net_bridge *br, unsigned long val)
|
|
{
|
|
struct net_bridge_port *port;
|
|
int err = -ENOENT;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
if (!netif_running(br->dev))
|
|
goto unlock;
|
|
|
|
err = 0;
|
|
if (br->multicast_disabled == !val)
|
|
goto unlock;
|
|
|
|
br->multicast_disabled = !val;
|
|
if (br->multicast_disabled)
|
|
goto unlock;
|
|
|
|
if (br->mdb) {
|
|
if (br->mdb->old) {
|
|
err = -EEXIST;
|
|
rollback:
|
|
br->multicast_disabled = !!val;
|
|
goto unlock;
|
|
}
|
|
|
|
err = br_mdb_rehash(&br->mdb, br->mdb->max,
|
|
br->hash_elasticity);
|
|
if (err)
|
|
goto rollback;
|
|
}
|
|
|
|
br_multicast_open(br);
|
|
list_for_each_entry(port, &br->port_list, list) {
|
|
if (port->state == BR_STATE_DISABLED ||
|
|
port->state == BR_STATE_BLOCKING)
|
|
continue;
|
|
|
|
__br_multicast_enable_port(port);
|
|
}
|
|
|
|
unlock:
|
|
spin_unlock(&br->multicast_lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val)
|
|
{
|
|
int err = -ENOENT;
|
|
u32 old;
|
|
|
|
spin_lock(&br->multicast_lock);
|
|
if (!netif_running(br->dev))
|
|
goto unlock;
|
|
|
|
err = -EINVAL;
|
|
if (!is_power_of_2(val))
|
|
goto unlock;
|
|
if (br->mdb && val < br->mdb->size)
|
|
goto unlock;
|
|
|
|
err = 0;
|
|
|
|
old = br->hash_max;
|
|
br->hash_max = val;
|
|
|
|
if (br->mdb) {
|
|
if (br->mdb->old) {
|
|
err = -EEXIST;
|
|
rollback:
|
|
br->hash_max = old;
|
|
goto unlock;
|
|
}
|
|
|
|
err = br_mdb_rehash(&br->mdb, br->hash_max,
|
|
br->hash_elasticity);
|
|
if (err)
|
|
goto rollback;
|
|
}
|
|
|
|
unlock:
|
|
spin_unlock(&br->multicast_lock);
|
|
|
|
return err;
|
|
}
|