root/net/ipv4/igmp.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. igmp_stop_timer
  2. random
  3. igmp_start_timer
  4. igmp_timer_expire
  5. igmp_init_timer
  6. igmp_heard_report
  7. igmp_heard_query
  8. ip_mc_map
  9. ip_mc_filter_add
  10. ip_mc_filter_del
  11. igmp_group_dropped
  12. igmp_group_added
  13. igmp_rcv
  14. ip_mc_inc_group
  15. ip_mc_dec_group
  16. ip_mc_drop_device
  17. ip_mc_allhost
  18. ip_mc_join_group
  19. ip_mc_leave_group
  20. ip_mc_drop_socket

   1 /*
   2  *      Linux NET3:     Internet Gateway Management Protocol  [IGMP]
   3  *
   4  *      This code implements the IGMP protocol as defined in RFC1122. There has
   5  *      been a further revision of this protocol since, but since it is not 
   6  *      cleanly specified in any IETF standards we implement the old one properly
   7  *      rather than play a game of guess the BSD unofficial extensions.
   8  *
   9  *      Authors:
  10  *              Alan Cox <Alan.Cox@linux.org>   
  11  *
  12  *      This program is free software; you can redistribute it and/or
  13  *      modify it under the terms of the GNU General Public License
  14  *      as published by the Free Software Foundation; either version
  15  *      2 of the License, or (at your option) any later version.
  16  *
  17  *      Fixes:
  18  *      
  19  *              Alan Cox        :       Added lots of __inline__ to optimise
  20  *                                      the memory usage of all the tiny little
  21  *                                      functions.
  22  *              Alan Cox        :       Dumped the header building experiment.
  23  *              Alan Cox        :       Minor tweaks ready for multicast routing
  24  *                                      and extended IGMP protocol.
  25  *              Alan Cox        :       Removed a load of inline directives. Gcc 2.5.8
  26  *                                      writes utterly bogus code otherwise (sigh)
  27  *                                      fixed IGMP loopback to behave in the manner
  28  *                                      desired by mrouted, fixed the fact it has been
  29  *                                      broken since 1.3.6 and cleaned up a few minor
  30  *                                      points.
  31  */
  32  
  33  
  34 #include <asm/segment.h>
  35 #include <asm/system.h>
  36 #include <linux/types.h>
  37 #include <linux/kernel.h>
  38 #include <linux/sched.h>
  39 #include <linux/string.h>
  40 #include <linux/config.h>
  41 #include <linux/socket.h>
  42 #include <linux/sockios.h>
  43 #include <linux/in.h>
  44 #include <linux/inet.h>
  45 #include <linux/netdevice.h>
  46 #include <linux/if_arp.h>
  47 #include <net/ip.h>
  48 #include <net/protocol.h>
  49 #include <net/route.h>
  50 #include <linux/skbuff.h>
  51 #include <net/sock.h>
  52 #include <linux/igmp.h>
  53 #include <net/checksum.h>
  54 
  55 #ifdef CONFIG_IP_MULTICAST
  56 
  57 
  58 /*
  59  *      Timer management
  60  */
  61  
  62  
  63 static void igmp_stop_timer(struct ip_mc_list *im)
     /* [previous][next][first][last][top][bottom][index][help] */
  64 {
  65         del_timer(&im->timer);
  66         im->tm_running=0;
  67 }
  68 
  69 extern __inline__ int random(void)
     /* [previous][next][first][last][top][bottom][index][help] */
  70 {
  71         static unsigned long seed=152L;
  72         seed=seed*69069L+1;
  73         return seed^jiffies;
  74 }
  75 
  76 /*
  77  *      Inlined as its only called once.
  78  */
  79 
  80 static void igmp_start_timer(struct ip_mc_list *im)
     /* [previous][next][first][last][top][bottom][index][help] */
  81 {
  82         int tv;
  83         if(im->tm_running)
  84                 return;
  85         tv=random()%(10*HZ);            /* Pick a number any number 8) */
  86         im->timer.expires=jiffies+tv;
  87         im->tm_running=1;
  88         add_timer(&im->timer);
  89 }
  90  
  91 /*
  92  *      Send an IGMP report.
  93  */
  94 
  95 #define MAX_IGMP_SIZE (sizeof(struct igmphdr)+sizeof(struct iphdr)+64)
  96 
  97 static void igmp_send_report(struct device *dev, unsigned long address, int type)
  98 {
  99         struct sk_buff *skb=alloc_skb(MAX_IGMP_SIZE, GFP_ATOMIC);
 100         int tmp;
 101         struct igmphdr *ih;
 102         
 103         if(skb==NULL)
 104                 return;
 105         tmp=ip_build_header(skb, INADDR_ANY, address, &dev, IPPROTO_IGMP, NULL,
 106                                 28 , 0, 1);
 107         if(tmp<0)
 108         {
 109                 kfree_skb(skb, FREE_WRITE);
 110                 return;
 111         }
 112         ih=(struct igmphdr *)skb_put(skb,sizeof(struct igmphdr));
 113         ih->type=IGMP_HOST_MEMBERSHIP_REPORT;
 114         ih->code=0;
 115         ih->csum=0;
 116         ih->group=address;
 117         ih->csum=ip_compute_csum((void *)ih,sizeof(struct igmphdr));    /* Checksum fill */
 118         ip_queue_xmit(NULL,dev,skb,1);
 119 }
 120 
 121 
 122 static void igmp_timer_expire(unsigned long data)
     /* [previous][next][first][last][top][bottom][index][help] */
 123 {
 124         struct ip_mc_list *im=(struct ip_mc_list *)data;
 125         igmp_stop_timer(im);
 126         igmp_send_report(im->interface, im->multiaddr, IGMP_HOST_MEMBERSHIP_REPORT);
 127 }
 128 
 129 static void igmp_init_timer(struct ip_mc_list *im)
     /* [previous][next][first][last][top][bottom][index][help] */
 130 {
 131         im->tm_running=0;
 132         init_timer(&im->timer);
 133         im->timer.data=(unsigned long)im;
 134         im->timer.function=&igmp_timer_expire;
 135 }
 136         
 137 
 138 static void igmp_heard_report(struct device *dev, unsigned long address)
     /* [previous][next][first][last][top][bottom][index][help] */
 139 {
 140         struct ip_mc_list *im;
 141         for(im=dev->ip_mc_list;im!=NULL;im=im->next)
 142                 if(im->multiaddr==address)
 143                         igmp_stop_timer(im);
 144 }
 145 
 146 static void igmp_heard_query(struct device *dev)
     /* [previous][next][first][last][top][bottom][index][help] */
 147 {
 148         struct ip_mc_list *im;
 149         for(im=dev->ip_mc_list;im!=NULL;im=im->next)
 150         {
 151                 if(!im->tm_running && im->multiaddr!=IGMP_ALL_HOSTS)
 152                         igmp_start_timer(im);
 153         }
 154 }
 155 
 156 /*
 157  *      Map a multicast IP onto multicast MAC for type ethernet.
 158  */
 159  
 160 extern __inline__ void ip_mc_map(unsigned long addr, char *buf)
     /* [previous][next][first][last][top][bottom][index][help] */
 161 {
 162         addr=ntohl(addr);
 163         buf[0]=0x01;
 164         buf[1]=0x00;
 165         buf[2]=0x5e;
 166         buf[5]=addr&0xFF;
 167         addr>>=8;
 168         buf[4]=addr&0xFF;
 169         addr>>=8;
 170         buf[3]=addr&0x7F;
 171 }
 172 
 173 /*
 174  *      Add a filter to a device
 175  */
 176  
 177 void ip_mc_filter_add(struct device *dev, unsigned long addr)
     /* [previous][next][first][last][top][bottom][index][help] */
 178 {
 179         char buf[6];
 180         if(dev->type!=ARPHRD_ETHER)
 181                 return; /* Only do ethernet now */
 182         ip_mc_map(addr,buf);    
 183         dev_mc_add(dev,buf,ETH_ALEN,0);
 184 }
 185 
 186 /*
 187  *      Remove a filter from a device
 188  */
 189  
 190 void ip_mc_filter_del(struct device *dev, unsigned long addr)
     /* [previous][next][first][last][top][bottom][index][help] */
 191 {
 192         char buf[6];
 193         if(dev->type!=ARPHRD_ETHER)
 194                 return; /* Only do ethernet now */
 195         ip_mc_map(addr,buf);    
 196         dev_mc_delete(dev,buf,ETH_ALEN,0);
 197 }
 198 
 199 extern __inline__ void igmp_group_dropped(struct ip_mc_list *im)
     /* [previous][next][first][last][top][bottom][index][help] */
 200 {
 201         del_timer(&im->timer);
 202         igmp_send_report(im->interface, im->multiaddr, IGMP_HOST_LEAVE_MESSAGE);
 203         ip_mc_filter_del(im->interface, im->multiaddr);
 204 }
 205 
 206 extern __inline__ void igmp_group_added(struct ip_mc_list *im)
     /* [previous][next][first][last][top][bottom][index][help] */
 207 {
 208         igmp_init_timer(im);
 209         ip_mc_filter_add(im->interface, im->multiaddr);
 210         igmp_send_report(im->interface, im->multiaddr, IGMP_HOST_MEMBERSHIP_REPORT);
 211 }
 212 
 213 int igmp_rcv(struct sk_buff *skb, struct device *dev, struct options *opt,
     /* [previous][next][first][last][top][bottom][index][help] */
 214         __u32 daddr, unsigned short len, __u32 saddr, int redo,
 215         struct inet_protocol *protocol)
 216 {
 217         /* This basically follows the spec line by line -- see RFC1112 */
 218         struct igmphdr *ih;
 219         
 220         /*
 221          *      Mrouted needs to able to query local interfaces. So
 222          *      report for the device this was sent at. (Which can
 223          *      be the loopback this time)
 224          */
 225          
 226         if(dev->flags&IFF_LOOPBACK)
 227         {
 228                 dev=ip_dev_find(saddr);
 229                 if(dev==NULL)
 230                         dev=&loopback_dev;
 231         }
 232         ih=(struct igmphdr *)skb->h.raw;
 233                 
 234         if(skb->len <sizeof(struct igmphdr) || skb->ip_hdr->ttl>1 || ip_compute_csum((void *)skb->h.raw,sizeof(struct igmphdr)))
 235         {
 236                 kfree_skb(skb, FREE_READ);
 237                 return 0;
 238         }
 239         
 240         if(ih->type==IGMP_HOST_MEMBERSHIP_QUERY && daddr==IGMP_ALL_HOSTS)
 241                 igmp_heard_query(dev);
 242         if(ih->type==IGMP_HOST_MEMBERSHIP_REPORT && daddr==ih->group)
 243                 igmp_heard_report(dev,ih->group);
 244         kfree_skb(skb, FREE_READ);
 245         return 0;
 246 }
 247 
 248 /*
 249  *      Multicast list managers
 250  */
 251  
 252  
 253 /*
 254  *      A socket has joined a multicast group on device dev.
 255  */
 256   
 257 static void ip_mc_inc_group(struct device *dev, unsigned long addr)
     /* [previous][next][first][last][top][bottom][index][help] */
 258 {
 259         struct ip_mc_list *i;
 260         for(i=dev->ip_mc_list;i!=NULL;i=i->next)
 261         {
 262                 if(i->multiaddr==addr)
 263                 {
 264                         i->users++;
 265                         return;
 266                 }
 267         }
 268         i=(struct ip_mc_list *)kmalloc(sizeof(*i), GFP_KERNEL);
 269         if(!i)
 270                 return;
 271         i->users=1;
 272         i->interface=dev;
 273         i->multiaddr=addr;
 274         i->next=dev->ip_mc_list;
 275         igmp_group_added(i);
 276         dev->ip_mc_list=i;
 277 }
 278 
 279 /*
 280  *      A socket has left a multicast group on device dev
 281  */
 282         
 283 static void ip_mc_dec_group(struct device *dev, unsigned long addr)
     /* [previous][next][first][last][top][bottom][index][help] */
 284 {
 285         struct ip_mc_list **i;
 286         for(i=&(dev->ip_mc_list);(*i)!=NULL;i=&(*i)->next)
 287         {
 288                 if((*i)->multiaddr==addr)
 289                 {
 290                         if(--((*i)->users))
 291                                 return;
 292                         else
 293                         {
 294                                 struct ip_mc_list *tmp= *i;
 295                                 igmp_group_dropped(tmp);
 296                                 *i=(*i)->next;
 297                                 kfree_s(tmp,sizeof(*tmp));
 298                         }
 299                 }
 300         }
 301 }
 302 
 303 /*
 304  *      Device going down: Clean up.
 305  */
 306  
 307 void ip_mc_drop_device(struct device *dev)
     /* [previous][next][first][last][top][bottom][index][help] */
 308 {
 309         struct ip_mc_list *i;
 310         struct ip_mc_list *j;
 311         for(i=dev->ip_mc_list;i!=NULL;i=j)
 312         {
 313                 j=i->next;
 314                 kfree_s(i,sizeof(*i));
 315         }
 316         dev->ip_mc_list=NULL;
 317 }
 318 
 319 /*
 320  *      Device going up. Make sure it is in all hosts
 321  */
 322  
 323 void ip_mc_allhost(struct device *dev)
     /* [previous][next][first][last][top][bottom][index][help] */
 324 {
 325         struct ip_mc_list *i;
 326         for(i=dev->ip_mc_list;i!=NULL;i=i->next)
 327                 if(i->multiaddr==IGMP_ALL_HOSTS)
 328                         return;
 329         i=(struct ip_mc_list *)kmalloc(sizeof(*i), GFP_KERNEL);
 330         if(!i)
 331                 return;
 332         i->users=1;
 333         i->interface=dev;
 334         i->multiaddr=IGMP_ALL_HOSTS;
 335         i->tm_running=0;
 336         i->next=dev->ip_mc_list;
 337         dev->ip_mc_list=i;
 338         ip_mc_filter_add(i->interface, i->multiaddr);
 339 
 340 }       
 341  
 342 /*
 343  *      Join a socket to a group
 344  */
 345  
 346 int ip_mc_join_group(struct sock *sk , struct device *dev, unsigned long addr)
     /* [previous][next][first][last][top][bottom][index][help] */
 347 {
 348         int unused= -1;
 349         int i;
 350         if(!MULTICAST(addr))
 351                 return -EINVAL;
 352         if(!(dev->flags&IFF_MULTICAST))
 353                 return -EADDRNOTAVAIL;
 354         if(sk->ip_mc_list==NULL)
 355         {
 356                 if((sk->ip_mc_list=(struct ip_mc_socklist *)kmalloc(sizeof(*sk->ip_mc_list), GFP_KERNEL))==NULL)
 357                         return -ENOMEM;
 358                 memset(sk->ip_mc_list,'\0',sizeof(*sk->ip_mc_list));
 359         }
 360         for(i=0;i<IP_MAX_MEMBERSHIPS;i++)
 361         {
 362                 if(sk->ip_mc_list->multiaddr[i]==addr && sk->ip_mc_list->multidev[i]==dev)
 363                         return -EADDRINUSE;
 364                 if(sk->ip_mc_list->multidev[i]==NULL)
 365                         unused=i;
 366         }
 367         
 368         if(unused==-1)
 369                 return -ENOBUFS;
 370         sk->ip_mc_list->multiaddr[unused]=addr;
 371         sk->ip_mc_list->multidev[unused]=dev;
 372         ip_mc_inc_group(dev,addr);
 373         return 0;
 374 }
 375 
 376 /*
 377  *      Ask a socket to leave a group.
 378  */
 379  
 380 int ip_mc_leave_group(struct sock *sk, struct device *dev, unsigned long addr)
     /* [previous][next][first][last][top][bottom][index][help] */
 381 {
 382         int i;
 383         if(!MULTICAST(addr))
 384                 return -EINVAL;
 385         if(!(dev->flags&IFF_MULTICAST))
 386                 return -EADDRNOTAVAIL;
 387         if(sk->ip_mc_list==NULL)
 388                 return -EADDRNOTAVAIL;
 389                 
 390         for(i=0;i<IP_MAX_MEMBERSHIPS;i++)
 391         {
 392                 if(sk->ip_mc_list->multiaddr[i]==addr && sk->ip_mc_list->multidev[i]==dev)
 393                 {
 394                         sk->ip_mc_list->multidev[i]=NULL;
 395                         ip_mc_dec_group(dev,addr);
 396                         return 0;
 397                 }
 398         }
 399         return -EADDRNOTAVAIL;
 400 }
 401 
 402 /*
 403  *      A socket is closing.
 404  */
 405  
 406 void ip_mc_drop_socket(struct sock *sk)
     /* [previous][next][first][last][top][bottom][index][help] */
 407 {
 408         int i;
 409         
 410         if(sk->ip_mc_list==NULL)
 411                 return;
 412                 
 413         for(i=0;i<IP_MAX_MEMBERSHIPS;i++)
 414         {
 415                 if(sk->ip_mc_list->multidev[i])
 416                 {
 417                         ip_mc_dec_group(sk->ip_mc_list->multidev[i], sk->ip_mc_list->multiaddr[i]);
 418                         sk->ip_mc_list->multidev[i]=NULL;
 419                 }
 420         }
 421         kfree_s(sk->ip_mc_list,sizeof(*sk->ip_mc_list));
 422         sk->ip_mc_list=NULL;
 423 }
 424 
 425 #endif

/* [previous][next][first][last][top][bottom][index][help] */