From 0ed2fbb665e2b3a9dd941e9c74c005cf2f27d2c7 Mon Sep 17 00:00:00 2001 From: Emil Popov Date: Thu, 13 Apr 2023 14:05:27 -0400 Subject: [PATCH] Adds support for receiving Multicast Group addresses + IGMP ( IPv4 only ) Adds 2 function pointers to the network interface struct that handle adding and removing multicast MAC addresses. Updates IGMP to use function pointers through the network interface. Makes the Add/Remove Multicast functions private to NetworkInterface.c They are now used through pointers in the NetworkInterface_t struct. Improves the SAME70 driver to handle adding/removing muticast MAC addresses --- source/FreeRTOS_DNS_Networking.c | 5 + source/FreeRTOS_IGMP.c | 810 ++++++++++++++++++ source/FreeRTOS_IP.c | 35 + source/FreeRTOS_IP_Timers.c | 41 + source/FreeRTOS_Sockets.c | 46 +- source/FreeRTOS_UDP_IPv4.c | 44 + source/include/FreeRTOSIPConfigDefaults.h | 24 + source/include/FreeRTOS_IGMP.h | 127 +++ source/include/FreeRTOS_IP.h | 3 + source/include/FreeRTOS_IP_Private.h | 39 +- source/include/FreeRTOS_IP_Timers.h | 4 + source/include/FreeRTOS_Routing.h | 12 + source/include/FreeRTOS_Sockets.h | 9 +- .../DriverSAM/NetworkInterface.c | 133 ++- 14 files changed, 1300 insertions(+), 32 deletions(-) create mode 100644 source/FreeRTOS_IGMP.c create mode 100644 source/include/FreeRTOS_IGMP.h diff --git a/source/FreeRTOS_DNS_Networking.c b/source/FreeRTOS_DNS_Networking.c index 9d41bcd877..ecf5c2a19c 100644 --- a/source/FreeRTOS_DNS_Networking.c +++ b/source/FreeRTOS_DNS_Networking.c @@ -84,6 +84,11 @@ * going to be '0' i.e. success. Thus, return value is discarded */ ( void ) FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_SNDTIMEO, &( uxWriteTimeOut_ticks ), sizeof( TickType_t ) ); ( void ) FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_RCVTIMEO, &( uxReadTimeOut_ticks ), sizeof( TickType_t ) ); + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + /* Since this socket may be used for LLMNR or mDNS, set the multicast TTL to 1. */ + uint8_t ucMulticastTTL = 1; + ( void ) FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_IP_MULTICAST_TTL, &( ucMulticastTTL ), sizeof( uint8_t ) ); + #endif } return xSocket; diff --git a/source/FreeRTOS_IGMP.c b/source/FreeRTOS_IGMP.c new file mode 100644 index 0000000000..80e8e09e61 --- /dev/null +++ b/source/FreeRTOS_IGMP.c @@ -0,0 +1,810 @@ +/* + * FreeRTOS+TCP V2.4.0 + * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file FreeRTOS_IGMP.c + * @brief Implements the optional IGMP functionality of the FreeRTOS+TCP network stack. + */ + +/* Standard includes. */ +#include +#include +#include + +/* FreeRTOS includes. */ +#include "FreeRTOS.h" +#include "task.h" +#include "semphr.h" + +/* FreeRTOS+TCP includes. */ +#include "FreeRTOS_IP.h" +#include "FreeRTOS_Sockets.h" +#include "FreeRTOS_IP_Private.h" +#include "FreeRTOS_UDP_IP.h" +#include "FreeRTOS_DHCP.h" +#include "FreeRTOS_ARP.h" +#include "FreeRTOS_IP_Timers.h" +#include "FreeRTOS_IGMP.h" + +/* Exclude the entire file if multicast support is not enabled. */ +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + + + +/*-----------------------------------------------------------*/ + +/** @brief IGMP timer. Used for sending asynchronous IGMP reports. */ + static List_t xIGMP_ScheduleList; + + uint16_t usIPv4_IGMP_Identification = 0; + +/*-----------------------------------------------------------*/ + + static portBASE_TYPE xSendIGMP( uint32_t uiBlockTime, + uint8_t ucIgmpMsgType, + uint8_t ucIgmpRespTime, + uint32_t uiMulticastGroup_NBO, + NetworkEndPoint_t * pxEndPoint ); + void prvScheduleIgmpReports( uint32_t uiGroupAddress, + uint8_t ucMaxRespTime ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Set the multicast-specific socket options for the given socket. + * This is an internal function that should only get called from + * FreeRTOS_setsockopt() in an attempt to keep the FreeRTOS_setsockopt() + * function clean. + * + * @param[in] xSocket: The socket for which the options are to be set. + * @param[in] lLevel: Not used. Parameter is used to maintain the Berkeley sockets + * standard. + * @param[in] lOptionName: The name of the option to be set. + * @param[in] pvOptionValue: The value of the option to be set. + * @param[in] uxOptionLength: Not used. Parameter is used to maintain the Berkeley + * sockets standard. + * + * @return If the option can be set with the given value, then 0 is returned. Else, + * an error code is returned. + */ + BaseType_t xSetMulticastSocketOption( Socket_t xSocket, + int32_t lLevel, + int32_t lOptionName, + const void * pvOptionValue, + size_t uxOptionLength ) + { + BaseType_t xReturn = -pdFREERTOS_ERRNO_EINVAL; + FreeRTOS_Socket_t * pxSocket; + + pxSocket = ( FreeRTOS_Socket_t * ) xSocket; + + /* The function prototype is designed to maintain the expected Berkeley + * sockets standard, but this implementation does not use all the parameters. */ + ( void ) lLevel; + + if( ( pxSocket == NULL ) || ( pxSocket == FREERTOS_INVALID_SOCKET ) || ( pxSocket->ucProtocol != ipPROTOCOL_UDP ) ) + { + xReturn = -pdFREERTOS_ERRNO_EINVAL; + return xReturn; + } + + switch( lOptionName ) + { + case FREERTOS_SO_IP_MULTICAST_TTL: + + if( ( pxSocket->ucProtocol != ( uint8_t ) FREERTOS_IPPROTO_UDP ) || ( uxOptionLength != sizeof( uint8_t ) ) ) + { + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + + /* Override the default TTL value with this one. */ + pxSocket->u.xUDP.ucMulticastTTL = *( ( uint8_t * ) pvOptionValue ); + + xReturn = 0; + break; + + case FREERTOS_SO_IP_ADD_MEMBERSHIP: + { + if( ( pxSocket->ucProtocol != ( uint8_t ) FREERTOS_IPPROTO_UDP ) || ( uxOptionLength != sizeof( struct freertos_ip_mreq ) ) ) + { + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + + struct freertos_ip_mreq * pMReq = ( struct freertos_ip_mreq * ) pvOptionValue; + + if( pdFALSE == xIsIPv4Multicast( pMReq->imr_multiaddr.sin_address.ulIP_IPv4 ) ) + { + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + + /* Allocate some RAM to remember the multicast group that is being registered */ + MCastGroupDesc_t * pxMCG = ( MCastGroupDesc_t * ) pvPortMalloc( sizeof( MCastGroupDesc_t ) ); + IGMPReportDesc_t * pxIRD = ( IGMPReportDesc_t * ) pvPortMalloc( sizeof( IGMPReportDesc_t ) ); + + if( NULL == pxMCG ) + { + xReturn = -pdFREERTOS_ERRNO_ENOMEM; + break; + } + + if( NULL == pxIRD ) + { + xReturn = -pdFREERTOS_ERRNO_ENOMEM; + vPortFree( pxMCG ); + pxMCG = NULL; + break; + } + + /* Store this whole option in case we are later called to drop this membership. + * This data also needs to be stored so that incoming packets can be filtered properly. + * This can be done more efficiently, but storing the whole struct will be more portable + * in the multi-interface, dual-stack case.*/ + ( void ) memcpy( ( void * ) &pxMCG->mreq, pvOptionValue, uxOptionLength ); + listSET_LIST_ITEM_OWNER( &( pxMCG->xListItem ), ( void * ) pxMCG ); + pxMCG->pxSocket = pxSocket; + + /* Init the IGMP report description. It needs to hold the same membership information + * and it will eventually be added to the IGMP list. */ + ( void ) memcpy( ( void * ) &pxIRD->mreq, pvOptionValue, uxOptionLength ); + listSET_LIST_ITEM_OWNER( &( pxIRD->xListItem ), ( void * ) pxIRD ); + pxIRD->xNumSockets = 0; + pxIRD->ucCountDown = 0; + /* Quick and dirty assignment of end-point. This will probably have to be re-designed and re-done. */ + pxIRD->pxEndPoint = ( pxSocket->bits.bIsIPv6 ) ? FreeRTOS_FirstEndPoint_IPv6( FreeRTOS_FirstNetworkInterface() ) : FreeRTOS_FirstEndPoint( FreeRTOS_FirstNetworkInterface() ); + + + /* Pass the IGMP report descriptor inside the multicast group descriptor, + * so we can easily pass it to the IP task in one message. */ + pxMCG->pxIGMPReportDesc = pxIRD; + + IPStackEvent_t xSockOptsEvent = { eSocketOptAddMembership, ( void * ) pxMCG }; + + if( xSendEventStructToIPTask( &( xSockOptsEvent ), portMAX_DELAY ) != pdPASS ) + { + vPortFree( pxMCG ); + vPortFree( pxIRD ); + xReturn = -1; + } + else + { + xReturn = 0; + } + } + break; + + case FREERTOS_SO_IP_DROP_MEMBERSHIP: + { + if( ( pxSocket->ucProtocol != ( uint8_t ) FREERTOS_IPPROTO_UDP ) || ( uxOptionLength != sizeof( struct freertos_ip_mreq ) ) ) + { + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + + struct freertos_ip_mreq * pMReq = ( struct freertos_ip_mreq * ) pvOptionValue; + + if( pdFALSE == xIsIPv4Multicast( pMReq->imr_multiaddr.sin_address.ulIP_IPv4 ) ) + { + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + + /* Allocate some RAM to remember the multicast group that is being registered */ + MCastGroupDesc_t * pxMCG = ( MCastGroupDesc_t * ) pvPortMalloc( sizeof( MCastGroupDesc_t ) ); + + if( NULL == pxMCG ) + { + xReturn = -pdFREERTOS_ERRNO_ENOMEM; + break; + } + + /* Store this whole option in case we are later called to drop this membership. + * This data also needs to be stored so that incoming packets can be filtered properly. + * This can be done more efficiently, but storing the whole struct will be more portable + * in the multi-interface, dual-stack case.*/ + ( void ) memcpy( ( void * ) &pxMCG->mreq, pvOptionValue, uxOptionLength ); + listSET_LIST_ITEM_OWNER( &( pxMCG->xListItem ), ( void * ) pxMCG ); + pxMCG->pxSocket = pxSocket; + + /* When dropping memberships, we don't need an IGMP report descriptor. */ + pxMCG->pxIGMPReportDesc = NULL; + + IPStackEvent_t xSockOptsEvent = { eSocketOptDropMembership, ( void * ) pxMCG }; + + if( xSendEventStructToIPTask( &( xSockOptsEvent ), portMAX_DELAY ) != pdPASS ) + { + vPortFree( pxMCG ); + xReturn = -1; + } + else + { + xReturn = 0; + } + } + break; + + default: + /* This function doesn't handle any other options. */ + xReturn = -pdFREERTOS_ERRNO_ENOPROTOOPT; + break; + } /* switch */ + + return xReturn; + } + + + void vIGMP_Init( void ) + { + vListInitialise( &xIGMP_ScheduleList ); + #if ( ipconfigIGMP_SNOOPING != 0 ) + extern void vIgmpSnooping_Initialize( void ); + ( void ) vIgmpSnooping_Initialize(); + #endif + + MACAddress_t IGMP_MacAddress; + vSetMultiCastIPv4MacAddress( ipIGMP_IP_ADDR, IGMP_MacAddress.ucBytes ); + + for( NetworkInterface_t * pxInterface = FreeRTOS_FirstNetworkInterface(); pxInterface != NULL; pxInterface = FreeRTOS_NextNetworkInterface( pxInterface ) ) + { + pxInterface->pfAddMulticastMAC( IGMP_MacAddress.ucBytes ); + } + + IGMPReportDesc_t * pxIRD; + #if ( ipconfigUSE_LLMNR != 0 ) + if( NULL != ( pxIRD = ( IGMPReportDesc_t * ) pvPortMalloc( sizeof( IGMPReportDesc_t ) ) ) ) + { + listSET_LIST_ITEM_OWNER( &( pxIRD->xListItem ), ( void * ) pxIRD ); + /* Quick and dirty assignment of end-point. This will probably have to be re-designed and re-done. */ + pxIRD->pxEndPoint = FreeRTOS_FirstEndPoint( FreeRTOS_FirstNetworkInterface() ); + pxIRD->mreq.imr_interface.sin_family = FREERTOS_AF_INET; + pxIRD->mreq.imr_interface.sin_len = sizeof( struct freertos_sockaddr ); + pxIRD->mreq.imr_interface.sin_address.ulIP_IPv4 = FreeRTOS_htonl( 0x00000000U ); + pxIRD->mreq.imr_multiaddr.sin_family = FREERTOS_AF_INET; + pxIRD->mreq.imr_multiaddr.sin_len = sizeof( struct freertos_sockaddr ); + pxIRD->mreq.imr_multiaddr.sin_address.ulIP_IPv4 = ipLLMNR_IP_ADDR; + BaseType_t bReportItemConsumed = xAddIGMPReportToList( pxIRD ); + + if( pdTRUE != bReportItemConsumed ) + { + /* This should not happen, but if it does, free the memory that was used */ + vPortFree( pxIRD ); + pxIRD = NULL; + } + } + #endif /* ipconfigUSE_LLMNR */ + + vIGMPTimerReload( pdMS_TO_TICKS( ipIGMP_TIMER_PERIOD_MS ) ); + } + +/** + * @brief Process an IGMP packet. + * + * @param[in,out] pxIGMPPacket: The IP packet that contains the IGMP message. + * + * @return eReleaseBuffer This function usually returns eReleaseBuffer as IGMP reports are + * scheduled for later. If however the user has implemented IGMP Snooping, the return is + * controlled by the eApplicationIgmpFrameReceivedHook function. That function might return + * eFrameConsumed if it decided to forward the frame somewhere. + */ + eFrameProcessingResult_t eProcessIGMPPacket( NetworkBufferDescriptor_t * const pxNetworkBuffer ) + { + eFrameProcessingResult_t eReturn = eReleaseBuffer; + + if( pxNetworkBuffer->xDataLength < sizeof( IGMPPacket_t ) ) + { + return eReturn; + } + + IGMPPacket_t * pxIGMPPacket = ( IGMPPacket_t * ) pxNetworkBuffer->pucEthernetBuffer; + + if( ipIGMP_MEMBERSHIP_QUERY == pxIGMPPacket->xIGMPHeader.ucTypeOfMessage ) + { + extern uint32_t uiNumIGMP_Queries; + uiNumIGMP_Queries++; + + prvScheduleIgmpReports( pxIGMPPacket->xIGMPHeader.uiGroupAddress, pxIGMPPacket->xIGMPHeader.ucMaxResponseTime ); + } + + #if ( ipconfigIGMP_SNOOPING != 0 ) + extern eFrameProcessingResult_t eApplicationIgmpFrameReceivedHook( NetworkBufferDescriptor_t * pxNetworkBuffer ); + eReturn = eApplicationIgmpFrameReceivedHook( pxNetworkBuffer ); + #endif /* ( ipconfigIGMP_SNOOPING != 0 ) */ + + return eReturn; + } + + void prvScheduleIgmpReports( uint32_t uiGroupAddress, + uint8_t ucMaxRespTime ) + { + if( !xIGMP_ScheduleList.uxNumberOfItems ) + { + return; + } + + /* Schedule reports at random times withing the required response time. + * Instead of calling xApplicationGetRandomNumber() multiple times until we get + * an acceptable value lower than ucMaxRespTime, + * let's calculate a mask that guarantees we can get a random number and mask it + * out such that it is less than ucMaxRespTime. */ + + /* Prepare a fake random number in case the random generator fails. */ + uint32_t uiNonRandomCounter = 1; + + /* Sanity enforcement. */ + ucMaxRespTime = max( 2, ucMaxRespTime ); + ucMaxRespTime--; + /* Now we can safely search for random numbers between 1 and ucMaxRespTime which is 1 or more. */ + + /* Find the next power of 2 that is larger than ucMaxRespTime. The algorithm for 32 bit values is described below: + * n--; // 1101 1101 --> 1101 1100 + * n |= n >> 1; // 1101 1100 | 0110 1110 = 1111 1110 + * n |= n >> 2; // 1111 1110 | 0011 1111 = 1111 1111 + * n |= n >> 4; // ... + * n |= n >> 8; + * n |= n >> 16; // 1111 1111 | 1111 1111 = 1111 1111 + * n++; // 1111 1111 --> 1 0000 0000 + * In our case, we don't need the ++ at the end as we need a mask-type value. Since we are skipping the ++ though, + * we have to check for zeros again. */ + uint32_t RandMask = ucMaxRespTime; + RandMask |= RandMask >> 1; + RandMask |= RandMask >> 2; + RandMask |= RandMask >> 4; + + if( 0 == RandMask ) + { + RandMask = 1; + } + + /* Go through the list of IGMP reports */ + + /* and schedule the reports. Note, the IGMP event is set at 100ms + * which corresponds to the increment used in ucMaxRespTime. + * pxIRD->ucCountDown holds a count in increments of the IGMP event time, so 12 = 1200ms = 1.2s */ + const ListItem_t * pxIterator; + const ListItem_t * xEnd = listGET_END_MARKER( &xIGMP_ScheduleList ); + IGMPReportDesc_t * pxIRD; + + for( pxIterator = ( const ListItem_t * ) listGET_NEXT( xEnd ); + pxIterator != ( const ListItem_t * ) xEnd; + pxIterator = ( const ListItem_t * ) listGET_NEXT( pxIterator ) ) + { + pxIRD = ( IGMPReportDesc_t * ) listGET_LIST_ITEM_OWNER( pxIterator ); + + /* Continue parsing if we are dealing with a general query, or if we are servicing a group-specific + * query and this report matches the group-specific query's destination address*/ + if( ( uiGroupAddress == 0U ) || ( uiGroupAddress == pxIRD->mreq.imr_multiaddr.sin_address.ulIP_IPv4 ) ) + { + /* This report needs to be scheduled for sending. Remember that it may already be scheduled. + * pxIRD->ucCountDown of zero means the report is not scheduled to be sent. If a report is scheduled, and it's + * scheduled time is before ucMaxRespTime, there is nothing to be done. If a + * report is scheduled past ucMaxRespTime, or not scheduled at all, we need + * to schedule it for a random time between 0 and ucMaxRespTime. */ + if( ( pxIRD->ucCountDown == 0 ) || ( pxIRD->ucCountDown >= ucMaxRespTime ) ) + { + uint32_t uiRandom; + + if( xApplicationGetRandomNumber( &( uiRandom ) ) == pdFALSE ) + { + pxIRD->ucCountDown = uiNonRandomCounter++; + + if( uiNonRandomCounter > ucMaxRespTime ) + { + uiNonRandomCounter = 1; + } + } + else + { + uiRandom &= RandMask; + + if( 0 == uiRandom ) + { + uiRandom = 1; + } + + while( uiRandom > ucMaxRespTime ) + { + uiRandom -= ucMaxRespTime; + } + + pxIRD->ucCountDown = ( uint8_t ) uiRandom; + } + } + } + } /* for(;;) iterating over xIGMP_ScheduleList */ + } + + void vHandleIGMP_Event( void ) + { + extern uint32_t uiNumIGMP_Events; + + uiNumIGMP_Events++; + + /* Go through the list of IGMP reports and send anything that needs to be sent. */ + const ListItem_t * pxIterator; + const ListItem_t * xEnd = listGET_END_MARKER( &xIGMP_ScheduleList ); + IGMPReportDesc_t * pxIRD; + + for( pxIterator = ( const ListItem_t * ) listGET_NEXT( xEnd ); pxIterator != ( const ListItem_t * ) xEnd; pxIterator = ( const ListItem_t * ) listGET_NEXT( pxIterator ) ) + { + pxIRD = ( IGMPReportDesc_t * ) listGET_LIST_ITEM_OWNER( pxIterator ); + + /* Only decrement down to one. Decrementing to zero is handled later. */ + if( pxIRD->ucCountDown > 1 ) + { + pxIRD->ucCountDown--; + } + + if( pxIRD->pxEndPoint->bits.bIPv6 ) + { + /* ToDo: handle IPv6 multicast groups through ICMPv6 messages */ + continue; + } + + /* Hold off on sending reports until our IP is non-zero. This allows a bit of a wait during power up + * when the IP can be zero and also allows us to add reports to the list with a countdown of 1 for fast initial delay. */ + if( ( pxIRD->ucCountDown == 1 ) && ( pxIRD->pxEndPoint->ipv4_settings.ulIPAddress != 0 ) ) + { + pxIRD->ucCountDown = 0; + xSendIGMP( 0, ipIGMP_MEMBERSHIP_REPORT_V2, 0, pxIRD->mreq.imr_multiaddr.sin_address.ulIP_IPv4, pxIRD->pxEndPoint ); + } + } + + #if ( ipconfigIGMP_SNOOPING != 0 ) + extern void vApplicationIgmpEventHook( void ); + ( void ) vApplicationIgmpEventHook(); + #endif + } + +/** + * @brief Create a IGMP event. + * + * @return pdPASS or pdFAIL, depending on whether xSendEventStructToIPTask() + * succeeded. + */ + BaseType_t xSendIGMPEvent( void ) + { + IPStackEvent_t xEventMessage; + const TickType_t uxDontBlock = 0U; + uintptr_t uxOption = 0U; + + xEventMessage.eEventType = eIGMPEvent; + xEventMessage.pvData = ( void * ) uxOption; + + return xSendEventStructToIPTask( &xEventMessage, uxDontBlock ); + } + + +/** + * @brief Removes an IGMP report from the list of reports. + * + * @param[in] pMCastGroup: The multicast group descriptor the specifies the group and interface. + */ + void vRemoveIGMPReportFromList( struct freertos_ip_mreq * pMCastGroup ) + { + configASSERT( pMCastGroup != NULL ); + + const ListItem_t * pxIterator; + const ListItem_t * xEnd = listGET_END_MARKER( &xIGMP_ScheduleList ); + IGMPReportDesc_t * pxIRD; + + for( pxIterator = ( const ListItem_t * ) listGET_NEXT( xEnd ); + pxIterator != ( const ListItem_t * ) xEnd; + pxIterator = ( const ListItem_t * ) listGET_NEXT( pxIterator ) ) + { + pxIRD = ( IGMPReportDesc_t * ) listGET_LIST_ITEM_OWNER( pxIterator ); + + if( pxIRD->mreq.imr_multiaddr.sin_address.ulIP_IPv4 == pMCastGroup->imr_multiaddr.sin_address.ulIP_IPv4 ) + { + /* Found a match. */ + if( pxIRD->xNumSockets > 0 ) + { + pxIRD->xNumSockets--; + } + + if( 0 == pxIRD->xNumSockets ) + { + ( void ) uxListRemove( &pxIRD->xListItem ); + vPortFree( pxIRD ); + } + + break; + } + } + } + +/** + * @brief Adds an IGMP report from the list of reports. + * + * @param[in] pNewEntry: The multicast group descriptor to search for. + */ + BaseType_t xAddIGMPReportToList( IGMPReportDesc_t * pNewEntry ) + { + configASSERT( pNewEntry != NULL ); + + const ListItem_t * pxIterator; + const ListItem_t * xEnd = listGET_END_MARKER( &xIGMP_ScheduleList ); + IGMPReportDesc_t * pxIRD; + + for( pxIterator = ( const ListItem_t * ) listGET_NEXT( xEnd ); + pxIterator != ( const ListItem_t * ) xEnd; + pxIterator = ( const ListItem_t * ) listGET_NEXT( pxIterator ) ) + { + pxIRD = ( IGMPReportDesc_t * ) listGET_LIST_ITEM_OWNER( pxIterator ); + + if( pxIRD->mreq.imr_multiaddr.sin_address.ulIP_IPv4 == pNewEntry->mreq.imr_multiaddr.sin_address.ulIP_IPv4 ) + { + /* Found a duplicate. All IGMP snooping switches already know that we are interested. + * Just keep track of how many sockets are interested in this multicast group. */ + pxIRD->xNumSockets++; + + /* Inform the caller that we did NOT consume the item they sent us and that + * they are allowed to free it if they so choose. */ + return pdFALSE; + } + } + + if( pxIterator == xEnd ) + { + /* Not found. */ + pNewEntry->xNumSockets = 1; + + /* Schedule an unsolicited report to quickly inform IGMP snooping switches that we want + * to receive this multicast group. During power up, our IP may be 0.0.0.0 and we don't + * want to send IGMP reports with an IP of 0.0.0.0. To get around this, the + * vHandleIGMP_Event code will keep ucCountDown at 1 until the IP becomes non-zero. */ + pNewEntry->ucCountDown = 1; + vListInsertEnd( &xIGMP_ScheduleList, &( pNewEntry->xListItem ) ); + + /* Inform the caller that we consumed the item they sent us, so they know + * not to free it. */ + return pdTRUE; + } + } + +/** + * @brief Adds or drops a multicast group to/from a socket. + * + * @param[in] pxMulticastGroup: The multicast group descriptor. Also holds the socket that this call is for. + * @param[in] bAction: eSocketOptAddMembership or eSocketOptDropMembership. + */ + void vModifyMulticastMembership( MCastGroupDesc_t * pxMulticastGroup, + uint8_t bAction ) + { + if( ( eSocketOptAddMembership != bAction ) && ( eSocketOptDropMembership != bAction ) ) + { + return; + } + + FreeRTOS_Socket_t * pxSocket = pxMulticastGroup->pxSocket; + uint8_t bFreeInputItem = pdTRUE; + uint8_t bFreeMatchedItem = pdFALSE; + NetworkInterface_t * pxNetIf = ( pxSocket->pxEndPoint != NULL && pxSocket->pxEndPoint->pxNetworkInterface != NULL ) ? pxSocket->pxEndPoint->pxNetworkInterface : NULL; + + /* Go through the list of registered groups and try to locate the group that + * we are being asked to add or remove. This check prevents adding duplicates.*/ + const ListItem_t * pxIterator; + const ListItem_t * xEnd = listGET_END_MARKER( &( pxSocket->u.xUDP.xMulticastGroupsList ) ); + MCastGroupDesc_t * pxMCG; + + for( pxIterator = ( const ListItem_t * ) listGET_NEXT( xEnd ); + pxIterator != ( const ListItem_t * ) xEnd; + pxIterator = ( const ListItem_t * ) listGET_NEXT( pxIterator ) ) + { + pxMCG = ( MCastGroupDesc_t * ) listGET_LIST_ITEM_OWNER( pxIterator ); + + if( pxMCG->mreq.imr_multiaddr.sin_address.ulIP_IPv4 == pxMulticastGroup->mreq.imr_multiaddr.sin_address.ulIP_IPv4 ) + { + /* Found a match. If we need to remove this address, go ahead. + * If we need to add it, it's already there, so just free the the descriptor to prevent memory leaks. */ + if( eSocketOptDropMembership == bAction ) + { + ( void ) uxListRemove( &pxMCG->xListItem ); + + /* Defer freeing this list item because when called from vSocketClose, this matching item + * is the same as our input parameter item, and we need the input parameter item further + * down when informing the network interface driver. */ + bFreeMatchedItem = pdTRUE; + + if( pxMulticastGroup == pxMCG ) + { + bFreeInputItem = pdFALSE; + } + } + + break; + } + } + + if( eSocketOptAddMembership == bAction ) + { + if( pxIterator == xEnd ) + { + /* We are adding an item and we couldn't find an identical one. Simply add it. */ + vListInsertEnd( &( pxSocket->u.xUDP.xMulticastGroupsList ), &( pxMulticastGroup->xListItem ) ); + /* Inform the network driver */ + uint8_t MCastDestMacBytes[ 6 ]; + vSetMultiCastIPv4MacAddress( pxMulticastGroup->mreq.imr_multiaddr.sin_address.ulIP_IPv4, MCastDestMacBytes ); + + if( pxNetIf ) + { + pxNetIf->pfAddMulticastMAC( MCastDestMacBytes ); + } + + bFreeInputItem = pdFALSE; + + /* Since we've added a multicast group to this socket, we need to prepare an IGMP report + * for when we receive an IGMP query. Keep in mind that such a report might already exist. + * If such an IGMP report is already present in the list, we will increment it's socket + * count and free the report we have here. In either case, the MCastGroupDesc_t that we were + * passed, no longer needs to hold a reference to this IGMP report. */ + if( pxMulticastGroup->pxIGMPReportDesc ) + { + BaseType_t bReportItemConsumed = xAddIGMPReportToList( pxMulticastGroup->pxIGMPReportDesc ); + + if( pdTRUE != bReportItemConsumed ) + { + /* If adding to the list did not consume the item that we sent, that means a duplicate + * was found and its socket count was incremented instead of adding the item we sent. + * Free the item that was passed to us. */ + vPortFree( pxMulticastGroup->pxIGMPReportDesc ); + pxMulticastGroup->pxIGMPReportDesc = NULL; + } + } + } + else + { + /* Adding, but found duplicate. No need to inform the network driver. Simply free + * the IGMPReportDesc_t */ + if( pxMulticastGroup->pxIGMPReportDesc ) + { + vPortFree( pxMulticastGroup->pxIGMPReportDesc ); + pxMulticastGroup->pxIGMPReportDesc = NULL; + } + } + } + else + { + if( pxIterator == xEnd ) + { + /* Removing, but no match. No need to inform the network driver. */ + } + else + { + /* Removing and found a match. */ + /* Inform the network driver */ + uint8_t MCastDestMacBytes[ 6 ]; + vSetMultiCastIPv4MacAddress( pxMulticastGroup->mreq.imr_multiaddr.sin_address.ulIP_IPv4, MCastDestMacBytes ); + + if( pxNetIf ) + { + pxNetIf->pfRemoveMulticastMAC( MCastDestMacBytes ); + } + + /* Lastly, locate the IGMP report for this group. Decrement its socket count and + * if it becomes zero, remove it from the list and free it. */ + vRemoveIGMPReportFromList( &( pxMCG->mreq ) ); + } + } + + /* Free the message that was sent to us. */ + if( bFreeInputItem ) + { + vPortFree( pxMulticastGroup ); + } + + if( bFreeMatchedItem ) + { + vPortFree( pxMCG ); + } + } + + static portBASE_TYPE xSendIGMP( uint32_t uiBlockTime, + uint8_t ucIgmpMsgType, + uint8_t ucIgmpRespTime, + uint32_t uiMulticastGroup_NBO, + NetworkEndPoint_t * pxEndPoint ) + { + NetworkBufferDescriptor_t * pxNetworkBuffer; + + if( NULL == ( pxNetworkBuffer = pxGetNetworkBufferWithDescriptor( sizeof( IGMPPacket_t ), uiBlockTime ) ) ) + { + return pdFAIL; + } + + IGMPPacket_t * pxIGMPPacket = ( IGMPPacket_t * ) pxNetworkBuffer->pucEthernetBuffer; + uint16_t usEthType = ipIPv4_FRAME_TYPE; + + /* Fill out the Ethernet header */ + vSetMultiCastIPv4MacAddress( uiMulticastGroup_NBO, &pxIGMPPacket->xEthernetHeader.xDestinationAddress ); + memcpy( ( void * ) pxIGMPPacket->xEthernetHeader.xSourceAddress.ucBytes, ( void * ) DEFAULT_MAC_ADDRESS, ( size_t ) ipMAC_ADDRESS_LENGTH_BYTES ); + memcpy( ( void * ) &pxIGMPPacket->xEthernetHeader.usFrameType, ( void * ) &usEthType, sizeof( uint16_t ) ); + + + IPHeader_t * pxIPHeader; + + pxIPHeader = &( pxIGMPPacket->xIPHeader ); + + pxIGMPPacket->xIGMPHeader.ucTypeOfMessage = ucIgmpMsgType; + pxIGMPPacket->xIGMPHeader.ucMaxResponseTime = ucIgmpRespTime; + pxIGMPPacket->xIGMPHeader.uiGroupAddress = uiMulticastGroup_NBO; + + pxIPHeader->ulDestinationIPAddress = uiMulticastGroup_NBO; + pxIPHeader->ulSourceIPAddress = pxEndPoint->ipv4_settings.ulIPAddress; + pxIPHeader->ucProtocol = ipPROTOCOL_IGMP; + pxIPHeader->usLength = ( uint16_t ) ( 0 + sizeof( IPHeader_t ) + sizeof( IGMPHeader_t ) ); + pxIPHeader->usLength = FreeRTOS_htons( pxIPHeader->usLength ); + pxIPHeader->ucVersionHeaderLength = 0x45U; /*ipIPV4_VERSION_HEADER_LENGTH_MIN; */ + pxIPHeader->ucDifferentiatedServicesCode = 0; + pxIPHeader->usIdentification = FreeRTOS_ntohs( usIPv4_IGMP_Identification ); + usIPv4_IGMP_Identification++; + pxIPHeader->ucTimeToLive = 1; + pxIPHeader->usHeaderChecksum = 0U; + + /* The stack doesn't support fragments, so the fragment offset field must always be zero. + * The header was never memset to zero, so set both the fragment offset and fragmentation flags in one go. + */ + #if ( ipconfigFORCE_IP_DONT_FRAGMENT != 0 ) + pxIPHeader->usFragmentOffset = ipFRAGMENT_FLAGS_DONT_FRAGMENT; + #else + pxIPHeader->usFragmentOffset = 0U; + #endif + + + #if ( ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM == 0 ) + { + pxIPHeader->usHeaderChecksum = 0U; + pxIPHeader->usHeaderChecksum = usGenerateChecksum( 0U, ( uint8_t * ) &( pxIPHeader->ucVersionHeaderLength ), ipSIZE_OF_IPv4_HEADER ); + pxIPHeader->usHeaderChecksum = ~FreeRTOS_htons( pxIPHeader->usHeaderChecksum ); + } + #endif + + /* Calculate frame length */ + uint32_t uiFrameLen = sizeof( IGMPPacket_t ); + pxNetworkBuffer->xDataLength = uiFrameLen; + + #if ( ipconfigIGMP_SNOOPING != 0 ) + + /* Because we are doing IGMP snooping, let the IGMP Snooping module decide + * which port this packet needs to be sent to. */ + extern void vApplicationIgmpSendLocalMessageHook( NetworkBufferDescriptor_t * pxNetworkBuffer, + uint8_t ucIgmpMsgType, + uint32_t uiMulticastGroup ); + ( void ) vApplicationIgmpSendLocalMessageHook( pxNetworkBuffer, ucIgmpMsgType, uiMulticastGroup_NBO ); + #else + + /* Since this is a normal host without an attached switch and IGMP snooping, + * we simply send the frame out */ + pxEndPoint->pxNetworkInterface->pfOutput( pxEndPoint->pxNetworkInterface, pxNetworkBuffer, pdTRUE ); + #endif + + return pdPASS; + } + + + +/************************************************************************/ +/* Test code below this point */ +/************************************************************************/ + + + +#endif /* if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ diff --git a/source/FreeRTOS_IP.c b/source/FreeRTOS_IP.c index 8d6c765ab9..27270c2ca1 100644 --- a/source/FreeRTOS_IP.c +++ b/source/FreeRTOS_IP.c @@ -59,6 +59,9 @@ #include "FreeRTOS_DNS.h" #include "FreeRTOS_Routing.h" #include "FreeRTOS_ND.h" +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + #include "FreeRTOS_IGMP.h" +#endif /** @brief Time delay between repeated attempts to initialise the network hardware. */ #ifndef ipINITIALISATION_RETRY_DELAY @@ -467,6 +470,26 @@ static void prvProcessIPEventsAndTimers( void ) /* xQueueReceive() returned because of a normal time-out. */ break; + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + case eSocketOptAddMembership: + { + MCastGroupDesc_t * pxMCG = ( MCastGroupDesc_t * ) xReceivedEvent.pvData; + ( void ) vModifyMulticastMembership( pxMCG, eSocketOptAddMembership ); + break; + } + + case eSocketOptDropMembership: + { + MCastGroupDesc_t * pxMCG = ( MCastGroupDesc_t * ) xReceivedEvent.pvData; + ( void ) vModifyMulticastMembership( pxMCG, eSocketOptDropMembership ); + break; + } + + case eIGMPEvent: + ( void ) vHandleIGMP_Event(); + break; + #endif /* ( ipconfigSUPPORT_IP_MULTICAST != 0) */ + default: /* Should not get here. */ break; @@ -526,6 +549,11 @@ static void prvIPTask_Initialise( void ) } #endif /* ( ( ipconfigUSE_DNS_CACHE != 0 ) && ( ipconfigUSE_DNS != 0 ) ) */ + /* Init the list that will hold scheduled IGMP reports. */ + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + ( void ) vIGMP_Init(); + #endif + /* Initialisation is complete and events can now be processed. */ xIPTaskInitialised = pdTRUE; } @@ -1980,6 +2008,13 @@ static eFrameProcessingResult_t prvProcessIPPacket( const IPPacket_t * pxIPPacke break; #endif /* ( ipconfigUSE_IPv6 != 0 ) */ + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + case ipPROTOCOL_IGMP: + /* The IP packet contained an IGMP frame. */ + eReturn = eProcessIGMPPacket( pxNetworkBuffer ); + break; + #endif /* ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ + case ipPROTOCOL_UDP: /* The IP packet contained a UDP frame. */ diff --git a/source/FreeRTOS_IP_Timers.c b/source/FreeRTOS_IP_Timers.c index 709c9e5b8b..b7060ccc55 100644 --- a/source/FreeRTOS_IP_Timers.c +++ b/source/FreeRTOS_IP_Timers.c @@ -55,6 +55,9 @@ #include "NetworkBufferManagement.h" #include "FreeRTOS_Routing.h" #include "FreeRTOS_DNS.h" +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + #include "FreeRTOS_IGMP.h" +#endif /*-----------------------------------------------------------*/ /** @brief 'xAllNetworksUp' becomes pdTRUE as soon as all network interfaces have @@ -110,6 +113,11 @@ static IPTimer_t xARPTimer; static IPTimer_t xDNSTimer; #endif +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + /** @brief IGMP timer. Used for sending scheduled IGMP Reports */ + static IPTimer_t xIGMPTimer; +#endif + /** @brief As long as not all networks are up, repeat initialisation by calling the * xNetworkInterfaceInitialise() function of the interfaces that are not ready. */ @@ -176,6 +184,15 @@ TickType_t xCalculateSleepTime( void ) } #endif + #if ( ipconfigSUPPORT_IP_MULTICAST == 1 ) + { + if( xIGMPTimer.ulRemainingTime < uxMaximumSleepTime ) + { + uxMaximumSleepTime = xIGMPTimer.ulRemainingTime; + } + } + #endif /* ipconfigSUPPORT_IP_MULTICAST */ + #if ( ipconfigDNS_USE_CALLBACKS != 0 ) { if( xDNSTimer.bActive != pdFALSE_UNSIGNED ) @@ -312,6 +329,16 @@ void vCheckNetworkTimers( void ) vSocketListenNextTime( NULL ); #endif /* ipconfigUSE_TCP == 1 */ + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + { + /* Is it time to send any IGMP reports? */ + if( prvIPTimerCheck( &xIGMPTimer ) != pdFALSE ) + { + ( void ) xSendIGMPEvent(); + } + } + #endif /* ipconfigSUPPORT_IP_MULTICAST != 0 */ + /* Is it time to trigger the repeated NetworkDown events? */ if( xAllNetworksUp == pdFALSE ) { @@ -412,6 +439,20 @@ void vARPTimerReload( TickType_t xTime ) /*-----------------------------------------------------------*/ +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + +/** + * @brief Reload the IGMP timer. + * + * @param[in] xIgmpTickTime: The reload value. + */ + void vIGMPTimerReload( TickType_t xIgmpTickTime ) + { + prvIPTimerReload( &xIGMPTimer, pdMS_TO_TICKS( ipIGMP_TIMER_PERIOD_MS ) ); + } +#endif /* ipconfigSUPPORT_IP_MULTICAST */ +/*-----------------------------------------------------------*/ + #if ( ipconfigDNS_USE_CALLBACKS != 0 ) /** diff --git a/source/FreeRTOS_Sockets.c b/source/FreeRTOS_Sockets.c index 9fd643b3d6..8298c50fd2 100644 --- a/source/FreeRTOS_Sockets.c +++ b/source/FreeRTOS_Sockets.c @@ -52,7 +52,9 @@ #include "FreeRTOS_DNS.h" #include "NetworkBufferManagement.h" #include "FreeRTOS_Routing.h" - +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + #include "FreeRTOS_IGMP.h" +#endif #if ( ipconfigUSE_TCP_MEM_STATS != 0 ) #include "tcp_mem_stats.h" #endif @@ -738,6 +740,11 @@ Socket_t FreeRTOS_socket( BaseType_t xDomain, pxSocket->u.xUDP.uxMaxPackets = ( UBaseType_t ) ipconfigUDP_MAX_RX_PACKETS; } #endif /* ipconfigUDP_MAX_RX_PACKETS > 0 */ + + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + pxSocket->u.xUDP.ucMulticastTTL = ipconfigMULTICAST_DEFAULT_TTL; + vListInitialise( &pxSocket->u.xUDP.xMulticastGroupsList ); + #endif } #if ( ipconfigUSE_TCP == 1 ) @@ -1432,6 +1439,17 @@ static int32_t prvSendUDPPacket( const FreeRTOS_Socket_t * pxSocket, pxNetworkBuffer->usPort = pxDestinationAddress->sin_port; pxNetworkBuffer->usBoundPort = ( uint16_t ) socketGET_SOCKET_PORT( pxSocket ); + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + if( xIsIPv4Multicast( pxDestinationAddress->sin_address.ulIP_IPv4 ) ) + { + pxNetworkBuffer->ucSendTTL = pxSocket->u.xUDP.ucMulticastTTL; + } + else + { + pxNetworkBuffer->ucSendTTL = ipconfigUDP_TIME_TO_LIVE; + } + #endif + /* The socket options are passed to the IP layer in the * space that will eventually get used by the Ethernet header. */ pxNetworkBuffer->pucEthernetBuffer[ ipSOCKET_OPTIONS_OFFSET ] = pxSocket->ucSocketOptions; @@ -2132,6 +2150,18 @@ void * vSocketClose( FreeRTOS_Socket_t * pxSocket ) #endif /* ipconfigETHERNET_DRIVER_FILTERS_PACKETS */ } + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + if( pxSocket->ucProtocol == ipPROTOCOL_UDP ) + { + /* Un-register all multicast groups that might have been added. */ + while( listCURRENT_LIST_LENGTH( &( pxSocket->u.xUDP.xMulticastGroupsList ) ) > 0U ) + { + MCastGroupDesc_t * pMCD = ( MCastGroupDesc_t * ) listGET_OWNER_OF_HEAD_ENTRY( &( pxSocket->u.xUDP.xMulticastGroupsList ) ); + ( void ) vModifyMulticastMembership( pMCD, eSocketOptDropMembership ); + } + } + #endif /* ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ + /* Now the socket is not bound the list of waiting packets can be * drained. */ if( pxSocket->ucProtocol == ( uint8_t ) FREERTOS_IPPROTO_UDP ) @@ -2929,6 +2959,20 @@ BaseType_t FreeRTOS_setsockopt( Socket_t xSocket, break; #endif /* ipconfigUSE_TCP == 1 */ + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + case FREERTOS_SO_IP_MULTICAST_TTL: + case FREERTOS_SO_IP_ADD_MEMBERSHIP: + case FREERTOS_SO_IP_DROP_MEMBERSHIP: + /* Use extern here, because we don't want to expose xSetMulticastSocketOption through .h files */ + extern BaseType_t xSetMulticastSocketOption( Socket_t xSocket, + int32_t lLevel, + int32_t lOptionName, + const void * pvOptionValue, + size_t uxOptionLength ); + xReturn = xSetMulticastSocketOption( xSocket, lLevel, lOptionName, pvOptionValue, uxOptionLength ); + break; + #endif /* (ipconfigSUPPORT_IP_MULTICAST != 0) */ + default: /* No other options are handled. */ xReturn = -pdFREERTOS_ERRNO_ENOPROTOOPT; diff --git a/source/FreeRTOS_UDP_IPv4.c b/source/FreeRTOS_UDP_IPv4.c index 9562ea90d8..6a4372361d 100644 --- a/source/FreeRTOS_UDP_IPv4.c +++ b/source/FreeRTOS_UDP_IPv4.c @@ -63,6 +63,9 @@ /* *INDENT-OFF* */ #if( ipconfigUSE_IPv4 != 0 ) /* *INDENT-ON* */ +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + #include "FreeRTOS_IGMP.h" +#endif /** @brief The expected IP version and header length coded into the IP header itself. */ #define ipIP_VERSION_AND_HEADER_LENGTH_BYTE ( ( uint8_t ) 0x45 ) @@ -229,6 +232,13 @@ void vProcessGeneratedUDPPacket_IPv4( NetworkBufferDescriptor_t * const pxNetwor } #endif + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + if( ( pxNetworkBuffer->xIPAddress.ulIP_IPv4 != ipLLMNR_IP_ADDR ) && ( pxNetworkBuffer->xIPAddress.ulIP_IPv4 == ipMDNS_IP_ADDRESS ) ) + { + pxIPHeader->ucTimeToLive = pxNetworkBuffer->ucSendTTL; + } + #endif + #if ( ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM == 0 ) { pxIPHeader->usHeaderChecksum = 0U; @@ -364,6 +374,40 @@ BaseType_t xProcessReceivedUDPPacket_IPv4( NetworkBufferDescriptor_t * pxNetwork *pxIsWaitingForARPResolution = pdFALSE; + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + + /* If this incoming packet is a multicast, we may have a socket for the port, but we still need + * to ensure the socket is subscribed to that particular multicast group. Note: Since this stack + * does not support port reusing, we don't have to worry about two UDP sockets bound to the exact same + * local port, but subscribed to different multicast groups. If this was allowed, this check + * would have to be moved to the pxUDPSocketLookup() function itself. */ + if( ( pxSocket != NULL ) && ( xIsIPv4Multicast( pxUDPPacket->xIPHeader.ulDestinationIPAddress ) ) ) + { + /* Note: There is no need for vTaskSuspendAll/xTaskResumeAll here because only the IP Task + * touches the socket's multicast groups list directly. */ + const ListItem_t * pxIterator; + const ListItem_t * xEnd = listGET_END_MARKER( &( pxSocket->u.xUDP.xMulticastGroupsList ) ); + + for( pxIterator = ( const ListItem_t * ) listGET_NEXT( xEnd ); + pxIterator != ( const ListItem_t * ) xEnd; + pxIterator = ( const ListItem_t * ) listGET_NEXT( pxIterator ) ) + { + MCastGroupDesc_t * pxMCG = ( MCastGroupDesc_t * ) listGET_LIST_ITEM_OWNER( pxIterator ); + + if( pxMCG->mreq.imr_multiaddr.sin_address.ulIP_IPv4 == pxUDPPacket->xIPHeader.ulDestinationIPAddress ) + { + break; + } + } + + if( pxIterator == ( const ListItem_t * ) xEnd ) + { + /* This socket is not subscribed to this multicast group. Nullify the result from pxUDPSocketLookup() */ + pxSocket = NULL; + } + } + #endif /* ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ + do { if( pxSocket != NULL ) diff --git a/source/include/FreeRTOSIPConfigDefaults.h b/source/include/FreeRTOSIPConfigDefaults.h index 93a384f646..ea3d0a6f29 100644 --- a/source/include/FreeRTOSIPConfigDefaults.h +++ b/source/include/FreeRTOSIPConfigDefaults.h @@ -1160,4 +1160,28 @@ #define ipconfigPORT_SUPPRESS_WARNING ( 0 ) #endif +/* Setting to 1 will enable the reception of IPv4 multicast groups + * and the associated socket options that need to be set before + * a socket would properly receive multicast packets. */ +#ifndef ipconfigSUPPORT_IP_MULTICAST + #define ipconfigSUPPORT_IP_MULTICAST ( 0 ) +#endif + +/* Specifies the TTL value that will be used for multicast + * UDP packets by default. Can be overridden per socket by + * setting the FREERTOS_SO_IP_MULTICAST_TTL socket option. */ +#ifndef ipconfigMULTICAST_DEFAULT_TTL + #define ipconfigMULTICAST_DEFAULT_TTL ( 1 ) +#endif + +/* Set to 1 if you plan on implementing IGMP snooping. + * When set to 1, the user must implement the following hooks: + * eFrameProcessingResult_t eApplicationIgmpFrameReceivedHook( NetworkBufferDescriptor_t * pxNetworkBuffer ) + * void vApplicationIgmpSendLocalMessageHook( NetworkBufferDescriptor_t * pxNetworkBuffer, uint8_t ucIgmpMsgType, uint32_t uiMulticastGroup ) + * void vApplicationIgmpEventHook( void ) + */ +#ifndef ipconfigIGMP_SNOOPING + #define ipconfigIGMP_SNOOPING ( 0 ) +#endif + #endif /* FREERTOS_DEFAULT_IP_CONFIG_H */ diff --git a/source/include/FreeRTOS_IGMP.h b/source/include/FreeRTOS_IGMP.h new file mode 100644 index 0000000000..c74982265d --- /dev/null +++ b/source/include/FreeRTOS_IGMP.h @@ -0,0 +1,127 @@ +/* + * FreeRTOS+TCP V2.4.0 + * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +#ifndef FREERTOS_IGMP_H + #define FREERTOS_IGMP_H + + #ifdef __cplusplus + extern "C" { + #endif + +/* Application level configuration options. */ + #include "FreeRTOSIPConfig.h" + #include "FreeRTOSIPConfigDefaults.h" + #include "FreeRTOS_Sockets.h" + #include "IPTraceMacroDefaults.h" + #include "FreeRTOS_Stream_Buffer.h" + #if ( ipconfigUSE_TCP == 1 ) + #include "FreeRTOS_TCP_WIN.h" + #include "FreeRTOS_TCP_IP.h" + #endif + + #include "semphr.h" + + #include "event_groups.h" + + +/** @brief IGMP times events at 100ms. */ + #define ipIGMP_TIMER_PERIOD_MS ( 100U ) + +/* IGMP protocol definitions. */ + #define ipIGMP_MEMBERSHIP_QUERY ( ( uint8_t ) 0x11U ) /**< IGMP membership query. */ + #define ipIGMP_MEMBERSHIP_REPORT_V1 ( ( uint8_t ) 0x12U ) /**< IGMP v1 and v2 membership report. */ + #define ipIGMP_MEMBERSHIP_REPORT_V2 ( ( uint8_t ) 0x16U ) /**< IGMP v1 and v2 membership report. */ + #define ipIGMP_MEMBERSHIP_REPORT_V3 ( ( uint8_t ) 0x22U ) /**< IGMP v3 membership report. */ + #define ipIGMP_LEAVE_GROUP ( ( uint8_t ) 0x17U ) /**< IGMP leave group */ + + #if ( ipconfigBYTE_ORDER == pdFREERTOS_BIG_ENDIAN ) + #define ipIGMP_IP_ADDR 0xE0000001UL + #else + #define ipIGMP_IP_ADDR 0x010000E0UL + #endif /* ipconfigBYTE_ORDER == pdFREERTOS_BIG_ENDIAN */ + + struct freertos_ip_mreq + { + /* _EVP_ This can be simplified a bit on a single IF, IPv4 only system + * but keeping it more generic allows for future use in multi-IF dual-stack implementations. */ + struct freertos_sockaddr imr_multiaddr; /* IP multicast address of a group */ + struct freertos_sockaddr imr_interface; /* local IP address of the interface to be used */ + }; + +/** @brief The structure information about the IGMP reports that will get sent when the stack receives an IGMP general query. */ + typedef struct xIGMPReportDesc + { + struct freertos_ip_mreq mreq; /**< Struct for storing the original mreq structure that was sent to setsockopts() */ + struct xLIST_ITEM xListItem; /**< List struct. */ + NetworkEndPoint_t * pxEndPoint; + BaseType_t xNumSockets; + uint8_t ucCountDown; + } IGMPReportDesc_t; + +/** @brief The structure to hold a "descriptor" for a multicast group that a socket has registered to. */ + typedef struct xMCastGroupDesc + { + struct freertos_ip_mreq mreq; /**< Struct for storing the original mreq structure that was sent to setsockopts() */ + struct xLIST_ITEM xListItem; /**< List struct. */ + FreeRTOS_Socket_t * pxSocket; + IGMPReportDesc_t * pxIGMPReportDesc; /** Optional. used to hold the allocated IGMP report descriptor while passing from user code to the IP Task. */ + } MCastGroupDesc_t; + + #include "pack_struct_start.h" + struct xIGMP_HEADER + { + uint8_t ucTypeOfMessage; /**< The IGMP type 0 + 1 = 1 */ + uint8_t ucMaxResponseTime; /**< Maximum time (sec) for responses. 1 + 1 = 2 */ + uint16_t usChecksum; /**< The checksum of whole IGMP packet 2 + 2 = 4 */ + uint32_t uiGroupAddress; /**< The multicast group address 4 + 4 = 8 */ + } + #include "pack_struct_end.h" + typedef struct xIGMP_HEADER IGMPHeader_t; + + #include "pack_struct_start.h" + struct xIGMP_PACKET + { + EthernetHeader_t xEthernetHeader; /**< The Ethernet header of an IGMP packet. */ + IPHeader_t xIPHeader; /**< The IP header of an IGMP packet. */ + IGMPHeader_t xIGMPHeader; /**< The IGMP header of an IGMP packet. */ + } + #include "pack_struct_end.h" + typedef struct xIGMP_PACKET IGMPPacket_t; + + void vIGMP_Init( void ); + void vModifyMulticastMembership( MCastGroupDesc_t * pxMulticastGroup, + uint8_t bAction ); + BaseType_t xSendIGMPEvent( void ); + void vHandleIGMP_Event( void ); + void vRemoveIGMPReportFromList( struct freertos_ip_mreq * pMCastGroup ); + BaseType_t xAddIGMPReportToList( IGMPReportDesc_t * pNewEntry ); + eFrameProcessingResult_t eProcessIGMPPacket( NetworkBufferDescriptor_t * const pxNetworkBuffer ); + + + #ifdef __cplusplus +} /* extern "C" */ + #endif + +#endif /* FREERTOS_IP_PRIVATE_H */ diff --git a/source/include/FreeRTOS_IP.h b/source/include/FreeRTOS_IP.h index 98f1237614..8caf7a7fd4 100644 --- a/source/include/FreeRTOS_IP.h +++ b/source/include/FreeRTOS_IP.h @@ -168,6 +168,9 @@ typedef struct xNETWORK_BUFFER #if ( ipconfigUSE_LINKED_RX_MESSAGES != 0 ) struct xNETWORK_BUFFER * pxNextBuffer; /**< Possible optimisation for expert users - requires network driver support. */ #endif + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + uint8_t ucSendTTL; /**< Outgoing TimeToLive value. Required for multicasts. */ + #endif #define ul_IPAddress xIPAddress.xIP_IPv4 #define x_IPv6Address xIPAddress.xIP_IPv6 diff --git a/source/include/FreeRTOS_IP_Private.h b/source/include/FreeRTOS_IP_Private.h index 1c97d5c898..4bc551b151 100644 --- a/source/include/FreeRTOS_IP_Private.h +++ b/source/include/FreeRTOS_IP_Private.h @@ -72,20 +72,23 @@ typedef enum eFrameProcessingResult typedef enum { eNoEvent = -1, - eNetworkDownEvent, /* 0: The network interface has been lost and/or needs [re]connecting. */ - eNetworkRxEvent, /* 1: The network interface has queued a received Ethernet frame. */ - eNetworkTxEvent, /* 2: Let the IP-task send a network packet. */ - eARPTimerEvent, /* 3: The ARP timer expired. */ - eStackTxEvent, /* 4: The software stack has queued a packet to transmit. */ - eDHCPEvent, /* 5: Process the DHCP state machine. */ - eTCPTimerEvent, /* 6: See if any TCP socket needs attention. */ - eTCPAcceptEvent, /* 7: Client API FreeRTOS_accept() waiting for client connections. */ - eTCPNetStat, /* 8: IP-task is asked to produce a netstat listing. */ - eSocketBindEvent, /* 9: Send a message to the IP-task to bind a socket to a port. */ - eSocketCloseEvent, /*10: Send a message to the IP-task to close a socket. */ - eSocketSelectEvent, /*11: Send a message to the IP-task for select(). */ - eSocketSignalEvent, /*12: A socket must be signalled. */ - eSocketSetDeleteEvent, /*13: A socket set must be deleted. */ + eNetworkDownEvent, /* 0: The network interface has been lost and/or needs [re]connecting. */ + eNetworkRxEvent, /* 1: The network interface has queued a received Ethernet frame. */ + eNetworkTxEvent, /* 2: Let the IP-task send a network packet. */ + eARPTimerEvent, /* 3: The ARP timer expired. */ + eStackTxEvent, /* 4: The software stack has queued a packet to transmit. */ + eDHCPEvent, /* 5: Process the DHCP state machine. */ + eTCPTimerEvent, /* 6: See if any TCP socket needs attention. */ + eTCPAcceptEvent, /* 7: Client API FreeRTOS_accept() waiting for client connections. */ + eTCPNetStat, /* 8: IP-task is asked to produce a netstat listing. */ + eSocketBindEvent, /* 9: Send a message to the IP-task to bind a socket to a port. */ + eSocketCloseEvent, /*10: Send a message to the IP-task to close a socket. */ + eSocketSelectEvent, /*11: Send a message to the IP-task for select(). */ + eSocketSignalEvent, /*12: A socket must be signalled. */ + eSocketSetDeleteEvent, /*13: A socket set must be deleted. */ + eSocketOptAddMembership, /*14: Register a UDP socket to a multicast group. */ + eSocketOptDropMembership, /*15: Unregister a UDP socket from a multicast group. */ + eIGMPEvent, /*16: */ } eIPEvent_t; /** @@ -644,6 +647,14 @@ typedef struct UDPSOCKET */ FOnUDPSent_t pxHandleSent; /**< Function pointer to handle the events after a successful send. */ #endif /* ipconfigUSE_CALLBACKS */ + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + List_t xMulticastGroupsList; + uint8_t ucMulticastTTL; /**< Allows for multicast sockets to use a different TTL value to limit the scope of the multicast packet. Usually set to 1. + * Example: + * uint8_t ucMulticastTTL = 1; + * FreeRTOS_setsockopt( MCastSendSock, 0, FREERTOS_SO_IP_MULTICAST_TTL, ( void * ) &ucMulticastTTL, sizeof( uint8_t ) ); + */ + #endif } IPUDPSocket_t; /* Formally typedef'd as eSocketEvent_t. */ diff --git a/source/include/FreeRTOS_IP_Timers.h b/source/include/FreeRTOS_IP_Timers.h index 8d7a84e263..652816bb60 100644 --- a/source/include/FreeRTOS_IP_Timers.h +++ b/source/include/FreeRTOS_IP_Timers.h @@ -130,6 +130,10 @@ void vTCPTimerReload( TickType_t xTime ); #if ( ipconfigDNS_USE_CALLBACKS != 0 ) + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + void vIGMPTimerReload( TickType_t xIgmpTickTime ); + #endif + /** * Reload the DNS timer. */ diff --git a/source/include/FreeRTOS_Routing.h b/source/include/FreeRTOS_Routing.h index 7d143b9b7a..b9933f5c9c 100644 --- a/source/include/FreeRTOS_Routing.h +++ b/source/include/FreeRTOS_Routing.h @@ -54,6 +54,14 @@ /* Return true as long as the LinkStatus on the PHY is present. */ typedef BaseType_t ( * GetPhyLinkStatusFunction_t ) ( struct xNetworkInterface * pxDescriptor ); + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) +/* Adds a multicast MAC address to the list of received MAC addresses for this interface */ + typedef void ( * NetworkInterfaceAddMulticastMACFunction_t ) ( const uint8_t * MacAddressBytes ); + +/* Removes a multicast MAC address from the list of MAC addresses that this interface receives */ + typedef void ( * NetworkInterfaceRemoveMulticastMACFunction_t ) ( const uint8_t * MacAddressBytes ); + #endif + /** @brief These NetworkInterface access functions are collected in a struct: */ typedef struct xNetworkInterface { @@ -62,6 +70,10 @@ NetworkInterfaceInitialiseFunction_t pfInitialise; /**< This function will be called upon initialisation and repeated until it returns pdPASS. */ NetworkInterfaceOutputFunction_t pfOutput; /**< This function is supposed to send out a packet. */ GetPhyLinkStatusFunction_t pfGetPhyLinkStatus; /**< This function will return pdTRUE as long as the PHY Link Status is high. */ + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + NetworkInterfaceAddMulticastMACFunction_t pfAddMulticastMAC; + NetworkInterfaceAddMulticastMACFunction_t pfRemoveMulticastMAC; + #endif struct { uint32_t diff --git a/source/include/FreeRTOS_Sockets.h b/source/include/FreeRTOS_Sockets.h index 5296098c99..aada45bdcd 100644 --- a/source/include/FreeRTOS_Sockets.h +++ b/source/include/FreeRTOS_Sockets.h @@ -148,8 +148,15 @@ #endif #if ( ipconfigUSE_TCP == 1 ) - #define FREERTOS_SO_SET_LOW_HIGH_WATER ( 18 ) + #define FREERTOS_SO_SET_LOW_HIGH_WATER ( 18 ) #endif + + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + #define FREERTOS_SO_IP_MULTICAST_TTL ( 19 ) /* TTL value to me used when sending multicast packets. Defaults to ipconfigMULTICAST_DEFAULT_TTL */ + #define FREERTOS_SO_IP_ADD_MEMBERSHIP ( 20 ) /* Mark the socket as able to receive multicast messages from a multicast group address */ + #define FREERTOS_SO_IP_DROP_MEMBERSHIP ( 21 ) /* Remove membership from a multicast group address */ + #endif /* (ipconfigSUPPORT_IP_MULTICAST != 0) */ + #define FREERTOS_INADDR_ANY ( 0U ) /* The 0.0.0.0 IPv4 address. */ #if ( 0 ) /* Not Used */ diff --git a/source/portable/NetworkInterface/DriverSAM/NetworkInterface.c b/source/portable/NetworkInterface/DriverSAM/NetworkInterface.c index 7ad92f940b..33d986ca48 100644 --- a/source/portable/NetworkInterface/DriverSAM/NetworkInterface.c +++ b/source/portable/NetworkInterface/DriverSAM/NetworkInterface.c @@ -195,6 +195,9 @@ static BaseType_t prvSAM_GetPhyLinkStatus( NetworkInterface_t * pxInterface ); NetworkInterface_t * pxSAM_FillInterfaceDescriptor( BaseType_t xEMACIndex, NetworkInterface_t * pxInterface ); +static void prvAddMulticastMACAddress( const uint8_t * ucMacAddress ); +static void prvRemoveMulticastMACAddress( const uint8_t * ucMacAddress ); + /* * Handle transmission errors. */ @@ -202,7 +205,6 @@ static void hand_tx_errors( void ); /* Functions to set the hash table for multicast addresses. */ static uint16_t prvGenerateCRC16( const uint8_t * pucAddress ); -static void prvAddMulticastMACAddress( const uint8_t * ucMacAddress ); /* Checks IP queue, buffers, and semaphore and logs diagnostic info if configured */ static void vCheckBuffersAndQueue( void ); @@ -521,6 +523,10 @@ NetworkInterface_t * pxSAM_FillInterfaceDescriptor( BaseType_t xEMACIndex, pxInterface->pfInitialise = prvSAM_NetworkInterfaceInitialise; pxInterface->pfOutput = prvSAM_NetworkInterfaceOutput; pxInterface->pfGetPhyLinkStatus = prvSAM_GetPhyLinkStatus; + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + pxInterface->pfAddMulticastMAC = prvAddMulticastMACAddress; + pxInterface->pfRemoveMulticastMAC = prvRemoveMulticastMACAddress; + #endif FreeRTOS_AddNetworkInterface( pxInterface ); @@ -708,27 +714,46 @@ static BaseType_t prvGMACInit( NetworkInterface_t * pxInterface ) /* set Multicast Hash Enable. */ GMAC->GMAC_NCFGR |= GMAC_NCFGR_MTIHEN; - #if ( ipconfigUSE_LLMNR == 1 ) + #if ( ipconfigUSE_LLMNR != 0 ) { prvAddMulticastMACAddress( xLLMNR_MacAddress.ucBytes ); - } - #endif /* ipconfigUSE_LLMNR */ + } /* ipconfigUSE_LLMNR */ + + #if ( ipconfigUSE_MDNS != 0 ) + prvAddMulticastMACAddress( xMDNS_MacAdress.ucBytes ); + #endif /* ipconfigUSE_MDNS */ #if ( ipconfigUSE_IPv6 != 0 ) { - NetworkEndPoint_t * pxEndPoint; + /* Register the Link-Local All-Nodes address */ + /* FF02::1 --> 33-33-00-00-00-01 */ + uint8_t pcLOCAL_ALL_NODES_MULTICAST_MAC[ ipMAC_ADDRESS_LENGTH_BYTES ] = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x01 }; + prvAddMulticastMACAddress( pcLOCAL_ALL_NODES_MULTICAST_MAC ); + #if ( ipconfigUSE_LLMNR == 1 ) { prvAddMulticastMACAddress( xLLMNR_MacAddressIPv6.ucBytes ); } #endif /* ipconfigUSE_LLMNR */ + #if ( ipconfigUSE_MDNS != 0 ) + { + prvAddMulticastMACAddress( xMDNS_MACAddressIPv6.ucBytes ); + } + #endif /* ipconfigUSE_MDNS */ + + // Register the Link-Local All-Nodes address + // FF02::1 --> 33-33-00-00-00-01 + uint8_t pcLOCAL_ALL_NODES_MULTICAST_MAC[ ipMAC_ADDRESS_LENGTH_BYTES ] = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x01 }; + prvAddMulticastMACAddress(pcLOCAL_ALL_NODES_MULTICAST_MAC); + for( pxEndPoint = FreeRTOS_FirstEndPoint( pxMyInterface ); pxEndPoint != NULL; pxEndPoint = FreeRTOS_NextEndPoint( pxMyInterface, pxEndPoint ) ) { if( pxEndPoint->bits.bIPv6 != pdFALSE_UNSIGNED ) { + // Since this is an IPv6 end point, add the solicited-node MAC address. uint8_t ucMACAddress[ 6 ] = { 0x33, 0x33, 0xff, 0, 0, 0 }; ucMACAddress[ 3 ] = pxEndPoint->ipv6_settings.xIPAddress.ucBytes[ 13 ]; @@ -775,6 +800,12 @@ static BaseType_t prvGMACInit( NetworkInterface_t * pxInterface ) } /*-----------------------------------------------------------*/ +#define GMAC_ADDRESS_HASH_BITS ( 64U ) +#define GMAC_ADDRESS_HASH_MASK ( GMAC_ADDRESS_HASH_BITS - 1 ) +#define GMAC_ADDRESS_HASH_COUNTERS_MAX_VALUE ( 255U ) +static uint8_t prvAddressHashCounters[ GMAC_ADDRESS_HASH_BITS ] = { 0 }; +static uint64_t prvAddressHashBitMask = ( 0 ); + static uint16_t prvGenerateCRC16( const uint8_t * pucAddress ) { uint16_t usSum; @@ -795,30 +826,100 @@ static uint16_t prvGenerateCRC16( const uint8_t * pucAddress ) usSum ^= ( usValues[ 4 ] >> 4 ) ^ ( usValues[ 4 ] << 2 ); usSum ^= ( usValues[ 5 ] >> 2 ) ^ ( usValues[ 5 ] << 4 ); - usSum &= 0x3FU; + usSum &= GMAC_ADDRESS_HASH_MASK; return usSum; } /*-----------------------------------------------------------*/ static void prvAddMulticastMACAddress( const uint8_t * ucMacAddress ) { - uint32_t ulMask; - uint16_t usIndex; + uint8_t ucBinNumber; + + if( NULL == ucMacAddress ) + { + return; + } + + #if ( ipconfigIGMP_SNOOPING != 0 ) + /* If we're doing IGMP snooping, we are already receiving all multicasts, */ + /* so nothing to do here */ + return; + #endif + + DebugPrintf( "prvAddMulticastMACAddress %02X:%02X:%02X:%02X:%02X:%02X", + ucMacAddress[ 0 ], ucMacAddress[ 1 ], ucMacAddress[ 2 ], ucMacAddress[ 3 ], ucMacAddress[ 4 ], ucMacAddress[ 5 ] ); - usIndex = prvGenerateCRC16( ucMacAddress ); - ulMask = 1U << ( usIndex % 32 ); + ucBinNumber = prvGenerateCRC16( ucMacAddress ); - if( usIndex < 32U ) + vTaskSuspendAll(); + + /* If the bin counter corresponding to this mac address has is already non-zero, */ + /* a multicast address with the same hash has already been added, so there's nothing more to do. */ + if( 0 == prvAddressHashCounters[ ucBinNumber ] ) { - /* 0 .. 31 */ - GMAC->GMAC_HRB |= ulMask; + /* This bin counter is zero, so this is the first time we are registering a MAC with this hash. */ + prvAddressHashBitMask |= ( uint64_t ) ( ( uint64_t ) 1 << ucBinNumber ); + gmac_set_hash64( GMAC, prvAddressHashBitMask ); + gmac_enable_multicast_hash( GMAC, true ); } - else + + /* Increment the counter, but make sure we don't overflow it. */ + if( prvAddressHashCounters[ ucBinNumber ] < GMAC_ADDRESS_HASH_COUNTERS_MAX_VALUE ) { - /* 32 .. 63 */ - GMAC->GMAC_HRT |= ulMask; + prvAddressHashCounters[ ucBinNumber ]++; } + + xTaskResumeAll(); +} + +/* Only called from the IPTask, so no thread-safety is required. */ +static void prvRemoveMulticastMACAddress( const uint8_t * ucMacAddress ) +{ + uint8_t ucBinNumber; + + if( NULL == ucMacAddress ) + { + return; + } + + #if ( ipconfigIGMP_SNOOPING != 0 ) + /* If we're doing IGMP snooping, we are already receiving all multicasts, */ + /* so nothing to do here */ + return; + #endif + + DebugPrintf( "prvRemoveMulticastMACAddress %02X:%02X:%02X:%02X:%02X:%02X", + ucMacAddress[ 0 ], ucMacAddress[ 1 ], ucMacAddress[ 2 ], ucMacAddress[ 3 ], ucMacAddress[ 4 ], ucMacAddress[ 5 ] ); + + ucBinNumber = prvGenerateCRC16( ucMacAddress ); + + vTaskSuspendAll(); + + if( prvAddressHashCounters[ ucBinNumber ] > 0 ) + { + /* If so many multicasts with the same hash were added that the bin counter was maxed out, */ + /* we don't really know how many times we can decrement before actually un-registerring this hash. */ + /* Because of this, if the bin counter ever maxes out, we can never un-register the hash. */ + if( prvAddressHashCounters[ ucBinNumber ] < GMAC_ADDRESS_HASH_COUNTERS_MAX_VALUE ) + { + prvAddressHashCounters[ ucBinNumber ]--; + + if( 0 == prvAddressHashCounters[ ucBinNumber ] ) + { + uint64_t hash = ( uint64_t ) ( ( uint64_t ) 1 << ucBinNumber ); + prvAddressHashBitMask &= ~hash; + gmac_set_hash64( GMAC, prvAddressHashBitMask ); + + if( 0 == prvAddressHashBitMask ) + { + gmac_enable_multicast_hash( GMAC, false ); + } + } + } + } + + xTaskResumeAll(); } /*-----------------------------------------------------------*/