summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/include/gpxe/dhcp.h87
-rw-r--r--src/net/dhcpopts.c217
2 files changed, 304 insertions, 0 deletions
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 <stdint.h>
+#include <gpxe/list.h>
+
+/** 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 <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 );
+}