| /** |
| * @file |
| * IGMP - Internet Group Management Protocol |
| * |
| */ |
| |
| /* |
| * Copyright (c) 2002 CITEL Technologies Ltd. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of CITEL Technologies Ltd nor the names of its contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY CITEL TECHNOLOGIES AND CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL CITEL TECHNOLOGIES OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * |
| * This file is a contribution to the lwIP TCP/IP stack. |
| * The Swedish Institute of Computer Science and Adam Dunkels |
| * are specifically granted permission to redistribute this |
| * source code. |
| */ |
| |
| /*------------------------------------------------------------- |
| Note 1) |
| Although the rfc requires V1 AND V2 capability |
| we will only support v2 since now V1 is very old (August 1989) |
| V1 can be added if required |
| |
| a debug print and statistic have been implemented to |
| show this up. |
| ------------------------------------------------------------- |
| ------------------------------------------------------------- |
| Note 2) |
| A query for a specific group address (as opposed to ALLHOSTS) |
| has now been implemented as I am unsure if it is required |
| |
| a debug print and statistic have been implemented to |
| show this up. |
| ------------------------------------------------------------- |
| ------------------------------------------------------------- |
| Note 3) |
| The router alert rfc 2113 is implemented in outgoing packets |
| but not checked rigorously incoming |
| ------------------------------------------------------------- |
| Steve Reynolds |
| ------------------------------------------------------------*/ |
| |
| /*----------------------------------------------------------------------------- |
| * RFC 988 - Host extensions for IP multicasting - V0 |
| * RFC 1054 - Host extensions for IP multicasting - |
| * RFC 1112 - Host extensions for IP multicasting - V1 |
| * RFC 2236 - Internet Group Management Protocol, Version 2 - V2 <- this code is based on this RFC (it's the "de facto" standard) |
| * RFC 3376 - Internet Group Management Protocol, Version 3 - V3 |
| * RFC 4604 - Using Internet Group Management Protocol Version 3... - V3+ |
| * RFC 2113 - IP Router Alert Option - |
| *----------------------------------------------------------------------------*/ |
| |
| /*----------------------------------------------------------------------------- |
| * Includes |
| *----------------------------------------------------------------------------*/ |
| |
| #include "lwip/opt.h" |
| |
| #if LWIP_IGMP /* don't build if not configured for use in lwipopts.h */ |
| |
| #include "lwip/igmp.h" |
| #include "lwip/debug.h" |
| #include "lwip/def.h" |
| #include "lwip/mem.h" |
| #include "lwip/ip.h" |
| #include "lwip/inet_chksum.h" |
| #include "lwip/netif.h" |
| #include "lwip/icmp.h" |
| #include "lwip/udp.h" |
| #include "lwip/tcp.h" |
| #include "lwip/stats.h" |
| |
| #include "string.h" |
| |
| /* |
| * IGMP constants |
| */ |
| #define IGMP_TTL 1 |
| #define IGMP_MINLEN 8 |
| #define ROUTER_ALERT 0x9404U |
| #define ROUTER_ALERTLEN 4 |
| |
| /* |
| * IGMP message types, including version number. |
| */ |
| #define IGMP_MEMB_QUERY 0x11 /* Membership query */ |
| #define IGMP_V1_MEMB_REPORT 0x12 /* Ver. 1 membership report */ |
| #define IGMP_V2_MEMB_REPORT 0x16 /* Ver. 2 membership report */ |
| #define IGMP_LEAVE_GROUP 0x17 /* Leave-group message */ |
| |
| /* Group membership states */ |
| #define IGMP_GROUP_NON_MEMBER 0 |
| #define IGMP_GROUP_DELAYING_MEMBER 1 |
| #define IGMP_GROUP_IDLE_MEMBER 2 |
| |
| /** |
| * IGMP packet format. |
| */ |
| #ifdef PACK_STRUCT_USE_INCLUDES |
| # include "arch/bpstruct.h" |
| #endif |
| PACK_STRUCT_BEGIN |
| struct igmp_msg { |
| PACK_STRUCT_FIELD(u8_t igmp_msgtype); |
| PACK_STRUCT_FIELD(u8_t igmp_maxresp); |
| PACK_STRUCT_FIELD(u16_t igmp_checksum); |
| PACK_STRUCT_FIELD(ip_addr_p_t igmp_group_address); |
| } PACK_STRUCT_STRUCT; |
| PACK_STRUCT_END |
| #ifdef PACK_STRUCT_USE_INCLUDES |
| # include "arch/epstruct.h" |
| #endif |
| |
| |
| static struct igmp_group *igmp_lookup_group(struct netif *ifp, ip_addr_t *addr); |
| static err_t igmp_remove_group(struct igmp_group *group); |
| static void igmp_timeout( struct igmp_group *group); |
| static void igmp_start_timer(struct igmp_group *group, u8_t max_time); |
| static void igmp_delaying_member(struct igmp_group *group, u8_t maxresp); |
| static err_t igmp_ip_output_if(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest, struct netif *netif); |
| static void igmp_send(struct igmp_group *group, u8_t type); |
| |
| |
| static struct igmp_group* igmp_group_list; |
| static ip_addr_t allsystems; |
| static ip_addr_t allrouters; |
| |
| |
| /** |
| * Initialize the IGMP module |
| */ |
| void |
| igmp_init(void) |
| { |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_init: initializing\n")); |
| |
| IP4_ADDR(&allsystems, 224, 0, 0, 1); |
| IP4_ADDR(&allrouters, 224, 0, 0, 2); |
| } |
| |
| #ifdef LWIP_DEBUG |
| /** |
| * Dump global IGMP groups list |
| */ |
| void |
| igmp_dump_group_list() |
| { |
| struct igmp_group *group = igmp_group_list; |
| |
| while (group != NULL) { |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_dump_group_list: [%"U32_F"] ", (u32_t)(group->group_state))); |
| ip_addr_debug_print(IGMP_DEBUG, &group->group_address); |
| LWIP_DEBUGF(IGMP_DEBUG, (" on if %p\n", group->netif)); |
| group = group->next; |
| } |
| LWIP_DEBUGF(IGMP_DEBUG, ("\n")); |
| } |
| #else |
| #define igmp_dump_group_list() |
| #endif /* LWIP_DEBUG */ |
| |
| /** |
| * Start IGMP processing on interface |
| * |
| * @param netif network interface on which start IGMP processing |
| */ |
| err_t |
| igmp_start(struct netif *netif) |
| { |
| err_t err = ERR_OK; |
| struct igmp_group* group; |
| |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_start: starting IGMP processing on if %p\n", netif)); |
| |
| group = igmp_lookup_group(netif, &allsystems); |
| |
| if (group != NULL) { |
| group->group_state = IGMP_GROUP_IDLE_MEMBER; |
| group->use++; |
| |
| /* Allow the igmp messages at the MAC level */ |
| if (netif->igmp_mac_filter != NULL) { |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_start: igmp_mac_filter(ADD ")); |
| ip_addr_debug_print(IGMP_DEBUG, &allsystems); |
| LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", netif)); |
| err = netif->igmp_mac_filter(netif, &allsystems, IGMP_ADD_MAC_FILTER); |
| } |
| |
| return err; |
| } |
| |
| return ERR_MEM; |
| } |
| |
| /** |
| * Stop IGMP processing on interface |
| * |
| * @param netif network interface on which stop IGMP processing |
| */ |
| err_t |
| igmp_stop(struct netif *netif) |
| { |
| err_t err = ERR_OK; |
| struct igmp_group *group = igmp_group_list; |
| struct igmp_group *prev = NULL; |
| struct igmp_group *next; |
| |
| /* look for groups joined on this interface further down the list */ |
| while (group != NULL) { |
| next = group->next; |
| /* is it a group joined on this interface? */ |
| if (group->netif == netif) { |
| /* is it the first group of the list? */ |
| if (group == igmp_group_list) { |
| igmp_group_list = next; |
| } |
| /* is there a "previous" group defined? */ |
| if (prev != NULL) { |
| prev->next = next; |
| } |
| /* disable the group at the MAC level */ |
| if (netif->igmp_mac_filter != NULL) { |
| err_t macFilterErr; |
| |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_stop: igmp_mac_filter(DEL ")); |
| ip_addr_debug_print(IGMP_DEBUG, &group->group_address); |
| LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", netif)); |
| |
| macFilterErr = netif->igmp_mac_filter(netif, &(group->group_address), IGMP_DEL_MAC_FILTER); |
| |
| /* If there was an error, don't overwrite it */ |
| if (macFilterErr != ERR_OK) { |
| err = macFilterErr; |
| } |
| } |
| /* free group */ |
| memp_free(MEMP_IGMP_GROUP, group); |
| } else { |
| /* change the "previous" */ |
| prev = group; |
| } |
| /* move to "next" */ |
| group = next; |
| } |
| return err; |
| } |
| |
| /** |
| * Report IGMP memberships for this interface |
| * |
| * @param netif network interface on which report IGMP memberships |
| */ |
| void |
| igmp_report_groups(struct netif *netif) |
| { |
| struct igmp_group *group = igmp_group_list; |
| |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_report_groups: sending IGMP reports on if %p\n", netif)); |
| |
| while (group != NULL) { |
| if (group->netif == netif) { |
| igmp_delaying_member(group, IGMP_JOIN_DELAYING_MEMBER_TMR); |
| } |
| group = group->next; |
| } |
| } |
| |
| /** |
| * Search for a group in the global igmp_group_list |
| * |
| * @param ifp the network interface for which to look |
| * @param addr the group ip address to search for |
| * @return a struct igmp_group* if the group has been found, |
| * NULL if the group wasn't found. |
| */ |
| struct igmp_group * |
| igmp_lookfor_group(struct netif *ifp, ip_addr_t *addr) |
| { |
| struct igmp_group *group = igmp_group_list; |
| |
| while (group != NULL) { |
| if ((group->netif == ifp) && (ip_addr_cmp(&(group->group_address), addr))) { |
| return group; |
| } |
| group = group->next; |
| } |
| |
| /* to be clearer, we return NULL here instead of |
| * 'group' (which is also NULL at this point). |
| */ |
| return NULL; |
| } |
| |
| /** |
| * Search for a specific igmp group and create a new one if not found- |
| * |
| * @param ifp the network interface for which to look |
| * @param addr the group ip address to search |
| * @return a struct igmp_group*, |
| * NULL on memory error. |
| */ |
| struct igmp_group * |
| igmp_lookup_group(struct netif *ifp, ip_addr_t *addr) |
| { |
| struct igmp_group *group = igmp_group_list; |
| |
| /* Search if the group already exists */ |
| group = igmp_lookfor_group(ifp, addr); |
| if (group != NULL) { |
| /* Group already exists. */ |
| return group; |
| } |
| |
| /* Group doesn't exist yet, create a new one */ |
| group = (struct igmp_group *)memp_malloc(MEMP_IGMP_GROUP); |
| if (group != NULL) { |
| group->netif = ifp; |
| ip_addr_set(&(group->group_address), addr); |
| group->timer = 0; /* Not running */ |
| group->group_state = IGMP_GROUP_NON_MEMBER; |
| group->last_reporter_flag = 0; |
| group->use = 0; |
| group->next = igmp_group_list; |
| |
| igmp_group_list = group; |
| } |
| |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_lookup_group: %sallocated a new group with address ", (group?"":"impossible to "))); |
| ip_addr_debug_print(IGMP_DEBUG, addr); |
| LWIP_DEBUGF(IGMP_DEBUG, (" on if %p\n", ifp)); |
| |
| return group; |
| } |
| |
| /** |
| * Remove a group in the global igmp_group_list |
| * |
| * @param group the group to remove from the global igmp_group_list |
| * @return ERR_OK if group was removed from the list, an err_t otherwise |
| */ |
| static err_t |
| igmp_remove_group(struct igmp_group *group) |
| { |
| err_t err = ERR_OK; |
| |
| /* Is it the first group? */ |
| if (igmp_group_list == group) { |
| igmp_group_list = group->next; |
| } else { |
| /* look for group further down the list */ |
| struct igmp_group *tmpGroup; |
| for (tmpGroup = igmp_group_list; tmpGroup != NULL; tmpGroup = tmpGroup->next) { |
| if (tmpGroup->next == group) { |
| tmpGroup->next = group->next; |
| break; |
| } |
| } |
| /* Group not found in the global igmp_group_list */ |
| if (tmpGroup == NULL) |
| err = ERR_ARG; |
| } |
| /* free group */ |
| memp_free(MEMP_IGMP_GROUP, group); |
| |
| return err; |
| } |
| |
| /** |
| * Called from ip_input() if a new IGMP packet is received. |
| * |
| * @param p received igmp packet, p->payload pointing to the igmp header |
| * @param inp network interface on which the packet was received |
| * @param dest destination ip address of the igmp packet |
| */ |
| void |
| igmp_input(struct pbuf *p, struct netif *inp, ip_addr_t *dest) |
| { |
| struct igmp_msg* igmp; |
| struct igmp_group* group; |
| struct igmp_group* groupref; |
| |
| IGMP_STATS_INC(igmp.recv); |
| |
| /* Note that the length CAN be greater than 8 but only 8 are used - All are included in the checksum */ |
| if (p->len < IGMP_MINLEN) { |
| pbuf_free(p); |
| IGMP_STATS_INC(igmp.lenerr); |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: length error\n")); |
| return; |
| } |
| |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: message from ")); |
| ip_addr_debug_print(IGMP_DEBUG, &(ip_current_header()->src)); |
| LWIP_DEBUGF(IGMP_DEBUG, (" to address ")); |
| ip_addr_debug_print(IGMP_DEBUG, &(ip_current_header()->dest)); |
| LWIP_DEBUGF(IGMP_DEBUG, (" on if %p\n", inp)); |
| |
| /* Now calculate and check the checksum */ |
| igmp = (struct igmp_msg *)p->payload; |
| if (inet_chksum(igmp, p->len)) { |
| pbuf_free(p); |
| IGMP_STATS_INC(igmp.chkerr); |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: checksum error\n")); |
| return; |
| } |
| |
| /* Packet is ok so find an existing group */ |
| group = igmp_lookfor_group(inp, dest); /* use the destination IP address of incoming packet */ |
| |
| /* If group can be found or create... */ |
| if (!group) { |
| pbuf_free(p); |
| IGMP_STATS_INC(igmp.drop); |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: IGMP frame not for us\n")); |
| return; |
| } |
| |
| /* NOW ACT ON THE INCOMING MESSAGE TYPE... */ |
| switch (igmp->igmp_msgtype) { |
| case IGMP_MEMB_QUERY: { |
| /* IGMP_MEMB_QUERY to the "all systems" address ? */ |
| if ((ip_addr_cmp(dest, &allsystems)) && ip_addr_isany(&igmp->igmp_group_address)) { |
| /* THIS IS THE GENERAL QUERY */ |
| 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))); |
| |
| if (igmp->igmp_maxresp == 0) { |
| IGMP_STATS_INC(igmp.rx_v1); |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: got an all hosts query with time== 0 - this is V1 and not implemented - treat as v2\n")); |
| igmp->igmp_maxresp = IGMP_V1_DELAYING_MEMBER_TMR; |
| } else { |
| IGMP_STATS_INC(igmp.rx_general); |
| } |
| |
| groupref = igmp_group_list; |
| while (groupref) { |
| /* Do not send messages on the all systems group address! */ |
| if ((groupref->netif == inp) && (!(ip_addr_cmp(&(groupref->group_address), &allsystems)))) { |
| igmp_delaying_member(groupref, igmp->igmp_maxresp); |
| } |
| groupref = groupref->next; |
| } |
| } else { |
| /* IGMP_MEMB_QUERY to a specific group ? */ |
| if (!ip_addr_isany(&igmp->igmp_group_address)) { |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: IGMP_MEMB_QUERY to a specific group ")); |
| ip_addr_debug_print(IGMP_DEBUG, &igmp->igmp_group_address); |
| if (ip_addr_cmp(dest, &allsystems)) { |
| ip_addr_t groupaddr; |
| LWIP_DEBUGF(IGMP_DEBUG, (" using \"ALL SYSTEMS\" address (224.0.0.1) [igmp_maxresp=%i]\n", (int)(igmp->igmp_maxresp))); |
| /* we first need to re-look for the group since we used dest last time */ |
| ip_addr_copy(groupaddr, igmp->igmp_group_address); |
| group = igmp_lookfor_group(inp, &groupaddr); |
| } else { |
| LWIP_DEBUGF(IGMP_DEBUG, (" with the group address as destination [igmp_maxresp=%i]\n", (int)(igmp->igmp_maxresp))); |
| } |
| |
| if (group != NULL) { |
| IGMP_STATS_INC(igmp.rx_group); |
| igmp_delaying_member(group, igmp->igmp_maxresp); |
| } else { |
| IGMP_STATS_INC(igmp.drop); |
| } |
| } else { |
| IGMP_STATS_INC(igmp.proterr); |
| } |
| } |
| break; |
| } |
| case IGMP_V2_MEMB_REPORT: { |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: IGMP_V2_MEMB_REPORT\n")); |
| IGMP_STATS_INC(igmp.rx_report); |
| if (group->group_state == IGMP_GROUP_DELAYING_MEMBER) { |
| /* This is on a specific group we have already looked up */ |
| group->timer = 0; /* stopped */ |
| group->group_state = IGMP_GROUP_IDLE_MEMBER; |
| group->last_reporter_flag = 0; |
| } |
| break; |
| } |
| default: { |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: unexpected msg %d in state %d on group %p on if %p\n", |
| igmp->igmp_msgtype, group->group_state, &group, group->netif)); |
| IGMP_STATS_INC(igmp.proterr); |
| break; |
| } |
| } |
| |
| pbuf_free(p); |
| return; |
| } |
| |
| /** |
| * Join a group on one network interface. |
| * |
| * @param ifaddr ip address of the network interface which should join a new group |
| * @param groupaddr the ip address of the group which to join |
| * @return ERR_OK if group was joined on the netif(s), an err_t otherwise |
| */ |
| err_t |
| igmp_joingroup(ip_addr_t *ifaddr, ip_addr_t *groupaddr) |
| { |
| err_t err = ERR_VAL; /* no matching interface */ |
| struct igmp_group *group; |
| struct netif *netif; |
| |
| /* make sure it is multicast address */ |
| LWIP_ERROR("igmp_joingroup: attempt to join non-multicast address", ip_addr_ismulticast(groupaddr), return ERR_VAL;); |
| LWIP_ERROR("igmp_joingroup: attempt to join allsystems address", (!ip_addr_cmp(groupaddr, &allsystems)), return ERR_VAL;); |
| |
| /* loop through netif's */ |
| netif = netif_list; |
| while (netif != NULL) { |
| /* Should we join this interface ? */ |
| if ((netif->flags & NETIF_FLAG_IGMP) && ((ip_addr_isany(ifaddr) || ip_addr_cmp(&(netif->ip_addr), ifaddr)))) { |
| /* find group or create a new one if not found */ |
| group = igmp_lookup_group(netif, groupaddr); |
| |
| if (group != NULL) { |
| /* Indicate success when there is a match for the first time */ |
| if (err == ERR_VAL) { |
| err = ERR_OK; |
| } |
| |
| /* This should create a new group, check the state to make sure */ |
| if (group->group_state != IGMP_GROUP_NON_MEMBER) { |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_joingroup: join to group not in state IGMP_GROUP_NON_MEMBER\n")); |
| } else { |
| /* OK - it was new group */ |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_joingroup: join to new group: ")); |
| ip_addr_debug_print(IGMP_DEBUG, groupaddr); |
| LWIP_DEBUGF(IGMP_DEBUG, ("\n")); |
| |
| /* If first use of the group, allow the group at the MAC level */ |
| if ((group->use==0) && (netif->igmp_mac_filter != NULL)) { |
| err_t macFilterErr; |
| |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_joingroup: igmp_mac_filter(ADD ")); |
| ip_addr_debug_print(IGMP_DEBUG, groupaddr); |
| LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", netif)); |
| |
| macFilterErr = netif->igmp_mac_filter(netif, groupaddr, IGMP_ADD_MAC_FILTER); |
| |
| /* If there was an error, don't overwrite it */ |
| if (macFilterErr != ERR_OK) { |
| err = macFilterErr; |
| } |
| } |
| |
| IGMP_STATS_INC(igmp.tx_join); |
| igmp_send(group, IGMP_V2_MEMB_REPORT); |
| |
| igmp_start_timer(group, IGMP_JOIN_DELAYING_MEMBER_TMR); |
| |
| /* Need to work out where this timer comes from */ |
| group->group_state = IGMP_GROUP_DELAYING_MEMBER; |
| } |
| /* Increment group use */ |
| group->use++; |
| |
| } else { |
| /* Return an error even if some network interfaces are joined */ |
| /** @todo undo any other netif already joined */ |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_joingroup: Not enought memory to join to group\n")); |
| return ERR_MEM; |
| } |
| } |
| /* proceed to next network interface */ |
| netif = netif->next; |
| } |
| |
| return err; |
| } |
| |
| /** |
| * Leave a group on one network interface. |
| * |
| * @param ifaddr ip address of the network interface which should leave a group |
| * @param groupaddr the ip address of the group which to leave |
| * @return ERR_OK if group was left on the netif(s), an err_t otherwise |
| */ |
| err_t |
| igmp_leavegroup(ip_addr_t *ifaddr, ip_addr_t *groupaddr) |
| { |
| err_t err = ERR_VAL; /* no matching interface */ |
| struct igmp_group *group; |
| struct netif *netif; |
| |
| /* make sure it is multicast address */ |
| LWIP_ERROR("igmp_leavegroup: attempt to leave non-multicast address", ip_addr_ismulticast(groupaddr), return ERR_VAL;); |
| LWIP_ERROR("igmp_leavegroup: attempt to leave allsystems address", (!ip_addr_cmp(groupaddr, &allsystems)), return ERR_VAL;); |
| |
| /* loop through netif's */ |
| netif = netif_list; |
| while (netif != NULL) { |
| /* Should we leave this interface ? */ |
| if ((netif->flags & NETIF_FLAG_IGMP) && ((ip_addr_isany(ifaddr) || ip_addr_cmp(&(netif->ip_addr), ifaddr)))) { |
| /* find group */ |
| group = igmp_lookfor_group(netif, groupaddr); |
| |
| if (group != NULL) { |
| /* Indicate success when there is a match for the first time */ |
| if (err == ERR_VAL) { |
| err = ERR_OK; |
| } |
| |
| /* Only send a leave if the flag is set according to the state diagram */ |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup: Leaving group: ")); |
| ip_addr_debug_print(IGMP_DEBUG, groupaddr); |
| LWIP_DEBUGF(IGMP_DEBUG, ("\n")); |
| |
| /* If there is no other use of the group */ |
| if (group->use <= 1) { |
| /* If we are the last reporter for this group */ |
| if (group->last_reporter_flag) { |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup: sending leaving group\n")); |
| IGMP_STATS_INC(igmp.tx_leave); |
| igmp_send(group, IGMP_LEAVE_GROUP); |
| } |
| |
| /* Disable the group at the MAC level */ |
| if (netif->igmp_mac_filter != NULL) { |
| err_t macFilterErr; |
| |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup: igmp_mac_filter(DEL ")); |
| ip_addr_debug_print(IGMP_DEBUG, groupaddr); |
| LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", netif)); |
| |
| macFilterErr = netif->igmp_mac_filter(netif, groupaddr, IGMP_DEL_MAC_FILTER); |
| |
| /* If there was an error, don't overwrite it */ |
| if (macFilterErr != ERR_OK) { |
| err = macFilterErr; |
| } |
| } |
| |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup: remove group: ")); |
| ip_addr_debug_print(IGMP_DEBUG, groupaddr); |
| LWIP_DEBUGF(IGMP_DEBUG, ("\n")); |
| |
| /* Free the group */ |
| igmp_remove_group(group); |
| } else { |
| /* Decrement group use */ |
| group->use--; |
| } |
| } else { |
| /* It's not a fatal error on "leavegroup" */ |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup: not member of group\n")); |
| } |
| } |
| /* proceed to next network interface */ |
| netif = netif->next; |
| } |
| |
| return err; |
| } |
| |
| /** |
| * The igmp timer function (both for NO_SYS=1 and =0) |
| * Should be called every IGMP_TMR_INTERVAL milliseconds (100 ms is default). |
| */ |
| void |
| igmp_tmr(void) |
| { |
| struct igmp_group *group = igmp_group_list; |
| |
| while (group != NULL) { |
| if (group->timer > 0) { |
| group->timer--; |
| if (group->timer == 0) { |
| igmp_timeout(group); |
| } |
| } |
| group = group->next; |
| } |
| } |
| |
| /** |
| * Called if a timeout for one group is reached. |
| * Sends a report for this group. |
| * |
| * @param group an igmp_group for which a timeout is reached |
| */ |
| static void |
| igmp_timeout(struct igmp_group *group) |
| { |
| /* If the state is IGMP_GROUP_DELAYING_MEMBER then we send a report for this group */ |
| if (group->group_state == IGMP_GROUP_DELAYING_MEMBER) { |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_timeout: report membership for group with address ")); |
| ip_addr_debug_print(IGMP_DEBUG, &(group->group_address)); |
| LWIP_DEBUGF(IGMP_DEBUG, (" on if %p\n", group->netif)); |
| |
| IGMP_STATS_INC(igmp.tx_report); |
| igmp_send(group, IGMP_V2_MEMB_REPORT); |
| } |
| } |
| |
| /** |
| * Start a timer for an igmp group |
| * |
| * @param group the igmp_group for which to start a timer |
| * @param max_time the time in multiples of IGMP_TMR_INTERVAL (decrease with |
| * every call to igmp_tmr()) |
| */ |
| static void |
| igmp_start_timer(struct igmp_group *group, u8_t max_time) |
| { |
| /* ensure the input value is > 0 */ |
| if (max_time == 0) { |
| max_time = 1; |
| } |
| #ifdef LWIP_RAND |
| /* ensure the random value is > 0 */ |
| group->timer = (LWIP_RAND() % (max_time - 1)) + 1; |
| #endif /* LWIP_RAND */ |
| } |
| |
| /** |
| * Delaying membership report for a group if necessary |
| * |
| * @param group the igmp_group for which "delaying" membership report |
| * @param maxresp query delay |
| */ |
| static void |
| igmp_delaying_member(struct igmp_group *group, u8_t maxresp) |
| { |
| if ((group->group_state == IGMP_GROUP_IDLE_MEMBER) || |
| ((group->group_state == IGMP_GROUP_DELAYING_MEMBER) && |
| ((group->timer == 0) || (maxresp < group->timer)))) { |
| igmp_start_timer(group, maxresp); |
| group->group_state = IGMP_GROUP_DELAYING_MEMBER; |
| } |
| } |
| |
| |
| /** |
| * Sends an IP packet on a network interface. This function constructs the IP header |
| * and calculates the IP header checksum. If the source IP address is NULL, |
| * the IP address of the outgoing network interface is filled in as source address. |
| * |
| * @param p the packet to send (p->payload points to the data, e.g. next |
| protocol header; if dest == IP_HDRINCL, p already includes an IP |
| header and p->payload points to that IP header) |
| * @param src the source IP address to send from (if src == IP_ADDR_ANY, the |
| * IP address of the netif used to send is used as source address) |
| * @param dest the destination IP address to send the packet to |
| * @param ttl the TTL value to be set in the IP header |
| * @param proto the PROTOCOL to be set in the IP header |
| * @param netif the netif on which to send this packet |
| * @return ERR_OK if the packet was sent OK |
| * ERR_BUF if p doesn't have enough space for IP/LINK headers |
| * returns errors returned by netif->output |
| */ |
| static err_t |
| igmp_ip_output_if(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest, struct netif *netif) |
| { |
| /* This is the "router alert" option */ |
| u16_t ra[2]; |
| ra[0] = PP_HTONS(ROUTER_ALERT); |
| ra[1] = 0x0000; /* Router shall examine packet */ |
| IGMP_STATS_INC(igmp.xmit); |
| return ip_output_if_opt(p, src, dest, IGMP_TTL, 0, IP_PROTO_IGMP, netif, ra, ROUTER_ALERTLEN); |
| } |
| |
| /** |
| * Send an igmp packet to a specific group. |
| * |
| * @param group the group to which to send the packet |
| * @param type the type of igmp packet to send |
| */ |
| static void |
| igmp_send(struct igmp_group *group, u8_t type) |
| { |
| struct pbuf* p = NULL; |
| struct igmp_msg* igmp = NULL; |
| ip_addr_t src = *IP_ADDR_ANY; |
| ip_addr_t* dest = NULL; |
| |
| /* IP header + "router alert" option + IGMP header */ |
| p = pbuf_alloc(PBUF_TRANSPORT, IGMP_MINLEN, PBUF_RAM); |
| |
| if (p) { |
| igmp = (struct igmp_msg *)p->payload; |
| LWIP_ASSERT("igmp_send: check that first pbuf can hold struct igmp_msg", |
| (p->len >= sizeof(struct igmp_msg))); |
| ip_addr_copy(src, group->netif->ip_addr); |
| |
| if (type == IGMP_V2_MEMB_REPORT) { |
| dest = &(group->group_address); |
| ip_addr_copy(igmp->igmp_group_address, group->group_address); |
| group->last_reporter_flag = 1; /* Remember we were the last to report */ |
| } else { |
| if (type == IGMP_LEAVE_GROUP) { |
| dest = &allrouters; |
| ip_addr_copy(igmp->igmp_group_address, group->group_address); |
| } |
| } |
| |
| if ((type == IGMP_V2_MEMB_REPORT) || (type == IGMP_LEAVE_GROUP)) { |
| igmp->igmp_msgtype = type; |
| igmp->igmp_maxresp = 0; |
| igmp->igmp_checksum = 0; |
| igmp->igmp_checksum = inet_chksum(igmp, IGMP_MINLEN); |
| |
| igmp_ip_output_if(p, &src, dest, group->netif); |
| } |
| |
| pbuf_free(p); |
| } else { |
| LWIP_DEBUGF(IGMP_DEBUG, ("igmp_send: not enough memory for igmp_send\n")); |
| IGMP_STATS_INC(igmp.memerr); |
| } |
| } |
| |
| #endif /* LWIP_IGMP */ |