diff --git a/src/include/gpxe/dhcp.h b/src/include/gpxe/dhcp.h new file mode 100644 index 00000000..6d293935 --- /dev/null +++ b/src/include/gpxe/dhcp.h @@ -0,0 +1,87 @@ +#ifndef _GPXE_DHCP_H +#define _GPXE_DHCP_H + +/** @file + * + * Dynamic Host Configuration Protocol + * + */ + +#include +#include + +/** Construct a tag value for an encapsulated option + * + * This tag value can be passed to Etherboot functions when searching + * for DHCP options in order to search for a tag within an + * encapsulated options block. + */ +#define DHCP_ENCAP_OPT( encapsulator, encapsulated ) \ + ( ( (encapsulator) << 8 ) | (encapsulated) ) +/** Extract encapsulating option block tag from encapsulated tag value */ +#define DHCP_ENCAPSULATOR( encap_opt ) ( (encap_opt) >> 8 ) +/** Extract encapsulated option tag from encapsulated tag value */ +#define DHCP_ENCAPSULATED( encap_opt ) ( (encap_opt) & 0xff ) + +/** + * @defgroup dhcpopts DHCP option tags + * @{ + */ + +#define DHCP_PAD 0 +#define DHCP_END 255 + +#define DHCP_EB_ENCAP 175 + +#define DHCP_EB_PRIORITY DHCP_ENCAP_OPT ( DHCP_EB_ENCAP, 1 ) + +/** @} */ + +/** + * A DHCP option + * + * DHCP options consist of a mandatory tag, a length field that is + * mandatory for all options except @c DHCP_PAD and @c DHCP_END, and a + * payload. + */ +struct dhcp_option { + /** Tag + * + * Must be a @c DHCP_XXX value. + */ + uint8_t tag; + /** Length + * + * This is the length of the data field (i.e. excluding the + * tag and length fields). For the two tags @c DHCP_PAD and + * @c DHCP_END, the length field is implicitly zero and is + * also missing, i.e. these DHCP options are only a single + * byte in length. + */ + uint8_t len; + /** Option data + * + * Interpretation of the content is entirely dependent upon + * the tag. For fields containing a multi-byte integer, the + * field is defined to be in network-endian order (unless you + * are Intel and feel like violating the spec for fun). + */ + union { + uint8_t byte; + uint16_t word; + uint32_t dword; + uint8_t bytes[0]; + } data; +} __attribute__ (( packed )); + +/** A DHCP options block */ +struct dhcp_option_block { + /** List of option blocks */ + struct list_head list; + /** Option block raw data */ + void *data; + /** Option block length */ + size_t len; +}; + +#endif /* _GPXE_DHCP_H */ diff --git a/src/net/dhcpopts.c b/src/net/dhcpopts.c new file mode 100644 index 00000000..4fe5bcf1 --- /dev/null +++ b/src/net/dhcpopts.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2006 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include +#include + +/** @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 ); +}