summaryrefslogtreecommitdiffstats
path: root/net/ipv4/igmp.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1999-10-09 00:00:47 +0000
committerRalf Baechle <ralf@linux-mips.org>1999-10-09 00:00:47 +0000
commitd6434e1042f3b0a6dfe1b1f615af369486f9b1fa (patch)
treee2be02f33984c48ec019c654051d27964e42c441 /net/ipv4/igmp.c
parent609d1e803baf519487233b765eb487f9ec227a18 (diff)
Merge with 2.3.19.
Diffstat (limited to 'net/ipv4/igmp.c')
-rw-r--r--net/ipv4/igmp.c270
1 files changed, 162 insertions, 108 deletions
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index 61c530418..0ff9ffeda 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -8,7 +8,7 @@
* the older version didn't come out right using gcc 2.5.8, the newer one
* seems to fall out with gcc 2.6.2.
*
- * Version: $Id: igmp.c,v 1.32 1999/06/09 10:10:53 davem Exp $
+ * Version: $Id: igmp.c,v 1.34 1999/08/20 11:05:12 davem Exp $
*
* Authors:
* Alan Cox <Alan.Cox@linux.org>
@@ -97,19 +97,12 @@
#include <linux/mroute.h>
#endif
-/* Big mc list lock for all the devices */
-static rwlock_t ip_mc_lock = RW_LOCK_UNLOCKED;
-/* Big mc list semaphore for all the sockets.
- We do not refer to this list in IP data paths or from BH,
- so that semaphore is OK.
- */
-DECLARE_MUTEX(ip_sk_mc_sem);
-
#define IP_MAX_MEMBERSHIPS 20
#ifdef CONFIG_IP_MULTICAST
+
/* Parameter names and values are taken from igmp-v2-06 draft */
#define IGMP_V1_Router_Present_Timeout (400*HZ)
@@ -129,36 +122,72 @@ DECLARE_MUTEX(ip_sk_mc_sem);
#define IGMP_V1_SEEN(in_dev) ((in_dev)->mr_v1_seen && (long)(jiffies - (in_dev)->mr_v1_seen) < 0)
+#endif
+
+static void ip_ma_put(struct ip_mc_list *im)
+{
+ if (atomic_dec_and_test(&im->refcnt)) {
+ in_dev_put(im->interface);
+ kfree_s(im, sizeof(*im));
+ }
+}
+
+#ifdef CONFIG_IP_MULTICAST
+
/*
* Timer management
*/
static __inline__ void igmp_stop_timer(struct ip_mc_list *im)
{
- if (im->tm_running) {
- del_timer(&im->timer);
- im->tm_running=0;
- }
+ spin_lock_bh(&im->lock);
+ if (del_timer(&im->timer))
+ atomic_dec(&im->refcnt);
+ im->tm_running=0;
+ im->reporter = 0;
+ im->unsolicit_count = 0;
+ spin_unlock_bh(&im->lock);
}
static __inline__ void igmp_start_timer(struct ip_mc_list *im, int max_delay)
{
- int tv;
- if (im->tm_running)
- return;
- tv=net_random() % max_delay;
+ int tv=net_random() % max_delay;
+
+ spin_lock_bh(&im->lock);
+ if (!del_timer(&im->timer))
+ atomic_inc(&im->refcnt);
im->timer.expires=jiffies+tv+2;
im->tm_running=1;
add_timer(&im->timer);
+ spin_unlock_bh(&im->lock);
+}
+
+static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
+{
+ spin_lock_bh(&im->lock);
+ im->unsolicit_count = 0;
+ if (del_timer(&im->timer)) {
+ if ((long)(im->timer.expires-jiffies) < max_delay) {
+ add_timer(&im->timer);
+ im->tm_running=1;
+ spin_unlock_bh(&im->lock);
+ return;
+ }
+ atomic_dec(&im->refcnt);
+ }
+ spin_unlock_bh(&im->lock);
+
+ igmp_start_timer(im, max_delay);
}
+
/*
* Send an IGMP report.
*/
#define IGMP_SIZE (sizeof(struct igmphdr)+sizeof(struct iphdr)+4)
-static int igmp_send_report(struct device *dev, u32 group, int type)
+static int igmp_send_report(struct net_device *dev, u32 group, int type)
{
struct sk_buff *skb;
struct iphdr *iph;
@@ -225,8 +254,6 @@ static void igmp_timer_expire(unsigned long data)
struct in_device *in_dev = im->interface;
int err;
- read_lock(&ip_mc_lock);
-
im->tm_running=0;
if (IGMP_V1_SEEN(in_dev))
@@ -236,7 +263,9 @@ static void igmp_timer_expire(unsigned long data)
/* Failed. Retry later. */
if (err) {
- igmp_start_timer(im, IGMP_Unsolicited_Report_Interval);
+ if (!in_dev->dead)
+ igmp_start_timer(im, IGMP_Unsolicited_Report_Interval);
+ ip_ma_put(im);
return;
}
@@ -245,7 +274,7 @@ static void igmp_timer_expire(unsigned long data)
igmp_start_timer(im, IGMP_Unsolicited_Report_Interval);
}
im->reporter = 1;
- read_unlock(&ip_mc_lock);
+ ip_ma_put(im);
}
static void igmp_heard_report(struct in_device *in_dev, u32 group)
@@ -254,19 +283,17 @@ static void igmp_heard_report(struct in_device *in_dev, u32 group)
/* Timers are only set for non-local groups */
- if (LOCAL_MCAST(group))
+ if (group == IGMP_ALL_HOSTS)
return;
- read_lock(&ip_mc_lock);
+ read_lock(&in_dev->lock);
for (im=in_dev->mc_list; im!=NULL; im=im->next) {
if (im->multiaddr == group) {
igmp_stop_timer(im);
- im->reporter = 0;
- im->unsolicit_count = 0;
break;
}
}
- read_unlock(&ip_mc_lock);
+ read_unlock(&in_dev->lock);
}
static void igmp_heard_query(struct in_device *in_dev, unsigned char max_resp_time,
@@ -284,7 +311,7 @@ static void igmp_heard_query(struct in_device *in_dev, unsigned char max_resp_ti
in_dev->mr_v1_seen = jiffies + IGMP_V1_Router_Present_Timeout;
group = 0;
}
-
+
/*
* - Start the timers in all of our membership records
* that the query applies to for the interface on
@@ -295,28 +322,30 @@ static void igmp_heard_query(struct in_device *in_dev, unsigned char max_resp_ti
* - Use the igmp->igmp_code field as the maximum
* delay possible
*/
- read_lock(&ip_mc_lock);
+ read_lock(&in_dev->lock);
for (im=in_dev->mc_list; im!=NULL; im=im->next) {
if (group && group != im->multiaddr)
continue;
- if (LOCAL_MCAST(im->multiaddr))
+ if (im->multiaddr == IGMP_ALL_HOSTS)
continue;
- im->unsolicit_count = 0;
- if (im->tm_running && (long)(im->timer.expires-jiffies) > max_delay)
- igmp_stop_timer(im);
- igmp_start_timer(im, max_delay);
+ igmp_mod_timer(im, max_delay);
}
- read_unlock(&ip_mc_lock);
+ read_unlock(&in_dev->lock);
}
int igmp_rcv(struct sk_buff *skb, unsigned short len)
{
/* This basically follows the spec line by line -- see RFC1112 */
struct igmphdr *ih = skb->h.igmph;
- struct in_device *in_dev = skb->dev->ip_ptr;
+ struct in_device *in_dev = in_dev_get(skb->dev);
+
+ if (in_dev==NULL) {
+ kfree_skb(skb);
+ return 0;
+ }
- if (len < sizeof(struct igmphdr) || ip_compute_csum((void *)ih, len)
- || in_dev==NULL) {
+ if (len < sizeof(struct igmphdr) || ip_compute_csum((void *)ih, len)) {
+ in_dev_put(in_dev);
kfree_skb(skb);
return 0;
}
@@ -334,6 +363,7 @@ int igmp_rcv(struct sk_buff *skb, unsigned short len)
break;
case IGMP_PIM:
#ifdef CONFIG_IP_PIMSM_V1
+ in_dev_put(in_dev);
return pim_rcv_v1(skb, len);
#endif
case IGMP_DVMRP:
@@ -345,6 +375,7 @@ int igmp_rcv(struct sk_buff *skb, unsigned short len)
default:
NETDEBUG(printk(KERN_DEBUG "New IGMP type=%d, why we do not know about it?\n", ih->type));
}
+ in_dev_put(in_dev);
kfree_skb(skb);
return 0;
}
@@ -359,7 +390,7 @@ int igmp_rcv(struct sk_buff *skb, unsigned short len)
static void ip_mc_filter_add(struct in_device *in_dev, u32 addr)
{
char buf[MAX_ADDR_LEN];
- struct device *dev = in_dev->dev;
+ struct net_device *dev = in_dev->dev;
/* Checking for IFF_MULTICAST here is WRONG-WRONG-WRONG.
We will get multicast token leakage, when IFF_MULTICAST
@@ -367,7 +398,7 @@ static void ip_mc_filter_add(struct in_device *in_dev, u32 addr)
routine. Something sort of:
if (dev->mc_list && dev->flags&IFF_MULTICAST) { do it; }
--ANK
- */
+ */
if (arp_mc_map(addr, buf, dev, 0) == 0)
dev_mc_add(dev,buf,dev->addr_len,0);
}
@@ -379,7 +410,7 @@ static void ip_mc_filter_add(struct in_device *in_dev, u32 addr)
static void ip_mc_filter_del(struct in_device *in_dev, u32 addr)
{
char buf[MAX_ADDR_LEN];
- struct device *dev = in_dev->dev;
+ struct net_device *dev = in_dev->dev;
if (arp_mc_map(addr, buf, dev, 0) == 0)
dev_mc_delete(dev,buf,dev->addr_len,0);
@@ -387,18 +418,23 @@ static void ip_mc_filter_del(struct in_device *in_dev, u32 addr)
static void igmp_group_dropped(struct ip_mc_list *im)
{
+#ifdef CONFIG_IP_MULTICAST
+ int reporter;
+#endif
+
if (im->loaded) {
im->loaded = 0;
ip_mc_filter_del(im->interface, im->multiaddr);
}
#ifdef CONFIG_IP_MULTICAST
- if (LOCAL_MCAST(im->multiaddr))
+ if (im->multiaddr == IGMP_ALL_HOSTS)
return;
+ reporter = im->reporter;
igmp_stop_timer(im);
- if (im->reporter && !IGMP_V1_SEEN(im->interface))
+ if (reporter && !IGMP_V1_SEEN(im->interface))
igmp_send_report(im->interface->dev, im->multiaddr, IGMP_HOST_LEAVE_MESSAGE);
#endif
}
@@ -411,7 +447,7 @@ static void igmp_group_added(struct ip_mc_list *im)
}
#ifdef CONFIG_IP_MULTICAST
- if (LOCAL_MCAST(im->multiaddr))
+ if (im->multiaddr == IGMP_ALL_HOSTS)
return;
igmp_start_timer(im, IGMP_Initial_Report_Delay);
@@ -430,24 +466,27 @@ static void igmp_group_added(struct ip_mc_list *im)
void ip_mc_inc_group(struct in_device *in_dev, u32 addr)
{
- struct ip_mc_list *i, *im;
+ struct ip_mc_list *im;
- im = (struct ip_mc_list *)kmalloc(sizeof(*im), GFP_KERNEL);
+ ASSERT_RTNL();
- write_lock_bh(&ip_mc_lock);
- for (i=in_dev->mc_list; i; i=i->next) {
- if (i->multiaddr == addr) {
- i->users++;
- if (im)
- kfree(im);
+ for (im=in_dev->mc_list; im; im=im->next) {
+ if (im->multiaddr == addr) {
+ im->users++;
goto out;
}
}
+
+ im = (struct ip_mc_list *)kmalloc(sizeof(*im), GFP_KERNEL);
if (!im)
goto out;
+
im->users=1;
im->interface=in_dev;
+ in_dev_hold(in_dev);
im->multiaddr=addr;
+ atomic_set(&im->refcnt, 1);
+ spin_lock_init(&im->lock);
#ifdef CONFIG_IP_MULTICAST
im->tm_running=0;
init_timer(&im->timer);
@@ -457,15 +496,14 @@ void ip_mc_inc_group(struct in_device *in_dev, u32 addr)
im->reporter = 0;
im->loaded = 0;
#endif
+ write_lock_bh(&in_dev->lock);
im->next=in_dev->mc_list;
in_dev->mc_list=im;
+ write_unlock_bh(&in_dev->lock);
igmp_group_added(im);
- write_unlock_bh(&ip_mc_lock);
if (in_dev->dev->flags & IFF_UP)
ip_rt_multicast_event(in_dev);
- return;
out:
- write_unlock_bh(&ip_mc_lock);
return;
}
@@ -477,25 +515,27 @@ int ip_mc_dec_group(struct in_device *in_dev, u32 addr)
{
int err = -ESRCH;
struct ip_mc_list *i, **ip;
-
- write_lock_bh(&ip_mc_lock);
+
+ ASSERT_RTNL();
+
for (ip=&in_dev->mc_list; (i=*ip)!=NULL; ip=&i->next) {
if (i->multiaddr==addr) {
if (--i->users == 0) {
+ write_lock_bh(&in_dev->lock);
*ip = i->next;
+ write_unlock_bh(&in_dev->lock);
igmp_group_dropped(i);
- write_unlock_bh(&ip_mc_lock);
if (in_dev->dev->flags & IFF_UP)
ip_rt_multicast_event(in_dev);
- kfree_s(i, sizeof(*i));
+
+ ip_ma_put(i);
return 0;
}
err = 0;
break;
}
}
- write_unlock_bh(&ip_mc_lock);
return -ESRCH;
}
@@ -505,10 +545,10 @@ void ip_mc_down(struct in_device *in_dev)
{
struct ip_mc_list *i;
- read_lock_bh(&ip_mc_lock);
+ ASSERT_RTNL();
+
for (i=in_dev->mc_list; i; i=i->next)
igmp_group_dropped(i);
- read_unlock_bh(&ip_mc_lock);
ip_mc_dec_group(in_dev, IGMP_ALL_HOSTS);
}
@@ -519,12 +559,12 @@ void ip_mc_up(struct in_device *in_dev)
{
struct ip_mc_list *i;
+ ASSERT_RTNL();
+
ip_mc_inc_group(in_dev, IGMP_ALL_HOSTS);
- read_lock_bh(&ip_mc_lock);
for (i=in_dev->mc_list; i; i=i->next)
igmp_group_added(i);
- read_unlock_bh(&ip_mc_lock);
}
/*
@@ -535,24 +575,32 @@ void ip_mc_destroy_dev(struct in_device *in_dev)
{
struct ip_mc_list *i;
- write_lock_bh(&ip_mc_lock);
+ ASSERT_RTNL();
+
+ write_lock_bh(&in_dev->lock);
while ((i = in_dev->mc_list) != NULL) {
in_dev->mc_list = i->next;
+ write_unlock_bh(&in_dev->lock);
+
igmp_group_dropped(i);
- kfree_s(i, sizeof(*i));
+ ip_ma_put(i);
+
+ write_lock_bh(&in_dev->lock);
}
- write_unlock_bh(&ip_mc_lock);
+ write_unlock_bh(&in_dev->lock);
}
static struct in_device * ip_mc_find_dev(struct ip_mreqn *imr)
{
struct rtable *rt;
- struct device *dev = NULL;
+ struct net_device *dev = NULL;
+ struct in_device *idev = NULL;
if (imr->imr_address.s_addr) {
dev = ip_dev_find(imr->imr_address.s_addr);
if (!dev)
return NULL;
+ __dev_put(dev);
}
if (!dev && !ip_route_output(&rt, imr->imr_multiaddr.s_addr, 0, 0, 0)) {
@@ -561,9 +609,9 @@ static struct in_device * ip_mc_find_dev(struct ip_mreqn *imr)
}
if (dev) {
imr->imr_ifindex = dev->ifindex;
- return dev->ip_ptr;
+ idev = __in_dev_get(dev);
}
- return NULL;
+ return idev;
}
/*
@@ -586,8 +634,11 @@ int ip_mc_join_group(struct sock *sk , struct ip_mreqn *imr)
if (!imr->imr_ifindex)
in_dev = ip_mc_find_dev(imr);
- else
+ else {
in_dev = inetdev_by_index(imr->imr_ifindex);
+ if (in_dev)
+ __in_dev_put(in_dev);
+ }
if (!in_dev) {
iml = NULL;
@@ -598,31 +649,28 @@ int ip_mc_join_group(struct sock *sk , struct ip_mreqn *imr)
iml = (struct ip_mc_socklist *)sock_kmalloc(sk, sizeof(*iml), GFP_KERNEL);
err = -EADDRINUSE;
- down(&ip_sk_mc_sem);
- for (i=sk->ip_mc_list; i; i=i->next) {
+ for (i=sk->protinfo.af_inet.mc_list; i; i=i->next) {
if (memcmp(&i->multi, imr, sizeof(*imr)) == 0) {
/* New style additions are reference counted */
if (imr->imr_address.s_addr == 0) {
i->count++;
err = 0;
}
- goto done_unlock;
+ goto done;
}
count++;
}
err = -ENOBUFS;
if (iml == NULL || count >= sysctl_igmp_max_memberships)
- goto done_unlock;
+ goto done;
memcpy(&iml->multi, imr, sizeof(*imr));
- iml->next = sk->ip_mc_list;
+ iml->next = sk->protinfo.af_inet.mc_list;
iml->count = 1;
- sk->ip_mc_list = iml;
+ sk->protinfo.af_inet.mc_list = iml;
ip_mc_inc_group(in_dev, addr);
iml = NULL;
err = 0;
-done_unlock:
- up(&ip_sk_mc_sem);
done:
rtnl_shunlock();
if (iml)
@@ -638,26 +686,30 @@ int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr)
{
struct ip_mc_socklist *iml, **imlp;
- down(&ip_sk_mc_sem);
- for (imlp=&sk->ip_mc_list; (iml=*imlp)!=NULL; imlp=&iml->next) {
+ rtnl_lock();
+ for (imlp=&sk->protinfo.af_inet.mc_list; (iml=*imlp)!=NULL; imlp=&iml->next) {
if (iml->multi.imr_multiaddr.s_addr==imr->imr_multiaddr.s_addr &&
iml->multi.imr_address.s_addr==imr->imr_address.s_addr &&
(!imr->imr_ifindex || iml->multi.imr_ifindex==imr->imr_ifindex)) {
struct in_device *in_dev;
- if (--iml->count)
+ if (--iml->count) {
+ rtnl_unlock();
return 0;
+ }
*imlp = iml->next;
- up(&ip_sk_mc_sem);
in_dev = inetdev_by_index(iml->multi.imr_ifindex);
- if (in_dev)
+ if (in_dev) {
ip_mc_dec_group(in_dev, imr->imr_multiaddr.s_addr);
+ in_dev_put(in_dev);
+ }
+ rtnl_unlock();
sock_kfree_s(sk, iml, sizeof(*iml));
return 0;
}
}
- up(&ip_sk_mc_sem);
+ rtnl_unlock();
return -EADDRNOTAVAIL;
}
@@ -669,36 +721,36 @@ void ip_mc_drop_socket(struct sock *sk)
{
struct ip_mc_socklist *iml;
- down(&ip_sk_mc_sem);
- while ((iml=sk->ip_mc_list) != NULL) {
+ if (sk->protinfo.af_inet.mc_list == NULL)
+ return;
+
+ rtnl_lock();
+ while ((iml=sk->protinfo.af_inet.mc_list) != NULL) {
struct in_device *in_dev;
- sk->ip_mc_list = iml->next;
- up(&ip_sk_mc_sem);
+ sk->protinfo.af_inet.mc_list = iml->next;
- if ((in_dev = inetdev_by_index(iml->multi.imr_ifindex)) != NULL)
+ if ((in_dev = inetdev_by_index(iml->multi.imr_ifindex)) != NULL) {
ip_mc_dec_group(in_dev, iml->multi.imr_multiaddr.s_addr);
+ in_dev_put(in_dev);
+ }
sock_kfree_s(sk, iml, sizeof(*iml));
- down(&ip_sk_mc_sem);
}
- up(&ip_sk_mc_sem);
+ rtnl_unlock();
}
-int ip_check_mc(struct device *dev, u32 mc_addr)
+int ip_check_mc(struct in_device *in_dev, u32 mc_addr)
{
- struct in_device *in_dev = dev->ip_ptr;
struct ip_mc_list *im;
- if (in_dev) {
- read_lock(&ip_mc_lock);
- for (im=in_dev->mc_list; im; im=im->next) {
- if (im->multiaddr == mc_addr) {
- read_unlock(&ip_mc_lock);
- return 1;
- }
+ read_lock(&in_dev->lock);
+ for (im=in_dev->mc_list; im; im=im->next) {
+ if (im->multiaddr == mc_addr) {
+ read_unlock(&in_dev->lock);
+ return 1;
}
- read_unlock(&ip_mc_lock);
}
+ read_unlock(&in_dev->lock);
return 0;
}
@@ -710,15 +762,15 @@ int ip_mc_procinfo(char *buffer, char **start, off_t offset, int length, int dum
off_t pos=0, begin=0;
struct ip_mc_list *im;
int len=0;
- struct device *dev;
+ struct net_device *dev;
len=sprintf(buffer,"Idx\tDevice : Count Querier\tGroup Users Timer\tReporter\n");
read_lock(&dev_base_lock);
for(dev = dev_base; dev; dev = dev->next) {
- struct in_device *in_dev = dev->ip_ptr;
+ struct in_device *in_dev = in_dev_get(dev);
char *querier = "NONE";
-
+
if (in_dev == NULL)
continue;
@@ -727,7 +779,7 @@ int ip_mc_procinfo(char *buffer, char **start, off_t offset, int length, int dum
len+=sprintf(buffer+len,"%d\t%-10s: %5d %7s\n",
dev->ifindex, dev->name, dev->mc_count, querier);
- read_lock(&ip_mc_lock);
+ read_lock(&in_dev->lock);
for (im = in_dev->mc_list; im; im = im->next) {
len+=sprintf(buffer+len,
"\t\t\t\t%08lX %5d %d:%08lX\t\t%d\n",
@@ -741,11 +793,13 @@ int ip_mc_procinfo(char *buffer, char **start, off_t offset, int length, int dum
begin=pos;
}
if(pos>offset+length) {
- read_unlock(&ip_mc_lock);
+ read_unlock(&in_dev->lock);
+ in_dev_put(in_dev);
goto done;
}
}
- read_unlock(&ip_mc_lock);
+ read_unlock(&in_dev->lock);
+ in_dev_put(in_dev);
}
done:
read_unlock(&dev_base_lock);