diff options
Diffstat (limited to 'src/net/dhcpopts.c')
-rw-r--r-- | src/net/dhcpopts.c | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/src/net/dhcpopts.c b/src/net/dhcpopts.c new file mode 100644 index 000000000..4fe5bcf10 --- /dev/null +++ b/src/net/dhcpopts.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <byteswap.h> +#include <errno.h> +#include <malloc.h> +#include <assert.h> +#include <gpxe/list.h> +#include <gpxe/dhcp.h> + +/** @file + * + * DHCP options + * + */ + +/** List of registered DHCP option blocks */ +static LIST_HEAD ( option_blocks ); + +/** + * Obtain value of a numerical DHCP option + * + * @v option DHCP option, or NULL + * @v value Unsigned long for storing the result + * @ret rc Return status code + * + * Parses the numerical value from a DHCP option, if present. It is + * permitted to call dhcp_num_option() with @c option set to NULL; in + * this case the result value will not be modified and an error will + * be returned. + * + * The caller does not specify the size of the DHCP option data; this + * is implied by the length field stored within the DHCP option + * itself. + */ +int dhcp_num_option ( struct dhcp_option *option, unsigned long *value ) { + uint8_t *data; + unsigned long tmp = 0; + + if ( ! option ) + return -EINVAL; + + /* This is actually smaller code than using htons() etc., and + * will also cope well with malformed options (such as + * zero-length options). + */ + for ( data = option->data.bytes ; + data < ( option->data.bytes + option->len ) ; data++ ) + tmp = ( ( tmp << 8 ) | *data ); + *value = tmp; + return 0; +} + +/** + * Calculate length of a 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 ) { + if ( ( option->tag == DHCP_END ) || ( option->tag == DHCP_PAD ) ) { + return 1; + } else { + return ( option->len + 2 ); + } +} + +/** + * Find DHCP option within block of raw data + * + * @v tag DHCP option tag to search for + * @v data Data block + * @v len Length of data 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. + * + * 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. + * + * Searching for @c DHCP_PAD or @c DHCP_END tags, or using either @c + * DHCP_PAD or @c DHCP_END as the encapsulator when constructing the + * tag via DHCP_ENCAP_OPT() will produce undefined behaviour. + */ +static struct dhcp_option * find_dhcp_option_raw ( unsigned int tag, + void *data, size_t len ) { + struct dhcp_option *option = data; + ssize_t remaining = len; + unsigned int option_len; + + assert ( tag != DHCP_PAD ); + assert ( tag != DHCP_END ); + assert ( DHCP_ENCAPSULATOR ( tag ) != DHCP_END ); + + while ( remaining ) { + /* Check for explicit end marker */ + if ( option->tag == DHCP_END ) + break; + /* Calculate length of this option. Abort processing + * if the length is malformed (i.e. takes us beyond + * the end of the data block). + */ + option_len = dhcp_option_len ( option ); + remaining -= option_len; + if ( remaining < 0 ) + break; + /* Check for matching tag */ + if ( option->tag == tag ) + return option; + /* Check for start of matching encapsulation block */ + if ( DHCP_ENCAPSULATOR ( tag ) && + ( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) { + /* Search within encapsulated option block */ + return find_dhcp_option_raw ( DHCP_ENCAPSULATED( tag ), + &option->data, + option->len ); + } + option = ( ( ( void * ) option ) + option_len ); + } + return NULL; +} + +/** + * Find DHCP option within all registered DHCP options blocks + * + * @v tag DHCP option tag to search for + * @ret option DHCP option, or NULL if not found + * + * Searches within all registered DHCP option blocks for the specified + * tag. 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; + struct dhcp_option *option; + + list_for_each_entry ( options, &option_blocks, list ) { + if ( ( option = find_dhcp_option_raw ( tag, options->data, + options->len ) ) ) + return option; + } + return NULL; +} + +/** + * Register DHCP option block + * + * @v options DHCP option block + * + * Register a block of DHCP options + */ +void register_dhcp_options ( struct dhcp_option_block *options ) { + list_add ( &options->list, &option_blocks ); +} + +/** + * Unregister DHCP option block + * + * @v options DHCP option block + */ +void unregister_dhcp_options ( struct dhcp_option_block *options ) { + list_del ( &options->list ); +} + +/** + * Allocate space for a block of DHCP options + * + * @v len Maximum length of option block + * @ret options 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 *options; + struct dhcp_option *option; + + options = malloc ( sizeof ( *options ) + len ); + if ( options ) { + options->data = ( ( void * ) options + sizeof ( *options ) ); + options->len = len; + if ( len ) { + option = options->data; + option->tag = DHCP_END; + } + } + return options; +} + +/** + * Free DHCP options block + * + * @v options Option block + */ +void free_dhcp_options ( struct dhcp_option_block *options ) { + free ( options ); +} |