/**************************************************************************** * net/igmp/igmp_input.c * * Copyright (C) 2010, 2014, 2020 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * The NuttX implementation of IGMP was inspired by the IGMP add-on for the * lwIP TCP/IP stack by Steve Reynolds: * * 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. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include "devif/devif.h" #include "igmp/igmp.h" #include "utils/utils.h" #ifdef CONFIG_NET_IGMP /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: igmp_input * * Description: * An IGMP packet has been received. * * ________________ * | | * | | * | | * | | * +--------->| Non-Member |<---------+ * | | | | * | | | | * | | | | * | |________________| | * | | | * | leave group | join group | leave group * | (stop timer, |(send report, | (send leave * | send leave if | set flag, | if flag set) * | flag set) | start timer) | * ________|________ | ________|________ * | |<---------+ | | * | | | | * | |<-------------------| | * | | query received | | * | Delaying Member | (start timer) | Idle Member | * +---->| |------------------->| | * | | | report received | | * | | | (stop timer, | | * | | | clear flag) | | * | |_________________|------------------->|_________________| * | query received | timer expired * | (reset timer if | (send report, * | Max Resp Time | set flag) * | < current timer) | * +-------------------+ * * NOTE: The network must be locked. * ****************************************************************************/ void igmp_input(struct net_driver_s *dev) { FAR struct igmp_iphdr_s *ipv4 = IPBUF(0); FAR struct igmp_hdr_s *igmp; FAR struct igmp_group_s *group; in_addr_t destipaddr; in_addr_t grpaddr; uint16_t iphdrlen; unsigned int ticks; ninfo("IGMP message: %04x%04x\n", ipv4->destipaddr[1], ipv4->destipaddr[0]); /* Get the IP header length (accounting for possible options). */ iphdrlen = (ipv4->vhl & IPv4_HLMASK) << 2; /* The IGMP header immediately follows the IP header */ igmp = IPBUF(iphdrlen); /* Verify the message length */ if (dev->d_len < NET_LL_HDRLEN(dev) + (iphdrlen + IGMP_HDRLEN)) { IGMP_STATINCR(g_netstats.igmp.length_errors); nwarn("WARNING: Length error\n"); goto drop; } #ifdef CONFIG_NET_IGMP_CHECKSUMS /* Calculate and check the IGMP checksum */ if (net_chksum((FAR uint16_t *)igmp, IGMP_HDRLEN) != 0) { IGMP_STATINCR(g_netstats.igmp.chksum_errors); nwarn("WARNING: Checksum error\n"); goto drop; } #endif /* Find the group (or create a new one) using the incoming IP address. */ destipaddr = net_ip4addr_conv32(ipv4->destipaddr); group = igmp_grpallocfind(dev, &destipaddr); if (group == NULL) { nerr("ERROR: Failed to find/allocate group: %08" PRIx32 "\n", (uint32_t)destipaddr); goto drop; } /* Now handle the message based on the IGMP message type */ switch (igmp->type) { case IGMP_MEMBERSHIP_QUERY: /* RFC 2236, 2.2. ax Response Time * "The Max Response Time field is meaningful only in Membership * Query messages, and specifies the maximum allowed time before * sending a responding report in units of 1/10 second. In all * other messages, it is set to zero by the sender and ignored by * receivers. */ /* Check if the query was sent to all systems */ if (net_ipv4addr_cmp(destipaddr, g_ipv4_allsystems)) { /* Yes... Now check the if this is a general or a group * specific query. * * RFC 2236, 2.1. Type * There are two sub-types of Membership Query messages: * - General Query, used to learn which groups have members on an * attached network. * - Group-Specific Query, used to learn if a particular group * has any members on an attached network. * * RFC 2236, 2.4. Group Address * "In a Membership Query message, the group address field is * set to zero when sending a General Query, and set to the * group address being queried when sending a Group-Specific * Query." */ if (net_ipv4addr_cmp(igmp->grpaddr, INADDR_ANY) != 0) { FAR struct igmp_group_s *member; /* This is the general query */ ninfo("General multicast query\n"); if (igmp->maxresp == 0) { IGMP_STATINCR(g_netstats.igmp.v1_received); igmp->maxresp = 10; nwarn("WARNING: V1 not implemented\n"); } IGMP_STATINCR(g_netstats.igmp.query_received); member = (FAR struct igmp_group_s *)dev->d_igmp_grplist.head; for (; member; member = member->next) { /* Skip over the all systems group entry */ if (!net_ipv4addr_cmp(member->grpaddr, g_ipv4_allsystems)) { ticks = net_dsec2tick((int)igmp->maxresp); if (IS_IDLEMEMBER(member->flags) || igmp_cmptimer(member, ticks)) { igmp_startticks(member, ticks); CLR_IDLEMEMBER(member->flags); } } } } else /* if (net_ipv4addr_cmp(igmp->grpaddr, INADDR_ANY) == 0) */ { ninfo("Group-specific multicast query\n"); /* We first need to re-lookup the group since we used dest * last time. Use the incoming IPaddress! */ IGMP_STATINCR(g_netstats.igmp.ucast_query); grpaddr = net_ip4addr_conv32(igmp->grpaddr); group = igmp_grpallocfind(dev, &grpaddr); if (group != NULL) { ticks = net_dsec2tick((int)igmp->maxresp); if (IS_IDLEMEMBER(group->flags) || igmp_cmptimer(group, ticks)) { igmp_startticks(group, ticks); CLR_IDLEMEMBER(group->flags); } } } } /* Not sent to all systems -- Unicast query */ else if (net_ipv4addr_cmp(igmp->grpaddr, INADDR_ANY) == 0) { ninfo("Unicast query\n"); IGMP_STATINCR(g_netstats.igmp.ucast_query); ninfo("Query to a specific group with the group address as " "destination\n"); ticks = net_dsec2tick((int)igmp->maxresp); if (IS_IDLEMEMBER(group->flags) || igmp_cmptimer(group, ticks)) { igmp_startticks(group, ticks); CLR_IDLEMEMBER(group->flags); } } break; case IGMPv2_MEMBERSHIP_REPORT: { ninfo("Membership report\n"); IGMP_STATINCR(g_netstats.igmp.report_received); if (!IS_IDLEMEMBER(group->flags)) { /* This is on a specific group we have already looked up */ wd_cancel(&group->wdog); SET_IDLEMEMBER(group->flags); CLR_LASTREPORT(group->flags); } } break; default: { nwarn("WARNING: Unexpected msg %02x\n", igmp->type); } break; } drop: dev->d_len = 0; } #endif /* CONFIG_NET_IGMP */