/** @file | |
The internal functions and routines to transmit the IP6 packet. | |
Copyright (c) 2009 - 2014, Intel Corporation. All rights reserved.<BR> | |
This program and the accompanying materials | |
are licensed and made available under the terms and conditions of the BSD License | |
which accompanies this distribution. The full text of the license may be found at | |
http://opensource.org/licenses/bsd-license.php. | |
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
**/ | |
#include "Ip6Impl.h" | |
UINT32 mIp6Id; | |
/** | |
Output all the available source addresses to a list entry head SourceList. The | |
number of source addresses are also returned. | |
@param[in] IpSb Points to an IP6 service binding instance. | |
@param[out] SourceList The list entry head of all source addresses. | |
It is the caller's responsiblity to free the | |
resources. | |
@param[out] SourceCount The number of source addresses. | |
@retval EFI_SUCCESS The source addresses were copied to a list entry head | |
SourceList. | |
@retval EFI_OUT_OF_RESOURCES Failed to allocate resources to complete the operation. | |
**/ | |
EFI_STATUS | |
Ip6CandidateSource ( | |
IN IP6_SERVICE *IpSb, | |
OUT LIST_ENTRY *SourceList, | |
OUT UINT32 *SourceCount | |
) | |
{ | |
IP6_INTERFACE *IpIf; | |
LIST_ENTRY *Entry; | |
LIST_ENTRY *Entry2; | |
IP6_ADDRESS_INFO *AddrInfo; | |
IP6_ADDRESS_INFO *Copy; | |
*SourceCount = 0; | |
if (IpSb->LinkLocalOk) { | |
Copy = AllocatePool (sizeof (IP6_ADDRESS_INFO)); | |
if (Copy == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Copy->Signature = IP6_ADDR_INFO_SIGNATURE; | |
IP6_COPY_ADDRESS (&Copy->Address, &IpSb->LinkLocalAddr); | |
Copy->IsAnycast = FALSE; | |
Copy->PrefixLength = IP6_LINK_LOCAL_PREFIX_LENGTH; | |
Copy->ValidLifetime = (UINT32) IP6_INFINIT_LIFETIME; | |
Copy->PreferredLifetime = (UINT32) IP6_INFINIT_LIFETIME; | |
InsertTailList (SourceList, &Copy->Link); | |
(*SourceCount)++; | |
} | |
NET_LIST_FOR_EACH (Entry, &IpSb->Interfaces) { | |
IpIf = NET_LIST_USER_STRUCT (Entry, IP6_INTERFACE, Link); | |
NET_LIST_FOR_EACH (Entry2, &IpIf->AddressList) { | |
AddrInfo = NET_LIST_USER_STRUCT_S (Entry2, IP6_ADDRESS_INFO, Link, IP6_ADDR_INFO_SIGNATURE); | |
if (AddrInfo->IsAnycast) { | |
// | |
// Never use an anycast address. | |
// | |
continue; | |
} | |
Copy = AllocateCopyPool (sizeof (IP6_ADDRESS_INFO), AddrInfo); | |
if (Copy == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
InsertTailList (SourceList, &Copy->Link); | |
(*SourceCount)++; | |
} | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Caculate how many bits are the same between two IPv6 addresses. | |
@param[in] AddressA Points to an IPv6 address. | |
@param[in] AddressB Points to another IPv6 address. | |
@return The common bits of the AddressA and AddressB. | |
**/ | |
UINT8 | |
Ip6CommonPrefixLen ( | |
IN EFI_IPv6_ADDRESS *AddressA, | |
IN EFI_IPv6_ADDRESS *AddressB | |
) | |
{ | |
UINT8 Count; | |
UINT8 Index; | |
UINT8 ByteA; | |
UINT8 ByteB; | |
UINT8 NumBits; | |
Count = 0; | |
Index = 0; | |
while (Index < 16) { | |
ByteA = AddressA->Addr[Index]; | |
ByteB = AddressB->Addr[Index]; | |
if (ByteA == ByteB) { | |
Count += 8; | |
Index++; | |
continue; | |
} | |
// | |
// Check how many bits are common between the two bytes. | |
// | |
NumBits = 8; | |
ByteA = (UINT8) (ByteA ^ ByteB); | |
while (ByteA != 0) { | |
NumBits--; | |
ByteA = (UINT8) (ByteA >> 1); | |
} | |
return (UINT8) (Count + NumBits); | |
} | |
return Count; | |
} | |
/** | |
Output all the available source addresses to a list entry head SourceList. The | |
number of source addresses are also returned. | |
@param[in] IpSb Points to a IP6 service binding instance. | |
@param[in] Destination The IPv6 destination address. | |
@param[out] Source The selected IPv6 source address according to | |
the Destination. | |
@retval EFI_SUCCESS The source addresses were copied to a list entry | |
head SourceList. | |
@retval EFI_NO_MAPPING The IPv6 stack is not auto configured. | |
**/ | |
EFI_STATUS | |
Ip6SelectSourceAddress ( | |
IN IP6_SERVICE *IpSb, | |
IN EFI_IPv6_ADDRESS *Destination, | |
OUT EFI_IPv6_ADDRESS *Source | |
) | |
{ | |
EFI_STATUS Status; | |
LIST_ENTRY SourceList; | |
UINT32 SourceCount; | |
UINT8 ScopeD; | |
LIST_ENTRY *Entry; | |
IP6_ADDRESS_INFO *AddrInfo; | |
IP6_PREFIX_LIST_ENTRY *Prefix; | |
UINT8 LastCommonLength; | |
UINT8 CurrentCommonLength; | |
EFI_IPv6_ADDRESS *TmpAddress; | |
NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE); | |
Status = EFI_SUCCESS; | |
InitializeListHead (&SourceList); | |
if (!IpSb->LinkLocalOk) { | |
return EFI_NO_MAPPING; | |
} | |
// | |
// Rule 1: Prefer same address. | |
// | |
if (Ip6IsOneOfSetAddress (IpSb, Destination, NULL, NULL)) { | |
IP6_COPY_ADDRESS (Source, Destination); | |
goto Exit; | |
} | |
// | |
// Rule 2: Prefer appropriate scope. | |
// | |
if (IP6_IS_MULTICAST (Destination)) { | |
ScopeD = (UINT8) (Destination->Addr[1] >> 4); | |
} else if (NetIp6IsLinkLocalAddr (Destination)) { | |
ScopeD = 0x2; | |
} else { | |
ScopeD = 0xE; | |
} | |
if (ScopeD <= 0x2) { | |
// | |
// Return the link-local address if it exists | |
// One IP6_SERVICE only has one link-local address. | |
// | |
IP6_COPY_ADDRESS (Source, &IpSb->LinkLocalAddr); | |
goto Exit; | |
} | |
// | |
// All candidate source addresses are global unicast address. | |
// | |
Ip6CandidateSource (IpSb, &SourceList, &SourceCount); | |
if (SourceCount == 0) { | |
Status = EFI_NO_MAPPING; | |
goto Exit; | |
} | |
IP6_COPY_ADDRESS (Source, &IpSb->LinkLocalAddr); | |
if (SourceCount == 1) { | |
goto Exit; | |
} | |
// | |
// Rule 3: Avoid deprecated addresses. | |
// TODO: check the "deprecated" state of the stateful configured address | |
// | |
NET_LIST_FOR_EACH (Entry, &IpSb->AutonomousPrefix) { | |
Prefix = NET_LIST_USER_STRUCT (Entry, IP6_PREFIX_LIST_ENTRY, Link); | |
if (Prefix->PreferredLifetime == 0) { | |
Ip6RemoveAddr (NULL, &SourceList, &SourceCount, &Prefix->Prefix, Prefix->PrefixLength); | |
if (SourceCount == 1) { | |
goto Exit; | |
} | |
} | |
} | |
// | |
// TODO: Rule 4: Prefer home addresses. | |
// TODO: Rule 5: Prefer outgoing interface. | |
// TODO: Rule 6: Prefer matching label. | |
// TODO: Rule 7: Prefer public addresses. | |
// | |
// | |
// Rule 8: Use longest matching prefix. | |
// | |
LastCommonLength = Ip6CommonPrefixLen (Source, Destination); | |
TmpAddress = NULL; | |
for (Entry = SourceList.ForwardLink; Entry != &SourceList; Entry = Entry->ForwardLink) { | |
AddrInfo = NET_LIST_USER_STRUCT_S (Entry, IP6_ADDRESS_INFO, Link, IP6_ADDR_INFO_SIGNATURE); | |
CurrentCommonLength = Ip6CommonPrefixLen (&AddrInfo->Address, Destination); | |
if (CurrentCommonLength > LastCommonLength) { | |
LastCommonLength = CurrentCommonLength; | |
TmpAddress = &AddrInfo->Address; | |
} | |
} | |
if (TmpAddress != NULL) { | |
IP6_COPY_ADDRESS (Source, TmpAddress); | |
} | |
Exit: | |
Ip6RemoveAddr (NULL, &SourceList, &SourceCount, NULL, 0); | |
return Status; | |
} | |
/** | |
Select an interface to send the packet generated in the IP6 driver | |
itself: that is, not by the requests of the IP6 child's consumer. Such | |
packets include the ICMPv6 echo replies and other ICMPv6 error packets. | |
@param[in] IpSb The IP4 service that wants to send the packets. | |
@param[in] Destination The destination of the packet. | |
@param[in, out] Source The source of the packet. | |
@return NULL if no proper interface is found, otherwise, the interface that | |
can be used to send the system packet from. | |
**/ | |
IP6_INTERFACE * | |
Ip6SelectInterface ( | |
IN IP6_SERVICE *IpSb, | |
IN EFI_IPv6_ADDRESS *Destination, | |
IN OUT EFI_IPv6_ADDRESS *Source | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_IPv6_ADDRESS SelectedSource; | |
IP6_INTERFACE *IpIf; | |
BOOLEAN Exist; | |
NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE); | |
ASSERT (Destination != NULL && Source != NULL); | |
if (NetIp6IsUnspecifiedAddr (Destination)) { | |
return NULL; | |
} | |
if (!NetIp6IsUnspecifiedAddr (Source)) { | |
Exist = Ip6IsOneOfSetAddress (IpSb, Source, &IpIf, NULL); | |
ASSERT (Exist); | |
return IpIf; | |
} | |
// | |
// If source is unspecified, select a source according to the destination. | |
// | |
Status = Ip6SelectSourceAddress (IpSb, Destination, &SelectedSource); | |
if (EFI_ERROR (Status)) { | |
return IpSb->DefaultInterface; | |
} | |
Ip6IsOneOfSetAddress (IpSb, &SelectedSource, &IpIf, NULL); | |
IP6_COPY_ADDRESS (Source, &SelectedSource); | |
return IpIf; | |
} | |
/** | |
The default callback function for the system generated packet. | |
It will free the packet. | |
@param[in] Packet The packet that transmitted. | |
@param[in] IoStatus The result of the transmission, succeeded or failed. | |
@param[in] LinkFlag Not used when transmitted. Check IP6_FRAME_CALLBACK | |
for reference. | |
@param[in] Context The context provided by us. | |
**/ | |
VOID | |
Ip6SysPacketSent ( | |
NET_BUF *Packet, | |
EFI_STATUS IoStatus, | |
UINT32 LinkFlag, | |
VOID *Context | |
) | |
{ | |
NetbufFree (Packet); | |
Packet = NULL; | |
} | |
/** | |
Prefix an IP6 basic head and unfragmentable extension headers and a fragment header | |
to the Packet. Used for IP6 fragmentation. | |
@param[in] IpSb The IP6 service instance to transmit the packet. | |
@param[in] Packet The packet to prefix the IP6 header to. | |
@param[in] Head The caller supplied header. | |
@param[in] FragmentOffset The fragment offset of the data following the header. | |
@param[in] ExtHdrs The length of the original extension header. | |
@param[in] ExtHdrsLen The length of the extension headers. | |
@param[in] LastHeader The pointer of next header of last extension header. | |
@param[in] HeadLen The length of the unfragmented part of the IP6 header. | |
@retval EFI_BAD_BUFFER_SIZE There is no enought room in the head space of | |
Packet. | |
@retval EFI_SUCCESS The operation performed successfully. | |
**/ | |
EFI_STATUS | |
Ip6PrependHead ( | |
IN IP6_SERVICE *IpSb, | |
IN NET_BUF *Packet, | |
IN EFI_IP6_HEADER *Head, | |
IN UINT16 FragmentOffset, | |
IN UINT8 *ExtHdrs, | |
IN UINT32 ExtHdrsLen, | |
IN UINT8 LastHeader, | |
IN UINT32 HeadLen | |
) | |
{ | |
UINT32 Len; | |
UINT32 UnFragExtHdrsLen; | |
EFI_IP6_HEADER *PacketHead; | |
UINT8 *UpdatedExtHdrs; | |
EFI_STATUS Status; | |
UINT8 NextHeader; | |
UpdatedExtHdrs = NULL; | |
// | |
// HeadLen is the length of the fixed part of the sequences of fragments, i.e. | |
// the unfragment part. | |
// | |
PacketHead = (EFI_IP6_HEADER *) NetbufAllocSpace (Packet, HeadLen, NET_BUF_HEAD); | |
if (PacketHead == NULL) { | |
return EFI_BAD_BUFFER_SIZE; | |
} | |
// | |
// Set the head up, convert the host byte order to network byte order | |
// | |
CopyMem (PacketHead, Head, sizeof (EFI_IP6_HEADER)); | |
PacketHead->PayloadLength = HTONS ((UINT16) (Packet->TotalSize - sizeof (EFI_IP6_HEADER))); | |
Packet->Ip.Ip6 = PacketHead; | |
Len = HeadLen - sizeof (EFI_IP6_HEADER); | |
UnFragExtHdrsLen = Len - sizeof (IP6_FRAGMENT_HEADER); | |
if (UnFragExtHdrsLen == 0) { | |
PacketHead->NextHeader = IP6_FRAGMENT; | |
} | |
// | |
// Append the extension headers: firstly copy the unfragmentable headers, then append | |
// fragmentation header. | |
// | |
if ((FragmentOffset & IP6_FRAGMENT_OFFSET_MASK) == 0) { | |
NextHeader = Head->NextHeader; | |
} else { | |
NextHeader = PacketHead->NextHeader; | |
} | |
Status = Ip6FillFragmentHeader ( | |
IpSb, | |
NextHeader, | |
LastHeader, | |
ExtHdrs, | |
ExtHdrsLen, | |
FragmentOffset, | |
&UpdatedExtHdrs | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
CopyMem ( | |
(UINT8 *) (PacketHead + 1), | |
UpdatedExtHdrs, | |
UnFragExtHdrsLen + sizeof (IP6_FRAGMENT_HEADER) | |
); | |
FreePool (UpdatedExtHdrs); | |
return EFI_SUCCESS; | |
} | |
/** | |
Transmit an IP6 packet. The packet comes either from the IP6 | |
child's consumer (IpInstance != NULL) or the IP6 driver itself | |
(IpInstance == NULL). It will route the packet, fragment it, | |
then transmit all the fragments through an interface. | |
@param[in] IpSb The IP6 service instance to transmit the packet. | |
@param[in] Interface The IP6 interface to transmit the packet. Ignored | |
if NULL. | |
@param[in] IpInstance The IP6 child that issues the transmission. It is | |
NULL if the packet is from the system. | |
@param[in] Packet The user data to send, excluding the IP header. | |
@param[in] Head The caller supplied header. The caller should set | |
the following header fields: NextHeader, HopLimit, | |
Src, Dest, FlowLabel, PayloadLength. This function | |
will fill in the Ver, TrafficClass. | |
@param[in] ExtHdrs The extension headers to append to the IPv6 basic | |
header. | |
@param[in] ExtHdrsLen The length of the extension headers. | |
@param[in] Callback The callback function to issue when transmission | |
completed. | |
@param[in] Context The opaque context for the callback. | |
@retval EFI_INVALID_PARAMETER Any input parameter or the packet is invalid. | |
@retval EFI_NO_MAPPING There is no interface to the destination. | |
@retval EFI_NOT_FOUND There is no route to the destination. | |
@retval EFI_SUCCESS The packet successfully transmitted. | |
@retval EFI_OUT_OF_RESOURCES Failed to finish the operation due to lack of | |
resources. | |
@retval Others Failed to transmit the packet. | |
**/ | |
EFI_STATUS | |
Ip6Output ( | |
IN IP6_SERVICE *IpSb, | |
IN IP6_INTERFACE *Interface OPTIONAL, | |
IN IP6_PROTOCOL *IpInstance OPTIONAL, | |
IN NET_BUF *Packet, | |
IN EFI_IP6_HEADER *Head, | |
IN UINT8 *ExtHdrs, | |
IN UINT32 ExtHdrsLen, | |
IN IP6_FRAME_CALLBACK Callback, | |
IN VOID *Context | |
) | |
{ | |
IP6_INTERFACE *IpIf; | |
EFI_IPv6_ADDRESS NextHop; | |
IP6_NEIGHBOR_ENTRY *NeighborCache; | |
IP6_ROUTE_CACHE_ENTRY *RouteCache; | |
EFI_STATUS Status; | |
UINT32 Mtu; | |
UINT32 HeadLen; | |
UINT16 FragmentOffset; | |
UINT8 *LastHeader; | |
UINT32 UnFragmentLen; | |
UINT32 UnFragmentHdrsLen; | |
UINT32 FragmentHdrsLen; | |
UINT16 *Checksum; | |
UINT16 PacketChecksum; | |
UINT16 PseudoChecksum; | |
UINT32 Index; | |
UINT32 PacketLen; | |
UINT32 RealExtLen; | |
UINT32 Offset; | |
NET_BUF *TmpPacket; | |
NET_BUF *Fragment; | |
UINT32 Num; | |
UINT8 *Buf; | |
EFI_IP6_HEADER *PacketHead; | |
IP6_ICMP_HEAD *IcmpHead; | |
IP6_TXTOKEN_WRAP *Wrap; | |
IP6_ROUTE_ENTRY *RouteEntry; | |
UINT8 *UpdatedExtHdrs; | |
UINT8 NextHeader; | |
UINT8 LastHeaderBackup; | |
BOOLEAN FragmentHeadInserted; | |
UINT8 *ExtHdrsBackup; | |
UINT8 NextHeaderBackup; | |
EFI_IPv6_ADDRESS Source; | |
EFI_IPv6_ADDRESS Destination; | |
NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE); | |
// | |
// RFC2460: Each extension header is an integer multiple of 8 octets long, | |
// in order to retain 8-octet alignment for subsequent headers. | |
// | |
if ((ExtHdrsLen & 0x7) != 0) { | |
return EFI_INVALID_PARAMETER; | |
} | |
LastHeader = NULL; | |
Ip6IsExtsValid ( | |
NULL, | |
NULL, | |
&Head->NextHeader, | |
ExtHdrs, | |
ExtHdrsLen, | |
FALSE, | |
NULL, | |
&LastHeader, | |
NULL, | |
NULL, | |
NULL | |
); | |
// | |
// Select an interface/source for system packet, application | |
// should select them itself. | |
// | |
IpIf = Interface; | |
if (IpIf == NULL) { | |
// | |
// IpInstance->Interface is NULL when IpInstance is configured with both stationaddress | |
// and destinationaddress is unspecified. | |
// | |
if (IpInstance == NULL || IpInstance->Interface == NULL) { | |
IpIf = Ip6SelectInterface (IpSb, &Head->DestinationAddress, &Head->SourceAddress); | |
if (IpInstance != NULL) { | |
IpInstance->Interface = IpIf; | |
} | |
} else { | |
IpIf = IpInstance->Interface; | |
} | |
} | |
if (IpIf == NULL) { | |
return EFI_NO_MAPPING; | |
} | |
// | |
// Update the common field in Head here. | |
// | |
Head->Version = 6; | |
Head->TrafficClassL = 0; | |
Head->TrafficClassH = 0; | |
Checksum = NULL; | |
NextHeader = *LastHeader; | |
switch (NextHeader) { | |
case EFI_IP_PROTO_UDP: | |
Packet->Udp = (EFI_UDP_HEADER *) NetbufGetByte (Packet, 0, NULL); | |
ASSERT (Packet->Udp != NULL); | |
if (Packet->Udp->Checksum == 0) { | |
Checksum = &Packet->Udp->Checksum; | |
} | |
break; | |
case EFI_IP_PROTO_TCP: | |
Packet->Tcp = (TCP_HEAD *) NetbufGetByte (Packet, 0, NULL); | |
ASSERT (Packet->Tcp != NULL); | |
if (Packet->Tcp->Checksum == 0) { | |
Checksum = &Packet->Tcp->Checksum; | |
} | |
break; | |
case IP6_ICMP: | |
// | |
// Don't send ICMP packet to an IPv6 anycast address. | |
// | |
if (Ip6IsAnycast (IpSb, &Head->DestinationAddress)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
IcmpHead = (IP6_ICMP_HEAD *) NetbufGetByte (Packet, 0, NULL); | |
ASSERT (IcmpHead != NULL); | |
if (IcmpHead->Checksum == 0) { | |
Checksum = &IcmpHead->Checksum; | |
} | |
break; | |
default: | |
break; | |
} | |
if (Checksum != NULL) { | |
// | |
// Calculate the checksum for upper layer protocol if it is not calculated due to lack of | |
// IPv6 source address. | |
// | |
PacketChecksum = NetbufChecksum (Packet); | |
PseudoChecksum = NetIp6PseudoHeadChecksum ( | |
&Head->SourceAddress, | |
&Head->DestinationAddress, | |
NextHeader, | |
Packet->TotalSize | |
); | |
*Checksum = (UINT16) ~NetAddChecksum (PacketChecksum, PseudoChecksum); | |
} | |
Status = Ip6IpSecProcessPacket ( | |
IpSb, | |
&Head, | |
LastHeader, // no need get the lasthead value for output | |
&Packet, | |
&ExtHdrs, | |
&ExtHdrsLen, | |
EfiIPsecOutBound, | |
Context | |
); | |
if (EFI_ERROR(Status)) { | |
return Status; | |
} | |
LastHeader = NULL; | |
// | |
// Check incoming parameters. | |
// | |
if (!Ip6IsExtsValid ( | |
IpSb, | |
Packet, | |
&Head->NextHeader, | |
ExtHdrs, | |
ExtHdrsLen, | |
FALSE, | |
NULL, | |
&LastHeader, | |
&RealExtLen, | |
&UnFragmentHdrsLen, | |
NULL | |
)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if ((RealExtLen & 0x7) != 0) { | |
return EFI_INVALID_PARAMETER; | |
} | |
LastHeaderBackup = *LastHeader; | |
// | |
// Perform next hop determination: | |
// For multicast packets, the next-hop is always the destination address and | |
// is considered to be on-link. | |
// | |
if (IP6_IS_MULTICAST (&Head->DestinationAddress)) { | |
IP6_COPY_ADDRESS (&NextHop, &Head->DestinationAddress); | |
} else { | |
// | |
// For unicast packets, use a combination of the Destination Cache, the Prefix List | |
// and the Default Router List to determine the IP address of the appropriate next hop. | |
// | |
NeighborCache = Ip6FindNeighborEntry (IpSb, &Head->DestinationAddress); | |
if (NeighborCache != NULL) { | |
// | |
// Hit Neighbor Cache. | |
// | |
IP6_COPY_ADDRESS (&NextHop, &Head->DestinationAddress); | |
} else { | |
// | |
// Not in Neighbor Cache, check Router cache | |
// | |
RouteCache = Ip6Route (IpSb, &Head->DestinationAddress, &Head->SourceAddress); | |
if (RouteCache == NULL) { | |
return EFI_NOT_FOUND; | |
} | |
IP6_COPY_ADDRESS (&NextHop, &RouteCache->NextHop); | |
Ip6FreeRouteCacheEntry (RouteCache); | |
} | |
} | |
// | |
// Examines the Neighbor Cache for link-layer information about that neighbor. | |
// DO NOT create neighbor cache if neighbor is itself - when reporting ICMP error. | |
// | |
if (!IP6_IS_MULTICAST (&NextHop) && !EFI_IP6_EQUAL (&Head->DestinationAddress, &Head->SourceAddress)) { | |
NeighborCache = Ip6FindNeighborEntry (IpSb, &NextHop); | |
if (NeighborCache == NULL) { | |
NeighborCache = Ip6CreateNeighborEntry (IpSb, Ip6OnArpResolved, &NextHop, NULL); | |
if (NeighborCache == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
// | |
// Send out multicast neighbor solicitation for address resolution immediatly. | |
// | |
Ip6CreateSNMulticastAddr (&NeighborCache->Neighbor, &Destination); | |
Status = Ip6SelectSourceAddress (IpSb, &NeighborCache->Neighbor, &Source); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = Ip6SendNeighborSolicit ( | |
IpSb, | |
&Source, | |
&Destination, | |
&NeighborCache->Neighbor, | |
&IpSb->SnpMode.CurrentAddress | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
--NeighborCache->Transmit; | |
NeighborCache->Ticks = IP6_GET_TICKS (IpSb->RetransTimer) + 1; | |
} | |
NeighborCache->Interface = IpIf; | |
} | |
UpdatedExtHdrs = NULL; | |
ExtHdrsBackup = NULL; | |
NextHeaderBackup = 0; | |
FragmentHeadInserted = FALSE; | |
// | |
// Check whether we received Packet Too Big message for the packet sent to the | |
// Destination. If yes include a Fragment Header in the subsequent packets. | |
// | |
RouteEntry = Ip6FindRouteEntry ( | |
IpSb->RouteTable, | |
&Head->DestinationAddress, | |
NULL | |
); | |
if (RouteEntry != NULL) { | |
if ((RouteEntry->Flag & IP6_PACKET_TOO_BIG) == IP6_PACKET_TOO_BIG) { | |
// | |
// FragmentHead is inserted after Hop-by-Hop Options header, Destination | |
// Options header (first occur), Routing header, and before Fragment header, | |
// Authentication header, Encapsulating Security Payload header, and | |
// Destination Options header (last occur), and upper-layer header. | |
// | |
Status = Ip6FillFragmentHeader ( | |
IpSb, | |
Head->NextHeader, | |
LastHeaderBackup, | |
ExtHdrs, | |
ExtHdrsLen, | |
0, | |
&UpdatedExtHdrs | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if ((ExtHdrs == NULL) && (ExtHdrsLen == 0)) { | |
NextHeaderBackup = Head->NextHeader; | |
Head->NextHeader = IP6_FRAGMENT; | |
} | |
ExtHdrsBackup = ExtHdrs; | |
ExtHdrs = UpdatedExtHdrs; | |
ExtHdrsLen = ExtHdrsLen + sizeof (IP6_FRAGMENT_HEADER); | |
RealExtLen = RealExtLen + sizeof (IP6_FRAGMENT_HEADER); | |
mIp6Id++; | |
FragmentHeadInserted = TRUE; | |
} | |
Ip6FreeRouteEntry (RouteEntry); | |
} | |
// | |
// OK, selected the source and route, fragment the packet then send | |
// them. Tag each fragment other than the first one as spawn from it. | |
// Each extension header is an integar multiple of 8 octets long, in | |
// order to retain 8-octet alignment for subsequent headers. | |
// | |
Mtu = IpSb->MaxPacketSize + sizeof (EFI_IP6_HEADER); | |
HeadLen = sizeof (EFI_IP6_HEADER) + RealExtLen; | |
if (Packet->TotalSize + HeadLen > Mtu) { | |
// | |
// Remove the inserted Fragment Header since we need fragment the packet. | |
// | |
if (FragmentHeadInserted) { | |
ExtHdrs = ExtHdrsBackup; | |
ExtHdrsLen = ExtHdrsLen - sizeof (IP6_FRAGMENT_HEADER); | |
if ((ExtHdrs == NULL) && (ExtHdrsLen == 0)) { | |
Head->NextHeader = NextHeaderBackup; | |
} | |
} | |
FragmentHdrsLen = ExtHdrsLen - UnFragmentHdrsLen; | |
// | |
// The packet is beyond the maximum which can be described through the | |
// fragment offset field in Fragment header. | |
// | |
if ((((Packet->TotalSize + FragmentHdrsLen) >> 3) & (~0x1fff)) != 0) { | |
Status = EFI_BAD_BUFFER_SIZE; | |
goto Error; | |
} | |
if (FragmentHdrsLen != 0) { | |
// | |
// Append the fragmentable extension hdrs before the upper layer payload | |
// to form a new NET_BUF. This NET_BUF contains all the buffer which will | |
// be fragmented below. | |
// | |
TmpPacket = NetbufGetFragment (Packet, 0, Packet->TotalSize, FragmentHdrsLen); | |
ASSERT (TmpPacket != NULL); | |
// | |
// Allocate the space to contain the fragmentable hdrs and copy the data. | |
// | |
Buf = NetbufAllocSpace (TmpPacket, FragmentHdrsLen, TRUE); | |
ASSERT (Buf != NULL); | |
CopyMem (Buf, ExtHdrs + UnFragmentHdrsLen, FragmentHdrsLen); | |
// | |
// Free the old Packet. | |
// | |
NetbufFree (Packet); | |
Packet = TmpPacket; | |
} | |
// | |
// The unfragment part which appears in every fragmented IPv6 packet includes | |
// the IPv6 header, the unfragmentable extension hdrs and the fragment header. | |
// | |
UnFragmentLen = sizeof (EFI_IP6_HEADER) + UnFragmentHdrsLen + sizeof (IP6_FRAGMENT_HEADER); | |
// | |
// Mtu now is the length of the fragment part in a full-length fragment. | |
// | |
Mtu = (Mtu - UnFragmentLen) & (~0x07); | |
Num = (Packet->TotalSize + Mtu - 1) / Mtu; | |
for (Index = 0, Offset = 0, PacketLen = Mtu; Index < Num; Index++) { | |
// | |
// Get fragment from the Packet, append UnFragnmentLen spare buffer | |
// before the fragmented data, the corresponding data is filled in later. | |
// | |
Fragment = NetbufGetFragment (Packet, Offset, PacketLen, UnFragmentLen); | |
if (Fragment == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto Error; | |
} | |
FragmentOffset = (UINT16) ((UINT16) Offset | 0x1); | |
if (Index == Num - 1){ | |
// | |
// The last fragment, clear the M flag. | |
// | |
FragmentOffset &= (~0x1); | |
} | |
Status = Ip6PrependHead ( | |
IpSb, | |
Fragment, | |
Head, | |
FragmentOffset, | |
ExtHdrs, | |
ExtHdrsLen, | |
LastHeaderBackup, | |
UnFragmentLen | |
); | |
ASSERT (Status == EFI_SUCCESS); | |
Status = Ip6SendFrame ( | |
IpIf, | |
IpInstance, | |
Fragment, | |
&NextHop, | |
Ip6SysPacketSent, | |
Packet | |
); | |
if (EFI_ERROR (Status)) { | |
goto Error; | |
} | |
// | |
// The last fragment of upper layer packet, update the IP6 token status. | |
// | |
if ((Index == Num -1) && (Context != NULL)) { | |
Wrap = (IP6_TXTOKEN_WRAP *) Context; | |
Wrap->Token->Status = Status; | |
} | |
Offset += PacketLen; | |
PacketLen = Packet->TotalSize - Offset; | |
if (PacketLen > Mtu) { | |
PacketLen = Mtu; | |
} | |
} | |
NetbufFree (Packet); | |
mIp6Id++; | |
if (UpdatedExtHdrs != NULL) { | |
FreePool (UpdatedExtHdrs); | |
} | |
return EFI_SUCCESS; | |
} | |
// | |
// Need not fragment the packet, send it in one frame. | |
// | |
PacketHead = (EFI_IP6_HEADER *) NetbufAllocSpace (Packet, HeadLen, NET_BUF_HEAD); | |
if (PacketHead == NULL) { | |
Status = EFI_BAD_BUFFER_SIZE; | |
goto Error; | |
} | |
CopyMem (PacketHead, Head, sizeof (EFI_IP6_HEADER)); | |
Packet->Ip.Ip6 = PacketHead; | |
if (ExtHdrs != NULL) { | |
Buf = (UINT8 *) (PacketHead + 1); | |
CopyMem (Buf, ExtHdrs, ExtHdrsLen); | |
} | |
if (UpdatedExtHdrs != NULL) { | |
// | |
// A Fragment Header is inserted to the packet, update the payload length. | |
// | |
PacketHead->PayloadLength = (UINT16) (NTOHS (PacketHead->PayloadLength) + | |
sizeof (IP6_FRAGMENT_HEADER)); | |
PacketHead->PayloadLength = HTONS (PacketHead->PayloadLength); | |
FreePool (UpdatedExtHdrs); | |
} | |
return Ip6SendFrame ( | |
IpIf, | |
IpInstance, | |
Packet, | |
&NextHop, | |
Callback, | |
Context | |
); | |
Error: | |
if (UpdatedExtHdrs != NULL) { | |
FreePool (UpdatedExtHdrs); | |
} | |
Ip6CancelPacket (IpIf, Packet, Status); | |
return Status; | |
} | |
/** | |
The filter function to find a packet and all its fragments. | |
The packet's fragments have their Context set to the packet. | |
@param[in] Frame The frames hold by the low level interface. | |
@param[in] Context Context to the function, which is the packet. | |
@retval TRUE This is the packet to cancel or its fragments. | |
@retval FALSE This is an unrelated packet. | |
**/ | |
BOOLEAN | |
Ip6CancelPacketFragments ( | |
IN IP6_LINK_TX_TOKEN *Frame, | |
IN VOID *Context | |
) | |
{ | |
if ((Frame->Packet == (NET_BUF *) Context) || (Frame->Context == Context)) { | |
return TRUE; | |
} | |
return FALSE; | |
} | |
/** | |
Remove all the frames on the interface that pass the FrameToCancel, | |
either queued on ARP queues or that have already been delivered to | |
MNP and not yet recycled. | |
@param[in] Interface Interface to remove the frames from. | |
@param[in] IoStatus The transmit status returned to the frames' callback. | |
@param[in] FrameToCancel Function to select the frame to cancel; NULL to select all. | |
@param[in] Context Opaque parameters passed to FrameToCancel. Ignored if | |
FrameToCancel is NULL. | |
**/ | |
VOID | |
Ip6CancelFrames ( | |
IN IP6_INTERFACE *Interface, | |
IN EFI_STATUS IoStatus, | |
IN IP6_FRAME_TO_CANCEL FrameToCancel OPTIONAL, | |
IN VOID *Context OPTIONAL | |
) | |
{ | |
LIST_ENTRY *Entry; | |
LIST_ENTRY *Next; | |
IP6_LINK_TX_TOKEN *Token; | |
IP6_SERVICE *IpSb; | |
IP6_NEIGHBOR_ENTRY *ArpQue; | |
EFI_STATUS Status; | |
IpSb = Interface->Service; | |
NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE); | |
// | |
// Cancel all the pending frames on ARP requests | |
// | |
NET_LIST_FOR_EACH_SAFE (Entry, Next, &Interface->ArpQues) { | |
ArpQue = NET_LIST_USER_STRUCT (Entry, IP6_NEIGHBOR_ENTRY, ArpList); | |
Status = Ip6FreeNeighborEntry ( | |
IpSb, | |
ArpQue, | |
FALSE, | |
FALSE, | |
IoStatus, | |
FrameToCancel, | |
Context | |
); | |
ASSERT_EFI_ERROR (Status); | |
} | |
// | |
// Cancel all the frames that have been delivered to MNP | |
// but not yet recycled. | |
// | |
NET_LIST_FOR_EACH_SAFE (Entry, Next, &Interface->SentFrames) { | |
Token = NET_LIST_USER_STRUCT (Entry, IP6_LINK_TX_TOKEN, Link); | |
if ((FrameToCancel == NULL) || FrameToCancel (Token, Context)) { | |
IpSb->Mnp->Cancel (IpSb->Mnp, &Token->MnpToken); | |
} | |
} | |
} | |
/** | |
Cancel the Packet and all its fragments. | |
@param[in] IpIf The interface from which the Packet is sent. | |
@param[in] Packet The Packet to cancel. | |
@param[in] IoStatus The status returns to the sender. | |
**/ | |
VOID | |
Ip6CancelPacket ( | |
IN IP6_INTERFACE *IpIf, | |
IN NET_BUF *Packet, | |
IN EFI_STATUS IoStatus | |
) | |
{ | |
Ip6CancelFrames (IpIf, IoStatus, Ip6CancelPacketFragments, Packet); | |
} | |