Home | History | Annotate | Download | only in Ip6Dxe
      1 /** @file
      2   Multicast Listener Discovery support routines.
      3 
      4   Copyright (c) 2009 - 2010, Intel Corporation. All rights reserved.<BR>
      5 
      6   This program and the accompanying materials
      7   are licensed and made available under the terms and conditions of the BSD License
      8   which accompanies this distribution.  The full text of the license may be found at
      9   http://opensource.org/licenses/bsd-license.php.
     10 
     11   THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
     12   WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
     13 
     14 **/
     15 
     16 #include "Ip6Impl.h"
     17 
     18 /**
     19   Create a IP6_MLD_GROUP list entry node and record to IP6 service binding data.
     20 
     21   @param[in, out]  IpSb          Points to IP6 service binding instance.
     22   @param[in]       MulticastAddr The IPv6 multicast address to be recorded.
     23   @param[in]       DelayTimer    The maximum allowed delay before sending a responding
     24                                  report, in units of milliseconds.
     25   @return The created IP6_ML_GROUP list entry or NULL.
     26 
     27 **/
     28 IP6_MLD_GROUP *
     29 Ip6CreateMldEntry (
     30   IN OUT IP6_SERVICE        *IpSb,
     31   IN EFI_IPv6_ADDRESS       *MulticastAddr,
     32   IN UINT32                 DelayTimer
     33   )
     34 {
     35   IP6_MLD_GROUP             *Entry;
     36 
     37   NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
     38   ASSERT (MulticastAddr != NULL && IP6_IS_MULTICAST (MulticastAddr));
     39 
     40   Entry = AllocatePool (sizeof (IP6_MLD_GROUP));
     41   if (Entry != NULL) {
     42     Entry->RefCnt     = 1;
     43     Entry->DelayTimer = DelayTimer;
     44     Entry->SendByUs   = FALSE;
     45     IP6_COPY_ADDRESS (&Entry->Address, MulticastAddr);
     46     InsertTailList (&IpSb->MldCtrl.Groups, &Entry->Link);
     47   }
     48 
     49   return Entry;
     50 }
     51 
     52 /**
     53   Search a IP6_MLD_GROUP list entry node from a list array.
     54 
     55   @param[in]       IpSb          Points to IP6 service binding instance.
     56   @param[in]       MulticastAddr The IPv6 multicast address to be searched.
     57 
     58   @return The found IP6_ML_GROUP list entry or NULL.
     59 
     60 **/
     61 IP6_MLD_GROUP *
     62 Ip6FindMldEntry (
     63   IN IP6_SERVICE            *IpSb,
     64   IN EFI_IPv6_ADDRESS       *MulticastAddr
     65   )
     66 {
     67   LIST_ENTRY                *Entry;
     68   IP6_MLD_GROUP             *Group;
     69 
     70   NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
     71   ASSERT (MulticastAddr != NULL && IP6_IS_MULTICAST (MulticastAddr));
     72 
     73   NET_LIST_FOR_EACH (Entry, &IpSb->MldCtrl.Groups) {
     74     Group = NET_LIST_USER_STRUCT (Entry, IP6_MLD_GROUP, Link);
     75     if (EFI_IP6_EQUAL (MulticastAddr, &Group->Address)) {
     76       return Group;
     77     }
     78   }
     79 
     80   return NULL;
     81 }
     82 
     83 /**
     84   Count the number of IP6 multicast groups that are mapped to the
     85   same MAC address. Several IP6 multicast address may be mapped to
     86   the same MAC address.
     87 
     88   @param[in]  MldCtrl              The MLD control block to search in.
     89   @param[in]  Mac                  The MAC address to search.
     90 
     91   @return The number of the IP6 multicast group that mapped to the same
     92           multicast group Mac.
     93 
     94 **/
     95 INTN
     96 Ip6FindMac (
     97   IN IP6_MLD_SERVICE_DATA   *MldCtrl,
     98   IN EFI_MAC_ADDRESS        *Mac
     99   )
    100 {
    101   LIST_ENTRY                *Entry;
    102   IP6_MLD_GROUP             *Group;
    103   INTN                      Count;
    104 
    105   Count = 0;
    106 
    107   NET_LIST_FOR_EACH (Entry, &MldCtrl->Groups) {
    108     Group = NET_LIST_USER_STRUCT (Entry, IP6_MLD_GROUP, Link);
    109 
    110     if (NET_MAC_EQUAL (&Group->Mac, Mac, sizeof (EFI_MAC_ADDRESS))) {
    111       Count++;
    112     }
    113   }
    114 
    115   return Count;
    116 }
    117 
    118 /**
    119   Generate MLD report message and send it out to MulticastAddr.
    120 
    121   @param[in]  IpSb               The IP service to send the packet.
    122   @param[in]  Interface          The IP interface to send the packet.
    123                                  If NULL, a system interface will be selected.
    124   @param[in]  MulticastAddr      The specific IPv6 multicast address to which
    125                                  the message sender is listening.
    126 
    127   @retval EFI_OUT_OF_RESOURCES   There are not sufficient resources to complete the
    128                                  operation.
    129   @retval EFI_SUCCESS            The MLD report message was successfully sent out.
    130 
    131 **/
    132 EFI_STATUS
    133 Ip6SendMldReport (
    134   IN IP6_SERVICE            *IpSb,
    135   IN IP6_INTERFACE          *Interface OPTIONAL,
    136   IN EFI_IPv6_ADDRESS       *MulticastAddr
    137   )
    138 {
    139   IP6_MLD_HEAD              *MldHead;
    140   NET_BUF                   *Packet;
    141   EFI_IP6_HEADER            Head;
    142   UINT16                    PayloadLen;
    143   UINTN                     OptionLen;
    144   UINT8                     *Options;
    145   EFI_STATUS                Status;
    146   UINT16                    HeadChecksum;
    147   UINT16                    PseudoChecksum;
    148 
    149   NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
    150   ASSERT (MulticastAddr != NULL && IP6_IS_MULTICAST (MulticastAddr));
    151 
    152   //
    153   // Generate the packet to be sent
    154   // IPv6 basic header + Hop by Hop option + MLD message
    155   //
    156 
    157   OptionLen = 0;
    158   Status = Ip6FillHopByHop (NULL, &OptionLen, IP6_ICMP);
    159   ASSERT (Status == EFI_BUFFER_TOO_SMALL);
    160 
    161   PayloadLen = (UINT16) (OptionLen + sizeof (IP6_MLD_HEAD));
    162   Packet = NetbufAlloc (sizeof (EFI_IP6_HEADER) + (UINT32) PayloadLen);
    163   if (Packet == NULL) {
    164     return EFI_OUT_OF_RESOURCES;
    165   }
    166 
    167   //
    168   // Create the basic IPv6 header.
    169   // RFC3590: Use link-local address as source address if it is available,
    170   // otherwise use the unspecified address.
    171   //
    172   Head.FlowLabelL     = 0;
    173   Head.FlowLabelH     = 0;
    174   Head.PayloadLength  = HTONS (PayloadLen);
    175   Head.NextHeader     = IP6_HOP_BY_HOP;
    176   Head.HopLimit       = 1;
    177   IP6_COPY_ADDRESS (&Head.DestinationAddress, MulticastAddr);
    178 
    179   //
    180   // If Link-Local address is not ready, we use unspecified address.
    181   //
    182   IP6_COPY_ADDRESS (&Head.SourceAddress, &IpSb->LinkLocalAddr);
    183 
    184   NetbufReserve (Packet, sizeof (EFI_IP6_HEADER));
    185 
    186   //
    187   // Fill a IPv6 Router Alert option in a Hop-by-Hop Options Header
    188   //
    189   Options = NetbufAllocSpace (Packet, (UINT32) OptionLen, FALSE);
    190   ASSERT (Options != NULL);
    191   Status = Ip6FillHopByHop (Options, &OptionLen, IP6_ICMP);
    192   if (EFI_ERROR (Status)) {
    193     NetbufFree (Packet);
    194     Packet = NULL;
    195     return Status;
    196   }
    197 
    198   //
    199   // Fill in MLD message - Report
    200   //
    201   MldHead = (IP6_MLD_HEAD *) NetbufAllocSpace (Packet, sizeof (IP6_MLD_HEAD), FALSE);
    202   ASSERT (MldHead != NULL);
    203   ZeroMem (MldHead, sizeof (IP6_MLD_HEAD));
    204   MldHead->Head.Type = ICMP_V6_LISTENER_REPORT;
    205   MldHead->Head.Code = 0;
    206   IP6_COPY_ADDRESS (&MldHead->Group, MulticastAddr);
    207 
    208   HeadChecksum   = NetblockChecksum ((UINT8 *) MldHead, sizeof (IP6_MLD_HEAD));
    209   PseudoChecksum = NetIp6PseudoHeadChecksum (
    210                      &Head.SourceAddress,
    211                      &Head.DestinationAddress,
    212                      IP6_ICMP,
    213                      sizeof (IP6_MLD_HEAD)
    214                      );
    215 
    216   MldHead->Head.Checksum = (UINT16) ~NetAddChecksum (HeadChecksum, PseudoChecksum);
    217 
    218   //
    219   // Transmit the packet
    220   //
    221   return Ip6Output (IpSb, Interface, NULL, Packet, &Head, NULL, 0, Ip6SysPacketSent, NULL);
    222 }
    223 
    224 /**
    225   Generate MLD Done message and send it out to MulticastAddr.
    226 
    227   @param[in]  IpSb               The IP service to send the packet.
    228   @param[in]  MulticastAddr      The specific IPv6 multicast address to which
    229                                  the message sender is ceasing to listen.
    230 
    231   @retval EFI_OUT_OF_RESOURCES   There are not sufficient resources to complete the
    232                                  operation.
    233   @retval EFI_SUCCESS            The MLD report message was successfully sent out.
    234 
    235 **/
    236 EFI_STATUS
    237 Ip6SendMldDone (
    238   IN IP6_SERVICE            *IpSb,
    239   IN EFI_IPv6_ADDRESS       *MulticastAddr
    240   )
    241 {
    242   IP6_MLD_HEAD              *MldHead;
    243   NET_BUF                   *Packet;
    244   EFI_IP6_HEADER            Head;
    245   UINT16                    PayloadLen;
    246   UINTN                     OptionLen;
    247   UINT8                     *Options;
    248   EFI_STATUS                Status;
    249   EFI_IPv6_ADDRESS          Destination;
    250   UINT16                    HeadChecksum;
    251   UINT16                    PseudoChecksum;
    252 
    253   NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
    254   ASSERT (MulticastAddr != NULL && IP6_IS_MULTICAST (MulticastAddr));
    255 
    256   //
    257   // Generate the packet to be sent
    258   // IPv6 basic header + Hop by Hop option + MLD message
    259   //
    260 
    261   OptionLen = 0;
    262   Status = Ip6FillHopByHop (NULL, &OptionLen, IP6_ICMP);
    263   ASSERT (Status == EFI_BUFFER_TOO_SMALL);
    264 
    265   PayloadLen = (UINT16) (OptionLen + sizeof (IP6_MLD_HEAD));
    266   Packet = NetbufAlloc (sizeof (EFI_IP6_HEADER) + (UINT32) PayloadLen);
    267   if (Packet == NULL) {
    268     return EFI_OUT_OF_RESOURCES;
    269   }
    270 
    271   //
    272   // Create the basic IPv6 header.
    273   //
    274   Head.FlowLabelL     = 0;
    275   Head.FlowLabelH     = 0;
    276   Head.PayloadLength  = HTONS (PayloadLen);
    277   Head.NextHeader     = IP6_HOP_BY_HOP;
    278   Head.HopLimit       = 1;
    279 
    280   //
    281   // If Link-Local address is not ready, we use unspecified address.
    282   //
    283   IP6_COPY_ADDRESS (&Head.SourceAddress, &IpSb->LinkLocalAddr);
    284 
    285   Ip6SetToAllNodeMulticast (TRUE, IP6_LINK_LOCAL_SCOPE, &Destination);
    286   IP6_COPY_ADDRESS (&Head.DestinationAddress, &Destination);
    287 
    288   NetbufReserve (Packet, sizeof (EFI_IP6_HEADER));
    289 
    290   //
    291   // Fill a IPv6 Router Alert option in a Hop-by-Hop Options Header
    292   //
    293   Options = NetbufAllocSpace (Packet, (UINT32) OptionLen, FALSE);
    294   ASSERT (Options != NULL);
    295   Status = Ip6FillHopByHop (Options, &OptionLen, IP6_ICMP);
    296   if (EFI_ERROR (Status)) {
    297     NetbufFree (Packet);
    298     Packet = NULL;
    299     return Status;
    300   }
    301 
    302   //
    303   // Fill in MLD message - Done
    304   //
    305   MldHead = (IP6_MLD_HEAD *) NetbufAllocSpace (Packet, sizeof (IP6_MLD_HEAD), FALSE);
    306   ASSERT (MldHead != NULL);
    307   ZeroMem (MldHead, sizeof (IP6_MLD_HEAD));
    308   MldHead->Head.Type = ICMP_V6_LISTENER_DONE;
    309   MldHead->Head.Code = 0;
    310   IP6_COPY_ADDRESS (&MldHead->Group, MulticastAddr);
    311 
    312   HeadChecksum   = NetblockChecksum ((UINT8 *) MldHead, sizeof (IP6_MLD_HEAD));
    313   PseudoChecksum = NetIp6PseudoHeadChecksum (
    314                      &Head.SourceAddress,
    315                      &Head.DestinationAddress,
    316                      IP6_ICMP,
    317                      sizeof (IP6_MLD_HEAD)
    318                      );
    319 
    320   MldHead->Head.Checksum = (UINT16) ~NetAddChecksum (HeadChecksum, PseudoChecksum);
    321 
    322   //
    323   // Transmit the packet
    324   //
    325   return Ip6Output (IpSb, NULL, NULL, Packet, &Head, NULL, 0, Ip6SysPacketSent, NULL);
    326 }
    327 
    328 /**
    329   Init the MLD data of the IP6 service instance. Configure
    330   MNP to receive ALL SYSTEM multicast.
    331 
    332   @param[in]  IpSb              The IP6 service whose MLD is to be initialized.
    333 
    334   @retval EFI_OUT_OF_RESOURCES  There are not sufficient resourcet to complete the
    335                                 operation.
    336   @retval EFI_SUCCESS           The MLD module successfully initialized.
    337 
    338 **/
    339 EFI_STATUS
    340 Ip6InitMld (
    341   IN IP6_SERVICE            *IpSb
    342   )
    343 {
    344   EFI_IPv6_ADDRESS          AllNodes;
    345   IP6_MLD_GROUP             *Group;
    346   EFI_STATUS                Status;
    347 
    348   //
    349   // Join the link-scope all-nodes multicast address (FF02::1).
    350   // This address is started in Idle Listener state and never transitions to
    351   // another state, and never sends a Report or Done for that address.
    352   //
    353 
    354   Ip6SetToAllNodeMulticast (FALSE, IP6_LINK_LOCAL_SCOPE, &AllNodes);
    355 
    356   Group = Ip6CreateMldEntry (IpSb, &AllNodes, (UINT32) IP6_INFINIT_LIFETIME);
    357   if (Group == NULL) {
    358     return EFI_OUT_OF_RESOURCES;
    359   }
    360 
    361   Status = Ip6GetMulticastMac (IpSb->Mnp, &AllNodes, &Group->Mac);
    362   if (EFI_ERROR (Status)) {
    363     goto ERROR;
    364   }
    365 
    366   //
    367   // Configure MNP to receive all-nodes multicast
    368   //
    369   Status = IpSb->Mnp->Groups (IpSb->Mnp, TRUE, &Group->Mac);
    370   if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
    371     goto ERROR;
    372   }
    373 
    374   return EFI_SUCCESS;
    375 
    376 ERROR:
    377   RemoveEntryList (&Group->Link);
    378   FreePool (Group);
    379   return Status;
    380 }
    381 
    382 /**
    383   Add a group address to the array of group addresses.
    384   The caller should make sure that no duplicated address
    385   existed in the array.
    386 
    387   @param[in, out]  IpInstance       Points to an IP6_PROTOCOL instance.
    388   @param[in]       Group            The IP6 multicast address to add.
    389 
    390   @retval EFI_OUT_OF_RESOURCES      There are not sufficient resources to complete
    391                                     the operation.
    392   @retval EFI_SUCESS                The address is added to the group address array.
    393 
    394 **/
    395 EFI_STATUS
    396 Ip6CombineGroups (
    397   IN OUT IP6_PROTOCOL *IpInstance,
    398   IN EFI_IPv6_ADDRESS *Group
    399   )
    400 {
    401   EFI_IPv6_ADDRESS     *GroupList;
    402 
    403   NET_CHECK_SIGNATURE (IpInstance, IP6_PROTOCOL_SIGNATURE);
    404   ASSERT (Group != NULL && IP6_IS_MULTICAST (Group));
    405 
    406   IpInstance->GroupCount++;
    407 
    408   GroupList = AllocatePool (IpInstance->GroupCount * sizeof (EFI_IPv6_ADDRESS));
    409   if (GroupList == NULL) {
    410     return EFI_OUT_OF_RESOURCES;
    411   }
    412 
    413   if (IpInstance->GroupCount > 1) {
    414     ASSERT (IpInstance->GroupList != NULL);
    415 
    416     CopyMem (
    417       GroupList,
    418       IpInstance->GroupList,
    419       (IpInstance->GroupCount - 1) * sizeof (EFI_IPv6_ADDRESS)
    420       );
    421 
    422     FreePool (IpInstance->GroupList);
    423   }
    424 
    425   IP6_COPY_ADDRESS (GroupList + (IpInstance->GroupCount - 1), Group);
    426 
    427   IpInstance->GroupList = GroupList;
    428 
    429   return EFI_SUCCESS;
    430 }
    431 
    432 /**
    433   Remove a group address from the array of group addresses.
    434   Although the function doesn't assume the byte order of Group,
    435   the network byte order is used by the caller.
    436 
    437   @param[in, out]  IpInstance       Points to an IP6_PROTOCOL instance.
    438   @param[in]       Group            The IP6 multicast address to remove.
    439 
    440   @retval EFI_NOT_FOUND             Cannot find the to be removed group address.
    441   @retval EFI_SUCCESS               The group address was successfully removed.
    442 
    443 **/
    444 EFI_STATUS
    445 Ip6RemoveGroup (
    446   IN OUT IP6_PROTOCOL *IpInstance,
    447   IN EFI_IPv6_ADDRESS *Group
    448   )
    449 {
    450   UINT32                    Index;
    451   UINT32                    Count;
    452 
    453   Count = IpInstance->GroupCount;
    454 
    455   for (Index = 0; Index < Count; Index++) {
    456     if (EFI_IP6_EQUAL (IpInstance->GroupList + Index, Group)) {
    457       break;
    458     }
    459   }
    460 
    461   if (Index == Count) {
    462     return EFI_NOT_FOUND;
    463   }
    464 
    465   while (Index < Count - 1) {
    466     IP6_COPY_ADDRESS (IpInstance->GroupList + Index, IpInstance->GroupList + Index + 1);
    467     Index++;
    468   }
    469 
    470   ASSERT (IpInstance->GroupCount > 0);
    471   IpInstance->GroupCount--;
    472 
    473   return EFI_SUCCESS;
    474 }
    475 
    476 /**
    477   Join the multicast group on behalf of this IP6 service binding instance.
    478 
    479   @param[in]  IpSb               The IP6 service binding instance.
    480   @param[in]  Interface          Points to an IP6_INTERFACE structure.
    481   @param[in]  Address            The group address to join.
    482 
    483   @retval EFI_SUCCESS            Successfully join the multicast group.
    484   @retval EFI_OUT_OF_RESOURCES   Failed to allocate resources.
    485   @retval Others                 Failed to join the multicast group.
    486 
    487 **/
    488 EFI_STATUS
    489 Ip6JoinGroup (
    490   IN IP6_SERVICE            *IpSb,
    491   IN IP6_INTERFACE          *Interface,
    492   IN EFI_IPv6_ADDRESS       *Address
    493   )
    494 {
    495   IP6_MLD_GROUP            *Group;
    496   EFI_STATUS               Status;
    497 
    498   Group = Ip6FindMldEntry (IpSb, Address);
    499   if (Group != NULL) {
    500     Group->RefCnt++;
    501     return EFI_SUCCESS;
    502   }
    503 
    504   //
    505   // Repeat the report once or twcie after short delays [Unsolicited Report Interval] (default:10s)
    506   // Simulate this operation as a Multicast-Address-Specific Query was received for that addresss.
    507   //
    508   Group = Ip6CreateMldEntry (IpSb, Address, IP6_UNSOLICITED_REPORT_INTERVAL);
    509   if (Group == NULL) {
    510     return EFI_OUT_OF_RESOURCES;
    511   }
    512 
    513   Group->SendByUs = TRUE;
    514 
    515   Status = Ip6GetMulticastMac (IpSb->Mnp, Address, &Group->Mac);
    516   if (EFI_ERROR (Status)) {
    517     return Status;
    518   }
    519 
    520   Status = IpSb->Mnp->Groups (IpSb->Mnp, TRUE, &Group->Mac);
    521   if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
    522     goto ERROR;
    523   }
    524 
    525   //
    526   // Send unsolicited report when a node starts listening to a multicast address
    527   //
    528   Status = Ip6SendMldReport (IpSb, Interface, Address);
    529   if (EFI_ERROR (Status)) {
    530     goto ERROR;
    531   }
    532 
    533   return EFI_SUCCESS;
    534 
    535 ERROR:
    536   RemoveEntryList (&Group->Link);
    537   FreePool (Group);
    538   return Status;
    539 }
    540 
    541 /**
    542   Leave the IP6 multicast group.
    543 
    544   @param[in]  IpSb               The IP6 service binding instance.
    545   @param[in]  Address            The group address to leave.
    546 
    547   @retval EFI_NOT_FOUND          The IP6 service instance isn't in the group.
    548   @retval EFI_SUCCESS            Successfully leave the multicast group..
    549   @retval Others                 Failed to leave the multicast group.
    550 
    551 **/
    552 EFI_STATUS
    553 Ip6LeaveGroup (
    554  IN IP6_SERVICE            *IpSb,
    555  IN EFI_IPv6_ADDRESS       *Address
    556   )
    557 {
    558   IP6_MLD_GROUP            *Group;
    559   EFI_STATUS               Status;
    560 
    561   Group = Ip6FindMldEntry (IpSb, Address);
    562   if (Group == NULL) {
    563     return EFI_NOT_FOUND;
    564   }
    565 
    566   //
    567   // If more than one instance is in the group, decrease
    568   // the RefCnt then return.
    569   //
    570   if ((Group->RefCnt > 0) && (--Group->RefCnt > 0)) {
    571     return EFI_SUCCESS;
    572   }
    573 
    574   //
    575   // If multiple IP6 group addresses are mapped to the same
    576   // multicast MAC address, don't configure the MNP to leave
    577   // the MAC.
    578   //
    579   if (Ip6FindMac (&IpSb->MldCtrl, &Group->Mac) == 1) {
    580     Status = IpSb->Mnp->Groups (IpSb->Mnp, FALSE, &Group->Mac);
    581     if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) {
    582       return Status;
    583     }
    584   }
    585 
    586   //
    587   // Send a leave report if we are the last node to report
    588   //
    589   if (Group->SendByUs) {
    590     Status = Ip6SendMldDone (IpSb, Address);
    591     if (EFI_ERROR (Status)) {
    592       return Status;
    593     }
    594   }
    595 
    596   RemoveEntryList (&Group->Link);
    597   FreePool (Group);
    598 
    599   return EFI_SUCCESS;
    600 }
    601 
    602 /**
    603   Worker function for EfiIp6Groups(). The caller
    604   should make sure that the parameters are valid.
    605 
    606   @param[in]  IpInstance        The IP6 child to change the setting.
    607   @param[in]  JoinFlag          TRUE to join the group, otherwise leave it.
    608   @param[in]  GroupAddress      The target group address. If NULL, leave all
    609                                 the group addresses.
    610 
    611   @retval EFI_ALREADY_STARTED   Wants to join the group, but is already a member of it
    612   @retval EFI_OUT_OF_RESOURCES  Failed to allocate sufficient resources.
    613   @retval EFI_DEVICE_ERROR      Failed to set the group configuraton.
    614   @retval EFI_SUCCESS           Successfully updated the group setting.
    615   @retval EFI_NOT_FOUND         Try to leave the group which it isn't a member.
    616 
    617 **/
    618 EFI_STATUS
    619 Ip6Groups (
    620   IN IP6_PROTOCOL           *IpInstance,
    621   IN BOOLEAN                JoinFlag,
    622   IN EFI_IPv6_ADDRESS       *GroupAddress       OPTIONAL
    623   )
    624 {
    625   EFI_STATUS                Status;
    626   IP6_SERVICE               *IpSb;
    627   UINT32                    Index;
    628   EFI_IPv6_ADDRESS          *Group;
    629 
    630   IpSb = IpInstance->Service;
    631 
    632   if (JoinFlag) {
    633     ASSERT (GroupAddress != NULL);
    634 
    635     for (Index = 0; Index < IpInstance->GroupCount; Index++) {
    636       if (EFI_IP6_EQUAL (IpInstance->GroupList + Index, GroupAddress)) {
    637         return EFI_ALREADY_STARTED;
    638       }
    639     }
    640 
    641     Status = Ip6JoinGroup (IpSb, IpInstance->Interface, GroupAddress);
    642     if (!EFI_ERROR (Status)) {
    643       return Ip6CombineGroups (IpInstance, GroupAddress);
    644     }
    645 
    646     return Status;
    647   }
    648 
    649   //
    650   // Leave the group. Leave all the groups if GroupAddress is NULL.
    651   //
    652   for (Index = IpInstance->GroupCount; Index > 0; Index--) {
    653     Group = IpInstance->GroupList + (Index - 1);
    654 
    655     if ((GroupAddress == NULL) || EFI_IP6_EQUAL (Group, GroupAddress)) {
    656       Status = Ip6LeaveGroup (IpInstance->Service, Group);
    657       if (EFI_ERROR (Status)) {
    658         return Status;
    659       }
    660 
    661       Ip6RemoveGroup (IpInstance, Group);
    662 
    663       if (IpInstance->GroupCount == 0) {
    664         ASSERT (Index == 1);
    665         FreePool (IpInstance->GroupList);
    666         IpInstance->GroupList = NULL;
    667       }
    668 
    669       if (GroupAddress != NULL) {
    670         return EFI_SUCCESS;
    671       }
    672     }
    673   }
    674 
    675   return ((GroupAddress != NULL) ? EFI_NOT_FOUND : EFI_SUCCESS);
    676 }
    677 
    678 /**
    679   Set a random value of the delay timer for the multicast address from the range
    680   [0, Maximum Response Delay]. If a timer for any address is already
    681   running, it is reset to the new random value only if the requested
    682   Maximum Response Delay is less than the remaining value of the
    683   running timer.  If the Query packet specifies a Maximum Response
    684   Delay of zero, each timer is effectively set to zero, and the action
    685   specified below for timer expiration is performed immediately.
    686 
    687   @param[in]      IpSb               The IP6 service binding instance.
    688   @param[in]      MaxRespDelay       The Maximum Response Delay, in milliseconds.
    689   @param[in]      MulticastAddr      The multicast address.
    690   @param[in, out] Group              Points to a IP6_MLD_GROUP list entry node.
    691 
    692   @retval EFI_SUCCESS                The delay timer is successfully updated or
    693                                      timer expiration is performed immediately.
    694   @retval Others                     Failed to send out MLD report message.
    695 
    696 **/
    697 EFI_STATUS
    698 Ip6UpdateDelayTimer (
    699   IN IP6_SERVICE            *IpSb,
    700   IN UINT16                 MaxRespDelay,
    701   IN EFI_IPv6_ADDRESS       *MulticastAddr,
    702   IN OUT IP6_MLD_GROUP      *Group
    703   )
    704 {
    705   UINT32                    Delay;
    706 
    707   //
    708   // If the Query packet specifies a Maximum Response Delay of zero, perform timer
    709   // expiration immediately.
    710   //
    711   if (MaxRespDelay == 0) {
    712     Group->DelayTimer = 0;
    713     return Ip6SendMldReport (IpSb, NULL, MulticastAddr);
    714   }
    715 
    716   Delay = (UINT32) (MaxRespDelay / 1000);
    717 
    718   //
    719   // Sets a delay timer to a random value selected from the range [0, Maximum Response Delay]
    720   // If a timer is already running, resets it if the request Maximum Response Delay
    721   // is less than the remaining value of the running timer.
    722   //
    723   if (Group->DelayTimer == 0 || Delay < Group->DelayTimer) {
    724     Group->DelayTimer = Delay / 4294967295UL * NET_RANDOM (NetRandomInitSeed ());
    725   }
    726 
    727   return EFI_SUCCESS;
    728 }
    729 
    730 /**
    731   Process the Multicast Listener Query message.
    732 
    733   @param[in]  IpSb               The IP service that received the packet.
    734   @param[in]  Head               The IP head of the MLD query packet.
    735   @param[in]  Packet             The content of the MLD query packet with IP head
    736                                  removed.
    737 
    738   @retval EFI_SUCCESS            The MLD query packet processed successfully.
    739   @retval EFI_INVALID_PARAMETER  The packet is invalid.
    740   @retval Others                 Failed to process the packet.
    741 
    742 **/
    743 EFI_STATUS
    744 Ip6ProcessMldQuery (
    745   IN IP6_SERVICE            *IpSb,
    746   IN EFI_IP6_HEADER         *Head,
    747   IN NET_BUF                *Packet
    748   )
    749 {
    750   EFI_IPv6_ADDRESS          AllNodes;
    751   IP6_MLD_GROUP             *Group;
    752   IP6_MLD_HEAD              MldPacket;
    753   LIST_ENTRY                *Entry;
    754   EFI_STATUS                Status;
    755 
    756   Status = EFI_INVALID_PARAMETER;
    757 
    758   //
    759   // Check the validity of the packet, generic query or specific query
    760   //
    761   if (!NetIp6IsUnspecifiedAddr (&Head->SourceAddress) && !NetIp6IsLinkLocalAddr (&Head->SourceAddress)) {
    762     goto Exit;
    763   }
    764 
    765   if (Head->HopLimit != 1 || !IP6_IS_MULTICAST (&Head->DestinationAddress)) {
    766     goto Exit;
    767   }
    768 
    769   //
    770   // The Packet points to MLD report raw data without Hop-By-Hop option.
    771   //
    772   NetbufCopy (Packet, 0, sizeof (IP6_MLD_HEAD), (UINT8 *) &MldPacket);
    773   MldPacket.MaxRespDelay = NTOHS (MldPacket.MaxRespDelay);
    774 
    775   Ip6SetToAllNodeMulticast (FALSE, IP6_LINK_LOCAL_SCOPE, &AllNodes);
    776   if (!EFI_IP6_EQUAL (&Head->DestinationAddress, &AllNodes)) {
    777     //
    778     // Receives a Multicast-Address-Specific Query, check it firstly
    779     //
    780     if (!EFI_IP6_EQUAL (&Head->DestinationAddress, &MldPacket.Group)) {
    781       goto Exit;
    782     }
    783     //
    784     // The node is not listening but it receives the specific query. Just return.
    785     //
    786     Group = Ip6FindMldEntry (IpSb, &MldPacket.Group);
    787     if (Group == NULL) {
    788       Status = EFI_SUCCESS;
    789       goto Exit;
    790     }
    791 
    792     Status = Ip6UpdateDelayTimer (
    793                IpSb,
    794                MldPacket.MaxRespDelay,
    795                &MldPacket.Group,
    796                Group
    797                );
    798     goto Exit;
    799   }
    800 
    801   //
    802   // Receives a General Query, sets a delay timer for each multicast address it is listening
    803   //
    804   NET_LIST_FOR_EACH (Entry, &IpSb->MldCtrl.Groups) {
    805     Group  = NET_LIST_USER_STRUCT (Entry, IP6_MLD_GROUP, Link);
    806     Status = Ip6UpdateDelayTimer (IpSb, MldPacket.MaxRespDelay, &Group->Address, Group);
    807     if (EFI_ERROR (Status)) {
    808       goto Exit;
    809     }
    810   }
    811 
    812   Status = EFI_SUCCESS;
    813 
    814 Exit:
    815   NetbufFree (Packet);
    816   return Status;
    817 }
    818 
    819 /**
    820   Process the Multicast Listener Report message.
    821 
    822   @param[in]  IpSb               The IP service that received the packet.
    823   @param[in]  Head               The IP head of the MLD report packet.
    824   @param[in]  Packet             The content of the MLD report packet with IP head
    825                                  removed.
    826 
    827   @retval EFI_SUCCESS            The MLD report packet processed successfully.
    828   @retval EFI_INVALID_PARAMETER  The packet is invalid.
    829 
    830 **/
    831 EFI_STATUS
    832 Ip6ProcessMldReport (
    833   IN IP6_SERVICE            *IpSb,
    834   IN EFI_IP6_HEADER         *Head,
    835   IN NET_BUF                *Packet
    836   )
    837 {
    838   IP6_MLD_HEAD              MldPacket;
    839   IP6_MLD_GROUP             *Group;
    840   EFI_STATUS                Status;
    841 
    842   Status = EFI_INVALID_PARAMETER;
    843 
    844   //
    845   // Validate the incoming message, if invalid, drop it.
    846   //
    847   if (!NetIp6IsUnspecifiedAddr (&Head->SourceAddress) && !NetIp6IsLinkLocalAddr (&Head->SourceAddress)) {
    848     goto Exit;
    849   }
    850 
    851   if (Head->HopLimit != 1 || !IP6_IS_MULTICAST (&Head->DestinationAddress)) {
    852     goto Exit;
    853   }
    854 
    855   //
    856   // The Packet points to MLD report raw data without Hop-By-Hop option.
    857   //
    858   NetbufCopy (Packet, 0, sizeof (IP6_MLD_HEAD), (UINT8 *) &MldPacket);
    859   if (!EFI_IP6_EQUAL (&Head->DestinationAddress, &MldPacket.Group)) {
    860     goto Exit;
    861   }
    862 
    863   Group = Ip6FindMldEntry (IpSb, &MldPacket.Group);
    864   if (Group == NULL) {
    865     goto Exit;
    866   }
    867 
    868   //
    869   // The report is sent by another node, stop its own timer relates to the multicast address and clear
    870   //
    871 
    872   if (!Group->SendByUs) {
    873     Group->DelayTimer = 0;
    874   }
    875 
    876   Status = EFI_SUCCESS;
    877 
    878 Exit:
    879   NetbufFree (Packet);
    880   return Status;
    881 }
    882 
    883 /**
    884   The heartbeat timer of MLD module. It sends out a solicited MLD report when
    885   DelayTimer expires.
    886 
    887   @param[in]  IpSb              The IP6 service binding instance.
    888 
    889 **/
    890 VOID
    891 Ip6MldTimerTicking (
    892   IN IP6_SERVICE            *IpSb
    893   )
    894 {
    895   IP6_MLD_GROUP             *Group;
    896   LIST_ENTRY                *Entry;
    897 
    898   //
    899   // Send solicited report when timer expires
    900   //
    901   NET_LIST_FOR_EACH (Entry, &IpSb->MldCtrl.Groups) {
    902     Group = NET_LIST_USER_STRUCT (Entry, IP6_MLD_GROUP, Link);
    903     if ((Group->DelayTimer > 0) && (--Group->DelayTimer == 0)) {
    904       Ip6SendMldReport (IpSb, NULL, &Group->Address);
    905     }
    906   }
    907 }
    908 
    909