Merge gpxe-for-syslinux
authorH. Peter Anvin <hpa@zytor.com>
Mon, 31 Mar 2008 12:27:05 +0000 (05:27 -0700)
committerH. Peter Anvin <hpa@zytor.com>
Mon, 31 Mar 2008 12:27:05 +0000 (05:27 -0700)
Merge gpxe-for-syslinux up to commit
71c509be409820a12efeebf31f486e144c9efdae; upstream commit
b107637008d15e00a4d95cdb5c8f5c11fda490f7.

15 files changed:
gpxe/src/arch/i386/firmware/pcbios/smbios.c
gpxe/src/arch/i386/firmware/pcbios/smbios_settings.c [new file with mode: 0644]
gpxe/src/arch/i386/include/bits/errfile.h
gpxe/src/arch/i386/include/bits/uuid.h [deleted file]
gpxe/src/arch/i386/include/smbios.h
gpxe/src/core/main.c
gpxe/src/core/settings.c
gpxe/src/hci/shell_banner.c
gpxe/src/include/gpxe/dhcp.h
gpxe/src/include/gpxe/settings.h
gpxe/src/include/gpxe/uuid.h
gpxe/src/net/dhcppkt.c
gpxe/src/net/fakedhcp.c
gpxe/src/net/tcp/http.c
gpxe/src/net/udp/dhcp.c

index 4d710d6..aa4f3f3 100644 (file)
 
 #include <stdint.h>
 #include <string.h>
-#include <stdio.h>
 #include <errno.h>
+#include <assert.h>
 #include <gpxe/uaccess.h>
-#include <gpxe/uuid.h>
 #include <realmode.h>
 #include <pnpbios.h>
 #include <smbios.h>
