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..250a52dec8 --- /dev/null +++ b/source/FreeRTOS_IGMP.c @@ -0,0 +1,767 @@ +/* + * 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 diff --git a/source/FreeRTOS_IP.c b/source/FreeRTOS_IP.c index c6fd21cc1b..f73d2ed515 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 @@ -475,6 +478,28 @@ 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; @@ -534,6 +559,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; } @@ -1988,6 +2018,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 286d8c7fd4..3478f3cd90 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 6f495e65d5..26b24c482d 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,18 @@ 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 8b545616c0..0eb0915302 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,37 @@ 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 db11848fe4..feac5ebdf8 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..dfceb46ac4 --- /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 1a7fb567ef..fe71b67ba4 100644 --- a/source/include/FreeRTOS_IP_Private.h +++ b/source/include/FreeRTOS_IP_Private.h @@ -86,6 +86,9 @@ typedef enum 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; /** @@ -653,6 +656,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..0b7d8252e2 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 9612491f2e..5a28ab76b1 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 a4b108c57c..7ffc075c5a 100644 --- a/source/include/FreeRTOS_Sockets.h +++ b/source/include/FreeRTOS_Sockets.h @@ -150,6 +150,13 @@ #if ( ipconfigUSE_TCP == 1 ) #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 3540a324a5..97161b6dc8 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 ); /*-----------------------------------------------------------*/ @@ -520,6 +522,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 ); @@ -709,21 +715,32 @@ 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_MacAdress.ucBytes ); - } #endif /* 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_MacAdressIPv6.ucBytes ); } #endif /* ipconfigUSE_LLMNR */ + #if ( ipconfigUSE_MDNS != 0 ) + prvAddMulticastMACAddress( xMDNS_MACAdressIPv6.ucBytes ); + #endif /* ipconfigUSE_MDNS */ + + NetworkEndPoint_t * pxEndPoint; for( pxEndPoint = FreeRTOS_FirstEndPoint( pxMyInterface ); pxEndPoint != NULL; pxEndPoint = FreeRTOS_NextEndPoint( pxMyInterface, pxEndPoint ) ) @@ -776,6 +793,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; @@ -796,30 +819,97 @@ 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; - - usIndex = prvGenerateCRC16( ucMacAddress ); + uint8_t ucBinNumber; - ulMask = 1U << ( usIndex % 32 ); - - if( usIndex < 32U ) + if ( NULL == ucMacAddress) { - /* 0 .. 31 */ - GMAC->GMAC_HRB |= ulMask; + return; } - else + + #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]); + + + ucBinNumber = prvGenerateCRC16( ucMacAddress ); + + 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]) + { + // 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<GMAC_HRT |= ulMask; + prvAddressHashCounters[ucBinNumber]++; } + xTaskResumeAll(); + return; +} + +/* 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<