diff --git a/source/FreeRTOS_DNS_Networking.c b/source/FreeRTOS_DNS_Networking.c index 9d41bcd877..f0d02f5ac6 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 ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + /* 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( ucMulticastTTL ) ); + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ } return xSocket; diff --git a/source/FreeRTOS_DNS_Parser.c b/source/FreeRTOS_DNS_Parser.c index 67fd2d4f6e..ee1fa928df 100644 --- a/source/FreeRTOS_DNS_Parser.c +++ b/source/FreeRTOS_DNS_Parser.c @@ -896,6 +896,26 @@ } xUDPPacket_IPv6->xUDPHeader.usLength = FreeRTOS_htons( ( uint16_t ) lNetLength + ipSIZE_OF_UDP_HEADER ); + + if( xUDPPacket_IPv6->xUDPHeader.usDestinationPort == FreeRTOS_ntohs( ipMDNS_PORT ) ) + { + /* RFC6762, section 11 */ + xUDPPacket_IPv6->xIPHeader.ucHopLimit = 255U; + } + else if( xUDPPacket_IPv6->xUDPHeader.usDestinationPort == FreeRTOS_ntohs( ipLLMNR_PORT ) ) + { + /* LLMNR: RFC4795 section 2.5 recommends UDP requests and responses use TTL of 255 */ + + /* Theoretically, LLMNR replies can go "off-link" and create a DDoS scenario. That should be preventable + * by settings our rely's TTL/HopLimit to 1. Please note that in certain situations ( I think unicast + * responses), Wireshark flags some LLMNR packets that have TTL of 1 as too low. */ + xUDPPacket_IPv6->xIPHeader.ucHopLimit = 1U; + } + else + { + xUDPPacket_IPv6->xIPHeader.ucHopLimit = ipconfigUDP_TIME_TO_LIVE; + } + vFlip_16( pxUDPHeader->usSourcePort, pxUDPHeader->usDestinationPort ); uxDataLength = ( size_t ) lNetLength + ipSIZE_OF_IPv6_HEADER + ipSIZE_OF_UDP_HEADER + ipSIZE_OF_ETH_HEADER; } @@ -911,8 +931,18 @@ /* HT:endian: should not be translated, copying from packet to packet */ if( pxIPHeader->ulDestinationIPAddress == ipMDNS_IP_ADDRESS ) { + /* RFC6762, section 11 */ pxIPHeader->ucTimeToLive = ipMDNS_TIME_TO_LIVE; } + else if( pxUDPHeader->usDestinationPort == FreeRTOS_ntohs( ipLLMNR_PORT ) ) + { + /* LLMNR: RFC4795 section 2.5 recommends UDP requests and responses use TTL of 255 */ + + /* Theoretically, LLMNR replies can go "off-link" and create a DDoS scenario. That should be preventable + * by settings our rely's TTL/HopLimit to 1. Please note that in certain situations ( I think unicast + * responses), Wireshark flags some LLMNR packets that have TTL of 1 as too low. */ + pxIPHeader->ucTimeToLive = 1; + } else { pxIPHeader->ulDestinationIPAddress = pxIPHeader->ulSourceIPAddress; diff --git a/source/FreeRTOS_IP.c b/source/FreeRTOS_IP.c index a43e65f324..c1b5e53c26 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 ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + #include "FreeRTOS_IGMP.h" +#endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ /** @brief Time delay between repeated attempts to initialise the network hardware. */ #ifndef ipINITIALISATION_RETRY_DELAY @@ -460,6 +463,20 @@ static void prvProcessIPEventsAndTimers( void ) /* xQueueReceive() returned because of a normal time-out. */ break; + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + case eSocketOptAddMembership: + case eSocketOptDropMembership: + { + MulticastAction_t * pxMCA = ( MulticastAction_t * ) xReceivedEvent.pvData; + vModifyMulticastMembership( pxMCA, xReceivedEvent.eEventType ); + break; + } + + case eMulticastTimerEvent: + vIPMulticast_HandleTimerEvent(); + break; + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + default: /* Should not get here. */ break; @@ -519,6 +536,11 @@ static void prvIPTask_Initialise( void ) } #endif /* ( ( ipconfigUSE_DNS_CACHE != 0 ) && ( ipconfigUSE_DNS != 0 ) ) */ + /* Init the list that will hold scheduled IGMP reports. */ + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + ( void ) vIPMulticast_Init(); + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + /* Initialisation is complete and events can now be processed. */ xIPTaskInitialised = pdTRUE; } @@ -632,6 +654,16 @@ void vIPNetworkUpCalls( struct xNetworkEndPoint * pxEndPoint ) #endif } + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + + /* Reschedule all multicast reports associated with this end-point. + * Note: countdown is in increments of ipIGMP_TIMER_PERIOD_MS. It's a good idea to spread out all reports a little. + * 200 to 500ms ( xMaxCountdown of 2 - 5 ) should be a good happy medium. If the network we just connected to has a IGMP/MLD querier, + * they will soon ask us for reports anyways, so sending these unsolicited reports is not required. It simply enhances the user + * experience by shortening the time it takes before we begin receiving the multicasts that we care for. */ + vRescheduleAllMulticastReports( pxEndPoint->pxNetworkInterface, 5 ); + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + pxEndPoint->bits.bEndPointUp = pdTRUE_UNSIGNED; #if ( ipconfigUSE_NETWORK_EVENT_HOOK == 1 ) @@ -1321,6 +1353,7 @@ void FreeRTOS_ReleaseUDPPayloadBuffer( void const * pvBuffer ) pxNetworkBuffer->pucEthernetBuffer[ ipSOCKET_OPTIONS_OFFSET ] = FREERTOS_SO_UDPCKSUM_OUT; pxNetworkBuffer->xIPAddress.ulIP_IPv4 = ulIPAddress; pxNetworkBuffer->usPort = ipPACKET_CONTAINS_ICMP_DATA; + pxNetworkBuffer->ucMaximumHops = ipconfigICMP_TIME_TO_LIVE; /* xDataLength is the size of the total packet, including the Ethernet header. */ pxNetworkBuffer->xDataLength = uxTotalLength; @@ -1477,34 +1510,50 @@ eFrameProcessingResult_t eConsiderFrameForProcessing( const uint8_t * const pucE /* The packet was directed to this node - process it. */ eReturn = eProcessBuffer; } - else if( memcmp( xBroadcastMACAddress.ucBytes, pxEthernetHeader->xDestinationAddress.ucBytes, sizeof( MACAddress_t ) ) == 0 ) - { - /* The packet was a broadcast - process it. */ - eReturn = eProcessBuffer; - } - else - #if ( ( ipconfigUSE_LLMNR == 1 ) && ( ipconfigUSE_DNS != 0 ) ) - if( memcmp( xLLMNR_MacAddress.ucBytes, pxEthernetHeader->xDestinationAddress.ucBytes, sizeof( MACAddress_t ) ) == 0 ) + + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + + /* + * With ipconfigSUPPORT_IP_MULTICAST enabled, FreeRTOS+TCP needs access to all + * multicast packets. It is too early to filter them out here because we don't + * know which socket needs which multicast address. Another thing to consider is + * that unless this function returns eProcessBuffer, eApplicationProcessCustomFrameHook() + * will not be called, so handling custom multicast frames would be impossible. + * Note that the broadcast MAC is a type of multicast so the multicast check covers it. + */ + else if( MAC_IS_MULTICAST( pxEthernetHeader->xDestinationAddress.ucBytes ) ) { - /* The packet is a request for LLMNR - process it. */ eReturn = eProcessBuffer; } - else - #endif /* ipconfigUSE_LLMNR */ - #if ( ( ipconfigUSE_MDNS == 1 ) && ( ipconfigUSE_DNS != 0 ) ) - if( memcmp( xMDNS_MacAddress.ucBytes, pxEthernetHeader->xDestinationAddress.ucBytes, sizeof( MACAddress_t ) ) == 0 ) + #else /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + else if( memcmp( xBroadcastMACAddress.ucBytes, pxEthernetHeader->xDestinationAddress.ucBytes, sizeof( MACAddress_t ) ) == 0 ) { - /* The packet is a request for MDNS - process it. */ + /* The packet was a broadcast - process it. */ eReturn = eProcessBuffer; } - else - #endif /* ipconfigUSE_MDNS */ - if( ( pxEthernetHeader->xDestinationAddress.ucBytes[ 0 ] == ipMULTICAST_MAC_ADDRESS_IPv6_0 ) && - ( pxEthernetHeader->xDestinationAddress.ucBytes[ 1 ] == ipMULTICAST_MAC_ADDRESS_IPv6_1 ) ) - { - /* The packet is a request for LLMNR - process it. */ - eReturn = eProcessBuffer; - } + #if ( ( ipconfigUSE_LLMNR == 1 ) && ( ipconfigUSE_DNS != 0 ) ) + else if( memcmp( xLLMNR_MacAddress.ucBytes, pxEthernetHeader->xDestinationAddress.ucBytes, sizeof( MACAddress_t ) ) == 0 ) + { + /* The packet is a request for LLMNR - process it. */ + eReturn = eProcessBuffer; + } + #endif /* ipconfigUSE_LLMNR */ + #if ( ( ipconfigUSE_MDNS == 1 ) && ( ipconfigUSE_DNS != 0 ) ) + else if( memcmp( xMDNS_MacAddress.ucBytes, pxEthernetHeader->xDestinationAddress.ucBytes, sizeof( MACAddress_t ) ) == 0 ) + { + /* The packet is a request for MDNS - process it. */ + eReturn = eProcessBuffer; + } + #endif /* ipconfigUSE_MDNS */ + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) + else if( ( pxEthernetHeader->xDestinationAddress.ucBytes[ 0 ] == ipMULTICAST_MAC_ADDRESS_IPv6_0 ) && + ( pxEthernetHeader->xDestinationAddress.ucBytes[ 1 ] == ipMULTICAST_MAC_ADDRESS_IPv6_1 ) ) + { + /* The packet is an IPv6 multicast - process it. */ + eReturn = eProcessBuffer; + } + #endif /* ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) */ + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ else { /* The packet was not a broadcast, or for this node, just release @@ -2009,6 +2058,13 @@ static eFrameProcessingResult_t prvProcessIPPacket( const IPPacket_t * pxIPPacke break; #endif /* ( ipconfigUSE_IPv6 != 0 ) */ + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) && ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) + case ipPROTOCOL_IGMP: + /* The IP packet contained an IGMP frame. */ + eReturn = eProcessIGMPPacket( pxNetworkBuffer ); + break; + #endif /* ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) && ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) */ + case ipPROTOCOL_UDP: /* The IP packet contained a UDP frame. */ diff --git a/source/FreeRTOS_IP_Multicast.c b/source/FreeRTOS_IP_Multicast.c new file mode 100644 index 0000000000..8f319706db --- /dev/null +++ b/source/FreeRTOS_IP_Multicast.c @@ -0,0 +1,983 @@ +/* + * 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. + */ + +/* ToDo List ( remove the items below as progress is made ) + * - Add FREERTOS_SO_IP_MULTICAST_IF to specify outgoing interface used for multicasts + * - Investigate local reception of outgoing multicasts + * - Write a demo and add to https://github.com/FreeRTOS/FreeRTOS/tree/main/FreeRTOS-Plus/Demo + * - Documentation: Caution about calling FREERTOS_SO_IP_ADD_MEMBERSHIP followed by FREERTOS_SO_IP_DROP_MEMBERSHIP + * in close succession. The DROP may fail because the IP task hasn't handled the ADD yet. + * - Documentation: The values used for FREERTOS_SO_IP_ADD_MEMBERSHIP and FREERTOS_SO_IP_DROP_MEMBERSHIP + * must be exactly the same. This includes the interface pointer! + * - Documentation: Sockets are either IPv4 or v6, same applies to the groups they can subscribe to. + * Topics to discuss over email or in a conference call: + * - Integration with other hardware. For now, only SAME70 target has the proper functions for receive multicasts. + * - Is local reception of outgoing multicasts really needed? In order to get that feature, we need code that handles every + * outgoing multicast as if it were an incoming packet and possibly duplicate it. I don't think this functionality is + * really needed and maybe we should leave it for possible future implementation if really needed by the community. + * This may also be needed if we want a send/receive demo to work on a single device. An alternative would be to have a + * demo that sends to multicast_A and receives multicast_B and then have a PC-based python script that does the opposite. + * Need Help Testing: + * - Multiple interfaces receiving IGMP/MLD queries. Two sockets receiving the same multicast, one socket subscribed on + * all interfaces and another socket subscribed on just one of the interfaces. Are the proper reports getting scheduled and sent? + */ + +/* 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. */ +/* *INDENT-OFF* */ +#if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) +/* *INDENT-ON* */ + +/*-----------------------------------------------------------*/ + +/** @brief A list that holds reports for all multicasts group addresses that this + * host is subscribed to. The list holds entries for IGMP and MLD reports. */ +static List_t prvMulticastReportsList; + +static uint16_t prvIPv4_IGMP_Identification = 0; + +/*-----------------------------------------------------------*/ + +static BaseType_t prvSendIGMPv2( IP_Address_t * pxGroupAddress, + uint8_t ucMessageType, + NetworkEndPoint_t * pxEndPoint ); +static BaseType_t prvSendMLD_Report_v1( IP_Address_t * pxGroupAddress, + NetworkEndPoint_t * pxEndPoint ); + +void prvScheduleMulticastReports( BaseType_t xIsIPv6, + void * pvGroupAddress, + NetworkInterface_t * pxInterface, + BaseType_t xMaxCountdown ); + +static BaseType_t prvSendMulticastReport( MCastReportData_t * pxMRD, + NetworkInterface_t * pxInterface ); + + +/*-----------------------------------------------------------*/ + +void vIPMulticast_Init( void ) +{ + MCastReportData_t * pxMRD; + + vListInitialise( &prvMulticastReportsList ); + #if ( ipconfigIGMP_SNOOPING != 0 ) + extern void vIgmpSnooping_Initialize( void ); + ( void ) vIgmpSnooping_Initialize(); + #endif + + #if ( ipconfigUSE_LLMNR != 0 ) + if( NULL != ( pxMRD = ( MCastReportData_t * ) pvPortMalloc( sizeof( MCastReportData_t ) ) ) ) + { + listSET_LIST_ITEM_OWNER( &( pxMRD->xListItem ), ( void * ) pxMRD ); + pxMRD->pxInterface = NULL; + pxMRD->xMCastGroupAddress.xIs_IPv6 = pdFALSE_UNSIGNED; + pxMRD->xMCastGroupAddress.xIPAddress.ulIP_IPv4 = ipLLMNR_IP_ADDR; + BaseType_t xReportDataConsumed = xEnlistMulticastReport( pxMRD ); + + if( xReportDataConsumed == pdFALSE ) + { + /* This should not happen, but if it does, free the memory that was used */ + vPortFree( pxMRD ); + pxMRD = NULL; + } + } + + #if ( ipconfigUSE_IPv6 == 1 ) + if( NULL != ( pxMRD = ( MCastReportData_t * ) pvPortMalloc( sizeof( MCastReportData_t ) ) ) ) + { + listSET_LIST_ITEM_OWNER( &( pxMRD->xListItem ), ( void * ) pxMRD ); + pxMRD->pxInterface = NULL; + pxMRD->xMCastGroupAddress.xIs_IPv6 = pdTRUE_UNSIGNED; + memcpy( ( void * ) &pxMRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes[ 0 ], &ipLLMNR_IP_ADDR_IPv6.ucBytes[ 0 ], sizeof( IPv6_Address_t ) ); + BaseType_t xReportDataConsumed = xEnlistMulticastReport( pxMRD ); + + if( xReportDataConsumed == pdFALSE ) + { + /* This should not happen, but if it does, free the memory that was used */ + vPortFree( pxMRD ); + pxMRD = NULL; + } + } + #endif /* ( ipconfigUSE_IPv6 == 1) */ + #endif /* ipconfigUSE_LLMNR */ + + #if ( ipconfigUSE_MDNS == 1 ) + if( NULL != ( pxMRD = ( MCastReportData_t * ) pvPortMalloc( sizeof( MCastReportData_t ) ) ) ) + { + listSET_LIST_ITEM_OWNER( &( pxMRD->xListItem ), ( void * ) pxMRD ); + pxMRD->pxInterface = NULL; + pxMRD->xMCastGroupAddress.xIs_IPv6 = pdFALSE_UNSIGNED; + pxMRD->xMCastGroupAddress.xIPAddress.ulIP_IPv4 = ipMDNS_IP_ADDRESS; + BaseType_t xReportDataConsumed = xEnlistMulticastReport( pxMRD ); + + if( xReportDataConsumed == pdFALSE ) + { + /* This should not happen, but if it does, free the memory that was used */ + vPortFree( pxMRD ); + pxMRD = NULL; + } + } + + #if ( ipconfigUSE_IPv6 == 1 ) + if( NULL != ( pxMRD = ( MCastReportData_t * ) pvPortMalloc( sizeof( MCastReportData_t ) ) ) ) + { + listSET_LIST_ITEM_OWNER( &( pxMRD->xListItem ), ( void * ) pxMRD ); + pxMRD->pxInterface = NULL; + pxMRD->xMCastGroupAddress.xIs_IPv6 = pdTRUE_UNSIGNED; + memcpy( ( void * ) &pxMRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes[ 0 ], &ipMDNS_IP_ADDR_IPv6.ucBytes[ 0 ], sizeof( IPv6_Address_t ) ); + BaseType_t xReportDataConsumed = xEnlistMulticastReport( pxMRD ); + + if( xReportDataConsumed == pdFALSE ) + { + /* This should not happen, but if it does, free the memory that was used */ + vPortFree( pxMRD ); + pxMRD = NULL; + } + } + #endif /* ( ipconfigUSE_IPv6 == 1) ) */ + #endif /* ( ipconfigUSE_MDNS == 1) */ + + /* Configure a periodic timer to generate events every 100ms. + * Reuse the timer for MLD, even though MLD uses millisecond resolution. */ + vIPMulticastReportsTimerReload( pdMS_TO_TICKS( igmpTIMING_PERIOD_MS ) ); +} + +#if ( ipconfigUSE_IPv4 == 1 ) + +/** + * @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; + IGMPPacket_t * pxIGMPPacket; + + if( pxNetworkBuffer->xDataLength >= sizeof( IGMPPacket_t ) ) + { + pxIGMPPacket = ( IGMPPacket_t * ) pxNetworkBuffer->pucEthernetBuffer; + + if( igmpIGMP_MEMBERSHIP_QUERY == pxIGMPPacket->xIGMPHeader.ucMessageType ) + { + prvScheduleMulticastReports( pdFALSE, ( void * ) &( pxIGMPPacket->xIGMPHeader.ulGroupAddress ), pxNetworkBuffer->pxInterface, ( uint16_t ) pxIGMPPacket->xIGMPHeader.ucMaxResponseTime ); + } + + #if ( ipconfigIGMP_SNOOPING != 0 ) + extern eFrameProcessingResult_t eApplicationIgmpFrameReceivedHook( NetworkBufferDescriptor_t * pxNetworkBuffer ); + eReturn = eApplicationIgmpFrameReceivedHook( pxNetworkBuffer ); + #endif /* ( ipconfigIGMP_SNOOPING != 0 ) */ + } + + return eReturn; + } +#endif /*( ipconfigUSE_IPv4 == 1 ) */ + +#if ( ipconfigUSE_IPv6 == 1 ) + void vProcessMLDPacket( NetworkBufferDescriptor_t * const pxNetworkBuffer ) + { + /* Note: pxNetworkBuffer->xDataLength was already checked to be >= sizeof( MLDv1_Rx_Packet_t ) */ + const MLDv1_Rx_Packet_t * pxMLD_Packet; + + if( pxNetworkBuffer->xDataLength >= sizeof( MLDv1_Rx_Packet_t ) ) + { + pxMLD_Packet = ( ( const MLDv1_Rx_Packet_t * ) pxNetworkBuffer->pucEthernetBuffer ); + + if( pxMLD_Packet->xMLD.ucTypeOfMessage == ipICMP_MULTICAST_LISTENER_QUERY ) + { + prvScheduleMulticastReports( pdTRUE, ( void * ) ( pxMLD_Packet->xMLD.xGroupAddress.ucBytes ), pxNetworkBuffer->pxInterface, ( FreeRTOS_ntohs( pxMLD_Packet->xMLD.usMaxResponseDelay ) / igmpTIMING_PERIOD_MS ) ); + } + + #if ( ipconfigIGMP_SNOOPING != 0 ) + extern eFrameProcessingResult_t eApplicationIgmpFrameReceivedHook( NetworkBufferDescriptor_t * pxNetworkBuffer ); + ( void ) eApplicationIgmpFrameReceivedHook( pxNetworkBuffer ); + #endif /* ( ipconfigIGMP_SNOOPING != 0 ) */ + } + } +#endif /* ( ipconfigUSE_IPv4 == 1 ) */ + +void vRescheduleAllMulticastReports( NetworkInterface_t * pxInterface, + BaseType_t xMaxCountdown ) +{ + const ListItem_t * pxIterator; + const ListItem_t * xEnd; + uint32_t ulRandom; + MCastReportData_t * pxMRD; + + xMaxCountdown = min( 1, xMaxCountdown ); + xEnd = listGET_END_MARKER( &prvMulticastReportsList ); + + for( pxIterator = ( const ListItem_t * ) listGET_NEXT( xEnd ); + pxIterator != ( const ListItem_t * ) xEnd; + pxIterator = ( const ListItem_t * ) listGET_NEXT( pxIterator ) ) + { + pxMRD = ( MCastReportData_t * ) listGET_LIST_ITEM_OWNER( pxIterator ); + + if( ( pxInterface == pxMRD->pxInterface ) || ( pxMRD->pxInterface == NULL ) ) + { + if( ( pxMRD->xCountDown < 0 ) || ( pxMRD->xCountDown >= xMaxCountdown ) ) + { + if( xApplicationGetRandomNumber( &( ulRandom ) ) == pdFALSE ) + { + /* The random number generator failed. Schedule the report for immediate sending. */ + ulRandom = 0; + } + else + { + /* Everything went well. ulRandom now holds a good random value. */ + } + + /* Generate a random countdown between 0 and usMaxCountdown - 1. */ + pxMRD->xCountDown = ulRandom % xMaxCountdown; + } + else + { + /* The report is already scheduled for sooner than xMaxCountdown. Do nothing. */ + } + } + else + { + /* The multicast report is for a different interface. Do nothing. */ + } + } /* for(;;) iterating over prvMulticastReportsList */ +} + +void prvScheduleMulticastReports( BaseType_t xIsIPv6, + void * pvGroupAddress, + NetworkInterface_t * pxInterface, + BaseType_t xMaxCountdown ) +{ + const ListItem_t * pxIterator; + const ListItem_t * xEnd; + uint32_t ulRandom; + MCastReportData_t * pxMRD; + const IPv6_Address_t * pxIPv6_GroupAddress; + static uint32_t ulNonRandomCounter = 0; /* In case the random number generator fails have a "not so random number" ready. */ + + /* Go through the list of IGMP reports and schedule them. Note, the IGMP event is set at 100ms */ + + /* Sanity enforcement. Technically, there is nothing wrong with trying to schedule reports "right away", + * but having xMaxCountdown = 0 will cause problems with the modulo operation further down. */ + xMaxCountdown = max( 1, xMaxCountdown ); + + xEnd = listGET_END_MARKER( &prvMulticastReportsList ); + + for( pxIterator = ( const ListItem_t * ) listGET_NEXT( xEnd ); + pxIterator != ( const ListItem_t * ) xEnd; + pxIterator = ( const ListItem_t * ) listGET_NEXT( pxIterator ) ) + { + BaseType_t xReschedule = pdFALSE; + pxMRD = ( MCastReportData_t * ) listGET_LIST_ITEM_OWNER( pxIterator ); + + /* Make sure IP versions are the same. */ + if( xIsIPv6 != pxMRD->xMCastGroupAddress.xIs_IPv6 ) + { + continue; + } + + if( xIsIPv6 ) + { + /* IPv6 MLD */ + pxIPv6_GroupAddress = ( const IPv6_Address_t * ) pvGroupAddress; + BaseType_t i, xSpecificQuery = pdFALSE; + + for( i = 0; i < ipSIZE_OF_IPv6_ADDRESS; i++ ) + { + if( pxIPv6_GroupAddress->ucBytes[ i ] != 0 ) + { + xSpecificQuery = pdTRUE; + break; + } + } + + /* Skip ahead for specific queries that do not match this report's address. */ + if( ( xSpecificQuery == pdTRUE ) && + ( ( memcmp( pxIPv6_GroupAddress->ucBytes, pxMRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes, ipSIZE_OF_IPv6_ADDRESS ) ) != 0 ) ) + { + continue; + } + + /* Lastly, only reschedule if the report is for all interfaces or the interface that this query arrived on. */ + if( ( pxMRD->pxInterface == NULL ) || ( pxMRD->pxInterface == pxInterface ) ) + { + xReschedule = pdTRUE; + } + } + else + { + /* IPv4 IGMP*/ + uint32_t * pulQueryGroupAddress; + pulQueryGroupAddress = ( uint32_t * ) pvGroupAddress; + + /* Skip ahead for specific queries that do not match this report's address. */ + if( ( *pulQueryGroupAddress != 0U ) && ( *pulQueryGroupAddress != pxMRD->xMCastGroupAddress.xIPAddress.ulIP_IPv4 ) ) + { + continue; + } + + /* Lastly, only reschedule if the report is for all interfaces or the interface that this query arrived on. */ + if( ( pxMRD->pxInterface == NULL ) || ( pxMRD->pxInterface == pxInterface ) ) + { + xReschedule = pdTRUE; + /* IGMP maximum response time is stored in single byte. Every count represents 0.1s */ + xMaxCountdown = min( 255, xMaxCountdown ); + } + } + + if( xReschedule ) + { + /* This report needs to be scheduled for sending. Remember that it may already be scheduled. + * Negative pxMRD->xCountDown means the report is not scheduled to be sent. If a report is scheduled, and it's + * scheduled time is before usMaxCountdown, there is nothing to be done. If a + * report is scheduled for later than usMaxCountdown, or not scheduled at all, we need + * to schedule it for a random time between 0 and usMaxCountdown - 1. */ + if( ( pxMRD->xCountDown < 0 ) || ( pxMRD->xCountDown >= xMaxCountdown ) ) + { + if( xApplicationGetRandomNumber( &( ulRandom ) ) == pdFALSE ) + { + /* The world is ending, our random number generator has failed. Use a not very random up-counter. */ + ulRandom = ulNonRandomCounter; + ulNonRandomCounter++; + } + + /* Generate a random countdown between 0 and usMaxCountdown - 1. */ + pxMRD->xCountDown = ulRandom % xMaxCountdown; + } + else + { + /* This report is currently scheduled to be sent earlier than usMaxCountdown. + * Do nothing. */ + } + } + } /* for(;;) iterating over prvMulticastReportsList */ +} + +/** + * @brief Helper function for vHandleIGMP_Event. Sends an IGMP or MLD report. + * + * @param[in] pxMRD The struct describing the report. + * @param[in] pxInterface The network interface on which the report should be sent. + */ +static BaseType_t prvSendMulticastReport( MCastReportData_t * pxMRD, + NetworkInterface_t * pxInterface ) +{ + NetworkEndPoint_t * pxEndPoint; + NetworkEndPoint_t * pxSelectedEndPoint = NULL; + BaseType_t xReturn = pdFAIL; + + /* For every end-point of the current interface, pick the first one that is + * usable as an outgoing end-point for the current multicast report. */ + for( pxEndPoint = FreeRTOS_FirstEndPoint( pxInterface ); + pxEndPoint != NULL; + pxEndPoint = FreeRTOS_NextEndPoint( pxInterface, pxEndPoint ) ) + { + /* Skip sending IPv4 reports on IPv6 end-points and vice-versa */ + if( pxMRD->xMCastGroupAddress.xIs_IPv6 != pxEndPoint->bits.bIPv6 ) + { + continue; + } + + /* Skip trying to send on end-points that are not UP */ + if( pxEndPoint->bits.bEndPointUp == pdFALSE ) + { + continue; + } + + /* Remember the last end-point that fits our search */ + pxSelectedEndPoint = pxEndPoint; + + if( pxMRD->xMCastGroupAddress.xIs_IPv6 == pdTRUE ) + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) + + /* IPv6 MLD report and end-point. + * Only send from the link-local end-point. + * https://www.rfc-editor.org/rfc/rfc2710#section-3 + * https://www.rfc-editor.org/rfc/rfc3810#section-5 + * If we can't find a link-local end-point, default to the last seen IPv6 end-point. + * In this case, prvSendMLD_Report_v1() will set the source address to "::" + * https://www.rfc-editor.org/rfc/rfc3810#section-5.2.13 allows it. */ + if( xIPv6_GetIPType( &( pxEndPoint->ipv6_settings.xIPAddress ) ) == eIPv6_LinkLocal ) + { + /* Found a link-local end-point. The search is over. */ + break; + } + else + { + /* This IPv6 end-point is not link-local, keep on searching. */ + } + #endif /* if ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) */ + } + else + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) + + /* IPv4 IGMP report and end-point. + * Note: the way I interpret + * https://www.rfc-editor.org/rfc/rfc2236#section-10 + * https://www.rfc-editor.org/rfc/rfc3376#section-4.2.13 + * https://www.rfc-editor.org/rfc/rfc3376#section-9.2 + * Sending reports from any source IPv4 including 0.0.0.0 is allowed, + * so any IPv4 end-point will do and the search is over. */ + break; + #endif + } + } /* end-point for(;;) */ + + if( pxSelectedEndPoint != NULL ) + { + if( pxMRD->xMCastGroupAddress.xIs_IPv6 == pdTRUE ) + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) + xReturn = prvSendMLD_Report_v1( &( pxMRD->xMCastGroupAddress.xIPAddress ), pxSelectedEndPoint ); + #endif + } + else + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) + xReturn = prvSendIGMPv2( &( pxMRD->xMCastGroupAddress.xIPAddress ), igmpIGMP_MEMBERSHIP_REPORT_V2, pxSelectedEndPoint ); + #endif + } + } + + return xReturn; +} + +void vIPMulticast_HandleTimerEvent( void ) +{ + /* 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( &prvMulticastReportsList ); + MCastReportData_t * pxMRD; + + for( pxIterator = ( const ListItem_t * ) listGET_NEXT( xEnd ); pxIterator != ( const ListItem_t * ) xEnd; pxIterator = ( const ListItem_t * ) listGET_NEXT( pxIterator ) ) + { + pxMRD = ( MCastReportData_t * ) listGET_LIST_ITEM_OWNER( pxIterator ); + + /* Decrement down to 0. Decrementing from 0 to -1 triggers the sending of the scheduled report. */ + if( pxMRD->xCountDown > 0 ) + { + pxMRD->xCountDown--; + } + else if( pxMRD->xCountDown == 0 ) + { + /* Time to send a report...*/ + /* If the interface is NULL, the report should be sent on all interfaces. */ + if( pxMRD->pxInterface == NULL ) + { + NetworkInterface_t * pxInterface; + + /* Go through all interfaces. */ + for( pxInterface = FreeRTOS_FirstNetworkInterface(); + pxInterface != NULL; + pxInterface = FreeRTOS_NextNetworkInterface( pxInterface ) ) + { + if( prvSendMulticastReport( pxMRD, pxInterface ) == pdPASS ) + { + /* Success on any interface counts... */ + /* ToDo: This should change once I add per-interface lists. */ + pxMRD->xCountDown = ipconfigPERIODIC_MULTICAST_REPORT_INTERVAL; + } + } + } + else + { + /* The report is assigned to a specific interface */ + if( prvSendMulticastReport( pxMRD, pxMRD->pxInterface ) == pdPASS ) + { + /* Success */ + pxMRD->xCountDown = ipconfigPERIODIC_MULTICAST_REPORT_INTERVAL; + } + else + { + /* The report could not be sent. Doing nothing here will result in a retry the next time this event is called. */ + } + } + } + else + { + /* ( pxMRD->xCountDown < 0 ) + * Do nothing. */ + } + } /* for(;;) loop iterating over the list of multicast reports */ + + #if ( ipconfigIGMP_SNOOPING != 0 ) + extern void vApplicationIgmpEventHook( void ); + ( void ) vApplicationIgmpEventHook(); + #endif +} + +/** + * @brief Send an IGMP/MLD event. + * + * @return pdPASS or pdFAIL, depending on whether xSendEventStructToIPTask() + * succeeded. + */ +BaseType_t xSendMulticastTimerEvent( void ) +{ + IPStackEvent_t xEventMessage; + const TickType_t uxDontBlock = 0U; + uintptr_t uxOption = 0U; + + xEventMessage.eEventType = eMulticastTimerEvent; + xEventMessage.pvData = ( void * ) uxOption; + + return xSendEventStructToIPTask( &xEventMessage, uxDontBlock ); +} + + +/** + * @brief Locates the IGMP/MLD report for this group. Decrements its socket counter and + * if the counter becomes zero, removes the report from the list and frees it. + * + * @param[in] pxInterface: The network interface on which the report is scheduled. + * @param[in] pxMulticastAddress: The multicast group descriptor the specifies the group and interface. + * @param[in] uxIsIPv6: pdTRUE if pxMulticastAddress is a pointer to an IPv6 group address, pdFALSE otherwise. + */ +void vDelistMulticastReport( NetworkInterface_t * pxInterface, + IP_Address_t * pxMulticastAddress, + UBaseType_t uxIsIPv6 ) +{ + const ListItem_t * pxIterator; + const ListItem_t * xEnd = listGET_END_MARKER( &prvMulticastReportsList ); + MCastReportData_t * pxMRD; + + #ifdef FreeRTOS_debug_printf + { + uint8_t ucMCastGroupAddressString[ ( 45 + 1 ) ] = { 0 }; + + if( uxIsIPv6 == pdTRUE_UNSIGNED ) + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) + FreeRTOS_inet_ntop6( ( void * ) &pxMulticastAddress->xIP_IPv6.ucBytes[ 0 ], ucMCastGroupAddressString, ( 45 + 1 ) ); + FreeRTOS_debug_printf( ( "vDelistMulticastReport %s", ucMCastGroupAddressString ) ); + #endif + } + else + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) + FreeRTOS_inet_ntop4( ( void * ) &pxMulticastAddress->ulIP_IPv4, ucMCastGroupAddressString, ( 45 + 1 ) ); + FreeRTOS_debug_printf( ( "vDelistMulticastReport %s", ucMCastGroupAddressString ) ); + #endif + } + } + #endif /* FreeRTOS_debug_printf */ + + for( pxIterator = ( const ListItem_t * ) listGET_NEXT( xEnd ); + pxIterator != ( const ListItem_t * ) xEnd; + pxIterator = ( const ListItem_t * ) listGET_NEXT( pxIterator ) ) + { + pxMRD = ( MCastReportData_t * ) listGET_LIST_ITEM_OWNER( pxIterator ); + + if( pxMRD->pxInterface == pxInterface ) + { + if( pxMRD->xMCastGroupAddress.xIs_IPv6 == pdTRUE_UNSIGNED ) + { + if( 0 == memcmp( pxMRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes, pxMulticastAddress->xIP_IPv6.ucBytes, ipSIZE_OF_IPv6_ADDRESS ) ) + { + break; + } + else + { + /* Address is different Do Nothing. */ + } + } + else + { + if( pxMRD->xMCastGroupAddress.xIPAddress.ulIP_IPv4 == pxMulticastAddress->ulIP_IPv4 ) + { + break; + } + else + { + /* Address is different Do Nothing. */ + } + } + } + else + { + /* Network interface is different Do Nothing. */ + } + } /* for(;;) over prvMulticastReportsList */ + + if( pxIterator != xEnd ) + { + /* Found a match. */ + if( pxMRD->xNumSockets > 0 ) + { + pxMRD->xNumSockets--; + } + + if( 0 == pxMRD->xNumSockets ) + { + if( pxMRD->xMCastGroupAddress.xIs_IPv6 ) + { + /* Todo: Handle leaving IPv6 multicast group. */ + } + else + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) + prvSendIGMPv2( &( pxMRD->xMCastGroupAddress.xIPAddress ), igmpIGMP_LEAVE_GROUP, FreeRTOS_FirstEndPoint( NULL ) ); + #endif + } + + ( void ) uxListRemove( &pxMRD->xListItem ); + vPortFree( pxMRD ); + } + } +} + +/** + * @brief Adds an IGMP/MLD report to the global list of reports. + * + * @param[in] pNewEntry: The multicast group data that describes the report. + */ +BaseType_t xEnlistMulticastReport( struct MCastReportDescription * pNewEntry ) +{ + const ListItem_t * pxIterator; + const ListItem_t * xEnd = listGET_END_MARKER( &prvMulticastReportsList ); + MCastReportData_t * pxMRD; + UBaseType_t bFoundDuplicate = pdFALSE_UNSIGNED; + + configASSERT( pNewEntry != NULL ); + + #ifdef FreeRTOS_debug_printf + { + uint8_t ucMCastGroupAddressString[ ( 45 + 1 ) ] = { 0 }; + + if( pNewEntry->xMCastGroupAddress.xIs_IPv6 == pdTRUE_UNSIGNED ) + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) + FreeRTOS_inet_ntop6( ( void * ) &pNewEntry->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes[ 0 ], ucMCastGroupAddressString, ( 45 + 1 ) ); + FreeRTOS_debug_printf( ( "xEnlistMulticastReport %s", ucMCastGroupAddressString ) ); + #endif + } + else + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) + FreeRTOS_inet_ntop4( ( void * ) &pNewEntry->xMCastGroupAddress.xIPAddress.ulIP_IPv4, ucMCastGroupAddressString, ( 45 + 1 ) ); + FreeRTOS_debug_printf( ( "xEnlistMulticastReport %s", ucMCastGroupAddressString ) ); + #endif + } + } + #endif /* FreeRTOS_debug_printf */ + + /* Try to find a duplicate entry. A duplicate is a report entry with the same IP address AND the same + * network interface. It is important that we include the interface in the check as well because + * we may have multiple sockets subscribed to the same address but on different interfaces. + * Example: Socket_A subsribes to ff02::17 on eth0. Later Socket_B subscribes to ff02::17 on ALL interfaces. + * The reports for the two sockets are scheduled differently, so they cannot be bundled into a single entry. */ + for( pxIterator = ( const ListItem_t * ) listGET_NEXT( xEnd ); + pxIterator != ( const ListItem_t * ) xEnd; + pxIterator = ( const ListItem_t * ) listGET_NEXT( pxIterator ) ) + { + pxMRD = ( MCastReportData_t * ) listGET_LIST_ITEM_OWNER( pxIterator ); + + if( pxMRD->pxInterface == pNewEntry->pxInterface ) + { + if( pxMRD->xMCastGroupAddress.xIs_IPv6 == pdTRUE_UNSIGNED ) + { + if( memcmp( &pxMRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes[ 0 ], &pNewEntry->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes[ 0 ], sizeof( IPv6_Address_t ) ) == 0 ) + { + bFoundDuplicate = pdTRUE_UNSIGNED; + break; + } + else + { + /* Address is different Do Nothing. */ + } + } + else + { + if( pxMRD->xMCastGroupAddress.xIPAddress.ulIP_IPv4 == pNewEntry->xMCastGroupAddress.xIPAddress.ulIP_IPv4 ) + { + bFoundDuplicate = pdTRUE_UNSIGNED; + break; + } + else + { + /* Address is different Do Nothing. */ + } + } + } + else + { + /* Network interface is different Do Nothing. */ + } + } /* for(;;) over prvMulticastReportsList */ + + if( bFoundDuplicate == pdTRUE_UNSIGNED ) + { + /* 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. */ + pxMRD->xNumSockets++; + } + else + { + /* Not found. */ + pNewEntry->xNumSockets = 1; + + /* Schedule an unsolicited report to quickly inform IGMP snooping switches that we want + * to receive this multicast group. */ + pNewEntry->xCountDown = 0; + vListInsertEnd( &prvMulticastReportsList, &( pNewEntry->xListItem ) ); + } + + /* Inform the caller whether we consumed the item or not. */ + return ( bFoundDuplicate == pdTRUE_UNSIGNED ) ? pdFALSE : pdTRUE; +} + + + +#if ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) + static BaseType_t prvSendIGMPv2( IP_Address_t * pxGroupAddress, + uint8_t ucMessageType, + NetworkEndPoint_t * pxEndPoint ) + { + NetworkBufferDescriptor_t * pxNetworkBuffer; + IGMPPacket_t * pxIGMPPacket; + portBASE_TYPE xReturn = pdFAIL; + + configASSERT( pxEndPoint != NULL ); + + if( NULL != ( pxNetworkBuffer = pxGetNetworkBufferWithDescriptor( sizeof( IGMPPacket_t ), 0 ) ) ) + { + pxIGMPPacket = ( IGMPPacket_t * ) pxNetworkBuffer->pucEthernetBuffer; + uint16_t usEthType = ipIPv4_FRAME_TYPE; + + /* Fill out the Ethernet header */ + vSetMultiCastIPv4MacAddress( pxGroupAddress->ulIP_IPv4, &pxIGMPPacket->xEthernetHeader.xDestinationAddress ); + memcpy( ( void * ) pxIGMPPacket->xEthernetHeader.xSourceAddress.ucBytes, ( void * ) pxEndPoint->xMACAddress.ucBytes, ( size_t ) ipMAC_ADDRESS_LENGTH_BYTES ); + memcpy( ( void * ) &pxIGMPPacket->xEthernetHeader.usFrameType, ( void * ) &usEthType, sizeof( uint16_t ) ); + + + IPHeader_t * pxIPHeader; + + pxIPHeader = &( pxIGMPPacket->xIPHeader ); + + pxIGMPPacket->xIGMPHeader.ucMessageType = ucMessageType; + pxIGMPPacket->xIGMPHeader.ucMaxResponseTime = 0; + pxIGMPPacket->xIGMPHeader.ulGroupAddress = pxGroupAddress->ulIP_IPv4; + + pxIPHeader->ulDestinationIPAddress = pxGroupAddress->ulIP_IPv4; + 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( prvIPv4_IGMP_Identification ); + prvIPv4_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 */ + pxNetworkBuffer->xDataLength = sizeof( IGMPPacket_t ); + + #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 + + xReturn = pdPASS; + } + else + { + /* Could not allocate a buffer */ + } + + return xReturn; + } + +#endif /* ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) */ + +#if ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) + +/** + * @brief Send an ICMPv6 Multicast Listener Report version 1. + * + * @param[in] pxGroupAddress The multicast group address to that the report is for. + * @param[in] pxEndPoint The outgoing end-point. + * + * @return pdPASS/pdFAIL + */ + static BaseType_t prvSendMLD_Report_v1( IP_Address_t * pxGroupAddress, + NetworkEndPoint_t * pxEndPoint ) + { + NetworkBufferDescriptor_t * pxNetworkBuffer; + MLDv1_Tx_Packet_t * pxMLRPacket; + BaseType_t xReturn = pdFAIL; + + configASSERT( pxEndPoint != NULL ); + + if( ( ( pxNetworkBuffer = pxGetNetworkBufferWithDescriptor( sizeof( MLDv1_Tx_Packet_t ), 0 ) ) != NULL ) ) + { + pxNetworkBuffer->pxEndPoint = pxEndPoint; + + configASSERT( pxEndPoint->pxNetworkInterface != NULL ); + + /* MISRA Ref 11.3.1 [Misaligned access] */ + /* More details at: https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/blob/main/MISRA.md#rule-113 */ + /* coverity[misra_c_2012_rule_11_3_violation] */ + pxMLRPacket = ( ( MLDv1_Tx_Packet_t * ) pxNetworkBuffer->pucEthernetBuffer ); + pxNetworkBuffer->xDataLength = sizeof( MLDv1_Tx_Packet_t ); + + /* MLD Reports version 1 are sent to the MAC address corresponding to the multicast address. */ + vSetMultiCastIPv6MacAddress( &( pxGroupAddress->xIP_IPv6 ), &pxMLRPacket->xEthernetHeader.xDestinationAddress ); + ( void ) memcpy( pxMLRPacket->xEthernetHeader.xSourceAddress.ucBytes, pxEndPoint->xMACAddress.ucBytes, ipMAC_ADDRESS_LENGTH_BYTES ); + pxMLRPacket->xEthernetHeader.usFrameType = ipIPv6_FRAME_TYPE; /* 12 + 2 = 14 */ + + pxMLRPacket->xIPHeader.ucVersionTrafficClass = 0x60; + pxMLRPacket->xIPHeader.ucTrafficClassFlow = 0; + pxMLRPacket->xIPHeader.usFlowLabel = 0; + + pxMLRPacket->xIPHeader.usPayloadLength = FreeRTOS_htons( sizeof( ICMPHeader_IPv6_t ) ); + pxMLRPacket->xIPHeader.ucNextHeader = ipIPv6_EXT_HEADER_HOP_BY_HOP; + pxMLRPacket->xIPHeader.ucHopLimit = 1; + + if( xIPv6_GetIPType( &( pxEndPoint->ipv6_settings.xIPAddress ) ) == eIPv6_LinkLocal ) + { + /* This end-point has a link-local address. Use it. */ + ( void ) memcpy( pxMLRPacket->xIPHeader.xSourceAddress.ucBytes, pxEndPoint->ipv6_settings.xIPAddress.ucBytes, ipSIZE_OF_IPv6_ADDRESS ); + } + else + { + /* This end-point doesn't have a link-local address or it still has not */ + /* completed it's DAD ( Duplicate Address Detection ) */ + /* Use "::" as described in https://www.rfc-editor.org/rfc/rfc3810#section-5.2.13 */ + memset( pxMLRPacket->xIPHeader.xSourceAddress.ucBytes, 0x00, ipSIZE_OF_IPv6_ADDRESS ); + } + + ( void ) memcpy( pxMLRPacket->xIPHeader.xDestinationAddress.ucBytes, pxGroupAddress->xIP_IPv6.ucBytes, ipSIZE_OF_IPv6_ADDRESS ); + + /* Fill out the Hop By Hop Router Alert option extension header */ + pxMLRPacket->xRAOption.ucNextHeader = ipPROTOCOL_ICMP_IPv6; + pxMLRPacket->xRAOption.ucHeaderExtLength = 0; + pxMLRPacket->xRAOption.xRouterAlert.ucType = ipHOP_BY_HOP_ROUTER_ALERT; + pxMLRPacket->xRAOption.xRouterAlert.ucLength = sizeof( uint16_t ); + pxMLRPacket->xRAOption.xRouterAlert.usValue = FreeRTOS_htons( ipROUTER_ALERT_VALUE_MLD ); + pxMLRPacket->xRAOption.xPadding.ucType = ipHOP_BY_HOP_PadN; + pxMLRPacket->xRAOption.xPadding.ucLength = 0; /* in multiples of 8 octets, not counting the first 8 */ + + pxMLRPacket->xMLD.ucTypeOfMessage = ipICMP_MULTICAST_LISTENER_REPORT_V1; + pxMLRPacket->xMLD.ucTypeOfService = 0; + pxMLRPacket->xMLD.usMaxResponseDelay = FreeRTOS_htons( 0 ); + pxMLRPacket->xMLD.usReserved = FreeRTOS_htons( 0 ); + ( void ) memcpy( pxMLRPacket->xMLD.xGroupAddress.ucBytes, pxGroupAddress->xIP_IPv6.ucBytes, ipSIZE_OF_IPv6_ADDRESS ); + + #if ( ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM == 0 ) + { + /* calculate the ICMPv6 checksum for outgoing package */ + ( void ) usGenerateProtocolChecksum( pxNetworkBuffer->pucEthernetBuffer, pxNetworkBuffer->xDataLength, pdTRUE ); + } + #else + { + /* Many EMAC peripherals will only calculate the ICMP checksum + * correctly if the field is nulled beforehand. */ + pxMLRPacket->xMLD.usChecksum = 0; + } + #endif + + if( pxEndPoint->pxNetworkInterface->pfOutput != NULL ) + { + ( void ) pxEndPoint->pxNetworkInterface->pfOutput( pxEndPoint->pxNetworkInterface, pxNetworkBuffer, pdTRUE ); + xReturn = pdPASS; + } + } + + return xReturn; + } + +#endif /* ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) */ + + +/************************************************************************/ +/* Test code below this point */ +/************************************************************************/ + + +/*-----------------------------------------------------------*/ + + +/* *INDENT-OFF* */ +#endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ +/* *INDENT-ON* */ diff --git a/source/FreeRTOS_IP_Timers.c b/source/FreeRTOS_IP_Timers.c index 09b2bd6b39..ca4408cd63 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 ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + #include "FreeRTOS_IGMP.h" +#endif /*-----------------------------------------------------------*/ /** @brief 'xAllNetworksUp' becomes pdTRUE when all network interfaces are initialised @@ -110,6 +113,11 @@ static IPTimer_t xARPTimer; static IPTimer_t xDNSTimer; #endif +#if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + /** @brief IGMP timer. Used for sending scheduled IGMP Reports */ + static IPTimer_t xMulticastTimer; +#endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + /** @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 ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + { + if( xMulticastTimer.ulRemainingTime < uxMaximumSleepTime ) + { + uxMaximumSleepTime = xMulticastTimer.ulRemainingTime; + } + } + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + #if ( ipconfigDNS_USE_CALLBACKS != 0 ) { if( xDNSTimer.bActive != pdFALSE_UNSIGNED ) @@ -311,6 +328,16 @@ void vCheckNetworkTimers( void ) vSocketListenNextTime( NULL ); #endif /* ipconfigUSE_TCP == 1 */ + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + { + /* Is it time to send any IGMP reports? */ + if( prvIPTimerCheck( &xMulticastTimer ) != pdFALSE ) + { + ( void ) xSendMulticastTimerEvent(); + } + } + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + /* Is it time to trigger the repeated NetworkDown events? */ if( xAllNetworksUp == pdFALSE ) { @@ -411,6 +438,20 @@ void vARPTimerReload( TickType_t xTime ) /*-----------------------------------------------------------*/ +#if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + +/** + * @brief Reload the IGMP timer. + * + * @param[in] xTicks: The reload value. Should be pdMS_TO_TICKS( igmpMULTICAST_EVENT_PERIOD_MS ) + */ + void vIPMulticastReportsTimerReload( TickType_t xTicks ) + { + prvIPTimerReload( &xMulticastTimer, xTicks ); + } +#endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ +/*-----------------------------------------------------------*/ + #if ( ipconfigDNS_USE_CALLBACKS != 0 ) /** diff --git a/source/FreeRTOS_IPv6.c b/source/FreeRTOS_IPv6.c index 0e8f85ea19..81369d2ced 100644 --- a/source/FreeRTOS_IPv6.c +++ b/source/FreeRTOS_IPv6.c @@ -389,7 +389,7 @@ BaseType_t xIsIPv6AllowedMulticast( const IPv6_Address_t * pxIPAddress ) /** * @brief Compares 2 IPv6 addresses and checks if the one * on the left can handle the one on right. Note that 'xCompareIPv6_Address' will also check if 'pxRight' is - * the special unicast address: ff02::1:ffnn:nnnn, where nn:nnnn are + * the special multicast address: ff02::1:ffnn:nnnn, where nn:nnnn are * the last 3 bytes of the IPv6 address. * * @param[in] pxLeft First IP address. @@ -413,6 +413,7 @@ BaseType_t xCompareIPv6_Address( const IPv6_Address_t * pxLeft, ( pxRight->ucBytes[ 12 ] == 0xffU ) ) { /* This is an LLMNR address. */ + /* _EP_: I don't think this is LLMNR. Did someone mean to say "the solicited-node multicast address" ? */ xResult = memcmp( &( pxLeft->ucBytes[ 13 ] ), &( pxRight->ucBytes[ 13 ] ), 3 ); } else diff --git a/source/FreeRTOS_IPv6_Utils.c b/source/FreeRTOS_IPv6_Utils.c index b0792f9223..26b19dc58d 100644 --- a/source/FreeRTOS_IPv6_Utils.c +++ b/source/FreeRTOS_IPv6_Utils.c @@ -348,6 +348,77 @@ void vManageSolicitedNodeAddress( const struct xNetworkEndPoint * pxEndPoint, pxEndPoint->pxNetworkInterface->pfRemoveAllowedMAC( pxEndPoint->pxNetworkInterface, xMACAddress.ucBytes ); } } + + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + { + MCastReportData_t * pxMRD; + + /* This is sub-optimal. We only need an IP_Address_t on network DOWN, however in the + * interest of having one piece of common code, we'll have a pointer that points to either + * xIPv6Address or &pxMRD->xMCastGroupAddress.xIPAddress */ + IP_Address_t * pxIPv6Address; + IP_Address_t xIPv6Address; + + if( xNetworkGoingUp == pdTRUE ) + { + /* We will need an MLD report structure a few lines down, so allocate one. */ + pxMRD = ( MCastReportData_t * ) pvPortMalloc( sizeof( MCastReportData_t ) ); + + if( pxMRD == NULL ) + { + break; + } + + pxIPv6Address = &( pxMRD->xMCastGroupAddress.xIPAddress ); + } + else + { + pxIPv6Address = &xIPv6Address; + } + + /* Generate the solicited-node multicast address. It has the form of + * ff02::1:ffnn:nnnn, where nn:nnnn are the last 3 bytes of the IPv6 address. */ + do + { + uint8_t * pucTarget = pxIPv6Address->xIP_IPv6.ucBytes; + uint8_t * pucSource = pxEndPoint->ipv6_settings.xIPAddress.ucBytes; + + pucTarget[ 0 ] = 0xFFU; + pucTarget[ 1 ] = 0x02U; + ( void ) memset( &( pucTarget[ 2 ] ), 0x00, 9 ); + pucTarget[ 11 ] = 0x01U; + pucTarget[ 12 ] = 0xFFU; + pucTarget[ 13 ] = pucSource[ 13 ]; + pucTarget[ 14 ] = pucSource[ 14 ]; + pucTarget[ 15 ] = pucSource[ 15 ]; + } while( pdFALSE ); + + if( xNetworkGoingUp == pdTRUE ) + { + /* Network going UP. Generate and enlist an MLD report for the solicited node multicast address. */ + listSET_LIST_ITEM_OWNER( &( pxMRD->xListItem ), ( void * ) pxMRD ); + pxMRD->pxInterface = pxEndPoint->pxNetworkInterface; + pxMRD->xMCastGroupAddress.xIs_IPv6 = pdTRUE_UNSIGNED; + /* pxMRD->xMCastGroupAddress.xIPAddress was filled out through pxIPv6Address above.*/ + + if( xEnlistMulticastReport( pxMRD ) == pdFALSE ) + { + /* The report data was not consumed. This is odd since we are dealing with + * the solicited-node multicast address and there should not have been a report + * enlisted for it already. Anyway, keep things clean. */ + vPortFree( pxMRD ); + pxMRD = NULL; + } + } + else + { + /* Network going DOWN. De-list the MLD report for the solicited node multicast address. + * Todo: Check if there are other places that this same action needs to be taken. + * Places like when DHCPv6 or RA releases an address. Needs further investigation. */ + vDelistMulticastReport( pxEndPoint->pxNetworkInterface, pxIPv6Address, pdTRUE_UNSIGNED ); + } + } + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ } while( pdFALSE ); } /*-----------------------------------------------------------*/ diff --git a/source/FreeRTOS_ND.c b/source/FreeRTOS_ND.c index d5c55e478e..c130ab4f7e 100644 --- a/source/FreeRTOS_ND.c +++ b/source/FreeRTOS_ND.c @@ -805,6 +805,7 @@ ( void ) memcpy( pxNetworkBuffer->xIPAddress.xIP_IPv6.ucBytes, pxIPAddress->ucBytes, ipSIZE_OF_IPv6_ADDRESS ); /* Let vProcessGeneratedUDPPacket() know that this is an ICMP packet. */ pxNetworkBuffer->usPort = ipPACKET_CONTAINS_ICMP_DATA; + pxNetworkBuffer->ucMaximumHops = ipconfigICMP_TIME_TO_LIVE; /* 'uxPacketLength' is initialised due to the flow of the program. */ pxNetworkBuffer->xDataLength = uxPacketLength; @@ -1138,6 +1139,26 @@ break; #endif /* ( ipconfigUSE_RA != 0 ) */ + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + case ipICMP_MULTICAST_LISTENER_QUERY: + case ipICMP_MULTICAST_LISTENER_REPORT_V1: + case ipICMP_MULTICAST_LISTENER_REPORT_V2: + + /* Note: prvProcessIPPacket() stripped the extension headers, so this packet struct is defined without them and they cannot be checked. + * per RFC, MLD packets must use the RouterAlert option in a Hop By Hop extension header. */ + /* All MLD packets are at least as large as a v1 query packet. */ + uxNeededSize = ( size_t ) ( ipSIZE_OF_ETH_HEADER + ipSIZE_OF_IPv6_HEADER + ipSIZE_OF_ICMPv6_HEADER ); + + if( uxNeededSize > pxNetworkBuffer->xDataLength ) + { + FreeRTOS_printf( ( "Too small\n" ) ); + break; + } + + vProcessMLDPacket( pxNetworkBuffer ); + break; + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + default: /* All possible values are included here above. */ break; diff --git a/source/FreeRTOS_Sockets.c b/source/FreeRTOS_Sockets.c index 48e3a63dfa..c552c9c2cb 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 ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + #include "FreeRTOS_IGMP.h" +#endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ #if ( ipconfigUSE_TCP_MEM_STATS != 0 ) #include "tcp_mem_stats.h" #endif @@ -369,6 +371,15 @@ static int32_t prvSendTo_ActualSend( const FreeRTOS_Socket_t * pxSocket, static void vTCPNetStat_TCPSocket( const FreeRTOS_Socket_t * pxSocket ); #endif +#if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + static BaseType_t prvSetOptionMulticast( Socket_t xSocket, + int32_t lLevel, + int32_t lOptionName, + const void * pvOptionValue, + size_t uxOptionLength ); + static void prvDropMulticastMembership( FreeRTOS_Socket_t * pxSocket ); +#endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + /*-----------------------------------------------------------*/ /** @brief The list that contains mappings between sockets and port numbers. @@ -737,6 +748,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 ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + pxSocket->u.xUDP.ucMulticastMaxHops = ipconfigMULTICAST_DEFAULT_TTL; + memset( &( pxSocket->u.xUDP.xMulticastAddress ), 0, sizeof( pxSocket->u.xUDP.xMulticastAddress ) ); + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ } #if ( ipconfigUSE_TCP == 1 ) @@ -1412,12 +1428,38 @@ static int32_t prvSendUDPPacket( const FreeRTOS_Socket_t * pxSocket, #if ( ipconfigUSE_IPv6 != 0 ) case FREERTOS_AF_INET6: ( void ) xSend_UDP_Update_IPv6( pxNetworkBuffer, pxDestinationAddress ); + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + /* _EP_ Verify if using xIsIPv6AllowedMulticast is appropriate or if we need to check for any multicast */ + if( xIsIPv6AllowedMulticast( &( pxDestinationAddress->sin_address.xIP_IPv6 ) ) ) + { + /* Sending a multicast, so use whatever outgoing multicast HopLimit value was configured. */ + pxNetworkBuffer->ucMaximumHops = ( uint8_t ) pxSocket->u.xUDP.ucMulticastMaxHops; + } + else + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + { + /* If multicasts are not enabled or if the destination is an unicast, use the default TTL value. */ + pxNetworkBuffer->ucMaximumHops = ipconfigUDP_TIME_TO_LIVE; + } break; #endif /* ( ipconfigUSE_IPv6 != 0 ) */ #if ( ipconfigUSE_IPv4 != 0 ) case FREERTOS_AF_INET4: ( void ) xSend_UDP_Update_IPv4( pxNetworkBuffer, pxDestinationAddress ); + + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + if( xIsIPv4Multicast( pxDestinationAddress->sin_address.ulIP_IPv4 ) ) + { + /* Sending a multicast, so use whatever outgoing multicast TTL value was configured. */ + pxNetworkBuffer->ucMaximumHops = ( uint8_t ) pxSocket->u.xUDP.ucMulticastMaxHops; + } + else + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + { + /* If multicasts are not enabled or if the destination is an unicast, use the default TTL value. */ + pxNetworkBuffer->ucMaximumHops = ipconfigUDP_TIME_TO_LIVE; + } break; #endif /* ( ipconfigUSE_IPv4 != 0 ) */ @@ -2141,6 +2183,13 @@ void * vSocketClose( FreeRTOS_Socket_t * pxSocket ) #endif /* ipconfigETHERNET_DRIVER_FILTERS_PACKETS */ } + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + if( pxSocket->ucProtocol == ipPROTOCOL_UDP ) + { + prvDropMulticastMembership( pxSocket ); + } + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + /* Now the socket is not bound the list of waiting packets can be * drained. */ if( pxSocket->ucProtocol == ( uint8_t ) FREERTOS_IPPROTO_UDP ) @@ -2938,6 +2987,14 @@ BaseType_t FreeRTOS_setsockopt( Socket_t xSocket, break; #endif /* ipconfigUSE_TCP == 1 */ + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + case FREERTOS_SO_IP_MULTICAST_TTL: + case FREERTOS_SO_IP_ADD_MEMBERSHIP: + case FREERTOS_SO_IP_DROP_MEMBERSHIP: + xReturn = prvSetOptionMulticast( xSocket, lLevel, lOptionName, pvOptionValue, uxOptionLength ); + break; + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + default: /* No other options are handled. */ xReturn = -pdFREERTOS_ERRNO_ENOPROTOOPT; @@ -6337,3 +6394,491 @@ BaseType_t FreeRTOS_GetIPType( ConstSocket_t xSocket ) #endif /* ipconfigSUPPORT_SELECT_FUNCTION */ #endif /* 0 */ + +#if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + +/** + * @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. + */ + static BaseType_t prvSetOptionMulticast( 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 ) || ( pvOptionValue == NULL ) ) + { + 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 */ + } + + /* Set the new TTL/HOPS value. */ + pxSocket->u.xUDP.ucMulticastMaxHops = *( ( uint8_t * ) pvOptionValue ); + + xReturn = pdFREERTOS_ERRNO_NONE; + break; + + case FREERTOS_SO_IP_ADD_MEMBERSHIP: + { + IP_MReq_t * pxMReq = ( IP_MReq_t * ) pvOptionValue; + IPStackEvent_t xSockOptsEvent = { eSocketOptAddMembership, NULL }; + + if( ( pxSocket->ucProtocol != ( uint8_t ) FREERTOS_IPPROTO_UDP ) || + ( uxOptionLength != sizeof( IP_MReq_t ) ) ) + { + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + + if( pxSocket->bits.bIsIPv6 == pdTRUE ) + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) + if( pdFALSE == xIsIPv6AllowedMulticast( &( pxMReq->xMulticastGroup.xIP_IPv6 ) ) ) + { + /* Invalid multicast group address */ + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + #else + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + #endif + } + else + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) + if( pdFALSE == xIsIPv4Multicast( pxMReq->xMulticastGroup.ulIP_IPv4 ) ) + { + /* Invalid multicast group address */ + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + #else + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + #endif + } + + /* Check if the interface is pointer to valid network interface or NULL */ + if( pxMReq->pxMulticastNetIf != NULL ) + { + NetworkInterface_t * pxNetIf; + + for( pxNetIf = FreeRTOS_FirstNetworkInterface(); pxNetIf != NULL; pxNetIf = pxNetIf->pxNext ) + { + if( pxNetIf == pxMReq->pxMulticastNetIf ) + { + break; + } + } + + if( pxNetIf == NULL ) + { + /* No matching interface found */ + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + } + + /* From here on down, pxMReq->pxMulticastNetIf is either NULL or a pointer to a valid interface */ + + /* Allocate some RAM to remember what the user code is requesting */ + MulticastAction_t * pxMCA = ( MulticastAction_t * ) pvPortMalloc( sizeof( MulticastAction_t ) ); + MCastReportData_t * pxMRD = ( MCastReportData_t * ) pvPortMalloc( sizeof( MCastReportData_t ) ); + + if( NULL == pxMCA ) + { + xReturn = -pdFREERTOS_ERRNO_ENOMEM; + break; + } + + if( NULL == pxMRD ) + { + xReturn = -pdFREERTOS_ERRNO_ENOMEM; + vPortFree( pxMCA ); + pxMCA = NULL; + break; + } + + pxMCA->pxSocket = pxSocket; + pxMCA->pxInterface = pxMReq->pxMulticastNetIf; + + /* Store the multicast address in the action and report structs. + * Note: multicast report fields like xNumSockets and xCountDown don't need to be initialized. They will + * be set to their proper values if this reports is added to the global list. */ + if( pxSocket->bits.bIsIPv6 == pdTRUE ) + { + memcpy( pxMCA->xMulticastGroup.xIP_IPv6.ucBytes, pxMReq->xMulticastGroup.xIP_IPv6.ucBytes, ipSIZE_OF_IPv6_ADDRESS ); + memcpy( pxMRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes, pxMReq->xMulticastGroup.xIP_IPv6.ucBytes, ipSIZE_OF_IPv6_ADDRESS ); + } + else + { + pxMCA->xMulticastGroup.ulIP_IPv4 = pxMReq->xMulticastGroup.ulIP_IPv4; + pxMRD->xMCastGroupAddress.xIPAddress.ulIP_IPv4 = pxMReq->xMulticastGroup.ulIP_IPv4; + } + + /* There is no direct link between a multicast report and the socket(s) that require it. + * Store the IP version information in the report so the timer event knows whether to send an IGMP or MLD report. */ + pxMRD->xMCastGroupAddress.xIs_IPv6 = pxSocket->bits.bIsIPv6; + listSET_LIST_ITEM_OWNER( &( pxMRD->xListItem ), ( void * ) pxMRD ); + pxMRD->pxInterface = pxMReq->pxMulticastNetIf; + + /* Pass the multicast report data inside the multicast group descriptor, + * so we can easily pass it to the IP task in one message. */ + pxMCA->pxMCastReportData = pxMRD; + + xSockOptsEvent.pvData = ( void * ) pxMCA; + + if( xSendEventStructToIPTask( &( xSockOptsEvent ), portMAX_DELAY ) != pdPASS ) + { + vPortFree( pxMCA ); + xReturn = -pdFREERTOS_ERRNO_ECANCELED; + } + else + { + xReturn = pdFREERTOS_ERRNO_NONE; + } + } + break; + + case FREERTOS_SO_IP_DROP_MEMBERSHIP: + { + IP_MReq_t * pMReq = ( IP_MReq_t * ) pvOptionValue; + IPStackEvent_t xSockOptsEvent = { eSocketOptDropMembership, NULL }; + + if( ( pxSocket->ucProtocol != ( uint8_t ) FREERTOS_IPPROTO_UDP ) || + ( uxOptionLength != sizeof( IP_MReq_t ) ) ) + { + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + + /* When unsubscribing from a multicast group, the socket option values must + * be exactly the same as when the user subscribed to the multicast group */ + if( pxSocket->bits.bIsIPv6 == pdTRUE ) + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) + if( pdFALSE == xIsIPv6AllowedMulticast( &( pMReq->xMulticastGroup.xIP_IPv6 ) ) ) + { + /* Invalid multicast group address */ + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + + if( memcmp( pxSocket->u.xUDP.xMulticastAddress.xIP_IPv6.ucBytes, pMReq->xMulticastGroup.xIP_IPv6.ucBytes, ipSIZE_OF_IPv6_ADDRESS ) != 0 ) + { + /* The socket was not subscribed to this multicast group. */ + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + #else /* if ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) */ + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + #endif /* if ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) */ + } + else + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) + if( pdFALSE == xIsIPv4Multicast( pMReq->xMulticastGroup.ulIP_IPv4 ) ) + { + /* Invalid multicast group address */ + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + + if( pxSocket->u.xUDP.xMulticastAddress.ulIP_IPv4 != pMReq->xMulticastGroup.ulIP_IPv4 ) + { + /* The socket was not subscribed to this multicast group. */ + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + #else /* if ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) */ + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + #endif /* if ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) */ + } + + if( pMReq->pxMulticastNetIf != pxSocket->u.xUDP.pxMulticastNetIf ) + { + /* The socket was not subscribed on this interface or we are given a bad interface pointer. */ + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + + /* Allocate some RAM to remember the multicast group that is being dropped */ + MulticastAction_t * pxMCA = ( MulticastAction_t * ) pvPortMalloc( sizeof( MulticastAction_t ) ); + + if( NULL == pxMCA ) + { + xReturn = -pdFREERTOS_ERRNO_ENOMEM; + break; + } + + pxMCA->pxSocket = pxSocket; + + /* When dropping memberships, we don't need a multicast report data. */ + pxMCA->pxMCastReportData = NULL; + + xSockOptsEvent.pvData = ( void * ) pxMCA; + + if( xSendEventStructToIPTask( &( xSockOptsEvent ), portMAX_DELAY ) != pdPASS ) + { + vPortFree( pxMCA ); + xReturn = -pdFREERTOS_ERRNO_ECANCELED; + } + else + { + xReturn = pdFREERTOS_ERRNO_NONE; + } + } + break; + + default: + /* This function doesn't handle any other options. */ + xReturn = -pdFREERTOS_ERRNO_ENOPROTOOPT; + break; + } /* switch */ + + return xReturn; + } + +/** + * @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: MUST be eSocketOptAddMembership or eSocketOptDropMembership. + */ + void vModifyMulticastMembership( MulticastAction_t * pxMulticastAction, + uint8_t bAction ) + { + uint8_t MCastMacBytes[ 6 ]; + FreeRTOS_Socket_t * pxSocket = pxMulticastAction->pxSocket; + uint8_t bFreeMatchedItem = pdFALSE; + NetworkInterface_t * pxNetIf = pxMulticastAction->pxInterface; + BaseType_t xReportDataConsumed = pdFALSE; + + configASSERT( pxSocket != NULL ); + + /* Note: This function is only called with eSocketOptDropMembership or eSocketOptAddMembership*/ + + /* This TCP stack does NOT support sockets subscribing to more than one multicast group. + * If the socket is already subscribed to a multicast group, we need to unsubscribe it and remove the + * IGMP/MLD reports corresponding to that group address. */ + prvDropMulticastMembership( pxSocket ); + + if( eSocketOptAddMembership == bAction ) + { + /* Store the multicast IP address and calculate the multicast MAC. */ + + if( pxSocket->bits.bIsIPv6 == pdFALSE ) + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) + pxSocket->u.xUDP.xMulticastAddress.ulIP_IPv4 = pxMulticastAction->xMulticastGroup.ulIP_IPv4; + vSetMultiCastIPv4MacAddress( pxMulticastAction->xMulticastGroup.ulIP_IPv4, MCastMacBytes ); + #else + /* FreeRTOS_setsockopt() will prevent this case */ + #endif + } + else + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) + ( void ) memcpy( &( pxSocket->u.xUDP.xMulticastAddress.xIP_IPv6.ucBytes ), &( pxMulticastAction->xMulticastGroup.xIP_IPv6.ucBytes ), ipSIZE_OF_IPv6_ADDRESS ); + vSetMultiCastIPv6MacAddress( &( pxMulticastAction->xMulticastGroup.xIP_IPv6 ), MCastMacBytes ); + #else + /* FreeRTOS_setsockopt() will prevent this case */ + #endif + } + + /* Inform the network driver that it needs to begin receiving the multicast MAC address */ + if( pxNetIf != NULL ) + { + /* We were given a specific interface to subsribe on. Use it. */ + if( pxNetIf->pfAddAllowedMAC != NULL ) + { + pxNetIf->pfAddAllowedMAC( pxNetIf, MCastMacBytes ); + } + } + else + { + /* pxNetIf is NULL. In FreeRTOS+TCP that means "use all interfaces". */ + for( pxNetIf = FreeRTOS_FirstNetworkInterface(); pxNetIf != NULL; pxNetIf = FreeRTOS_NextNetworkInterface( pxNetIf ) ) + { + if( pxNetIf->pfAddAllowedMAC != NULL ) + { + pxNetIf->pfAddAllowedMAC( pxNetIf, MCastMacBytes ); + } + } + } + + /* Remember which interface(s) this socket is subscribed on. */ + pxSocket->u.xUDP.pxMulticastNetIf = pxMulticastAction->pxInterface; + + /* Since we've added a multicast group to this socket, we need to prepare an IGMP/MLD report + * for when we receive an IGMP/MLD query. Keep in mind that such a report might already exist. + * If such an IGMP/MLD 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 MulticastAction_t that we were + * passed, no longer needs to hold a reference to this multicast report. */ + do + { + if( pxMulticastAction->pxMCastReportData == NULL ) + { + break; + } + + if( pxMulticastAction->pxMCastReportData->xMCastGroupAddress.xIs_IPv6 == pdTRUE ) + { + /* RFC2710 end of section section 5 and RFC3810 section 6: + * ff02::1 is a special case and we do not send reports for it. */ + static const struct xIPv6_Address FreeRTOS_in6addr_allnodes = { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }; + + if( memcmp( pxMulticastAction->pxMCastReportData->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes, FreeRTOS_in6addr_allnodes.ucBytes, sizeof( IPv6_Address_t ) ) == 0 ) + { + break; + } + + /* RFC2710 end of section section 5 and RFC3810 section 6: + * Never send reports for multicast scopes of: 0 (reserved) or 1 (node-local). + * Note: the address was already checked to be a valid multicast in FreeRTOS_setsockopt()*/ + if( ( pxMulticastAction->pxMCastReportData->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes[ 1 ] & 0x0FU ) <= 1 ) + { + break; + } + } + else + { + /* RFC2236 end of section 6: + * 224.0.0.1 is a special case and we do not send reports for it. */ + if( pxMulticastAction->pxMCastReportData->xMCastGroupAddress.xIPAddress.ulIP_IPv4 == igmpIGMP_IP_ADDR ) + { + break; + } + } + + xReportDataConsumed = xEnlistMulticastReport( pxMulticastAction->pxMCastReportData ); + } while( pdFALSE ); + + /* If the report was a special case address or was not consumed by + * xEnlistMulticastReport(), free the multicast report. */ + if( xReportDataConsumed == pdFALSE ) + { + vPortFree( pxMulticastAction->pxMCastReportData ); + pxMulticastAction->pxMCastReportData = NULL; + } + } + + /* Free the message that was sent to us. */ + vPortFree( pxMulticastAction ); + } + + static void prvDropMulticastMembership( FreeRTOS_Socket_t * pxSocket ) + { + uint8_t MCastMacBytes[ 6 ]; + BaseType_t xAddressIsGood = pdFALSE; + NetworkInterface_t * pxNetIf = pxSocket->u.xUDP.pxMulticastNetIf; + + if( pxSocket->bits.bIsIPv6 == pdTRUE_UNSIGNED ) + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) + /* IPv6 */ + if( xIsIPv6AllowedMulticast( &( pxSocket->u.xUDP.xMulticastAddress.xIP_IPv6 ) ) ) + { + xAddressIsGood = pdTRUE; + + vSetMultiCastIPv6MacAddress( &( pxSocket->u.xUDP.xMulticastAddress.xIP_IPv6 ), MCastMacBytes ); + + if( pxNetIf != NULL ) + { + if( pxNetIf->pfRemoveAllowedMAC != NULL ) + { + pxNetIf->pfRemoveAllowedMAC( pxNetIf, MCastMacBytes ); + } + } + else + { + /* pxNetIf is NULL. That means "all interfaces", so unsubscribe from all. */ + for( pxNetIf = FreeRTOS_FirstNetworkInterface(); pxNetIf != NULL; pxNetIf = FreeRTOS_NextNetworkInterface( pxNetIf ) ) + { + if( pxNetIf->pfRemoveAllowedMAC != NULL ) + { + pxNetIf->pfRemoveAllowedMAC( pxNetIf, MCastMacBytes ); + } + } + } + } + else + { + /* Whatever is stored in pxSocket->u.xUDP.xMulticastAddress is not a valid multicast group + * or prvDropMulticastMembership was called for a socket that is not still subscribed to a multicast group. + * Do nothing. */ + } + #endif /* if ( ipconfigIS_ENABLED( ipconfigUSE_IPv6 ) ) */ + } + else + { + #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) + /* IPv4 */ + if( xIsIPv4Multicast( pxSocket->u.xUDP.xMulticastAddress.ulIP_IPv4 ) ) + { + xAddressIsGood = pdTRUE; + + vSetMultiCastIPv4MacAddress( pxSocket->u.xUDP.xMulticastAddress.ulIP_IPv4, MCastMacBytes ); + + if( pxNetIf != NULL ) + { + if( pxNetIf->pfRemoveAllowedMAC != NULL ) + { + pxNetIf->pfRemoveAllowedMAC( pxNetIf, MCastMacBytes ); + } + } + else + { + /* pxNetIf is NULL. That means "all interfaces", so unsubscribe from all. */ + for( pxNetIf = FreeRTOS_FirstNetworkInterface(); pxNetIf != NULL; pxNetIf = FreeRTOS_NextNetworkInterface( pxNetIf ) ) + { + if( pxNetIf->pfRemoveAllowedMAC != NULL ) + { + pxNetIf->pfRemoveAllowedMAC( pxNetIf, MCastMacBytes ); + } + } + } + } + else + { + /* Whatever is stored in pxSocket->u.xUDP.xMulticastAddress is not a valid multicast group + * or prvDropMulticastMembership was called for a socket that is not still subscribed to a multicast group. + * Do nothing. */ + } + #endif /* if ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) */ + } + + if( xAddressIsGood == pdTRUE ) + { + vDelistMulticastReport( pxSocket->u.xUDP.pxMulticastNetIf, &( pxSocket->u.xUDP.xMulticastAddress ), ( UBaseType_t ) pxSocket->bits.bIsIPv6 ); + } + + /* Invalidate the multicast group address to prevent erroneous matches if someone calls + * FREERTOS_SO_IP_DROP_MEMBERSHIP multiple times. */ + memset( &pxSocket->u.xUDP.xMulticastAddress, 0x00, sizeof( pxSocket->u.xUDP.xMulticastAddress ) ); + pxSocket->u.xUDP.pxMulticastNetIf = NULL; /* not really needed, but just looks cleaner when debugging. */ + } + +#endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ diff --git a/source/FreeRTOS_UDP_IPv4.c b/source/FreeRTOS_UDP_IPv4.c index ceed53e93d..95a2ef87c0 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 ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + #include "FreeRTOS_IGMP.h" +#endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ /*-----------------------------------------------------------*/ @@ -195,6 +198,8 @@ void vProcessGeneratedUDPPacket_IPv4( NetworkBufferDescriptor_t * const pxNetwor pvCopyDest = &pxNetworkBuffer->pucEthernetBuffer[ sizeof( MACAddress_t ) ]; ( void ) memcpy( pvCopyDest, pvCopySource, sizeof( ucDefaultPartUDPPacketHeader ) ); + pxIPHeader->ucTimeToLive = pxNetworkBuffer->ucMaximumHops; + #if ipconfigSUPPORT_OUTGOING_PINGS == 1 if( pxNetworkBuffer->usPort == ( uint16_t ) ipPACKET_CONTAINS_ICMP_DATA ) { @@ -224,26 +229,6 @@ void vProcessGeneratedUDPPacket_IPv4( NetworkBufferDescriptor_t * const pxNetwor pxIPHeader->usFragmentOffset = 0U; #endif - #if ( ipconfigUSE_LLMNR == 1 ) - { - /* LLMNR messages are typically used on a LAN and they're - * not supposed to cross routers */ - if( pxNetworkBuffer->xIPAddress.ulIP_IPv4 == ipLLMNR_IP_ADDR ) - { - pxIPHeader->ucTimeToLive = 0x01; - } - } - #endif - #if ( ipconfigUSE_MDNS == 1 ) - { - /* mDNS messages have a hop-count of 255, see RFC 6762, section 11. */ - if( pxNetworkBuffer->xIPAddress.ulIP_IPv4 == ipMDNS_IP_ADDRESS ) - { - pxIPHeader->ucTimeToLive = 0xffU; - } - } - #endif - #if ( ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM == 0 ) { pxIPHeader->usHeaderChecksum = 0U; @@ -384,6 +369,40 @@ BaseType_t xProcessReceivedUDPPacket_IPv4( NetworkBufferDescriptor_t * pxNetwork { if( pxSocket != NULL ) { + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + + /* 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( xIsIPv4Multicast( pxUDPPacket->xIPHeader.ulDestinationIPAddress ) ) + { + /* Destination is a good multicast address, but is the socket subscribed to this group? */ + if( ( pxSocket->u.xUDP.xMulticastAddress.ulIP_IPv4 == pxUDPPacket->xIPHeader.ulDestinationIPAddress ) && + ( ( pxSocket->u.xUDP.pxMulticastNetIf == NULL ) || ( pxSocket->u.xUDP.pxMulticastNetIf == pxNetworkBuffer->pxInterface ) ) ) + { + /* Multicast group and network interface match. Allow further parsing by doing nothing here. */ + } + else + { + /* This socket is not subscribed to this multicast group or the interface on which the socket + * is subscribed doesn't match. Nullify the result from pxUDPSocketLookup(). + * Setting the socket to NULL is not strictly necessary. Leave here for clarity and insurance. */ + pxSocket = NULL; + /* return pdFAIL so the buffer can be released */ + xReturn = pdFAIL; + /* Do not continue parsing */ + break; + } + } + else + { + /* The incoming packet is not a multicast and we already know it + * matches this socket's port number, so just proceed */ + } + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + if( ( pxEndpoint != NULL ) && ( pxEndpoint->ipv4_settings.ulIPAddress != 0U ) ) { if( xCheckRequiresARPResolution( pxNetworkBuffer ) == pdTRUE ) diff --git a/source/FreeRTOS_UDP_IPv6.c b/source/FreeRTOS_UDP_IPv6.c index 80762bd9ca..b5f2d483c2 100644 --- a/source/FreeRTOS_UDP_IPv6.c +++ b/source/FreeRTOS_UDP_IPv6.c @@ -210,6 +210,8 @@ void vProcessGeneratedUDPPacket_IPv6( NetworkBufferDescriptor_t * const pxNetwor pxNetworkBuffer->pxEndPoint = pxEndPoint; + pxIPHeader_IPv6->ucHopLimit = pxNetworkBuffer->ucMaximumHops; + #if ( ipconfigSUPPORT_OUTGOING_PINGS == 1 ) /* Is it possible that the packet is not actually a UDP packet @@ -218,7 +220,6 @@ void vProcessGeneratedUDPPacket_IPv6( NetworkBufferDescriptor_t * const pxNetwor { pxIPHeader_IPv6->ucVersionTrafficClass = 0x60; pxIPHeader_IPv6->ucNextHeader = ipPROTOCOL_ICMP_IPv6; - pxIPHeader_IPv6->ucHopLimit = 128; } else #endif /* ipconfigSUPPORT_OUTGOING_PINGS */ @@ -230,7 +231,6 @@ void vProcessGeneratedUDPPacket_IPv6( NetworkBufferDescriptor_t * const pxNetwor pxIPHeader_IPv6->ucVersionTrafficClass = 0x60; pxIPHeader_IPv6->ucTrafficClassFlow = 0; pxIPHeader_IPv6->usFlowLabel = 0; - pxIPHeader_IPv6->ucHopLimit = 255; pxUDPHeader->usLength = ( uint16_t ) ( pxNetworkBuffer->xDataLength - ( ipSIZE_OF_ETH_HEADER + ipSIZE_OF_IPv6_HEADER ) ); pxIPHeader_IPv6->ucNextHeader = ipPROTOCOL_UDP; @@ -419,6 +419,40 @@ BaseType_t xProcessReceivedUDPPacket_IPv6( NetworkBufferDescriptor_t * pxNetwork if( pxSocket != NULL ) { + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + + /* 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( xIsIPv6AllowedMulticast( pxUDPPacket_IPv6->xIPHeader.xDestinationAddress.ucBytes ) ) + { + /* Destination is a good multicast address, but is the socket subscribed to this group? */ + if( ( memcmp( pxSocket->u.xUDP.xMulticastAddress.xIP_IPv6.ucBytes, pxUDPPacket_IPv6->xIPHeader.xDestinationAddress.ucBytes, ipSIZE_OF_IPv6_ADDRESS ) == 0 ) && + ( ( pxSocket->u.xUDP.pxMulticastNetIf == NULL ) || ( pxSocket->u.xUDP.pxMulticastNetIf == pxNetworkBuffer->pxInterface ) ) ) + { + /* Multicast group and network interface match. Allow further parsing by doing nothing here. */ + } + else + { + /* This socket is not subscribed to this multicast group or the interface on which the socket + * is subscribed doesn't match. Nullify the result from pxUDPSocketLookup(). + * Setting the socket to NULL is not strictly necessary. Leave here for clarity and insurance. */ + pxSocket = NULL; + /* return pdFAIL so the buffer can be released */ + xReturn = pdFAIL; + /* Do not continue parsing */ + break; + } + } + else + { + /* The incoming packet is not a multicast and we already know it + * matches this socket's port number, so just proceed */ + } + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + if( xCheckRequiresARPResolution( pxNetworkBuffer ) == pdTRUE ) { /* Mark this packet as waiting for ARP resolution. */ diff --git a/source/include/FreeRTOSIPConfigDefaults.h b/source/include/FreeRTOSIPConfigDefaults.h index 50810ada78..f09912027e 100644 --- a/source/include/FreeRTOSIPConfigDefaults.h +++ b/source/include/FreeRTOSIPConfigDefaults.h @@ -3374,6 +3374,62 @@ STATIC_ASSERT( ipconfigDNS_SEND_BLOCK_TIME_TICKS <= portMAX_DELAY ); /*---------------------------------------------------------------------------*/ +/* + * ipconfigSUPPORT_IP_MULTICAST + * + * Type: BaseType_t ( ipconfigENABLE | ipconfigDISABLE ) + * + * When set to ipconfigENABLE, this macro will + * enable the reception of multicast groups addresses. When enabled, + * It is highly recommended to enable ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES + * and use a network driver that supports MAC filtering through the + * pfAddAllowedMAC/pfRemoveAllowedMAC functions. + */ +#ifndef ipconfigSUPPORT_IP_MULTICAST + #define ipconfigSUPPORT_IP_MULTICAST ipconfigDISABLE +#endif + +/*---------------------------------------------------------------------------*/ + +/* + * ipconfigPERIODIC_MULTICAST_REPORT_INTERVAL + * + * Type: BaseType_t + * Unit: count of igmpMULTICAST_EVENT_PERIOD_MS + * + * When set to -1, no periodic unsolicited multicast reports are sent out. + * This is the correct behavior. For debug purposes, set to > 0 to cause + * periodic sending of multicast reports even if there are no IGMP/MLD + * queries heard. Example: 150 = 15.0 seconds. + * Note: Maybe remove that ? + */ +#ifndef ipconfigPERIODIC_MULTICAST_REPORT_INTERVAL + #define ipconfigPERIODIC_MULTICAST_REPORT_INTERVAL ( -1 ) +#endif + +/*---------------------------------------------------------------------------*/ + +/* + * ipconfigMULTICAST_DEFAULT_TTL + * + * Type: uint8_t + * Unit: 'hops' + * Minimum: 0 + * + * 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 or by + * setting the FREERTOS_SO_IPV6_MULTICAST_HOPS in case of an IPv6 socket. + * Please note that in certain situations, RFCs or standards may require + * a certain value to be used,like in Neighbor Solicitation where the hop + * limit field must be set to 255. In those cases, the value is hard-coded + * instead of being controlled by this define. */ +#ifndef ipconfigMULTICAST_DEFAULT_TTL + #define ipconfigMULTICAST_DEFAULT_TTL ( 1 ) +#endif + +/*---------------------------------------------------------------------------*/ + /* Should only be included here, ensures trace config is set first. */ #include "IPTraceMacroDefaults.h" diff --git a/source/include/FreeRTOS_DNS.h b/source/include/FreeRTOS_DNS.h index 04daeb2329..3f2af7f184 100644 --- a/source/include/FreeRTOS_DNS.h +++ b/source/include/FreeRTOS_DNS.h @@ -67,10 +67,10 @@ uint32_t ulDNSHandlePacket( const NetworkBufferDescriptor_t * pxNetworkBuffer ); #if ( ipconfigUSE_MDNS == 1 ) && ( ipconfigUSE_IPv6 != 0 ) -/* The MDNS IPv6 address is ff02::1:3 */ +/* The MDNS IPv6 address is ff02::fb */ extern const IPv6_Address_t ipMDNS_IP_ADDR_IPv6; -/* The MDNS IPv6 MAC address is 33:33:00:01:00:03 */ +/* The MDNS IPv6 MAC address is 33:33:00:00:00:fb */ extern const MACAddress_t xMDNS_MACAddressIPv6; #endif /* ipconfigUSE_MDNS */ diff --git a/source/include/FreeRTOS_IGMP.h b/source/include/FreeRTOS_IGMP.h new file mode 100644 index 0000000000..2d0de91172 --- /dev/null +++ b/source/include/FreeRTOS_IGMP.h @@ -0,0 +1,92 @@ +/* + * 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 things in 100ms increments. https://www.rfc-editor.org/rfc/rfc2236#section-2.2 */ + #define igmpTIMING_PERIOD_MS ( 100U ) + +/* IGMP protocol definitions. */ + #define igmpIGMP_MEMBERSHIP_QUERY ( ( uint8_t ) 0x11U ) /**< IGMP membership query. */ + #define igmpIGMP_MEMBERSHIP_REPORT_V1 ( ( uint8_t ) 0x12U ) /**< IGMP v1 membership report. */ + #define igmpIGMP_MEMBERSHIP_REPORT_V2 ( ( uint8_t ) 0x16U ) /**< IGMP v2 membership report. */ + #define igmpIGMP_MEMBERSHIP_REPORT_V3 ( ( uint8_t ) 0x22U ) /**< IGMP v3 membership report. */ + #define igmpIGMP_LEAVE_GROUP ( ( uint8_t ) 0x17U ) /**< IGMP leave group */ + + #if ( ipconfigBYTE_ORDER == pdFREERTOS_BIG_ENDIAN ) + #define igmpIGMP_IP_ADDR 0xE0000001UL + #else + #define igmpIGMP_IP_ADDR 0x010000E0UL + #endif /* ipconfigBYTE_ORDER == pdFREERTOS_BIG_ENDIAN */ + + #include "pack_struct_start.h" + struct xIGMP_HEADER + { + uint8_t ucMessageType; /**< 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 ulGroupAddress; /**< 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; + + + + #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 766c184508..3bb5e3ac60 100644 --- a/source/include/FreeRTOS_IP.h +++ b/source/include/FreeRTOS_IP.h @@ -169,6 +169,7 @@ typedef struct xNETWORK_BUFFER #if ( ipconfigUSE_LINKED_RX_MESSAGES != 0 ) struct xNETWORK_BUFFER * pxNextBuffer; /**< Possible optimisation for expert users - requires network driver support. */ #endif + uint8_t ucMaximumHops; /**< TTL/HopLimit value for outgoing multicast/unicast UDP/ICMP/ICMPv6 frames. */ #define ul_IPAddress xIPAddress.xIP_IPv4 #define x_IPv6Address xIPAddress.xIP_IPv6 diff --git a/source/include/FreeRTOS_IP_Common.h b/source/include/FreeRTOS_IP_Common.h index e557fd07b2..3481bcb17c 100644 --- a/source/include/FreeRTOS_IP_Common.h +++ b/source/include/FreeRTOS_IP_Common.h @@ -58,6 +58,16 @@ typedef struct xxIPv46_Address struct xNetworkEndPoint; struct xNetworkInterface; +#if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) +/** @brief The structure is used to join/leave an IPv4/IPv6 multicast group. */ + typedef struct freertos_ip_mreq + { + IP_Address_t xMulticastGroup; /**< The address of the multicast group */ + struct xNetworkInterface * pxMulticastNetIf; /**< A pointer to the network interface on which the above + * multicast group is to be joined or left. NULL means "on all interfaces" */ + } IP_MReq_t; +#endif /* if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) */ + /* Return true if a given end-point is up and running. * When FreeRTOS_IsNetworkUp() is called with NULL as a parameter, * it will return pdTRUE when all end-points are up. */ diff --git a/source/include/FreeRTOS_IP_Private.h b/source/include/FreeRTOS_IP_Private.h index b594582fc9..b7e75f0968 100644 --- a/source/include/FreeRTOS_IP_Private.h +++ b/source/include/FreeRTOS_IP_Private.h @@ -71,20 +71,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. */ + eMulticastTimerEvent, /*16: Handles the sending of scheduled IGMP/MLD multicast reports. */ } eIPEvent_t; /** @@ -624,6 +627,17 @@ typedef struct UDPSOCKET */ FOnUDPSent_t pxHandleSent; /**< Function pointer to handle the events after a successful send. */ #endif /* ipconfigUSE_CALLBACKS */ + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + IP_Address_t xMulticastAddress; /**< Holds the multicast group address that the socket may have subscribed to receive. */ + NetworkInterface_t * pxMulticastNetIf; /**< The interface on which multicasts are to be received. NULL for all interfaces. */ + uint8_t ucMulticastMaxHops; /**< Allows for multicast sockets to use a TTL/Hops value that is different than for unicast packets + * in order to limit the reach of multicast packets. + * Defaults to ipconfigMULTICAST_DEFAULT_TTL. For local network use, it is recommended to use a TTL of 1. + * Example: + * uint8_t ttl = 1; + * FreeRTOS_setsockopt( MCastSendSock, 0, FREERTOS_SO_IP_MULTICAST_TTL, ( void * ) &ttl, sizeof( ttl ) ); + */ + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ } IPUDPSocket_t; /* Formally typedef'd as eSocketEvent_t. */ @@ -859,6 +873,24 @@ BaseType_t xIsCallingFromIPTask( void ); #endif /* ipconfigSUPPORT_SELECT_FUNCTION */ +#if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + struct xMCastGroupDesc; + struct MCastReportDescription; + void vModifyMulticastMembership( struct xMCastGroupDesc * pxMulticastAction, + uint8_t bAction ); + BaseType_t xEnlistMulticastReport( struct MCastReportDescription * pNewEntry ); + void vDelistMulticastReport( NetworkInterface_t * pxInterface, + IP_Address_t * pxMulticastAddress, + UBaseType_t xIsIPv6 ); + BaseType_t xSendMulticastTimerEvent( void ); + void vIPMulticast_HandleTimerEvent( void ); + void vIPMulticast_Init( void ); + eFrameProcessingResult_t eProcessIGMPPacket( NetworkBufferDescriptor_t * const pxNetworkBuffer ); + void vProcessMLDPacket( NetworkBufferDescriptor_t * const pxNetworkBuffer ); + void vRescheduleAllMulticastReports( NetworkInterface_t * pxInterface, + BaseType_t xMaxCountdown ); +#endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + /* Send the network-up event and start the ARP timer. */ void vIPNetworkUpCalls( struct xNetworkEndPoint * pxEndPoint ); diff --git a/source/include/FreeRTOS_IP_Timers.h b/source/include/FreeRTOS_IP_Timers.h index 8d7a84e263..5e9a1f5a70 100644 --- a/source/include/FreeRTOS_IP_Timers.h +++ b/source/include/FreeRTOS_IP_Timers.h @@ -136,6 +136,14 @@ void vTCPTimerReload( TickType_t xTime ); void vDNSTimerReload( uint32_t ulCheckTime ); #endif /* ipconfigDNS_USE_CALLBACKS != 0 */ +#if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + +/** + * Reload the IP Multicast Reports timer. + */ + void vIPMulticastReportsTimerReload( TickType_t xPeriodTicks ); +#endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + /** * Reload the Network timer. */ diff --git a/source/include/FreeRTOS_IP_Utils.h b/source/include/FreeRTOS_IP_Utils.h index 301f149055..05abb28220 100644 --- a/source/include/FreeRTOS_IP_Utils.h +++ b/source/include/FreeRTOS_IP_Utils.h @@ -64,6 +64,33 @@ #endif /* *INDENT-ON* */ +#if ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) + /** @brief A structure holding information about a multicast group address. Used during generation of IGMP/ICMPv6 reports. */ + typedef struct MCastReportDescription + { + IPv46_Address_t xMCastGroupAddress; /**< Holds the IPv4/IPv6 multicast group address. xMCastGroupAddress.xIs_IPv6 denotes whether this represents and IGMP or MLD report. */ + ListItem_t xListItem; /**< List item for adding to the global list of reports. */ + NetworkInterface_t * pxInterface; /**< The network interface used for sending this report. NULL to send on all interfaces. */ + BaseType_t xNumSockets; /**< The number of sockets that are subscribed to this multicast group. */ + BaseType_t xCountDown; + } MCastReportData_t; + +/** @brief A structure to hold all data related to an multicast socket option "action". When someone calls FreeRTOS_setsockopt() + * with one of the multicast socket options, the code allocates a structure like this and stores all the relevant information. + * The structure is then passed to the IP task for handling. This approach allows us to return an error if we don't have enough + * memory for a multicast report and allows all actual manipulations to happen within the IP task therefore avoiding the need + * for critical sections. An exception to this is setting the TTL/HopLimit as it can be done straight from the user task. as + * an atomic write operation. */ + typedef struct xMCastGroupDesc + { + IP_Address_t xMulticastGroup; /**< Holds the IPv4/IPv6 multicast group address */ + NetworkInterface_t * pxInterface; /**< Not implemented yet, but should point to a specific interface or NULL for all/default interface */ + FreeRTOS_Socket_t * pxSocket; /**< The socket this action is applied to */ + MCastReportData_t * pxMCastReportData; /**< Holds the allocated IGMP report descriptor while passing from user code to the IP Task. */ + } MulticastAction_t; +#endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + + /* Forward declaration. */ struct xNetworkInterface; diff --git a/source/include/FreeRTOS_IPv6.h b/source/include/FreeRTOS_IPv6.h index 0706a7dac4..7f8d6217f1 100644 --- a/source/include/FreeRTOS_IPv6.h +++ b/source/include/FreeRTOS_IPv6.h @@ -54,10 +54,13 @@ #define ipICMP_PARAMETER_PROBLEM_IPv6 ( ( uint8_t ) 4U ) #define ipICMP_PING_REQUEST_IPv6 ( ( uint8_t ) 128U ) #define ipICMP_PING_REPLY_IPv6 ( ( uint8_t ) 129U ) +#define ipICMP_MULTICAST_LISTENER_QUERY ( ( uint8_t ) 130U ) +#define ipICMP_MULTICAST_LISTENER_REPORT_V1 ( ( uint8_t ) 131U ) #define ipICMP_ROUTER_SOLICITATION_IPv6 ( ( uint8_t ) 133U ) #define ipICMP_ROUTER_ADVERTISEMENT_IPv6 ( ( uint8_t ) 134U ) #define ipICMP_NEIGHBOR_SOLICITATION_IPv6 ( ( uint8_t ) 135U ) #define ipICMP_NEIGHBOR_ADVERTISEMENT_IPv6 ( ( uint8_t ) 136U ) +#define ipICMP_MULTICAST_LISTENER_REPORT_V2 ( ( uint8_t ) 143U ) #define ipIPv6_EXT_HEADER_HOP_BY_HOP 0U @@ -113,7 +116,7 @@ extern void FreeRTOS_ClearND( void ); BaseType_t xIsIPv6AllowedMulticast( const IPv6_Address_t * pxIPAddress ); /* Note that 'xCompareIPv6_Address' will also check if 'pxRight' is - * the special unicast address: ff02::1:ffnn:nnnn, where nn:nnnn are + * the special multicast address: ff02::1:ffnn:nnnn, where nn:nnnn are * the last 3 bytes of the IPv6 address. */ BaseType_t xCompareIPv6_Address( const IPv6_Address_t * pxLeft, const IPv6_Address_t * pxRight, diff --git a/source/include/FreeRTOS_IPv6_Private.h b/source/include/FreeRTOS_IPv6_Private.h index c56c9b32b3..9cd6ba2888 100644 --- a/source/include/FreeRTOS_IPv6_Private.h +++ b/source/include/FreeRTOS_IPv6_Private.h @@ -211,6 +211,78 @@ typedef struct xICMPRouterAdvertisement_IPv6 ICMPRouterAdvertisement_IPv6_t; typedef struct xICMPPrefixOption_IPv6 ICMPPrefixOption_IPv6_t; #endif /* ipconfigUSE_RA != 0 */ +#if ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) + #include "pack_struct_start.h" + struct xIP_HOP_BY_HOP_EXT_ROUTER_ALERT_IPv6 + { + uint8_t ucNextHeader; /**< Next header: TCP, UDP, or ICMP. 0 + 1 = 1 */ + uint8_t ucHeaderExtLength; /**< Length of this header in 8-octet units, not including the first 8 octets. 1 + 1 = 2 */ + struct + { + uint8_t ucType; + uint8_t ucLength; + uint16_t usValue; + } + xRouterAlert; + struct + { + uint8_t ucType; + uint8_t ucLength; + } + xPadding; + } + #include "pack_struct_end.h" + typedef struct xIP_HOP_BY_HOP_EXT_ROUTER_ALERT_IPv6 IPHopByHopExtRouterAlert_IPv6_t; + + #include "pack_struct_start.h" + struct xICMPv6_MLDv1 + { + uint8_t ucTypeOfMessage; /**< The message type. 0 + 1 = 1 */ + uint8_t ucTypeOfService; /**< Type of service. 1 + 1 = 2 */ + uint16_t usChecksum; /**< Checksum. 2 + 2 = 4 */ + uint16_t usMaxResponseDelay; /**< Max Response Delay. 4 + 2 = 6 */ + uint16_t usReserved; /**< Reserved. 6 + 2 = 8 */ + IPv6_Address_t xGroupAddress; /**< The IPv6 address. 8 + 16 = 24 */ + } + #include "pack_struct_end.h" + typedef struct xICMPv6_MLDv1 ICMPv6_MLDv1_t; + +/* Note: MLD packets are required to use the Router-Alert option + * in an IPv6 extension header. */ + #include "pack_struct_start.h" + struct xICMPv6_MLDv1_TX_PACKET + { + EthernetHeader_t xEthernetHeader; /* 0 + 14 = 14 */ + IPHeader_IPv6_t xIPHeader; /* 14 + 40 = 54 */ + IPHopByHopExtRouterAlert_IPv6_t xRAOption; /* 54 + 8 = 62 */ + ICMPv6_MLDv1_t xMLD; /* 62 + 24 = 86 */ + } + #include "pack_struct_end.h" + typedef struct xICMPv6_MLDv1_TX_PACKET MLDv1_Tx_Packet_t; + +/* This TCP stack strips the extension headers from IPv6 packets, so even though + * MLD packets include a Router-Alert option in an IPv6 extension header, the ICMP + * layer will not see it in the packet prvProcessIPPacket() stripped the extension headers. */ + #include "pack_struct_start.h" + struct xICMPv6_MLDv1_RX_PACKET + { + EthernetHeader_t xEthernetHeader; /* 0 + 14 = 14 */ + IPHeader_IPv6_t xIPHeader; /* 14 + 40 = 54 */ + ICMPv6_MLDv1_t xMLD; /* 54 + 24 = 78 */ + } + #include "pack_struct_end.h" + typedef struct xICMPv6_MLDv1_RX_PACKET MLDv1_Rx_Packet_t; + +/** @brief Options that can be sent in a Multicast Listener Report packet. + * more info at https://www.rfc-editor.org/rfc/rfc2711#section-2.0 */ + #define ipROUTER_ALERT_VALUE_MLD 0 + +/** from https://www.iana.org/assignments/ipv6-parameters/ipv6-parameters.xhtml#ipv6-parameters-2 */ + #define ipHOP_BY_HOP_ROUTER_ALERT 5 + #define ipHOP_BY_HOP_PadN 1 + +#endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + /*-----------------------------------------------------------*/ /* Nested protocol packets. */ /*-----------------------------------------------------------*/ diff --git a/source/include/FreeRTOS_Sockets.h b/source/include/FreeRTOS_Sockets.h index 0a93660a8f..e3a3bab2ab 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 ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + #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 /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + #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 6619f6afbc..db99b73eab 100644 --- a/source/portable/NetworkInterface/DriverSAM/NetworkInterface.c +++ b/source/portable/NetworkInterface/DriverSAM/NetworkInterface.c @@ -189,7 +189,7 @@ static uint32_t prvEMACRxPoll( void ); static BaseType_t prvSAM_NetworkInterfaceInitialise( NetworkInterface_t * pxInterface ); static BaseType_t prvSAM_NetworkInterfaceOutput( NetworkInterface_t * pxInterface, - NetworkBufferDescriptor_t * const pxBuffer, + NetworkBufferDescriptor_t * const pxDescriptor, BaseType_t bReleaseAfterSend ); static BaseType_t prvSAM_GetPhyLinkStatus( NetworkInterface_t * pxInterface ); @@ -764,6 +764,15 @@ static BaseType_t prvGMACInit( NetworkInterface_t * pxInterface ) } #if ( ipconfigIS_ENABLED( ipconfigUSE_IPv4 ) ) + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + { + /* Receive IGMP queries by allowing the MAC address that corresponds to 224.0.0.1 */ + MACAddress_t xIGMP_MacAddress; + vSetMultiCastIPv4MacAddress( igmpIGMP_IP_ADDR, &xIGMP_MacAddress ); + prvAddAllowedMACAddress( pxInterface, xIGMP_MacAddress.ucBytes ); + } + #endif /* ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) */ + #if ( ipconfigUSE_LLMNR == ipconfigENABLE ) prvAddAllowedMACAddress( pxInterface, xLLMNR_MacAddress.ucBytes ); #endif /* ipconfigUSE_LLMNR */