summaryrefslogtreecommitdiffstats
path: root/NetworkPkg/Ip4Dxe/Ip4Igmp.c
diff options
context:
space:
mode:
Diffstat (limited to 'NetworkPkg/Ip4Dxe/Ip4Igmp.c')
-rw-r--r--NetworkPkg/Ip4Dxe/Ip4Igmp.c615
1 files changed, 615 insertions, 0 deletions
diff --git a/NetworkPkg/Ip4Dxe/Ip4Igmp.c b/NetworkPkg/Ip4Dxe/Ip4Igmp.c
new file mode 100644
index 0000000000..41d9bce971
--- /dev/null
+++ b/NetworkPkg/Ip4Dxe/Ip4Igmp.c
@@ -0,0 +1,615 @@
+/** @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 destinaton 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 refernce 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 malformated.
+ @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 nubmer 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;
+}