Files
Kernel/net/bridge/br_igmp.c

1008 lines
28 KiB
C

/*
* <:copyright-BRCM:2011:DUAL/GPL:standard
*
* Copyright (c) 2011 Broadcom Corporation
* All Rights Reserved
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed
* to you under the terms of the GNU General Public License version 2
* (the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php,
* with the following added to such license:
*
* As a special exception, the copyright holders of this software give
* you permission to link this software with independent modules, and
* to copy and distribute the resulting executable under terms of your
* choice, provided that you also meet, for each linked independent
* module, the terms and conditions of the license of that module.
* An independent module is a module which is not derived from this
* software. The special exception does not apply to any modifications
* of the software.
*
* Not withstanding the above, under no circumstances may you combine
* this software in any way with any other Broadcom software provided
* under a license other than the GPL, without Broadcom's express prior
* written consent.
*
:>
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/times.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/jhash.h>
#include <asm/atomic.h>
#include <linux/ip.h>
#include <linux/if_vlan.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/list.h>
#include <linux/rtnetlink.h>
#include "br_private.h"
#include "br_igmp.h"
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
#include <linux/if_vlan.h>
#include <linux/blog.h>
#include <linux/blog_rule.h>
#endif
#include "br_mcast.h"
#include <linux/bcm_skb_defines.h>
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BR_IGMP_SNOOP)
static struct kmem_cache *br_igmp_mc_fdb_cache __read_mostly;
static struct kmem_cache *br_igmp_mc_rep_cache __read_mostly;
static u32 br_igmp_mc_fdb_salt __read_mostly;
static struct proc_dir_entry *br_igmp_entry = NULL;
static int br_igmp_lan2lan_snooping = 0;
extern int mcpd_process_skb(struct net_bridge *br, struct sk_buff *skb,
int protocol);
static struct in_addr ip_upnp_addr = {0xEFFFFFFA}; /* UPnP / SSDP */
static struct in_addr ip_ntfy_srvr_addr = {0xE000FF87}; /* Notificatoin Server*/
static inline int br_igmp_mc_fdb_hash(const u32 grp)
{
return jhash_1word(grp, br_igmp_mc_fdb_salt) & (BR_IGMP_HASH_SIZE - 1);
}
int br_igmp_control_filter(const unsigned char *dest, __be32 dest_ip)
{
if(((dest) && is_broadcast_ether_addr(dest)) ||
((htonl(dest_ip) & htonl(0xFFFFFF00)) == htonl(0xE0000000)) ||
(htonl(dest_ip) == htonl(ip_upnp_addr.s_addr)) || /* UPnp/SSDP */
(htonl(dest_ip) == htonl(ip_ntfy_srvr_addr.s_addr))) /* Notification srvr */
{
return 0;
}
else
{
return 1;
}
} /* br_igmp_control_filter */
void br_igmp_lan2lan_snooping_update(int val)
{
br_igmp_lan2lan_snooping = val;
}
int br_igmp_get_lan2lan_snooping_info(void)
{
return br_igmp_lan2lan_snooping;
}
static void br_igmp_query_timeout(unsigned long ptr)
{
struct net_bridge_mc_fdb_entry *dst;
struct net_bridge *br;
struct net_bridge_mc_rep_entry *rep_entry, *rep_entry_n;
int i;
br = (struct net_bridge *) ptr;
/* if snooping is disabled just return */
if ( 0 == br->igmp_snooping )
return;
spin_lock_bh(&br->mcl_lock);
for (i = 0; i < BR_IGMP_HASH_SIZE; i++)
{
struct hlist_node *h, *n;
hlist_for_each_entry_safe(dst, h, n, &br->mc_hash[i], hlist)
{
if (time_after_eq(jiffies, dst->tstamp))
{
mcpd_nl_send_igmp_purge_entry(dst);
list_for_each_entry_safe(rep_entry,
rep_entry_n, &dst->rep_list, list)
{
list_del(&rep_entry->list);
kmem_cache_free(br_igmp_mc_rep_cache, rep_entry);
}
hlist_del(&dst->hlist);
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
br_mcast_blog_release(BR_MCAST_PROTO_IGMP, (void *)dst);
#endif
kmem_cache_free(br_igmp_mc_fdb_cache, dst);
}
}
}
spin_unlock_bh(&br->mcl_lock);
mod_timer(&br->igmp_timer, jiffies + TIMER_CHECK_TIMEOUT*HZ);
}
static struct net_bridge_mc_rep_entry *
br_igmp_rep_find(const struct net_bridge_mc_fdb_entry *mc_fdb,
const struct in_addr *rep)
{
struct net_bridge_mc_rep_entry *rep_entry;
list_for_each_entry(rep_entry, &mc_fdb->rep_list, list)
{
if(rep_entry->rep.s_addr == rep->s_addr)
return rep_entry;
}
return NULL;
}
/* this is called during addition of a snooping entry and requires that
mcl_lock is already held */
static int br_mc_fdb_update(struct net_bridge *br,
struct net_bridge_port *prt,
struct in_addr *grp,
struct in_addr *rep,
int mode,
struct in_addr *src,
struct net_device *from_dev)
{
struct net_bridge_mc_fdb_entry *dst;
struct net_bridge_mc_rep_entry *rep_entry = NULL;
int ret = 0;
int filt_mode;
struct hlist_head *head;
struct hlist_node *h;
if(mode == SNOOP_IN_ADD)
filt_mode = MCAST_INCLUDE;
else
filt_mode = MCAST_EXCLUDE;
head = &br->mc_hash[br_igmp_mc_fdb_hash(grp->s_addr)];
hlist_for_each_entry(dst, h, head, hlist) {
if (dst->grp.s_addr == grp->s_addr)
{
if((src->s_addr == dst->src_entry.src.s_addr) &&
(filt_mode == dst->src_entry.filt_mode) &&
(dst->from_dev == from_dev) &&
(dst->dst == prt))
{
/* found entry - update TS */
dst->tstamp = jiffies + BR_IGMP_MEMBERSHIP_TIMEOUT*HZ;
if(!br_igmp_rep_find(dst, rep))
{
rep_entry = kmem_cache_alloc(br_igmp_mc_rep_cache, GFP_ATOMIC);
if(rep_entry)
{
rep_entry->rep.s_addr = rep->s_addr;
list_add_tail(&rep_entry->list, &dst->rep_list);
}
}
ret = 1;
}
#if defined(CONFIG_BR_IGMP_SNOOP_SWITCH_PATCH)
/* patch for igmp report flooding by robo */
else if ((0 == dst->src_entry.src.s_addr) &&
(MCAST_EXCLUDE == dst->src_entry.filt_mode)) {
dst->tstamp = jiffies + BR_IGMP_MEMBERSHIP_TIMEOUT*HZ;
}
#endif /* CONFIG_BR_IGMP_SNOOP_SWITCH_PATCH*/
}
}
return ret;
}
#if 0
static struct net_bridge_mc_fdb_entry *br_mc_fdb_get(struct net_bridge *br,
struct net_bridge_port *prt,
struct in_addr *grp,
struct in_addr *rep,
int mode,
struct in_addr *src,
struct net_device *from_dev)
{
struct net_bridge_mc_fdb_entry *dst;
int filt_mode;
struct hlist_head *head;
struct hlist_node *h;
if(mode == SNOOP_IN_CLEAR)
filt_mode = MCAST_INCLUDE;
else
filt_mode = MCAST_EXCLUDE;
spin_lock_bh(&br->mcl_lock);
head = &br->mc_hash[br_igmp_mc_fdb_hash(grp->s_addr)];
hlist_for_each_entry(dst, h, head, hlist) {
if ((dst->grp.s_addr == grp->s_addr) &&
(br_igmp_rep_find(dst, rep)) &&
(filt_mode == dst->src_entry.filt_mode) &&
(dst->src_entry.src.s_addr == src->s_addr) &&
(dst->from_dev == from_dev) &&
(dst->dst == prt))
{
spin_unlock_bh(&br->mcl_lock);
return dst;
}
}
spin_unlock_bh(&br->mcl_lock);
return NULL;
}
#endif
int br_igmp_process_if_change(struct net_bridge *br, struct net_device *ndev)
{
struct net_bridge_mc_fdb_entry *dst;
int i;
spin_lock_bh(&br->mcl_lock);
for (i = 0; i < BR_IGMP_HASH_SIZE; i++)
{
struct hlist_node *h, *n;
hlist_for_each_entry_safe(dst, h, n, &br->mc_hash[i], hlist)
{
if ((NULL == ndev) ||
(dst->dst->dev == ndev) ||
(dst->from_dev == ndev))
{
mcpd_nl_send_igmp_purge_entry(dst);
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
br_mcast_blog_release(BR_MCAST_PROTO_IGMP, (void *)dst);
#endif
br_igmp_mc_fdb_del_entry(br, dst);
}
}
}
spin_unlock_bh(&br->mcl_lock);
return 0;
}
int br_igmp_mc_fdb_add(struct net_device *from_dev,
int wan_ops,
struct net_bridge *br,
struct net_bridge_port *prt,
struct in_addr *grp,
struct in_addr *rep,
int mode,
int tci,
struct in_addr *src)
{
struct net_bridge_mc_fdb_entry *mc_fdb = NULL;
struct net_bridge_mc_rep_entry *rep_entry = NULL;
struct hlist_head *head = NULL;
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
int ret = 1;
#endif
#if defined(CONFIG_BR_IGMP_SNOOP_SWITCH_PATCH)
struct net_bridge_mc_fdb_entry *mc_fdb_robo, *mc_fdb_robo_n;
#endif /* CONFIG_BR_IGMP_SNOOP_SWITCH_PATCH */
if(!br || !prt || !grp|| !rep || !from_dev)
return 0;
if(!br_igmp_control_filter(NULL, grp->s_addr))
return 0;
if(!netdev_path_is_leaf(from_dev))
return 0;
if((SNOOP_IN_ADD != mode) && (SNOOP_EX_ADD != mode))
return 0;
if(grp->s_addr == ip_upnp_addr.s_addr)
return 0;
mc_fdb = kmem_cache_alloc(br_igmp_mc_fdb_cache, GFP_KERNEL);
if ( !mc_fdb )
{
return -ENOMEM;
}
rep_entry = kmem_cache_alloc(br_igmp_mc_rep_cache, GFP_KERNEL);
if ( !rep_entry )
{
kmem_cache_free(br_igmp_mc_fdb_cache, mc_fdb);
return -ENOMEM;
}
spin_lock_bh(&br->mcl_lock);
if (br_mc_fdb_update(br, prt, grp, rep, mode, src, from_dev))
{
kmem_cache_free(br_igmp_mc_fdb_cache, mc_fdb);
kmem_cache_free(br_igmp_mc_rep_cache, rep_entry);
spin_unlock_bh(&br->mcl_lock);
return 0;
}
#if defined(CONFIG_BR_IGMP_SNOOP_SWITCH_PATCH)
/* patch for snooping entry when LAN client access port is moved &
igmp report flooding by robo */
head = &br->mc_hash[br_igmp_mc_fdb_hash(grp->s_addr)];
hlist_for_each_entry(mc_fdb_robo, h, head, hlist) {
if ((mc_fdb_robo->grp.s_addr == grp->s_addr) &&
(0 == mc_fdb_robo->src_entry.src.s_addr) &&
(MCAST_EXCLUDE == mc_fdb_robo->src_entry.filt_mode) &&
(br_igmp_rep_find(mc_fdb_robo, rep)) &&
(mc_fdb_robo->dst != prt)) {
list_del(&mc_fdb_robo->list);
kmem_cache_free(br_igmp_mc_fdb_cache, mc_fdb_robo);
}
}
#endif /* CONFIG_BR_IGMP_SNOOP_SWITCH_PATCH */
mc_fdb->grp.s_addr = grp->s_addr;
memcpy(&mc_fdb->src_entry, src, sizeof(struct in_addr));
mc_fdb->src_entry.filt_mode = (mode == SNOOP_IN_ADD) ? MCAST_INCLUDE : MCAST_EXCLUDE;
mc_fdb->dst = prt;
mc_fdb->tstamp = jiffies + BR_IGMP_MEMBERSHIP_TIMEOUT * HZ;
mc_fdb->lan_tci = tci;
mc_fdb->wan_tci = 0;
mc_fdb->num_tags = 0;
mc_fdb->from_dev = from_dev;
mc_fdb->type = wan_ops;
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
mc_fdb->root = 1;
mc_fdb->blog_idx = BLOG_KEY_INVALID;
#endif
memcpy(mc_fdb->wan_name, from_dev->name, IFNAMSIZ);
memcpy(mc_fdb->lan_name, prt->dev->name, IFNAMSIZ);
INIT_LIST_HEAD(&mc_fdb->rep_list);
rep_entry->rep.s_addr = rep->s_addr;
list_add_tail(&rep_entry->list, &mc_fdb->rep_list);
head = &br->mc_hash[br_igmp_mc_fdb_hash(grp->s_addr)];
hlist_add_head(&mc_fdb->hlist, head);
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
ret = br_mcast_blog_process(br, (void*)mc_fdb, BR_MCAST_PROTO_IGMP);
if(ret < 0)
{
hlist_del(&mc_fdb->hlist);
kmem_cache_free(br_igmp_mc_fdb_cache, mc_fdb);
kmem_cache_free(br_igmp_mc_rep_cache, rep_entry);
spin_unlock_bh(&br->mcl_lock);
return ret;
}
#endif
spin_unlock_bh(&br->mcl_lock);
if (!br->start_timer) {
init_timer(&br->igmp_timer);
br->igmp_timer.expires = jiffies + TIMER_CHECK_TIMEOUT*HZ;
br->igmp_timer.function = br_igmp_query_timeout;
br->igmp_timer.data = (unsigned long) br;
add_timer(&br->igmp_timer);
br->start_timer = 1;
}
return 1;
}
void br_igmp_mc_fdb_cleanup(struct net_bridge *br)
{
struct net_bridge_mc_fdb_entry *dst;
struct net_bridge_mc_rep_entry *rep_entry, *rep_entry_n;
int i;
spin_lock_bh(&br->mcl_lock);
for (i = 0; i < BR_IGMP_HASH_SIZE; i++)
{
struct hlist_node *h, *n;
hlist_for_each_entry_safe(dst, h, n, &br->mc_hash[i], hlist)
{
list_for_each_entry_safe(rep_entry,
rep_entry_n, &dst->rep_list, list) {
list_del(&rep_entry->list);
kmem_cache_free(br_igmp_mc_rep_cache, rep_entry);
}
hlist_del(&dst->hlist);
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
br_mcast_blog_release(BR_MCAST_PROTO_IGMP, (void *)dst);
#endif
kmem_cache_free(br_igmp_mc_fdb_cache, dst);
}
}
spin_unlock_bh(&br->mcl_lock);
}
void br_igmp_mc_fdb_remove_grp(struct net_bridge *br,
struct net_bridge_port *prt,
struct in_addr *grp)
{
struct net_bridge_mc_fdb_entry *dst;
struct net_bridge_mc_rep_entry *rep_entry, *rep_entry_n;
struct hlist_head *head = NULL;
struct hlist_node *h, *n;
if(!br || !prt || !grp)
return;
spin_lock_bh(&br->mcl_lock);
head = &br->mc_hash[br_igmp_mc_fdb_hash(grp->s_addr)];
hlist_for_each_entry_safe(dst, h, n, head, hlist) {
if ((dst->grp.s_addr == grp->s_addr) &&
(dst->dst == prt))
{
list_for_each_entry_safe(rep_entry,
rep_entry_n, &dst->rep_list, list) {
list_del(&rep_entry->list);
kmem_cache_free(br_igmp_mc_rep_cache, rep_entry);
}
hlist_del(&dst->hlist);
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
br_mcast_blog_release(BR_MCAST_PROTO_IGMP, (void *)dst);
#endif
kmem_cache_free(br_igmp_mc_fdb_cache, dst);
}
}
spin_unlock_bh(&br->mcl_lock);
}
int br_igmp_mc_fdb_remove(struct net_device *from_dev,
struct net_bridge *br,
struct net_bridge_port *prt,
struct in_addr *grp,
struct in_addr *rep,
int mode,
struct in_addr *src)
{
struct net_bridge_mc_fdb_entry *mc_fdb;
struct net_bridge_mc_rep_entry *rep_entry, *rep_entry_n;
int filt_mode;
struct hlist_head *head = NULL;
struct hlist_node *h, *n;
//printk("--- remove mc entry ---\n");
if(!br || !prt || !grp|| !rep || !from_dev)
return 0;
if(!br_igmp_control_filter(NULL, grp->s_addr))
return 0;
if(!netdev_path_is_leaf(from_dev))
return 0;
if((SNOOP_IN_CLEAR != mode) && (SNOOP_EX_CLEAR != mode))
return 0;
if(mode == SNOOP_IN_CLEAR)
filt_mode = MCAST_INCLUDE;
else
filt_mode = MCAST_EXCLUDE;
spin_lock_bh(&br->mcl_lock);
head = &br->mc_hash[br_igmp_mc_fdb_hash(grp->s_addr)];
hlist_for_each_entry_safe(mc_fdb, h, n, head, hlist)
{
if ((mc_fdb->grp.s_addr == grp->s_addr) &&
(filt_mode == mc_fdb->src_entry.filt_mode) &&
(mc_fdb->src_entry.src.s_addr == src->s_addr) &&
(mc_fdb->from_dev == from_dev) &&
(mc_fdb->dst == prt))
{
list_for_each_entry_safe(rep_entry,
rep_entry_n, &mc_fdb->rep_list, list)
{
if(rep_entry->rep.s_addr == rep->s_addr)
{
list_del(&rep_entry->list);
kmem_cache_free(br_igmp_mc_rep_cache, rep_entry);
}
}
if(list_empty(&mc_fdb->rep_list))
{
hlist_del(&mc_fdb->hlist);
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
br_mcast_blog_release(BR_MCAST_PROTO_IGMP, (void *)mc_fdb);
#endif
kmem_cache_free(br_igmp_mc_fdb_cache, mc_fdb);
}
}
}
spin_unlock_bh(&br->mcl_lock);
return 0;
}
int br_igmp_mc_forward(struct net_bridge *br,
struct sk_buff *skb,
int forward,
int is_routed)
{
struct net_bridge_mc_fdb_entry *dst;
int status = 0;
struct sk_buff *skb2;
struct net_bridge_port *p, *p_n;
struct iphdr *pip = NULL;
const unsigned char *dest = eth_hdr(skb)->h_dest;
struct hlist_head *head = NULL;
struct hlist_node *h;
__u8 igmpTypeOffset = 0;
if(vlan_eth_hdr(skb)->h_vlan_proto != ETH_P_IP)
{
if ( vlan_eth_hdr(skb)->h_vlan_proto == ETH_P_8021Q )
{
if ( vlan_eth_hdr(skb)->h_vlan_encapsulated_proto != ETH_P_IP )
{
return status;
}
pip = (struct iphdr *)(skb_network_header(skb) + sizeof(struct vlan_hdr));
igmpTypeOffset = (pip->ihl << 2) + sizeof(struct vlan_hdr);
}
else
{
return status;
}
}
else
{
pip = ip_hdr(skb);
igmpTypeOffset = (pip->ihl << 2);
}
if ((pip->protocol == IPPROTO_IGMP) &&
(br->igmp_proxy || br->igmp_snooping))
{
/* for bridged WAN service, do not pass any IGMP packets
coming from the WAN port to mcpd. Queries can be passed
through for forwarding, other types should be dropped */
if (skb->dev)
{
if ( skb->dev->priv_flags & IFF_WANDEV )
{
unsigned char igmp_type = skb->data[igmpTypeOffset];
if ( igmp_type != IGMP_HOST_MEMBERSHIP_QUERY )
{
kfree_skb(skb);
status = 1;
}
}
else
{
if(skb->dev->br_port)
{
mcpd_process_skb(br, skb, ETH_P_IP);
}
}
}
return status;
}
/* snooping could be disabled and still have manual entries */
/* drop traffic by default when snooping is enabled
in blocking mode */
if ((br->igmp_snooping == SNOOPING_BLOCKING_MODE) &&
br_igmp_control_filter(dest, pip->daddr))
{
status = 1;
}
spin_lock_bh(&br->mcl_lock);
head = &br->mc_hash[br_igmp_mc_fdb_hash(pip->daddr)];
hlist_for_each_entry(dst, h, head, hlist) {
if (dst->grp.s_addr == pip->daddr) {
/* routed packet will have bridge as dev - cannot match to mc_fdb */
if ( is_routed ) {
if ( dst->type != MCPD_IF_TYPE_ROUTED ) {
continue;
}
}
else {
if ( dst->type != MCPD_IF_TYPE_BRIDGED ) {
continue;
}
if (skb->dev->priv_flags & IFF_WANDEV) {
/* match exactly if skb device is a WAN device - otherwise continue */
if (dst->from_dev != skb->dev)
continue;
}
else {
/* if this is not an L2L mc_fdb entry continue */
if (dst->from_dev != br->dev)
continue;
}
}
if((dst->src_entry.filt_mode == MCAST_INCLUDE) &&
(pip->saddr == dst->src_entry.src.s_addr)) {
if (!dst->dst->dirty) {
if((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL)
{
return 0;
}
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
blog_clone(skb, blog_ptr(skb2));
#endif
if(forward)
br_forward(dst->dst, skb2);
else
br_deliver(dst->dst, skb2);
}
dst->dst->dirty = 1;
status = 1;
}
else if(dst->src_entry.filt_mode == MCAST_EXCLUDE) {
if((0 == dst->src_entry.src.s_addr) ||
(pip->saddr != dst->src_entry.src.s_addr)) {
if (!dst->dst->dirty) {
if((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL)
{
return 0;
}
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
blog_clone(skb, blog_ptr(skb2));
#endif
if(forward)
br_forward(dst->dst, skb2);
else
br_deliver(dst->dst, skb2);
}
dst->dst->dirty = 1;
status = 1;
}
else if(pip->saddr == dst->src_entry.src.s_addr) {
status = 1;
}
}
}
}
spin_unlock_bh(&br->mcl_lock);
if (status) {
list_for_each_entry_safe(p, p_n, &br->port_list, list) {
p->dirty = 0;
}
}
if(status)
kfree_skb(skb);
return status;
}
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
int br_igmp_mc_fdb_update_bydev( struct net_bridge *br,
struct net_device *dev )
{
struct net_bridge_mc_fdb_entry *mc_fdb;
int ret;
int i;
if(!br || !dev)
return 0;
if(!netdev_path_is_leaf(dev))
return 0;
spin_lock_bh(&br->mcl_lock);
for (i = 0; i < BR_IGMP_HASH_SIZE; i++)
{
struct hlist_node *h, *n;
hlist_for_each_entry_safe(mc_fdb, h, n, &br->mc_hash[i], hlist)
{
if ((mc_fdb->dst->dev == dev) ||
(mc_fdb->from_dev == dev))
{
br_mcast_blog_release(BR_MCAST_PROTO_IGMP, (void *)mc_fdb);
/* do note remove the root entry */
if (0 == mc_fdb->root)
{
br_igmp_mc_fdb_del_entry(br, mc_fdb);
}
}
}
}
for (i = 0; i < BR_IGMP_HASH_SIZE; i++)
{
struct hlist_node *h, *n;
hlist_for_each_entry_safe(mc_fdb, h, n, &br->mc_hash[i], hlist)
{
if ( (1 == mc_fdb->root) &&
((mc_fdb->dst->dev == dev) ||
(mc_fdb->from_dev == dev)) )
{
mc_fdb->wan_tci = 0;
mc_fdb->num_tags = 0;
ret = br_mcast_blog_process(br, (void*)mc_fdb, BR_MCAST_PROTO_IGMP);
if(ret < 0)
{
/* br_mcast_blog_process may return -1 if there are no blog rules
* which may be a valid scenario, in which case we delete the
* multicast entry.
*/
br_igmp_mc_fdb_del_entry(br, mc_fdb);
// printk(KERN_DEBUG "%s: Failed to create the blog\n", __FUNCTION__);
}
}
}
}
spin_unlock_bh(&br->mcl_lock);
return 0;
}
/* This is a support function for vlan/blog processing that requires that
br->mcl_lock is already held */
struct net_bridge_mc_fdb_entry *br_igmp_mc_fdb_copy(
struct net_bridge *br,
const struct net_bridge_mc_fdb_entry *igmp_fdb)
{
struct net_bridge_mc_fdb_entry *new_igmp_fdb = NULL;
struct net_bridge_mc_rep_entry *rep_entry = NULL;
struct net_bridge_mc_rep_entry *rep_entry_n = NULL;
int success = 1;
struct hlist_head *head = NULL;
new_igmp_fdb = kmem_cache_alloc(br_igmp_mc_fdb_cache, GFP_ATOMIC);
if (new_igmp_fdb)
{
memcpy(new_igmp_fdb, igmp_fdb, sizeof(struct net_bridge_mc_fdb_entry));
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
new_igmp_fdb->blog_idx = BLOG_KEY_INVALID;
#endif
new_igmp_fdb->root = 0;
INIT_LIST_HEAD(&new_igmp_fdb->rep_list);
list_for_each_entry(rep_entry, &igmp_fdb->rep_list, list) {
rep_entry_n = kmem_cache_alloc(br_igmp_mc_rep_cache, GFP_ATOMIC);
if(rep_entry_n)
{
memcpy(rep_entry_n,
rep_entry,
sizeof(struct net_bridge_mc_rep_entry));
list_add_tail(&rep_entry_n->list, &new_igmp_fdb->rep_list);
}
else
{
success = 0;
break;
}
}
if(success)
{
head = &br->mc_hash[br_igmp_mc_fdb_hash(igmp_fdb->grp.s_addr)];
hlist_add_head(&new_igmp_fdb->hlist, head);
}
else
{
list_for_each_entry_safe(rep_entry,
rep_entry_n, &new_igmp_fdb->rep_list, list) {
list_del(&rep_entry->list);
kmem_cache_free(br_igmp_mc_rep_cache, rep_entry);
}
kmem_cache_free(br_igmp_mc_fdb_cache, new_igmp_fdb);
new_igmp_fdb = NULL;
}
}
return new_igmp_fdb;
} /* br_igmp_mc_fdb_copy */
#endif
/* This function requires that br->mcl_lock is already held */
void br_igmp_mc_fdb_del_entry(struct net_bridge *br,
struct net_bridge_mc_fdb_entry *igmp_fdb)
{
struct net_bridge_mc_rep_entry *rep_entry = NULL;
struct net_bridge_mc_rep_entry *rep_entry_n = NULL;
list_for_each_entry_safe(rep_entry,
rep_entry_n, &igmp_fdb->rep_list, list) {
list_del(&rep_entry->list);
kmem_cache_free(br_igmp_mc_rep_cache, rep_entry);
}
hlist_del(&igmp_fdb->hlist);
kmem_cache_free(br_igmp_mc_fdb_cache, igmp_fdb);
return;
}
static void *snoop_seq_start(struct seq_file *seq, loff_t *pos)
{
struct net_device *dev;
loff_t offs = 0;
read_lock(&dev_base_lock);
for_each_netdev(&init_net, dev)
{
if ((dev->priv_flags & IFF_EBRIDGE) &&
(*pos == offs)) {
return dev;
}
}
++offs;
return NULL;
}
static void *snoop_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
struct net_device *dev = v;
++*pos;
for(dev = next_net_device(dev); dev; dev = next_net_device(dev)) {
if(dev->priv_flags & IFF_EBRIDGE)
return dev;
}
return NULL;
}
static int snoop_seq_show(struct seq_file *seq, void *v)
{
struct net_device *dev = v;
struct net_bridge_mc_fdb_entry *dst;
struct net_bridge *br = netdev_priv(dev);
struct net_bridge_mc_rep_entry *rep_entry;
int first;
int i;
int tstamp;
seq_printf(seq, "igmp snooping %d proxy %d lan2lan-snooping %d, rate-limit %dpps, priority %d\n",
br->igmp_snooping,
br->igmp_proxy,
br_igmp_lan2lan_snooping,
br->igmp_rate_limit,
br_mcast_get_pri_queue());
seq_printf(seq, "bridge device src-dev #tags lan-tci wan-tci");
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
seq_printf(seq, " group mode source timeout reporter Index\n");
#else
seq_printf(seq, " group mode source timeout reporter\n");
#endif
for (i = 0; i < BR_IGMP_HASH_SIZE; i++)
{
struct hlist_node *h;
hlist_for_each(h, &br->mc_hash[i])
{
dst = hlist_entry(h, struct net_bridge_mc_fdb_entry, hlist);
if(dst)
{
seq_printf(seq, "%-6s %-6s %-7s %02d 0x%08x 0x%08x",
br->dev->name,
dst->dst->dev->name,
dst->from_dev->name,
dst->num_tags,
dst->lan_tci,
dst->wan_tci);
seq_printf(seq, " 0x%08x", dst->grp.s_addr);
if ( 0 == br->igmp_snooping )
{
tstamp = 0;
}
else
{
tstamp = (int)(dst->tstamp - jiffies) / HZ;
}
seq_printf(seq, " %-4s 0x%08x %-7d",
(dst->src_entry.filt_mode == MCAST_EXCLUDE) ?
"EX" : "IN", dst->src_entry.src.s_addr,
tstamp);
first = 1;
list_for_each_entry(rep_entry, &dst->rep_list, list)
{
if(first)
{
#if defined(CONFIG_MIPS_BRCM) && defined(CONFIG_BLOG)
seq_printf(seq, " 0x%08x 0x%08x\n", rep_entry->rep.s_addr, dst->blog_idx);
#else
seq_printf(seq, " 0x%08x\n", rep_entry->rep.s_addr);
#endif
first = 0;
}
else
{
seq_printf(seq, "%84s 0x%08x\n", " ", rep_entry->rep.s_addr);
}
}
}
}
}
return 0;
}
static void snoop_seq_stop(struct seq_file *seq, void *v)
{
read_unlock(&dev_base_lock);
}
static struct seq_operations snoop_seq_ops = {
.start = snoop_seq_start,
.next = snoop_seq_next,
.stop = snoop_seq_stop,
.show = snoop_seq_show,
};
static int snoop_seq_open(struct inode *inode, struct file *file)
{
return seq_open(file, &snoop_seq_ops);
}
static struct file_operations br_igmp_snoop_proc_fops = {
.owner = THIS_MODULE,
.open = snoop_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
void br_igmp_mc_rep_free(struct net_bridge_mc_rep_entry *rep)
{
kmem_cache_free(br_igmp_mc_rep_cache, rep);
return;
}
void br_igmp_mc_fdb_free(struct net_bridge_mc_fdb_entry *mc_fdb)
{
kmem_cache_free(br_igmp_mc_fdb_cache, mc_fdb);
return;
}
int __init br_igmp_snooping_init(void)
{
br_igmp_entry = proc_create("igmp_snooping", 0, init_net.proc_net,
&br_igmp_snoop_proc_fops);
if(!br_igmp_entry) {
printk("error while creating igmp_snooping proc\n");
return -ENOMEM;
}
br_igmp_mc_fdb_cache = kmem_cache_create("bridge_igmp_mc_fdb_cache",
sizeof(struct net_bridge_mc_fdb_entry),
0,
SLAB_HWCACHE_ALIGN, NULL);
if (!br_igmp_mc_fdb_cache)
return -ENOMEM;
br_igmp_mc_rep_cache = kmem_cache_create("bridge_igmp_mc_rep_cache",
sizeof(struct net_bridge_mc_rep_entry),
0,
SLAB_HWCACHE_ALIGN, NULL);
if (!br_igmp_mc_rep_cache)
{
kmem_cache_destroy(br_igmp_mc_fdb_cache);
return -ENOMEM;
}
get_random_bytes(&br_igmp_mc_fdb_salt, sizeof(br_igmp_mc_fdb_salt));
return 0;
}
void br_igmp_snooping_fini(void)
{
kmem_cache_destroy(br_igmp_mc_fdb_cache);
kmem_cache_destroy(br_igmp_mc_rep_cache);
return;
}
#endif