audk/MdeModulePkg/Universal/Network/Dhcp4Dxe/Dhcp4Option.c

899 lines
26 KiB
C

/** @file
Copyright (c) 2006 - 2008, 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.
Module Name:
Dhcp4Option.c
Abstract:
Function to validate, parse, process the DHCP options
**/
#include "Dhcp4Impl.h"
//
// A list of the format of DHCP Options sorted by option tag
// to validate a dhcp message. Refere the comments of the
// DHCP_OPTION_FORMAT structure.
//
STATIC DHCP_OPTION_FORMAT DhcpOptionFormats[] = {
{DHCP_TAG_NETMASK, DHCP_OPTION_IP, 1, 1 , TRUE},
{DHCP_TAG_TIME_OFFSET, DHCP_OPTION_INT32, 1, 1 , FALSE},
{DHCP_TAG_ROUTER, DHCP_OPTION_IP, 1, -1 , TRUE},
{DHCP_TAG_TIME_SERVER, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_NAME_SERVER, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_DNS_SERVER, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_LOG_SERVER, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_COOKIE_SERVER, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_LPR_SERVER, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_IMPRESS_SERVER, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_RL_SERVER, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_HOSTNAME, DHCP_OPTION_INT8, 1, -1 , FALSE},
{DHCP_TAG_BOOTFILE_LEN, DHCP_OPTION_INT16, 1, 1 , FALSE},
{DHCP_TAG_DUMP, DHCP_OPTION_INT8, 1, -1 , FALSE},
{DHCP_TAG_DOMAINNAME, DHCP_OPTION_INT8, 1, -1 , FALSE},
{DHCP_TAG_SWAP_SERVER, DHCP_OPTION_IP, 1, 1 , FALSE},
{DHCP_TAG_ROOTPATH, DHCP_OPTION_INT8, 1, -1 , FALSE},
{DHCP_TAG_EXTEND_PATH, DHCP_OPTION_INT8, 1, -1 , FALSE},
{DHCP_TAG_IPFORWARD, DHCP_OPTION_SWITCH, 1, 1 , FALSE},
{DHCP_TAG_NONLOCAL_SRR, DHCP_OPTION_SWITCH, 1, 1 , FALSE},
{DHCP_TAG_POLICY_SRR, DHCP_OPTION_IPPAIR, 1, -1 , FALSE},
{DHCP_TAG_EMTU, DHCP_OPTION_INT16, 1, 1 , FALSE},
{DHCP_TAG_TTL, DHCP_OPTION_INT8, 1, 1 , FALSE},
{DHCP_TAG_PATHMTU_AGE, DHCP_OPTION_INT32, 1, 1 , FALSE},
{DHCP_TAG_PATHMTU_PLATEAU,DHCP_OPTION_INT16, 1, -1 , FALSE},
{DHCP_TAG_IFMTU, DHCP_OPTION_INT16, 1, 1 , FALSE},
{DHCP_TAG_SUBNET_LOCAL, DHCP_OPTION_SWITCH, 1, 1 , FALSE},
{DHCP_TAG_BROADCAST, DHCP_OPTION_IP, 1, 1 , FALSE},
{DHCP_TAG_DISCOVER_MASK, DHCP_OPTION_SWITCH, 1, 1 , FALSE},
{DHCP_TAG_SUPPLY_MASK, DHCP_OPTION_SWITCH, 1, 1 , FALSE},
{DHCP_TAG_DISCOVER_ROUTE, DHCP_OPTION_SWITCH, 1, 1 , FALSE},
{DHCP_TAG_ROUTER_SOLICIT, DHCP_OPTION_IP, 1, 1 , FALSE},
{DHCP_TAG_STATIC_ROUTE, DHCP_OPTION_IPPAIR, 1, -1 , FALSE},
{DHCP_TAG_TRAILER, DHCP_OPTION_SWITCH, 1, 1 , FALSE},
{DHCP_TAG_ARPAGE, DHCP_OPTION_INT32, 1, 1 , FALSE},
{DHCP_TAG_ETHER_ENCAP, DHCP_OPTION_SWITCH, 1, 1 , FALSE},
{DHCP_TAG_TCP_TTL, DHCP_OPTION_INT8, 1, 1 , FALSE},
{DHCP_TAG_KEEP_INTERVAL, DHCP_OPTION_INT32, 1, 1 , FALSE},
{DHCP_TAG_KEEP_GARBAGE, DHCP_OPTION_SWITCH, 1, 1 , FALSE},
{DHCP_TAG_NIS_DOMAIN, DHCP_OPTION_INT8, 1, -1 , FALSE},
{DHCP_TAG_NIS_SERVER, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_NTP_SERVER, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_VENDOR, DHCP_OPTION_INT8, 1, -1 , FALSE},
{DHCP_TAG_NBNS, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_NBDD, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_NBTYPE, DHCP_OPTION_INT8, 1, 1 , FALSE},
{DHCP_TAG_NBSCOPE, DHCP_OPTION_INT8, 1, -1 , FALSE},
{DHCP_TAG_XFONT, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_XDM, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_REQUEST_IP, DHCP_OPTION_IP, 1, 1 , FALSE},
{DHCP_TAG_LEASE, DHCP_OPTION_INT32, 1, 1 , TRUE},
{DHCP_TAG_OVERLOAD, DHCP_OPTION_INT8, 1, 1 , TRUE},
{DHCP_TAG_TYPE, DHCP_OPTION_INT8, 1, 1 , TRUE},
{DHCP_TAG_SERVER_ID, DHCP_OPTION_IP, 1, 1 , TRUE},
{DHCP_TAG_PARA_LIST, DHCP_OPTION_INT8, 1, -1 , FALSE},
{DHCP_TAG_MESSAGE, DHCP_OPTION_INT8, 1, -1 , FALSE},
{DHCP_TAG_MAXMSG, DHCP_OPTION_INT16, 1, 1 , FALSE},
{DHCP_TAG_T1, DHCP_OPTION_INT32, 1, 1 , TRUE},
{DHCP_TAG_T2, DHCP_OPTION_INT32, 1, 1 , TRUE},
{DHCP_TAG_VENDOR_CLASS, DHCP_OPTION_INT8, 1, -1 , FALSE},
{DHCP_TAG_CLIENT_ID, DHCP_OPTION_INT8, 2, -1 , FALSE},
{DHCP_TAG_NISPLUS, DHCP_OPTION_INT8, 1, -1 , FALSE},
{DHCP_TAG_NISPLUS_SERVER, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_TFTP, DHCP_OPTION_INT8, 1, -1 , FALSE},
{DHCP_TAG_BOOTFILE, DHCP_OPTION_INT8, 1, -1 , FALSE},
{DHCP_TAG_MOBILEIP, DHCP_OPTION_IP, 0, -1 , FALSE},
{DHCP_TAG_SMTP, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_POP3, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_NNTP, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_WWW, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_FINGER, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_IRC, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_STTALK, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_STDA, DHCP_OPTION_IP, 1, -1 , FALSE},
{DHCP_TAG_CLASSLESS_ROUTE,DHCP_OPTION_INT8, 5, -1 , FALSE},
};
/**
Binary search the DhcpOptionFormats array to find the format
information about a specific option.
@param Tag The option's tag.
@return The point to the option's format, NULL if not found.
**/
DHCP_OPTION_FORMAT *
DhcpFindOptionFormat (
IN UINT8 Tag
)
{
INTN Left;
INTN Right;
INTN Middle;
Left = 0;
Right = sizeof (DhcpOptionFormats) / sizeof (DHCP_OPTION_FORMAT) - 1;
while (Right >= Left) {
Middle = (Left + Right) / 2;
if (Tag == DhcpOptionFormats[Middle].Tag) {
return &DhcpOptionFormats[Middle];
}
if (Tag < DhcpOptionFormats[Middle].Tag) {
Right = Middle - 1;
} else {
Left = Middle + 1;
}
}
return NULL;
}
/**
Validate whether a single DHCP option is valid according to its format.
@param Format The option's format
@param OptValue The value of the option
@param Len The length of the option value
@return TRUE is the option is valid, otherwise FALSE.
**/
BOOLEAN
DhcpOptionIsValid (
IN DHCP_OPTION_FORMAT *Format,
IN UINT8 *OptValue,
IN INTN Len
)
{
INTN Unit;
INTN Occur;
INTN Index;
Unit = 0;
switch (Format->Type) {
case DHCP_OPTION_SWITCH:
case DHCP_OPTION_INT8:
Unit = 1;
break;
case DHCP_OPTION_INT16:
Unit = 2;
break;
case DHCP_OPTION_INT32:
case DHCP_OPTION_IP:
Unit = 4;
break;
case DHCP_OPTION_IPPAIR:
Unit = 8;
break;
}
ASSERT (Unit != 0);
//
// Validate that the option appears in the full units.
//
if ((Len % Unit) != 0) {
return FALSE;
}
//
// Validate the occurance of the option unit is with in [MinOccur, MaxOccur]
//
Occur = Len / Unit;
if (((Format->MinOccur != -1) && (Occur < Format->MinOccur)) ||
((Format->MaxOccur != -1) && (Occur > Format->MaxOccur))) {
return FALSE;
}
//
// If the option is of type switch, only 0/1 are valid values.
//
if (Format->Type == DHCP_OPTION_SWITCH) {
for (Index = 0; Index < Occur; Index++) {
if ((OptValue[Index] != 0) && (OptValue[Index] != 1)) {
return FALSE;
}
}
}
return TRUE;
}
/**
Extract the client interested options, all the parameters are
converted to host byte order.
@param Tag The DHCP option tag
@param Len The length of the option
@param Data The value of the DHCP option
@param Para The variable to save the interested parameter
@retval EFI_SUCCESS The DHCP option is successfully extracted.
@retval EFI_INVALID_PARAMETER The DHCP option is mal-formated
**/
EFI_STATUS
DhcpGetParameter (
IN UINT8 Tag,
IN INTN Len,
IN UINT8 *Data,
IN DHCP_PARAMETER *Para
)
{
switch (Tag) {
case DHCP_TAG_NETMASK:
Para->NetMask = NetGetUint32 (Data);
break;
case DHCP_TAG_ROUTER:
//
// Return the first router to consumer which is the preferred one
//
Para->Router = NetGetUint32 (Data);
break;
case DHCP_TAG_LEASE:
Para->Lease = NetGetUint32 (Data);
break;
case DHCP_TAG_OVERLOAD:
Para->Overload = *Data;
if ((Para->Overload < 1) || (Para->Overload > 3)) {
return EFI_INVALID_PARAMETER;
}
break;
case DHCP_TAG_TYPE:
Para->DhcpType = *Data;
if ((Para->DhcpType < 1) || (Para->DhcpType > 9)) {
return EFI_INVALID_PARAMETER;
}
break;
case DHCP_TAG_SERVER_ID:
Para->ServerId = NetGetUint32 (Data);
break;
case DHCP_TAG_T1:
Para->T1 = NetGetUint32 (Data);
break;
case DHCP_TAG_T2:
Para->T2 = NetGetUint32 (Data);
break;
}
return EFI_SUCCESS;
}
/**
Inspect all the options in a single buffer. DHCP options may be contained
in several buffers, such as the BOOTP options filed, boot file or server
name. Each option buffer is required to end with DHCP_TAG_EOP.
@param Buffer The buffer which contains DHCP options
@param BufLen The length of the buffer
@param Check The callback function for each option found
@param Context The opaque parameter for the Check
@param Overload variable to save the value of DHCP_TAG_OVERLOAD
option.
@retval EFI_SUCCESS All the options are valid
@retval EFI_INVALID_PARAMETER The options are mal-formated.
**/
EFI_STATUS
DhcpIterateBufferOptions (
IN UINT8 *Buffer,
IN INTN BufLen,
IN DHCP_CHECK_OPTION Check, OPTIONAL
IN VOID *Context,
OUT UINT8 *Overload OPTIONAL
)
{
INTN Cur;
UINT8 Tag;
UINT8 Len;
Cur = 0;
while (Cur < BufLen) {
Tag = Buffer[Cur];
if (Tag == DHCP_TAG_PAD) {
Cur++;
continue;
} else if (Tag == DHCP_TAG_EOP) {
return EFI_SUCCESS;
}
Cur++;
if (Cur == BufLen) {
return EFI_INVALID_PARAMETER;
}
Len = Buffer[Cur++];
if (Cur + Len > BufLen) {
return EFI_INVALID_PARAMETER;
}
if ((Tag == DHCP_TAG_OVERLOAD) && (Overload != NULL)) {
if (Len != 1) {
return EFI_INVALID_PARAMETER;
}
*Overload = Buffer[Cur];
}
if ((Check != NULL) && EFI_ERROR (Check (Tag, Len, Buffer + Cur, Context))) {
return EFI_INVALID_PARAMETER;
}
Cur += Len;
}
//
// Each option buffer is expected to end with an EOP
//
return EFI_INVALID_PARAMETER;
}
/**
Iterate through a DHCP message to visit each option. First inspect
all the options in the OPTION field. Then if overloaded, inspect
the options in FILENAME and SERVERNAME fields. One option may be
encoded in several places. See RFC 3396 Encoding Long Options in DHCP
@param Packet The DHCP packet to check the options for
@param Check The callback function to be called for each option
found
@param Context The opaque parameter for Check
@retval EFI_SUCCESS The DHCP packet's options are well formated
@retval Others The DHCP packet's options are not well formated
**/
EFI_STATUS
DhcpIterateOptions (
IN EFI_DHCP4_PACKET *Packet,
IN DHCP_CHECK_OPTION Check, OPTIONAL
IN VOID *Context
)
{
EFI_STATUS Status;
UINT8 Overload;
Overload = 0;
Status = DhcpIterateBufferOptions (
Packet->Dhcp4.Option,
Packet->Length - sizeof (EFI_DHCP4_HEADER) - sizeof (UINT32),
Check,
Context,
&Overload
);
if (EFI_ERROR (Status)) {
return Status;
}
if ((Overload == DHCP_OVERLOAD_FILENAME) || (Overload == DHCP_OVERLOAD_BOTH)) {
Status = DhcpIterateBufferOptions (
(UINT8 *) Packet->Dhcp4.Header.BootFileName,
128,
Check,
Context,
NULL
);
if (EFI_ERROR (Status)) {
return Status;
}
}
if ((Overload == DHCP_OVERLOAD_SVRNAME) || (Overload == DHCP_OVERLOAD_BOTH)) {
Status = DhcpIterateBufferOptions (
(UINT8 *) Packet->Dhcp4.Header.ServerName,
64,
Check,
Context,
NULL
);
if (EFI_ERROR (Status)) {
return Status;
}
}
return EFI_SUCCESS;
}
/**
Call back function to DhcpiterateOptions to compute each option's
length. It just adds the data length of all the occurances of this
Tag. Context is an array of 256 DHCP_OPTION_COUNT.
@param Tag The current option to check
@param Len The length of the option data
@param Data The option data
@param Context The context, which is a array of 256
DHCP_OPTION_COUNT.
@retval EFI_SUCCESS It always returns EFI_SUCCESS.
**/
EFI_STATUS
DhcpGetOptionLen (
IN UINT8 Tag,
IN UINT8 Len,
IN UINT8 *Data,
IN VOID *Context
)
{
DHCP_OPTION_COUNT *OpCount;
OpCount = (DHCP_OPTION_COUNT *) Context;
OpCount[Tag].Offset = (UINT16) (OpCount[Tag].Offset + Len);
return EFI_SUCCESS;
}
/**
Call back function to DhcpiterateOptions to consolidate each option's
data. There are maybe several occurance of the same option.
@param Tag The option to consolidate its data
@param Len The length of option data
@param Data The data of the option's current occurance
@param Context The context, which is DHCP_OPTION_CONTEXT. This
array is just a wrap to pass THREE parameters.
@retval EFI_SUCCESS It always returns EFI_SUCCESS
**/
EFI_STATUS
DhcpFillOption (
IN UINT8 Tag,
IN UINT8 Len,
IN UINT8 *Data,
IN VOID *Context
)
{
DHCP_OPTION_CONTEXT *OptContext;
DHCP_OPTION_COUNT *OptCount;
DHCP_OPTION *Options;
UINT8 *Buf;
UINT8 Index;
OptContext = (DHCP_OPTION_CONTEXT *) Context;
OptCount = OptContext->OpCount;
Index = OptCount[Tag].Index;
Options = OptContext->Options;
Buf = OptContext->Buf;
if (Options[Index].Data == NULL) {
Options[Index].Tag = Tag;
Options[Index].Data = Buf + OptCount[Tag].Offset;
}
CopyMem (Buf + OptCount[Tag].Offset, Data, Len);
OptCount[Tag].Offset = (UINT16) (OptCount[Tag].Offset + Len);
Options[Index].Len = (UINT16) (Options[Index].Len + Len);
return EFI_SUCCESS;
}
/**
Parse the options of a DHCP packet. It supports RFC 3396: Encoding
Long Options in DHCP. That is, it will combine all the option value
of all the occurances of each option.
A little bit of implemenation:
It adopts the "Key indexed counting" algorithm. First, it allocates
an array of 256 DHCP_OPTION_COUNTs because DHCP option tag is encoded
as a UINT8. It then iterates the DHCP packet to get data length of
each option by calling DhcpIterOptions with DhcpGetOptionLen. Now, it
knows the number of present options and their length. It allocates a
array of DHCP_OPTION and a continous buffer after the array to put
all the options' data. Each option's data is pointed to by the Data
field in DHCP_OPTION structure. At last, it call DhcpIterateOptions
with DhcpFillOption to fill each option's data to its position in the
buffer.
@param Packet The DHCP packet to parse the options
@param Count The number of valid dhcp options present in the
packet
@param OptionPoint The array that contains the DHCP options. Caller
should free it.
@retval EFI_OUT_OF_RESOURCES Failed to allocate memory to parse the packet.
@retval EFI_INVALID_PARAMETER The options are mal-formated
@retval EFI_SUCCESS The options are parsed into OptionPoint
**/
EFI_STATUS
DhcpParseOption (
IN EFI_DHCP4_PACKET *Packet,
OUT INTN *Count,
OUT DHCP_OPTION **OptionPoint
)
{
DHCP_OPTION_CONTEXT Context;
DHCP_OPTION *Options;
DHCP_OPTION_COUNT *OptCount;
EFI_STATUS Status;
UINT16 TotalLen;
INTN OptNum;
INTN Index;
ASSERT ((Count != NULL) && (OptionPoint != NULL));
//
// First compute how many options and how long each option is
// with the "Key indexed counting" algorithms.
//
OptCount = AllocateZeroPool (DHCP_MAX_OPTIONS * sizeof (DHCP_OPTION_COUNT));
if (OptCount == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Status = DhcpIterateOptions (Packet, DhcpGetOptionLen, OptCount);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
//
// Before the loop, Offset is the length of the option. After loop,
// OptCount[Index].Offset specifies the offset into the continuous
// option value buffer to put the data.
//
TotalLen = 0;
OptNum = 0;
for (Index = 0; Index < DHCP_MAX_OPTIONS; Index++) {
if (OptCount[Index].Offset != 0) {
OptCount[Index].Index = (UINT8) OptNum;
TotalLen = (UINT16) (TotalLen + OptCount[Index].Offset);
OptCount[Index].Offset = (UINT16) (TotalLen - OptCount[Index].Offset);
OptNum++;
}
}
*Count = OptNum;
*OptionPoint = NULL;
if (OptNum == 0) {
goto ON_EXIT;
}
//
// Allocate a buffer to hold the DHCP options, and after that, a
// continuous buffer to put all the options' data.
//
Options = AllocateZeroPool (OptNum * sizeof (DHCP_OPTION) + TotalLen);
if (Options == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto ON_EXIT;
}
Context.OpCount = OptCount;
Context.Options = Options;
Context.Buf = (UINT8 *) (Options + OptNum);
Status = DhcpIterateOptions (Packet, DhcpFillOption, &Context);
if (EFI_ERROR (Status)) {
gBS->FreePool (Options);
goto ON_EXIT;
}
*OptionPoint = Options;
ON_EXIT:
gBS->FreePool (OptCount);
return Status;
}
/**
Validate the packet's options. If necessary, allocate
and fill in the interested parameters.
@param Packet The packet to validate the options
@param Para The variable to save the DHCP parameters.
@retval EFI_OUT_OF_RESOURCES Failed to allocate memory to validate the packet.
@retval EFI_INVALID_PARAMETER The options are mal-formated
@retval EFI_SUCCESS The options are parsed into OptionPoint
**/
EFI_STATUS
DhcpValidateOptions (
IN EFI_DHCP4_PACKET *Packet,
OUT DHCP_PARAMETER **Para OPTIONAL
)
{
DHCP_PARAMETER Parameter;
DHCP_OPTION_FORMAT *Format;
DHCP_OPTION *AllOption;
DHCP_OPTION *Option;
EFI_STATUS Status;
BOOLEAN Updated;
INTN Count;
INTN Index;
if (Para != NULL) {
*Para = NULL;
}
AllOption = NULL;
Status = DhcpParseOption (Packet, &Count, &AllOption);
if (EFI_ERROR (Status) || (Count == 0)) {
return Status;
}
Updated = FALSE;
ZeroMem (&Parameter, sizeof (Parameter));
for (Index = 0; Index < Count; Index++) {
Option = &AllOption[Index];
//
// Find the format of the option then validate it.
//
Format = DhcpFindOptionFormat (Option->Tag);
if (Format == NULL) {
continue;
}
if (!DhcpOptionIsValid (Format, Option->Data, Option->Len)) {
Status = EFI_INVALID_PARAMETER;
goto ON_EXIT;
}
//
// Get the client interested parameters
//
if (Format->Alert && (Para != NULL)) {
Updated = TRUE;
Status = DhcpGetParameter (Option->Tag, Option->Len, Option->Data, &Parameter);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
}
}
if (Updated && (Para != NULL)) {
if ((*Para = AllocatePool (sizeof (DHCP_PARAMETER))) == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto ON_EXIT;
}
CopyMem (*Para, &Parameter, sizeof (**Para));
}
ON_EXIT:
gBS->FreePool (AllOption);
return Status;
}
/**
Append an option to the memory, if the option is longer than
255 bytes, splits it into several options.
@param Buf The buffer to append the option to
@param Tag The option's tag
@param DataLen The length of the option's data
@param Data The option's data
@return The position to append the next option
**/
UINT8 *
DhcpAppendOption (
IN UINT8 *Buf,
IN UINT8 Tag,
IN UINT16 DataLen,
IN UINT8 *Data
)
{
INTN Index;
INTN Len;
ASSERT (DataLen != 0);
for (Index = 0; Index < (DataLen + 254) / 255; Index++) {
Len = MIN (255, DataLen - Index * 255);
*(Buf++) = Tag;
*(Buf++) = (UINT8) Len;
CopyMem (Buf, Data + Index * 255, Len);
Buf += Len;
}
return Buf;
}
/**
Build a new DHCP packet from a seed packet. Options may be deleted or
appended. The caller should free the NewPacket when finished using it.
@param SeedPacket The seed packet to start with
@param DeleteCount The number of options to delete
@param DeleteList The options to delete from the packet
@param AppendCount The number of options to append
@param AppendList The options to append to the packet
@param NewPacket The new packet, allocated and built by this
function.
@retval EFI_OUT_OF_RESOURCES Failed to allocate memory
@retval EFI_SUCCESS The packet is build.
**/
EFI_STATUS
DhcpBuild (
IN EFI_DHCP4_PACKET *SeedPacket,
IN UINT32 DeleteCount,
IN UINT8 *DeleteList OPTIONAL,
IN UINT32 AppendCount,
IN EFI_DHCP4_PACKET_OPTION *AppendList[] OPTIONAL,
OUT EFI_DHCP4_PACKET **NewPacket
)
{
DHCP_OPTION *Mark;
DHCP_OPTION *SeedOptions;
EFI_DHCP4_PACKET *Packet;
EFI_STATUS Status;
INTN Count;
UINT32 Index;
UINT32 Len;
UINT8 *Buf;
//
// Use an array of DHCP_OPTION to mark the existance
// and position of each valid options.
//
Mark = AllocatePool (sizeof (DHCP_OPTION) * DHCP_MAX_OPTIONS);
if (Mark == NULL) {
return EFI_OUT_OF_RESOURCES;
}
for (Index = 0; Index < DHCP_MAX_OPTIONS; Index++) {
Mark[Index].Tag = (UINT8) Index;
Mark[Index].Len = 0;
}
//
// Get list of the options from the seed packet, then put
// them to the mark array according to their tags.
//
SeedOptions = NULL;
Status = DhcpParseOption (SeedPacket, &Count, &SeedOptions);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
for (Index = 0; Index < (UINT32) Count; Index++) {
Mark[SeedOptions[Index].Tag] = SeedOptions[Index];
}
//
// Mark the option's length is zero if it is in the DeleteList.
//
for (Index = 0; Index < DeleteCount; Index++) {
Mark[DeleteList[Index]].Len = 0;
}
//
// Add or replace the option if it is in the append list.
//
for (Index = 0; Index < AppendCount; Index++) {
Mark[AppendList[Index]->OpCode].Len = AppendList[Index]->Length;
Mark[AppendList[Index]->OpCode].Data = AppendList[Index]->Data;
}
//
// compute the new packet length. No need to add 1 byte for
// EOP option since EFI_DHCP4_PACKET includes one extra byte
// for option. It is necessary to split the option if it is
// longer than 255 bytes.
//
Len = sizeof (EFI_DHCP4_PACKET);
for (Index = 0; Index < DHCP_MAX_OPTIONS; Index++) {
if (Mark[Index].Len != 0) {
Len += ((Mark[Index].Len + 254) / 255) * 2 + Mark[Index].Len;
}
}
Status = EFI_OUT_OF_RESOURCES;
Packet = (EFI_DHCP4_PACKET *) AllocatePool (Len);
if (Packet == NULL) {
goto ON_ERROR;
}
Packet->Size = Len;
Packet->Length = 0;
CopyMem (&Packet->Dhcp4.Header, &SeedPacket->Dhcp4.Header, sizeof (Packet->Dhcp4.Header));
Packet->Dhcp4.Magik = DHCP_OPTION_MAGIC;
Buf = Packet->Dhcp4.Option;
for (Index = 0; Index < DHCP_MAX_OPTIONS; Index++) {
if (Mark[Index].Len != 0) {
Buf = DhcpAppendOption (Buf, Mark[Index].Tag, Mark[Index].Len, Mark[Index].Data);
}
}
*(Buf++) = DHCP_TAG_EOP;
Packet->Length = sizeof (EFI_DHCP4_HEADER) + sizeof (UINT32)
+ (UINT32) (Buf - Packet->Dhcp4.Option);
*NewPacket = Packet;
Status = EFI_SUCCESS;
ON_ERROR:
if (SeedOptions != NULL) {
gBS->FreePool (SeedOptions);
}
gBS->FreePool (Mark);
return Status;
}