From 098f8621634f1cbdd1253c9957eed09a505223f5 Mon Sep 17 00:00:00 2001 From: Guo Mang Date: Thu, 27 Apr 2017 11:16:34 +0800 Subject: NetWorkPkg: Move to new location Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Guo Mang --- Core/NetworkPkg/Ip6Dxe/Ip6Mld.c | 908 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 908 insertions(+) create mode 100644 Core/NetworkPkg/Ip6Dxe/Ip6Mld.c (limited to 'Core/NetworkPkg/Ip6Dxe/Ip6Mld.c') diff --git a/Core/NetworkPkg/Ip6Dxe/Ip6Mld.c b/Core/NetworkPkg/Ip6Dxe/Ip6Mld.c new file mode 100644 index 0000000000..4a418fade5 --- /dev/null +++ b/Core/NetworkPkg/Ip6Dxe/Ip6Mld.c @@ -0,0 +1,908 @@ +/** @file + Multicast Listener Discovery support routines. + + Copyright (c) 2009 - 2010, Intel Corporation. All rights reserved.
+ + This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php. + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + +**/ + +#include "Ip6Impl.h" + +/** + Create a IP6_MLD_GROUP list entry node and record to IP6 service binding data. + + @param[in, out] IpSb Points to IP6 service binding instance. + @param[in] MulticastAddr The IPv6 multicast address to be recorded. + @param[in] DelayTimer The maximum allowed delay before sending a responding + report, in units of milliseconds. + @return The created IP6_ML_GROUP list entry or NULL. + +**/ +IP6_MLD_GROUP * +Ip6CreateMldEntry ( + IN OUT IP6_SERVICE *IpSb, + IN EFI_IPv6_ADDRESS *MulticastAddr, + IN UINT32 DelayTimer + ) +{ + IP6_MLD_GROUP *Entry; + + NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE); + ASSERT (MulticastAddr != NULL && IP6_IS_MULTICAST (MulticastAddr)); + + Entry = AllocatePool (sizeof (IP6_MLD_GROUP)); + if (Entry != NULL) { + Entry->RefCnt = 1; + Entry->DelayTimer = DelayTimer; + Entry->SendByUs = FALSE; + IP6_COPY_ADDRESS (&Entry->Address, MulticastAddr); + InsertTailList (&IpSb->MldCtrl.Groups, &Entry->Link); + } + + return Entry; +} + +/** + Search a IP6_MLD_GROUP list entry node from a list array. + + @param[in] IpSb Points to IP6 service binding instance. + @param[in] MulticastAddr The IPv6 multicast address to be searched. + + @return The found IP6_ML_GROUP list entry or NULL. + +**/ +IP6_MLD_GROUP * +Ip6FindMldEntry ( + IN IP6_SERVICE *IpSb, + IN EFI_IPv6_ADDRESS *MulticastAddr + ) +{ + LIST_ENTRY *Entry; + IP6_MLD_GROUP *Group; + + NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE); + ASSERT (MulticastAddr != NULL && IP6_IS_MULTICAST (MulticastAddr)); + + NET_LIST_FOR_EACH (Entry, &IpSb->MldCtrl.Groups) { + Group = NET_LIST_USER_STRUCT (Entry, IP6_MLD_GROUP, Link); + if (EFI_IP6_EQUAL (MulticastAddr, &Group->Address)) { + return Group; + } + } + + return NULL; +} + +/** + Count the number of IP6 multicast groups that are mapped to the + same MAC address. Several IP6 multicast address may be mapped to + the same MAC address. + + @param[in] MldCtrl The MLD control block to search in. + @param[in] Mac The MAC address to search. + + @return The number of the IP6 multicast group that mapped to the same + multicast group Mac. + +**/ +INTN +Ip6FindMac ( + IN IP6_MLD_SERVICE_DATA *MldCtrl, + IN EFI_MAC_ADDRESS *Mac + ) +{ + LIST_ENTRY *Entry; + IP6_MLD_GROUP *Group; + INTN Count; + + Count = 0; + + NET_LIST_FOR_EACH (Entry, &MldCtrl->Groups) { + Group = NET_LIST_USER_STRUCT (Entry, IP6_MLD_GROUP, Link); + + if (NET_MAC_EQUAL (&Group->Mac, Mac, sizeof (EFI_MAC_ADDRESS))) { + Count++; + } + } + + return Count; +} + +/** + Generate MLD report message and send it out to MulticastAddr. + + @param[in] IpSb The IP service to send the packet. + @param[in] Interface The IP interface to send the packet. + If NULL, a system interface will be selected. + @param[in] MulticastAddr The specific IPv6 multicast address to which + the message sender is listening. + + @retval EFI_OUT_OF_RESOURCES There are not sufficient resources to complete the + operation. + @retval EFI_SUCCESS The MLD report message was successfully sent out. + +**/ +EFI_STATUS +Ip6SendMldReport ( + IN IP6_SERVICE *IpSb, + IN IP6_INTERFACE *Interface OPTIONAL, + IN EFI_IPv6_ADDRESS *MulticastAddr + ) +{ + IP6_MLD_HEAD *MldHead; + NET_BUF *Packet; + EFI_IP6_HEADER Head; + UINT16 PayloadLen; + UINTN OptionLen; + UINT8 *Options; + EFI_STATUS Status; + UINT16 HeadChecksum; + UINT16 PseudoChecksum; + + NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE); + ASSERT (MulticastAddr != NULL && IP6_IS_MULTICAST (MulticastAddr)); + + // + // Generate the packet to be sent + // IPv6 basic header + Hop by Hop option + MLD message + // + + OptionLen = 0; + Status = Ip6FillHopByHop (NULL, &OptionLen, IP6_ICMP); + ASSERT (Status == EFI_BUFFER_TOO_SMALL); + + PayloadLen = (UINT16) (OptionLen + sizeof (IP6_MLD_HEAD)); + Packet = NetbufAlloc (sizeof (EFI_IP6_HEADER) + (UINT32) PayloadLen); + if (Packet == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Create the basic IPv6 header. + // RFC3590: Use link-local address as source address if it is available, + // otherwise use the unspecified address. + // + Head.FlowLabelL = 0; + Head.FlowLabelH = 0; + Head.PayloadLength = HTONS (PayloadLen); + Head.NextHeader = IP6_HOP_BY_HOP; + Head.HopLimit = 1; + IP6_COPY_ADDRESS (&Head.DestinationAddress, MulticastAddr); + + // + // If Link-Local address is not ready, we use unspecified address. + // + IP6_COPY_ADDRESS (&Head.SourceAddress, &IpSb->LinkLocalAddr); + + NetbufReserve (Packet, sizeof (EFI_IP6_HEADER)); + + // + // Fill a IPv6 Router Alert option in a Hop-by-Hop Options Header + // + Options = NetbufAllocSpace (Packet, (UINT32) OptionLen, FALSE); + ASSERT (Options != NULL); + Status = Ip6FillHopByHop (Options, &OptionLen, IP6_ICMP); + if (EFI_ERROR (Status)) { + NetbufFree (Packet); + Packet = NULL; + return Status; + } + + // + // Fill in MLD message - Report + // + MldHead = (IP6_MLD_HEAD *) NetbufAllocSpace (Packet, sizeof (IP6_MLD_HEAD), FALSE); + ASSERT (MldHead != NULL); + ZeroMem (MldHead, sizeof (IP6_MLD_HEAD)); + MldHead->Head.Type = ICMP_V6_LISTENER_REPORT; + MldHead->Head.Code = 0; + IP6_COPY_ADDRESS (&MldHead->Group, MulticastAddr); + + HeadChecksum = NetblockChecksum ((UINT8 *) MldHead, sizeof (IP6_MLD_HEAD)); + PseudoChecksum = NetIp6PseudoHeadChecksum ( + &Head.SourceAddress, + &Head.DestinationAddress, + IP6_ICMP, + sizeof (IP6_MLD_HEAD) + ); + + MldHead->Head.Checksum = (UINT16) ~NetAddChecksum (HeadChecksum, PseudoChecksum); + + // + // Transmit the packet + // + return Ip6Output (IpSb, Interface, NULL, Packet, &Head, NULL, 0, Ip6SysPacketSent, NULL); +} + +/** + Generate MLD Done message and send it out to MulticastAddr. + + @param[in] IpSb The IP service to send the packet. + @param[in] MulticastAddr The specific IPv6 multicast address to which + the message sender is ceasing to listen. + + @retval EFI_OUT_OF_RESOURCES There are not sufficient resources to complete the + operation. + @retval EFI_SUCCESS The MLD report message was successfully sent out. + +**/ +EFI_STATUS +Ip6SendMldDone ( + IN IP6_SERVICE *IpSb, + IN EFI_IPv6_ADDRESS *MulticastAddr + ) +{ + IP6_MLD_HEAD *MldHead; + NET_BUF *Packet; + EFI_IP6_HEADER Head; + UINT16 PayloadLen; + UINTN OptionLen; + UINT8 *Options; + EFI_STATUS Status; + EFI_IPv6_ADDRESS Destination; + UINT16 HeadChecksum; + UINT16 PseudoChecksum; + + NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE); + ASSERT (MulticastAddr != NULL && IP6_IS_MULTICAST (MulticastAddr)); + + // + // Generate the packet to be sent + // IPv6 basic header + Hop by Hop option + MLD message + // + + OptionLen = 0; + Status = Ip6FillHopByHop (NULL, &OptionLen, IP6_ICMP); + ASSERT (Status == EFI_BUFFER_TOO_SMALL); + + PayloadLen = (UINT16) (OptionLen + sizeof (IP6_MLD_HEAD)); + Packet = NetbufAlloc (sizeof (EFI_IP6_HEADER) + (UINT32) PayloadLen); + if (Packet == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Create the basic IPv6 header. + // + Head.FlowLabelL = 0; + Head.FlowLabelH = 0; + Head.PayloadLength = HTONS (PayloadLen); + Head.NextHeader = IP6_HOP_BY_HOP; + Head.HopLimit = 1; + + // + // If Link-Local address is not ready, we use unspecified address. + // + IP6_COPY_ADDRESS (&Head.SourceAddress, &IpSb->LinkLocalAddr); + + Ip6SetToAllNodeMulticast (TRUE, IP6_LINK_LOCAL_SCOPE, &Destination); + IP6_COPY_ADDRESS (&Head.DestinationAddress, &Destination); + + NetbufReserve (Packet, sizeof (EFI_IP6_HEADER)); + + // + // Fill a IPv6 Router Alert option in a Hop-by-Hop Options Header + // + Options = NetbufAllocSpace (Packet, (UINT32) OptionLen, FALSE); + ASSERT (Options != NULL); + Status = Ip6FillHopByHop (Options, &OptionLen, IP6_ICMP); + if (EFI_ERROR (Status)) { + NetbufFree (Packet); + Packet = NULL; + return Status; + } + + // + // Fill in MLD message - Done + // + MldHead = (IP6_MLD_HEAD *) NetbufAllocSpace (Packet, sizeof (IP6_MLD_HEAD), FALSE); + ASSERT (MldHead != NULL); + ZeroMem (MldHead, sizeof (IP6_MLD_HEAD)); + MldHead->Head.Type = ICMP_V6_LISTENER_DONE; + MldHead->Head.Code = 0; + IP6_COPY_ADDRESS (&MldHead->Group, MulticastAddr); + + HeadChecksum = NetblockChecksum ((UINT8 *) MldHead, sizeof (IP6_MLD_HEAD)); + PseudoChecksum = NetIp6PseudoHeadChecksum ( + &Head.SourceAddress, + &Head.DestinationAddress, + IP6_ICMP, + sizeof (IP6_MLD_HEAD) + ); + + MldHead->Head.Checksum = (UINT16) ~NetAddChecksum (HeadChecksum, PseudoChecksum); + + // + // Transmit the packet + // + return Ip6Output (IpSb, NULL, NULL, Packet, &Head, NULL, 0, Ip6SysPacketSent, NULL); +} + +/** + Init the MLD data of the IP6 service instance. Configure + MNP to receive ALL SYSTEM multicast. + + @param[in] IpSb The IP6 service whose MLD is to be initialized. + + @retval EFI_OUT_OF_RESOURCES There are not sufficient resourcet to complete the + operation. + @retval EFI_SUCCESS The MLD module successfully initialized. + +**/ +EFI_STATUS +Ip6InitMld ( + IN IP6_SERVICE *IpSb + ) +{ + EFI_IPv6_ADDRESS AllNodes; + IP6_MLD_GROUP *Group; + EFI_STATUS Status; + + // + // Join the link-scope all-nodes multicast address (FF02::1). + // This address is started in Idle Listener state and never transitions to + // another state, and never sends a Report or Done for that address. + // + + Ip6SetToAllNodeMulticast (FALSE, IP6_LINK_LOCAL_SCOPE, &AllNodes); + + Group = Ip6CreateMldEntry (IpSb, &AllNodes, (UINT32) IP6_INFINIT_LIFETIME); + if (Group == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = Ip6GetMulticastMac (IpSb->Mnp, &AllNodes, &Group->Mac); + if (EFI_ERROR (Status)) { + goto ERROR; + } + + // + // Configure MNP to receive all-nodes multicast + // + Status = IpSb->Mnp->Groups (IpSb->Mnp, TRUE, &Group->Mac); + if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) { + goto ERROR; + } + + return EFI_SUCCESS; + +ERROR: + RemoveEntryList (&Group->Link); + FreePool (Group); + return Status; +} + +/** + Add a group address to the array of group addresses. + The caller should make sure that no duplicated address + existed in the array. + + @param[in, out] IpInstance Points to an IP6_PROTOCOL instance. + @param[in] Group The IP6 multicast address to add. + + @retval EFI_OUT_OF_RESOURCES There are not sufficient resources to complete + the operation. + @retval EFI_SUCESS The address is added to the group address array. + +**/ +EFI_STATUS +Ip6CombineGroups ( + IN OUT IP6_PROTOCOL *IpInstance, + IN EFI_IPv6_ADDRESS *Group + ) +{ + EFI_IPv6_ADDRESS *GroupList; + + NET_CHECK_SIGNATURE (IpInstance, IP6_PROTOCOL_SIGNATURE); + ASSERT (Group != NULL && IP6_IS_MULTICAST (Group)); + + IpInstance->GroupCount++; + + GroupList = AllocatePool (IpInstance->GroupCount * sizeof (EFI_IPv6_ADDRESS)); + if (GroupList == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + if (IpInstance->GroupCount > 1) { + ASSERT (IpInstance->GroupList != NULL); + + CopyMem ( + GroupList, + IpInstance->GroupList, + (IpInstance->GroupCount - 1) * sizeof (EFI_IPv6_ADDRESS) + ); + + FreePool (IpInstance->GroupList); + } + + IP6_COPY_ADDRESS (GroupList + (IpInstance->GroupCount - 1), Group); + + IpInstance->GroupList = GroupList; + + return EFI_SUCCESS; +} + +/** + Remove a group address from the array of group addresses. + Although the function doesn't assume the byte order of Group, + the network byte order is used by the caller. + + @param[in, out] IpInstance Points to an IP6_PROTOCOL instance. + @param[in] Group The IP6 multicast address to remove. + + @retval EFI_NOT_FOUND Cannot find the to be removed group address. + @retval EFI_SUCCESS The group address was successfully removed. + +**/ +EFI_STATUS +Ip6RemoveGroup ( + IN OUT IP6_PROTOCOL *IpInstance, + IN EFI_IPv6_ADDRESS *Group + ) +{ + UINT32 Index; + UINT32 Count; + + Count = IpInstance->GroupCount; + + for (Index = 0; Index < Count; Index++) { + if (EFI_IP6_EQUAL (IpInstance->GroupList + Index, Group)) { + break; + } + } + + if (Index == Count) { + return EFI_NOT_FOUND; + } + + while (Index < Count - 1) { + IP6_COPY_ADDRESS (IpInstance->GroupList + Index, IpInstance->GroupList + Index + 1); + Index++; + } + + ASSERT (IpInstance->GroupCount > 0); + IpInstance->GroupCount--; + + return EFI_SUCCESS; +} + +/** + Join the multicast group on behalf of this IP6 service binding instance. + + @param[in] IpSb The IP6 service binding instance. + @param[in] Interface Points to an IP6_INTERFACE structure. + @param[in] Address The group address to join. + + @retval EFI_SUCCESS Successfully join the multicast group. + @retval EFI_OUT_OF_RESOURCES Failed to allocate resources. + @retval Others Failed to join the multicast group. + +**/ +EFI_STATUS +Ip6JoinGroup ( + IN IP6_SERVICE *IpSb, + IN IP6_INTERFACE *Interface, + IN EFI_IPv6_ADDRESS *Address + ) +{ + IP6_MLD_GROUP *Group; + EFI_STATUS Status; + + Group = Ip6FindMldEntry (IpSb, Address); + if (Group != NULL) { + Group->RefCnt++; + return EFI_SUCCESS; + } + + // + // Repeat the report once or twcie after short delays [Unsolicited Report Interval] (default:10s) + // Simulate this operation as a Multicast-Address-Specific Query was received for that addresss. + // + Group = Ip6CreateMldEntry (IpSb, Address, IP6_UNSOLICITED_REPORT_INTERVAL); + if (Group == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Group->SendByUs = TRUE; + + Status = Ip6GetMulticastMac (IpSb->Mnp, Address, &Group->Mac); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = IpSb->Mnp->Groups (IpSb->Mnp, TRUE, &Group->Mac); + if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) { + goto ERROR; + } + + // + // Send unsolicited report when a node starts listening to a multicast address + // + Status = Ip6SendMldReport (IpSb, Interface, Address); + if (EFI_ERROR (Status)) { + goto ERROR; + } + + return EFI_SUCCESS; + +ERROR: + RemoveEntryList (&Group->Link); + FreePool (Group); + return Status; +} + +/** + Leave the IP6 multicast group. + + @param[in] IpSb The IP6 service binding instance. + @param[in] Address The group address to leave. + + @retval EFI_NOT_FOUND The IP6 service instance isn't in the group. + @retval EFI_SUCCESS Successfully leave the multicast group.. + @retval Others Failed to leave the multicast group. + +**/ +EFI_STATUS +Ip6LeaveGroup ( + IN IP6_SERVICE *IpSb, + IN EFI_IPv6_ADDRESS *Address + ) +{ + IP6_MLD_GROUP *Group; + EFI_STATUS Status; + + Group = Ip6FindMldEntry (IpSb, Address); + if (Group == NULL) { + return EFI_NOT_FOUND; + } + + // + // If more than one instance is in the group, decrease + // the RefCnt then return. + // + if ((Group->RefCnt > 0) && (--Group->RefCnt > 0)) { + return EFI_SUCCESS; + } + + // + // If multiple IP6 group addresses are mapped to the same + // multicast MAC address, don't configure the MNP to leave + // the MAC. + // + if (Ip6FindMac (&IpSb->MldCtrl, &Group->Mac) == 1) { + Status = IpSb->Mnp->Groups (IpSb->Mnp, FALSE, &Group->Mac); + if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) { + return Status; + } + } + + // + // Send a leave report if we are the last node to report + // + if (Group->SendByUs) { + Status = Ip6SendMldDone (IpSb, Address); + if (EFI_ERROR (Status)) { + return Status; + } + } + + RemoveEntryList (&Group->Link); + FreePool (Group); + + return EFI_SUCCESS; +} + +/** + Worker function for EfiIp6Groups(). The caller + should make sure that the parameters are valid. + + @param[in] IpInstance The IP6 child to change the setting. + @param[in] JoinFlag TRUE to join the group, otherwise leave it. + @param[in] GroupAddress The target group address. If NULL, leave all + the group addresses. + + @retval EFI_ALREADY_STARTED Wants to join the group, but is already a member of it + @retval EFI_OUT_OF_RESOURCES Failed to allocate sufficient resources. + @retval EFI_DEVICE_ERROR Failed to set the group configuraton. + @retval EFI_SUCCESS Successfully updated the group setting. + @retval EFI_NOT_FOUND Try to leave the group which it isn't a member. + +**/ +EFI_STATUS +Ip6Groups ( + IN IP6_PROTOCOL *IpInstance, + IN BOOLEAN JoinFlag, + IN EFI_IPv6_ADDRESS *GroupAddress OPTIONAL + ) +{ + EFI_STATUS Status; + IP6_SERVICE *IpSb; + UINT32 Index; + EFI_IPv6_ADDRESS *Group; + + IpSb = IpInstance->Service; + + if (JoinFlag) { + ASSERT (GroupAddress != NULL); + + for (Index = 0; Index < IpInstance->GroupCount; Index++) { + if (EFI_IP6_EQUAL (IpInstance->GroupList + Index, GroupAddress)) { + return EFI_ALREADY_STARTED; + } + } + + Status = Ip6JoinGroup (IpSb, IpInstance->Interface, GroupAddress); + if (!EFI_ERROR (Status)) { + return Ip6CombineGroups (IpInstance, GroupAddress); + } + + return Status; + } + + // + // Leave the group. Leave all the groups if GroupAddress is NULL. + // + for (Index = IpInstance->GroupCount; Index > 0; Index--) { + Group = IpInstance->GroupList + (Index - 1); + + if ((GroupAddress == NULL) || EFI_IP6_EQUAL (Group, GroupAddress)) { + Status = Ip6LeaveGroup (IpInstance->Service, Group); + if (EFI_ERROR (Status)) { + return Status; + } + + Ip6RemoveGroup (IpInstance, Group); + + if (IpInstance->GroupCount == 0) { + ASSERT (Index == 1); + FreePool (IpInstance->GroupList); + IpInstance->GroupList = NULL; + } + + if (GroupAddress != NULL) { + return EFI_SUCCESS; + } + } + } + + return ((GroupAddress != NULL) ? EFI_NOT_FOUND : EFI_SUCCESS); +} + +/** + Set a random value of the delay timer for the multicast address from the range + [0, Maximum Response Delay]. If a timer for any address is already + running, it is reset to the new random value only if the requested + Maximum Response Delay is less than the remaining value of the + running timer. If the Query packet specifies a Maximum Response + Delay of zero, each timer is effectively set to zero, and the action + specified below for timer expiration is performed immediately. + + @param[in] IpSb The IP6 service binding instance. + @param[in] MaxRespDelay The Maximum Response Delay, in milliseconds. + @param[in] MulticastAddr The multicast address. + @param[in, out] Group Points to a IP6_MLD_GROUP list entry node. + + @retval EFI_SUCCESS The delay timer is successfully updated or + timer expiration is performed immediately. + @retval Others Failed to send out MLD report message. + +**/ +EFI_STATUS +Ip6UpdateDelayTimer ( + IN IP6_SERVICE *IpSb, + IN UINT16 MaxRespDelay, + IN EFI_IPv6_ADDRESS *MulticastAddr, + IN OUT IP6_MLD_GROUP *Group + ) +{ + UINT32 Delay; + + // + // If the Query packet specifies a Maximum Response Delay of zero, perform timer + // expiration immediately. + // + if (MaxRespDelay == 0) { + Group->DelayTimer = 0; + return Ip6SendMldReport (IpSb, NULL, MulticastAddr); + } + + Delay = (UINT32) (MaxRespDelay / 1000); + + // + // Sets a delay timer to a random value selected from the range [0, Maximum Response Delay] + // If a timer is already running, resets it if the request Maximum Response Delay + // is less than the remaining value of the running timer. + // + if (Group->DelayTimer == 0 || Delay < Group->DelayTimer) { + Group->DelayTimer = Delay / 4294967295UL * NET_RANDOM (NetRandomInitSeed ()); + } + + return EFI_SUCCESS; +} + +/** + Process the Multicast Listener Query message. + + @param[in] IpSb The IP service that received the packet. + @param[in] Head The IP head of the MLD query packet. + @param[in] Packet The content of the MLD query packet with IP head + removed. + + @retval EFI_SUCCESS The MLD query packet processed successfully. + @retval EFI_INVALID_PARAMETER The packet is invalid. + @retval Others Failed to process the packet. + +**/ +EFI_STATUS +Ip6ProcessMldQuery ( + IN IP6_SERVICE *IpSb, + IN EFI_IP6_HEADER *Head, + IN NET_BUF *Packet + ) +{ + EFI_IPv6_ADDRESS AllNodes; + IP6_MLD_GROUP *Group; + IP6_MLD_HEAD MldPacket; + LIST_ENTRY *Entry; + EFI_STATUS Status; + + Status = EFI_INVALID_PARAMETER; + + // + // Check the validity of the packet, generic query or specific query + // + if (!NetIp6IsUnspecifiedAddr (&Head->SourceAddress) && !NetIp6IsLinkLocalAddr (&Head->SourceAddress)) { + goto Exit; + } + + if (Head->HopLimit != 1 || !IP6_IS_MULTICAST (&Head->DestinationAddress)) { + goto Exit; + } + + // + // The Packet points to MLD report raw data without Hop-By-Hop option. + // + NetbufCopy (Packet, 0, sizeof (IP6_MLD_HEAD), (UINT8 *) &MldPacket); + MldPacket.MaxRespDelay = NTOHS (MldPacket.MaxRespDelay); + + Ip6SetToAllNodeMulticast (FALSE, IP6_LINK_LOCAL_SCOPE, &AllNodes); + if (!EFI_IP6_EQUAL (&Head->DestinationAddress, &AllNodes)) { + // + // Receives a Multicast-Address-Specific Query, check it firstly + // + if (!EFI_IP6_EQUAL (&Head->DestinationAddress, &MldPacket.Group)) { + goto Exit; + } + // + // The node is not listening but it receives the specific query. Just return. + // + Group = Ip6FindMldEntry (IpSb, &MldPacket.Group); + if (Group == NULL) { + Status = EFI_SUCCESS; + goto Exit; + } + + Status = Ip6UpdateDelayTimer ( + IpSb, + MldPacket.MaxRespDelay, + &MldPacket.Group, + Group + ); + goto Exit; + } + + // + // Receives a General Query, sets a delay timer for each multicast address it is listening + // + NET_LIST_FOR_EACH (Entry, &IpSb->MldCtrl.Groups) { + Group = NET_LIST_USER_STRUCT (Entry, IP6_MLD_GROUP, Link); + Status = Ip6UpdateDelayTimer (IpSb, MldPacket.MaxRespDelay, &Group->Address, Group); + if (EFI_ERROR (Status)) { + goto Exit; + } + } + + Status = EFI_SUCCESS; + +Exit: + NetbufFree (Packet); + return Status; +} + +/** + Process the Multicast Listener Report message. + + @param[in] IpSb The IP service that received the packet. + @param[in] Head The IP head of the MLD report packet. + @param[in] Packet The content of the MLD report packet with IP head + removed. + + @retval EFI_SUCCESS The MLD report packet processed successfully. + @retval EFI_INVALID_PARAMETER The packet is invalid. + +**/ +EFI_STATUS +Ip6ProcessMldReport ( + IN IP6_SERVICE *IpSb, + IN EFI_IP6_HEADER *Head, + IN NET_BUF *Packet + ) +{ + IP6_MLD_HEAD MldPacket; + IP6_MLD_GROUP *Group; + EFI_STATUS Status; + + Status = EFI_INVALID_PARAMETER; + + // + // Validate the incoming message, if invalid, drop it. + // + if (!NetIp6IsUnspecifiedAddr (&Head->SourceAddress) && !NetIp6IsLinkLocalAddr (&Head->SourceAddress)) { + goto Exit; + } + + if (Head->HopLimit != 1 || !IP6_IS_MULTICAST (&Head->DestinationAddress)) { + goto Exit; + } + + // + // The Packet points to MLD report raw data without Hop-By-Hop option. + // + NetbufCopy (Packet, 0, sizeof (IP6_MLD_HEAD), (UINT8 *) &MldPacket); + if (!EFI_IP6_EQUAL (&Head->DestinationAddress, &MldPacket.Group)) { + goto Exit; + } + + Group = Ip6FindMldEntry (IpSb, &MldPacket.Group); + if (Group == NULL) { + goto Exit; + } + + // + // The report is sent by another node, stop its own timer relates to the multicast address and clear + // + + if (!Group->SendByUs) { + Group->DelayTimer = 0; + } + + Status = EFI_SUCCESS; + +Exit: + NetbufFree (Packet); + return Status; +} + +/** + The heartbeat timer of MLD module. It sends out a solicited MLD report when + DelayTimer expires. + + @param[in] IpSb The IP6 service binding instance. + +**/ +VOID +Ip6MldTimerTicking ( + IN IP6_SERVICE *IpSb + ) +{ + IP6_MLD_GROUP *Group; + LIST_ENTRY *Entry; + + // + // Send solicited report when timer expires + // + NET_LIST_FOR_EACH (Entry, &IpSb->MldCtrl.Groups) { + Group = NET_LIST_USER_STRUCT (Entry, IP6_MLD_GROUP, Link); + if ((Group->DelayTimer > 0) && (--Group->DelayTimer == 0)) { + Ip6SendMldReport (IpSb, NULL, &Group->Address); + } + } +} + -- cgit v1.2.3