patch-2.1.68 linux/net/ipv4/igmp.c
Next file: linux/net/ipv4/ip_alias.c
Previous file: linux/net/ipv4/icmp.c
Back to the patch index
Back to the overall index
- Lines: 996
- Date:
Sun Nov 30 14:00:39 1997
- Orig file:
v2.1.67/linux/net/ipv4/igmp.c
- Orig date:
Mon Jun 16 16:36:01 1997
diff -u --recursive --new-file v2.1.67/linux/net/ipv4/igmp.c linux/net/ipv4/igmp.c
@@ -8,6 +8,8 @@
* 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.22 1997/10/29 20:27:24 kuznet Exp $
+ *
* Authors:
* Alan Cox <Alan.Cox@linux.org>
*
@@ -65,9 +67,11 @@
* fix from pending 2.1.x patches.
* Alan Cox: Forget to enable FDDI support earlier.
* Alexey Kuznetsov: Fixed leaving groups on device down.
+ * Alexey Kuznetsov: Accordance to igmp-v2-06 draft.
*/
+#include <linux/config.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <linux/types.h>
@@ -79,141 +83,52 @@
#include <linux/in.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/inetdevice.h>
+#include <linux/igmp.h>
#include <linux/if_arp.h>
+#include <linux/rtnetlink.h>
#include <net/ip.h>
#include <net/protocol.h>
#include <net/route.h>
-#include <linux/skbuff.h>
#include <net/sock.h>
-#include <linux/igmp.h>
#include <net/checksum.h>
+#ifdef CONFIG_IP_MROUTE
+#include <linux/mroute.h>
+#endif
-int sysctl_igmp_max_host_report_delay = IGMP_MAX_HOST_REPORT_DELAY;
-int sysctl_igmp_timer_scale = IGMP_TIMER_SCALE;
-int sysctl_igmp_age_threshold = IGMP_AGE_THRESHOLD;
-/*
- * If time expired, change the router type to IGMP_NEW_ROUTER.
- */
+#ifdef CONFIG_IP_MULTICAST
-static void ip_router_timer_expire(unsigned long data)
-{
- struct ip_router_info *i=(struct ip_router_info *)data;
+/* Parameter names and values are taken from igmp-v2-06 draft */
- del_timer(&i->timer);
- i->type=IGMP_NEW_ROUTER; /* Revert to new multicast router */
- i->time=0;
-}
+#define IGMP_V1_Router_Present_Timeout (400*HZ)
+#define IGMP_Unsolicited_Report_Interval (10*HZ)
+#define IGMP_Query_Response_Interval (10*HZ)
+#define IGMP_Unsolicited_Report_Count 2
-/*
- * Multicast router info manager
- */
-struct ip_router_info *ip_router_info_head=(struct ip_router_info *)0;
+#define IGMP_Initial_Report_Delay (1*HZ)
-/*
- * Get the multicast router info on that device
+/* IGMP_Initial_Report_Delay is not from IGMP specs!
+ * IGMP specs require to report membership immediately after
+ * joining a group, but we delay the first report by a
+ * small interval. It seems more natural and still does not
+ * contradict to specs provided this delay is small enough.
*/
-static struct ip_router_info *igmp_get_mrouter_info(struct device *dev)
-{
- register struct ip_router_info *i;
-
- for(i=ip_router_info_head;i!=NULL;i=i->next)
- {
- if (i->dev == dev)
- {
- return i;
- }
- }
-
- /*
- * Not found. Create a new entry. The default is IGMP V2 router
- */
-
- i=(struct ip_router_info *)kmalloc(sizeof(*i), GFP_ATOMIC);
- if(i==NULL)
- return NULL;
- i->dev = dev;
- i->type = IGMP_NEW_ROUTER;
- i->time = sysctl_igmp_age_threshold;
- i->next = ip_router_info_head;
- ip_router_info_head = i;
-
- init_timer(&i->timer);
- i->timer.data=(unsigned long)i;
- i->timer.function=&ip_router_timer_expire;
-
- return i;
-}
-
-/*
- * Set the multicast router info on that device
- */
-
-static struct ip_router_info *igmp_set_mrouter_info(struct device *dev,int type,int time)
-{
- register struct ip_router_info *i;
-
- for(i=ip_router_info_head;i!=NULL;i=i->next)
- {
- if (i->dev == dev)
- {
- if(i->type==IGMP_OLD_ROUTER)
- {
- del_timer(&i->timer);
- }
-
- i->type = type;
- i->time = time;
-
- if(i->type==IGMP_OLD_ROUTER)
- {
- i->timer.expires=jiffies+i->time*HZ;
- add_timer(&i->timer);
- }
- return i;
- }
- }
-
- /*
- * Not found. Create a new entry.
- */
- i=(struct ip_router_info *)kmalloc(sizeof(*i), GFP_ATOMIC);
- if(i==NULL)
- return NULL;
- i->dev = dev;
- i->type = type;
- i->time = time;
- i->next = ip_router_info_head;
- ip_router_info_head = i;
-
- init_timer(&i->timer);
- i->timer.data=(unsigned long)i;
- i->timer.function=&ip_router_timer_expire;
- if(i->type==IGMP_OLD_ROUTER)
- {
- i->timer.expires=jiffies+i->time*HZ;
- add_timer(&i->timer);
- }
-
- return i;
-}
-
+#define IGMP_V1_SEEN(in_dev) ((in_dev)->mr_v1_seen && jiffies - (in_dev)->mr_v1_seen < 0)
/*
* Timer management
*/
-static void igmp_stop_timer(struct ip_mc_list *im)
+static __inline__ void igmp_stop_timer(struct ip_mc_list *im)
{
- if (im->tm_running)
- {
- del_timer(&im->timer);
- im->tm_running=0;
- }
- else
- printk(KERN_DEBUG "igmp_stop_timer() called with timer not running by %p\n",__builtin_return_address(0));
+ if (im->tm_running) {
+ del_timer(&im->timer);
+ im->tm_running=0;
+ }
}
extern __inline__ unsigned int random(void)
@@ -223,17 +138,13 @@
return seed^jiffies;
}
-/*
- * Inlined as it's only called once.
- */
-
-static void igmp_start_timer(struct ip_mc_list *im,unsigned char max_resp_time)
+static __inline__ void igmp_start_timer(struct ip_mc_list *im, int max_delay)
{
int tv;
- if(im->tm_running)
+ if (im->tm_running)
return;
- tv=random()%(max_resp_time*HZ/sysctl_igmp_timer_scale); /* Pick a number any number 8) */
- im->timer.expires=jiffies+tv;
+ tv=random() % max_delay;
+ im->timer.expires=jiffies+tv+2;
im->tm_running=1;
add_timer(&im->timer);
}
@@ -244,20 +155,32 @@
#define IGMP_SIZE (sizeof(struct igmphdr)+sizeof(struct iphdr)+4)
-static void igmp_send_report(struct device *dev, u32 group, int type)
+static int igmp_send_report(struct device *dev, u32 group, int type)
{
struct sk_buff *skb;
struct iphdr *iph;
struct igmphdr *ih;
struct rtable *rt;
+ u32 dst;
- if (ip_route_output(&rt, group, 0, 0, dev))
- return;
+ /* According to IGMPv2 specs, LEAVE messages are
+ * sent to all-routers group.
+ */
+ dst = group;
+ if (type == IGMP_HOST_LEAVE_MESSAGE)
+ dst = IGMP_ALL_ROUTER;
+
+ if (ip_route_output(&rt, dst, 0, 0, dev->ifindex))
+ return -1;
+ if (rt->rt_src == 0) {
+ ip_rt_put(rt);
+ return -1;
+ }
skb=alloc_skb(IGMP_SIZE+dev->hard_header_len+15, GFP_ATOMIC);
if (skb == NULL) {
ip_rt_put(rt);
- return;
+ return -1;
}
skb->dst = &rt->u.dst;
@@ -272,7 +195,7 @@
iph->tos = 0;
iph->frag_off = 0;
iph->ttl = 1;
- iph->daddr = group;
+ iph->daddr = dst;
iph->saddr = rt->rt_src;
iph->protocol = IPPROTO_IGMP;
iph->tot_len = htons(IGMP_SIZE);
@@ -290,115 +213,140 @@
ih->group=group;
ih->csum=ip_compute_csum((void *)ih, sizeof(struct igmphdr));
- skb->dst->output(skb);
+ return skb->dst->output(skb);
}
static void igmp_timer_expire(unsigned long data)
{
struct ip_mc_list *im=(struct ip_mc_list *)data;
- struct ip_router_info *r;
+ struct in_device *in_dev = im->interface;
+ int err;
im->tm_running=0;
- r=igmp_get_mrouter_info(im->interface);
- if(r==NULL)
- return;
- if(r->type==IGMP_NEW_ROUTER)
- igmp_send_report(im->interface, im->multiaddr, IGMP_HOST_NEW_MEMBERSHIP_REPORT);
+
+ if (IGMP_V1_SEEN(in_dev))
+ err = igmp_send_report(in_dev->dev, im->multiaddr, IGMP_HOST_MEMBERSHIP_REPORT);
else
- igmp_send_report(im->interface, im->multiaddr, IGMP_HOST_MEMBERSHIP_REPORT);
- im->reporter = 1;
-}
+ err = igmp_send_report(in_dev->dev, im->multiaddr, IGMP_HOST_NEW_MEMBERSHIP_REPORT);
-static void igmp_init_timer(struct ip_mc_list *im)
-{
- im->tm_running=0;
- init_timer(&im->timer);
- im->timer.data=(unsigned long)im;
- im->timer.function=&igmp_timer_expire;
-}
+ /* Failed. Retry later. */
+ if (err) {
+ igmp_start_timer(im, IGMP_Unsolicited_Report_Interval);
+ return;
+ }
+ if (im->unsolicit_count) {
+ im->unsolicit_count--;
+ igmp_start_timer(im, IGMP_Unsolicited_Report_Interval);
+ }
+ im->reporter = 1;
+}
-static void igmp_heard_report(struct device *dev, u32 group, u32 source)
+static void igmp_heard_report(struct in_device *in_dev, u32 group)
{
struct ip_mc_list *im;
/* Timers are only set for non-local groups */
+
if (LOCAL_MCAST(group))
return;
- for (im=dev->ip_mc_list; im!=NULL; im=im->next) {
+ for (im=in_dev->mc_list; im!=NULL; im=im->next) {
if (im->multiaddr == group) {
- if (im->tm_running)
- igmp_stop_timer(im);
- if (source != dev->pa_addr)
- im->reporter = 0;
+ igmp_stop_timer(im);
+ im->reporter = 0;
+ im->unsolicit_count = 0;
return;
}
}
}
-static void igmp_heard_query(struct device *dev, unsigned char max_resp_time,
+static void igmp_heard_query(struct in_device *in_dev, unsigned char max_resp_time,
u32 group)
{
- struct ip_mc_list *im;
- int mrouter_type;
+ struct ip_mc_list *im;
+ int max_delay;
+
+ max_delay = max_resp_time*(HZ/IGMP_TIMER_SCALE);
+ if (max_resp_time == 0) {
+ /* Alas, old v1 router presents here. */
+
+ max_delay = IGMP_Query_Response_Interval;
+ in_dev->mr_v1_seen = jiffies + IGMP_V1_Router_Present_Timeout;
+ group = 0;
+ }
+
/*
- * The max_resp_time is in units of 1/10 second.
+ * - Start the timers in all of our membership records
+ * that the query applies to for the interface on
+ * which the query arrived excl. those that belong
+ * to a "local" group (224.0.0.X)
+ * - For timers already running check if they need to
+ * be reset.
+ * - Use the igmp->igmp_code field as the maximum
+ * delay possible
*/
- if(max_resp_time>0) {
- mrouter_type=IGMP_NEW_ROUTER;
-
- if (igmp_set_mrouter_info(dev,mrouter_type,0)==NULL)
- return;
- /*
- * - Start the timers in all of our membership records
- * that the query applies to for the interface on
- * which the query arrived excl. those that belong
- * to a "local" group (224.0.0.X)
- * - For timers already running check if they need to
- * be reset.
- * - Use the igmp->igmp_code field as the maximum
- * delay possible
- */
- for(im=dev->ip_mc_list;im!=NULL;im=im->next) {
- if (group && group != im->multiaddr)
- continue;
- if(im->tm_running) {
- if(im->timer.expires>jiffies+max_resp_time*HZ/sysctl_igmp_timer_scale) {
- igmp_stop_timer(im);
- igmp_start_timer(im,max_resp_time);
- }
- } else if (!LOCAL_MCAST(im->multiaddr))
- igmp_start_timer(im,max_resp_time);
- }
- } else {
- mrouter_type=IGMP_OLD_ROUTER;
- max_resp_time=sysctl_igmp_max_host_report_delay*sysctl_igmp_timer_scale;
+ for (im=in_dev->mc_list; im!=NULL; im=im->next) {
+ if (group && group != im->multiaddr)
+ continue;
+ if (LOCAL_MCAST(im->multiaddr))
+ continue;
+ im->unsolicit_count = 0;
+ if (im->tm_running && im->timer.expires-jiffies > max_delay)
+ igmp_stop_timer(im);
+ igmp_start_timer(im, max_delay);
+ }
+}
- if(igmp_set_mrouter_info(dev,mrouter_type,sysctl_igmp_age_threshold)==NULL)
- return;
+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;
- /*
- * Start the timers in all of our membership records for
- * the interface on which the query arrived, except those
- * that are already running and those that belong to a
- * "local" group (224.0.0.X).
- */
-
- for(im=dev->ip_mc_list;im!=NULL;im=im->next) {
- if(!im->tm_running && !LOCAL_MCAST(im->multiaddr))
- igmp_start_timer(im,max_resp_time);
- }
+ if (len < sizeof(struct igmphdr) || ip_compute_csum((void *)ih, len)
+ || in_dev==NULL) {
+ kfree_skb(skb, FREE_READ);
+ return 0;
}
+
+ switch (ih->type) {
+ case IGMP_HOST_MEMBERSHIP_QUERY:
+ igmp_heard_query(in_dev, ih->code, ih->group);
+ break;
+ case IGMP_HOST_MEMBERSHIP_REPORT:
+ case IGMP_HOST_NEW_MEMBERSHIP_REPORT:
+ /* Is it our report looped back? */
+ if (((struct rtable*)skb->dst)->key.iif == 0)
+ break;
+ igmp_heard_report(in_dev, ih->group);
+ break;
+ case IGMP_PIM:
+#ifdef CONFIG_IP_PIMSM_V1
+ return pim_rcv_v1(skb, len);
+#endif
+ case IGMP_DVMRP:
+ case IGMP_TRACE:
+ case IGMP_HOST_LEAVE_MESSAGE:
+ case IGMP_MTRACE:
+ case IGMP_MTRACE_RESP:
+ break;
+ default:
+ NETDEBUG(printk(KERN_DEBUG "New IGMP type=%d, why we do not know about it?\n", ih->type));
+ }
+ kfree_skb(skb, FREE_READ);
+ return 0;
}
+#endif
+
/*
* Map a multicast IP onto multicast MAC for type ethernet.
*/
-extern __inline__ void ip_mc_map(unsigned long addr, char *buf)
+extern __inline__ void ip_mc_map(u32 addr, char *buf)
{
addr=ntohl(addr);
buf[0]=0x01;
@@ -415,15 +363,16 @@
* Add a filter to a device
*/
-void ip_mc_filter_add(struct device *dev, unsigned long addr)
+static void ip_mc_filter_add(struct in_device *in_dev, u32 addr)
{
char buf[6];
- ip_rt_multicast_event(dev);
- if(!(dev->flags & IFF_MULTICAST))
+ struct device *dev = in_dev->dev;
+
+ if (!(dev->flags & IFF_MULTICAST))
return;
- if(dev->type!=ARPHRD_ETHER && dev->type!=ARPHRD_FDDI)
+ if (dev->type!=ARPHRD_ETHER && dev->type!=ARPHRD_FDDI)
return; /* Only do ethernet or FDDI for now */
- ip_mc_map(addr,buf);
+ ip_mc_map(addr, buf);
dev_mc_add(dev,buf,ETH_ALEN,0);
}
@@ -431,70 +380,49 @@
* Remove a filter from a device
*/
-void ip_mc_filter_del(struct device *dev, unsigned long addr)
+static void ip_mc_filter_del(struct in_device *in_dev, u32 addr)
{
char buf[6];
- ip_rt_multicast_event(dev);
- if(dev->type!=ARPHRD_ETHER && dev->type!=ARPHRD_FDDI)
+ struct device *dev = in_dev->dev;
+
+ if (dev->type!=ARPHRD_ETHER && dev->type!=ARPHRD_FDDI)
return; /* Only do ethernet or FDDI for now */
ip_mc_map(addr,buf);
dev_mc_delete(dev,buf,ETH_ALEN,0);
}
-extern __inline__ void igmp_group_dropped(struct ip_mc_list *im)
+static void igmp_group_dropped(struct ip_mc_list *im)
{
- del_timer(&im->timer);
- if (im->reporter)
- igmp_send_report(im->interface, im->multiaddr, IGMP_HOST_LEAVE_MESSAGE);
ip_mc_filter_del(im->interface, im->multiaddr);
-}
-extern __inline__ void igmp_group_added(struct ip_mc_list *im)
-{
- struct ip_router_info *r;
- igmp_init_timer(im);
- ip_mc_filter_add(im->interface, im->multiaddr);
- r=igmp_get_mrouter_info(im->interface);
- if(r==NULL)
+#ifdef CONFIG_IP_MULTICAST
+ if (LOCAL_MCAST(im->multiaddr))
return;
- if(r->type==IGMP_NEW_ROUTER)
- igmp_send_report(im->interface, im->multiaddr, IGMP_HOST_NEW_MEMBERSHIP_REPORT);
- else
- igmp_send_report(im->interface, im->multiaddr, IGMP_HOST_MEMBERSHIP_REPORT);
+
+ start_bh_atomic();
+ igmp_stop_timer(im);
+ end_bh_atomic();
+
+ if (im->reporter && !IGMP_V1_SEEN(im->interface))
+ igmp_send_report(im->interface->dev, im->multiaddr, IGMP_HOST_LEAVE_MESSAGE);
+#endif
}
-int igmp_rcv(struct sk_buff *skb, unsigned short len)
+static void igmp_group_added(struct ip_mc_list *im)
{
- /* This basically follows the spec line by line -- see RFC1112 */
- struct igmphdr *ih = skb->h.igmph;
+ ip_mc_filter_add(im->interface, im->multiaddr);
- if (len < sizeof(struct igmphdr) || ip_compute_csum((void *)ih, len)) {
- kfree_skb(skb, FREE_READ);
- return 0;
- }
-
- switch (ih->type) {
- case IGMP_HOST_MEMBERSHIP_QUERY:
- igmp_heard_query(skb->dev, ih->code, ih->group);
- break;
- case IGMP_HOST_MEMBERSHIP_REPORT:
- case IGMP_HOST_NEW_MEMBERSHIP_REPORT:
- igmp_heard_report(skb->dev, ih->group, skb->nh.iph->saddr);
- break;
- case IGMP_DVMRP:
- case IGMP_PIM:
- case IGMP_TRACE:
- case IGMP_HOST_LEAVE_MESSAGE:
- case IGMP_MTRACE:
- case IGMP_MTRACE_RESP:
- break;
- default:
- NETDEBUG(printk(KERN_DEBUG "Unknown IGMP type=%d\n", ih->type));
- }
- kfree_skb(skb, FREE_READ);
- return 0;
+#ifdef CONFIG_IP_MULTICAST
+ if (LOCAL_MCAST(im->multiaddr))
+ return;
+
+ start_bh_atomic();
+ igmp_start_timer(im, IGMP_Initial_Report_Delay);
+ end_bh_atomic();
+#endif
}
+
/*
* Multicast list managers
*/
@@ -504,143 +432,210 @@
* A socket has joined a multicast group on device dev.
*/
-static void ip_mc_inc_group(struct device *dev, unsigned long addr)
+void ip_mc_inc_group(struct in_device *in_dev, u32 addr)
{
- struct ip_mc_list *i;
- for(i=dev->ip_mc_list;i!=NULL;i=i->next)
- {
- if(i->multiaddr==addr)
- {
+ struct ip_mc_list *i, *im;
+
+ im = (struct ip_mc_list *)kmalloc(sizeof(*im), GFP_KERNEL);
+
+ for (i=in_dev->mc_list; i; i=i->next) {
+ if (i->multiaddr == addr) {
i->users++;
+ if (im)
+ kfree(im);
return;
}
}
- i=(struct ip_mc_list *)kmalloc(sizeof(*i), GFP_KERNEL);
- if(!i)
+ if (!im)
return;
- i->users=1;
- i->interface=dev;
- i->multiaddr=addr;
- i->next=dev->ip_mc_list;
- igmp_group_added(i);
- dev->ip_mc_list=i;
+ im->users=1;
+ im->interface=in_dev;
+ im->multiaddr=addr;
+#ifdef CONFIG_IP_MULTICAST
+ im->tm_running=0;
+ init_timer(&im->timer);
+ im->timer.data=(unsigned long)im;
+ im->timer.function=&igmp_timer_expire;
+ im->unsolicit_count = IGMP_Unsolicited_Report_Count;
+ im->reporter = 0;
+#endif
+ im->next=in_dev->mc_list;
+ in_dev->mc_list=im;
+ if (in_dev->dev->flags & IFF_UP) {
+ igmp_group_added(im);
+ ip_rt_multicast_event(in_dev);
+ }
+ return;
}
/*
* A socket has left a multicast group on device dev
*/
-static void ip_mc_dec_group(struct device *dev, unsigned long addr)
+int ip_mc_dec_group(struct in_device *in_dev, u32 addr)
{
- struct ip_mc_list **i;
- for(i=&(dev->ip_mc_list);(*i)!=NULL;i=&(*i)->next)
- {
- if((*i)->multiaddr==addr)
- {
- if(--((*i)->users) == 0)
- {
- struct ip_mc_list *tmp= *i;
- igmp_group_dropped(tmp);
- *i=(*i)->next;
- kfree_s(tmp,sizeof(*tmp));
+ struct ip_mc_list *i, **ip;
+
+ for (ip=&in_dev->mc_list; (i=*ip)!=NULL; ip=&i->next) {
+ if (i->multiaddr==addr) {
+ if (--i->users == 0) {
+ *ip = i->next;
+ if (in_dev->dev->flags & IFF_UP) {
+ igmp_group_dropped(i);
+ ip_rt_multicast_event(in_dev);
+ }
+ kfree_s(i, sizeof(*i));
}
- return;
+ return 0;
}
}
+ return -ESRCH;
}
-/*
- * Device going down: Clean up.
- */
+/* Device going down */
-void ip_mc_drop_device(struct device *dev)
+void ip_mc_down(struct in_device *in_dev)
{
struct ip_mc_list *i;
- struct ip_mc_list *j;
- start_bh_atomic();
- for(i=dev->ip_mc_list;i!=NULL;i=j)
- {
- j=i->next;
- if(i->tm_running)
- del_timer(&i->timer);
- kfree_s(i,sizeof(*i));
- }
- dev->ip_mc_list=NULL;
- end_bh_atomic();
+
+ for (i=in_dev->mc_list; i; i=i->next)
+ igmp_group_dropped(i);
+}
+
+/* Device going up */
+
+void ip_mc_up(struct in_device *in_dev)
+{
+ struct ip_mc_list *i;
+
+ for (i=in_dev->mc_list; i; i=i->next)
+ igmp_group_added(i);
}
/*
- * Device going up. Make sure it is in all hosts
+ * Device is about to be destroyed: clean up.
*/
-void ip_mc_allhost(struct device *dev)
+void ip_mc_destroy_dev(struct in_device *in_dev)
{
struct ip_mc_list *i;
- for(i=dev->ip_mc_list;i!=NULL;i=i->next)
- if(i->multiaddr==IGMP_ALL_HOSTS)
- return;
- i=(struct ip_mc_list *)kmalloc(sizeof(*i), GFP_KERNEL);
- if(!i)
- return;
- i->users=1;
- i->interface=dev;
- i->multiaddr=IGMP_ALL_HOSTS;
- i->tm_running=0;
- i->next=dev->ip_mc_list;
- dev->ip_mc_list=i;
- ip_mc_filter_add(i->interface, i->multiaddr);
+
+ while ((i = in_dev->mc_list) != NULL) {
+ in_dev->mc_list = i->next;
+ kfree_s(i, sizeof(*i));
+ }
+}
+
+/* Initialize multicasting on an IP interface */
+
+void ip_mc_init_dev(struct in_device *in_dev)
+{
+ in_dev->mc_list = NULL;
+ in_dev->mr_v1_seen = 0;
+ ip_mc_inc_group(in_dev, IGMP_ALL_HOSTS);
+}
+
+static struct in_device * ip_mc_find_dev(struct ip_mreqn *imr)
+{
+ struct rtable *rt;
+ struct device *dev = NULL;
+
+ if (imr->imr_address.s_addr) {
+ dev = ip_dev_find(imr->imr_address.s_addr);
+ if (!dev)
+ return NULL;
+ }
+
+ if (!dev && !ip_route_output(&rt, imr->imr_multiaddr.s_addr, 0, 0, 0)) {
+ dev = rt->u.dst.dev;
+ ip_rt_put(rt);
+ }
+ if (dev) {
+ imr->imr_ifindex = dev->ifindex;
+ return dev->ip_ptr;
+ }
+ return NULL;
}
/*
* Join a socket to a group
*/
-int ip_mc_join_group(struct sock *sk , struct device *dev, unsigned long addr)
+int ip_mc_join_group(struct sock *sk , struct ip_mreqn *imr)
{
- int unused= -1;
- int i;
- if(!MULTICAST(addr))
+ int err;
+ u32 addr = imr->imr_multiaddr.s_addr;
+ struct ip_mc_socklist *iml, *i;
+ struct in_device *in_dev;
+ int count = 0;
+
+ if (!MULTICAST(addr))
return -EINVAL;
- if(sk->ip_mc_list==NULL)
- {
- if((sk->ip_mc_list=(struct ip_mc_socklist *)kmalloc(sizeof(*sk->ip_mc_list), GFP_KERNEL))==NULL)
- return -ENOMEM;
- memset(sk->ip_mc_list,'\0',sizeof(*sk->ip_mc_list));
- }
- for(i=0;i<IP_MAX_MEMBERSHIPS;i++)
- {
- if(sk->ip_mc_list->multiaddr[i]==addr && sk->ip_mc_list->multidev[i]==dev)
- return -EADDRINUSE;
- if(sk->ip_mc_list->multidev[i]==NULL)
- unused=i;
- }
- if(unused==-1)
- return -ENOBUFS;
- sk->ip_mc_list->multiaddr[unused]=addr;
- sk->ip_mc_list->multidev[unused]=dev;
- ip_mc_inc_group(dev,addr);
- return 0;
+ rtnl_shlock();
+
+ if (!imr->imr_ifindex)
+ in_dev = ip_mc_find_dev(imr);
+ else
+ in_dev = inetdev_by_index(imr->imr_ifindex);
+
+ if (!in_dev) {
+ iml = NULL;
+ err = -ENODEV;
+ goto done;
+ }
+
+ iml = (struct ip_mc_socklist *)kmalloc(sizeof(*iml), GFP_KERNEL);
+
+ err = -EADDRINUSE;
+ for (i=sk->ip_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;
+ }
+ count++;
+ }
+ err = -ENOBUFS;
+ if (iml == NULL || count >= IP_MAX_MEMBERSHIPS)
+ goto done;
+ memcpy(&iml->multi, imr, sizeof(*imr));
+ iml->next = sk->ip_mc_list;
+ iml->count = 1;
+ sk->ip_mc_list = iml;
+ ip_mc_inc_group(in_dev, addr);
+ iml = NULL;
+ err = 0;
+done:
+ rtnl_shunlock();
+ if (iml)
+ kfree(iml);
+ return err;
}
/*
* Ask a socket to leave a group.
*/
-int ip_mc_leave_group(struct sock *sk, struct device *dev, unsigned long addr)
+int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr)
{
- int i;
- if(!MULTICAST(addr))
- return -EINVAL;
- if(sk->ip_mc_list==NULL)
- return -EADDRNOTAVAIL;
+ struct ip_mc_socklist *iml, **imlp;
- for(i=0;i<IP_MAX_MEMBERSHIPS;i++)
- {
- if(sk->ip_mc_list->multiaddr[i]==addr && sk->ip_mc_list->multidev[i]==dev)
- {
- sk->ip_mc_list->multidev[i]=NULL;
- ip_mc_dec_group(dev,addr);
+ for (imlp=&sk->ip_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)
+ return 0;
+ *imlp = iml->next;
+ in_dev = inetdev_by_index(iml->multi.imr_ifindex);
+ if (in_dev)
+ ip_mc_dec_group(in_dev, imr->imr_multiaddr.s_addr);
+ kfree_s(iml, sizeof(*iml));
return 0;
}
}
@@ -653,69 +648,63 @@
void ip_mc_drop_socket(struct sock *sk)
{
- int i;
+ struct ip_mc_socklist *iml;
- if(sk->ip_mc_list==NULL)
- return;
-
- for(i=0;i<IP_MAX_MEMBERSHIPS;i++)
- {
- if(sk->ip_mc_list->multidev[i])
- {
- ip_mc_dec_group(sk->ip_mc_list->multidev[i], sk->ip_mc_list->multiaddr[i]);
- sk->ip_mc_list->multidev[i]=NULL;
- }
+ while ((iml=sk->ip_mc_list) != NULL) {
+ struct in_device *in_dev;
+ sk->ip_mc_list = iml->next;
+ if ((in_dev = inetdev_by_index(iml->multi.imr_ifindex)) != NULL)
+ ip_mc_dec_group(in_dev, iml->multi.imr_multiaddr.s_addr);
+ kfree_s(iml, sizeof(*iml));
}
- kfree_s(sk->ip_mc_list,sizeof(*sk->ip_mc_list));
- sk->ip_mc_list=NULL;
}
-/*
- * Write an multicast group list table for the IGMP daemon to
- * read.
- */
+#ifdef CONFIG_IP_MULTICAST
int ip_mc_procinfo(char *buffer, char **start, off_t offset, int length, int dummy)
{
off_t pos=0, begin=0;
struct ip_mc_list *im;
- unsigned long flags;
int len=0;
struct device *dev;
- len=sprintf(buffer,"Device : Count\tGroup Users Timer\tReporter\n");
- save_flags(flags);
- cli();
+ len=sprintf(buffer,"Idx\tDevice : Count Querier\tGroup Users Timer\tReporter\n");
for(dev = dev_base; dev; dev = dev->next)
{
- if(dev->flags&IFF_UP)
- {
- len+=sprintf(buffer+len,"%-10s: %5d\n",
- dev->name, dev->mc_count);
- for(im = dev->ip_mc_list; im; im = im->next)
- {
- len+=sprintf(buffer+len,
- "\t\t\t%08lX %5d %d:%08lX\t%d\n",
- im->multiaddr, im->users,
- im->tm_running, im->timer.expires-jiffies, im->reporter);
- pos=begin+len;
- if(pos<offset)
- {
- len=0;
- begin=pos;
- }
- if(pos>offset+length)
- break;
- }
- }
+ struct in_device *in_dev = dev->ip_ptr;
+ char *querier = "NONE";
+
+ if (in_dev == NULL)
+ continue;
+
+ querier = IGMP_V1_SEEN(in_dev) ? "V1" : "V2";
+
+ len+=sprintf(buffer+len,"%d\t%-10s: %5d %7s\n",
+ dev->ifindex, dev->name, dev->mc_count, querier);
+
+ 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",
+ im->multiaddr, im->users,
+ im->tm_running, im->timer.expires-jiffies, im->reporter);
+
+ pos=begin+len;
+ if(pos<offset)
+ {
+ len=0;
+ begin=pos;
+ }
+ if(pos>offset+length)
+ break;
+ }
}
- restore_flags(flags);
*start=buffer+(offset-begin);
len-=(offset-begin);
if(len>length)
- len=length;
+ len=length;
return len;
}
+#endif
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov