First stab at DHCP option handling in a way that will allow us to have
authorMichael Brown <mcb30@etherboot.org>
Tue, 27 Jun 2006 17:24:07 +0000 (17:24 +0000)
committerMichael Brown <mcb30@etherboot.org>
Tue, 27 Jun 2006 17:24:07 +0000 (17:24 +0000)
multiple option sources (e.g. multiple DHCP replies, non-volatile
storage etc.)

src/include/gpxe/dhcp.h [new file with mode: 0644]
src/net/dhcpopts.c [new file with mode: 0644]

diff --git a/src/include/gpxe/dhcp.h b/src/include/gpxe/dhcp.h
new file mode 100644 (file)
index 0000000..6d29393
--- /dev/null
@@ -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 (file)
index 0000000..4fe5bcf
--- /dev/null
@@ -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 );
+}