| /** @file | |
| This file implements the RFC2236: IGMP v2. | |
| Copyright (c) 2005 - 2018, Intel Corporation. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include "Ip4Impl.h" | |
| // | |
| // Route Alert option in IGMP report to direct routers to | |
| // examine the packet more closely. | |
| // | |
| UINT32 mRouteAlertOption = 0x00000494; | |
| /** | |
| Init the IGMP control data of the IP4 service instance, configure | |
| MNP to receive ALL SYSTEM multicast. | |
| @param[in, out] IpSb The IP4 service whose IGMP is to be initialized. | |
| @retval EFI_SUCCESS IGMP of the IpSb is successfully initialized. | |
| @retval EFI_OUT_OF_RESOURCES Failed to allocate resource to initialize IGMP. | |
| @retval Others Failed to initialize the IGMP of IpSb. | |
| **/ | |
| EFI_STATUS | |
| Ip4InitIgmp ( | |
| IN OUT IP4_SERVICE *IpSb | |
| ) | |
| { | |
| IGMP_SERVICE_DATA *IgmpCtrl; | |
| EFI_MANAGED_NETWORK_PROTOCOL *Mnp; | |
| IGMP_GROUP *Group; | |
| EFI_STATUS Status; | |
| IgmpCtrl = &IpSb->IgmpCtrl; | |
| // | |
| // Configure MNP to receive ALL_SYSTEM multicast | |
| // | |
| Group = AllocatePool (sizeof (IGMP_GROUP)); | |
| if (Group == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| Mnp = IpSb->Mnp; | |
| Group->Address = IP4_ALLSYSTEM_ADDRESS; | |
| Group->RefCnt = 1; | |
| Group->DelayTime = 0; | |
| Group->ReportByUs = FALSE; | |
| Status = Ip4GetMulticastMac (Mnp, IP4_ALLSYSTEM_ADDRESS, &Group->Mac); | |
| if (EFI_ERROR (Status)) { | |
| goto ON_ERROR; | |
| } | |
| Status = Mnp->Groups (Mnp, TRUE, &Group->Mac); | |
| if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) { | |
| goto ON_ERROR; | |
| } | |
| InsertHeadList (&IgmpCtrl->Groups, &Group->Link); | |
| return EFI_SUCCESS; | |
| ON_ERROR: | |
| FreePool (Group); | |
| return Status; | |
| } | |
| /** | |
| Find the IGMP_GROUP structure which contains the status of multicast | |
| group Address in this IGMP control block | |
| @param[in] IgmpCtrl The IGMP control block to search from. | |
| @param[in] Address The multicast address to search. | |
| @return NULL if the multicast address isn't in the IGMP control block. Otherwise | |
| the point to the IGMP_GROUP which contains the status of multicast group | |
| for Address. | |
| **/ | |
| IGMP_GROUP * | |
| Ip4FindGroup ( | |
| IN IGMP_SERVICE_DATA *IgmpCtrl, | |
| IN IP4_ADDR Address | |
| ) | |
| { | |
| LIST_ENTRY *Entry; | |
| IGMP_GROUP *Group; | |
| NET_LIST_FOR_EACH (Entry, &IgmpCtrl->Groups) { | |
| Group = NET_LIST_USER_STRUCT (Entry, IGMP_GROUP, Link); | |
| if (Group->Address == Address) { | |
| return Group; | |
| } | |
| } | |
| return NULL; | |
| } | |
| /** | |
| Count the number of IP4 multicast groups that are mapped to the | |
| same MAC address. Several IP4 multicast address may be mapped to | |
| the same MAC address. | |
| @param[in] IgmpCtrl The IGMP control block to search in. | |
| @param[in] Mac The MAC address to search. | |
| @return The number of the IP4 multicast group that mapped to the same | |
| multicast group Mac. | |
| **/ | |
| INTN | |
| Ip4FindMac ( | |
| IN IGMP_SERVICE_DATA *IgmpCtrl, | |
| IN EFI_MAC_ADDRESS *Mac | |
| ) | |
| { | |
| LIST_ENTRY *Entry; | |
| IGMP_GROUP *Group; | |
| INTN Count; | |
| Count = 0; | |
| NET_LIST_FOR_EACH (Entry, &IgmpCtrl->Groups) { | |
| Group = NET_LIST_USER_STRUCT (Entry, IGMP_GROUP, Link); | |
| if (NET_MAC_EQUAL (&Group->Mac, Mac, sizeof (EFI_MAC_ADDRESS))) { | |
| Count++; | |
| } | |
| } | |
| return Count; | |
| } | |
| /** | |
| Send an IGMP protocol message to the Dst, such as IGMP v1 membership report. | |
| @param[in] IpSb The IP4 service instance that requests the | |
| transmission. | |
| @param[in] Dst The destination to send to. | |
| @param[in] Type The IGMP message type, such as IGMP v1 membership | |
| report. | |
| @param[in] Group The group address in the IGMP message head. | |
| @retval EFI_OUT_OF_RESOURCES Failed to allocate memory to build the message. | |
| @retval EFI_SUCCESS The IGMP message is successfully send. | |
| @retval Others Failed to send the IGMP message. | |
| **/ | |
| EFI_STATUS | |
| Ip4SendIgmpMessage ( | |
| IN IP4_SERVICE *IpSb, | |
| IN IP4_ADDR Dst, | |
| IN UINT8 Type, | |
| IN IP4_ADDR Group | |
| ) | |
| { | |
| IP4_HEAD Head; | |
| NET_BUF *Packet; | |
| IGMP_HEAD *Igmp; | |
| // | |
| // Allocate a net buffer to hold the message | |
| // | |
| Packet = NetbufAlloc (IP4_MAX_HEADLEN + sizeof (IGMP_HEAD)); | |
| if (Packet == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| // | |
| // Fill in the IGMP and IP header, then transmit the message | |
| // | |
| NetbufReserve (Packet, IP4_MAX_HEADLEN); | |
| Igmp = (IGMP_HEAD *)NetbufAllocSpace (Packet, sizeof (IGMP_HEAD), FALSE); | |
| if (Igmp == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| Igmp->Type = Type; | |
| Igmp->MaxRespTime = 0; | |
| Igmp->Checksum = 0; | |
| Igmp->Group = HTONL (Group); | |
| Igmp->Checksum = (UINT16)(~NetblockChecksum ((UINT8 *)Igmp, sizeof (IGMP_HEAD))); | |
| Head.Tos = 0; | |
| Head.Protocol = IP4_PROTO_IGMP; | |
| Head.Ttl = 1; | |
| Head.Fragment = 0; | |
| Head.Dst = Dst; | |
| Head.Src = IP4_ALLZERO_ADDRESS; | |
| return Ip4Output ( | |
| IpSb, | |
| NULL, | |
| Packet, | |
| &Head, | |
| (UINT8 *)&mRouteAlertOption, | |
| sizeof (UINT32), | |
| IP4_ALLZERO_ADDRESS, | |
| Ip4SysPacketSent, | |
| NULL | |
| ); | |
| } | |
| /** | |
| Send an IGMP membership report. Depends on whether the server is | |
| v1 or v2, it will send either a V1 or V2 membership report. | |
| @param[in] IpSb The IP4 service instance that requests the | |
| transmission. | |
| @param[in] Group The group address to report. | |
| @retval EFI_OUT_OF_RESOURCES Failed to allocate memory to build the message. | |
| @retval EFI_SUCCESS The IGMP report message is successfully send. | |
| @retval Others Failed to send the report. | |
| **/ | |
| EFI_STATUS | |
| Ip4SendIgmpReport ( | |
| IN IP4_SERVICE *IpSb, | |
| IN IP4_ADDR Group | |
| ) | |
| { | |
| if (IpSb->IgmpCtrl.Igmpv1QuerySeen != 0) { | |
| return Ip4SendIgmpMessage (IpSb, Group, IGMP_V1_MEMBERSHIP_REPORT, Group); | |
| } else { | |
| return Ip4SendIgmpMessage (IpSb, Group, IGMP_V2_MEMBERSHIP_REPORT, Group); | |
| } | |
| } | |
| /** | |
| Join the multicast group on behalf of this IP4 child | |
| @param[in] IpInstance The IP4 child that wants to join the group. | |
| @param[in] Address The group 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 | |
| Ip4JoinGroup ( | |
| IN IP4_PROTOCOL *IpInstance, | |
| IN IP4_ADDR Address | |
| ) | |
| { | |
| EFI_MANAGED_NETWORK_PROTOCOL *Mnp; | |
| IP4_SERVICE *IpSb; | |
| IGMP_SERVICE_DATA *IgmpCtrl; | |
| IGMP_GROUP *Group; | |
| EFI_STATUS Status; | |
| IpSb = IpInstance->Service; | |
| IgmpCtrl = &IpSb->IgmpCtrl; | |
| Mnp = IpSb->Mnp; | |
| // | |
| // If the IP service already is a member in the group, just | |
| // increase the reference count and return. | |
| // | |
| Group = Ip4FindGroup (IgmpCtrl, Address); | |
| if (Group != NULL) { | |
| Group->RefCnt++; | |
| return EFI_SUCCESS; | |
| } | |
| // | |
| // Otherwise, create a new IGMP_GROUP, Get the multicast's MAC address, | |
| // send a report, then direct MNP to receive the multicast. | |
| // | |
| Group = AllocatePool (sizeof (IGMP_GROUP)); | |
| if (Group == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| Group->Address = Address; | |
| Group->RefCnt = 1; | |
| Group->DelayTime = IGMP_UNSOLICIATED_REPORT; | |
| Group->ReportByUs = TRUE; | |
| Status = Ip4GetMulticastMac (Mnp, Address, &Group->Mac); | |
| if (EFI_ERROR (Status)) { | |
| goto ON_ERROR; | |
| } | |
| Status = Ip4SendIgmpReport (IpSb, Address); | |
| if (EFI_ERROR (Status)) { | |
| goto ON_ERROR; | |
| } | |
| Status = Mnp->Groups (Mnp, TRUE, &Group->Mac); | |
| if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) { | |
| goto ON_ERROR; | |
| } | |
| InsertHeadList (&IgmpCtrl->Groups, &Group->Link); | |
| return EFI_SUCCESS; | |
| ON_ERROR: | |
| FreePool (Group); | |
| return Status; | |
| } | |
| /** | |
| Leave the IP4 multicast group on behalf of IpInstance. | |
| @param[in] IpInstance The IP4 child that wants to leave the group | |
| address. | |
| @param[in] Address The group address to leave. | |
| @retval EFI_NOT_FOUND The IP4 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 | |
| Ip4LeaveGroup ( | |
| IN IP4_PROTOCOL *IpInstance, | |
| IN IP4_ADDR Address | |
| ) | |
| { | |
| EFI_MANAGED_NETWORK_PROTOCOL *Mnp; | |
| IP4_SERVICE *IpSb; | |
| IGMP_SERVICE_DATA *IgmpCtrl; | |
| IGMP_GROUP *Group; | |
| EFI_STATUS Status; | |
| IpSb = IpInstance->Service; | |
| IgmpCtrl = &IpSb->IgmpCtrl; | |
| Mnp = IpSb->Mnp; | |
| Group = Ip4FindGroup (IgmpCtrl, 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) { | |
| return EFI_SUCCESS; | |
| } | |
| // | |
| // If multiple IP4 group addresses are mapped to the same | |
| // multicast MAC address, don't configure the MNP to leave | |
| // the MAC. | |
| // | |
| if (Ip4FindMac (IgmpCtrl, &Group->Mac) == 1) { | |
| Status = Mnp->Groups (Mnp, FALSE, &Group->Mac); | |
| if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) { | |
| return Status; | |
| } | |
| } | |
| // | |
| // Send a leave report if the membership is reported by us | |
| // and we are talking IGMPv2. | |
| // | |
| if (Group->ReportByUs && (IgmpCtrl->Igmpv1QuerySeen == 0)) { | |
| Ip4SendIgmpMessage (IpSb, IP4_ALLROUTER_ADDRESS, IGMP_LEAVE_GROUP, Group->Address); | |
| } | |
| RemoveEntryList (&Group->Link); | |
| FreePool (Group); | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Handle the received IGMP message for the IP4 service instance. | |
| @param[in] IpSb The IP4 service instance that received the message. | |
| @param[in] Head The IP4 header of the received message. | |
| @param[in] Packet The IGMP message, without IP4 header. | |
| @retval EFI_INVALID_PARAMETER The IGMP message is malformatted. | |
| @retval EFI_SUCCESS The IGMP message is successfully processed. | |
| **/ | |
| EFI_STATUS | |
| Ip4IgmpHandle ( | |
| IN IP4_SERVICE *IpSb, | |
| IN IP4_HEAD *Head, | |
| IN NET_BUF *Packet | |
| ) | |
| { | |
| IGMP_SERVICE_DATA *IgmpCtrl; | |
| IGMP_HEAD Igmp; | |
| IGMP_GROUP *Group; | |
| IP4_ADDR Address; | |
| LIST_ENTRY *Entry; | |
| IgmpCtrl = &IpSb->IgmpCtrl; | |
| // | |
| // Must checksum over the whole packet, later IGMP version | |
| // may employ message longer than 8 bytes. IP's header has | |
| // already been trimmed off. | |
| // | |
| if ((Packet->TotalSize < sizeof (Igmp)) || (NetbufChecksum (Packet) != 0)) { | |
| NetbufFree (Packet); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| // | |
| // Copy the packet in case it is fragmented | |
| // | |
| NetbufCopy (Packet, 0, sizeof (IGMP_HEAD), (UINT8 *)&Igmp); | |
| switch (Igmp.Type) { | |
| case IGMP_MEMBERSHIP_QUERY: | |
| // | |
| // If MaxRespTime is zero, it is most likely that we are | |
| // talking to a V1 router | |
| // | |
| if (Igmp.MaxRespTime == 0) { | |
| IgmpCtrl->Igmpv1QuerySeen = IGMP_V1ROUTER_PRESENT; | |
| Igmp.MaxRespTime = 100; | |
| } | |
| // | |
| // Igmp is ticking once per second but MaxRespTime is in | |
| // the unit of 100ms. | |
| // | |
| Igmp.MaxRespTime /= 10; | |
| Address = NTOHL (Igmp.Group); | |
| if (Address == IP4_ALLSYSTEM_ADDRESS) { | |
| break; | |
| } | |
| NET_LIST_FOR_EACH (Entry, &IgmpCtrl->Groups) { | |
| Group = NET_LIST_USER_STRUCT (Entry, IGMP_GROUP, Link); | |
| // | |
| // If address is all zero, all the memberships will be reported. | |
| // otherwise only one is reported. | |
| // | |
| if ((Address == IP4_ALLZERO_ADDRESS) || (Address == Group->Address)) { | |
| // | |
| // If the timer is pending, only update it if the time left | |
| // is longer than the MaxRespTime. TODO: randomize the DelayTime. | |
| // | |
| if ((Group->DelayTime == 0) || (Group->DelayTime > Igmp.MaxRespTime)) { | |
| Group->DelayTime = MAX (1, Igmp.MaxRespTime); | |
| } | |
| } | |
| } | |
| break; | |
| case IGMP_V1_MEMBERSHIP_REPORT: | |
| case IGMP_V2_MEMBERSHIP_REPORT: | |
| Address = NTOHL (Igmp.Group); | |
| Group = Ip4FindGroup (IgmpCtrl, Address); | |
| if ((Group != NULL) && (Group->DelayTime > 0)) { | |
| Group->DelayTime = 0; | |
| Group->ReportByUs = FALSE; | |
| } | |
| break; | |
| } | |
| NetbufFree (Packet); | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| The periodical timer function for IGMP. It does the following | |
| things: | |
| 1. Decrease the Igmpv1QuerySeen to make it possible to refresh | |
| the IGMP server type. | |
| 2. Decrease the report timer for each IGMP group in "delaying | |
| member" state. | |
| @param[in] IpSb The IP4 service instance that is ticking. | |
| **/ | |
| VOID | |
| Ip4IgmpTicking ( | |
| IN IP4_SERVICE *IpSb | |
| ) | |
| { | |
| IGMP_SERVICE_DATA *IgmpCtrl; | |
| LIST_ENTRY *Entry; | |
| IGMP_GROUP *Group; | |
| IgmpCtrl = &IpSb->IgmpCtrl; | |
| if (IgmpCtrl->Igmpv1QuerySeen > 0) { | |
| IgmpCtrl->Igmpv1QuerySeen--; | |
| } | |
| // | |
| // Decrease the report timer for each IGMP group in "delaying member" | |
| // | |
| NET_LIST_FOR_EACH (Entry, &IgmpCtrl->Groups) { | |
| Group = NET_LIST_USER_STRUCT (Entry, IGMP_GROUP, Link); | |
| ASSERT (Group->DelayTime >= 0); | |
| if (Group->DelayTime > 0) { | |
| Group->DelayTime--; | |
| if (Group->DelayTime == 0) { | |
| Ip4SendIgmpReport (IpSb, Group->Address); | |
| Group->ReportByUs = TRUE; | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| Add a group address to the array of group addresses. | |
| The caller should make sure that no duplicated address | |
| existed in the array. Although the function doesn't | |
| assume the byte order of the both Source and Addr, the | |
| network byte order is used by the caller. | |
| @param[in] Source The array of group addresses to add to. | |
| @param[in] Count The number of group addresses in the Source. | |
| @param[in] Addr The IP4 multicast address to add. | |
| @return NULL if failed to allocate memory for the new groups, | |
| otherwise the new combined group addresses. | |
| **/ | |
| IP4_ADDR * | |
| Ip4CombineGroups ( | |
| IN IP4_ADDR *Source, | |
| IN UINT32 Count, | |
| IN IP4_ADDR Addr | |
| ) | |
| { | |
| IP4_ADDR *Groups; | |
| Groups = AllocatePool (sizeof (IP4_ADDR) * (Count + 1)); | |
| if (Groups == NULL) { | |
| return NULL; | |
| } | |
| CopyMem (Groups, Source, Count * sizeof (IP4_ADDR)); | |
| Groups[Count] = Addr; | |
| return Groups; | |
| } | |
| /** | |
| Remove a group address from the array of group addresses. | |
| Although the function doesn't assume the byte order of the | |
| both Groups and Addr, the network byte order is used by | |
| the caller. | |
| @param Groups The array of group addresses to remove from. | |
| @param Count The number of group addresses in the Groups. | |
| @param Addr The IP4 multicast address to remove. | |
| @return The number of group addresses in the Groups after remove. | |
| It is Count if the Addr isn't in the Groups. | |
| **/ | |
| INTN | |
| Ip4RemoveGroupAddr ( | |
| IN OUT IP4_ADDR *Groups, | |
| IN UINT32 Count, | |
| IN IP4_ADDR Addr | |
| ) | |
| { | |
| UINT32 Index; | |
| for (Index = 0; Index < Count; Index++) { | |
| if (Groups[Index] == Addr) { | |
| break; | |
| } | |
| } | |
| while (Index < Count - 1) { | |
| Groups[Index] = Groups[Index + 1]; | |
| Index++; | |
| } | |
| return Index; | |
| } |