URL
https://opencores.org/ocsvn/or1k_old/or1k_old/trunk
Subversion Repositories or1k_old
[/] [or1k_old/] [trunk/] [uclinux/] [uClinux-2.0.x/] [net/] [ipv4/] [igmp.c] - Rev 1782
Compare with Previous | Blame | View Log
/* * Linux NET3: Internet Group Management Protocol [IGMP] * * This code implements the IGMP protocol as defined in RFC1112. There has * been a further revision of this protocol since which is now supported. * * If you have trouble with this module be careful what gcc you have used, * 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. * * Authors: * Alan Cox <Alan.Cox@linux.org> * * 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. * * Fixes: * * Alan Cox : Added lots of __inline__ to optimise * the memory usage of all the tiny little * functions. * Alan Cox : Dumped the header building experiment. * Alan Cox : Minor tweaks ready for multicast routing * and extended IGMP protocol. * Alan Cox : Removed a load of inline directives. Gcc 2.5.8 * writes utterly bogus code otherwise (sigh) * fixed IGMP loopback to behave in the manner * desired by mrouted, fixed the fact it has been * broken since 1.3.6 and cleaned up a few minor * points. * * Chih-Jen Chang : Tried to revise IGMP to Version 2 * Tsu-Sheng Tsao E-mail: chihjenc@scf.usc.edu and tsusheng@scf.usc.edu * The enhancements are mainly based on Steve Deering's * ipmulti-3.5 source code. * Chih-Jen Chang : Added the igmp_get_mrouter_info and * Tsu-Sheng Tsao igmp_set_mrouter_info to keep track of * the mrouted version on that device. * Chih-Jen Chang : Added the max_resp_time parameter to * Tsu-Sheng Tsao igmp_heard_query(). Using this parameter * to identify the multicast router version * and do what the IGMP version 2 specified. * Chih-Jen Chang : Added a timer to revert to IGMP V2 router * Tsu-Sheng Tsao if the specified time expired. * Alan Cox : Stop IGMP from 0.0.0.0 being accepted. * Alan Cox : Use GFP_ATOMIC in the right places. * Christian Daudt : igmp timer wasn't set for local group * memberships but was being deleted, * which caused a "del_timer() called * from %p with timer not initialized\n" * message (960131). * Christian Daudt : removed del_timer from * igmp_timer_expire function (960205). * Christian Daudt : igmp_heard_report now only calls * igmp_timer_expire if tm->running is * true (960216). * Malcolm Beattie : ttl comparison wrong in igmp_rcv made * igmp_heard_query never trigger. Expiry * miscalculation fixed in igmp_heard_query * and random() made to return unsigned to * prevent negative expiry times. * Alexey Kuznetsov: Wrong group leaving behaviour, backport * fix from pending 2.1.x patches. * Alan Cox: Forget to enable FDDI support earlier. * Elena Apolinario Fdez de Sousa,: IGMP Leave Messages must be sent to * Juan-Mariano de Goyeneche the "all routers" group, not the group * group being left. */ #include <asm/segment.h> #include <asm/system.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/string.h> #include <linux/config.h> #include <linux/socket.h> #include <linux/sockios.h> #include <linux/in.h> #include <linux/inet.h> #include <linux/netdevice.h> #include <linux/if_arp.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_MULTICAST /* * If time expired, change the router type to IGMP_NEW_ROUTER. */ static void ip_router_timer_expire(unsigned long data) { struct ip_router_info *i=(struct ip_router_info *)data; del_timer(&i->timer); i->type=IGMP_NEW_ROUTER; /* Revert to new multicast router */ i->time=0; } /* * Multicast router info manager */ struct ip_router_info *ip_router_info_head=(struct ip_router_info *)0; /* * Get the multicast router info on that device */ 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 = 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; } /* * Timer management */ static void igmp_stop_timer(struct ip_mc_list *im) { if (im->tm_running) { del_timer(&im->timer); im->tm_running=0; } else { printk(KERN_ERR "igmp_stop_timer() called with timer not running by %p\n",__builtin_return_address(0)); } } extern __inline__ unsigned int random(void) { static unsigned long seed=152L; seed=seed*69069L+1; 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) { int tv; if(im->tm_running) return; tv=random()%(max_resp_time*HZ/IGMP_TIMER_SCALE); /* Pick a number any number 8) */ im->timer.expires=jiffies+tv; im->tm_running=1; add_timer(&im->timer); } /* * Send an IGMP report. */ #define MAX_IGMP_SIZE (sizeof(struct igmphdr)+sizeof(struct iphdr)+64) static void igmp_send_report(struct device *dev, unsigned long address, int type) { struct sk_buff *skb=alloc_skb(MAX_IGMP_SIZE, GFP_ATOMIC); int tmp; struct igmphdr *ih; if(skb==NULL) return; if (type != IGMP_HOST_LEAVE_MESSAGE) tmp=ip_build_header(skb, dev->pa_addr, address, &dev, IPPROTO_IGMP, NULL, 28 , 0, 1, NULL); else tmp=ip_build_header(skb, dev->pa_addr, IGMP_ALL_ROUTER, &dev, IPPROTO_IGMP, NULL, 28, 0, 1, NULL); if(tmp<0) { kfree_skb(skb, FREE_WRITE); return; } ih=(struct igmphdr *)skb_put(skb,sizeof(struct igmphdr)); ih->type=type; ih->code=0; ih->csum=0; ih->group=address; ih->csum=ip_compute_csum((void *)ih,sizeof(struct igmphdr)); /* Checksum fill */ ip_queue_xmit(NULL,dev,skb,1); } static void igmp_timer_expire(unsigned long data) { struct ip_mc_list *im=(struct ip_mc_list *)data; struct ip_router_info *r; 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); else igmp_send_report(im->interface, im->multiaddr, IGMP_HOST_MEMBERSHIP_REPORT); im->reporter=1; } 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; } static void igmp_heard_report(struct device *dev, __u32 address, __u32 src) { struct ip_mc_list *im; if ((address & IGMP_LOCAL_GROUP_MASK) != IGMP_LOCAL_GROUP) { /* Timers are only set for non-local groups */ for(im=dev->ip_mc_list;im!=NULL;im=im->next) { if(im->multiaddr==address) { if(im->tm_running) igmp_stop_timer(im); if(src!=dev->pa_addr) im->reporter=0; return; } } } } static void igmp_heard_query(struct device *dev,unsigned char max_resp_time) { struct ip_mc_list *im; int mrouter_type; /* * The max_resp_time is in units of 1/10 second. */ 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(im->tm_running) { if(im->timer.expires>jiffies+max_resp_time*HZ/IGMP_TIMER_SCALE) { igmp_stop_timer(im); igmp_start_timer(im,max_resp_time); } } else { if((im->multiaddr & IGMP_LOCAL_GROUP_MASK)!=IGMP_LOCAL_GROUP) igmp_start_timer(im,max_resp_time); } } } else { mrouter_type=IGMP_OLD_ROUTER; max_resp_time=IGMP_MAX_HOST_REPORT_DELAY*IGMP_TIMER_SCALE; if(igmp_set_mrouter_info(dev,mrouter_type,IGMP_AGE_THRESHOLD)==NULL) return; /* * 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 && (im->multiaddr & IGMP_LOCAL_GROUP_MASK)!=IGMP_LOCAL_GROUP) igmp_start_timer(im,max_resp_time); } } } /* * Map a multicast IP onto multicast MAC for type ethernet. */ extern __inline__ void ip_mc_map(unsigned long addr, char *buf) { addr=ntohl(addr); buf[0]=0x01; buf[1]=0x00; buf[2]=0x5e; buf[5]=addr&0xFF; addr>>=8; buf[4]=addr&0xFF; addr>>=8; buf[3]=addr&0x7F; } /* * Add a filter to a device */ void ip_mc_filter_add(struct device *dev, unsigned long addr) { char buf[6]; if(dev->type!=ARPHRD_ETHER && dev->type!=ARPHRD_FDDI) return; /* Only do ethernet or FDDI for now */ ip_mc_map(addr,buf); dev_mc_add(dev,buf,ETH_ALEN,0); } /* * Remove a filter from a device */ void ip_mc_filter_del(struct device *dev, unsigned long addr) { char buf[6]; 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) { del_timer(&im->timer); /* It seems we have to send Leave Messages to 224.0.0.2 and not to the group itself, to remain RFC 2236 compliant... (jmel) */ /*igmp_send_report(im->interface, IGMP_ALL_ROUTER, IGMP_HOST_LEAVE_MESSAGE);*/ 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) 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); } int igmp_rcv(struct sk_buff *skb, struct device *dev, struct options *opt, __u32 daddr, unsigned short len, __u32 saddr, int redo, struct inet_protocol *protocol) { /* This basically follows the spec line by line -- see RFC1112 */ struct igmphdr *ih; /* * Mrouted needs to able to query local interfaces. So * report for the device this was sent at. (Which can * be the loopback this time) */ if(dev->flags&IFF_LOOPBACK) { dev=ip_dev_find(saddr); if(dev==NULL) dev=&loopback_dev; } ih=(struct igmphdr *)skb->h.raw; if(len <sizeof(struct igmphdr) || skb->ip_hdr->ttl<1 || ip_compute_csum((void *)skb->h.raw,sizeof(struct igmphdr))) { kfree_skb(skb, FREE_READ); return 0; } /* * I have a report that someone does this! */ if(saddr==0) { printk(KERN_INFO "Broken multicast host using 0.0.0.0 heard on %s\n", dev->name); kfree_skb(skb, FREE_READ); return 0; } if(ih->type==IGMP_HOST_MEMBERSHIP_QUERY && daddr==IGMP_ALL_HOSTS) igmp_heard_query(dev,ih->code); if(ih->type==IGMP_HOST_MEMBERSHIP_REPORT && daddr==ih->group) igmp_heard_report(dev,ih->group, saddr); if(ih->type==IGMP_HOST_NEW_MEMBERSHIP_REPORT && daddr==ih->group) igmp_heard_report(dev,ih->group, saddr); kfree_skb(skb, FREE_READ); return 0; } /* * Multicast list managers */ /* * A socket has joined a multicast group on device dev. */ static void ip_mc_inc_group(struct device *dev, unsigned long addr) { struct ip_mc_list *i; for(i=dev->ip_mc_list;i!=NULL;i=i->next) { if(i->multiaddr==addr) { i->users++; return; } } i=(struct ip_mc_list *)kmalloc(sizeof(*i), GFP_KERNEL); if(!i) 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; } /* * A socket has left a multicast group on device dev */ static void ip_mc_dec_group(struct device *dev, unsigned long 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)); } return; } } } /* * Device going down: Clean up. */ void ip_mc_drop_device(struct device *dev) { struct ip_mc_list *i; struct ip_mc_list *j; for(i=dev->ip_mc_list;i!=NULL;i=j) { j=i->next; kfree_s(i,sizeof(*i)); } dev->ip_mc_list=NULL; } /* * Device going up. Make sure it is in all hosts */ void ip_mc_allhost(struct device *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); } /* * Join a socket to a group */ int ip_mc_join_group(struct sock *sk , struct device *dev, unsigned long addr) { int unused= -1; int i; if(!MULTICAST(addr)) return -EINVAL; if(!(dev->flags&IFF_MULTICAST)) return -EADDRNOTAVAIL; 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; } /* * Ask a socket to leave a group. */ int ip_mc_leave_group(struct sock *sk, struct device *dev, unsigned long addr) { int i; if(!MULTICAST(addr)) return -EINVAL; if(!(dev->flags&IFF_MULTICAST)) return -EADDRNOTAVAIL; if(sk->ip_mc_list==NULL) return -EADDRNOTAVAIL; 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); return 0; } } return -EADDRNOTAVAIL; } /* * A socket is closing. */ void ip_mc_drop_socket(struct sock *sk) { int i; 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; } } kfree_s(sk->ip_mc_list,sizeof(*sk->ip_mc_list)); sk->ip_mc_list=NULL; } #endif