From a3bcde70e6dc69000f85cc5deee98101d2ae200a Mon Sep 17 00:00:00 2001 From: hhtian Date: Mon, 1 Nov 2010 06:13:54 +0000 Subject: Add NetworkPkg (P.UDK2010.UP3.Network.P1) git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@10986 6f19259b-4bc3-4df7-8a09-765794883524 --- NetworkPkg/Ip6Dxe/Ip6Output.c | 1077 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1077 insertions(+) create mode 100644 NetworkPkg/Ip6Dxe/Ip6Output.c (limited to 'NetworkPkg/Ip6Dxe/Ip6Output.c') diff --git a/NetworkPkg/Ip6Dxe/Ip6Output.c b/NetworkPkg/Ip6Dxe/Ip6Output.c new file mode 100644 index 0000000000..baa4904fc9 --- /dev/null +++ b/NetworkPkg/Ip6Dxe/Ip6Output.c @@ -0,0 +1,1077 @@ +/** @file + The internal functions and routines to transmit the IP6 packet. + + Copyright (c) 2009 - 2010, Intel Corporation. All rights reserved.
+ + 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; + + // + // 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. + // + 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); +} + -- cgit v1.2.3