*/
#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.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/in.h>
+#include <gpxe/uri.h>
#include <gpxe/dhcp.h>
/** @file
*/
/** List of registered DHCP option blocks */
-static LIST_HEAD ( option_blocks );
+LIST_HEAD ( dhcp_option_blocks );
/**
* Obtain printable version of a DHCP option tag
return value;
}
+/**
+ * Obtain value of an IPv4-address DHCP option
+ *
+ * @v option DHCP option, or NULL
+ * @v inp IPv4 address to fill in
+ *
+ * Parses the IPv4 address value from a DHCP option, if present. It
+ * is permitted to call dhcp_ipv4_option() with @c option set to NULL;
+ * in this case the address will be set to 0.0.0.0.
+ */
+void dhcp_ipv4_option ( struct dhcp_option *option, struct in_addr *inp ) {
+ if ( option )
+ *inp = option->data.in;
+}
+
+/**
+ * Print DHCP string option value into buffer
+ *
+ * @v buf Buffer into which to write the string
+ * @v size Size of buffer
+ * @v option DHCP option, or NULL
+ * @ret len Length of formatted string
+ *
+ * DHCP option strings are stored without a NUL terminator. This
+ * function provides a convenient way to extract these DHCP strings
+ * into standard C strings. It is permitted to call dhcp_snprintf()
+ * with @c option set to NULL; in this case the buffer will be filled
+ * with an empty string.
+ *
+ * The usual snprintf() semantics apply with regard to buffer size,
+ * return value when the buffer is too small, etc.
+ */
+int dhcp_snprintf ( char *buf, size_t size, struct dhcp_option *option ) {
+ size_t len;
+ char *content = "";
+
+ if ( option ) {
+ /* Shrink buffer size so that it is only just large
+ * enough to contain the option data. snprintf() will
+ * take care of everything else (inserting the NUL etc.)
+ */
+ len = ( option->len + 1 );
+ if ( len < size )
+ size = len;
+ content = option->data.string;
+ }
+ return snprintf ( buf, size, "%s", content );
+}
+
/**
* Calculate length of a normal DHCP option
*
*
* @v options DHCP options block
* @v tag DHCP option tag to search for
+ * @ret encapsulator Encapsulating DHCP option
* @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.
+ * DHCP option block. Encapsulated options may be searched for by
+ * using DHCP_ENCAP_OPT() to construct the tag value.
+ *
+ * If the option is encapsulated, and @c encapsulator is non-NULL, it
+ * will be filled in with a pointer to the encapsulating option.
*
* 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.
*/
-struct dhcp_option * find_dhcp_option ( struct dhcp_option_block *options,
- unsigned int tag ) {
+static struct dhcp_option *
+find_dhcp_option_with_encap ( struct dhcp_option_block *options,
+ unsigned int tag,
+ struct dhcp_option **encapsulator ) {
unsigned int original_tag __attribute__ (( unused )) = tag;
struct dhcp_option *option = options->data;
ssize_t remaining = options->len;
break;
/* Check for matching tag */
if ( option->tag == tag ) {
- DBG ( "Found DHCP option %s (length %d)\n",
- dhcp_tag_name ( original_tag ), option->len );
+ DBG ( "Found DHCP option %s (length %d) in block %p\n",
+ dhcp_tag_name ( original_tag ), option->len,
+ options );
return option;
}
/* Check for explicit end marker */
/* Check for start of matching encapsulation block */
if ( DHCP_IS_ENCAP_OPT ( tag ) &&
( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
+ if ( encapsulator )
+ *encapsulator = option;
/* Continue search within encapsulated option block */
tag = DHCP_ENCAPSULATED ( tag );
remaining = option->len;
}
/**
- * Find DHCP option within all registered DHCP options blocks
+ * Find DHCP option within DHCP options block
*
+ * @v options DHCP options block, or NULL
* @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.
+ * Searches for the DHCP option matching the specified tag within the
+ * DHCP option block. Encapsulated options may be searched for by
+ * using DHCP_ENCAP_OPT() to construct the tag value.
*
- * Where multiple option blocks contain the same DHCP option, the
- * option from the highest-priority block will be returned. (Priority
- * of an options block is determined by the value of the @c
- * DHCP_EB_PRIORITY option within the block, if present; the default
- * priority is zero).
+ * If @c options is NULL, all registered option blocks will be
+ * searched. Where multiple option blocks contain the same DHCP
+ * option, the option from the highest-priority block will be
+ * returned. (Priority of an options block is determined by the value
+ * of the @c DHCP_EB_PRIORITY option within the block, if present; the
+ * default priority is zero).
*/
-struct dhcp_option * find_global_dhcp_option ( unsigned int tag ) {
- struct dhcp_option_block *options;
+struct dhcp_option * find_dhcp_option ( struct dhcp_option_block *options,
+ unsigned int tag ) {
struct dhcp_option *option;
- list_for_each_entry ( options, &option_blocks, list ) {
- if ( ( option = find_dhcp_option ( options, tag ) ) )
- return option;
+ if ( options ) {
+ return find_dhcp_option_with_encap ( options, tag, NULL );
+ } else {
+ list_for_each_entry ( options, &dhcp_option_blocks, list ) {
+ if ( ( option = find_dhcp_option ( options, tag ) ) )
+ return option;
+ }
+ return NULL;
}
- return NULL;
}
/**
/* Determine priority of new block */
options->priority = find_dhcp_num_option ( options, DHCP_EB_PRIORITY );
- DBG ( "Registering DHCP options block with priority %d\n",
- options->priority );
+ DBG ( "Registering DHCP options block %p with priority %d\n",
+ options, options->priority );
/* Insert after any existing blocks which have a higher priority */
- list_for_each_entry ( existing, &option_blocks, list ) {
+ list_for_each_entry ( existing, &dhcp_option_blocks, list ) {
if ( options->priority > existing->priority )
break;
}
+ dhcpopt_get ( options );
list_add_tail ( &options->list, &existing->list );
+
+ /* Apply all registered DHCP options */
+ apply_global_dhcp_options();
}
/**
*/
void unregister_dhcp_options ( struct dhcp_option_block *options ) {
list_del ( &options->list );
+ dhcpopt_put ( options );
+}
+
+/**
+ * Initialise empty block of DHCP options
+ *
+ * @v options Uninitialised DHCP option block
+ * @v data Memory for DHCP option data
+ * @v max_len Length of memory for DHCP option data
+ *
+ * Populates the DHCP option data with a single @c DHCP_END option and
+ * fills in the fields of the @c dhcp_option_block structure.
+ */
+void init_dhcp_options ( struct dhcp_option_block *options,
+ void *data, size_t max_len ) {
+ struct dhcp_option *option;
+
+ options->data = data;
+ options->max_len = max_len;
+ option = options->data;
+ option->tag = DHCP_END;
+ options->len = 1;
+
+ DBG ( "DHCP options block %p initialised (data %p max_len %#zx)\n",
+ options, options->data, options->max_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 ) + max_len );
if ( options ) {
- options->data = ( ( void * ) options + sizeof ( *options ) );
- options->max_len = max_len;
- if ( max_len ) {
- option = options->data;
- option->tag = DHCP_END;
- options->len = 1;
- }
+ init_dhcp_options ( options,
+ ( (void *) options + sizeof ( *options ) ),
+ max_len );
}
return options;
}
-/**
- * Free DHCP options block
- *
- * @v options DHCP option block
- */
-void free_dhcp_options ( struct dhcp_option_block *options ) {
- free ( options );
-}
-
/**
* Resize a DHCP option
*
const void *data, size_t len ) {
static const uint8_t empty_encapsulator[] = { DHCP_END };
struct dhcp_option *option;
- void *insertion_point = options->data;
+ void *insertion_point;
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 );
+ /* Return NULL if no options block specified */
+ if ( ! options )
+ return NULL;
+
/* Find old instance of this option, if any */
- option = find_dhcp_option ( options, tag );
+ option = find_dhcp_option_with_encap ( options, tag, &encapsulator );
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 );
+ DBG ( "Resizing DHCP option %s from length %d to %zd in block "
+ "%p\n", dhcp_tag_name (tag), option->len, len, options );
} else {
old_len = 0;
- DBG ( "Creating DHCP option %s (length %d)\n",
- dhcp_tag_name ( tag ), new_len );
+ DBG ( "Creating DHCP option %s (length %zd) in block %p\n",
+ dhcp_tag_name ( tag ), len, options );
}
/* Ensure that encapsulator exists, if required */
+ insertion_point = options->data;
if ( DHCP_IS_ENCAP_OPT ( tag ) ) {
- encapsulator = find_dhcp_option ( options, encap_tag );
if ( ! encapsulator )
encapsulator = set_dhcp_option ( options, encap_tag,
empty_encapsulator,
return option;
}
+
+/**
+ * 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
+ *
+ * This function exists merely as a notational shorthand for
+ * find_dhcp_option() with @c options set to NULL.
+ */
+struct dhcp_option * find_global_dhcp_option ( unsigned int tag ) {
+ return find_dhcp_option ( NULL, tag );
+}
+
+/**
+ * Find DHCP numerical option, and return its value
+ *
+ * @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
+ * find_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_dhcp_option() returns a non-NULL value.
+ */
+unsigned long 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.
+ */
+unsigned long find_global_dhcp_num_option ( unsigned int tag ) {
+ return dhcp_num_option ( find_global_dhcp_option ( tag ) );
+}
+
+/**
+ * Find DHCP IPv4-address option, and return its value
+ *
+ * @v options DHCP options block
+ * @v tag DHCP option tag to search for
+ * @v inp IPv4 address to fill in
+ * @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_dhcp_option() followed by a call to dhcp_ipv4_option(). It is
+ * not possible to distinguish between the cases "option not found"
+ * and "option has a value of 0.0.0.0" using this function; if this
+ * matters to you then issue the two constituent calls directly and
+ * check that find_dhcp_option() returns a non-NULL value.
+ */
+void find_dhcp_ipv4_option ( struct dhcp_option_block *options,
+ unsigned int tag, struct in_addr *inp ) {
+ dhcp_ipv4_option ( find_dhcp_option ( options, tag ), inp );
+}
+
+/**
+ * Find DHCP IPv4-address option, and return its value
+ *
+ * @v options DHCP options block
+ * @v tag DHCP option tag to search for
+ * @v inp IPv4 address to fill in
+ * @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_dhcp_option() followed by a call to dhcp_ipv4_option(). It is
+ * not possible to distinguish between the cases "option not found"
+ * and "option has a value of 0.0.0.0" using this function; if this
+ * matters to you then issue the two constituent calls directly and
+ * check that find_dhcp_option() returns a non-NULL value.
+ */
+void find_global_dhcp_ipv4_option ( unsigned int tag, struct in_addr *inp ) {
+ dhcp_ipv4_option ( find_global_dhcp_option ( tag ), inp );
+}
+
+/**
+ * Delete DHCP option
+ *
+ * @v options DHCP options block
+ * @v tag DHCP option tag
+ *
+ * This function exists merely as a notational shorthand for a call to
+ * set_dhcp_option() with @c len set to zero.
+ */
+void delete_dhcp_option ( struct dhcp_option_block *options,
+ unsigned int tag ) {
+ set_dhcp_option ( options, tag, NULL, 0 );
+}
+
+/**
+ * Apply DHCP options
+ *
+ * @v options DHCP options block, or NULL
+ * @ret rc Return status code
+ */
+int apply_dhcp_options ( struct dhcp_option_block *options ) {
+ struct in_addr tftp_server;
+ struct uri *uri;
+ char uri_string[32];
+
+ /* Set current working URI based on TFTP server */
+ find_dhcp_ipv4_option ( options, DHCP_EB_SIADDR, &tftp_server );
+ snprintf ( uri_string, sizeof ( uri_string ),
+ "tftp://%s/", inet_ntoa ( tftp_server ) );
+ uri = parse_uri ( uri_string );
+ if ( ! uri )
+ return -ENOMEM;
+ churi ( uri );
+ uri_put ( uri );
+
+ return 0;
+}
+
+/**
+ * Apply global DHCP options
+ *
+ * @ret rc Return status code
+ */
+int apply_global_dhcp_options ( void ) {
+ return apply_dhcp_options ( NULL );
+}