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