Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[dhcp] Include support for PXE boot menus
PXE dictates a mechanism for boot menuing, involving prompting the
user with a variable message, waiting for a predefined keypress,
displaying a boot menu, and waiting for a selection.

This breaks the currently desirable abstraction that DHCP is a process
that can happen in the background without any user interaction.
  • Loading branch information
Michael Brown committed Jan 25, 2009
1 parent f1d17ae commit 027c72e
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 14 deletions.
5 changes: 4 additions & 1 deletion src/include/gpxe/dhcp.h
Expand Up @@ -88,6 +88,9 @@ struct dhcp_pxe_boot_menu_item;
/** PXE boot menu */
#define DHCP_PXE_BOOT_MENU DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 9 )

/** PXE boot menu prompt */
#define DHCP_PXE_BOOT_MENU_PROMPT DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 10 )

/** PXE boot menu item */
#define DHCP_PXE_BOOT_MENU_ITEM DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 71 )

Expand Down Expand Up @@ -478,7 +481,7 @@ struct dhcphdr {
#define DHCP_MIN_LEN 552

/** Maximum time that we will wait for ProxyDHCP responses */
#define PROXYDHCP_WAIT_TIME ( TICKS_PER_SEC * 1 )
#define PROXYDHCP_WAIT_TIME ( 2 * TICKS_PER_SEC )

/** Timeouts for sending DHCP packets */
#define DHCP_MIN_TIMEOUT ( 1 * TICKS_PER_SEC )
Expand Down
206 changes: 193 additions & 13 deletions src/net/udp/dhcp.c
Expand Up @@ -19,9 +19,11 @@
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <assert.h>
#include <byteswap.h>
#include <console.h>
#include <gpxe/if_ether.h>
#include <gpxe/netdevice.h>
#include <gpxe/device.h>
Expand All @@ -39,6 +41,7 @@
#include <gpxe/dhcpopts.h>
#include <gpxe/dhcppkt.h>
#include <gpxe/features.h>
#include <gpxe/keys.h>

/** @file
*
Expand Down Expand Up @@ -125,6 +128,32 @@ struct dhcp_client_uuid {

#define DHCP_CLIENT_UUID_TYPE 0

/** DHCP PXE boot prompt */
struct dhcp_pxe_boot_prompt {
/** Timeout
*
* A value of 0 means "time out immediately and select first
* boot item, without displaying the prompt". A value of 255
* means "display menu immediately with no timeout". Any
* other value means "display prompt, wait this many seconds
* for keypress, if key is F8, display menu, otherwise select
* first boot item".
*/
uint8_t timeout;
/** Prompt to press F8 */
char prompt[0];
} __attribute__ (( packed ));

/** DHCP PXE boot menu item description */
struct dhcp_pxe_boot_menu_item_desc {
/** "Type" */
uint16_t type;
/** Description length */
uint8_t desc_len;
/** Description */
char desc[0];
} __attribute__ (( packed ));

/** DHCP PXE boot menu item */
struct dhcp_pxe_boot_menu_item {
/** "Type"
Expand All @@ -140,6 +169,9 @@ struct dhcp_pxe_boot_menu_item {
uint16_t layer;
} __attribute__ (( packed ));

/** Maximum allowed number of PXE boot menu items */
#define PXE_BOOT_MENU_MAX_ITEMS 20

/**
* Name a DHCP packet type
*
Expand Down Expand Up @@ -352,6 +384,9 @@ struct dhcp_session {
struct dhcp_settings *pxedhcpack;
/** BootServerDHCPACK obtained during BootServerDHCPREQUEST */
struct dhcp_settings *bsdhcpack;
/** PXE boot menu item */
struct dhcp_pxe_boot_menu_item menu_item;

/** Retransmission timer */
struct retry_timer timer;
/** Start time of the current state (in ticks) */
Expand Down Expand Up @@ -602,7 +637,6 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) {
struct in_addr ciaddr = { 0 };
struct in_addr server = { 0 };
struct in_addr requested_ip = { 0 };
struct dhcp_pxe_boot_menu_item menu_item = { 0, 0 };
unsigned int msgtype;
int rc;

Expand Down Expand Up @@ -649,11 +683,7 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) {
DHCP_PXE_BOOT_SERVER_MCAST,
&dest.sin_addr, sizeof ( dest.sin_addr ) );
meta.dest = ( struct sockaddr * ) &dest;
dhcppkt_fetch ( &dhcp->pxedhcpack->dhcppkt,
DHCP_PXE_BOOT_MENU, &menu_item.type,
sizeof ( menu_item.type ) );
assert ( dest.sin_addr.s_addr );
assert ( menu_item.type );
assert ( ciaddr.s_addr );
break;
default:
Expand All @@ -677,8 +707,10 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) {
}
if ( requested_ip.s_addr )
DBGC ( dhcp, " for %s", inet_ntoa ( requested_ip ) );
if ( menu_item.type )
DBGC ( dhcp, " for item %04x", ntohs ( menu_item.type ) );
if ( dhcp->menu_item.type ) {
DBGC ( dhcp, " for item %04x",
ntohs ( dhcp->menu_item.type ) );
}
DBGC ( dhcp, "\n" );

