Home | History | Annotate | Download | only in Ip4Dxe
      1 /** @file
      2   This file implements the RFC2236: IGMP v2.
      3 
      4 Copyright (c) 2005 - 2015, Intel Corporation. All rights reserved.<BR>
      5 This program and the accompanying materials
      6 are licensed and made available under the terms and conditions of the BSD License
      7 which accompanies this distribution.  The full text of the license may be found at
      8 http://opensource.org/licenses/bsd-license.php
      9 
     10 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
     11 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
     12 
     13 **/
     14 
     15 #include "Ip4Impl.h"
     16 
     17 //
     18 // Route Alert option in IGMP report to direct routers to
     19 // examine the packet more closely.
     20 //
     21 UINT32  mRouteAlertOption = 0x00000494;
     22 
     23 
     24 /**
     25   Init the IGMP control data of the IP4 service instance, configure
     26   MNP to receive ALL SYSTEM multicast.
     27 
     28   @param[in, out]  IpSb          The IP4 service whose IGMP is to be initialized.
     29 
     30   @retval EFI_SUCCESS            IGMP of the IpSb is successfully initialized.
     31   @retval EFI_OUT_OF_RESOURCES   Failed to allocate resource to initialize IGMP.
     32   @retval Others                 Failed to initialize the IGMP of IpSb.
     33 
     34 **/
     35 EFI_STATUS
     36 Ip4InitIgmp (
     37   IN OUT IP4_SERVICE            *IpSb
     38   )
     39 {
     40   IGMP_SERVICE_DATA             *IgmpCtrl;
     41   EFI_MANAGED_NETWORK_PROTOCOL  *Mnp;
     42   IGMP_GROUP                    *Group;
     43   EFI_STATUS                    Status;
     44 
     45   IgmpCtrl = &IpSb->IgmpCtrl;
     46 
     47   //
     48   // Configure MNP to receive ALL_SYSTEM multicast
     49   //
     50   Group    = AllocatePool (sizeof (IGMP_GROUP));
     51 
     52   if (Group == NULL) {
     53     return EFI_OUT_OF_RESOURCES;
     54   }
     55 
     56   Mnp               = IpSb->Mnp;
     57 
     58   Group->Address    = IP4_ALLSYSTEM_ADDRESS;
     59   Group->RefCnt     = 1;
     60   Group->DelayTime  = 0;
     61   Group->ReportByUs = FALSE;
     62 
     63   Status = Ip4GetMulticastMac (Mnp, IP4_ALLSYSTEM_ADDRESS, &Group->Mac);
     64 
     65   if (EFI_ERROR (Status)) {
     66     goto ON_ERROR;
     67   }
     68 
     69   Status = Mnp->Groups (Mnp, TRUE, &Group->Mac);
     70 
     71   if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
     72     goto ON_ERROR;
     73   }
     74 
     75   InsertHeadList (&IgmpCtrl->Groups, &Group->Link);
     76   return EFI_SUCCESS;
     77 
     78 ON_ERROR:
     79   FreePool (Group);
     80   return Status;
     81 }
     82 
     83 
     84 /**
     85   Find the IGMP_GROUP structure which contains the status of multicast
     86   group Address in this IGMP control block
     87 
     88   @param[in]  IgmpCtrl               The IGMP control block to search from.
     89   @param[in]  Address                The multicast address to search.
     90 
     91   @return NULL if the multicast address isn't in the IGMP control block. Otherwise
     92           the point to the IGMP_GROUP which contains the status of multicast group
     93           for Address.
     94 
     95 **/
     96 IGMP_GROUP *
     97 Ip4FindGroup (
     98   IN IGMP_SERVICE_DATA      *IgmpCtrl,
     99   IN IP4_ADDR               Address
    100   )
    101 {
    102   LIST_ENTRY                *Entry;
    103   IGMP_GROUP                *Group;
    104 
    105   NET_LIST_FOR_EACH (Entry, &IgmpCtrl->Groups) {
    106     Group = NET_LIST_USER_STRUCT (Entry, IGMP_GROUP, Link);
    107 
    108     if (Group->Address == Address) {
    109       return Group;
    110     }
    111   }
    112 
    113   return NULL;
    114 }
    115 
    116 
    117 /**
    118   Count the number of IP4 multicast groups that are mapped to the
    119   same MAC address. Several IP4 multicast address may be mapped to
    120   the same MAC address.
    121 
    122   @param[in]  IgmpCtrl               The IGMP control block to search in.
    123   @param[in]  Mac                    The MAC address to search.
    124 
    125   @return The number of the IP4 multicast group that mapped to the same
    126           multicast group Mac.
    127 
    128 **/
    129 INTN
    130 Ip4FindMac (
    131   IN IGMP_SERVICE_DATA      *IgmpCtrl,
    132   IN EFI_MAC_ADDRESS        *Mac
    133   )
    134 {
    135   LIST_ENTRY                *Entry;
    136   IGMP_GROUP                *Group;
    137   INTN                      Count;
    138 
    139   Count = 0;
    140 
    141   NET_LIST_FOR_EACH (Entry, &IgmpCtrl->Groups) {
    142     Group = NET_LIST_USER_STRUCT (Entry, IGMP_GROUP, Link);
    143 
    144     if (NET_MAC_EQUAL (&Group->Mac, Mac, sizeof (EFI_MAC_ADDRESS))) {
    145       Count++;
    146     }
    147   }
    148 
    149   return Count;
    150 }
    151 
    152 
    153 /**
    154   Send an IGMP protocol message to the Dst, such as IGMP v1 membership report.
    155 
    156   @param[in]  IpSb               The IP4 service instance that requests the
    157                                  transmission.
    158   @param[in]  Dst                The destinaton to send to.
    159   @param[in]  Type               The IGMP message type, such as IGMP v1 membership
    160                                  report.
    161   @param[in]  Group              The group address in the IGMP message head.
    162 
    163   @retval EFI_OUT_OF_RESOURCES   Failed to allocate memory to build the message.
    164   @retval EFI_SUCCESS            The IGMP message is successfully send.
    165   @retval Others                 Failed to send the IGMP message.
    166 
    167 **/
    168 EFI_STATUS
    169 Ip4SendIgmpMessage (
    170   IN IP4_SERVICE            *IpSb,
    171   IN IP4_ADDR               Dst,
    172   IN UINT8                  Type,
    173   IN IP4_ADDR               Group
    174   )
    175 {
    176   IP4_HEAD                  Head;
    177   NET_BUF                   *Packet;
    178   IGMP_HEAD                 *Igmp;
    179 
    180   //
    181   // Allocate a net buffer to hold the message
    182   //
    183   Packet = NetbufAlloc (IP4_MAX_HEADLEN + sizeof (IGMP_HEAD));
    184 
    185   if (Packet == NULL) {
    186     return EFI_OUT_OF_RESOURCES;
    187   }
    188 
    189   //
    190   // Fill in the IGMP and IP header, then transmit the message
    191   //
    192   NetbufReserve (Packet, IP4_MAX_HEADLEN);
    193 
    194   Igmp = (IGMP_HEAD *) NetbufAllocSpace (Packet, sizeof (IGMP_HEAD), FALSE);
    195   if (Igmp == NULL) {
    196     return EFI_OUT_OF_RESOURCES;
    197   }
    198 
    199   Igmp->Type        = Type;
    200   Igmp->MaxRespTime = 0;
    201   Igmp->Checksum    = 0;
    202   Igmp->Group       = HTONL (Group);
    203   Igmp->Checksum    = (UINT16) (~NetblockChecksum ((UINT8 *) Igmp, sizeof (IGMP_HEAD)));
    204 
    205   Head.Tos          = 0;
    206   Head.Protocol     = IP4_PROTO_IGMP;
    207   Head.Ttl          = 1;
    208   Head.Fragment     = 0;
    209   Head.Dst          = Dst;
    210   Head.Src          = IP4_ALLZERO_ADDRESS;
    211 
    212   return Ip4Output (
    213            IpSb,
    214            NULL,
    215            Packet,
    216            &Head,
    217            (UINT8 *) &mRouteAlertOption,
    218            sizeof (UINT32),
    219            IP4_ALLZERO_ADDRESS,
    220            Ip4SysPacketSent,
    221            NULL
    222            );
    223 }
    224 
    225 
    226 /**
    227   Send an IGMP membership report. Depends on whether the server is
    228   v1 or v2, it will send either a V1 or V2 membership report.
    229 
    230   @param[in]  IpSb               The IP4 service instance that requests the
    231                                  transmission.
    232   @param[in]  Group              The group address to report.
    233 
    234   @retval EFI_OUT_OF_RESOURCES   Failed to allocate memory to build the message.
    235   @retval EFI_SUCCESS            The IGMP report message is successfully send.
    236   @retval Others                 Failed to send the report.
    237 
    238 **/
    239 EFI_STATUS
    240 Ip4SendIgmpReport (
    241   IN IP4_SERVICE            *IpSb,
    242   IN IP4_ADDR               Group
    243   )
    244 {
    245   if (IpSb->IgmpCtrl.Igmpv1QuerySeen != 0) {
    246     return Ip4SendIgmpMessage (IpSb, Group, IGMP_V1_MEMBERSHIP_REPORT, Group);
    247   } else {
    248     return Ip4SendIgmpMessage (IpSb, Group, IGMP_V2_MEMBERSHIP_REPORT, Group);
    249   }
    250 }
    251 
    252 
    253 /**
    254   Join the multicast group on behalf of this IP4 child
    255 
    256   @param[in]  IpInstance         The IP4 child that wants to join the group.
    257   @param[in]  Address            The group to join.
    258 
    259   @retval EFI_SUCCESS            Successfully join the multicast group.
    260   @retval EFI_OUT_OF_RESOURCES   Failed to allocate resources.
    261   @retval Others                 Failed to join the multicast group.
    262 
    263 **/
    264 EFI_STATUS
    265 Ip4JoinGroup (
    266   IN IP4_PROTOCOL           *IpInstance,
    267   IN IP4_ADDR               Address
    268   )
    269 {
    270   EFI_MANAGED_NETWORK_PROTOCOL  *Mnp;
    271   IP4_SERVICE                   *IpSb;
    272   IGMP_SERVICE_DATA             *IgmpCtrl;
    273   IGMP_GROUP                    *Group;
    274   EFI_STATUS                    Status;
    275 
    276   IpSb      = IpInstance->Service;
    277   IgmpCtrl  = &IpSb->IgmpCtrl;
    278   Mnp       = IpSb->Mnp;
    279 
    280   //
    281   // If the IP service already is a member in the group, just
    282   // increase the refernce count and return.
    283   //
    284   Group     = Ip4FindGroup (IgmpCtrl, Address);
    285 
    286   if (Group != NULL) {
    287     Group->RefCnt++;
    288     return EFI_SUCCESS;
    289   }
    290 
    291   //
    292   // Otherwise, create a new IGMP_GROUP,  Get the multicast's MAC address,
    293   // send a report, then direct MNP to receive the multicast.
    294   //
    295   Group = AllocatePool (sizeof (IGMP_GROUP));
    296 
    297   if (Group == NULL) {
    298     return EFI_OUT_OF_RESOURCES;
    299   }
    300 
    301   Group->Address    = Address;
    302   Group->RefCnt     = 1;
    303   Group->DelayTime  = IGMP_UNSOLICIATED_REPORT;
    304   Group->ReportByUs = TRUE;
    305 
    306   Status = Ip4GetMulticastMac (Mnp, Address, &Group->Mac);
    307 
    308   if (EFI_ERROR (Status)) {
    309     goto ON_ERROR;
    310   }
    311 
    312   Status = Ip4SendIgmpReport (IpSb, Address);
    313 
    314   if (EFI_ERROR (Status)) {
    315     goto ON_ERROR;
    316   }
    317 
    318   Status = Mnp->Groups (Mnp, TRUE, &Group->Mac);
    319 
    320   if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
    321     goto ON_ERROR;
    322   }
    323 
    324   InsertHeadList (&IgmpCtrl->Groups, &Group->Link);
    325   return EFI_SUCCESS;
    326 
    327 ON_ERROR:
    328   FreePool (Group);
    329   return Status;
    330 }
    331 
    332 
    333 /**
    334   Leave the IP4 multicast group on behalf of IpInstance.
    335 
    336   @param[in]  IpInstance         The IP4 child that wants to leave the group
    337                                  address.
    338   @param[in]  Address            The group address to leave.
    339 
    340   @retval EFI_NOT_FOUND          The IP4 service instance isn't in the group.
    341   @retval EFI_SUCCESS            Successfully leave the multicast group.
    342   @retval Others                 Failed to leave the multicast group.
    343 
    344 **/
    345 EFI_STATUS
    346 Ip4LeaveGroup (
    347   IN IP4_PROTOCOL           *IpInstance,
    348   IN IP4_ADDR               Address
    349   )
    350 {
    351   EFI_MANAGED_NETWORK_PROTOCOL  *Mnp;
    352   IP4_SERVICE                   *IpSb;
    353   IGMP_SERVICE_DATA             *IgmpCtrl;
    354   IGMP_GROUP                    *Group;
    355   EFI_STATUS                    Status;
    356 
    357   IpSb      = IpInstance->Service;
    358   IgmpCtrl  = &IpSb->IgmpCtrl;
    359   Mnp       = IpSb->Mnp;
    360 
    361   Group     = Ip4FindGroup (IgmpCtrl, Address);
    362 
    363   if (Group == NULL) {
    364     return EFI_NOT_FOUND;
    365   }
    366 
    367   //
    368   // If more than one instance is in the group, decrease
    369   // the RefCnt then return.
    370   //
    371   if (--Group->RefCnt > 0) {
    372     return EFI_SUCCESS;
    373   }
    374 
    375   //
    376   // If multiple IP4 group addresses are mapped to the same
    377   // multicast MAC address, don't configure the MNP to leave
    378   // the MAC.
    379   //
    380   if (Ip4FindMac (IgmpCtrl, &Group->Mac) == 1) {
    381     Status = Mnp->Groups (Mnp, FALSE, &Group->Mac);
    382 
    383     if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) {
    384       return Status;
    385     }
    386   }
    387 
    388   //
    389   // Send a leave report if the membership is reported by us
    390   // and we are talking IGMPv2.
    391   //
    392   if (Group->ReportByUs && IgmpCtrl->Igmpv1QuerySeen == 0) {
    393     Ip4SendIgmpMessage (IpSb, IP4_ALLROUTER_ADDRESS, IGMP_LEAVE_GROUP, Group->Address);
    394   }
    395 
    396   RemoveEntryList (&Group->Link);
    397   FreePool (Group);
    398 
    399   return EFI_SUCCESS;
    400 }
    401 
    402 
    403 /**
    404   Handle the received IGMP message for the IP4 service instance.
    405 
    406   @param[in]  IpSb               The IP4 service instance that received the message.
    407   @param[in]  Head               The IP4 header of the received message.
    408   @param[in]  Packet             The IGMP message, without IP4 header.
    409 
    410   @retval EFI_INVALID_PARAMETER  The IGMP message is malformated.
    411   @retval EFI_SUCCESS            The IGMP message is successfully processed.
    412 
    413 **/
    414 EFI_STATUS
    415 Ip4IgmpHandle (
    416   IN IP4_SERVICE            *IpSb,
    417   IN IP4_HEAD               *Head,
    418   IN NET_BUF                *Packet
    419   )
    420 {
    421   IGMP_SERVICE_DATA         *IgmpCtrl;
    422   IGMP_HEAD                 Igmp;
    423   IGMP_GROUP                *Group;
    424   IP4_ADDR                  Address;
    425   LIST_ENTRY                *Entry;
    426 
    427   IgmpCtrl = &IpSb->IgmpCtrl;
    428 
    429   //
    430   // Must checksum over the whole packet, later IGMP version
    431   // may employ message longer than 8 bytes. IP's header has
    432   // already been trimmed off.
    433   //
    434   if ((Packet->TotalSize < sizeof (Igmp)) || (NetbufChecksum (Packet) != 0)) {
    435     NetbufFree (Packet);
    436     return EFI_INVALID_PARAMETER;
    437   }
    438 
    439   //
    440   // Copy the packet in case it is fragmented
    441   //
    442   NetbufCopy (Packet, 0, sizeof (IGMP_HEAD), (UINT8 *)&Igmp);
    443 
    444   switch (Igmp.Type) {
    445   case IGMP_MEMBERSHIP_QUERY:
    446     //
    447     // If MaxRespTime is zero, it is most likely that we are
    448     // talking to a V1 router
    449     //
    450     if (Igmp.MaxRespTime == 0) {
    451       IgmpCtrl->Igmpv1QuerySeen = IGMP_V1ROUTER_PRESENT;
    452       Igmp.MaxRespTime          = 100;
    453     }
    454 
    455     //
    456     // Igmp is ticking once per second but MaxRespTime is in
    457     // the unit of 100ms.
    458     //
    459     Igmp.MaxRespTime /= 10;
    460     Address = NTOHL (Igmp.Group);
    461 
    462     if (Address == IP4_ALLSYSTEM_ADDRESS) {
    463       break;
    464     }
    465 
    466     NET_LIST_FOR_EACH (Entry, &IgmpCtrl->Groups) {
    467       Group = NET_LIST_USER_STRUCT (Entry, IGMP_GROUP, Link);
    468 
    469       //
    470       // If address is all zero, all the memberships will be reported.
    471       // otherwise only one is reported.
    472       //
    473       if ((Address == IP4_ALLZERO_ADDRESS) || (Address == Group->Address)) {
    474         //
    475         // If the timer is pending, only update it if the time left
    476         // is longer than the MaxRespTime. TODO: randomize the DelayTime.
    477         //
    478         if ((Group->DelayTime == 0) || (Group->DelayTime > Igmp.MaxRespTime)) {
    479           Group->DelayTime = MAX (1, Igmp.MaxRespTime);
    480         }
    481       }
    482     }
    483 
    484     break;
    485 
    486   case IGMP_V1_MEMBERSHIP_REPORT:
    487   case IGMP_V2_MEMBERSHIP_REPORT:
    488     Address = NTOHL (Igmp.Group);
    489     Group   = Ip4FindGroup (IgmpCtrl, Address);
    490 
    491     if ((Group != NULL) && (Group->DelayTime > 0)) {
    492       Group->DelayTime  = 0;
    493       Group->ReportByUs = FALSE;
    494     }
    495 
    496     break;
    497   }
    498 
    499   NetbufFree (Packet);
    500   return EFI_SUCCESS;
    501 }
    502 
    503 
    504 /**
    505   The periodical timer function for IGMP. It does the following
    506   things:
    507   1. Decrease the Igmpv1QuerySeen to make it possible to refresh
    508      the IGMP server type.
    509   2. Decrease the report timer for each IGMP group in "delaying
    510      member" state.
    511 
    512   @param[in]  IpSb                   The IP4 service instance that is ticking.
    513 
    514 **/
    515 VOID
    516 Ip4IgmpTicking (
    517   IN IP4_SERVICE            *IpSb
    518   )
    519 {
    520   IGMP_SERVICE_DATA         *IgmpCtrl;
    521   LIST_ENTRY                *Entry;
    522   IGMP_GROUP                *Group;
    523 
    524   IgmpCtrl = &IpSb->IgmpCtrl;
    525 
    526   if (IgmpCtrl->Igmpv1QuerySeen > 0) {
    527     IgmpCtrl->Igmpv1QuerySeen--;
    528   }
    529 
    530   //
    531   // Decrease the report timer for each IGMP group in "delaying member"
    532   //
    533   NET_LIST_FOR_EACH (Entry, &IgmpCtrl->Groups) {
    534     Group = NET_LIST_USER_STRUCT (Entry, IGMP_GROUP, Link);
    535     ASSERT (Group->DelayTime >= 0);
    536 
    537     if (Group->DelayTime > 0) {
    538       Group->DelayTime--;
    539 
    540       if (Group->DelayTime == 0) {
    541         Ip4SendIgmpReport (IpSb, Group->Address);
    542         Group->ReportByUs = TRUE;
    543       }
    544     }
    545   }
    546 }
    547 
    548 
    549 /**
    550   Add a group address to the array of group addresses.
    551   The caller should make sure that no duplicated address
    552   existed in the array. Although the function doesn't
    553   assume the byte order of the both Source and Addr, the
    554   network byte order is used by the caller.
    555 
    556   @param[in]  Source                 The array of group addresses to add to.
    557   @param[in]  Count                  The number of group addresses in the Source.
    558   @param[in]  Addr                   The IP4 multicast address to add.
    559 
    560   @return NULL if failed to allocate memory for the new groups,
    561           otherwise the new combined group addresses.
    562 
    563 **/
    564 IP4_ADDR *
    565 Ip4CombineGroups (
    566   IN  IP4_ADDR              *Source,
    567   IN  UINT32                Count,
    568   IN  IP4_ADDR              Addr
    569   )
    570 {
    571   IP4_ADDR                  *Groups;
    572 
    573   Groups = AllocatePool (sizeof (IP4_ADDR) * (Count + 1));
    574 
    575   if (Groups == NULL) {
    576     return NULL;
    577   }
    578 
    579   CopyMem (Groups, Source, Count * sizeof (IP4_ADDR));
    580   Groups[Count] = Addr;
    581 
    582   return Groups;
    583 }
    584 
    585 
    586 /**
    587   Remove a group address from the array of group addresses.
    588   Although the function doesn't assume the byte order of the
    589   both Groups and Addr, the network byte order is used by
    590   the caller.
    591 
    592   @param  Groups            The array of group addresses to remove from.
    593   @param  Count             The number of group addresses in the Groups.
    594   @param  Addr              The IP4 multicast address to remove.
    595 
    596   @return The nubmer of group addresses in the Groups after remove.
    597           It is Count if the Addr isn't in the Groups.
    598 
    599 **/
    600 INTN
    601 Ip4RemoveGroupAddr (
    602   IN OUT IP4_ADDR               *Groups,
    603   IN     UINT32                 Count,
    604   IN     IP4_ADDR               Addr
    605   )
    606 {
    607   UINT32                    Index;
    608 
    609   for (Index = 0; Index < Count; Index++) {
    610     if (Groups[Index] == Addr) {
    611       break;
    612     }
    613   }
    614 
    615   while (Index < Count - 1) {
    616     Groups[Index] = Groups[Index + 1];
    617     Index++;
    618   }
    619 
    620   return Index;
    621 }
    622