/** @file | |
IP6 option support functions and routines. | |
Copyright (c) 2009 - 2010, 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" | |
/** | |
Validate the IP6 option format for both the packets we received | |
and that we will transmit. It will compute the ICMPv6 error message fields | |
if the option is malformated. | |
@param[in] IpSb The IP6 service data. | |
@param[in] Packet The to be validated packet. | |
@param[in] Option The first byte of the option. | |
@param[in] OptionLen The length of the whole option. | |
@param[in] Pointer Identifies the octet offset within | |
the invoking packet where the error was detected. | |
@retval TRUE The option is properly formatted. | |
@retval FALSE The option is malformated. | |
**/ | |
BOOLEAN | |
Ip6IsOptionValid ( | |
IN IP6_SERVICE *IpSb, | |
IN NET_BUF *Packet, | |
IN UINT8 *Option, | |
IN UINT8 OptionLen, | |
IN UINT32 Pointer | |
) | |
{ | |
UINT8 Offset; | |
UINT8 OptionType; | |
Offset = 0; | |
while (Offset < OptionLen) { | |
OptionType = *(Option + Offset); | |
switch (OptionType) { | |
case Ip6OptionPad1: | |
// | |
// It is a Pad1 option | |
// | |
Offset++; | |
break; | |
case Ip6OptionPadN: | |
// | |
// It is a PadN option | |
// | |
Offset = (UINT8) (Offset + *(Option + Offset + 1) + 2); | |
break; | |
case Ip6OptionRouterAlert: | |
// | |
// It is a Router Alert Option | |
// | |
Offset += 4; | |
break; | |
default: | |
// | |
// The highest-order two bits specify the action must be taken if | |
// the processing IPv6 node does not recognize the option type. | |
// | |
switch (OptionType & Ip6OptionMask) { | |
case Ip6OptionSkip: | |
Offset = (UINT8) (Offset + *(Option + Offset + 1)); | |
break; | |
case Ip6OptionDiscard: | |
return FALSE; | |
case Ip6OptionParameterProblem: | |
Pointer = Pointer + Offset + sizeof (EFI_IP6_HEADER); | |
Ip6SendIcmpError ( | |
IpSb, | |
Packet, | |
NULL, | |
&Packet->Ip.Ip6->SourceAddress, | |
ICMP_V6_PARAMETER_PROBLEM, | |
2, | |
&Pointer | |
); | |
return FALSE; | |
case Ip6OptionMask: | |
if (!IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress)) { | |
Pointer = Pointer + Offset + sizeof (EFI_IP6_HEADER); | |
Ip6SendIcmpError ( | |
IpSb, | |
Packet, | |
NULL, | |
&Packet->Ip.Ip6->SourceAddress, | |
ICMP_V6_PARAMETER_PROBLEM, | |
2, | |
&Pointer | |
); | |
} | |
return FALSE; | |
break; | |
} | |
break; | |
} | |
} | |
return TRUE; | |
} | |
/** | |
Validate the IP6 option format for both the packets we received | |
and that we will transmit. It supports the defined options in Neighbor | |
Discovery messages. | |
@param[in] Option The first byte of the option. | |
@param[in] OptionLen The length of the whole option. | |
@retval TRUE The option is properly formatted. | |
@retval FALSE The option is malformated. | |
**/ | |
BOOLEAN | |
Ip6IsNDOptionValid ( | |
IN UINT8 *Option, | |
IN UINT16 OptionLen | |
) | |
{ | |
UINT16 Offset; | |
UINT8 OptionType; | |
UINT16 Length; | |
Offset = 0; | |
while (Offset < OptionLen) { | |
OptionType = *(Option + Offset); | |
Length = (UINT16) (*(Option + Offset + 1) * 8); | |
switch (OptionType) { | |
case Ip6OptionPrefixInfo: | |
if (Length != 32) { | |
return FALSE; | |
} | |
break; | |
case Ip6OptionMtu: | |
if (Length != 8) { | |
return FALSE; | |
} | |
break; | |
default: | |
// | |
// Check the length of Ip6OptionEtherSource, Ip6OptionEtherTarget, and | |
// Ip6OptionRedirected here. For unrecognized options, silently ignore | |
// and continue processsing the message. | |
// | |
if (Length == 0) { | |
return FALSE; | |
} | |
break; | |
} | |
Offset = (UINT16) (Offset + Length); | |
} | |
return TRUE; | |
} | |
/** | |
Validate whether the NextHeader is a known valid protocol or one of the user configured | |
protocols from the upper layer. | |
@param[in] IpSb The IP6 service instance. | |
@param[in] NextHeader The next header field. | |
@retval TRUE The NextHeader is a known valid protocol or user configured. | |
@retval FALSE The NextHeader is not a known valid protocol. | |
**/ | |
BOOLEAN | |
Ip6IsValidProtocol ( | |
IN IP6_SERVICE *IpSb, | |
IN UINT8 NextHeader | |
) | |
{ | |
LIST_ENTRY *Entry; | |
IP6_PROTOCOL *IpInstance; | |
if (NextHeader == EFI_IP_PROTO_TCP || | |
NextHeader == EFI_IP_PROTO_UDP || | |
NextHeader == IP6_ICMP || | |
NextHeader == IP6_ESP | |
) { | |
return TRUE; | |
} | |
if (IpSb == NULL) { | |
return FALSE; | |
} | |
if (IpSb->Signature != IP6_SERVICE_SIGNATURE) { | |
return FALSE; | |
} | |
NET_LIST_FOR_EACH (Entry, &IpSb->Children) { | |
IpInstance = NET_LIST_USER_STRUCT_S (Entry, IP6_PROTOCOL, Link, IP6_PROTOCOL_SIGNATURE); | |
if (IpInstance->State == IP6_STATE_CONFIGED) { | |
if (IpInstance->ConfigData.DefaultProtocol == NextHeader) { | |
return TRUE; | |
} | |
} | |
} | |
return FALSE; | |
} | |
/** | |
Validate the IP6 extension header format for both the packets we received | |
and that we will transmit. It will compute the ICMPv6 error message fields | |
if the option is mal-formated. | |
@param[in] IpSb The IP6 service instance. This is an optional parameter. | |
@param[in] Packet The data of the packet. Ignored if NULL. | |
@param[in] NextHeader The next header field in IPv6 basic header. | |
@param[in] ExtHdrs The first byte of the option. | |
@param[in] ExtHdrsLen The length of the whole option. | |
@param[in] Rcvd The option is from the packet we received if TRUE, | |
otherwise, the option we want to transmit. | |
@param[out] FormerHeader The offset of NextHeader which points to Fragment | |
Header when we received, of the ExtHdrs. | |
Ignored if we transmit. | |
@param[out] LastHeader The pointer of NextHeader of the last extension | |
header processed by IP6. | |
@param[out] RealExtsLen The length of extension headers processed by IP6 layer. | |
This is an optional parameter that may be NULL. | |
@param[out] UnFragmentLen The length of unfragmented length of extension headers. | |
This is an optional parameter that may be NULL. | |
@param[out] Fragmented Indicate whether the packet is fragmented. | |
This is an optional parameter that may be NULL. | |
@retval TRUE The option is properly formated. | |
@retval FALSE The option is malformated. | |
**/ | |
BOOLEAN | |
Ip6IsExtsValid ( | |
IN IP6_SERVICE *IpSb OPTIONAL, | |
IN NET_BUF *Packet OPTIONAL, | |
IN UINT8 *NextHeader, | |
IN UINT8 *ExtHdrs, | |
IN UINT32 ExtHdrsLen, | |
IN BOOLEAN Rcvd, | |
OUT UINT32 *FormerHeader OPTIONAL, | |
OUT UINT8 **LastHeader, | |
OUT UINT32 *RealExtsLen OPTIONAL, | |
OUT UINT32 *UnFragmentLen OPTIONAL, | |
OUT BOOLEAN *Fragmented OPTIONAL | |
) | |
{ | |
UINT32 Pointer; | |
UINT32 Offset; | |
UINT8 *Option; | |
UINT8 OptionLen; | |
BOOLEAN Flag; | |
UINT8 CountD; | |
UINT8 CountA; | |
IP6_FRAGMENT_HEADER *FragmentHead; | |
UINT16 FragmentOffset; | |
IP6_ROUTING_HEADER *RoutingHead; | |
if (RealExtsLen != NULL) { | |
*RealExtsLen = 0; | |
} | |
if (UnFragmentLen != NULL) { | |
*UnFragmentLen = 0; | |
} | |
if (Fragmented != NULL) { | |
*Fragmented = FALSE; | |
} | |
*LastHeader = NextHeader; | |
if (ExtHdrs == NULL && ExtHdrsLen == 0) { | |
return TRUE; | |
} | |
if ((ExtHdrs == NULL && ExtHdrsLen != 0) || (ExtHdrs != NULL && ExtHdrsLen == 0)) { | |
return FALSE; | |
} | |
Pointer = 0; | |
Offset = 0; | |
Flag = FALSE; | |
CountD = 0; | |
CountA = 0; | |
while (Offset <= ExtHdrsLen) { | |
switch (*NextHeader) { | |
case IP6_HOP_BY_HOP: | |
if (Offset != 0) { | |
if (!Rcvd) { | |
return FALSE; | |
} | |
// | |
// Hop-by-Hop Options header is restricted to appear immediately after an IPv6 header only. | |
// If not, generate a ICMP parameter problem message with code value of 1. | |
// | |
if (Pointer == 0) { | |
Pointer = sizeof (EFI_IP6_HEADER); | |
} else { | |
Pointer = Offset + sizeof (EFI_IP6_HEADER); | |
} | |
if ((IpSb != NULL) && (Packet != NULL) && | |
!IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress)) { | |
Ip6SendIcmpError ( | |
IpSb, | |
Packet, | |
NULL, | |
&Packet->Ip.Ip6->SourceAddress, | |
ICMP_V6_PARAMETER_PROBLEM, | |
1, | |
&Pointer | |
); | |
} | |
return FALSE; | |
} | |
Flag = TRUE; | |
// | |
// Fall through | |
// | |
case IP6_DESTINATION: | |
if (*NextHeader == IP6_DESTINATION) { | |
CountD++; | |
} | |
if (CountD > 2) { | |
return FALSE; | |
} | |
NextHeader = ExtHdrs + Offset; | |
Pointer = Offset; | |
Offset++; | |
Option = ExtHdrs + Offset; | |
OptionLen = (UINT8) ((*Option + 1) * 8 - 2); | |
Option++; | |
Offset++; | |
if (IpSb != NULL && Packet != NULL && !Ip6IsOptionValid (IpSb, Packet, Option, OptionLen, Offset)) { | |
return FALSE; | |
} | |
Offset = Offset + OptionLen; | |
if (Flag) { | |
if (UnFragmentLen != NULL) { | |
*UnFragmentLen = Offset; | |
} | |
Flag = FALSE; | |
} | |
break; | |
case IP6_ROUTING: | |
NextHeader = ExtHdrs + Offset; | |
RoutingHead = (IP6_ROUTING_HEADER *) NextHeader; | |
// | |
// Type 0 routing header is defined in RFC2460 and deprecated in RFC5095. | |
// Thus all routing types are processed as unrecognized. | |
// | |
if (RoutingHead->SegmentsLeft == 0) { | |
// | |
// Ignore the routing header and proceed to process the next header. | |
// | |
Offset = Offset + (RoutingHead->HeaderLen + 1) * 8; | |
if (UnFragmentLen != NULL) { | |
*UnFragmentLen = Offset; | |
} | |
} else { | |
// | |
// Discard the packet and send an ICMP Parameter Problem, Code 0, message | |
// to the packet's source address, pointing to the unrecognized routing | |
// type. | |
// | |
Pointer = Offset + 2 + sizeof (EFI_IP6_HEADER); | |
if ((IpSb != NULL) && (Packet != NULL) && | |
!IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress)) { | |
Ip6SendIcmpError ( | |
IpSb, | |
Packet, | |
NULL, | |
&Packet->Ip.Ip6->SourceAddress, | |
ICMP_V6_PARAMETER_PROBLEM, | |
0, | |
&Pointer | |
); | |
} | |
return FALSE; | |
} | |
break; | |
case IP6_FRAGMENT: | |
// | |
// RFC2402, AH header should after fragment header. | |
// | |
if (CountA > 1) { | |
return FALSE; | |
} | |
// | |
// RFC2460, ICMP Parameter Problem message with code 0 should be sent | |
// if the length of a fragment is not a multiple of 8 octects and the M | |
// flag of that fragment is 1, pointing to the Payload length field of the | |
// fragment packet. | |
// | |
if (IpSb != NULL && Packet != NULL && (ExtHdrsLen % 8) != 0) { | |
// | |
// Check whether it is the last fragment. | |
// | |
FragmentHead = (IP6_FRAGMENT_HEADER *) (ExtHdrs + Offset); | |
if (FragmentHead == NULL) { | |
return FALSE; | |
} | |
FragmentOffset = NTOHS (FragmentHead->FragmentOffset); | |
if (((FragmentOffset & 0x1) == 0x1) && | |
!IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress)) { | |
Pointer = sizeof (UINT32); | |
Ip6SendIcmpError ( | |
IpSb, | |
Packet, | |
NULL, | |
&Packet->Ip.Ip6->SourceAddress, | |
ICMP_V6_PARAMETER_PROBLEM, | |
0, | |
&Pointer | |
); | |
return FALSE; | |
} | |
} | |
if (Fragmented != NULL) { | |
*Fragmented = TRUE; | |
} | |
if (Rcvd && FormerHeader != NULL) { | |
*FormerHeader = (UINT32) (NextHeader - ExtHdrs); | |
} | |
NextHeader = ExtHdrs + Offset; | |
Offset = Offset + 8; | |
break; | |
case IP6_AH: | |
if (++CountA > 1) { | |
return FALSE; | |
} | |
Option = ExtHdrs + Offset; | |
NextHeader = Option; | |
Option++; | |
// | |
// RFC2402, Payload length is specified in 32-bit words, minus "2". | |
// | |
OptionLen = (UINT8) ((*Option + 2) * 4); | |
Offset = Offset + OptionLen; | |
break; | |
case IP6_NO_NEXT_HEADER: | |
*LastHeader = NextHeader; | |
return FALSE; | |
break; | |
default: | |
if (Ip6IsValidProtocol (IpSb, *NextHeader)) { | |
*LastHeader = NextHeader; | |
if (RealExtsLen != NULL) { | |
*RealExtsLen = Offset; | |
} | |
return TRUE; | |
} | |
// | |
// The Next Header value is unrecognized by the node, discard the packet and | |
// send an ICMP parameter problem message with code value of 1. | |
// | |
if (Offset == 0) { | |
// | |
// The Next Header directly follows IPv6 basic header. | |
// | |
Pointer = 6; | |
} else { | |
if (Pointer == 0) { | |
Pointer = sizeof (EFI_IP6_HEADER); | |
} else { | |
Pointer = Offset + sizeof (EFI_IP6_HEADER); | |
} | |
} | |
if ((IpSb != NULL) && (Packet != NULL) && | |
!IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress)) { | |
Ip6SendIcmpError ( | |
IpSb, | |
Packet, | |
NULL, | |
&Packet->Ip.Ip6->SourceAddress, | |
ICMP_V6_PARAMETER_PROBLEM, | |
1, | |
&Pointer | |
); | |
} | |
return FALSE; | |
} | |
} | |
*LastHeader = NextHeader; | |
if (RealExtsLen != NULL) { | |
*RealExtsLen = Offset; | |
} | |
return TRUE; | |
} | |
/** | |
Generate an IPv6 router alert option in network order and output it through Buffer. | |
@param[out] Buffer Points to a buffer to record the generated option. | |
@param[in, out] BufferLen The length of Buffer, in bytes. | |
@param[in] NextHeader The 8-bit selector indicates the type of header | |
immediately following the Hop-by-Hop Options header. | |
@retval EFI_BUFFER_TOO_SMALL The Buffer is too small to contain the generated | |
option. BufferLen is updated for the required size. | |
@retval EFI_SUCCESS The option is generated and filled in to Buffer. | |
**/ | |
EFI_STATUS | |
Ip6FillHopByHop ( | |
OUT UINT8 *Buffer, | |
IN OUT UINTN *BufferLen, | |
IN UINT8 NextHeader | |
) | |
{ | |
UINT8 BufferArray[8]; | |
if (*BufferLen < 8) { | |
*BufferLen = 8; | |
return EFI_BUFFER_TOO_SMALL; | |
} | |
// | |
// Form the Hop-By-Hop option in network order. | |
// NextHeader (1 octet) + HdrExtLen (1 octet) + RouterAlertOption(4 octets) + PadN | |
// The Hdr Ext Len is the length in 8-octet units, and does not including the first 8 octets. | |
// | |
ZeroMem (BufferArray, sizeof (BufferArray)); | |
BufferArray[0] = NextHeader; | |
BufferArray[2] = 0x5; | |
BufferArray[3] = 0x2; | |
BufferArray[6] = 1; | |
CopyMem (Buffer, BufferArray, sizeof (BufferArray)); | |
return EFI_SUCCESS; | |
} | |
/** | |
Insert a Fragment Header to the Extension headers and output it in UpdatedExtHdrs. | |
@param[in] IpSb The IP6 service instance to transmit the packet. | |
@param[in] NextHeader The extension header type of first extension header. | |
@param[in] LastHeader The extension header type of last extension header. | |
@param[in] ExtHdrs The length of the original extension header. | |
@param[in] ExtHdrsLen The length of the extension headers. | |
@param[in] FragmentOffset The fragment offset of the data following the header. | |
@param[out] UpdatedExtHdrs The updated ExtHdrs with Fragment header inserted. | |
It's caller's responsiblity to free this buffer. | |
@retval EFI_OUT_OF_RESOURCES Failed to finish the operation due to lake of | |
resource. | |
@retval EFI_UNSUPPORTED The extension header specified in ExtHdrs is not | |
supported currently. | |
@retval EFI_SUCCESS The operation performed successfully. | |
**/ | |
EFI_STATUS | |
Ip6FillFragmentHeader ( | |
IN IP6_SERVICE *IpSb, | |
IN UINT8 NextHeader, | |
IN UINT8 LastHeader, | |
IN UINT8 *ExtHdrs, | |
IN UINT32 ExtHdrsLen, | |
IN UINT16 FragmentOffset, | |
OUT UINT8 **UpdatedExtHdrs | |
) | |
{ | |
UINT32 Length; | |
UINT8 *Buffer; | |
UINT32 FormerHeader; | |
UINT32 Offset; | |
UINT32 Part1Len; | |
UINT32 HeaderLen; | |
UINT8 Current; | |
IP6_FRAGMENT_HEADER FragmentHead; | |
if (UpdatedExtHdrs == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Length = ExtHdrsLen + sizeof (IP6_FRAGMENT_HEADER); | |
Buffer = AllocatePool (Length); | |
if (Buffer == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Offset = 0; | |
Part1Len = 0; | |
FormerHeader = 0; | |
Current = NextHeader; | |
while ((ExtHdrs != NULL) && (Offset <= ExtHdrsLen)) { | |
switch (NextHeader) { | |
case IP6_ROUTING: | |
case IP6_HOP_BY_HOP: | |
case IP6_DESTINATION: | |
Current = NextHeader; | |
NextHeader = *(ExtHdrs + Offset); | |
if ((Current == IP6_DESTINATION) && (NextHeader != IP6_ROUTING)) { | |
// | |
// Destination Options header should occur at most twice, once before | |
// a Routing header and once before the upper-layer header. Here we | |
// find the one before the upper-layer header. Insert the Fragment | |
// Header before it. | |
// | |
CopyMem (Buffer, ExtHdrs, Part1Len); | |
*(Buffer + FormerHeader) = IP6_FRAGMENT; | |
// | |
// Exit the loop. | |
// | |
Offset = ExtHdrsLen + 1; | |
break; | |
} | |
FormerHeader = Offset; | |
HeaderLen = (*(ExtHdrs + Offset + 1) + 1) * 8; | |
Part1Len = Part1Len + HeaderLen; | |
Offset = Offset + HeaderLen; | |
break; | |
case IP6_FRAGMENT: | |
Current = NextHeader; | |
if (Part1Len != 0) { | |
CopyMem (Buffer, ExtHdrs, Part1Len); | |
} | |
*(Buffer + FormerHeader) = IP6_FRAGMENT; | |
// | |
// Exit the loop. | |
// | |
Offset = ExtHdrsLen + 1; | |
break; | |
case IP6_AH: | |
Current = NextHeader; | |
NextHeader = *(ExtHdrs + Offset); | |
// | |
// RFC2402, Payload length is specified in 32-bit words, minus "2". | |
// | |
HeaderLen = (*(ExtHdrs + Offset + 1) + 2) * 4; | |
Part1Len = Part1Len + HeaderLen; | |
Offset = Offset + HeaderLen; | |
break; | |
default: | |
if (Ip6IsValidProtocol (IpSb, NextHeader)) { | |
Current = NextHeader; | |
CopyMem (Buffer, ExtHdrs, Part1Len); | |
*(Buffer + FormerHeader) = IP6_FRAGMENT; | |
// | |
// Exit the loop. | |
// | |
Offset = ExtHdrsLen + 1; | |
break; | |
} | |
FreePool (Buffer); | |
return EFI_UNSUPPORTED; | |
} | |
} | |
// | |
// Append the Fragment header. If the fragment offset indicates the fragment | |
// is the first fragment. | |
// | |
if ((FragmentOffset & IP6_FRAGMENT_OFFSET_MASK) == 0) { | |
FragmentHead.NextHeader = Current; | |
} else { | |
FragmentHead.NextHeader = LastHeader; | |
} | |
FragmentHead.Reserved = 0; | |
FragmentHead.FragmentOffset = HTONS (FragmentOffset); | |
FragmentHead.Identification = mIp6Id; | |
CopyMem (Buffer + Part1Len, &FragmentHead, sizeof (IP6_FRAGMENT_HEADER)); | |
if ((ExtHdrs != NULL) && (Part1Len < ExtHdrsLen)) { | |
// | |
// Append the part2 (fragmentable part) of Extension headers | |
// | |
CopyMem ( | |
Buffer + Part1Len + sizeof (IP6_FRAGMENT_HEADER), | |
ExtHdrs + Part1Len, | |
ExtHdrsLen - Part1Len | |
); | |
} | |
*UpdatedExtHdrs = Buffer; | |
return EFI_SUCCESS; | |
} | |