]> git.proxmox.com Git - mirror_edk2.git/blobdiff - NetworkPkg/Ip4Dxe/Ip4Igmp.c
NetworkPkg: Move Network library and drivers from MdeModulePkg to NetworkPkg
[mirror_edk2.git] / NetworkPkg / Ip4Dxe / Ip4Igmp.c
diff --git a/NetworkPkg/Ip4Dxe/Ip4Igmp.c b/NetworkPkg/Ip4Dxe/Ip4Igmp.c
new file mode 100644 (file)
index 0000000..41d9bce
--- /dev/null
@@ -0,0 +1,615 @@
+/** @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