From b10fbd0326468e5c6d46dbce6a1584c39eadfd47 Mon Sep 17 00:00:00 2001 From: Emil Popov Date: Thu, 13 Apr 2023 14:05:27 -0400 Subject: [PATCH] Adds support for receiving IPv4 and IPv6 multicast groups 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 2 function pointers to the network interface struct that handle adding and removing multicast MAC addresses. 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 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 a NetworkInterface_t * to the socket struct to keep track of which network interface(s) should receive multicasts. 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 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. Adds ipconfigPERIODIC_MULTICAST_REPORT_INTERVAL for debug purposes when there is no IGMP/MLD querier (+3 squashed commit) Improves the SAME70 driver to handle adding/removing muticast MAC addresses 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_IGMP.c | 872 ++++++++++++++++++ source/FreeRTOS_IP.c | 98 ++ source/FreeRTOS_IP_Timers.c | 41 + source/FreeRTOS_IPv6.c | 3 +- source/FreeRTOS_ND.c | 23 +- source/FreeRTOS_Sockets.c | 491 +++++++++- source/FreeRTOS_UDP_IPv4.c | 52 +- source/FreeRTOS_UDP_IPv6.c | 32 +- source/include/FreeRTOSIPConfigDefaults.h | 69 ++ source/include/FreeRTOS_DNS.h | 4 +- source/include/FreeRTOS_IGMP.h | 97 ++ source/include/FreeRTOS_IP.h | 1 + source/include/FreeRTOS_IP_Common.h | 10 + source/include/FreeRTOS_IP_Private.h | 51 +- source/include/FreeRTOS_IP_Timers.h | 4 + source/include/FreeRTOS_IP_Utils.h | 27 + source/include/FreeRTOS_IPv6.h | 5 +- source/include/FreeRTOS_IPv6_Private.h | 72 ++ source/include/FreeRTOS_Routing.h | 12 + source/include/FreeRTOS_Sockets.h | 9 +- .../DriverSAM/NetworkInterface.c | 139 ++- 23 files changed, 2074 insertions(+), 73 deletions(-) create mode 100644 source/FreeRTOS_IGMP.c create mode 100644 source/include/FreeRTOS_IGMP.h diff --git a/source/FreeRTOS_DNS_Networking.c b/source/FreeRTOS_DNS_Networking.c index 9d41bcd877..86b8532fb5 100644 --- a/source/FreeRTOS_DNS_Networking.c +++ b/source/FreeRTOS_DNS_Networking.c @@ -84,6 +84,11 @@ * going to be '0' i.e. success. Thus, return value is discarded */ ( void ) FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_SNDTIMEO, &( uxWriteTimeOut_ticks ), sizeof( TickType_t ) ); ( void ) FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_RCVTIMEO, &( uxReadTimeOut_ticks ), sizeof( TickType_t ) ); + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + /* Since this socket may be used for LLMNR or mDNS, set the multicast TTL to 1. */ + uint8_t ucMulticastTTL = 1; + ( void ) FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_IP_MULTICAST_TTL, &( ucMulticastTTL ), sizeof( ucMulticastTTL ) ); + #endif } return xSocket; diff --git a/source/FreeRTOS_DNS_Parser.c b/source/FreeRTOS_DNS_Parser.c index dbe426f50d..e07bddd1f1 100644 --- a/source/FreeRTOS_DNS_Parser.c +++ b/source/FreeRTOS_DNS_Parser.c @@ -936,6 +936,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; } @@ -951,8 +971,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_IGMP.c b/source/FreeRTOS_IGMP.c new file mode 100644 index 0000000000..a301c9fc5a --- /dev/null +++ b/source/FreeRTOS_IGMP.c @@ -0,0 +1,872 @@ +/* + * 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 ) + * - Rename this file + * - netif: netif-pointer in the setsockopt struct, null means "all interfaces" + * - Check task to task multicast ( maybe the network driver can handle to loop + * - Rework the sockopt structures to be the same and use IP_Address_t or IPv46_Address_t + * - Write a demo and add to https://github.com/FreeRTOS/FreeRTOS/tree/main/FreeRTOS-Plus/Demo + * - Sockets cannot handle v4 and v6 at the same time, so enforce this for setsockopt multicast calls + * - 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! + * 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 task to task multicast 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 duplicates it. I don't think this functionality is + * really needed and this may only be needed if we want a send/receive demo to work on a single device. Maybe + * it's better 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 to complete the demo application. + */ + +/* 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 ( ipconfigSUPPORT_IP_MULTICAST == 1 ) +/* *INDENT-ON* */ + + +/*-----------------------------------------------------------*/ + +/** @brief IGMP timer. Used for sending asynchronous IGMP reports. */ +static List_t xIGMP_ScheduleList; + +uint16_t usIPv4_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, + uint16_t usMaxRespTime ); + +static void prvSendMulticastReportOnNetworkInterface( MCastReportData_t * pxMRD, + NetworkInterface_t * pxInterface ); + + +/*-----------------------------------------------------------*/ + +void vIGMP_Init( void ) +{ + MACAddress_t xIGMP_MacAddress; + NetworkInterface_t * pxInterface; + MCastReportData_t * pxMRD; + + vListInitialise( &xIGMP_ScheduleList ); + #if ( ipconfigIGMP_SNOOPING != 0 ) + extern void vIgmpSnooping_Initialize( void ); + ( void ) vIgmpSnooping_Initialize(); + #endif + + /* Calculate multicast MAC address that belongs to the IP-address 224.0.0.1 */ + vSetMultiCastIPv4MacAddress( igmpIGMP_IP_ADDR, &xIGMP_MacAddress ); + + for( pxInterface = FreeRTOS_FirstNetworkInterface(); + pxInterface != NULL; + pxInterface = FreeRTOS_NextNetworkInterface( pxInterface ) ) + { + if( pxInterface->pfAddMulticastMAC != NULL ) + { + /* Tell the interface (the EMAC) to allow the IGMP_MacAddress. */ + pxInterface->pfAddMulticastMAC( xIGMP_MacAddress.ucBytes ); + } + } + + #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 bReportItemConsumed = xAddMulticastReportToList( pxMRD ); + + if( pdTRUE != bReportItemConsumed ) + { + /* 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 bReportItemConsumed = xAddMulticastReportToList( pxMRD ); + + if( pdTRUE != bReportItemConsumed ) + { + /* 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 bReportItemConsumed = xAddMulticastReportToList( pxMRD ); + + if( pdTRUE != bReportItemConsumed ) + { + /* 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 bReportItemConsumed = xAddMulticastReportToList( pxMRD ); + + if( pdTRUE != bReportItemConsumed ) + { + /* 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) */ + + vIGMPTimerReload( pdMS_TO_TICKS( igmpMULTICAST_EVENT_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 ), ( 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_Tx_Packet_t ) ) + { + pxMLD_Packet = ( ( const MLDv1_Tx_Packet_t * ) pxNetworkBuffer->pucEthernetBuffer ); + + if( pxMLD_Packet->xMLD.ucTypeOfMessage == ipICMP_MULTICAST_LISTENER_QUERY ) + { + prvScheduleMulticastReports( pdTRUE, ( void * ) ( &pxMLD_Packet->xMLD.xGroupAddress.ucBytes[ 0 ] ), FreeRTOS_ntohs( pxMLD_Packet->xMLD.usMaxResponseDelay ) ); + } + + #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( &xIGMP_ScheduleList ); + + 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 ) + { + ulRandom = 0; + } + + /* Generate a random countdown between 0 and usMaxCountdown - 1. */ + pxMRD->xCountDown = ulRandom % xMaxCountdown; + } + } + } /* for(;;) iterating over xIGMP_ScheduleList */ +} + +void prvScheduleMulticastReports( BaseType_t xIsIPv6, + void * pvGroupAddress, + uint16_t usMaxRespTime ) +{ + const ListItem_t * pxIterator; + const ListItem_t * xEnd; + uint32_t ulRandom; + MCastReportData_t * pxMRD; + const IPv6_Address_t * pxIPv6_GroupAddress; + const uint32_t * plIPv4_GroupAddress; + uint16_t usMaxCountdown; + static uint32_t ulNonRandomCounter = 0; /* In case the random number generator fails have a "not so random number" ready. */ + + /* Sanity enforcement. */ + + /* Go through the list of IGMP reports and schedule them. Note, the IGMP event is set at 100ms + * pxMRD->usMaxRespTime is in milliseconds */ + if( xIsIPv6 ) + { + usMaxCountdown = usMaxRespTime / igmpMULTICAST_EVENT_PERIOD_MS; + } + else + { + usMaxCountdown = usMaxRespTime; + } + + usMaxCountdown = max( 1, usMaxCountdown ); + + xEnd = listGET_END_MARKER( &xIGMP_ScheduleList ); + + 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, xIsUnspecified = pdTRUE; + + for( i = 0; i < ipSIZE_OF_IPv6_ADDRESS; i++ ) + { + if( pxIPv6_GroupAddress->ucBytes[ i ] != 0 ) + { + xIsUnspecified = pdFALSE; + break; + } + } + + /* Continue if this a general query, or if we are servicing a group-specific + * query and this report matches the group-specific query's destination address*/ + if( ( xIsUnspecified == pdTRUE ) || + ( 0 == memcmp( pxIPv6_GroupAddress->ucBytes, pxMRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes, ipSIZE_OF_IPv6_ADDRESS ) ) ) + { + xReschedule = pdTRUE; + } + } + else + { + /* IPv4 IGMP*/ + plIPv4_GroupAddress = ( const uint32_t * ) pvGroupAddress; + + /* Continue if this a general query, or if we are servicing a group-specific + * query and this report matches the group-specific query's destination address*/ + if( ( *plIPv4_GroupAddress == 0U ) || ( *plIPv4_GroupAddress == pxMRD->xMCastGroupAddress.xIPAddress.ulIP_IPv4 ) ) + { + xReschedule = pdTRUE; + /* IGMP maximum response time is stored in single byte. Every count represents 0.1s */ + usMaxCountdown = min( 255, usMaxCountdown ); + } + } + + 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 >= usMaxCountdown ) ) + { + 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 % usMaxCountdown; + } + else + { + /* This report is currently scheduled to be sent earlier than usMaxCountdown. + * Do nothing. */ + } + } + } /* for(;;) iterating over xIGMP_ScheduleList */ +} + +/** + * @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 void prvSendMulticastReportOnNetworkInterface( MCastReportData_t * pxMRD, + NetworkInterface_t * pxInterface ) +{ + NetworkEndPoint_t * pxEndPoint; + + /* 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; + } + + if( pxEndPoint->bits.bIPv6 == pdTRUE ) + { + /* IPv6 MLD report. + * 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 */ + /* To-do: if possible, allow sending from "::" as well. https://www.rfc-editor.org/rfc/rfc3810#section-5.2.13 allows it. */ + if( xIPv6_GetIPType( &( pxEndPoint->ipv6_settings.xIPAddress ) ) == eIPv6_LinkLocal ) + { + /* IPv6 MLD Reports. */ + pxMRD->xCountDown = ipconfigPERIODIC_MULTICAST_REPORT_INTERVAL; + prvSendMLD_Report_v1( &( pxMRD->xMCastGroupAddress.xIPAddress ), pxEndPoint ); + break; + } + } + else + { + /* IPv4 IGMP report. + * https://www.rfc-editor.org/rfc/rfc2236#section-10 suggests that reports + * should be discarded unless one can verify that the source IP belongs to the subnet + * of the interface. I have no idea what real life hosts follow that recommendation. */ + /* For now at least, don't send reports from 0.0.0.0 */ + if( pxEndPoint->ipv4_settings.ulIPAddress != 0 ) + { + pxMRD->xCountDown = ipconfigPERIODIC_MULTICAST_REPORT_INTERVAL; + prvSendIGMPv2( &( pxMRD->xMCastGroupAddress.xIPAddress ), igmpIGMP_MEMBERSHIP_REPORT_V2, pxEndPoint ); + break; + } + } + } /* end-point for(;;) */ + + /* If we could not find a suitable end-point on this interface pxMRD->xCountDown will be left at 0 + * and the event code will retry in igmpMULTICAST_EVENT_PERIOD_MS milliseconds. */ +} + +void vHandleIGMP_Event( 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( &xIGMP_ScheduleList ); + 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 ) ) + { + prvSendMulticastReportOnNetworkInterface( pxMRD, pxInterface ); + } + } + else + { + /* The report is assigned to a specific interface */ + prvSendMulticastReportOnNetworkInterface( pxMRD, pxMRD->pxInterface ); + } + } + 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 Removes an IGMP report from the list of reports. + * + * @param[in] pMCastGroup: The multicast group descriptor the specifies the group and interface. + */ +void vRemoveMulticastReportFromList( IP_Address_t * pxMulticastAddress, + UBaseType_t uxIsIPv6 ) +{ + const ListItem_t * pxIterator; + const ListItem_t * xEnd = listGET_END_MARKER( &xIGMP_ScheduleList ); + MCastReportData_t * pxIRD; + + for( pxIterator = ( const ListItem_t * ) listGET_NEXT( xEnd ); + pxIterator != ( const ListItem_t * ) xEnd; + pxIterator = ( const ListItem_t * ) listGET_NEXT( pxIterator ) ) + { + pxIRD = ( MCastReportData_t * ) listGET_LIST_ITEM_OWNER( pxIterator ); + + if( pxIRD->xMCastGroupAddress.xIs_IPv6 == pdTRUE_UNSIGNED ) + { + if( 0 == memcmp( pxIRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes, pxMulticastAddress->xIP_IPv6.ucBytes, ipSIZE_OF_IPv6_ADDRESS ) ) + { + break; + } + } + else + { + if( pxIRD->xMCastGroupAddress.xIPAddress.ulIP_IPv4 == pxMulticastAddress->ulIP_IPv4 ) + { + break; + } + } + } + + if( pxIterator != xEnd ) + { + /* Found a match. */ + if( pxIRD->xNumSockets > 0 ) + { + pxIRD->xNumSockets--; + } + + if( 0 == pxIRD->xNumSockets ) + { + if( pxIRD->xMCastGroupAddress.xIs_IPv6 ) + { + } + else + { + prvSendIGMPv2( &( pxIRD->xMCastGroupAddress.xIPAddress ), igmpIGMP_LEAVE_GROUP, FreeRTOS_FirstEndPoint( NULL ) ); + } + + /* ToDo: Maybe here and now is the right time to schedule a "Leave" message and free this item when the message gets sent. */ + ( void ) uxListRemove( &pxIRD->xListItem ); + vPortFree( pxIRD ); + } + } +} + +/** + * @brief Adds an IGMP report to the global list of reports. + * + * @param[in] pNewEntry: The multicast group descriptor to search for. + */ +BaseType_t xAddMulticastReportToList( MCastReportData_t * pNewEntry ) +{ + const ListItem_t * pxIterator; + const ListItem_t * xEnd = listGET_END_MARKER( &xIGMP_ScheduleList ); + MCastReportData_t * pxMRD; + UBaseType_t bFoundDuplicate = pdFALSE_UNSIGNED; + + configASSERT( pNewEntry != NULL ); + + { + uint8_t ucMCastGroupAddressString[ ( 45 + 1 ) ] = { 0 }; + + if( pNewEntry->xMCastGroupAddress.xIs_IPv6 == pdTRUE_UNSIGNED ) + { + FreeRTOS_inet_ntop6( ( void * ) &pNewEntry->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes[ 0 ], ucMCastGroupAddressString, ( 45 + 1 ) ); + } + else + { + FreeRTOS_inet_ntop4( ( void * ) &pNewEntry->xMCastGroupAddress.xIPAddress.ulIP_IPv4, ucMCastGroupAddressString, ( 45 + 1 ) ); + } + } + + 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->xMCastGroupAddress.xIs_IPv6 == pdTRUE_UNSIGNED ) && + ( 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 if( ( pxMRD->xMCastGroupAddress.xIs_IPv6 == pdFALSE_UNSIGNED ) && + ( pxMRD->xMCastGroupAddress.xIPAddress.ulIP_IPv4 == pNewEntry->xMCastGroupAddress.xIPAddress.ulIP_IPv4 ) ) + { + bFoundDuplicate = pdTRUE_UNSIGNED; + break; + } + } + + 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. During power up, our IP may be 0.0.0.0 and we don't + * want to send IGMP reports with an IP of 0.0.0.0. To get around this, the + * vHandleIGMP_Event code will keep xCountDown at 0 until the IP becomes non-zero. */ + pNewEntry->xCountDown = 0; + vListInsertEnd( &xIGMP_ScheduleList, &( pNewEntry->xListItem ) ); + } + + /* Inform the caller whether we consumed the item or not. */ + return ( bFoundDuplicate == pdTRUE_UNSIGNED ) ? pdFALSE : pdTRUE; +} + + + +static BaseType_t prvSendIGMPv2( IP_Address_t * pxGroupAddress, + uint8_t ucMessageType, + NetworkEndPoint_t * pxEndPoint ) +{ + NetworkBufferDescriptor_t * pxNetworkBuffer; + IGMPPacket_t * pxIGMPPacket; + portBASE_TYPE xReturn; + + 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( usIPv4_IGMP_Identification ); + usIPv4_IGMP_Identification++; + pxIPHeader->ucTimeToLive = 1; + pxIPHeader->usHeaderChecksum = 0U; + + /* The stack doesn't support fragments, so the fragment offset field must always be zero. + * The header was never memset to zero, so set both the fragment offset and fragmentation flags in one go. + */ + #if ( ipconfigFORCE_IP_DONT_FRAGMENT != 0 ) + pxIPHeader->usFragmentOffset = ipFRAGMENT_FLAGS_DONT_FRAGMENT; + #else + pxIPHeader->usFragmentOffset = 0U; + #endif + + + #if ( ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM == 0 ) + { + pxIPHeader->usHeaderChecksum = 0U; + pxIPHeader->usHeaderChecksum = usGenerateChecksum( 0U, ( uint8_t * ) &( pxIPHeader->ucVersionHeaderLength ), ipSIZE_OF_IPv4_HEADER ); + pxIPHeader->usHeaderChecksum = ~FreeRTOS_htons( pxIPHeader->usHeaderChecksum ); + } + #endif + + /* Calculate frame length */ + 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 */ + xReturn = pdFAIL; + } + + return xReturn; +} + +/** + * @brief Send an ICMPv6 Multicast Listener Report. + * + * @param[in] _EP_ FixMe: pxGroupAddress The multicast group address to that the report will hold. + * @param[in] _EP_ FixMe: pxEndPoint The outgoing end-point. + * + * @return _EP_ FixMe: When failed: pdFAIL, otherwise the PING sequence number. + */ +static BaseType_t prvSendMLD_Report_v1( IP_Address_t * pxGroupAddress, + NetworkEndPoint_t * pxEndPoint ) +{ + NetworkBufferDescriptor_t * pxNetworkBuffer = NULL; + MLDv1_Tx_Packet_t * pxMLRPacket; + BaseType_t xReturn = pdFAIL; + + /* Quick and dirty, use whatever endpoint we were given, or find the link-local end-point. + * ToDo: maybe expand this to use "::" if no link-local end-point is available yet as + * described in https://www.rfc-editor.org/rfc/rfc3810#section-5.2.13 */ + if( pxEndPoint == NULL ) + { + for( pxEndPoint = FreeRTOS_FirstEndPoint( NULL ); + pxEndPoint != NULL; + pxEndPoint = FreeRTOS_NextEndPoint( NULL, pxEndPoint ) ) + { + if( ( pxEndPoint->bits.bIPv6 == pdTRUE_UNSIGNED ) && ( xIPv6_GetIPType( &( pxEndPoint->ipv6_settings.xIPAddress ) ) == eIPv6_LinkLocal ) ) + { + break; + } + } + } + + if( pxEndPoint == NULL ) + { + /* No usable end-point found. */ + } + else if( ( ( pxNetworkBuffer = pxGetNetworkBufferWithDescriptor( sizeof( MLDv1_Tx_Packet_t ), 0 ) ) == NULL ) ) + { + /* No free buffer */ + } + else + { + /* Note: valid end-point and buffer */ + 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; + ( void ) memcpy( pxMLRPacket->xIPHeader.xSourceAddress.ucBytes, pxEndPoint->ipv6_settings.xIPAddress.ucBytes, 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; +} + + +/************************************************************************/ +/* Test code below this point */ +/************************************************************************/ + + +/*-----------------------------------------------------------*/ + + +/* *INDENT-OFF* */ +#endif /* if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ +/* *INDENT-ON* */ diff --git a/source/FreeRTOS_IP.c b/source/FreeRTOS_IP.c index f412e914ff..5cf7e45031 100644 --- a/source/FreeRTOS_IP.c +++ b/source/FreeRTOS_IP.c @@ -59,6 +59,9 @@ #include "FreeRTOS_DNS.h" #include "FreeRTOS_Routing.h" #include "FreeRTOS_ND.h" +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + #include "FreeRTOS_IGMP.h" +#endif /** @brief Time delay between repeated attempts to initialise the network hardware. */ #ifndef ipINITIALISATION_RETRY_DELAY @@ -467,6 +470,20 @@ static void prvProcessIPEventsAndTimers( void ) /* xQueueReceive() returned because of a normal time-out. */ break; + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + case eSocketOptAddMembership: + case eSocketOptDropMembership: + { + MulticastAction_t * pxMCA = ( MulticastAction_t * ) xReceivedEvent.pvData; + vModifyMulticastMembership( pxMCA, xReceivedEvent.eEventType ); + break; + } + + case eMulticastTimerEvent: + vHandleIGMP_Event(); + break; + #endif /* ( ipconfigSUPPORT_IP_MULTICAST != 0) */ + default: /* Should not get here. */ break; @@ -526,6 +543,11 @@ static void prvIPTask_Initialise( void ) } #endif /* ( ( ipconfigUSE_DNS_CACHE != 0 ) && ( ipconfigUSE_DNS != 0 ) ) */ + /* Init the list that will hold scheduled IGMP reports. */ + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + ( void ) vIGMP_Init(); + #endif + /* Initialisation is complete and events can now be processed. */ xIPTaskInitialised = pdTRUE; } @@ -631,8 +653,76 @@ TaskHandle_t FreeRTOS_GetIPTaskHandle( void ) */ void vIPNetworkUpCalls( struct xNetworkEndPoint * pxEndPoint ) { + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + MCastReportData_t * pxMRD; + IPv6_Type_t xAddressType; + MACAddress_t xMACAddress; + #endif /* ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ + pxEndPoint->bits.bEndPointUp = pdTRUE_UNSIGNED; + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + if( pxEndPoint->bits.bIPv6 == pdTRUE_UNSIGNED ) + { + /* Now that the network is up, pxEndPoint->ipv6_settings should hold the actual address of this + * end-point. For unicast addresses, generate the solicited-node multicast address that corresponds + * to the address and generate an MLD report for it. + * ToDo: Figure out what the proper place is to remove multicast addresses that are no longer valid. For + * example when a DHCPv6 lease expires. */ + xAddressType = xIPv6_GetIPType( &( pxEndPoint->ipv6_settings.xIPAddress ) ); + + if( ( xAddressType == eIPv6_LinkLocal ) || ( xAddressType == eIPv6_SiteLocal ) || ( xAddressType == eIPv6_Global ) ) + { + if( NULL != ( pxMRD = ( MCastReportData_t * ) pvPortMalloc( sizeof( MCastReportData_t ) ) ) ) + { + listSET_LIST_ITEM_OWNER( &( pxMRD->xListItem ), ( void * ) pxMRD ); + pxMRD->pxInterface = pxEndPoint->pxNetworkInterface; + pxMRD->xMCastGroupAddress.xIs_IPv6 = pdTRUE_UNSIGNED; + + /* Generate the solicited-node multicast address in the form of + * ff02::1:ffnn:nnnn, where nn:nnnn are the last 3 bytes of the IPv6 address. */ + pxMRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes[ 0 ] = 0xFFU; + pxMRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes[ 1 ] = 0x02U; + ( void ) memset( &pxMRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes[ 2 ], 0x00, 9 ); + pxMRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes[ 11 ] = 0x01U; + pxMRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes[ 12 ] = 0xFFU; + ( void ) memcpy( &pxMRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes[ 13 ], &pxEndPoint->ipv6_settings.xIPAddress.ucBytes[ 13 ], 3 ); + + if( pdTRUE != xAddMulticastReportToList( pxMRD ) ) + { + vPortFree( pxMRD ); + pxMRD = NULL; + } + else + { + /* The report was consumed, therefore it was added to the list. Tell the network + * driver to begin receiving the associated MAC address */ + if( pxEndPoint->pxNetworkInterface && ( pxEndPoint->pxNetworkInterface->pfAddMulticastMAC != NULL ) ) + { + xMACAddress.ucBytes[ 0 ] = 0x33; + xMACAddress.ucBytes[ 1 ] = 0x33; + xMACAddress.ucBytes[ 2 ] = 0xFF; + xMACAddress.ucBytes[ 3 ] = pxEndPoint->ipv6_settings.xIPAddress.ucBytes[ 13 ]; + xMACAddress.ucBytes[ 4 ] = pxEndPoint->ipv6_settings.xIPAddress.ucBytes[ 14 ]; + xMACAddress.ucBytes[ 5 ] = pxEndPoint->ipv6_settings.xIPAddress.ucBytes[ 15 ]; + pxEndPoint->pxNetworkInterface->pfAddMulticastMAC( xMACAddress.ucBytes ); + } + } + } + } + } /* if( pxEndPoint->bits.bIPv6 == pdTRUE_UNSIGNED ) */ + + /* 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. */ + /* _EP_: vRescheduleAllMulticastReports() is NOT declared in header files because I don't want to expose it to the user */ + extern void vRescheduleAllMulticastReports( NetworkInterface_t * pxInterface, + BaseType_t xMaxCountdown ); + vRescheduleAllMulticastReports( pxEndPoint->pxNetworkInterface, 5 ); + #endif /* ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ + #if ( ipconfigUSE_NETWORK_EVENT_HOOK == 1 ) #if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) { @@ -1310,6 +1400,7 @@ void FreeRTOS_SetEndPointConfiguration( const uint32_t * pulIPAddress, 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; @@ -1976,6 +2067,13 @@ static eFrameProcessingResult_t prvProcessIPPacket( const IPPacket_t * pxIPPacke break; #endif /* ( ipconfigUSE_IPv6 != 0 ) */ + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + case ipPROTOCOL_IGMP: + /* The IP packet contained an IGMP frame. */ + eReturn = eProcessIGMPPacket( pxNetworkBuffer ); + break; + #endif /* ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ + case ipPROTOCOL_UDP: /* The IP packet contained a UDP frame. */ diff --git a/source/FreeRTOS_IP_Timers.c b/source/FreeRTOS_IP_Timers.c index 0e2259ac5c..6c3309b090 100644 --- a/source/FreeRTOS_IP_Timers.c +++ b/source/FreeRTOS_IP_Timers.c @@ -55,6 +55,9 @@ #include "NetworkBufferManagement.h" #include "FreeRTOS_Routing.h" #include "FreeRTOS_DNS.h" +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + #include "FreeRTOS_IGMP.h" +#endif /*-----------------------------------------------------------*/ /** @brief 'xAllNetworksUp' becomes pdTRUE when all network interfaces are initialised @@ -110,6 +113,11 @@ static IPTimer_t xARPTimer; static IPTimer_t xDNSTimer; #endif +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + /** @brief IGMP timer. Used for sending scheduled IGMP Reports */ + static IPTimer_t xIGMPTimer; +#endif + /** @brief As long as not all networks are up, repeat initialisation by calling the * xNetworkInterfaceInitialise() function of the interfaces that are not ready. */ @@ -176,6 +184,15 @@ TickType_t xCalculateSleepTime( void ) } #endif + #if ( ipconfigSUPPORT_IP_MULTICAST == 1 ) + { + if( xIGMPTimer.ulRemainingTime < uxMaximumSleepTime ) + { + uxMaximumSleepTime = xIGMPTimer.ulRemainingTime; + } + } + #endif /* ipconfigSUPPORT_IP_MULTICAST */ + #if ( ipconfigDNS_USE_CALLBACKS != 0 ) { if( xDNSTimer.bActive != pdFALSE_UNSIGNED ) @@ -312,6 +329,16 @@ void vCheckNetworkTimers( void ) vSocketListenNextTime( NULL ); #endif /* ipconfigUSE_TCP == 1 */ + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + { + /* Is it time to send any IGMP reports? */ + if( prvIPTimerCheck( &xIGMPTimer ) != pdFALSE ) + { + ( void ) xSendMulticastTimerEvent(); + } + } + #endif /* ipconfigSUPPORT_IP_MULTICAST != 0 */ + /* Is it time to trigger the repeated NetworkDown events? */ if( xAllNetworksUp == pdFALSE ) { @@ -412,6 +439,20 @@ void vARPTimerReload( TickType_t xTime ) /*-----------------------------------------------------------*/ +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + +/** + * @brief Reload the IGMP timer. + * + * @param[in] xIgmpTickTime: The reload value. + */ + void vIGMPTimerReload( TickType_t xIgmpTickTime ) + { + prvIPTimerReload( &xIGMPTimer, pdMS_TO_TICKS( igmpMULTICAST_EVENT_PERIOD_MS ) ); + } +#endif /* ipconfigSUPPORT_IP_MULTICAST */ +/*-----------------------------------------------------------*/ + #if ( ipconfigDNS_USE_CALLBACKS != 0 ) /** diff --git a/source/FreeRTOS_IPv6.c b/source/FreeRTOS_IPv6.c index b021f3ef27..2a7ba6433e 100644 --- a/source/FreeRTOS_IPv6.c +++ b/source/FreeRTOS_IPv6.c @@ -386,7 +386,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. @@ -410,6 +410,7 @@ BaseType_t xCompareIPv6_Address( const IPv6_Address_t * pxLeft, ( pxRight->ucBytes[ 12 ] == 0xffU ) ) { /* This is an LLMNR address. */ + /* _EP_: I don't think this is LLMNR. Did someone mean to say "the solicited-node multicast address" ? */ xResult = memcmp( &( pxLeft->ucBytes[ 13 ] ), &( pxRight->ucBytes[ 13 ] ), 3 ); } else diff --git a/source/FreeRTOS_ND.c b/source/FreeRTOS_ND.c index dcbf4d6ebd..e63fca2efb 100644 --- a/source/FreeRTOS_ND.c +++ b/source/FreeRTOS_ND.c @@ -77,7 +77,7 @@ /* MISRA Ref 8.9.1 [File scoped variables] */ /* More details at: https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/blob/main/MISRA.md#rule-89 */ /* coverity[misra_c_2012_rule_8_9_violation] */ - static const uint8_t pcLOCAL_ALL_NODES_MULTICAST_IP[ ipSIZE_OF_IPv6_ADDRESS ] = { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; /* ff02:1 */ + static const uint8_t pcLOCAL_ALL_NODES_MULTICAST_IP[ ipSIZE_OF_IPv6_ADDRESS ] = { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; /* ff02::1 */ /** @brief All nodes on the local network segment: MAC address. */ static const uint8_t pcLOCAL_ALL_NODES_MULTICAST_MAC[ ipMAC_ADDRESS_LENGTH_BYTES ] = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x01 }; @@ -801,6 +801,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; @@ -1130,6 +1131,26 @@ break; #endif /* ( ipconfigUSE_RA != 0 ) */ + #if ( ipconfigSUPPORT_IP_MULTICAST == 1 ) + 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 /* if ( ipconfigSUPPORT_IP_MULTICAST == 1 ) */ + default: /* All possible values are included here above. */ break; diff --git a/source/FreeRTOS_Sockets.c b/source/FreeRTOS_Sockets.c index 0d900d0cd3..166bec4499 100644 --- a/source/FreeRTOS_Sockets.c +++ b/source/FreeRTOS_Sockets.c @@ -52,7 +52,9 @@ #include "FreeRTOS_DNS.h" #include "NetworkBufferManagement.h" #include "FreeRTOS_Routing.h" - +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + #include "FreeRTOS_IGMP.h" +#endif #if ( ipconfigUSE_TCP_MEM_STATS != 0 ) #include "tcp_mem_stats.h" #endif @@ -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 ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + 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 /* ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ + /*-----------------------------------------------------------*/ /** @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 ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + pxSocket->u.xUDP.ucMulticastMaxHops = ipconfigMULTICAST_DEFAULT_TTL; + memset( &( pxSocket->u.xUDP.xMulticastAddress ), 0, sizeof( pxSocket->u.xUDP.xMulticastAddress ) ); + #endif } #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 ( ipconfigSUPPORT_IP_MULTICAST == 1 ) + /* _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 /* ( ipconfigSUPPORT_IP_MULTICAST == 1 ) */ + { + /* 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 ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + 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 /* ( ipconfigSUPPORT_IP_MULTICAST == 1 ) */ + { + /* 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 ) */ @@ -1430,6 +1472,7 @@ static int32_t prvSendUDPPacket( const FreeRTOS_Socket_t * pxSocket, pxNetworkBuffer->usPort = pxDestinationAddress->sin_port; pxNetworkBuffer->usBoundPort = ( uint16_t ) socketGET_SOCKET_PORT( pxSocket ); + /* The socket options are passed to the IP layer in the * space that will eventually get used by the Ethernet header. */ pxNetworkBuffer->pucEthernetBuffer[ ipSOCKET_OPTIONS_OFFSET ] = pxSocket->ucSocketOptions; @@ -2130,6 +2173,13 @@ void * vSocketClose( FreeRTOS_Socket_t * pxSocket ) #endif /* ipconfigETHERNET_DRIVER_FILTERS_PACKETS */ } + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + if( pxSocket->ucProtocol == ipPROTOCOL_UDP ) + { + prvDropMulticastMembership( pxSocket ); + } + #endif /* ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ + /* Now the socket is not bound the list of waiting packets can be * drained. */ if( pxSocket->ucProtocol == ( uint8_t ) FREERTOS_IPPROTO_UDP ) @@ -2927,6 +2977,14 @@ BaseType_t FreeRTOS_setsockopt( Socket_t xSocket, break; #endif /* ipconfigUSE_TCP == 1 */ + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + case FREERTOS_SO_IP_MULTICAST_TTL: + case FREERTOS_SO_IP_ADD_MEMBERSHIP: + case FREERTOS_SO_IP_DROP_MEMBERSHIP: + xReturn = prvSetOptionMulticast( xSocket, lLevel, lOptionName, pvOptionValue, uxOptionLength ); + break; + #endif /* (ipconfigSUPPORT_IP_MULTICAST != 0) */ + default: /* No other options are handled. */ xReturn = -pdFREERTOS_ERRNO_ENOPROTOOPT; @@ -6325,4 +6383,435 @@ void * pvSocketGetSocketID( const ConstSocket_t xSocket ) } #endif /* ipconfigSUPPORT_SELECT_FUNCTION */ + #endif /* 0 */ + +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + +/** + * @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 * pMReq = ( 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( pdFALSE == xIsIPv6AllowedMulticast( &( pMReq->xMulticastGroup.xIP_IPv6 ) ) ) + { + /* Invalid multicast group address */ + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + } + else + { + if( pdFALSE == xIsIPv4Multicast( pMReq->xMulticastGroup.ulIP_IPv4 ) ) + { + /* Invalid multicast group address */ + break; /* will return -pdFREERTOS_ERRNO_EINVAL */ + } + } + + /* 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 = NULL; /* ToDo: Assign a specific interface if requested by the user */ + + /* 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, pMReq->xMulticastGroup.xIP_IPv6.ucBytes, ipSIZE_OF_IPv6_ADDRESS ); + memcpy( pxMRD->xMCastGroupAddress.xIPAddress.xIP_IPv6.ucBytes, pMReq->xMulticastGroup.xIP_IPv6.ucBytes, ipSIZE_OF_IPv6_ADDRESS ); + } + else + { + pxMCA->xMulticastGroup.ulIP_IPv4 = pMReq->xMulticastGroup.ulIP_IPv4; + pxMRD->xMCastGroupAddress.xIPAddress.ulIP_IPv4 = pMReq->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 = pMReq->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( 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( 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 */ + } + } + + if( pMReq->pxMulticastNetIf != pxSocket->u.xUDP.pxMulticastNetIf ) + { + /* The socket was not subscribed on this interface. */ + 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 bReportItemConsumed = 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 address. */ + ( void ) memcpy( &( pxSocket->u.xUDP.xMulticastAddress ), &( pxMulticastAction->xMulticastGroup ), sizeof( pxSocket->u.xUDP.xMulticastAddress ) ); + + if( pxSocket->bits.bIsIPv6 == pdFALSE ) + { + vSetMultiCastIPv4MacAddress( pxMulticastAction->xMulticastGroup.ulIP_IPv4, MCastMacBytes ); + } + else + { + vSetMultiCastIPv6MacAddress( &( pxMulticastAction->xMulticastGroup.xIP_IPv6 ), MCastMacBytes ); + } + + /* Inform the network driver */ + if( pxNetIf ) + { + if( pxNetIf->pfAddMulticastMAC != NULL ) + { + pxNetIf->pfAddMulticastMAC( MCastMacBytes ); + } + } + else + { + /* pxNetIf is NULL. For IPv4 that means "use all interfaces". For IPv6, that means "use the default multicast interface" + * FreeRTOS+TCP does not have the notion of default multicast interface so for now, subscribe on all. */ + for( pxNetIf = FreeRTOS_FirstNetworkInterface(); pxNetIf != NULL; pxNetIf = FreeRTOS_NextNetworkInterface( pxNetIf ) ) + { + if( pxNetIf->pfAddMulticastMAC != NULL ) + { + pxNetIf->pfAddMulticastMAC( 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; + } + } + + bReportItemConsumed = xAddMulticastReportToList( pxMulticastAction->pxMCastReportData ); + } while( pdFALSE ); + + /* If the report was a special case address or was not consumed by + * xAddMulticastReportToList(), free the multicast report. */ + if( bReportItemConsumed == 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 ]; + UBaseType_t uxLeaveGroup = pdFALSE_UNSIGNED; + NetworkInterface_t * pxNetIf = pxSocket->u.xUDP.pxMulticastNetIf; + + if( pxSocket->bits.bIsIPv6 == pdTRUE_UNSIGNED ) + { + /* IPv6 */ + if( xIsIPv6AllowedMulticast( &( pxSocket->u.xUDP.xMulticastAddress.xIP_IPv6 ) ) ) + { + uxLeaveGroup = pdTRUE_UNSIGNED; + + if( pxNetIf ) + { + if( pxNetIf->pfRemoveMulticastMAC != NULL ) + { + vSetMultiCastIPv6MacAddress( &( pxSocket->u.xUDP.xMulticastAddress.xIP_IPv6 ), MCastMacBytes ); + pxNetIf->pfRemoveMulticastMAC( MCastMacBytes ); + } + } + else + { + } + } + 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. */ + } + } + else + { + /* IPv4 */ + if( xIsIPv4Multicast( pxSocket->u.xUDP.xMulticastAddress.ulIP_IPv4 ) ) + { + uxLeaveGroup = pdTRUE_UNSIGNED; + + vSetMultiCastIPv4MacAddress( pxSocket->u.xUDP.xMulticastAddress.ulIP_IPv4, MCastMacBytes ); + + if( pxNetIf ) + { + if( pxNetIf->pfRemoveMulticastMAC != NULL ) + { + pxNetIf->pfRemoveMulticastMAC( MCastMacBytes ); + } + } + else + { + /* pxNetIf is NULL. For IPv4 that means "all interfaces", so unsubscribe from all. */ + for( pxNetIf = FreeRTOS_FirstNetworkInterface(); pxNetIf != NULL; pxNetIf = FreeRTOS_NextNetworkInterface( pxNetIf ) ) + { + if( pxNetIf->pfRemoveMulticastMAC != NULL ) + { + pxNetIf->pfRemoveMulticastMAC( 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. */ + } + } + + if( uxLeaveGroup == pdTRUE_UNSIGNED ) + { + /* Locate the IGMP/MLD report for this group. Decrement its socket count and + * if it becomes zero, remove it from the list and free it. */ + vRemoveMulticastReportFromList( &( 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 /* ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ diff --git a/source/FreeRTOS_UDP_IPv4.c b/source/FreeRTOS_UDP_IPv4.c index 9562ea90d8..741390cd24 100644 --- a/source/FreeRTOS_UDP_IPv4.c +++ b/source/FreeRTOS_UDP_IPv4.c @@ -63,6 +63,9 @@ /* *INDENT-OFF* */ #if( ipconfigUSE_IPv4 != 0 ) /* *INDENT-ON* */ +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + #include "FreeRTOS_IGMP.h" +#endif /** @brief The expected IP version and header length coded into the IP header itself. */ #define ipIP_VERSION_AND_HEADER_LENGTH_BYTE ( ( uint8_t ) 0x45 ) @@ -180,6 +183,8 @@ void vProcessGeneratedUDPPacket_IPv4( NetworkBufferDescriptor_t * const pxNetwor pvCopyDest = &pxNetworkBuffer->pucEthernetBuffer[ sizeof( MACAddress_t ) ]; ( void ) memcpy( pvCopyDest, pvCopySource, sizeof( xDefaultPartUDPPacketHeader ) ); + pxIPHeader->ucTimeToLive = pxNetworkBuffer->ucMaximumHops; + #if ipconfigSUPPORT_OUTGOING_PINGS == 1 if( pxNetworkBuffer->usPort == ( uint16_t ) ipPACKET_CONTAINS_ICMP_DATA ) { @@ -209,26 +214,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; @@ -368,6 +353,33 @@ BaseType_t xProcessReceivedUDPPacket_IPv4( NetworkBufferDescriptor_t * pxNetwork { if( pxSocket != NULL ) { + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + + /* If this incoming packet is a multicast, we may have a socket for the port, but we still need + * to ensure the socket is subscribed to that particular multicast group. Note: Since this stack + * does not support port reusing, we don't have to worry about two UDP sockets bound to the exact same + * local port, but subscribed to different multicast groups. If this was allowed, this check + * would have to be moved to the pxUDPSocketLookup() function itself. */ + if( xIsIPv4Multicast( pxUDPPacket->xIPHeader.ulDestinationIPAddress ) ) + { + if( pxSocket->u.xUDP.xMulticastAddress.ulIP_IPv4 != pxUDPPacket->xIPHeader.ulDestinationIPAddress ) + { + /* This socket is not subscribed to this multicast group. 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 /* ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ + 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 7644780da4..e6d894d6ff 100644 --- a/source/FreeRTOS_UDP_IPv6.c +++ b/source/FreeRTOS_UDP_IPv6.c @@ -254,6 +254,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 @@ -262,7 +264,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 */ @@ -274,7 +275,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; @@ -463,6 +463,34 @@ BaseType_t xProcessReceivedUDPPacket_IPv6( NetworkBufferDescriptor_t * pxNetwork if( pxSocket != NULL ) { + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + + /* If this incoming packet is a multicast, we may have a socket for the port, but we still need + * to ensure the socket is subscribed to that particular multicast group. Note: Since this stack + * does not support port reusing, we don't have to worry about two UDP sockets bound to the exact same + * local port, but subscribed to different multicast groups. If this was allowed, this check + * would have to be moved to the pxUDPSocketLookup() function itself. */ + if( 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 ) + { + /* This socket is not subscribed to this multicast group. 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 /* ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ + 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 97a0b08684..35bc72e46f 100644 --- a/source/include/FreeRTOSIPConfigDefaults.h +++ b/source/include/FreeRTOSIPConfigDefaults.h @@ -3308,6 +3308,75 @@ /*---------------------------------------------------------------------------*/ +/* + * ipconfigSUPPORT_IP_MULTICAST + * + * Type: BaseType_t ( ipconfigENABLE | ipconfigDISABLE ) + * + * When set to ipconfigENABLE, this macro will + * enable the reception of multicast groups addresses. */ +#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 + +/*---------------------------------------------------------------------------*/ + +/* + * ipconfigSUPPORT_IP_MULTICAST + * + * Type: BaseType_t ( ipconfigENABLE | ipconfigDISABLE ) + * + * Set to ipconfigENABLE if you plan on implementing IGMP snooping. + * When enabled, the user must implement the following hooks: + * eFrameProcessingResult_t eApplicationIgmpFrameReceivedHook( NetworkBufferDescriptor_t * pxNetworkBuffer ) + * void vApplicationIgmpSendLocalMessageHook( NetworkBufferDescriptor_t * pxNetworkBuffer, uint8_t ucIgmpMsgType, uint32_t uiMulticastGroup ) + * void vApplicationIgmpEventHook( void ) + */ +#ifndef ipconfigIGMP_SNOOPING + #define ipconfigIGMP_SNOOPING ipconfigDISABLE +#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 67dc7f6d5b..533c523dcd 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..9092613158 --- /dev/null +++ b/source/include/FreeRTOS_IGMP.h @@ -0,0 +1,97 @@ +/* + * FreeRTOS+TCP V2.4.0 + * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +#ifndef FREERTOS_IGMP_H + #define FREERTOS_IGMP_H + + #ifdef __cplusplus + extern "C" { + #endif + +/* Application level configuration options. */ + #include "FreeRTOSIPConfig.h" + #include "FreeRTOSIPConfigDefaults.h" + #include "FreeRTOS_Sockets.h" + #include "IPTraceMacroDefaults.h" + #include "FreeRTOS_Stream_Buffer.h" + #if ( ipconfigUSE_TCP == 1 ) + #include "FreeRTOS_TCP_WIN.h" + #include "FreeRTOS_TCP_IP.h" + #endif + + #include "semphr.h" + + #include "event_groups.h" + + +/** @brief IGMP times events at 100ms. Use this interval for MLD as well. */ + #define igmpMULTICAST_EVENT_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 and v2 membership report. */ + #define igmpIGMP_MEMBERSHIP_REPORT_V2 ( ( uint8_t ) 0x16U ) /**< IGMP v1 and 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; + + void vIGMP_Init( void ); + void vHandleIGMP_Event( void ); + BaseType_t xAddMulticastReportToList( MCastReportData_t * pNewEntry ); + eFrameProcessingResult_t eProcessIGMPPacket( NetworkBufferDescriptor_t * const pxNetworkBuffer ); + void vProcessMLDPacket( NetworkBufferDescriptor_t * const pxNetworkBuffer ); + + + #ifdef __cplusplus +} /* extern "C" */ + #endif + +#endif /* FREERTOS_IP_PRIVATE_H */ diff --git a/source/include/FreeRTOS_IP.h b/source/include/FreeRTOS_IP.h index 1995b9ffec..b50f530216 100644 --- a/source/include/FreeRTOS_IP.h +++ b/source/include/FreeRTOS_IP.h @@ -167,6 +167,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..af4e4254e1 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 ( ipconfigSUPPORT_IP_MULTICAST != 0 ) +/** @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 ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ + /* 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 c69232e9bc..17b35961e5 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; /** @@ -643,6 +646,17 @@ typedef struct UDPSOCKET */ FOnUDPSent_t pxHandleSent; /**< Function pointer to handle the events after a successful send. */ #endif /* ipconfigUSE_CALLBACKS */ + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + 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 } IPUDPSocket_t; /* Formally typedef'd as eSocketEvent_t. */ @@ -878,6 +892,15 @@ BaseType_t xIsCallingFromIPTask( void ); #endif /* ipconfigSUPPORT_SELECT_FUNCTION */ +#if ( ipconfigSUPPORT_IP_MULTICAST == 1 ) + struct xMCastGroupDesc; + void vModifyMulticastMembership( struct xMCastGroupDesc * pxMulticastAction, + uint8_t bAction ); + void vRemoveMulticastReportFromList( IP_Address_t * pxMulticastAddress, + UBaseType_t xIsIPv6 ); + BaseType_t xSendMulticastTimerEvent( void ); +#endif /* 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..652816bb60 100644 --- a/source/include/FreeRTOS_IP_Timers.h +++ b/source/include/FreeRTOS_IP_Timers.h @@ -130,6 +130,10 @@ void vTCPTimerReload( TickType_t xTime ); #if ( ipconfigDNS_USE_CALLBACKS != 0 ) + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + void vIGMPTimerReload( TickType_t xIgmpTickTime ); + #endif + /** * Reload the DNS timer. */ diff --git a/source/include/FreeRTOS_IP_Utils.h b/source/include/FreeRTOS_IP_Utils.h index f34255d8de..acae7416c3 100644 --- a/source/include/FreeRTOS_IP_Utils.h +++ b/source/include/FreeRTOS_IP_Utils.h @@ -65,6 +65,33 @@ #endif /* *INDENT-ON* */ +#if ( ipconfigSUPPORT_IP_MULTICAST == 1 ) + /** @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 /* ( ipconfigSUPPORT_IP_MULTICAST == 1 ) */ + + /* 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 1e5ad5d07b..1195aafb78 100644 --- a/source/include/FreeRTOS_IPv6_Private.h +++ b/source/include/FreeRTOS_IPv6_Private.h @@ -211,6 +211,78 @@ typedef struct xICMPRouterSolicitation_IPv6 ICMPRouterSolicitation_IPv6_t; typedef struct xICMPPrefixOption_IPv6 ICMPPrefixOption_IPv6_t; #endif /* ipconfigUSE_RA != 0 */ +#if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + #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 /* ( ipconfigSUPPORT_IP_MULTICAST != 0 ) */ + /*-----------------------------------------------------------*/ /* Nested protocol packets. */ /*-----------------------------------------------------------*/ diff --git a/source/include/FreeRTOS_Routing.h b/source/include/FreeRTOS_Routing.h index 8b480d8f6b..603806db68 100644 --- a/source/include/FreeRTOS_Routing.h +++ b/source/include/FreeRTOS_Routing.h @@ -54,6 +54,14 @@ /* Return true as long as the LinkStatus on the PHY is present. */ typedef BaseType_t ( * GetPhyLinkStatusFunction_t ) ( struct xNetworkInterface * pxDescriptor ); + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) +/* Adds a multicast MAC address to the list of received MAC addresses for this interface */ + typedef void ( * NetworkInterfaceAddMulticastMACFunction_t ) ( const uint8_t * MacAddressBytes ); + +/* Removes a multicast MAC address from the list of MAC addresses that this interface receives */ + typedef void ( * NetworkInterfaceRemoveMulticastMACFunction_t ) ( const uint8_t * MacAddressBytes ); + #endif + /** @brief These NetworkInterface access functions are collected in a struct: */ typedef struct xNetworkInterface { @@ -62,6 +70,10 @@ NetworkInterfaceInitialiseFunction_t pfInitialise; /**< This function will be called upon initialisation and repeated until it returns pdPASS. */ NetworkInterfaceOutputFunction_t pfOutput; /**< This function is supposed to send out a packet. */ GetPhyLinkStatusFunction_t pfGetPhyLinkStatus; /**< This function will return pdTRUE as long as the PHY Link Status is high. */ + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + NetworkInterfaceAddMulticastMACFunction_t pfAddMulticastMAC; + NetworkInterfaceAddMulticastMACFunction_t pfRemoveMulticastMAC; + #endif struct { uint32_t diff --git a/source/include/FreeRTOS_Sockets.h b/source/include/FreeRTOS_Sockets.h index 60b127e3ee..f5749cd568 100644 --- a/source/include/FreeRTOS_Sockets.h +++ b/source/include/FreeRTOS_Sockets.h @@ -148,8 +148,15 @@ #endif #if ( ipconfigUSE_TCP == 1 ) - #define FREERTOS_SO_SET_LOW_HIGH_WATER ( 18 ) + #define FREERTOS_SO_SET_LOW_HIGH_WATER ( 18 ) #endif + + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + #define FREERTOS_SO_IP_MULTICAST_TTL ( 19 ) /* TTL value to me used when sending multicast packets. Defaults to ipconfigMULTICAST_DEFAULT_TTL */ + #define FREERTOS_SO_IP_ADD_MEMBERSHIP ( 20 ) /* Mark the socket as able to receive multicast messages from a multicast group address */ + #define FREERTOS_SO_IP_DROP_MEMBERSHIP ( 21 ) /* Remove membership from a multicast group address */ + #endif /* (ipconfigSUPPORT_IP_MULTICAST != 0) */ + #define FREERTOS_INADDR_ANY ( 0U ) /* The 0.0.0.0 IPv4 address. */ #if ( 0 ) /* Not Used */ diff --git a/source/portable/NetworkInterface/DriverSAM/NetworkInterface.c b/source/portable/NetworkInterface/DriverSAM/NetworkInterface.c index 81f654e033..a7cfb55029 100644 --- a/source/portable/NetworkInterface/DriverSAM/NetworkInterface.c +++ b/source/portable/NetworkInterface/DriverSAM/NetworkInterface.c @@ -188,13 +188,16 @@ 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 ); NetworkInterface_t * pxSAM_FillInterfaceDescriptor( BaseType_t xEMACIndex, NetworkInterface_t * pxInterface ); +static void prvAddMulticastMACAddress( const uint8_t * ucMacAddress ); +static void prvRemoveMulticastMACAddress( const uint8_t * ucMacAddress ); + /* * Handle transmission errors. */ @@ -202,7 +205,6 @@ static void hand_tx_errors( void ); /* Functions to set the hash table for multicast addresses. */ static uint16_t prvGenerateCRC16( const uint8_t * pucAddress ); -static void prvAddMulticastMACAddress( const uint8_t * ucMacAddress ); /* Checks IP queue, buffers, and semaphore and logs diagnostic info if configured */ static void vCheckBuffersAndQueue( void ); @@ -521,6 +523,10 @@ NetworkInterface_t * pxSAM_FillInterfaceDescriptor( BaseType_t xEMACIndex, pxInterface->pfInitialise = prvSAM_NetworkInterfaceInitialise; pxInterface->pfOutput = prvSAM_NetworkInterfaceOutput; pxInterface->pfGetPhyLinkStatus = prvSAM_GetPhyLinkStatus; + #if ( ipconfigSUPPORT_IP_MULTICAST != 0 ) + pxInterface->pfAddMulticastMAC = prvAddMulticastMACAddress; + pxInterface->pfRemoveMulticastMAC = prvRemoveMulticastMACAddress; + #endif FreeRTOS_AddNetworkInterface( pxInterface ); @@ -718,35 +724,32 @@ static BaseType_t prvGMACInit( NetworkInterface_t * pxInterface ) /* set Multicast Hash Enable. */ GMAC->GMAC_NCFGR |= GMAC_NCFGR_MTIHEN; - #if ( ipconfigUSE_LLMNR == 1 ) - { + #if ( ipconfigUSE_LLMNR != 0 ) prvAddMulticastMACAddress( xLLMNR_MacAddress.ucBytes ); - } #endif /* ipconfigUSE_LLMNR */ + #if ( ipconfigUSE_MDNS != 0 ) + prvAddMulticastMACAddress( xMDNS_MacAddress.ucBytes ); + #endif /* ipconfigUSE_MDNS */ + #if ( ipconfigUSE_IPv6 != 0 ) { - NetworkEndPoint_t * pxEndPoint; + /* Register the Link-Local All-Nodes address */ + /* FF02::1 --> 33-33-00-00-00-01 */ + uint8_t pcLOCAL_ALL_NODES_MULTICAST_MAC[ ipMAC_ADDRESS_LENGTH_BYTES ] = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x01 }; + prvAddMulticastMACAddress( pcLOCAL_ALL_NODES_MULTICAST_MAC ); + #if ( ipconfigUSE_LLMNR == 1 ) { prvAddMulticastMACAddress( xLLMNR_MacAddressIPv6.ucBytes ); } #endif /* ipconfigUSE_LLMNR */ - for( pxEndPoint = FreeRTOS_FirstEndPoint( pxMyInterface ); - pxEndPoint != NULL; - pxEndPoint = FreeRTOS_NextEndPoint( pxMyInterface, pxEndPoint ) ) + #if ( ipconfigUSE_MDNS != 0 ) { - if( pxEndPoint->bits.bIPv6 != pdFALSE_UNSIGNED ) - { - uint8_t ucMACAddress[ 6 ] = { 0x33, 0x33, 0xff, 0, 0, 0 }; - - ucMACAddress[ 3 ] = pxEndPoint->ipv6_settings.xIPAddress.ucBytes[ 13 ]; - ucMACAddress[ 4 ] = pxEndPoint->ipv6_settings.xIPAddress.ucBytes[ 14 ]; - ucMACAddress[ 5 ] = pxEndPoint->ipv6_settings.xIPAddress.ucBytes[ 15 ]; - prvAddMulticastMACAddress( ucMACAddress ); - } + prvAddMulticastMACAddress( xMDNS_MACAddressIPv6.ucBytes ); } + #endif /* ipconfigUSE_MDNS */ } #endif /* ipconfigUSE_IPv6 */ @@ -785,6 +788,12 @@ static BaseType_t prvGMACInit( NetworkInterface_t * pxInterface ) } /*-----------------------------------------------------------*/ +#define GMAC_ADDRESS_HASH_BITS ( 64U ) +#define GMAC_ADDRESS_HASH_MASK ( GMAC_ADDRESS_HASH_BITS - 1 ) +#define GMAC_ADDRESS_HASH_COUNTERS_MAX_VALUE ( 255U ) +static uint8_t prvAddressHashCounters[ GMAC_ADDRESS_HASH_BITS ] = { 0 }; +static uint64_t prvAddressHashBitMask = ( 0 ); + static uint16_t prvGenerateCRC16( const uint8_t * pucAddress ) { uint16_t usSum; @@ -805,30 +814,100 @@ static uint16_t prvGenerateCRC16( const uint8_t * pucAddress ) usSum ^= ( usValues[ 4 ] >> 4 ) ^ ( usValues[ 4 ] << 2 ); usSum ^= ( usValues[ 5 ] >> 2 ) ^ ( usValues[ 5 ] << 4 ); - usSum &= 0x3FU; + usSum &= GMAC_ADDRESS_HASH_MASK; return usSum; } /*-----------------------------------------------------------*/ static void prvAddMulticastMACAddress( const uint8_t * ucMacAddress ) { - uint32_t ulMask; - uint16_t usIndex; + uint8_t ucBinNumber; + + if( NULL == ucMacAddress ) + { + return; + } + + #if ( ipconfigIGMP_SNOOPING != 0 ) + /* If we're doing IGMP snooping, we are already receiving all multicasts, */ + /* so nothing to do here */ + return; + #endif - usIndex = prvGenerateCRC16( ucMacAddress ); + DebugPrintf( "prvAddMulticastMACAddress %02X:%02X:%02X:%02X:%02X:%02X", + ucMacAddress[ 0 ], ucMacAddress[ 1 ], ucMacAddress[ 2 ], ucMacAddress[ 3 ], ucMacAddress[ 4 ], ucMacAddress[ 5 ] ); - ulMask = 1U << ( usIndex % 32 ); - if( usIndex < 32U ) + ucBinNumber = prvGenerateCRC16( ucMacAddress ); + + vTaskSuspendAll(); + + /* If the bin counter corresponding to this mac address is already non-zero, */ + /* a multicast address with the same hash has already been added, so there's nothing more to do. */ + if( 0 == prvAddressHashCounters[ ucBinNumber ] ) { - /* 0 .. 31 */ - GMAC->GMAC_HRB |= ulMask; + /* This bin counter is zero, so this is the first time we are registering a MAC with this hash. */ + prvAddressHashBitMask |= ( uint64_t ) ( ( uint64_t ) 1 << ucBinNumber ); + gmac_set_hash64( GMAC, prvAddressHashBitMask ); + gmac_enable_multicast_hash( GMAC, true ); } - else + + /* Increment the counter, but make sure we don't overflow it. */ + if( prvAddressHashCounters[ ucBinNumber ] < GMAC_ADDRESS_HASH_COUNTERS_MAX_VALUE ) + { + prvAddressHashCounters[ ucBinNumber ]++; + } + + xTaskResumeAll(); +} + +/* Only called from the IPTask, so no thread-safety is required. */ +static void prvRemoveMulticastMACAddress( const uint8_t * ucMacAddress ) +{ + uint8_t ucBinNumber; + + if( NULL == ucMacAddress ) { - /* 32 .. 63 */ - GMAC->GMAC_HRT |= ulMask; + return; } + + #if ( ipconfigIGMP_SNOOPING != 0 ) + /* If we're doing IGMP snooping, we are already receiving all multicasts, */ + /* so nothing to do here */ + return; + #endif + + DebugPrintf( "prvRemoveMulticastMACAddress %02X:%02X:%02X:%02X:%02X:%02X", + ucMacAddress[ 0 ], ucMacAddress[ 1 ], ucMacAddress[ 2 ], ucMacAddress[ 3 ], ucMacAddress[ 4 ], ucMacAddress[ 5 ] ); + + ucBinNumber = prvGenerateCRC16( ucMacAddress ); + + vTaskSuspendAll(); + + if( prvAddressHashCounters[ ucBinNumber ] > 0 ) + { + /* If so many multicasts with the same hash were added that the bin counter was maxed out, */ + /* we don't really know how many times we can decrement before actually unregistering this hash. */ + /* Because of this, if the bin counter ever maxes out, we can never unregister the hash. */ + if( prvAddressHashCounters[ ucBinNumber ] < GMAC_ADDRESS_HASH_COUNTERS_MAX_VALUE ) + { + prvAddressHashCounters[ ucBinNumber ]--; + + if( 0 == prvAddressHashCounters[ ucBinNumber ] ) + { + uint64_t hash = ( uint64_t ) ( ( uint64_t ) 1 << ucBinNumber ); + prvAddressHashBitMask &= ~hash; + gmac_set_hash64( GMAC, prvAddressHashBitMask ); + + if( 0 == prvAddressHashBitMask ) + { + gmac_enable_multicast_hash( GMAC, false ); + } + } + } + } + + xTaskResumeAll(); } /*-----------------------------------------------------------*/ @@ -923,7 +1002,7 @@ void vGMACGenerateChecksum( uint8_t * pucBuffer, ProtocolPacket_t * xProtPacket = ( ProtocolPacket_t * ) pucBuffer; /* The SAM4E has problems offloading checksums for transmission. - * The SAME70 does not set the CRC for ICMP packets (ping). */ + * The SAME70 does not set the CRC for ICMP or IGMP packets. */ if( xProtPacket->xICMPPacket.xEthernetHeader.usFrameType == ipIPv4_FRAME_TYPE ) {