From 3033e3ec8300ba9e9a0864be08ef0972508a9962 Mon Sep 17 00:00:00 2001 From: Emil Popov Date: Tue, 23 Apr 2024 10:39:14 -0400 Subject: [PATCH] Adds support for receiving IPv4 and IPv6 multicast groups Modifies eConsiderFrameForProcessing() to allow all multicast ethernet frames when ipconfigSUPPORT_IP_MULTICAST is enabled and ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES is disabled. Adds parsing of IGMP and MLD queries. Sends IGMPv2 and MLDv1 reports on a schedule that is updated based on received IGMP/MLD queries. Sends unsolicited IGMP and MLD reports on network-up events and on add-membership socket option. Adds pxSocket->u.xUDP.xMulticastTTL that can be used for both IPv4 and IPv6 Adds pxSocket->u.xUDP.xMulticastAddress that can be used for both IPv4 and IPv6 Adds pxSocket->u.xUDP.pxMulticastNetIf that specifies the interface on which a sockets wants to receive multicasts. Adds socket option defines to add/drop membership as well as change the transmit TTL of multicasts. Makes all 3 multicast socket options (add/drop/ttl) work with both IPv4 and IPv6 Adds a ucMaximumHops field to NetworkBufferDescriptor_t and assigns it to the proper TTL/HopLimit value based on what packet is being sent. Adds exceptions so that we don't send multicast reports for 224.0.0.1, ff02::1, as well as anything with IPv6 multicast scope of 0 or 1 Adds defines for MLD packets like the Multicast Listener Query and Report. The MLD report defines are different for transmitted and received packets because the stack strips the optional headers from received MLD packets. Generates an MLD report for the solicited-node multicast addresses corresponding to all unicast IPv6 addresses Sends IGMPv2 Leave Group messages whenever the last socket subscribed to a group drops that membership. On network down, stops receiving the MAC address that corresponds to the solicited node multicast IPv6 address. This balances out the "network-up" calls that allow that MAC address. Removes the explicit broadcast MAC check in eConsiderFrameForProcessing. Broadcasts are a form of multicasts and will be received when ipconfigSUPPORT_IP_MULTICAST is enabled. Adds ipconfigSUPPORT_IP_MULTICAST to enable/disable all the functionality described above. Adds ipconfigPERIODIC_MULTICAST_REPORT_INTERVAL for debug purposes when there is no IGMP/MLD querier Moves the registration of the IGMP multicast MAC to the network driver init code. Adds a Multicast Todo list to help keep me on track. --- source/FreeRTOS_DNS_Networking.c | 5 + source/FreeRTOS_DNS_Parser.c | 30 + source/FreeRTOS_IP.c | 100 +- source/FreeRTOS_IP_Multicast.c | 983 ++++++++++++++++++ source/FreeRTOS_IP_Timers.c | 41 + source/FreeRTOS_IPv6.c | 4 +- source/FreeRTOS_IPv6_Utils.c | 71 ++ source/FreeRTOS_ND.c | 21 + source/FreeRTOS_Sockets.c | 547 +++++++++- source/FreeRTOS_UDP_IPv4.c | 59 +- source/FreeRTOS_UDP_IPv6.c | 38 +- source/include/FreeRTOSIPConfigDefaults.h | 56 + source/include/FreeRTOS_DNS.h | 4 +- source/include/FreeRTOS_IGMP.h | 92 ++ source/include/FreeRTOS_IP.h | 1 + source/include/FreeRTOS_IP_Common.h | 10 + source/include/FreeRTOS_IP_Private.h | 60 +- source/include/FreeRTOS_IP_Timers.h | 8 + source/include/FreeRTOS_IP_Utils.h | 27 + source/include/FreeRTOS_IPv6.h | 5 +- source/include/FreeRTOS_IPv6_Private.h | 72 ++ source/include/FreeRTOS_Sockets.h | 9 +- .../DriverSAM/NetworkInterface.c | 11 +- 23 files changed, 2188 insertions(+), 66 deletions(-) create mode 100644 source/FreeRTOS_IP_Multicast.c create mode 100644 source/include/FreeRTOS_IGMP.h diff --git a/source/FreeRTOS_DNS_Networking.c b/source/FreeRTOS_DNS_Networking.c index 9d41bcd877..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 c1148a646d..50266bc321 100644 --- a/source/FreeRTOS_DNS_Parser.c +++ b/source/FreeRTOS_DNS_Parser.c @@ -919,6 +919,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; } @@ -934,8 +954,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 5c82bd1fea..6dac3f8a0d 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 ) ) */ + #if ( ipconfigIS_ENABLED( ipconfigSUPPORT_IP_MULTICAST ) ) + /* Init the list that will hold scheduled IGMP reports. */ + ( 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..cd02e6726b 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. @@ -412,7 +412,7 @@ BaseType_t xCompareIPv6_Address( const IPv6_Address_t * pxLeft, ( pxRight->ucBytes[ 1 ] == 0x02U ) && ( pxRight->ucBytes[ 12 ] == 0xffU ) ) { - /* This is an LLMNR address. */ + /* This may be our 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 f35995bb5a..172d07f9fc 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 c787bbd0b8..952e533e00 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 */