2
0
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:
Michael Brown 2006-07-13 20:49:04 +00:00
parent 3acbff4f00
commit 19e8b41562
2 changed files with 248 additions and 81 deletions

View File

@ -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 */

View File

@ -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;
}