/* Allocate buffer for packet */
Expand All @@ -689,7 +721,7 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) {
/* Create DHCP packet in temporary buffer */
if ( ( rc = dhcp_create_request ( &dhcppkt, dhcp->netdev, msgtype,
ciaddr, server, requested_ip,
&menu_item, iobuf->data,
&dhcp->menu_item, iobuf->data,
iob_tailroom ( iobuf ) ) ) != 0 ) {
DBGC ( dhcp, "DHCP %p could not construct DHCP request: %s\n",
dhcp, strerror ( rc ) );
Expand Down Expand Up @@ -717,6 +749,154 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) {
return rc;
}

/**
* Prompt for PXE boot menu selection
*
* @v pxedhcpack PXEDHCPACK packet
* @ret rc Return status code
*
* Note that a success return status indicates that the PXE boot menu
* should be displayed.
*/
static int dhcp_pxe_boot_menu_prompt ( struct dhcp_packet *pxedhcpack ) {
union {
uint8_t buf[80];
struct dhcp_pxe_boot_prompt prompt;
} u;
ssize_t slen;
unsigned long start;
int key;

/* Parse menu prompt */
memset ( &u, 0, sizeof ( u ) );
if ( ( slen = dhcppkt_fetch ( pxedhcpack, DHCP_PXE_BOOT_MENU_PROMPT,
&u, sizeof ( u ) ) ) <= 0 ) {
/* If prompt is not present, we should always display
* the menu.
*/
return 0;
}

/* Display prompt, if applicable */
if ( u.prompt.timeout )
printf ( "\n%s\n", u.prompt.prompt );

/* Timeout==0xff means display menu immediately */
if ( u.prompt.timeout == 0xff )
return 0;

/* Wait for F8 or other key press */
start = currticks();
while ( ( currticks() - start ) <
( u.prompt.timeout * TICKS_PER_SEC ) ) {
if ( iskey() ) {
key = getkey();
return ( ( key == KEY_F8 ) ? 0 : -ECANCELED );
}
}

return -ECANCELED;
}

/**
* Perform PXE boot menu selection
*
* @v pxedhcpack PXEDHCPACK packet
* @v menu_item PXE boot menu item to fill in
* @ret rc Return status code
*
* Note that a success return status indicates that a PXE boot menu
* item has been selected, and that the DHCP session should perform a
* boot server request/ack.
*/
static int dhcp_pxe_boot_menu ( struct dhcp_packet *pxedhcpack,
struct dhcp_pxe_boot_menu_item *menu_item ) {
uint8_t buf[256];
ssize_t slen;
size_t menu_len;
struct dhcp_pxe_boot_menu_item_desc *menu_item_desc;
size_t menu_item_desc_len;
struct {
uint16_t type;
char *desc;
} menu[PXE_BOOT_MENU_MAX_ITEMS];
size_t offset = 0;
unsigned int num_menu_items = 0;
unsigned int i;
unsigned int selected_menu_item;
int key;
int rc;

/* Check for boot menu */
memset ( &buf, 0, sizeof ( buf ) );
if ( ( slen = dhcppkt_fetch ( pxedhcpack, DHCP_PXE_BOOT_MENU,
&buf, sizeof ( buf ) ) ) <= 0 ) {
DBGC2 ( pxedhcpack, "PXEDHCPACK %p has no boot menu\n",
pxedhcpack );
return slen;
}
menu_len = slen;

/* Parse boot menu */
while ( offset < menu_len ) {
menu_item_desc = ( ( void * ) ( buf + offset ) );
menu_item_desc_len = ( sizeof ( *menu_item_desc ) +
menu_item_desc->desc_len );
if ( ( offset + menu_item_desc_len ) > menu_len ) {
DBGC ( pxedhcpack, "PXEDHCPACK %p has malformed "
"boot menu\n", pxedhcpack );
return -EINVAL;
}
menu[num_menu_items].type = menu_item_desc->type;
menu[num_menu_items].desc = menu_item_desc->desc;
/* Set type to 0; this ensures that the description
* for the previous menu item is NUL-terminated.
* (Final item is NUL-terminated anyway.)
*/
menu_item_desc->type = 0;
offset += menu_item_desc_len;
num_menu_items++;
if ( num_menu_items == ( sizeof ( menu ) /
sizeof ( menu[0] ) ) ) {
DBGC ( pxedhcpack, "PXEDHCPACK %p has too many "
"menu items\n", pxedhcpack );
/* Silently ignore remaining items */
break;
}
}
if ( ! num_menu_items ) {
DBGC ( pxedhcpack, "PXEDHCPACK %p has no menu items\n",
pxedhcpack );
return -EINVAL;
}

/* Default to first menu item */
menu_item->type = menu[0].type;

/* Prompt for menu, if necessary */
if ( ( rc = dhcp_pxe_boot_menu_prompt ( pxedhcpack ) ) != 0 ) {
/* Failure to display menu means we should just
* continue with the boot.
*/
return 0;
}

/* Display menu */
for ( i = 0 ; i < num_menu_items ; i++ ) {
printf ( "%c. %s\n", ( 'A' + i ), menu[i].desc );
}

/* Obtain selection */
while ( 1 ) {
key = getkey();
selected_menu_item = ( toupper ( key ) - 'A' );
if ( selected_menu_item < num_menu_items ) {
menu_item->type = menu[selected_menu_item].type;
return 0;
}
}
}

/**
* Transition to new DHCP session state
*
Expand All @@ -739,7 +919,8 @@ static void dhcp_set_state ( struct dhcp_session *dhcp,
* @v dhcp DHCP session
*/
static void dhcp_next_state ( struct dhcp_session *dhcp ) {
struct in_addr bs_mcast = { 0 };

stop_timer ( &dhcp->timer );

switch ( dhcp->state ) {
case DHCP_STATE_DISCOVER:
Expand All @@ -760,10 +941,9 @@ static void dhcp_next_state ( struct dhcp_session *dhcp ) {
/* Fall through */
case DHCP_STATE_PROXYREQUEST:
if ( dhcp->pxedhcpack ) {
dhcppkt_fetch ( &dhcp->pxedhcpack->dhcppkt,
DHCP_PXE_BOOT_SERVER_MCAST,
&bs_mcast, sizeof ( bs_mcast ) );
if ( bs_mcast.s_addr ) {
dhcp_pxe_boot_menu ( &dhcp->pxedhcpack->dhcppkt,
&dhcp->menu_item );
if ( dhcp->menu_item.type ) {
dhcp_set_state ( dhcp, DHCP_STATE_BSREQUEST );
break;
}
Expand Down

0 comments on commit 027c72e

Please sign in to comment.