@@ -51,7 +50,7 @@ struct smbios_entry {
        /** Checksum */
        uint8_t checksum;
        /** Length */
-       uint8_t length;
+       uint8_t len;
        /** Major version */
        uint8_t major;
        /** Minor version */
@@ -67,7 +66,7 @@ struct smbios_entry {
        /** DMI checksum */
        uint8_t dmi_checksum;
        /** Structure table length */
-       uint16_t smbios_length;
+       uint16_t smbios_len;
        /** Structure table address */
        physaddr_t smbios_address;
        /** Number of SMBIOS structures */
@@ -86,49 +85,37 @@ struct smbios {
        /** Start of SMBIOS structures */
        userptr_t address;
        /** Length of SMBIOS structures */ 
-       size_t length;
+       size_t len;
        /** Number of SMBIOS structures */
        unsigned int count;
 };
 
-/**
- * SMBIOS strings descriptor
- *
- * This is returned as part of the search for an SMBIOS structure, and
- * contains the information needed for extracting the strings within
- * the "unformatted" portion of the structure.
- */
-struct smbios_strings {
-       /** Start of strings data */
-       userptr_t data;
-       /** Length of strings data */
-       size_t length;
+/** SMBIOS entry point descriptor */
+static struct smbios smbios = {
+       .address = UNULL,
 };
 
 /**
  * Find SMBIOS
  *
- * @ret smbios         SMBIOS entry point descriptor, or NULL if not found
+ * @ret rc             Return status code
  */
-static struct smbios * find_smbios ( void ) {
-       static struct smbios smbios = {
-               .address = UNULL,
-       };
+static int find_smbios ( void ) {
        union {
                struct smbios_entry entry;
                uint8_t bytes[256]; /* 256 is maximum length possible */
        } u;
-       unsigned int offset;
+       static unsigned int offset = 0;
        size_t len;
        unsigned int i;
        uint8_t sum;
 
-       /* Return cached result if available */
+       /* Return cached result if avaiable */
        if ( smbios.address != UNULL )
-               return &smbios;
+               return 0;
 
        /* Try to find SMBIOS */
-       for ( offset = 0 ; offset < 0x10000 ; offset += 0x10 ) {
+       for ( ; offset < 0x10000 ; offset += 0x10 ) {
 
                /* Read start of header and verify signature */
                copy_from_real ( &u.entry, BIOS_SEG, offset,
@@ -137,7 +124,7 @@ static struct smbios * find_smbios ( void ) {
                        continue;
 
                /* Read whole header and verify checksum */
-               len = u.entry.length;
+               len = u.entry.len;
                copy_from_real ( &u.bytes, BIOS_SEG, offset, len );
                for ( i = 0 , sum = 0 ; i < len ; i++ ) {
                        sum += u.bytes[i];
@@ -152,29 +139,27 @@ static struct smbios * find_smbios ( void ) {
                DBG ( "Found SMBIOS entry point at %04x:%04x\n",
                      BIOS_SEG, offset );
                smbios.address = phys_to_user ( u.entry.smbios_address );
-               smbios.length = u.entry.smbios_length;
+               smbios.len = u.entry.smbios_len;
                smbios.count = u.entry.smbios_count;
-               return &smbios;
+               return 0;
        }
 
        DBG ( "No SMBIOS found\n" );
-       return NULL;
+       return -ENODEV;
 }
 
 /**
  * Find SMBIOS strings terminator
  *
- * @v smbios           SMBIOS entry point descriptor
  * @v offset           Offset to start of strings
  * @ret offset         Offset to strings terminator, or 0 if not found
  */
-static size_t find_strings_terminator ( struct smbios *smbios,
-                                       size_t offset ) {
-       size_t max_offset = ( smbios->length - 2 );
+static size_t find_strings_terminator ( size_t offset ) {
+       size_t max_offset = ( smbios.len - 2 );
        uint16_t nulnul;
 
        for ( ; offset <= max_offset ; offset++ ) {
-               copy_from_user ( &nulnul, smbios->address, offset, 2 );
+               copy_from_user ( &nulnul, smbios.address, offset, 2 );
                if ( nulnul == 0 )
                        return ( offset + 1 );
        }
@@ -185,66 +170,52 @@ static size_t find_strings_terminator ( struct smbios *smbios,
  * Find specific structure type within SMBIOS
  *
  * @v type             Structure type to search for
- * @v structure                Buffer to fill in with structure
- * @v length           Length of buffer
- * @v strings          Strings descriptor to fill in, or NULL
+ * @v structure                SMBIOS structure descriptor to fill in
  * @ret rc             Return status code
  */
-int find_smbios_structure ( unsigned int type, void *structure,
-                           size_t length, struct smbios_strings *strings ) {
-       struct smbios *smbios;
-       struct smbios_header header;
-       struct smbios_strings temp_strings;
+int find_smbios_structure ( unsigned int type,
+                           struct smbios_structure *structure ) {
        unsigned int count = 0;
        size_t offset = 0;
        size_t strings_offset;
        size_t terminator_offset;
+       int rc;
 
-       /* Locate SMBIOS entry point */
-       if ( ! ( smbios = find_smbios() ) )
-               return -ENOENT;
-
-       /* Ensure that we have a usable strings descriptor buffer */
-       if ( ! strings )
-               strings = &temp_strings;
+       /* Find SMBIOS */
+       if ( ( rc = find_smbios() ) != 0 )
+               return rc;
 
        /* Scan through list of structures */
-       while ( ( ( offset + sizeof ( header ) ) < smbios->length ) &&
-               ( count < smbios->count ) ) {
+       while ( ( ( offset + sizeof ( structure->header ) ) < smbios.len )
+               && ( count < smbios.count ) ) {
 
                /* Read next SMBIOS structure header */
-               copy_from_user ( &header, smbios->address, offset,
-                                sizeof ( header ) );
+               copy_from_user ( &structure->header, smbios.address, offset,
+                                sizeof ( structure->header ) );
 
                /* Determine start and extent of strings block */
-               strings_offset = ( offset + header.length );
-               if ( strings_offset > smbios->length ) {
+               strings_offset = ( offset + structure->header.len );
+               if ( strings_offset > smbios.len ) {
                        DBG ( "SMBIOS structure at offset %zx with length "
                              "%x extends beyond SMBIOS\n", offset,
-                             header.length );
+                             structure->header.len );
                        return -ENOENT;
                }
-               terminator_offset =
-                       find_strings_terminator ( smbios, strings_offset );
+               terminator_offset = find_strings_terminator ( strings_offset );
                if ( ! terminator_offset ) {
                        DBG ( "SMBIOS structure at offset %zx has "
                              "unterminated strings section\n", offset );
                        return -ENOENT;
                }
-               strings->data = userptr_add ( smbios->address,
-                                             strings_offset );
-               strings->length = ( terminator_offset - strings_offset );
+               structure->strings_len = ( terminator_offset - strings_offset);
 
-               DBG ( "SMBIOS structure at offset %zx has type %d, "
-                     "length %x, strings length %zx\n",
-                     offset, header.type, header.length, strings->length );
+               DBG ( "SMBIOS structure at offset %zx has type %d, length %x, "
+                     "strings length %zx\n", offset, structure->header.type,
+                     structure->header.len, structure->strings_len );
 
                /* If this is the structure we want, return */
-               if ( header.type == type ) {
-                       if ( length > header.length )
-                               length = header.length;
-                       copy_from_user ( structure, smbios->address,
-                                        offset, length );
+               if ( structure->header.type == type ) {
+                       structure->offset = offset;
                        return 0;
                }
 
@@ -257,67 +228,65 @@ int find_smbios_structure ( unsigned int type, void *structure,
        return -ENOENT;
 }
 
+/**
+ * Copy SMBIOS structure
+ *
+ * @v structure                SMBIOS structure descriptor
+ * @v data             Buffer to hold SMBIOS structure
+ * @v len              Length of buffer
+ * @ret rc             Return status code
+ */
+int read_smbios_structure ( struct smbios_structure *structure,
+                           void *data, size_t len ) {
+
+       assert ( smbios.address != UNULL );
+
+       if ( len > structure->header.len )
+               len = structure->header.len;
+       copy_from_user ( data, smbios.address, structure->offset, len );
+       return 0;
+}
+
 /**
  * Find indexed string within SMBIOS structure
  *
- * @v strings          SMBIOS strings descriptor
+ * @v structure                SMBIOS structure descriptor
  * @v index            String index
- * @v buffer           Buffer for string
- * @v length           Length of string buffer
- * @ret rc             Return status code
+ * @v data             Buffer for string
+ * @v len              Length of string buffer
+ * @ret rc             Length of string, or negative error
  */
-int find_smbios_string ( struct smbios_strings *strings, unsigned int index,
-                        char *buffer, size_t length ) {
-       size_t offset = 0;
+int read_smbios_string ( struct smbios_structure *structure,
+                        unsigned int index, void *data, size_t len ) {
+       size_t strings_start = ( structure->offset + structure->header.len );
+       size_t strings_end = ( strings_start + structure->strings_len );
+       size_t offset;
        size_t string_len;
 
-       /* Zero buffer.  This ensures that a valid NUL terminator is
-        * always present (unless length==0).
-        */
-       memset ( buffer, 0, length );
-          
+       assert ( smbios.address != UNULL );
+
        /* String numbers start at 1 (0 is used to indicate "no string") */
        if ( ! index )
-               return 0;
+               return -ENOENT;
 
-       while ( offset < strings->length ) {
+       for ( offset = strings_start ; offset < strings_end ;
+             offset += ( string_len + 1 ) ) {
                /* Get string length.  This is known safe, since the
                 * smbios_strings struct is constructed so as to
                 * always end on a string boundary.
                 */
-               string_len = strlen_user ( strings->data, offset );
+               string_len = strlen_user ( smbios.address,
+                                          ( structure->offset + offset ) );
                if ( --index == 0 ) {
                        /* Copy string, truncating as necessary. */
-                       if ( string_len >= length )
-                               string_len = ( length - 1 );
-                       copy_from_user ( buffer, strings->data,
-                                        offset, string_len );
-                       return 0;
+                       if ( len > string_len )
+                               len = string_len;
+                       copy_from_user ( data, smbios.address,
+                                        ( structure->offset + offset ), len );
+                       return string_len;
                }
-               offset += ( string_len + 1 );
        }
 
        DBG ( "SMBIOS string index %d not found\n", index );
        return -ENOENT;
 }
-
-/**
- * Get UUID from SMBIOS
- *
- * @v uuid             UUID to fill in
- * @ret rc             Return status code
- */
-int smbios_get_uuid ( union uuid *uuid ) {
-       struct smbios_system_information sysinfo;
-       int rc;
-
-       if ( ( rc = find_smbios_structure ( SMBIOS_TYPE_SYSTEM_INFORMATION,
-                                           &sysinfo, sizeof ( sysinfo ),
-                                           NULL ) ) != 0 )
-               return rc;
-
-       memcpy ( uuid, sysinfo.uuid, sizeof ( *uuid ) );
-       DBG ( "SMBIOS found UUID %s\n", uuid_ntoa ( uuid ) );
-
-       return 0;
-}
diff --git a/gpxe/src/arch/i386/firmware/pcbios/smbios_settings.c b/gpxe/src/arch/i386/firmware/pcbios/smbios_settings.c
new file mode 100644 (file)
index 0000000..de08ec5
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2008 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 <string.h>
+#include <errno.h>
+#include <gpxe/settings.h>
+#include <gpxe/init.h>
+#include <gpxe/uuid.h>
+#include <smbios.h>
+
+/**
+ * Construct SMBIOS raw-data tag
+ *
+ * @v _type            SMBIOS structure type number
+ * @v _structure       SMBIOS structure data type
+ * @v _field           Field within SMBIOS structure data type
+ * @ret tag            SMBIOS setting tag
+ */
+#define SMBIOS_RAW_TAG( _type, _structure, _field )            \
+       ( ( (_type) << 16 ) |                                   \
+         ( offsetof ( _structure, _field ) << 8 ) |            \
+         ( sizeof ( ( ( _structure * ) 0 )->_field ) ) )
+
+/**
+ * Construct SMBIOS string tag
+ *
+ * @v _type            SMBIOS structure type number
+ * @v _structure       SMBIOS structure data type
+ * @v _field           Field within SMBIOS structure data type
+ * @ret tag            SMBIOS setting tag
+ */
+#define SMBIOS_STRING_TAG( _type, _structure, _field )         \
+       ( ( (_type) << 16 ) |                                   \
+         ( offsetof ( _structure, _field ) << 8 ) )
+
+/**
+ * Store value of SMBIOS setting
+ *
+ * @v settings         Settings block
+ * @v setting          Setting to store
+ * @v data             Setting data, or NULL to clear setting
+ * @v len              Length of setting data
+ * @ret rc             Return status code
+ */
+static int smbios_store ( struct settings *settings __unused,
+                         struct setting *setting __unused,
+                         const void *data __unused, size_t len __unused ) {
+       /* Cannot write data into SMBIOS */
+       return -ENOTSUP;
+}
+
+/**
+ * Fetch value of SMBIOS setting
+ *
+ * @v settings         Settings block, or NULL to search all blocks
+ * @v setting          Setting to fetch
+ * @v data             Buffer to fill with setting data
+ * @v len              Length of buffer
+ * @ret len            Length of setting data, or negative error
+ */
+static int smbios_fetch ( struct settings *settings __unused,
+                         struct setting *setting,
+                         void *data, size_t len ) {
+       struct smbios_structure structure;
+       unsigned int tag_type;
+       unsigned int tag_offset;
+       unsigned int tag_len;
+       int rc;
+
+       /* Split tag into type, offset and length */
+       tag_type = ( setting->tag >> 16 );
+       tag_offset = ( ( setting->tag >> 8 ) & 0xff );
+       tag_len = ( setting->tag & 0xff );
+       if ( ! tag_type )
+               return -ENOENT;
+
+       /* Find SMBIOS structure */
+       if ( ( rc = find_smbios_structure ( tag_type, &structure ) ) != 0 )
+               return rc;
+
+       {
+               uint8_t buf[structure.header.len];
+
+               /* Read SMBIOS structure */
+               if ( ( rc = read_smbios_structure ( &structure, buf,
+                                                   sizeof ( buf ) ) ) != 0 )
+                       return rc;
+
+               if ( tag_len == 0 ) {
+                       /* String */
+                       return read_smbios_string ( &structure,
+                                                   buf[tag_offset],
+                                                   data, len );
+               } else {
+                       /* Raw data */
+                       if ( len > tag_len )
+                               len = tag_len;
+                       memcpy ( data, &buf[tag_offset], len );
+                       return tag_len;
+               }
+       }
+}
+
+/** SMBIOS settings operations */
+static struct settings_operations smbios_settings_operations = {
+       .store = smbios_store,
+       .fetch = smbios_fetch,
+};
+
+/** SMBIOS settings */
+static struct settings smbios_settings = {
+       .refcnt = NULL,
+       .name = "smbios",
+       .siblings = LIST_HEAD_INIT ( smbios_settings.siblings ),
+       .children = LIST_HEAD_INIT ( smbios_settings.children ),
+       .op = &smbios_settings_operations,
+};
+
+/** Initialise SMBIOS settings */
+static void smbios_init ( void ) {
+       int rc;
+
+       if ( ( rc = register_settings ( &smbios_settings, NULL ) ) != 0 ) {
+               DBG ( "SMBIOS could not register settings: %s\n",
+                     strerror ( rc ) );
+               return;
+       }
+}
+
+/** SMBIOS settings initialiser */
+struct init_fn smbios_init_fn __init_fn ( INIT_NORMAL ) = {
+       .initialise = smbios_init,
+};
+
+/** UUID setting obtained via SMBIOS */
+struct setting uuid_setting __setting = {
+       .name = "uuid",
+       .description = "UUID",
+       .tag = SMBIOS_RAW_TAG ( SMBIOS_TYPE_SYSTEM_INFORMATION,
+                               struct smbios_system_information, uuid ),
+       .type = &setting_type_uuid,
+};
index 0f14021..678be04 100644 (file)
@@ -12,6 +12,7 @@
 #define ERRFILE_smbios         ( ERRFILE_ARCH | ERRFILE_CORE | 0x00030000 )
 #define ERRFILE_biosint                ( ERRFILE_ARCH | ERRFILE_CORE | 0x00040000 )
 #define ERRFILE_int13          ( ERRFILE_ARCH | ERRFILE_CORE | 0x00050000 )
+#define ERRFILE_smbios_settings        ( ERRFILE_ARCH | ERRFILE_CORE | 0x00060000 )
 
 #define ERRFILE_bootsector     ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00000000 )
 #define ERRFILE_bzimage               ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00010000 )
diff --git a/gpxe/src/arch/i386/include/bits/uuid.h b/gpxe/src/arch/i386/include/bits/uuid.h
deleted file mode 100644 (file)
index 0cbd320..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef _I386_UUID_H
-#define _I386_UUID_H
-
-#include <smbios.h>
-
-static inline int get_uuid ( union uuid *uuid ) {
-       return smbios_get_uuid ( uuid );
-}
-
-#endif /* _I386_UUID_H */
index 821eda1..f2736dc 100644 (file)
@@ -13,11 +13,21 @@ struct smbios_header {
        /** Type */
        uint8_t type;
        /** Length */
-       uint8_t length;
+       uint8_t len;
        /** Handle */
        uint16_t handle;
 } __attribute__ (( packed ));
 
+/** SMBIOS structure descriptor */
+struct smbios_structure {
+       /** Copy of SMBIOS structure header */
+       struct smbios_header header;
+       /** Offset of structure within SMBIOS */
+       size_t offset;
+       /** Length of strings section */
+       size_t strings_len;
+};
+
 /** SMBIOS system information structure */
 struct smbios_system_information {
        /** SMBIOS structure header */
@@ -39,13 +49,12 @@ struct smbios_system_information {
 /** SMBIOS system information structure type */
 #define SMBIOS_TYPE_SYSTEM_INFORMATION 1
 
-struct smbios_strings;
 extern int find_smbios_structure ( unsigned int type,
-                                  void *structure, size_t length,
-                                  struct smbios_strings *strings );
-extern int find_smbios_string ( struct smbios_strings *strings,
+                                  struct smbios_structure *structure );
+extern int read_smbios_structure ( struct smbios_structure *structure,
+                                  void *data, size_t len );
+extern int read_smbios_string ( struct smbios_structure *structure,
                                unsigned int index,
-                               char *buffer, size_t length );
-extern int smbios_get_uuid ( union uuid *uuid );
+                               void *data, size_t len );
 
 #endif /* _SMBIOS_H */
index 3295fea..ca62db2 100644 (file)
@@ -14,26 +14,54 @@ Literature dealing with the network protocols:
 
 **************************************************************************/
 
+#include <stdio.h>
 #include <gpxe/init.h>
+#include <gpxe/features.h>
 #include <gpxe/shell.h>
 #include <gpxe/shell_banner.h>
 #include <usr/autoboot.h>
 
+#define NORMAL "\033[0m"
+#define BOLD   "\033[1m"
+#define CYAN   "\033[36m"
+
+static struct feature features[0] __table_start ( struct feature, features );
+static struct feature features_end[0] __table_end ( struct feature, features );
+
 /**
  * Main entry point
  *
  * @ret rc             Return status code
  */
 __cdecl int main ( void ) {
+       struct feature *feature;
 
        initialise();
        startup();
 
-       if ( shell_banner() )
+       /* Print welcome banner */
+       printf ( NORMAL "\n\n\n" BOLD "gPXE " VERSION
+                NORMAL " -- Open Source Boot Firmware -- "
+                CYAN "http://etherboot.org" NORMAL "\n"
+                "Features:" );
+       for ( feature = features ; feature < features_end ; feature++ )
+               printf ( " %s", feature->name );
+       printf ( "\n" );
+
+       /* Prompt for shell */
+       if ( shell_banner() ) {
+               /* User wants shell; just give them a shell */
                shell();
-       else
+       } else {
+               /* User doesn't want shell; try booting.  If booting
+                * fails, offer a second chance to enter the shell for
+                * diagnostics.
+                */
                autoboot();
-       
+               if ( shell_banner() )
+                       shell();
+       }
+
        shutdown();
 
        return 0;
index b793ae6..7525318 100644 (file)
@@ -27,6 +27,7 @@
 #include <gpxe/in.h>
 #include <gpxe/vsprintf.h>
 #include <gpxe/dhcp.h>
+#include <gpxe/uuid.h>
 #include <gpxe/settings.h>
 
 /** @file
@@ -233,6 +234,10 @@ struct settings * find_child_settings ( struct settings *parent,
        struct settings *settings;
        size_t len;
 
+       /* NULL parent => add to settings root */
+       if ( parent == NULL )
+               parent = &settings_root;
+
        /* Look for a child whose name matches the initial component */
        list_for_each_entry ( settings, &parent->children, siblings ) {
                len = strlen ( settings->name );
@@ -483,6 +488,26 @@ unsigned long fetch_uintz_setting ( struct settings *settings,
        return value;
 }
 
+/**
+ * Fetch value of UUID setting
+ *
+ * @v settings         Settings block, or NULL to search all blocks
+ * @v setting          Setting to fetch
+ * @v uuid             UUID to fill in
+ * @ret len            Length of setting, or negative error
+ */
+int fetch_uuid_setting ( struct settings *settings, struct setting *setting,
+                        union uuid *uuid ) {
+       int len;
+
+       len = fetch_setting ( settings, setting, uuid, sizeof ( *uuid ) );
+       if ( len < 0 )
+               return len;
+       if ( len != sizeof ( *uuid ) )
+               return -ERANGE;
+       return len;
+}
+
 /**
  * Compare two settings
  *
@@ -748,11 +773,11 @@ static int storef_ipv4 ( struct settings *settings, struct setting *setting,
 static int fetchf_ipv4 ( struct settings *settings, struct setting *setting,
                         char *buf, size_t len ) {
        struct in_addr ipv4;
-       int rc;
+       int raw_len;
 
-       if ( ( rc = fetch_ipv4_setting ( settings, setting, &ipv4 ) ) < 0 )
-               return rc;
-       return snprintf ( buf, len, inet_ntoa ( ipv4 ) );
+       if ( ( raw_len = fetch_ipv4_setting ( settings, setting, &ipv4 ) ) < 0)
+               return raw_len;
+       return snprintf ( buf, len, "%s", inet_ntoa ( ipv4 ) );
 }
 
 /** An IPv4 address setting type */
@@ -981,6 +1006,46 @@ struct setting_type setting_type_hex __setting_type = {
        .fetchf = fetchf_hex,
 };
 
+/**
+ * Parse and store value of UUID setting
+ *
+ * @v settings         Settings block
+ * @v setting          Setting to store
+ * @v value            Formatted setting data
+ * @ret rc             Return status code
+ */
+static int storef_uuid ( struct settings *settings __unused,
+                        struct setting *setting __unused,
+                        const char *value __unused ) {
+       return -ENOTSUP;
+}
+
+/**
+ * Fetch and format value of UUID setting
+ *
+ * @v settings         Settings block, or NULL to search all blocks
+ * @v setting          Setting to fetch
+ * @v buf              Buffer to contain formatted value
+ * @v len              Length of buffer
+ * @ret len            Length of formatted value, or negative error
+ */
+static int fetchf_uuid ( struct settings *settings, struct setting *setting,
+                        char *buf, size_t len ) {
+       union uuid uuid;
+       int raw_len;
+
+       if ( ( raw_len = fetch_uuid_setting ( settings, setting, &uuid ) ) < 0)
+               return raw_len;
+       return snprintf ( buf, len, "%s", uuid_ntoa ( &uuid ) );
+}
+
+/** UUID setting type */
+struct setting_type setting_type_uuid __setting_type = {
+       .name = "uuid",
+       .storef = storef_uuid,
+       .fetchf = fetchf_uuid,
+};
+
 /******************************************************************************
  *
  * Settings
index 62da487..92cd17d 100644 (file)
@@ -18,7 +18,6 @@
 
 #include <stdio.h>
 #include <console.h>
-#include <gpxe/features.h>
 #include <gpxe/timer.h>
 #include <gpxe/shell_banner.h>
 
 
 #define BANNER_TIMEOUT ( 2 * TICKS_PER_SEC )
 
-#define NORMAL "\033[0m"
-#define BOLD   "\033[1m"
-#define CYAN   "\033[36m"
-
-static struct feature features[0] __table_start ( struct feature, features );
-static struct feature features_end[0] __table_end ( struct feature, features );
-
 /**
  * Print shell banner and prompt for shell entry
  *
@@ -44,18 +36,9 @@ static struct feature features_end[0] __table_end ( struct feature, features );
  */
 int shell_banner ( void ) {
        unsigned long timeout = ( currticks() + BANNER_TIMEOUT );
-       struct feature *feature;
-       int key;
        int enter_shell = 0;
+       int key;
 
-       /* Print welcome banner */
-       printf ( NORMAL "\n\n\n" BOLD "gPXE " VERSION
-                NORMAL " -- Open Source Boot Firmware -- "
-                CYAN "http://etherboot.org" NORMAL "\n"
-                "Features:" );
-       for ( feature = features ; feature < features_end ; feature++ ) {
-               printf ( " %s", feature->name );
-       }
        printf ( "\nPress Ctrl-B for the gPXE command line..." );
 
        /* Wait for key */
index 94cc201..6144597 100644 (file)
@@ -24,6 +24,9 @@ struct dhcp_packet;
 /** BOOTP/DHCP client port */
 #define BOOTPC_PORT 68
 
+/** ProxyDHCP server port */
+#define PROXYDHCP_PORT 4011
+
 /** Construct a tag value for an encapsulated option
  *
  * This tag value can be passed to Etherboot functions when searching
@@ -433,7 +436,7 @@ struct dhcphdr {
  */
 #define DHCP_MIN_LEN 552
 
-/** Maximum time that we will wait for ProxyDHCP offers */
+/** Maximum time that we will wait for ProxyDHCP responses */
 #define PROXYDHCP_WAIT_TIME ( TICKS_PER_SEC * 1 )
 
 /** Settings block name used for DHCP responses */
@@ -442,12 +445,13 @@ struct dhcphdr {
 /** Settings block name used for ProxyDHCP responses */
 #define PROXYDHCP_SETTINGS_NAME "proxydhcp"
 
-extern int create_dhcp_packet ( struct dhcp_packet *dhcppkt,
+extern int dhcp_create_packet ( struct dhcp_packet *dhcppkt,
                                struct net_device *netdev, uint8_t msgtype,
                                struct dhcp_options *options, 
                                void *data, size_t max_len );
-extern int create_dhcp_request ( struct dhcp_packet *dhcppkt,
+extern int dhcp_create_request ( struct dhcp_packet *dhcppkt,
                                 struct net_device *netdev,
+                                struct in_addr ciaddr,
                                 struct dhcp_packet *dhcpoffer,
                                 void *data, size_t max_len );
 extern int start_dhcp ( struct job_interface *job, struct net_device *netdev );
index 4082569..ae5a259 100644 (file)
@@ -15,6 +15,7 @@
 
 struct settings;
 struct in_addr;
+union uuid;
 
 /** A setting */
 struct setting {
@@ -177,6 +178,8 @@ extern long fetch_intz_setting ( struct settings *settings,
                                 struct setting *setting );
 extern unsigned long fetch_uintz_setting ( struct settings *settings,
                                           struct setting *setting );
+extern int fetch_uuid_setting ( struct settings *settings,
+                               struct setting *setting, union uuid *uuid );
 extern int setting_cmp ( struct setting *a, struct setting *b );
 
 extern struct settings * find_child_settings ( struct settings *parent,
@@ -198,6 +201,7 @@ extern struct setting_type setting_type_uint8 __setting_type;
 extern struct setting_type setting_type_uint16 __setting_type;
 extern struct setting_type setting_type_uint32 __setting_type;
 extern struct setting_type setting_type_hex __setting_type;
+extern struct setting_type setting_type_uuid __setting_type;
 
 extern struct setting ip_setting __setting;
 extern struct setting netmask_setting __setting;
@@ -210,6 +214,7 @@ extern struct setting username_setting __setting;
 extern struct setting password_setting __setting;
 extern struct setting priority_setting __setting;
 extern struct setting bios_drive_setting __setting;
+extern struct setting uuid_setting __setting;
 
 /**
  * Initialise a settings block
index a62735c..4f89be5 100644 (file)
@@ -8,9 +8,6 @@
 
 #include <stdint.h>
 
-union uuid;
-#include <bits/uuid.h>
-
 /** A universally unique ID */
 union uuid {
        /** Canonical form (00000000-0000-0000-0000-000000000000) */
index 1cf99d8..2537e25 100644 (file)
  *
  */
 
+/**
+ * Calculate used length of an IPv4 field within a DHCP packet
+ *
+ * @v data             Field data
+ * @v len              Length of field
+ * @ret used           Used length of field
+ */
+static size_t used_len_ipv4 ( const void *data, size_t len __unused ) {
+       const struct in_addr *in = data;
+
+       return ( in->s_addr ? sizeof ( *in ) : 0 );
+}
+
+/**
+ * Calculate used length of a string field within a DHCP packet
+ *
+ * @v data             Field data
+ * @v len              Length of field
+ * @ret used           Used length of field
+ */
+static size_t used_len_string ( const void *data, size_t len ) {
+       return strnlen ( data, len );
+}
+
 /** A dedicated field within a DHCP packet */
 struct dhcp_packet_field {
        /** Settings tag number */
@@ -40,25 +64,34 @@ struct dhcp_packet_field {
        uint16_t offset;
        /** Length of field */
        uint16_t len;
+       /** Calculate used length of field
+        *
+        * @v data      Field data
+        * @v len       Length of field
+        * @ret used    Used length of field
+        */
+       size_t ( * used_len ) ( const void *data, size_t len );
 };
 
 /** Declare a dedicated field within a DHCP packet
  *
  * @v _tag             Settings tag number
  * @v _field           Field name
+ * @v _used_len                Function to calculate used length of field
  */
-#define DHCP_PACKET_FIELD( _tag, _field ) {                            \
+#define DHCP_PACKET_FIELD( _tag, _field, _used_len ) {                 \
                .tag = (_tag),                                          \
                .offset = offsetof ( struct dhcphdr, _field ),          \
                .len = sizeof ( ( ( struct dhcphdr * ) 0 )->_field ),   \
+               .used_len = _used_len,                                  \
        }
 
 /** Dedicated fields within a DHCP packet */
 static struct dhcp_packet_field dhcp_packet_fields[] = {
-       DHCP_PACKET_FIELD ( DHCP_EB_YIADDR, yiaddr ),
-       DHCP_PACKET_FIELD ( DHCP_EB_SIADDR, siaddr ),
-       DHCP_PACKET_FIELD ( DHCP_TFTP_SERVER_NAME, sname ),
-       DHCP_PACKET_FIELD ( DHCP_BOOTFILE_NAME, file ),
+       DHCP_PACKET_FIELD ( DHCP_EB_YIADDR, yiaddr, used_len_ipv4 ),
+       DHCP_PACKET_FIELD ( DHCP_EB_SIADDR, siaddr, used_len_ipv4 ),
+       DHCP_PACKET_FIELD ( DHCP_TFTP_SERVER_NAME, sname, used_len_string ),
+       DHCP_PACKET_FIELD ( DHCP_BOOTFILE_NAME, file, used_len_string ),
 };
 
 /**
@@ -138,14 +171,19 @@ int dhcppkt_store ( struct dhcp_packet *dhcppkt, unsigned int tag,
 int dhcppkt_fetch ( struct dhcp_packet *dhcppkt, unsigned int tag,
                    void *data, size_t len ) {
        struct dhcp_packet_field *field;
+       void *field_data;
+       size_t field_len;
        
        /* If this is a special field, return it */
        if ( ( field = find_dhcp_packet_field ( tag ) ) != NULL ) {
-               if ( len > field->len )
-                       len = field->len;
-               memcpy ( data,
-                        dhcp_packet_field ( dhcppkt->dhcphdr, field ), len );
-               return field->len;
+               field_data = dhcp_packet_field ( dhcppkt->dhcphdr, field );
+               field_len = field->used_len ( field_data, field->len );
+               if ( ! field_len )
+                       return -ENOENT;
+               if ( len > field_len )
+                       len = field_len;
+               memcpy ( data, field_data, len );
+               return field_len;
        }
 
        /* Otherwise, use the generic options block */
index c3054db..a10e442 100644 (file)
@@ -108,9 +108,10 @@ static int copy_settings ( struct dhcp_packet *dest,
 int create_fakedhcpdiscover ( struct net_device *netdev,
                              void *data, size_t max_len ) {
        struct dhcp_packet dhcppkt;
+       struct in_addr ciaddr = { 0 };
        int rc;
 
-       if ( ( rc = create_dhcp_request ( &dhcppkt, netdev, NULL, data,
+       if ( ( rc = dhcp_create_request ( &dhcppkt, netdev, ciaddr, NULL, data,
                                          max_len ) ) != 0 ) {
                DBG ( "Could not create DHCPDISCOVER: %s\n",
                      strerror ( rc ) );
@@ -136,7 +137,7 @@ int create_fakedhcpack ( struct net_device *netdev,
        int rc;
 
        /* Create base DHCPACK packet */
-       if ( ( rc = create_dhcp_packet ( &dhcppkt, netdev, DHCPACK, NULL,
+       if ( ( rc = dhcp_create_packet ( &dhcppkt, netdev, DHCPACK, NULL,
                                         data, max_len ) ) != 0 ) {
                DBG ( "Could not create DHCPACK: %s\n", strerror ( rc ) );
                return rc;
@@ -187,7 +188,7 @@ int create_fakeproxydhcpack ( struct net_device *netdev,
        }
 
        /* Create base DHCPACK packet */
-       if ( ( rc = create_dhcp_packet ( &dhcppkt, netdev, DHCPACK, NULL,
+       if ( ( rc = dhcp_create_packet ( &dhcppkt, netdev, DHCPACK, NULL,
                                         data, max_len ) ) != 0 ) {
                DBG ( "Could not create ProxyDHCPACK: %s\n",
                      strerror ( rc ) );
index db92e9e..4dc1ab7 100644 (file)
@@ -393,7 +393,7 @@ static void http_step ( struct process *process ) {
        if ( xfer_window ( &http->socket ) ) {
                process_del ( &http->process );
                if ( ( rc = xfer_printf ( &http->socket,
-                                         "GET %s%s%s HTTP/1.1\r\n"
+                                         "GET %s%s%s HTTP/1.0\r\n"
                                          "User-Agent: gPXE/" VERSION "\r\n"
                                          "Host: %s\r\n"
                                          "\r\n",
index 3961c61..ecb7378 100644 (file)
@@ -92,6 +92,34 @@ static struct dhcp_options dhcp_request_options = {
 static uint8_t dhcp_features[0] __table_start ( uint8_t, dhcp_features );
 static uint8_t dhcp_features_end[0] __table_end ( uint8_t, dhcp_features );
 
+/** DHCP network device descriptor */
+struct dhcp_netdev_desc {
+       /** Bus type ID */
+       uint8_t type;
+       /** Vendor ID */
+       uint16_t vendor;
+       /** Device ID */
+       uint16_t device;
+} __attribute__ (( packed ));
+
+/** DHCP client identifier */
+struct dhcp_client_id {
+       /** Link-layer protocol */
+       uint8_t ll_proto;
+       /** Link-layer address */
+       uint8_t ll_addr[MAX_LL_ADDR_LEN];
+} __attribute__ (( packed ));
+
+/** DHCP client UUID */
+struct dhcp_client_uuid {
+       /** Identifier type */
+       uint8_t type;
+       /** UUID */
+       union uuid uuid;
+} __attribute__ (( packed ));
+
+#define DHCP_CLIENT_UUID_TYPE 0
+
 /**
  * Name a DHCP packet type
  *
@@ -130,208 +158,6 @@ static uint32_t dhcp_xid ( struct net_device *netdev ) {
        return xid;
 }
 
-/**
- * Create a DHCP packet
- *
- * @v dhcppkt          DHCP packet structure to fill in
- * @v netdev           Network device
- * @v msgtype          DHCP message type
- * @v options          Initial options to include (or NULL)
- * @v data             Buffer for DHCP packet
- * @v max_len          Size of DHCP packet buffer
- * @ret rc             Return status code
- *
- * Creates a DHCP packet in the specified buffer, and fills out a @c
- * dhcp_packet structure that can be passed to
- * set_dhcp_packet_option() or copy_dhcp_packet_options().
- */
-int create_dhcp_packet ( struct dhcp_packet *dhcppkt,
-                        struct net_device *netdev, uint8_t msgtype,
-                        struct dhcp_options *options, 
-                        void *data, size_t max_len ) {
-       struct dhcphdr *dhcphdr = data;
-       size_t options_len;
-       unsigned int hlen;
-       int rc;
-
-       /* Sanity check */
-       options_len = ( options ? options->len : 0 );
-       if ( max_len < ( sizeof ( *dhcphdr ) + options_len ) )
-               return -ENOSPC;
-
-       /* Initialise DHCP packet content */
-       memset ( dhcphdr, 0, max_len );
-       dhcphdr->xid = dhcp_xid ( netdev );
-       dhcphdr->magic = htonl ( DHCP_MAGIC_COOKIE );
-       dhcphdr->htype = ntohs ( netdev->ll_protocol->ll_proto );
-       dhcphdr->op = dhcp_op[msgtype];
-       /* If hardware length exceeds the chaddr field length, don't
-        * use the chaddr field.  This is as per RFC4390.
-        */
-       hlen = netdev->ll_protocol->ll_addr_len;
-       if ( hlen > sizeof ( dhcphdr->chaddr ) ) {
-               hlen = 0;
-               dhcphdr->flags = htons ( BOOTP_FL_BROADCAST );
-       }
-       dhcphdr->hlen = hlen;
-       memcpy ( dhcphdr->chaddr, netdev->ll_addr, hlen );
-       memcpy ( dhcphdr->options, options->data, options_len );
-
-       /* Initialise DHCP packet structure */
-       memset ( dhcppkt, 0, sizeof ( *dhcppkt ) );
-       dhcppkt_init ( dhcppkt, data, max_len );
-       
-       /* Set DHCP_MESSAGE_TYPE option */
-       if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_MESSAGE_TYPE,
-                                   &msgtype, sizeof ( msgtype ) ) ) != 0 )
-               return rc;
-
-       return 0;
-}
-
-/** DHCP network device descriptor */
-struct dhcp_netdev_desc {
-       /** Bus type ID */
-       uint8_t type;
-       /** Vendor ID */
-       uint16_t vendor;
-       /** Device ID */
-       uint16_t device;
-} __attribute__ (( packed ));
-
-/** DHCP client identifier */
-struct dhcp_client_id {
-       /** Link-layer protocol */
-       uint8_t ll_proto;
-       /** Link-layer address */
-       uint8_t ll_addr[MAX_LL_ADDR_LEN];
-} __attribute__ (( packed ));
-
-/** DHCP client UUID */
-struct dhcp_client_uuid {
-       /** Identifier type */
-       uint8_t type;
-       /** UUID */
-       union uuid uuid;
-} __attribute__ (( packed ));
-
-#define DHCP_CLIENT_UUID_TYPE 0
-
-/**
- * Create DHCP request packet
- *
- * @v dhcppkt          DHCP packet structure to fill in
- * @v netdev           Network device
- * @v dhcpoffer                DHCPOFFER packet received from server
- * @v data             Buffer for DHCP packet
- * @v max_len          Size of DHCP packet buffer
- * @ret rc             Return status code
- */
-int create_dhcp_request ( struct dhcp_packet *dhcppkt,
-                         struct net_device *netdev,
-                         struct dhcp_packet *dhcpoffer,
-                         void *data, size_t max_len ) {
-       struct device_description *desc = &netdev->dev->desc;
-       struct dhcp_netdev_desc dhcp_desc;
-       struct dhcp_client_id client_id;
-       struct dhcp_client_uuid client_uuid;
-       unsigned int msgtype;
-       size_t dhcp_features_len;
-       size_t ll_addr_len;
-       int rc;
-
-       /* Create DHCP packet */
-       msgtype = ( dhcpoffer ? DHCPREQUEST : DHCPDISCOVER );
-       if ( ( rc = create_dhcp_packet ( dhcppkt, netdev, msgtype,
-                                        &dhcp_request_options, data,
-                                        max_len ) ) != 0 ) {
-               DBG ( "DHCP could not create DHCP packet: %s\n",
-                     strerror ( rc ) );
-               return rc;
-       }
-
-       /* Copy any required options from previous server repsonse */
-       if ( dhcpoffer ) {
-               struct in_addr server_id;
-               struct in_addr requested_ip;
-
-               if ( dhcppkt_fetch ( dhcpoffer, DHCP_SERVER_IDENTIFIER,
-                                    &server_id, sizeof ( server_id ) )
-                    != sizeof ( server_id ) ) {
-                       DBG ( "DHCP offer missing server identifier\n" );
-                       return -EINVAL;
-               }
-               if ( dhcppkt_fetch ( dhcpoffer, DHCP_EB_YIADDR,
-                                    &requested_ip, sizeof ( requested_ip ) )
-                    != sizeof ( requested_ip ) ) {
-                       DBG ( "DHCP offer missing IP address\n" );
-                       return -EINVAL;
-               }
-               if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_SERVER_IDENTIFIER,
-                                           &server_id,
-                                           sizeof ( server_id ) ) ) != 0 ) {
-                       DBG ( "DHCP could not set server identifier: %s\n ",
-                             strerror ( rc ) );
-                       return rc;
-               }
-               if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_REQUESTED_ADDRESS,
-                                           &requested_ip,
-                                           sizeof ( requested_ip ) ) ) != 0 ){
-                       DBG ( "DHCP could not set requested address: %s\n",
-                             strerror ( rc ) );
-                       return rc;
-               }
-       }
-
-       /* Add options to identify the feature list */
-       dhcp_features_len = ( dhcp_features_end - dhcp_features );
-       if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_EB_ENCAP, dhcp_features,
-                                   dhcp_features_len ) ) != 0 ) {
-               DBG ( "DHCP could not set features list option: %s\n",
-                     strerror ( rc ) );
-               return rc;
-       }
-
-       /* Add options to identify the network device */
-       dhcp_desc.type = desc->bus_type;
-       dhcp_desc.vendor = htons ( desc->vendor );
-       dhcp_desc.device = htons ( desc->device );
-       if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_EB_BUS_ID, &dhcp_desc,
-                                   sizeof ( dhcp_desc ) ) ) != 0 ) {
-               DBG ( "DHCP could not set bus ID option: %s\n",
-                     strerror ( rc ) );
-               return rc;
-       }
-
-       /* Add DHCP client identifier.  Required for Infiniband, and
-        * doesn't hurt other link layers.
-        */
-       client_id.ll_proto = ntohs ( netdev->ll_protocol->ll_proto );
-       ll_addr_len = netdev->ll_protocol->ll_addr_len;
-       assert ( ll_addr_len <= sizeof ( client_id.ll_addr ) );
-       memcpy ( client_id.ll_addr, netdev->ll_addr, ll_addr_len );
-       if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_CLIENT_ID, &client_id,
-                                   ( ll_addr_len + 1 ) ) ) != 0 ) {
-               DBG ( "DHCP could not set client ID: %s\n",
-                     strerror ( rc ) );
-               return rc;
-       }
-
-       /* Add client UUID, if we have one.  Required for PXE. */
-       client_uuid.type = DHCP_CLIENT_UUID_TYPE;
-       if ( ( rc = get_uuid ( &client_uuid.uuid ) ) == 0 ) {
-               if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_CLIENT_UUID,
-                                           &client_uuid,
-                                           sizeof ( client_uuid ) ) ) != 0 ) {
-                       DBG ( "DHCP could not set client UUID: %s\n",
-                             strerror ( rc ) );
-                       return rc;
-               }
-       }
-
-       return 0;
-}
-
 /****************************************************************************
  *
  * DHCP settings
@@ -348,12 +174,25 @@ struct dhcp_settings {
        struct settings settings;
 };
 
+/**
+ * Increment reference count on DHCP settings block
+ *
+ * @v dhcpset          DHCP settings block
+ * @ret dhcpset                DHCP settings block
+ */
+static inline __attribute__ (( always_inline )) struct dhcp_settings *
+dhcpset_get ( struct dhcp_settings *dhcpset ) {
+       ref_get ( &dhcpset->refcnt );
+       return dhcpset;
+}
+
 /**
  * Decrement reference count on DHCP settings block
  *
  * @v dhcpset          DHCP settings block
  */
-static inline void dhcpset_put ( struct dhcp_settings *dhcpset ) {
+static inline __attribute__ (( always_inline )) void
+dhcpset_put ( struct dhcp_settings *dhcpset ) {
        ref_put ( &dhcpset->refcnt );
 }
 
@@ -375,7 +214,7 @@ static int dhcpset_store ( struct settings *settings, struct setting *setting,
 }
 
 /**
- * Fetch value of setting
+ * Fetch value of DHCP setting
  *
  * @v settings         Settings block, or NULL to search all blocks
  * @v setting          Setting to fetch
@@ -423,9 +262,34 @@ static struct dhcp_settings * dhcpset_create ( const struct dhcphdr *dhcphdr,
 
 /****************************************************************************
  *
- * DHCP to UDP interface
+ * DHCP session
+ *
+ */
+
+/** DHCP session states */
+enum dhcp_session_state {
+       /** Sending DHCPDISCOVERs, collecting DHCPOFFERs and ProxyDHCPOFFERs */
+       DHCP_STATE_DISCOVER = 0,
+       /** Sending DHCPREQUESTs, waiting for DHCPACK */
+       DHCP_STATE_REQUEST,
+       /** Sending ProxyDHCPREQUESTs, waiting for ProxyDHCPACK */
+       DHCP_STATE_PROXYREQUEST,
+};
+
+/**
+ * Name a DHCP session state
  *
+ * @v state            DHCP session state
+ * @ret string         DHCP session state name
  */
+static inline const char * dhcp_state_name ( enum dhcp_session_state state ) {
+       switch ( state ) {
+       case DHCP_STATE_DISCOVER:       return "DHCPDISCOVER";
+       case DHCP_STATE_REQUEST:        return "DHCPREQUEST";
+       case DHCP_STATE_PROXYREQUEST:   return "ProxyDHCPREQUEST";
+       default:                        return "<invalid>";
+       }
+}
 
 /** A DHCP session */
 struct dhcp_session {
@@ -444,14 +308,14 @@ struct dhcp_session {
         * This is a value for the @c DHCP_MESSAGE_TYPE option
         * (e.g. @c DHCPDISCOVER).
         */
-       int state;
-       /** Response obtained from DHCP server */
-       struct dhcp_settings *response;
-       /** Response obtained from ProxyDHCP server */
-       struct dhcp_settings *proxy_response;
+       enum dhcp_session_state state;
+       /** DHCPOFFER obtained during DHCPDISCOVER */
+       struct dhcp_settings *dhcpoffer;
+       /** ProxyDHCPOFFER obtained during DHCPDISCOVER */
+       struct dhcp_settings *proxydhcpoffer;
        /** Retransmission timer */
        struct retry_timer timer;
-       /** Session start time (in ticks) */
+       /** Start time of the current state (in ticks) */
        unsigned long start;
 };
 
@@ -465,8 +329,8 @@ static void dhcp_free ( struct refcnt *refcnt ) {
                container_of ( refcnt, struct dhcp_session, refcnt );
 
        netdev_put ( dhcp->netdev );
-       dhcpset_put ( dhcp->response );
-       dhcpset_put ( dhcp->proxy_response );
+       dhcpset_put ( dhcp->dhcpoffer );
+       dhcpset_put ( dhcp->proxydhcpoffer );
        free ( dhcp );
 }
 
@@ -490,46 +354,182 @@ static void dhcp_finished ( struct dhcp_session *dhcp, int rc ) {
        job_done ( &dhcp->job, rc );
 }
 
+/****************************************************************************
+ *
+ * Data transfer interface
+ *
+ */
+
 /**
- * Register options received via DHCP
+ * Create a DHCP packet
  *
- * @v dhcp             DHCP session
+ * @v dhcppkt          DHCP packet structure to fill in
+ * @v netdev           Network device
+ * @v msgtype          DHCP message type
+ * @v options          Initial options to include (or NULL)
+ * @v data             Buffer for DHCP packet
+ * @v max_len          Size of DHCP packet buffer
  * @ret rc             Return status code
+ *
+ * Creates a DHCP packet in the specified buffer, and fills out a @c
+ * dhcp_packet structure.
  */
-static int dhcp_register_settings ( struct dhcp_session *dhcp ) {
-       struct settings *old_settings;
-       struct settings *settings;
-       struct settings *parent;
+int dhcp_create_packet ( struct dhcp_packet *dhcppkt,
+                        struct net_device *netdev, uint8_t msgtype,
+                        struct dhcp_options *options, 
+                        void *data, size_t max_len ) {
+       struct dhcphdr *dhcphdr = data;
+       size_t options_len;
+       unsigned int hlen;
        int rc;
 
-       /* Register ProxyDHCP settings, if present */
-       if ( dhcp->proxy_response ) {
-               settings = &dhcp->proxy_response->settings;
-               settings->name = PROXYDHCP_SETTINGS_NAME;
-               old_settings = find_settings ( settings->name );
-               if ( old_settings )
-                       unregister_settings ( old_settings );
-               if ( ( rc = register_settings ( settings, NULL ) ) != 0 )
-                       return rc;
+       /* Sanity check */
+       options_len = ( options ? options->len : 0 );
+       if ( max_len < ( sizeof ( *dhcphdr ) + options_len ) )
+               return -ENOSPC;
+
+       /* Initialise DHCP packet content */
+       memset ( dhcphdr, 0, max_len );
+       dhcphdr->xid = dhcp_xid ( netdev );
+       dhcphdr->magic = htonl ( DHCP_MAGIC_COOKIE );
+       dhcphdr->htype = ntohs ( netdev->ll_protocol->ll_proto );
+       dhcphdr->op = dhcp_op[msgtype];
+       /* If hardware length exceeds the chaddr field length, don't
+        * use the chaddr field.  This is as per RFC4390.
+        */
+       hlen = netdev->ll_protocol->ll_addr_len;
+       if ( hlen > sizeof ( dhcphdr->chaddr ) ) {
+               hlen = 0;
+               dhcphdr->flags = htons ( BOOTP_FL_BROADCAST );
        }
+       dhcphdr->hlen = hlen;
+       memcpy ( dhcphdr->chaddr, netdev->ll_addr, hlen );
+       memcpy ( dhcphdr->options, options->data, options_len );
 
-       /* Register DHCP settings */
-       parent = netdev_settings ( dhcp->netdev );
-       settings = &dhcp->response->settings;
-       old_settings = find_child_settings ( parent, settings->name );
-       if ( old_settings )
-               unregister_settings ( old_settings );
-       if ( ( rc = register_settings ( settings, parent ) ) != 0 )
+       /* Initialise DHCP packet structure */
+       memset ( dhcppkt, 0, sizeof ( *dhcppkt ) );
+       dhcppkt_init ( dhcppkt, data, max_len );
+       
+       /* Set DHCP_MESSAGE_TYPE option */
+       if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_MESSAGE_TYPE,
+                                   &msgtype, sizeof ( msgtype ) ) ) != 0 )
                return rc;
 
        return 0;
 }
 
-/****************************************************************************
- *
- * Data transfer interface
+/**
+ * Create DHCP request packet
  *
+ * @v dhcppkt          DHCP packet structure to fill in
+ * @v netdev           Network device
+ * @v ciaddr           Client IP address
+ * @v offer            DHCP offer, if applicable
+ * @v data             Buffer for DHCP packet
+ * @v max_len          Size of DHCP packet buffer
+ * @ret rc             Return status code
  */
+int dhcp_create_request ( struct dhcp_packet *dhcppkt,
+                         struct net_device *netdev, struct in_addr ciaddr,
+                         struct dhcp_packet *offer,
+                         void *data, size_t max_len ) {
+       struct device_description *desc = &netdev->dev->desc;
+       struct dhcp_netdev_desc dhcp_desc;
+       struct dhcp_client_id client_id;
+       struct dhcp_client_uuid client_uuid;
+       unsigned int msgtype;
+       size_t dhcp_features_len;
+       size_t ll_addr_len;
+       int rc;
+
+       /* Create DHCP packet */
+       msgtype = ( offer ? DHCPREQUEST : DHCPDISCOVER );
+       if ( ( rc = dhcp_create_packet ( dhcppkt, netdev, msgtype,
+                                        &dhcp_request_options, data,
+                                        max_len ) ) != 0 ) {
+               DBG ( "DHCP could not create DHCP packet: %s\n",
+                     strerror ( rc ) );
+               return rc;
+       }
+
+       /* Set client IP address */
+       dhcppkt->dhcphdr->ciaddr = ciaddr;
+
+       /* Copy any required options from previous server repsonse */
+       if ( offer ) {
+               struct in_addr server = { 0 };
+               struct in_addr *ip = &offer->dhcphdr->yiaddr;
+
+               /* Copy server identifier, if present */
+               if ( ( dhcppkt_fetch ( offer, DHCP_SERVER_IDENTIFIER, &server,
+                                      sizeof ( server ) ) >= 0 ) &&
+                    ( ( rc = dhcppkt_store ( dhcppkt, DHCP_SERVER_IDENTIFIER,
+                                             &server,
+                                             sizeof ( server ) ) ) != 0 ) ) {
+                       DBG ( "DHCP could not set server ID: %s\n",
+                             strerror ( rc ) );
+                       return rc;
+               }
+
+               /* Copy requested IP address, if present */
+               if ( ( ip->s_addr != 0 ) &&
+                    ( ( rc = dhcppkt_store ( dhcppkt, DHCP_REQUESTED_ADDRESS,
+                                             ip, sizeof ( *ip ) ) ) != 0 ) ) {
+                       DBG ( "DHCP could not set requested address: %s\n",
+                             strerror ( rc ) );
+                       return rc;
+               }
+       }
+
+       /* Add options to identify the feature list */
+       dhcp_features_len = ( dhcp_features_end - dhcp_features );
+       if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_EB_ENCAP, dhcp_features,
+                                   dhcp_features_len ) ) != 0 ) {
+               DBG ( "DHCP could not set features list option: %s\n",
+                     strerror ( rc ) );
+               return rc;
+       }
+
+       /* Add options to identify the network device */
+       dhcp_desc.type = desc->bus_type;
+       dhcp_desc.vendor = htons ( desc->vendor );
+       dhcp_desc.device = htons ( desc->device );
+       if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_EB_BUS_ID, &dhcp_desc,
+                                   sizeof ( dhcp_desc ) ) ) != 0 ) {
+               DBG ( "DHCP could not set bus ID option: %s\n",
+                     strerror ( rc ) );
+               return rc;
+       }
+
+       /* Add DHCP client identifier.  Required for Infiniband, and
+        * doesn't hurt other link layers.
+        */
+       client_id.ll_proto = ntohs ( netdev->ll_protocol->ll_proto );
+       ll_addr_len = netdev->ll_protocol->ll_addr_len;
+       assert ( ll_addr_len <= sizeof ( client_id.ll_addr ) );
+       memcpy ( client_id.ll_addr, netdev->ll_addr, ll_addr_len );
+       if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_CLIENT_ID, &client_id,
+                                   ( ll_addr_len + 1 ) ) ) != 0 ) {
+               DBG ( "DHCP could not set client ID: %s\n",
+                     strerror ( rc ) );
+               return rc;
+       }
+
+       /* Add client UUID, if we have one.  Required for PXE. */
+       client_uuid.type = DHCP_CLIENT_UUID_TYPE;
+       if ( ( rc = fetch_uuid_setting ( NULL, &uuid_setting,
+                                        &client_uuid.uuid ) ) >= 0 ) {
+               if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_CLIENT_UUID,
+                                           &client_uuid,
+                                           sizeof ( client_uuid ) ) ) != 0 ) {
+                       DBG ( "DHCP could not set client UUID: %s\n",
+                             strerror ( rc ) );
+                       return rc;
+               }
+       }
+
+       return 0;
+}
 
 /**
  * Transmit DHCP request
@@ -537,38 +537,63 @@ static int dhcp_register_settings ( struct dhcp_session *dhcp ) {
  * @v dhcp             DHCP session
  * @ret rc             Return status code
  */
-static int dhcp_send_request ( struct dhcp_session *dhcp ) {
+static int dhcp_tx ( struct dhcp_session *dhcp ) {
+       static struct sockaddr_in proxydhcp_server = {
+               .sin_family = AF_INET,
+               .sin_port = htons ( PROXYDHCP_PORT ),
+       };
        struct xfer_metadata meta = {
                .netdev = dhcp->netdev,
        };
        struct io_buffer *iobuf;
-       struct dhcp_packet *dhcpoffer = NULL;
        struct dhcp_packet dhcppkt;
+       struct dhcp_packet *offer = NULL;
+       struct in_addr ciaddr = { 0 };
+       int check_len;
        int rc;
-       
-       DBGC ( dhcp, "DHCP %p transmitting %s\n",
-              dhcp, dhcp_msgtype_name ( dhcp->state ) );
-
-       assert ( ( dhcp->state == DHCPDISCOVER ) ||
-                ( dhcp->state == DHCPREQUEST ) );
 
        /* Start retry timer.  Do this first so that failures to
         * transmit will be retried.
         */
        start_timer ( &dhcp->timer );
 
+       /* Determine packet contents based on current state */
+       switch ( dhcp->state ) {
+       case DHCP_STATE_DISCOVER:
+               DBGC ( dhcp, "DHCP %p transmitting DHCPDISCOVER\n", dhcp );
+               break;
+       case DHCP_STATE_REQUEST:
+               DBGC ( dhcp, "DHCP %p transmitting DHCPREQUEST\n", dhcp );
+               assert ( dhcp->dhcpoffer );
+               offer = &dhcp->dhcpoffer->dhcppkt;
+               break;
+       case DHCP_STATE_PROXYREQUEST:
+               DBGC ( dhcp, "DHCP %p transmitting ProxyDHCPREQUEST\n", dhcp );
+               assert ( dhcp->dhcpoffer );
+               assert ( dhcp->proxydhcpoffer );
+               offer = &dhcp->proxydhcpoffer->dhcppkt;
+               ciaddr = dhcp->dhcpoffer->dhcppkt.dhcphdr->yiaddr;
+               check_len = dhcppkt_fetch ( offer, DHCP_SERVER_IDENTIFIER,
+                                           &proxydhcp_server.sin_addr,
+                                           sizeof(proxydhcp_server.sin_addr));
+               meta.dest = ( struct sockaddr * ) &proxydhcp_server;
+               assert ( ciaddr.s_addr != 0 );
+               assert ( proxydhcp_server.sin_addr.s_addr != 0 );
+               assert ( check_len == sizeof ( proxydhcp_server.sin_addr ) );
+               break;
+       default:
+               assert ( 0 );
+               break;
+       }
+
        /* Allocate buffer for packet */
        iobuf = xfer_alloc_iob ( &dhcp->xfer, DHCP_MIN_LEN );
        if ( ! iobuf )
                return -ENOMEM;
 
        /* Create DHCP packet in temporary buffer */
-       if ( dhcp->state == DHCPREQUEST ) {
-               assert ( dhcp->response );
-               dhcpoffer = &dhcp->response->dhcppkt;
-       }
-       if ( ( rc = create_dhcp_request ( &dhcppkt, dhcp->netdev,
-                                         dhcpoffer, iobuf->data,
+       if ( ( rc = dhcp_create_request ( &dhcppkt, dhcp->netdev,
+                                         ciaddr, offer, iobuf->data,
                                          iob_tailroom ( iobuf ) ) ) != 0 ) {
                DBGC ( dhcp, "DHCP %p could not construct DHCP request: %s\n",
                       dhcp, strerror ( rc ) );
@@ -591,27 +616,229 @@ static int dhcp_send_request ( struct dhcp_session *dhcp ) {
 }
 
 /**
- * Handle DHCP retry timer expiry
+ * Transition to new DHCP session state
  *
- * @v timer            DHCP retry timer
- * @v fail             Failure indicator
+ * @v dhcp             DHCP session
+ * @v state            New session state
  */
-static void dhcp_timer_expired ( struct retry_timer *timer, int fail ) {
-       struct dhcp_session *dhcp =
-               container_of ( timer, struct dhcp_session, timer );
+static void dhcp_set_state ( struct dhcp_session *dhcp,
+                            enum dhcp_session_state state ) {
+       DBGC ( dhcp, "DHCP %p entering %s state\n",
+              dhcp, dhcp_state_name ( state ) );
+       dhcp->state = state;
+       dhcp->start = currticks();
+       start_timer_nodelay ( &dhcp->timer );
+}
 
-       if ( fail ) {
-               dhcp_finished ( dhcp, -ETIMEDOUT );
-       } else {
-               dhcp_send_request ( dhcp );
+/**
+ * Store received DHCPOFFER
+ *
+ * @v dhcp             DHCP session
+ * @v dhcpoffer                Received DHCPOFFER
+ * @v stored_dhcpoffer Location to store DHCPOFFER
+ *
+ * The DHCPOFFER will be stored in place of the existing stored
+ * DHCPOFFER if its priority is equal to or greater than the stored
+ * DHCPOFFER.
+ */
+static void dhcp_store_dhcpoffer ( struct dhcp_session *dhcp,
+                                  struct dhcp_settings *dhcpoffer,
+                                  struct dhcp_settings **stored_dhcpoffer ) {
+       uint8_t stored_priority = 0;
+       uint8_t priority = 0;
+
+       /* Get priorities of the two DHCPOFFERs */
+       if ( *stored_dhcpoffer ) {
+               dhcppkt_fetch ( &(*stored_dhcpoffer)->dhcppkt,
+                               DHCP_EB_PRIORITY, &stored_priority,
+                               sizeof ( stored_priority ) );
+       }
+       dhcppkt_fetch ( &dhcpoffer->dhcppkt, DHCP_EB_PRIORITY, &priority,
+                       sizeof ( priority ) );
+
+       /* Replace stored offer only if priority is equal or greater */
+       if ( priority >= stored_priority ) {
+               if ( *stored_dhcpoffer ) {
+                       DBGC ( dhcp, "DHCP %p stored DHCPOFFER %p discarded\n",
+                              dhcp, *stored_dhcpoffer );
+               }
+               DBGC ( dhcp, "DHCP %p received DHCPOFFER %p stored\n",
+                      dhcp, dhcpoffer );
+               dhcpset_put ( *stored_dhcpoffer );
+               *stored_dhcpoffer = dhcpset_get ( dhcpoffer );
+       }
+}
+
+/**
+ * Handle received DHCPOFFER
+ *
+ * @v dhcp             DHCP session
+ * @v dhcpoffer                Received DHCPOFFER
+ */
+static void dhcp_rx_dhcpoffer ( struct dhcp_session *dhcp,
+                               struct dhcp_settings *dhcpoffer ) {
+       char vci[9]; /* "PXEClient" */
+       int len;
+       uint8_t ignore_proxy = 0;
+       unsigned long elapsed;
+
+       /* Check for presence of DHCP server ID */
+       if ( dhcppkt_fetch ( &dhcpoffer->dhcppkt, DHCP_SERVER_IDENTIFIER,
+                            NULL, 0 ) != sizeof ( struct in_addr ) ) {
+               DBGC ( dhcp, "DHCP %p received DHCPOFFER %p missing server "
+                      "identifier\n", dhcp, dhcpoffer );
+               return;
+       }
+
+       /* If there is an IP address, it's a normal DHCPOFFER */
+       if ( dhcpoffer->dhcppkt.dhcphdr->yiaddr.s_addr != 0 ) {
+               DBGC ( dhcp, "DHCP %p received DHCPOFFER %p has IP address\n",
+                      dhcp, dhcpoffer );
+               dhcp_store_dhcpoffer ( dhcp, dhcpoffer, &dhcp->dhcpoffer );
+       }
+
+       /* If there is a "PXEClient" vendor class ID, it's a
+        * ProxyDHCPOFFER.  Note that it could be both a normal
+        * DHCPOFFER and a ProxyDHCPOFFER.
+        */
+       len = dhcppkt_fetch ( &dhcpoffer->dhcppkt, DHCP_VENDOR_CLASS_ID,
+                             vci, sizeof ( vci ) );
+       if ( ( len >= ( int ) sizeof ( vci ) ) &&
+            ( strncmp ( "PXEClient", vci, sizeof ( vci ) ) == 0 ) ) {
+               DBGC ( dhcp, "DHCP %p received DHCPOFFER %p is a "
+                      "ProxyDHCPOFFER\n", dhcp, dhcpoffer );
+               dhcp_store_dhcpoffer ( dhcp, dhcpoffer,
+                                      &dhcp->proxydhcpoffer );
+       }
+
+       /* We can transition to making the DHCPREQUEST when we have a
+        * valid DHCPOFFER, and either:
+        *
+        *  o  The DHCPOFFER instructs us to not wait for ProxyDHCP, or
+        *  o  We have a valid ProxyDHCPOFFER, or
+         *  o  We have allowed sufficient time for ProxyDHCPOFFERs.
+        */
+
+       /* If we don't yet have a DHCPOFFER, do nothing */
+       if ( ! dhcp->dhcpoffer )
+               return;
+
+       /* If the DHCPOFFER instructs us to ignore ProxyDHCP, discard
+        * any ProxyDHCPOFFER
+        */
+       dhcppkt_fetch ( &dhcp->dhcpoffer->dhcppkt, DHCP_EB_NO_PROXYDHCP,
+                       &ignore_proxy, sizeof ( ignore_proxy ) );
+       if ( ignore_proxy && dhcp->proxydhcpoffer ) {
+               DBGC ( dhcp, "DHCP %p discarding ProxyDHCPOFFER\n", dhcp );
+               dhcpset_put ( dhcp->proxydhcpoffer );
+               dhcp->proxydhcpoffer = NULL;
+       }
+
+       /* If we can't yet transition to DHCPREQUEST, do nothing */
+       elapsed = ( currticks() - dhcp->start );
+       if ( ! ( ignore_proxy || dhcp->proxydhcpoffer ||
+                ( elapsed > PROXYDHCP_WAIT_TIME ) ) )
+               return;
+
+       /* Transition to DHCPREQUEST */
+       dhcp_set_state ( dhcp, DHCP_STATE_REQUEST );
+}
+
+/**
+ * Store received DHCPACK
+ *
+ * @v dhcp             DHCP session
+ * @v dhcpack          Received DHCPACK
+ *
+ * The DHCPACK will be registered as a settings block.
+ */
+static int dhcp_store_dhcpack ( struct dhcp_session *dhcp,
+                               struct dhcp_settings *dhcpack,
+                               struct settings *parent ) {
+       struct settings *settings = &dhcpack->settings;
+       struct settings *old_settings;
+       int rc;
+
+       /* Unregister any old settings obtained via DHCP */
+       if ( ( old_settings = find_child_settings ( parent, settings->name ) ))
+               unregister_settings ( old_settings );
+
+       /* Register new settings */
+       if ( ( rc = register_settings ( settings, parent ) ) != 0 ) {
+               DBGC ( dhcp, "DHCP %p could not register settings: %s\n",
+                      dhcp, strerror ( rc ) );
+               dhcp_finished ( dhcp, rc ); /* This is a fatal error */
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Handle received DHCPACK
+ *
+ * @v dhcp             DHCP session
+ * @v dhcpack          Received DHCPACK
+ */
+static void dhcp_rx_dhcpack ( struct dhcp_session *dhcp,
+                             struct dhcp_settings *dhcpack ) {
+       struct settings *parent;
+       struct in_addr offer_server_id = { 0 };
+       struct in_addr ack_server_id = { 0 };
+       int rc;
+
+       /* Verify server ID matches */
+       assert ( dhcp->dhcpoffer != NULL );
+       dhcppkt_fetch ( &dhcp->dhcpoffer->dhcppkt, DHCP_SERVER_IDENTIFIER,
+                       &offer_server_id, sizeof ( offer_server_id ) );
+       dhcppkt_fetch ( &dhcpack->dhcppkt, DHCP_SERVER_IDENTIFIER,
+                       &ack_server_id, sizeof ( ack_server_id ) );
+       if ( offer_server_id.s_addr != ack_server_id.s_addr ) {
+               DBGC ( dhcp, "DHCP %p ignoring DHCPACK with wrong server ID\n",
+                      dhcp );
+               return;
+       }
+
+       /* Register settings */
+       parent = netdev_settings ( dhcp->netdev );
+       if ( ( rc = dhcp_store_dhcpack ( dhcp, dhcpack, parent ) ) !=0 )
+               return;
+
+       /* If we have a ProxyDHCPOFFER, transition to PROXYDHCPREQUEST */
+       if ( dhcp->proxydhcpoffer ) {
+               dhcp_set_state ( dhcp, DHCP_STATE_PROXYREQUEST );
+               return;
        }
+
+       /* Terminate DHCP */
+       dhcp_finished ( dhcp, 0 );
+}
+
+/**
+ * Handle received ProxyDHCPACK
+ *
+ * @v dhcp             DHCP session
+ * @v proxydhcpack     Received ProxyDHCPACK
+ */
+static void dhcp_rx_proxydhcpack ( struct dhcp_session *dhcp,
+                                  struct dhcp_settings *proxydhcpack ) {
+       int rc;
+
+       /* Rename settings */
+       proxydhcpack->settings.name = PROXYDHCP_SETTINGS_NAME;
+
+       /* Register settings */
+       if ( ( rc = dhcp_store_dhcpack ( dhcp, proxydhcpack, NULL ) ) != 0 )
+               return;
+
+       /* Terminate DHCP */
+       dhcp_finished ( dhcp, 0 );
 }
 
 /**
  * Receive new data
  *
  * @v xfer             Data transfer interface
- * @v iobuf            I/O buffer
  * @v data             Received data
  * @v len              Length of received data
  * @ret rc             Return status code
@@ -620,108 +847,52 @@ static int dhcp_deliver_raw ( struct xfer_interface *xfer,
                              const void *data, size_t len ) {
        struct dhcp_session *dhcp =
                container_of ( xfer, struct dhcp_session, xfer );
-       struct dhcp_settings *response;
-       struct dhcp_settings **store_response;
+       struct dhcp_settings *dhcpset;
        struct dhcphdr *dhcphdr;
        uint8_t msgtype = 0;
-       uint8_t priority = 0;
-       uint8_t existing_priority = 0;
-       unsigned long elapsed;
-       int is_proxy;
-       uint8_t ignore_proxy = 0;
-       int rc;
 
        /* Convert packet into a DHCP settings block */
-       response = dhcpset_create ( data, len );
-       if ( ! response ) {
+       dhcpset = dhcpset_create ( data, len );
+       if ( ! dhcpset ) {
                DBGC ( dhcp, "DHCP %p could not store DHCP packet\n", dhcp );
                return -ENOMEM;
        }
-       dhcphdr = response->dhcppkt.dhcphdr;
+       dhcphdr = dhcpset->dhcppkt.dhcphdr;
 
-       /* Check for matching transaction ID */
-       if ( dhcphdr->xid != dhcp_xid ( dhcp->netdev ) ) {
-               DBGC ( dhcp, "DHCP %p wrong transaction ID (wanted %08lx, "
-                       "got %08lx)\n", dhcp, ntohl ( dhcphdr->xid ),
-                       ntohl ( dhcp_xid ( dhcp->netdev ) ) );
-               goto out_discard;
-       };      
-
-       /* Determine and verify message type */
-       is_proxy = ( dhcphdr->yiaddr.s_addr == 0 );
-       dhcppkt_fetch ( &response->dhcppkt, DHCP_MESSAGE_TYPE, &msgtype,
+       /* Identify message type */
+       dhcppkt_fetch ( &dhcpset->dhcppkt, DHCP_MESSAGE_TYPE, &msgtype,
                        sizeof ( msgtype ) );
-       DBGC ( dhcp, "DHCP %p received %s%s\n", dhcp,
-              ( is_proxy ? "Proxy" : "" ), dhcp_msgtype_name ( msgtype ) );
-       if ( ( ( dhcp->state != DHCPDISCOVER ) || ( msgtype != DHCPOFFER ) ) &&
-            ( ( dhcp->state != DHCPREQUEST ) || ( msgtype != DHCPACK ) ) ) {
-               DBGC ( dhcp, "DHCP %p discarding %s while in %s state\n",
-                      dhcp, dhcp_msgtype_name ( msgtype ),
-                      dhcp_msgtype_name ( dhcp->state ) );
-               goto out_discard;
-       }
-
-       /* Update stored standard/ProxyDHCP options, if the new
-        * options have equal or higher priority than the
-        * currently-stored options.
-        */
-       store_response = ( is_proxy ? &dhcp->proxy_response : &dhcp->response);
-       if ( *store_response ) {
-               dhcppkt_fetch ( &(*store_response)->dhcppkt, DHCP_EB_PRIORITY,
-                               &existing_priority,
-                               sizeof ( existing_priority ) );
-       }
-       dhcppkt_fetch ( &response->dhcppkt, DHCP_EB_PRIORITY, &priority,
-                       sizeof ( priority ) );
-       if ( priority >= existing_priority ) {
-               dhcpset_put ( *store_response );
-               *store_response = response;
-       } else {
-               dhcpset_put ( response );
-       }
+       DBGC ( dhcp, "DHCP %p received %s %p\n",
+              dhcp, dhcp_msgtype_name ( msgtype ), dhcpset );
 
-       /* If we don't yet have a standard DHCP response (i.e. one
-        * with an IP address), then just leave the timer running.
-        */
-       if ( ! dhcp->response )
+       /* Check for matching transaction ID */
+       if ( dhcphdr->xid != dhcp_xid ( dhcp->netdev ) ) {
+               DBGC ( dhcp, "DHCP %p received %s %p has bad transaction ID\n",
+                      dhcp, dhcp_msgtype_name ( msgtype ), dhcpset );
                goto out;
+       };
 
-       /* Handle DHCP response */
-       dhcppkt_fetch ( &dhcp->response->dhcppkt, DHCP_EB_NO_PROXYDHCP,
-                       &ignore_proxy, sizeof ( ignore_proxy ) );
+       /* Handle packet based on current state */
        switch ( dhcp->state ) {
-       case DHCPDISCOVER:
-               /* If we have allowed sufficient time for ProxyDHCP
-                * reponses, then transition to making the DHCPREQUEST.
-                */
-               elapsed = ( currticks() - dhcp->start );
-               if ( ignore_proxy || ( elapsed > PROXYDHCP_WAIT_TIME ) ) {
-                       stop_timer ( &dhcp->timer );
-                       dhcp->state = DHCPREQUEST;
-                       dhcp_send_request ( dhcp );
-               }
+       case DHCP_STATE_DISCOVER:
+               if ( msgtype == DHCPOFFER )
+                       dhcp_rx_dhcpoffer ( dhcp, dhcpset );
                break;
-       case DHCPREQUEST:
-               /* DHCP finished; register options and exit */
-               if ( ignore_proxy && dhcp->proxy_response ) {
-                       dhcpset_put ( dhcp->proxy_response );
-                       dhcp->proxy_response = NULL;
-               }
-               if ( ( rc = dhcp_register_settings ( dhcp ) ) != 0 ) {
-                       dhcp_finished ( dhcp, rc );
-                       break;
-               }
-               dhcp_finished ( dhcp, 0 );
+       case DHCP_STATE_REQUEST:
+               if ( msgtype == DHCPACK )
+                       dhcp_rx_dhcpack ( dhcp, dhcpset );
+               break;
+       case DHCP_STATE_PROXYREQUEST:
+               if ( msgtype == DHCPACK )
+                       dhcp_rx_proxydhcpack ( dhcp, dhcpset );
                break;
        default:
                assert ( 0 );
+               break;
        }
 
  out:
-       return 0;
-
- out_discard:
-       dhcpset_put ( response );
+       dhcpset_put ( dhcpset );
        return 0;
 }
 
@@ -735,6 +906,38 @@ static struct xfer_interface_operations dhcp_xfer_operations = {
        .deliver_raw    = dhcp_deliver_raw,
 };
 
+/**
+ * Handle DHCP retry timer expiry
+ *
+ * @v timer            DHCP retry timer
+ * @v fail             Failure indicator
+ */
+static void dhcp_timer_expired ( struct retry_timer *timer, int fail ) {
+       struct dhcp_session *dhcp =
+               container_of ( timer, struct dhcp_session, timer );
+       unsigned long elapsed = ( currticks() - dhcp->start );
+
+       /* If we have failed, terminate DHCP */
+       if ( fail ) {
+               dhcp_finished ( dhcp, -ETIMEDOUT );
+               return;
+       }
+
+       /* Give up waiting for ProxyDHCP before we reach the failure point */
+       if ( elapsed > PROXYDHCP_WAIT_TIME ) {
+               if ( dhcp->state == DHCP_STATE_DISCOVER ) {
+                       dhcp_set_state ( dhcp, DHCP_STATE_REQUEST );
+                       return;
+               } else if ( dhcp->state == DHCP_STATE_PROXYREQUEST ) {
+                       dhcp_finished ( dhcp, 0 );
+                       return;
+               }
+       }
+
+       /* Otherwise, retransmit current packet */
+       dhcp_tx ( dhcp );
+}
+
 /****************************************************************************
  *
  * Job control interface
@@ -801,7 +1004,6 @@ int start_dhcp ( struct job_interface *job, struct net_device *netdev ) {
        xfer_init ( &dhcp->xfer, &dhcp_xfer_operations, &dhcp->refcnt );
        dhcp->netdev = netdev_get ( netdev );
        dhcp->timer.expired = dhcp_timer_expired;
-       dhcp->state = DHCPDISCOVER;
        dhcp->start = currticks();
 
        /* Instantiate child objects and attach to our interfaces */