mirror of https://github.com/ipxe/ipxe.git
Add code to modify DHCP option values within a block.
parent
3acbff4f00
commit
19e8b41562
|
@ -23,6 +23,8 @@
|
||||||
/** Extract encapsulated option tag from encapsulated tag value */
|
/** Extract encapsulated option tag from encapsulated tag value */
|
||||||
#define DHCP_ENCAPSULATED( encap_opt ) ( (encap_opt) & 0xff )
|
#define DHCP_ENCAPSULATED( encap_opt ) ( (encap_opt) & 0xff )
|
||||||
|
|
||||||
|
#define DHCP_IS_ENCAP_OPT( opt ) DHCP_ENCAPSULATOR( opt )
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @defgroup dhcpopts DHCP option tags
|
* @defgroup dhcpopts DHCP option tags
|
||||||
* @{
|
* @{
|
||||||
|
@ -74,6 +76,17 @@ struct dhcp_option {
|
||||||
} data;
|
} data;
|
||||||
} __attribute__ (( packed ));
|
} __attribute__ (( packed ));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of a DHCP option header
|
||||||
|
*
|
||||||
|
* The header is the portion excluding the data, i.e. the tag and the
|
||||||
|
* length.
|
||||||
|
*/
|
||||||
|
#define DHCP_OPTION_HEADER_LEN ( offsetof ( struct dhcp_option, data ) )
|
||||||
|
|
||||||
|
/** Maximum length for a single DHCP option */
|
||||||
|
#define DHCP_MAX_LEN 0xff
|
||||||
|
|
||||||
/** A DHCP options block */
|
/** A DHCP options block */
|
||||||
struct dhcp_option_block {
|
struct dhcp_option_block {
|
||||||
/** List of option blocks */
|
/** List of option blocks */
|
||||||
|
@ -82,17 +95,35 @@ struct dhcp_option_block {
|
||||||
void *data;
|
void *data;
|
||||||
/** Option block length */
|
/** Option block length */
|
||||||
size_t len;
|
size_t len;
|
||||||
|
/** Option block maximum length */
|
||||||
|
size_t max_len;
|
||||||
|
/** Block priority
|
||||||
|
*
|
||||||
|
* This is determined at the time of the call to
|
||||||
|
* register_options() by searching for the @c DHCP_EB_PRIORITY
|
||||||
|
* option.
|
||||||
|
*/
|
||||||
|
signed int priority;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern unsigned long dhcp_num_option ( struct dhcp_option *option );
|
extern unsigned long dhcp_num_option ( struct dhcp_option *option );
|
||||||
extern struct dhcp_option * find_dhcp_option ( unsigned int tag,
|
extern struct dhcp_option *
|
||||||
struct dhcp_option_block *options );
|
find_dhcp_option ( struct dhcp_option_block *options, unsigned int tag );
|
||||||
|
extern struct dhcp_option * find_global_dhcp_option ( unsigned int tag );
|
||||||
|
extern void register_dhcp_options ( struct dhcp_option_block *options );
|
||||||
|
extern void unregister_dhcp_options ( struct dhcp_option_block *options );
|
||||||
|
extern struct dhcp_option_block * alloc_dhcp_options ( size_t max_len );
|
||||||
|
extern void free_dhcp_options ( struct dhcp_option_block *options );
|
||||||
|
extern struct dhcp_option *
|
||||||
|
set_dhcp_option ( struct dhcp_option_block *options, unsigned int tag,
|
||||||
|
const void *data, size_t len );
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find DHCP numerical option, and return its value
|
* Find DHCP numerical option, and return its value
|
||||||
*
|
*
|
||||||
* @v tag DHCP option tag to search for
|
|
||||||
* @v options DHCP options block
|
* @v options DHCP options block
|
||||||
|
* @v tag DHCP option tag to search for
|
||||||
* @ret value Numerical value of the option, or 0 if not found
|
* @ret value Numerical value of the option, or 0 if not found
|
||||||
*
|
*
|
||||||
* This function exists merely as a notational shorthand for a call to
|
* This function exists merely as a notational shorthand for a call to
|
||||||
|
@ -103,8 +134,37 @@ extern struct dhcp_option * find_dhcp_option ( unsigned int tag,
|
||||||
* check that find_dhcp_option() returns a non-NULL value.
|
* check that find_dhcp_option() returns a non-NULL value.
|
||||||
*/
|
*/
|
||||||
static inline unsigned long
|
static inline unsigned long
|
||||||
find_dhcp_num_option ( unsigned int tag, struct dhcp_option_block *options ) {
|
find_dhcp_num_option ( struct dhcp_option_block *options, unsigned int tag ) {
|
||||||
return dhcp_num_option ( find_dhcp_option ( tag, options ) );
|
return dhcp_num_option ( find_dhcp_option ( options, tag ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find DHCP numerical option, and return its value
|
||||||
|
*
|
||||||
|
* @v tag DHCP option tag to search for
|
||||||
|
* @ret value Numerical value of the option, or 0 if not found
|
||||||
|
*
|
||||||
|
* This function exists merely as a notational shorthand for a call to
|
||||||
|
* find_global_dhcp_option() followed by a call to dhcp_num_option().
|
||||||
|
* It is not possible to distinguish between the cases "option not
|
||||||
|
* found" and "option has a value of zero" using this function; if
|
||||||
|
* this matters to you then issue the two constituent calls directly
|
||||||
|
* and check that find_global_dhcp_option() returns a non-NULL value.
|
||||||
|
*/
|
||||||
|
static inline unsigned long
|
||||||
|
find_global_dhcp_num_option ( unsigned int tag ) {
|
||||||
|
return dhcp_num_option ( find_global_dhcp_option ( tag ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete DHCP option
|
||||||
|
*
|
||||||
|
* @v options DHCP options block
|
||||||
|
* @v tag DHCP option tag
|
||||||
|
*/
|
||||||
|
static inline void delete_dhcp_option ( struct dhcp_option_block *options,
|
||||||
|
unsigned int tag ) {
|
||||||
|
set_dhcp_option ( options, tag, NULL, 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* _GPXE_DHCP_H */
|
#endif /* _GPXE_DHCP_H */
|
||||||
|
|
|
@ -19,8 +19,10 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <byteswap.h>
|
#include <byteswap.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <vsprintf.h>
|
||||||
#include <gpxe/list.h>
|
#include <gpxe/list.h>
|
||||||
#include <gpxe/dhcp.h>
|
#include <gpxe/dhcp.h>
|
||||||
|
|
||||||
|
@ -33,6 +35,26 @@
|
||||||
/** List of registered DHCP option blocks */
|
/** List of registered DHCP option blocks */
|
||||||
static LIST_HEAD ( option_blocks );
|
static LIST_HEAD ( option_blocks );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain printable version of a DHCP option tag
|
||||||
|
*
|
||||||
|
* @v tag DHCP option tag
|
||||||
|
* @ret name String representation of the tag
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static inline char * dhcp_tag_name ( unsigned int tag ) {
|
||||||
|
static char name[8];
|
||||||
|
|
||||||
|
if ( DHCP_IS_ENCAP_OPT ( tag ) ) {
|
||||||
|
snprintf ( name, sizeof ( name ), "%d.%d",
|
||||||
|
DHCP_ENCAPSULATOR ( tag ),
|
||||||
|
DHCP_ENCAPSULATED ( tag ) );
|
||||||
|
} else {
|
||||||
|
snprintf ( name, sizeof ( name ), "%d", tag );
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain value of a numerical DHCP option
|
* Obtain value of a numerical DHCP option
|
||||||
*
|
*
|
||||||
|
@ -64,44 +86,52 @@ unsigned long dhcp_num_option ( struct dhcp_option *option ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate length of a DHCP option
|
* Calculate length of a normal DHCP option
|
||||||
|
*
|
||||||
|
* @v option DHCP option
|
||||||
|
* @ret len Length (including tag and length field)
|
||||||
|
*
|
||||||
|
* @c option may not be a @c DHCP_PAD or @c DHCP_END option.
|
||||||
|
*/
|
||||||
|
static inline unsigned int dhcp_option_len ( struct dhcp_option *option ) {
|
||||||
|
assert ( option->tag != DHCP_PAD );
|
||||||
|
assert ( option->tag != DHCP_END );
|
||||||
|
return ( option->len + DHCP_OPTION_HEADER_LEN );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate length of any DHCP option
|
||||||
*
|
*
|
||||||
* @v option DHCP option
|
* @v option DHCP option
|
||||||
* @ret len Length (including tag and length field)
|
* @ret len Length (including tag and length field)
|
||||||
*/
|
*/
|
||||||
static inline unsigned int dhcp_option_len ( struct dhcp_option *option ) {
|
static inline unsigned int dhcp_any_option_len ( struct dhcp_option *option ) {
|
||||||
if ( ( option->tag == DHCP_END ) || ( option->tag == DHCP_PAD ) ) {
|
if ( ( option->tag == DHCP_END ) || ( option->tag == DHCP_PAD ) ) {
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
return ( option->len + 2 );
|
return dhcp_option_len ( option );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find DHCP option within DHCP options block, and its encapsulator (if any)
|
* Find DHCP option within DHCP options block, and its encapsulator (if any)
|
||||||
*
|
*
|
||||||
* @v tag DHCP option tag to search for
|
|
||||||
* @v options DHCP options block
|
* @v options DHCP options block
|
||||||
* @ret encapsulator Encapsulating option (if applicable, may be NULL)
|
* @v tag DHCP option tag to search for
|
||||||
* @ret option DHCP option, or NULL if not found
|
* @ret option DHCP option, or NULL if not found
|
||||||
*
|
*
|
||||||
* Searches for the DHCP option matching the specified tag within the
|
* Searches for the DHCP option matching the specified tag within the
|
||||||
* block of data. Encapsulated options may be searched for by using
|
* block of data. Encapsulated options may be searched for by using
|
||||||
* DHCP_ENCAP_OPT() to construct the tag value. If the option is an
|
* DHCP_ENCAP_OPT() to construct the tag value.
|
||||||
* encapsulated option, and @c encapsulator is non-NULL, it will be
|
|
||||||
* filled in with a pointer to the encapsulating option, if present.
|
|
||||||
* Note that the encapsulating option may be present even if the
|
|
||||||
* encapsulated option is absent, in which case @c encapsulator will
|
|
||||||
* be set but the function will return NULL.
|
|
||||||
*
|
*
|
||||||
* This routine is designed to be paranoid. It does not assume that
|
* This routine is designed to be paranoid. It does not assume that
|
||||||
* the option data is well-formatted, and so must guard against flaws
|
* the option data is well-formatted, and so must guard against flaws
|
||||||
* such as options missing a @c DHCP_END terminator, or options whose
|
* such as options missing a @c DHCP_END terminator, or options whose
|
||||||
* length would take them beyond the end of the data block.
|
* length would take them beyond the end of the data block.
|
||||||
*/
|
*/
|
||||||
static struct dhcp_option *
|
struct dhcp_option * find_dhcp_option ( struct dhcp_option_block *options,
|
||||||
find_dhcp_option_encap ( unsigned int tag, struct dhcp_option_block *options,
|
unsigned int tag ) {
|
||||||
struct dhcp_option **encapsulator ) {
|
unsigned int original_tag __attribute__ (( unused )) = tag;
|
||||||
struct dhcp_option *option = options->data;
|
struct dhcp_option *option = options->data;
|
||||||
ssize_t remaining = options->len;
|
ssize_t remaining = options->len;
|
||||||
unsigned int option_len;
|
unsigned int option_len;
|
||||||
|
@ -111,22 +141,24 @@ find_dhcp_option_encap ( unsigned int tag, struct dhcp_option_block *options,
|
||||||
* if the length is malformed (i.e. takes us beyond
|
* if the length is malformed (i.e. takes us beyond
|
||||||
* the end of the data block).
|
* the end of the data block).
|
||||||
*/
|
*/
|
||||||
option_len = dhcp_option_len ( option );
|
option_len = dhcp_any_option_len ( option );
|
||||||
remaining -= option_len;
|
remaining -= option_len;
|
||||||
if ( remaining < 0 )
|
if ( remaining < 0 )
|
||||||
break;
|
break;
|
||||||
/* Check for matching tag */
|
/* Check for matching tag */
|
||||||
if ( option->tag == tag )
|
if ( option->tag == tag ) {
|
||||||
|
DBG ( "Found DHCP option %s (length %d)\n",
|
||||||
|
dhcp_tag_name ( original_tag ), option->len );
|
||||||
return option;
|
return option;
|
||||||
|
}
|
||||||
/* Check for explicit end marker */
|
/* Check for explicit end marker */
|
||||||
if ( option->tag == DHCP_END )
|
if ( option->tag == DHCP_END )
|
||||||
break;
|
break;
|
||||||
/* Check for start of matching encapsulation block */
|
/* Check for start of matching encapsulation block */
|
||||||
if ( DHCP_ENCAPSULATOR ( tag ) &&
|
if ( DHCP_IS_ENCAP_OPT ( tag ) &&
|
||||||
( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
|
( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
|
||||||
/* Continue search within encapsulated option block */
|
/* Continue search within encapsulated option block */
|
||||||
if ( encapsulator )
|
tag = DHCP_ENCAPSULATED ( tag );
|
||||||
*encapsulator = option;
|
|
||||||
remaining = option->len;
|
remaining = option->len;
|
||||||
option = ( void * ) &option->data;
|
option = ( void * ) &option->data;
|
||||||
continue;
|
continue;
|
||||||
|
@ -136,46 +168,6 @@ find_dhcp_option_encap ( unsigned int tag, struct dhcp_option_block *options,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Find DHCP option within DHCP options block
|
|
||||||
*
|
|
||||||
* @v tag DHCP option tag to search for
|
|
||||||
* @v options DHCP options block
|
|
||||||
* @ret option DHCP option, or NULL if not found
|
|
||||||
*
|
|
||||||
* Searches for the DHCP option matching the specified tag within the
|
|
||||||
* block of data. Encapsulated options may be searched for by using
|
|
||||||
* DHCP_ENCAP_OPT() to construct the tag value.
|
|
||||||
*/
|
|
||||||
struct dhcp_option * find_dhcp_option ( unsigned int tag,
|
|
||||||
struct dhcp_option_block *options ) {
|
|
||||||
return find_dhcp_option_encap ( tag, options, NULL );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find length of used portion of DHCP options block
|
|
||||||
*
|
|
||||||
* @v options DHCP options block
|
|
||||||
* @ret len Length of used portion of data block
|
|
||||||
*
|
|
||||||
* This searches for the @c DHCP_END marker within the options block.
|
|
||||||
* If found, the length of the used portion of the block (i.e. the
|
|
||||||
* portion containing everything @b before the @c DHCP_END marker, but
|
|
||||||
* excluding the @c DHCP_END marker itself) is returned.
|
|
||||||
*
|
|
||||||
* If no @c DHCP_END marker is present, the length of the whole
|
|
||||||
* options block is returned.
|
|
||||||
*/
|
|
||||||
size_t dhcp_option_block_len ( struct dhcp_option_block *options ) {
|
|
||||||
void *dhcpend;
|
|
||||||
|
|
||||||
if ( ( dhcpend = find_dhcp_option ( DHCP_END, options ) ) ) {
|
|
||||||
return ( dhcpend - options->data );
|
|
||||||
} else {
|
|
||||||
return options->len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find DHCP option within all registered DHCP options blocks
|
* Find DHCP option within all registered DHCP options blocks
|
||||||
*
|
*
|
||||||
|
@ -197,7 +189,7 @@ struct dhcp_option * find_global_dhcp_option ( unsigned int tag ) {
|
||||||
struct dhcp_option *option;
|
struct dhcp_option *option;
|
||||||
|
|
||||||
list_for_each_entry ( options, &option_blocks, list ) {
|
list_for_each_entry ( options, &option_blocks, list ) {
|
||||||
if ( ( option = find_dhcp_option ( tag, options ) ) )
|
if ( ( option = find_dhcp_option ( options, tag ) ) )
|
||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -211,21 +203,19 @@ struct dhcp_option * find_global_dhcp_option ( unsigned int tag ) {
|
||||||
* Register a block of DHCP options.
|
* Register a block of DHCP options.
|
||||||
*/
|
*/
|
||||||
void register_dhcp_options ( struct dhcp_option_block *options ) {
|
void register_dhcp_options ( struct dhcp_option_block *options ) {
|
||||||
struct dhcp_option_block *existing_options;
|
struct dhcp_option_block *existing;
|
||||||
signed int existing_priority;
|
|
||||||
signed int priority;
|
|
||||||
|
|
||||||
/* Determine priority of new block */
|
/* Determine priority of new block */
|
||||||
priority = find_dhcp_num_option ( DHCP_EB_PRIORITY, options );
|
options->priority = find_dhcp_num_option ( options, DHCP_EB_PRIORITY );
|
||||||
|
DBG ( "Registering DHCP options block with priority %d\n",
|
||||||
|
options->priority );
|
||||||
|
|
||||||
/* Insert after any existing blocks which have a higher priority */
|
/* Insert after any existing blocks which have a higher priority */
|
||||||
list_for_each_entry ( existing_options, &option_blocks, list ) {
|
list_for_each_entry ( existing, &option_blocks, list ) {
|
||||||
existing_priority = find_dhcp_num_option ( DHCP_EB_PRIORITY,
|
if ( options->priority > existing->priority )
|
||||||
existing_options );
|
|
||||||
if ( priority > existing_priority )
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
list_add_tail ( &options->list, &existing_options->list );
|
list_add_tail ( &options->list, &existing->list );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -240,23 +230,24 @@ void unregister_dhcp_options ( struct dhcp_option_block *options ) {
|
||||||
/**
|
/**
|
||||||
* Allocate space for a block of DHCP options
|
* Allocate space for a block of DHCP options
|
||||||
*
|
*
|
||||||
* @v len Maximum length of option block
|
* @v max_len Maximum length of option block
|
||||||
* @ret options Option block, or NULL
|
* @ret options DHCP option block, or NULL
|
||||||
*
|
*
|
||||||
* Creates a new DHCP option block and populates it with an empty
|
* Creates a new DHCP option block and populates it with an empty
|
||||||
* options list. This call does not register the options block.
|
* options list. This call does not register the options block.
|
||||||
*/
|
*/
|
||||||
struct dhcp_option_block * alloc_dhcp_options ( size_t len ) {
|
struct dhcp_option_block * alloc_dhcp_options ( size_t max_len ) {
|
||||||
struct dhcp_option_block *options;
|
struct dhcp_option_block *options;
|
||||||
struct dhcp_option *option;
|
struct dhcp_option *option;
|
||||||
|
|
||||||
options = malloc ( sizeof ( *options ) + len );
|
options = malloc ( sizeof ( *options ) + max_len );
|
||||||
if ( options ) {
|
if ( options ) {
|
||||||
options->data = ( ( void * ) options + sizeof ( *options ) );
|
options->data = ( ( void * ) options + sizeof ( *options ) );
|
||||||
options->len = len;
|
options->max_len = max_len;
|
||||||
if ( len ) {
|
if ( max_len ) {
|
||||||
option = options->data;
|
option = options->data;
|
||||||
option->tag = DHCP_END;
|
option->tag = DHCP_END;
|
||||||
|
options->len = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return options;
|
return options;
|
||||||
|
@ -265,8 +256,124 @@ struct dhcp_option_block * alloc_dhcp_options ( size_t len ) {
|
||||||
/**
|
/**
|
||||||
* Free DHCP options block
|
* Free DHCP options block
|
||||||
*
|
*
|
||||||
* @v options Option block
|
* @v options DHCP option block
|
||||||
*/
|
*/
|
||||||
void free_dhcp_options ( struct dhcp_option_block *options ) {
|
void free_dhcp_options ( struct dhcp_option_block *options ) {
|
||||||
free ( options );
|
free ( options );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize a DHCP option
|
||||||
|
*
|
||||||
|
* @v options DHCP option block
|
||||||
|
* @v option DHCP option to resize
|
||||||
|
* @v encapsulator Encapsulating option (or NULL)
|
||||||
|
* @v old_len Old length (including header)
|
||||||
|
* @v new_len New length (including header)
|
||||||
|
* @ret rc Return status code
|
||||||
|
*/
|
||||||
|
static int resize_dhcp_option ( struct dhcp_option_block *options,
|
||||||
|
struct dhcp_option *option,
|
||||||
|
struct dhcp_option *encapsulator,
|
||||||
|
size_t old_len, size_t new_len ) {
|
||||||
|
void *source = ( ( ( void * ) option ) + old_len );
|
||||||
|
void *dest = ( ( ( void * ) option ) + new_len );
|
||||||
|
void *end = ( options->data + options->max_len );
|
||||||
|
ssize_t delta = ( new_len - old_len );
|
||||||
|
size_t new_options_len;
|
||||||
|
size_t new_encapsulator_len;
|
||||||
|
|
||||||
|
/* Check for sufficient space, and update length fields */
|
||||||
|
if ( new_len > DHCP_MAX_LEN )
|
||||||
|
return -ENOMEM;
|
||||||
|
new_options_len = ( options->len + delta );
|
||||||
|
if ( new_options_len > options->max_len )
|
||||||
|
return -ENOMEM;
|
||||||
|
if ( encapsulator ) {
|
||||||
|
new_encapsulator_len = ( encapsulator->len + delta );
|
||||||
|
if ( new_encapsulator_len > DHCP_MAX_LEN )
|
||||||
|
return -ENOMEM;
|
||||||
|
encapsulator->len = new_encapsulator_len;
|
||||||
|
}
|
||||||
|
options->len = new_options_len;
|
||||||
|
|
||||||
|
/* Move remainder of option data */
|
||||||
|
memmove ( dest, source, ( end - dest ) );
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set value of DHCP option
|
||||||
|
*
|
||||||
|
* @v options DHCP option block
|
||||||
|
* @v tag DHCP option tag
|
||||||
|
* @v data New value for DHCP option
|
||||||
|
* @v len Length of value, in bytes
|
||||||
|
* @ret option DHCP option, or NULL
|
||||||
|
*
|
||||||
|
* Sets the value of a DHCP option within the options block. The
|
||||||
|
* option may or may not already exist. Encapsulators will be created
|
||||||
|
* (and deleted) as necessary.
|
||||||
|
*
|
||||||
|
* This call may fail due to insufficient space in the options block.
|
||||||
|
* If it does fail, and the option existed previously, the option will
|
||||||
|
* be left with its original value.
|
||||||
|
*/
|
||||||
|
struct dhcp_option * set_dhcp_option ( struct dhcp_option_block *options,
|
||||||
|
unsigned int tag,
|
||||||
|
const void *data, size_t len ) {
|
||||||
|
static const uint8_t empty_encapsulator[] = { DHCP_END };
|
||||||
|
struct dhcp_option *option;
|
||||||
|
void *insertion_point = options->data;
|
||||||
|
struct dhcp_option *encapsulator = NULL;
|
||||||
|
unsigned int encap_tag = DHCP_ENCAPSULATOR ( tag );
|
||||||
|
size_t old_len = 0;
|
||||||
|
size_t new_len = ( len ? ( len + DHCP_OPTION_HEADER_LEN ) : 0 );
|
||||||
|
|
||||||
|
/* Find old instance of this option, if any */
|
||||||
|
option = find_dhcp_option ( options, tag );
|
||||||
|
if ( option ) {
|
||||||
|
old_len = dhcp_option_len ( option );
|
||||||
|
DBG ( "Resizing DHCP option %s from length %d to %d\n",
|
||||||
|
dhcp_tag_name ( tag ), option->len, len );
|
||||||
|
} else {
|
||||||
|
old_len = 0;
|
||||||
|
DBG ( "Creating DHCP option %s (length %d)\n",
|
||||||
|
dhcp_tag_name ( tag ), new_len );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure that encapsulator exists, if required */
|
||||||
|
if ( DHCP_IS_ENCAP_OPT ( tag ) ) {
|
||||||
|
encapsulator = find_dhcp_option ( options, encap_tag );
|
||||||
|
if ( ! encapsulator )
|
||||||
|
encapsulator = set_dhcp_option ( options, encap_tag,
|
||||||
|
empty_encapsulator,
|
||||||
|
sizeof ( empty_encapsulator) );
|
||||||
|
if ( ! encapsulator )
|
||||||
|
return NULL;
|
||||||
|
insertion_point = &encapsulator->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create new option if necessary */
|
||||||
|
if ( ! option )
|
||||||
|
option = insertion_point;
|
||||||
|
|
||||||
|
/* Resize option to fit new data */
|
||||||
|
if ( resize_dhcp_option ( options, option, encapsulator,
|
||||||
|
old_len, new_len ) != 0 )
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Copy new data into option, if applicable */
|
||||||
|
if ( len ) {
|
||||||
|
option->tag = tag;
|
||||||
|
option->len = len;
|
||||||
|
memcpy ( &option->data, data, len );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delete encapsulator if there's nothing else left in it */
|
||||||
|
if ( encapsulator && ( encapsulator->len <= 1 ) )
|
||||||
|
set_dhcp_option ( options, encap_tag, NULL, 0 );
|
||||||
|
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue