/** @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 destination 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 reference 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 malformatted. | |
@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 number 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; | |
} |