1 /**
2  * @file
3  * IGMP - Internet Group Management Protocol
4  *
5  * @defgroup igmp IGMP
6  * @ingroup ip4
7  * To be called from TCPIP thread
8  */
9 
10 /*
11  * Copyright (c) 2002 CITEL Technologies Ltd.
12  * All rights reserved.
13  *
14  * Redistribution and use in source and binary forms, with or without
15  * modification, are permitted provided that the following conditions
16  * are met:
17  * 1. Redistributions of source code must retain the above copyright
18  *    notice, this list of conditions and the following disclaimer.
19  * 2. Redistributions in binary form must reproduce the above copyright
20  *    notice, this list of conditions and the following disclaimer in the
21  *    documentation and/or other materials provided with the distribution.
22  * 3. Neither the name of CITEL Technologies Ltd nor the names of its contributors
23  *    may be used to endorse or promote products derived from this software
24  *    without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY CITEL TECHNOLOGIES AND CONTRIBUTORS ``AS IS''
27  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29  * ARE DISCLAIMED.  IN NO EVENT SHALL CITEL TECHNOLOGIES OR CONTRIBUTORS BE LIABLE
30  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  *
38  * This file is a contribution to the lwIP TCP/IP stack.
39  * The Swedish Institute of Computer Science and Adam Dunkels
40  * are specifically granted permission to redistribute this
41  * source code.
42 */
43 
44 /*-------------------------------------------------------------
45 Note 1)
46 Although the rfc requires V1 AND V2 capability
47 we will only support v2 since now V1 is very old (August 1989)
48 V1 can be added if required
49 
50 a debug print and statistic have been implemented to
51 show this up.
52 -------------------------------------------------------------
53 -------------------------------------------------------------
54 Note 2)
55 A query for a specific group address (as opposed to ALLHOSTS)
56 has now been implemented as I am unsure if it is required
57 
58 a debug print and statistic have been implemented to
59 show this up.
60 -------------------------------------------------------------
61 -------------------------------------------------------------
62 Note 3)
63 The router alert rfc 2113 is implemented in outgoing packets
64 but not checked rigorously incoming
65 -------------------------------------------------------------
66 Steve Reynolds
67 ------------------------------------------------------------*/
68 
69 /*-----------------------------------------------------------------------------
70  * RFC 988  - Host extensions for IP multicasting                         - V0
71  * RFC 1054 - Host extensions for IP multicasting                         -
72  * RFC 1112 - Host extensions for IP multicasting                         - V1
73  * RFC 2236 - Internet Group Management Protocol, Version 2               - V2  <- this code is based on this RFC (it's the "de facto" standard)
74  * RFC 3376 - Internet Group Management Protocol, Version 3               - V3
75  * RFC 4604 - Using Internet Group Management Protocol Version 3...       - V3+
76  * RFC 2113 - IP Router Alert Option                                      -
77  *----------------------------------------------------------------------------*/
78 
79 /*-----------------------------------------------------------------------------
80  * Includes
81  *----------------------------------------------------------------------------*/
82 
83 #include "lwip/opt.h"
84 
85 #if LWIP_IPV4 && LWIP_IGMP /* don't build if not configured for use in lwipopts.h */
86 
87 #include "lwip/igmp.h"
88 #include "lwip/debug.h"
89 #include "lwip/def.h"
90 #include "lwip/mem.h"
91 #include "lwip/ip.h"
92 #include "lwip/inet_chksum.h"
93 #include "lwip/netif.h"
94 #include "lwip/stats.h"
95 #include "lwip/prot/igmp.h"
96 
97 #include <stdlib.h>
98 #include <string.h>
99 
100 static struct igmp_group *igmp_lookup_group(struct netif *ifp, const ip4_addr_t *addr);
101 static err_t  igmp_remove_group(struct netif* netif, struct igmp_group *group);
102 static void   igmp_timeout(struct netif *netif, struct igmp_group *group);
103 static void   igmp_start_timer(struct igmp_group *group, u8_t max_time);
104 static void   igmp_delaying_member(struct igmp_group *group, u8_t maxresp);
105 static err_t  igmp_ip_output_if(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest, struct netif *netif);
106 static void   igmp_send(struct netif *netif, struct igmp_group *group, u8_t type);
107 
108 static ip4_addr_t     allsystems;
109 static ip4_addr_t     allrouters;
110 
111 /**
112  * Initialize the IGMP module
113  */
114 void
igmp_init(void)115 igmp_init(void)
116 {
117   LWIP_DEBUGF(IGMP_DEBUG, ("igmp_init: initializing\n"));
118 
119   IP4_ADDR(&allsystems, 224, 0, 0, 1);
120   IP4_ADDR(&allrouters, 224, 0, 0, 2);
121 }
122 
123 /**
124  * Start IGMP processing on interface
125  *
126  * @param netif network interface on which start IGMP processing
127  */
128 err_t
igmp_start(struct netif * netif)129 igmp_start(struct netif *netif)
130 {
131   struct igmp_group* group;
132 
133   LWIP_DEBUGF(IGMP_DEBUG, ("igmp_start: starting IGMP processing on if %p\n", (void*)netif));
134 
135   group = igmp_lookup_group(netif, &allsystems);
136 
137   if (group != NULL) {
138     group->group_state = IGMP_GROUP_IDLE_MEMBER;
139     group->use++;
140 
141     /* Allow the igmp messages at the MAC level */
142     if (netif->igmp_mac_filter != NULL) {
143       LWIP_DEBUGF(IGMP_DEBUG, ("igmp_start: igmp_mac_filter(ADD "));
144       ip4_addr_debug_print_val(IGMP_DEBUG, allsystems);
145       LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", (void*)netif));
146       netif->igmp_mac_filter(netif, &allsystems, NETIF_ADD_MAC_FILTER);
147     }
148 
149     return ERR_OK;
150   }
151 
152   return ERR_MEM;
153 }
154 
155 /**
156  * Stop IGMP processing on interface
157  *
158  * @param netif network interface on which stop IGMP processing
159  */
160 err_t
igmp_stop(struct netif * netif)161 igmp_stop(struct netif *netif)
162 {
163   struct igmp_group *group = netif_igmp_data(netif);
164 
165   netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_IGMP, NULL);
166 
167   while (group != NULL) {
168     struct igmp_group *next = group->next; /* avoid use-after-free below */
169 
170     /* disable the group at the MAC level */
171     if (netif->igmp_mac_filter != NULL) {
172       LWIP_DEBUGF(IGMP_DEBUG, ("igmp_stop: igmp_mac_filter(DEL "));
173       ip4_addr_debug_print(IGMP_DEBUG, &group->group_address);
174       LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", (void*)netif));
175       netif->igmp_mac_filter(netif, &(group->group_address), NETIF_DEL_MAC_FILTER);
176     }
177 
178     /* free group */
179     memp_free(MEMP_IGMP_GROUP, group);
180 
181     /* move to "next" */
182     group = next;
183   }
184   return ERR_OK;
185 }
186 
187 /**
188  * Report IGMP memberships for this interface
189  *
190  * @param netif network interface on which report IGMP memberships
191  */
192 void
igmp_report_groups(struct netif * netif)193 igmp_report_groups(struct netif *netif)
194 {
195   struct igmp_group *group = netif_igmp_data(netif);
196 
197   LWIP_DEBUGF(IGMP_DEBUG, ("igmp_report_groups: sending IGMP reports on if %p\n", (void*)netif));
198 
199   /* Skip the first group in the list, it is always the allsystems group added in igmp_start() */
200   if(group != NULL) {
201     group = group->next;
202   }
203 
204   while (group != NULL) {
205     igmp_delaying_member(group, IGMP_JOIN_DELAYING_MEMBER_TMR);
206     group = group->next;
207   }
208 }
209 
210 /**
211  * Search for a group in the global igmp_group_list
212  *
213  * @param ifp the network interface for which to look
214  * @param addr the group ip address to search for
215  * @return a struct igmp_group* if the group has been found,
216  *         NULL if the group wasn't found.
217  */
218 struct igmp_group *
igmp_lookfor_group(struct netif * ifp,const ip4_addr_t * addr)219 igmp_lookfor_group(struct netif *ifp, const ip4_addr_t *addr)
220 {
221   struct igmp_group *group = netif_igmp_data(ifp);
222 
223   while (group != NULL) {
224     if (ip4_addr_cmp(&(group->group_address), addr)) {
225       return group;
226     }
227     group = group->next;
228   }
229 
230   /* to be clearer, we return NULL here instead of
231    * 'group' (which is also NULL at this point).
232    */
233   return NULL;
234 }
235 
236 /**
237  * Search for a specific igmp group and create a new one if not found-
238  *
239  * @param ifp the network interface for which to look
240  * @param addr the group ip address to search
241  * @return a struct igmp_group*,
242  *         NULL on memory error.
243  */
244 struct igmp_group *
igmp_lookup_group(struct netif * ifp,const ip4_addr_t * addr)245 igmp_lookup_group(struct netif *ifp, const ip4_addr_t *addr)
246 {
247   struct igmp_group *group;
248 
249   /* Search if the group already exists */
250   group = igmp_lookfor_group(ifp, addr);
251   if (group != NULL) {
252     /* Group already exists. */
253     return group;
254   }
255 
256   /* Group doesn't exist yet, create a new one */
257   group = (struct igmp_group *)memp_malloc(MEMP_IGMP_GROUP);
258   if (group != NULL) {
259     ip4_addr_set(&(group->group_address), addr);
260     group->timer              = 0; /* Not running */
261     group->group_state        = IGMP_GROUP_NON_MEMBER;
262     group->last_reporter_flag = 0;
263     group->use                = 0;
264     group->next               = netif_igmp_data(ifp);
265 
266     netif_set_client_data(ifp, LWIP_NETIF_CLIENT_DATA_INDEX_IGMP, group);
267   }
268 
269   LWIP_DEBUGF(IGMP_DEBUG, ("igmp_lookup_group: %sallocated a new group with address ", (group?"":"impossible to ")));
270   ip4_addr_debug_print(IGMP_DEBUG, addr);
271   LWIP_DEBUGF(IGMP_DEBUG, (" on if %p\n", (void*)ifp));
272 
273   return group;
274 }
275 
276 /**
277  * Remove a group in the global igmp_group_list, but don't free it yet
278  *
279  * @param group the group to remove from the global igmp_group_list
280  * @return ERR_OK if group was removed from the list, an err_t otherwise
281  */
282 static err_t
igmp_remove_group(struct netif * netif,struct igmp_group * group)283 igmp_remove_group(struct netif* netif, struct igmp_group *group)
284 {
285   err_t err = ERR_OK;
286 
287   /* Is it the first group? */
288   if (netif_igmp_data(netif) == group) {
289     netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_IGMP, group->next);
290   } else {
291     /* look for group further down the list */
292     struct igmp_group *tmpGroup;
293     for (tmpGroup = netif_igmp_data(netif); tmpGroup != NULL; tmpGroup = tmpGroup->next) {
294       if (tmpGroup->next == group) {
295         tmpGroup->next = group->next;
296         break;
297       }
298     }
299     /* Group not found in the global igmp_group_list */
300     if (tmpGroup == NULL) {
301       err = ERR_ARG;
302     }
303   }
304 
305   return err;
306 }
307 
308 /**
309  * Called from ip_input() if a new IGMP packet is received.
310  *
311  * @param p received igmp packet, p->payload pointing to the igmp header
312  * @param inp network interface on which the packet was received
313  * @param dest destination ip address of the igmp packet
314  */
315 void
igmp_input(struct pbuf * p,struct netif * inp,const ip4_addr_t * dest)316 igmp_input(struct pbuf *p, struct netif *inp, const ip4_addr_t *dest)
317 {
318   struct igmp_msg*   igmp;
319   struct igmp_group* group;
320   struct igmp_group* groupref;
321 
322   IGMP_STATS_INC(igmp.recv);
323 
324   /* Note that the length CAN be greater than 8 but only 8 are used - All are included in the checksum */
325   if (p->len < IGMP_MINLEN) {
326     pbuf_free(p);
327     IGMP_STATS_INC(igmp.lenerr);
328     LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: length error\n"));
329     return;
330   }
331 
332   LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: message from "));
333   ip4_addr_debug_print(IGMP_DEBUG, &(ip4_current_header()->src));
334   LWIP_DEBUGF(IGMP_DEBUG, (" to address "));
335   ip4_addr_debug_print(IGMP_DEBUG, &(ip4_current_header()->dest));
336   LWIP_DEBUGF(IGMP_DEBUG, (" on if %p\n", (void*)inp));
337 
338   /* Now calculate and check the checksum */
339   igmp = (struct igmp_msg *)p->payload;
340   if (inet_chksum(igmp, p->len)) {
341     pbuf_free(p);
342     IGMP_STATS_INC(igmp.chkerr);
343     LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: checksum error\n"));
344     return;
345   }
346 
347   /* Packet is ok so find an existing group */
348   group = igmp_lookfor_group(inp, dest); /* use the destination IP address of incoming packet */
349 
350   /* If group can be found or create... */
351   if (!group) {
352     pbuf_free(p);
353     IGMP_STATS_INC(igmp.drop);
354     LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: IGMP frame not for us\n"));
355     return;
356   }
357 
358   /* NOW ACT ON THE INCOMING MESSAGE TYPE... */
359   switch (igmp->igmp_msgtype) {
360   case IGMP_MEMB_QUERY:
361     /* IGMP_MEMB_QUERY to the "all systems" address ? */
362     if ((ip4_addr_cmp(dest, &allsystems)) && ip4_addr_isany(&igmp->igmp_group_address)) {
363       /* THIS IS THE GENERAL QUERY */
364       LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: General IGMP_MEMB_QUERY on \"ALL SYSTEMS\" address (224.0.0.1) [igmp_maxresp=%i]\n", (int)(igmp->igmp_maxresp)));
365 
366       if (igmp->igmp_maxresp == 0) {
367         IGMP_STATS_INC(igmp.rx_v1);
368         LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: got an all hosts query with time== 0 - this is V1 and not implemented - treat as v2\n"));
369         igmp->igmp_maxresp = IGMP_V1_DELAYING_MEMBER_TMR;
370       } else {
371         IGMP_STATS_INC(igmp.rx_general);
372       }
373 
374       groupref = netif_igmp_data(inp);
375 
376       /* Do not send messages on the all systems group address! */
377       /* Skip the first group in the list, it is always the allsystems group added in igmp_start() */
378       if(groupref != NULL) {
379         groupref = groupref->next;
380       }
381 
382       while (groupref) {
383         igmp_delaying_member(groupref, igmp->igmp_maxresp);
384         groupref = groupref->next;
385       }
386     } else {
387       /* IGMP_MEMB_QUERY to a specific group ? */
388       if (!ip4_addr_isany(&igmp->igmp_group_address)) {
389         LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: IGMP_MEMB_QUERY to a specific group "));
390         ip4_addr_debug_print(IGMP_DEBUG, &igmp->igmp_group_address);
391         if (ip4_addr_cmp(dest, &allsystems)) {
392           ip4_addr_t groupaddr;
393           LWIP_DEBUGF(IGMP_DEBUG, (" using \"ALL SYSTEMS\" address (224.0.0.1) [igmp_maxresp=%i]\n", (int)(igmp->igmp_maxresp)));
394           /* we first need to re-look for the group since we used dest last time */
395           ip4_addr_copy(groupaddr, igmp->igmp_group_address);
396           group = igmp_lookfor_group(inp, &groupaddr);
397         } else {
398           LWIP_DEBUGF(IGMP_DEBUG, (" with the group address as destination [igmp_maxresp=%i]\n", (int)(igmp->igmp_maxresp)));
399         }
400 
401         if (group != NULL) {
402           IGMP_STATS_INC(igmp.rx_group);
403           igmp_delaying_member(group, igmp->igmp_maxresp);
404         } else {
405           IGMP_STATS_INC(igmp.drop);
406         }
407       } else {
408         IGMP_STATS_INC(igmp.proterr);
409       }
410     }
411     break;
412   case IGMP_V2_MEMB_REPORT:
413     LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: IGMP_V2_MEMB_REPORT\n"));
414     IGMP_STATS_INC(igmp.rx_report);
415     if (group->group_state == IGMP_GROUP_DELAYING_MEMBER) {
416       /* This is on a specific group we have already looked up */
417       group->timer = 0; /* stopped */
418       group->group_state = IGMP_GROUP_IDLE_MEMBER;
419       group->last_reporter_flag = 0;
420     }
421     break;
422   default:
423     LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: unexpected msg %d in state %d on group %p on if %p\n",
424       igmp->igmp_msgtype, group->group_state, (void*)&group, (void*)inp));
425     IGMP_STATS_INC(igmp.proterr);
426     break;
427   }
428 
429   pbuf_free(p);
430   return;
431 }
432 
433 /**
434  * @ingroup igmp
435  * Join a group on one network interface.
436  *
437  * @param ifaddr ip address of the network interface which should join a new group
438  * @param groupaddr the ip address of the group which to join
439  * @return ERR_OK if group was joined on the netif(s), an err_t otherwise
440  */
441 err_t
igmp_joingroup(const ip4_addr_t * ifaddr,const ip4_addr_t * groupaddr)442 igmp_joingroup(const ip4_addr_t *ifaddr, const ip4_addr_t *groupaddr)
443 {
444   err_t err = ERR_VAL; /* no matching interface */
445   struct netif *netif;
446 
447   /* make sure it is multicast address */
448   LWIP_ERROR("igmp_joingroup: attempt to join non-multicast address", ip4_addr_ismulticast(groupaddr), return ERR_VAL;);
449   LWIP_ERROR("igmp_joingroup: attempt to join allsystems address", (!ip4_addr_cmp(groupaddr, &allsystems)), return ERR_VAL;);
450 
451   /* loop through netif's */
452   netif = netif_list;
453   while (netif != NULL) {
454     /* Should we join this interface ? */
455     if ((netif->flags & NETIF_FLAG_IGMP) && ((ip4_addr_isany(ifaddr) || ip4_addr_cmp(netif_ip4_addr(netif), ifaddr)))) {
456       err = igmp_joingroup_netif(netif, groupaddr);
457       if (err != ERR_OK) {
458         /* Return an error even if some network interfaces are joined */
459         /** @todo undo any other netif already joined */
460         return err;
461       }
462     }
463     /* proceed to next network interface */
464     netif = netif->next;
465   }
466 
467   return err;
468 }
469 
470 /**
471  * @ingroup igmp
472  * Join a group on one network interface.
473  *
474  * @param netif the network interface which should join a new group
475  * @param groupaddr the ip address of the group which to join
476  * @return ERR_OK if group was joined on the netif, an err_t otherwise
477  */
478 err_t
igmp_joingroup_netif(struct netif * netif,const ip4_addr_t * groupaddr)479 igmp_joingroup_netif(struct netif *netif, const ip4_addr_t *groupaddr)
480 {
481   struct igmp_group *group;
482 
483   /* make sure it is multicast address */
484   LWIP_ERROR("igmp_joingroup_netif: attempt to join non-multicast address", ip4_addr_ismulticast(groupaddr), return ERR_VAL;);
485   LWIP_ERROR("igmp_joingroup_netif: attempt to join allsystems address", (!ip4_addr_cmp(groupaddr, &allsystems)), return ERR_VAL;);
486 
487   /* make sure it is an igmp-enabled netif */
488   LWIP_ERROR("igmp_joingroup_netif: attempt to join on non-IGMP netif", netif->flags & NETIF_FLAG_IGMP, return ERR_VAL;);
489 
490   /* find group or create a new one if not found */
491   group = igmp_lookup_group(netif, groupaddr);
492 
493   if (group != NULL) {
494     /* This should create a new group, check the state to make sure */
495     if (group->group_state != IGMP_GROUP_NON_MEMBER) {
496       LWIP_DEBUGF(IGMP_DEBUG, ("igmp_joingroup_netif: join to group not in state IGMP_GROUP_NON_MEMBER\n"));
497     } else {
498       /* OK - it was new group */
499       LWIP_DEBUGF(IGMP_DEBUG, ("igmp_joingroup_netif: join to new group: "));
500       ip4_addr_debug_print(IGMP_DEBUG, groupaddr);
501       LWIP_DEBUGF(IGMP_DEBUG, ("\n"));
502 
503       /* If first use of the group, allow the group at the MAC level */
504       if ((group->use==0) && (netif->igmp_mac_filter != NULL)) {
505         LWIP_DEBUGF(IGMP_DEBUG, ("igmp_joingroup_netif: igmp_mac_filter(ADD "));
506         ip4_addr_debug_print(IGMP_DEBUG, groupaddr);
507         LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", (void*)netif));
508         netif->igmp_mac_filter(netif, groupaddr, NETIF_ADD_MAC_FILTER);
509       }
510 
511       IGMP_STATS_INC(igmp.tx_join);
512       igmp_send(netif, group, IGMP_V2_MEMB_REPORT);
513 
514       igmp_start_timer(group, IGMP_JOIN_DELAYING_MEMBER_TMR);
515 
516       /* Need to work out where this timer comes from */
517       group->group_state = IGMP_GROUP_DELAYING_MEMBER;
518     }
519     /* Increment group use */
520     group->use++;
521     /* Join on this interface */
522     return ERR_OK;
523   } else {
524     LWIP_DEBUGF(IGMP_DEBUG, ("igmp_joingroup_netif: Not enough memory to join to group\n"));
525     return ERR_MEM;
526   }
527 }
528 
529 /**
530  * @ingroup igmp
531  * Leave a group on one network interface.
532  *
533  * @param ifaddr ip address of the network interface which should leave a group
534  * @param groupaddr the ip address of the group which to leave
535  * @return ERR_OK if group was left on the netif(s), an err_t otherwise
536  */
537 err_t
igmp_leavegroup(const ip4_addr_t * ifaddr,const ip4_addr_t * groupaddr)538 igmp_leavegroup(const ip4_addr_t *ifaddr, const ip4_addr_t *groupaddr)
539 {
540   err_t err = ERR_VAL; /* no matching interface */
541   struct netif *netif;
542 
543   /* make sure it is multicast address */
544   LWIP_ERROR("igmp_leavegroup: attempt to leave non-multicast address", ip4_addr_ismulticast(groupaddr), return ERR_VAL;);
545   LWIP_ERROR("igmp_leavegroup: attempt to leave allsystems address", (!ip4_addr_cmp(groupaddr, &allsystems)), return ERR_VAL;);
546 
547   /* loop through netif's */
548   netif = netif_list;
549   while (netif != NULL) {
550     /* Should we leave this interface ? */
551     if ((netif->flags & NETIF_FLAG_IGMP) && ((ip4_addr_isany(ifaddr) || ip4_addr_cmp(netif_ip4_addr(netif), ifaddr)))) {
552       err_t res = igmp_leavegroup_netif(netif, groupaddr);
553       if (err != ERR_OK) {
554         /* Store this result if we have not yet gotten a success */
555         err = res;
556       }
557     }
558     /* proceed to next network interface */
559     netif = netif->next;
560   }
561 
562   return err;
563 }
564 
565 /**
566  * @ingroup igmp
567  * Leave a group on one network interface.
568  *
569  * @param netif the network interface which should leave a group
570  * @param groupaddr the ip address of the group which to leave
571  * @return ERR_OK if group was left on the netif, an err_t otherwise
572  */
573 err_t
igmp_leavegroup_netif(struct netif * netif,const ip4_addr_t * groupaddr)574 igmp_leavegroup_netif(struct netif *netif, const ip4_addr_t *groupaddr)
575 {
576   struct igmp_group *group;
577 
578   /* make sure it is multicast address */
579   LWIP_ERROR("igmp_leavegroup_netif: attempt to leave non-multicast address", ip4_addr_ismulticast(groupaddr), return ERR_VAL;);
580   LWIP_ERROR("igmp_leavegroup_netif: attempt to leave allsystems address", (!ip4_addr_cmp(groupaddr, &allsystems)), return ERR_VAL;);
581 
582   /* make sure it is an igmp-enabled netif */
583   LWIP_ERROR("igmp_leavegroup_netif: attempt to leave on non-IGMP netif", netif->flags & NETIF_FLAG_IGMP, return ERR_VAL;);
584 
585   /* find group */
586   group = igmp_lookfor_group(netif, groupaddr);
587 
588   if (group != NULL) {
589     /* Only send a leave if the flag is set according to the state diagram */
590     LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup_netif: Leaving group: "));
591     ip4_addr_debug_print(IGMP_DEBUG, groupaddr);
592     LWIP_DEBUGF(IGMP_DEBUG, ("\n"));
593 
594     /* If there is no other use of the group */
595     if (group->use <= 1) {
596       /* Remove the group from the list */
597       igmp_remove_group(netif, group);
598 
599       /* If we are the last reporter for this group */
600       if (group->last_reporter_flag) {
601         LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup_netif: sending leaving group\n"));
602         IGMP_STATS_INC(igmp.tx_leave);
603         igmp_send(netif, group, IGMP_LEAVE_GROUP);
604       }
605 
606       /* Disable the group at the MAC level */
607       if (netif->igmp_mac_filter != NULL) {
608         LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup_netif: igmp_mac_filter(DEL "));
609         ip4_addr_debug_print(IGMP_DEBUG, groupaddr);
610         LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", (void*)netif));
611         netif->igmp_mac_filter(netif, groupaddr, NETIF_DEL_MAC_FILTER);
612       }
613 
614       /* Free group struct */
615       memp_free(MEMP_IGMP_GROUP, group);
616     } else {
617       /* Decrement group use */
618       group->use--;
619     }
620     return ERR_OK;
621   } else {
622     LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup_netif: not member of group\n"));
623     return ERR_VAL;
624   }
625 }
626 
627 /**
628  * The igmp timer function (both for NO_SYS=1 and =0)
629  * Should be called every IGMP_TMR_INTERVAL milliseconds (100 ms is default).
630  */
631 void
igmp_tmr(void)632 igmp_tmr(void)
633 {
634   struct netif *netif = netif_list;
635 
636   while (netif != NULL) {
637     struct igmp_group *group = netif_igmp_data(netif);
638 
639     while (group != NULL) {
640       if (group->timer > 0) {
641         group->timer--;
642         if (group->timer == 0) {
643           igmp_timeout(netif, group);
644         }
645       }
646       group = group->next;
647     }
648     netif = netif->next;
649   }
650 }
651 
652 /**
653  * Called if a timeout for one group is reached.
654  * Sends a report for this group.
655  *
656  * @param group an igmp_group for which a timeout is reached
657  */
658 static void
igmp_timeout(struct netif * netif,struct igmp_group * group)659 igmp_timeout(struct netif *netif, struct igmp_group *group)
660 {
661   /* If the state is IGMP_GROUP_DELAYING_MEMBER then we send a report for this group
662      (unless it is the allsystems group) */
663   if ((group->group_state == IGMP_GROUP_DELAYING_MEMBER) &&
664       (!(ip4_addr_cmp(&(group->group_address), &allsystems)))) {
665     LWIP_DEBUGF(IGMP_DEBUG, ("igmp_timeout: report membership for group with address "));
666     ip4_addr_debug_print(IGMP_DEBUG, &(group->group_address));
667     LWIP_DEBUGF(IGMP_DEBUG, (" on if %p\n", (void*)netif));
668 
669     group->group_state = IGMP_GROUP_IDLE_MEMBER;
670 
671     IGMP_STATS_INC(igmp.tx_report);
672     igmp_send(netif, group, IGMP_V2_MEMB_REPORT);
673   }
674 }
675 
676 /**
677  * Start a timer for an igmp group
678  *
679  * @param group the igmp_group for which to start a timer
680  * @param max_time the time in multiples of IGMP_TMR_INTERVAL (decrease with
681  *        every call to igmp_tmr())
682  */
683 static void
igmp_start_timer(struct igmp_group * group,u8_t max_time)684 igmp_start_timer(struct igmp_group *group, u8_t max_time)
685 {
686 #ifdef LWIP_RAND
687   group->timer = max_time > 2 ? (LWIP_RAND() % max_time) : 1;
688 #else /* LWIP_RAND */
689   /* ATTENTION: use this only if absolutely necessary! */
690   group->timer = max_time / 2;
691 #endif /* LWIP_RAND */
692 
693   if (group->timer == 0) {
694     group->timer = 1;
695   }
696 }
697 
698 /**
699  * Delaying membership report for a group if necessary
700  *
701  * @param group the igmp_group for which "delaying" membership report
702  * @param maxresp query delay
703  */
704 static void
igmp_delaying_member(struct igmp_group * group,u8_t maxresp)705 igmp_delaying_member(struct igmp_group *group, u8_t maxresp)
706 {
707   if ((group->group_state == IGMP_GROUP_IDLE_MEMBER) ||
708      ((group->group_state == IGMP_GROUP_DELAYING_MEMBER) &&
709       ((group->timer == 0) || (maxresp < group->timer)))) {
710     igmp_start_timer(group, maxresp);
711     group->group_state = IGMP_GROUP_DELAYING_MEMBER;
712   }
713 }
714 
715 
716 /**
717  * Sends an IP packet on a network interface. This function constructs the IP header
718  * and calculates the IP header checksum. If the source IP address is NULL,
719  * the IP address of the outgoing network interface is filled in as source address.
720  *
721  * @param p the packet to send (p->payload points to the data, e.g. next
722             protocol header; if dest == LWIP_IP_HDRINCL, p already includes an
723             IP header and p->payload points to that IP header)
724  * @param src the source IP address to send from (if src == IP4_ADDR_ANY, the
725  *         IP  address of the netif used to send is used as source address)
726  * @param dest the destination IP address to send the packet to
727  * @param netif the netif on which to send this packet
728  * @return ERR_OK if the packet was sent OK
729  *         ERR_BUF if p doesn't have enough space for IP/LINK headers
730  *         returns errors returned by netif->output
731  */
732 static err_t
igmp_ip_output_if(struct pbuf * p,const ip4_addr_t * src,const ip4_addr_t * dest,struct netif * netif)733 igmp_ip_output_if(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest, struct netif *netif)
734 {
735   /* This is the "router alert" option */
736   u16_t ra[2];
737   ra[0] = PP_HTONS(ROUTER_ALERT);
738   ra[1] = 0x0000; /* Router shall examine packet */
739   IGMP_STATS_INC(igmp.xmit);
740   return ip4_output_if_opt(p, src, dest, IGMP_TTL, 0, IP_PROTO_IGMP, netif, ra, ROUTER_ALERTLEN);
741 }
742 
743 /**
744  * Send an igmp packet to a specific group.
745  *
746  * @param group the group to which to send the packet
747  * @param type the type of igmp packet to send
748  */
749 static void
igmp_send(struct netif * netif,struct igmp_group * group,u8_t type)750 igmp_send(struct netif *netif, struct igmp_group *group, u8_t type)
751 {
752   struct pbuf*     p    = NULL;
753   struct igmp_msg* igmp = NULL;
754   ip4_addr_t   src  = *IP4_ADDR_ANY4;
755   ip4_addr_t*  dest = NULL;
756 
757   /* IP header + "router alert" option + IGMP header */
758   p = pbuf_alloc(PBUF_TRANSPORT, IGMP_MINLEN, PBUF_RAM);
759 
760   if (p) {
761     igmp = (struct igmp_msg *)p->payload;
762     LWIP_ASSERT("igmp_send: check that first pbuf can hold struct igmp_msg",
763                (p->len >= sizeof(struct igmp_msg)));
764     ip4_addr_copy(src, *netif_ip4_addr(netif));
765 
766     if (type == IGMP_V2_MEMB_REPORT) {
767       dest = &(group->group_address);
768       ip4_addr_copy(igmp->igmp_group_address, group->group_address);
769       group->last_reporter_flag = 1; /* Remember we were the last to report */
770     } else {
771       if (type == IGMP_LEAVE_GROUP) {
772         dest = &allrouters;
773         ip4_addr_copy(igmp->igmp_group_address, group->group_address);
774       }
775     }
776 
777     if ((type == IGMP_V2_MEMB_REPORT) || (type == IGMP_LEAVE_GROUP)) {
778       igmp->igmp_msgtype  = type;
779       igmp->igmp_maxresp  = 0;
780       igmp->igmp_checksum = 0;
781       igmp->igmp_checksum = inet_chksum(igmp, IGMP_MINLEN);
782 
783       igmp_ip_output_if(p, &src, dest, netif);
784     }
785 
786     pbuf_free(p);
787   } else {
788     LWIP_DEBUGF(IGMP_DEBUG, ("igmp_send: not enough memory for igmp_send\n"));
789     IGMP_STATS_INC(igmp.memerr);
790   }
791 }
792 
793 #endif /* LWIP_IPV4 && LWIP_IGMP */
794