mirror of
https://github.com/xcat2/xNBA.git
synced 2024-12-14 23:31:39 +00:00
Add code to modify DHCP option values within a block.
This commit is contained in:
parent
3acbff4f00
commit
19e8b41562
@ -23,6 +23,8 @@
|
||||
/** Extract encapsulated option tag from encapsulated tag value */
|
||||
#define DHCP_ENCAPSULATED( encap_opt ) ( (encap_opt) & 0xff )
|
||||
|
||||
#define DHCP_IS_ENCAP_OPT( opt ) DHCP_ENCAPSULATOR( opt )
|
||||
|
||||
/**
|
||||
* @defgroup dhcpopts DHCP option tags
|
||||
* @{
|
||||
@ -74,6 +76,17 @@ struct dhcp_option {
|
||||
} data;
|
||||
} __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 */
|
||||
struct dhcp_option_block {
|
||||
/** List of option blocks */
|
||||
@ -82,17 +95,35 @@ struct dhcp_option_block {
|
||||
void *data;
|
||||
/** Option block length */
|
||||
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 struct dhcp_option * find_dhcp_option ( unsigned int tag,
|
||||
struct dhcp_option_block *options );
|
||||
extern struct dhcp_option *
|
||||
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
|
||||
*
|
||||
* @v tag DHCP option tag to search for
|
||||
* @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
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
static inline unsigned long
|
||||
find_dhcp_num_option ( unsigned int tag, struct dhcp_option_block *options ) {
|
||||
return dhcp_num_option ( find_dhcp_option ( tag, options ) );
|
||||
find_dhcp_num_option ( struct dhcp_option_block *options, unsigned int tag ) {
|
||||
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 */
|
||||
|
@ -19,8 +19,10 @@
|
||||
#include <stdint.h>
|
||||
#include <byteswap.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
#include <assert.h>
|
||||
#include <vsprintf.h>
|
||||
#include <gpxe/list.h>
|
||||
#include <gpxe/dhcp.h>
|
||||
|
||||
@ -33,6 +35,26 @@
|
||||
/** List of registered DHCP 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
|
||||
*
|
||||
@ -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
|
||||
* @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 ) ) {
|
||||
return 1;
|
||||
} else {
|
||||
return ( option->len + 2 );
|
||||
return dhcp_option_len ( option );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @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
|
||||
*
|
||||
* 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. If the option is an
|
||||
* 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.
|
||||
* DHCP_ENCAP_OPT() to construct the tag value.
|
||||
*
|
||||
* This routine is designed to be paranoid. It does not assume that
|
||||
* the option data is well-formatted, and so must guard against flaws
|
||||
* such as options missing a @c DHCP_END terminator, or options whose
|
||||
* length would take them beyond the end of the data block.
|
||||
*/
|
||||
static struct dhcp_option *
|
||||
find_dhcp_option_encap ( unsigned int tag, struct dhcp_option_block *options,
|
||||
struct dhcp_option **encapsulator ) {
|
||||
struct dhcp_option * find_dhcp_option ( struct dhcp_option_block *options,
|
||||
unsigned int tag ) {
|
||||
unsigned int original_tag __attribute__ (( unused )) = tag;
|
||||
struct dhcp_option *option = options->data;
|
||||
ssize_t remaining = options->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
|
||||
* the end of the data block).
|
||||
*/
|
||||
option_len = dhcp_option_len ( option );
|
||||
option_len = dhcp_any_option_len ( option );
|
||||
remaining -= option_len;
|
||||
if ( remaining < 0 )
|
||||
break;
|
||||
/* 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;
|
||||
}
|
||||
/* Check for explicit end marker */
|
||||
if ( option->tag == DHCP_END )
|
||||
break;
|
||||
/* Check for start of matching encapsulation block */
|
||||
if ( DHCP_ENCAPSULATOR ( tag ) &&
|
||||
if ( DHCP_IS_ENCAP_OPT ( tag ) &&
|
||||
( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
|
||||
/* Continue search within encapsulated option block */
|
||||
if ( encapsulator )
|
||||
*encapsulator = option;
|
||||
tag = DHCP_ENCAPSULATED ( tag );
|
||||
remaining = option->len;
|
||||
option = ( void * ) &option->data;
|
||||
continue;
|
||||
@ -136,46 +168,6 @@ find_dhcp_option_encap ( unsigned int tag, struct dhcp_option_block *options,
|
||||
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
|
||||
*
|
||||
@ -197,7 +189,7 @@ struct dhcp_option * find_global_dhcp_option ( unsigned int tag ) {
|
||||
struct dhcp_option *option;
|
||||
|
||||
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 NULL;
|
||||
@ -211,21 +203,19 @@ struct dhcp_option * find_global_dhcp_option ( unsigned int tag ) {
|
||||
* Register a block of DHCP options.
|
||||
*/
|
||||
void register_dhcp_options ( struct dhcp_option_block *options ) {
|
||||
struct dhcp_option_block *existing_options;
|
||||
signed int existing_priority;
|
||||
signed int priority;
|
||||
struct dhcp_option_block *existing;
|
||||
|
||||
/* 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 */
|
||||
list_for_each_entry ( existing_options, &option_blocks, list ) {
|
||||
existing_priority = find_dhcp_num_option ( DHCP_EB_PRIORITY,
|
||||
existing_options );
|
||||
if ( priority > existing_priority )
|
||||
list_for_each_entry ( existing, &option_blocks, list ) {
|
||||
if ( options->priority > existing->priority )
|
||||
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
|
||||
*
|
||||
* @v len Maximum length of option block
|
||||
* @ret options Option block, or NULL
|
||||
* @v max_len Maximum length of option block
|
||||
* @ret options DHCP option block, or NULL
|
||||
*
|
||||
* Creates a new DHCP option block and populates it with an empty
|
||||
* 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 *option;
|
||||
|
||||
options = malloc ( sizeof ( *options ) + len );
|
||||
options = malloc ( sizeof ( *options ) + max_len );
|
||||
if ( options ) {
|
||||
options->data = ( ( void * ) options + sizeof ( *options ) );
|
||||
options->len = len;
|
||||
if ( len ) {
|
||||
options->max_len = max_len;
|
||||
if ( max_len ) {
|
||||
option = options->data;
|
||||
option->tag = DHCP_END;
|
||||
options->len = 1;
|
||||
}
|
||||
}
|
||||
return options;
|
||||
@ -265,8 +256,124 @@ struct dhcp_option_block * alloc_dhcp_options ( size_t len ) {
|
||||
/**
|
||||
* Free DHCP options block
|
||||
*
|
||||
* @v options Option block
|
||||
* @v options DHCP option block
|
||||
*/
|
||||
void free_dhcp_options ( struct dhcp_option_block *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
Block a user