diff --git a/src/arch/i386/interface/pxe/pxe_preboot.c b/src/arch/i386/interface/pxe/pxe_preboot.c index 8220d1f2..193abc3d 100644 --- a/src/arch/i386/interface/pxe/pxe_preboot.c +++ b/src/arch/i386/interface/pxe/pxe_preboot.c @@ -82,7 +82,7 @@ struct pxe_dhcp_packet_creator { static struct pxe_dhcp_packet_creator pxe_dhcp_packet_creators[] = { [CACHED_INFO_DHCPDISCOVER] = { create_fakedhcpdiscover }, [CACHED_INFO_DHCPACK] = { create_fakedhcpack }, - [CACHED_INFO_BINL] = { create_fakeproxydhcpack }, + [CACHED_INFO_BINL] = { create_fakepxebsack }, }; /* The case in which the caller doesn't supply a buffer is really diff --git a/src/hci/commands/dhcp_cmd.c b/src/hci/commands/dhcp_cmd.c index 07acc615..b44433e7 100644 --- a/src/hci/commands/dhcp_cmd.c +++ b/src/hci/commands/dhcp_cmd.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -60,7 +61,7 @@ static int dhcp_exec ( int argc, char **argv ) { { "help", 0, NULL, 'h' }, { NULL, 0, NULL, 0 }, }; - const char *name; + const char *netdev_txt; struct net_device *netdev; int c; int rc; @@ -82,14 +83,16 @@ static int dhcp_exec ( int argc, char **argv ) { dhcp_syntax ( argv ); return 1; } - name = argv[optind]; + netdev_txt = argv[optind]; - /* Perform DHCP */ - netdev = find_netdev ( name ); + /* Parse arguments */ + netdev = find_netdev ( netdev_txt ); if ( ! netdev ) { - printf ( "No such interface: %s\n", name ); + printf ( "No such interface: %s\n", netdev_txt ); return 1; } + + /* Perform DHCP */ if ( ( rc = dhcp ( netdev ) ) != 0 ) { printf ( "Could not configure %s: %s\n", netdev->name, strerror ( rc ) ); @@ -99,10 +102,96 @@ static int dhcp_exec ( int argc, char **argv ) { return 0; } +/** + * "pxebs" command syntax message + * + * @v argv Argument list + */ +static void pxebs_syntax ( char **argv ) { + printf ( "Usage:\n" + " %s \n" + "\n" + "Perform PXE Boot Server discovery\n", + argv[0] ); +} + +/** + * The "pxebs" command + * + * @v argc Argument count + * @v argv Argument list + * @ret rc Exit code + */ +static int pxebs_exec ( int argc, char **argv ) { + static struct option longopts[] = { + { "help", 0, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + const char *netdev_txt; + const char *pxe_server_txt; + const char *pxe_type_txt; + struct net_device *netdev; + struct in_addr pxe_server; + unsigned int pxe_type; + char *end; + int c; + int rc; + + /* Parse options */ + while ( ( c = getopt_long ( argc, argv, "h", longopts, NULL ) ) >= 0 ){ + switch ( c ) { + case 'h': + /* Display help text */ + default: + /* Unrecognised/invalid option */ + pxebs_syntax ( argv ); + return 1; + } + } + + /* Need exactly one interface name remaining after the options */ + if ( optind != ( argc - 3 ) ) { + pxebs_syntax ( argv ); + return 1; + } + netdev_txt = argv[optind]; + pxe_server_txt = argv[ optind + 1 ]; + pxe_type_txt = argv[ optind + 2 ]; + + /* Parse arguments */ + netdev = find_netdev ( netdev_txt ); + if ( ! netdev ) { + printf ( "No such interface: %s\n", netdev_txt ); + return 1; + } + if ( inet_aton ( pxe_server_txt, &pxe_server ) == 0 ) { + printf ( "Bad discovery IP address: %s\n", pxe_server_txt ); + return 1; + } + pxe_type = strtoul ( pxe_type_txt, &end, 0 ); + if ( *end ) { + printf ( "Bad server type: %s\n", pxe_type_txt ); + return 1; + } + + /* Perform Boot Server Discovery */ + if ( ( rc = pxebs ( netdev, pxe_server, pxe_type ) ) != 0 ) { + printf ( "Could not discover boot server on %s: %s\n", + netdev->name, strerror ( rc ) ); + return 1; + } + + return 0; +} + /** DHCP management commands */ struct command dhcp_commands[] __command = { { .name = "dhcp", .exec = dhcp_exec, }, + { + .name = "pxebs", + .exec = pxebs_exec, + }, }; diff --git a/src/hci/mucurses/ansi_screen.c b/src/hci/mucurses/ansi_screen.c index 0742a7d4..468bac02 100644 --- a/src/hci/mucurses/ansi_screen.c +++ b/src/hci/mucurses/ansi_screen.c @@ -15,7 +15,7 @@ static void ansiscr_reset ( struct _curses_screen *scr ) { scr->attrs = 0; scr->curs_x = 0; scr->curs_y = 0; - printf ( "\033[0m\033[2J\033[1;1H" ); + printf ( "\033[0m" ); } static void ansiscr_movetoyx ( struct _curses_screen *scr, diff --git a/src/hci/mucurses/wininit.c b/src/hci/mucurses/wininit.c index dfd0ca0a..cd27f9fe 100644 --- a/src/hci/mucurses/wininit.c +++ b/src/hci/mucurses/wininit.c @@ -18,7 +18,7 @@ WINDOW *initscr ( void ) { stdscr->scr->init( stdscr->scr ); stdscr->height = LINES; stdscr->width = COLS; - erase(); + move ( 0, 0 ); return stdscr; } @@ -29,7 +29,7 @@ WINDOW *initscr ( void ) { int endwin ( void ) { attrset ( 0 ); color_set ( 0, NULL ); - erase(); + mvprintw ( ( LINES - 1 ), 0, "\n" ); stdscr->scr->exit( stdscr->scr ); return OK; } diff --git a/src/include/gpxe/dhcp.h b/src/include/gpxe/dhcp.h index d49ba7fd..2fddb404 100644 --- a/src/include/gpxe/dhcp.h +++ b/src/include/gpxe/dhcp.h @@ -12,12 +12,12 @@ #include #include #include +#include +#include -struct net_device; struct job_interface; struct dhcp_options; struct dhcp_packet; -struct dhcp_pxe_boot_menu_item; /** BOOTP/DHCP server port */ #define BOOTPS_PORT 67 @@ -88,12 +88,53 @@ struct dhcp_pxe_boot_menu_item; /** PXE boot menu */ #define DHCP_PXE_BOOT_MENU DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 9 ) +/** PXE boot menu */ +struct dhcp_pxe_boot_menu { + /** "Type" */ + uint16_t type; + /** Description length */ + uint8_t desc_len; + /** Description */ + char desc[0]; +} __attribute__ (( packed )); + /** PXE boot menu prompt */ #define DHCP_PXE_BOOT_MENU_PROMPT DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 10 ) +/** PXE boot menu prompt */ +struct dhcp_pxe_boot_menu_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 )); + /** PXE boot menu item */ #define DHCP_PXE_BOOT_MENU_ITEM DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 71 ) +/** PXE boot menu item */ +struct dhcp_pxe_boot_menu_item { + /** "Type" + * + * This field actually identifies the specific boot server (or + * cluster of boot servers offering identical boot files). + */ + uint16_t type; + /** "Layer" + * + * Just don't ask. + */ + uint16_t layer; +} __attribute__ (( packed )); + /** Requested IP address */ #define DHCP_REQUESTED_ADDRESS 50 @@ -140,6 +181,14 @@ struct dhcp_pxe_boot_menu_item; /** Client identifier */ #define DHCP_CLIENT_ID 61 +/** 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 )); + /** TFTP server name * * This option replaces the fixed "sname" field, when that field is @@ -163,6 +212,16 @@ struct dhcp_pxe_boot_menu_item; /** UUID client identifier */ #define DHCP_CLIENT_UUID 97 +/** UUID client identifier */ +struct dhcp_client_uuid { + /** Identifier type */ + uint8_t type; + /** UUID */ + union uuid uuid; +} __attribute__ (( packed )); + +#define DHCP_CLIENT_UUID_TYPE 0 + /** Etherboot-specific encapsulated options * * This encapsulated options field is used to contain all options @@ -213,7 +272,7 @@ struct dhcp_pxe_boot_menu_item; /** Skip PXE DHCP protocol extensions such as ProxyDHCP * * If set to a non-zero value, gPXE will not wait for ProxyDHCP offers - * and will ignore any PXE-specific DHCP offers that it receives. + * and will ignore any PXE-specific DHCP options that it receives. */ #define DHCP_EB_NO_PXEDHCP DHCP_ENCAP_OPT ( DHCP_EB_ENCAP, 0xb0 ) @@ -230,6 +289,16 @@ struct dhcp_pxe_boot_menu_item; */ #define DHCP_EB_BUS_ID DHCP_ENCAP_OPT ( DHCP_EB_ENCAP, 0xb1 ) +/** 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 )); + /** BIOS drive number * * This is the drive number for a drive emulated via INT 13. 0x80 is @@ -480,13 +549,13 @@ struct dhcphdr { */ #define DHCP_MIN_LEN 552 -/** Maximum time that we will wait for ProxyDHCP responses */ -#define PROXYDHCP_WAIT_TIME ( 2 * TICKS_PER_SEC ) - /** Timeouts for sending DHCP packets */ #define DHCP_MIN_TIMEOUT ( 1 * TICKS_PER_SEC ) #define DHCP_MAX_TIMEOUT ( 10 * TICKS_PER_SEC ) +/** Maximum time that we will wait for ProxyDHCP responses */ +#define PROXYDHCP_MAX_TIMEOUT ( 2 * TICKS_PER_SEC ) + /** Settings block name used for DHCP responses */ #define DHCP_SETTINGS_NAME "dhcp" @@ -494,19 +563,18 @@ struct dhcphdr { #define PROXYDHCP_SETTINGS_NAME "proxydhcp" /** Setting block name used for BootServerDHCP responses */ -#define BSDHCP_SETTINGS_NAME "bs" +#define PXEBS_SETTINGS_NAME "pxebs" extern int dhcp_create_packet ( struct dhcp_packet *dhcppkt, struct net_device *netdev, uint8_t msgtype, - struct dhcp_options *options, + const void *options, size_t options_len, void *data, size_t max_len ); extern int dhcp_create_request ( struct dhcp_packet *dhcppkt, struct net_device *netdev, unsigned int msgtype, struct in_addr ciaddr, - struct in_addr server, - struct in_addr requested_ip, - struct dhcp_pxe_boot_menu_item *menu_item, void *data, size_t max_len ); extern int start_dhcp ( struct job_interface *job, struct net_device *netdev ); +extern int start_pxebs ( struct job_interface *job, struct net_device *netdev, + struct in_addr pxe_server, unsigned int pxe_type ); #endif /* _GPXE_DHCP_H */ diff --git a/src/include/gpxe/dhcppkt.h b/src/include/gpxe/dhcppkt.h index 179be2f8..e8f8fafd 100644 --- a/src/include/gpxe/dhcppkt.h +++ b/src/include/gpxe/dhcppkt.h @@ -9,27 +9,54 @@ #include #include +#include /** * A DHCP packet * */ struct dhcp_packet { + /** Reference counter */ + struct refcnt refcnt; /** The DHCP packet contents */ struct dhcphdr *dhcphdr; /** Maximum length of the DHCP packet buffer */ size_t max_len; /** Used length of the DHCP packet buffer */ size_t len; - /** DHCP option blocks */ + /** DHCP options */ struct dhcp_options options; + /** Settings interface */ + struct settings settings; }; +/** + * Increment reference count on DHCP packet + * + * @v dhcppkt DHCP packet + * @ret dhcppkt DHCP packet + */ +static inline __attribute__ (( always_inline )) struct dhcp_packet * +dhcppkt_get ( struct dhcp_packet *dhcppkt ) { + ref_get ( &dhcppkt->refcnt ); + return dhcppkt; +} + +/** + * Decrement reference count on DHCP packet + * + * @v dhcppkt DHCP packet + */ +static inline __attribute__ (( always_inline )) void +dhcppkt_put ( struct dhcp_packet *dhcppkt ) { + ref_put ( &dhcppkt->refcnt ); +} + extern int dhcppkt_store ( struct dhcp_packet *dhcppkt, unsigned int tag, const void *data, size_t len ); extern int dhcppkt_fetch ( struct dhcp_packet *dhcppkt, unsigned int tag, void *data, size_t len ); extern void dhcppkt_init ( struct dhcp_packet *dhcppkt, - void *data, size_t len ); + struct dhcphdr *data, size_t len ); #endif /* _GPXE_DHCPPKT_H */ diff --git a/src/include/gpxe/errfile.h b/src/include/gpxe/errfile.h index 6b7f3a4b..df3717f1 100644 --- a/src/include/gpxe/errfile.h +++ b/src/include/gpxe/errfile.h @@ -167,6 +167,7 @@ #define ERRFILE_smbios ( ERRFILE_OTHER | 0x00120000 ) #define ERRFILE_smbios_settings ( ERRFILE_OTHER | 0x00130000 ) #define ERRFILE_efi_smbios ( ERRFILE_OTHER | 0x00140000 ) +#define ERRFILE_pxemenu ( ERRFILE_OTHER | 0x00150000 ) /** @} */ diff --git a/src/include/gpxe/fakedhcp.h b/src/include/gpxe/fakedhcp.h index 990b56af..550b74f7 100644 --- a/src/include/gpxe/fakedhcp.h +++ b/src/include/gpxe/fakedhcp.h @@ -15,7 +15,7 @@ extern int create_fakedhcpdiscover ( struct net_device *netdev, void *data, size_t max_len ); extern int create_fakedhcpack ( struct net_device *netdev, void *data, size_t max_len ); -extern int create_fakeproxydhcpack ( struct net_device *netdev, - void *data, size_t max_len ); +extern int create_fakepxebsack ( struct net_device *netdev, + void *data, size_t max_len ); #endif /* _GPXE_FAKEDHCP_H */ diff --git a/src/include/gpxe/settings.h b/src/include/gpxe/settings.h index 60244d3a..bf80b1e1 100644 --- a/src/include/gpxe/settings.h +++ b/src/include/gpxe/settings.h @@ -304,4 +304,16 @@ static inline int delete_named_setting ( const char *name ) { return storef_named_setting ( name, NULL ); } +/** + * Check existence of setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @ret exists Setting exists + */ +static inline int setting_exists ( struct settings *settings, + struct setting *setting ) { + return ( fetch_setting_len ( settings, setting ) >= 0 ); +} + #endif /* _GPXE_SETTINGS_H */ diff --git a/src/include/usr/autoboot.h b/src/include/usr/autoboot.h index b64cbb8e..1e9647c3 100644 --- a/src/include/usr/autoboot.h +++ b/src/include/usr/autoboot.h @@ -7,9 +7,15 @@ * */ +#include +struct net_device; + extern int shutdown_exit_flags; extern void autoboot ( void ); +extern int boot_next_server_and_filename ( struct in_addr next_server, + const char *filename ); extern int boot_root_path ( const char *root_path ); +extern int pxe_menu_boot ( struct net_device *netdev ); #endif /* _USR_AUTOBOOT_H */ diff --git a/src/include/usr/dhcpmgmt.h b/src/include/usr/dhcpmgmt.h index 2757a1c1..dc9de7bb 100644 --- a/src/include/usr/dhcpmgmt.h +++ b/src/include/usr/dhcpmgmt.h @@ -9,6 +9,8 @@ struct net_device; -int dhcp ( struct net_device *netdev ); +extern int dhcp ( struct net_device *netdev ); +extern int pxebs ( struct net_device *netdev, struct in_addr pxe_server, + unsigned int pxe_type ); #endif /* _USR_DHCPMGMT_H */ diff --git a/src/net/dhcppkt.c b/src/net/dhcppkt.c index c8bf215b..1f2d373c 100644 --- a/src/net/dhcppkt.c +++ b/src/net/dhcppkt.c @@ -32,6 +32,12 @@ * */ +/**************************************************************************** + * + * DHCP packet raw interface + * + */ + /** * Calculate used length of an IPv4 field within a DHCP packet * @@ -193,21 +199,79 @@ int dhcppkt_fetch ( struct dhcp_packet *dhcppkt, unsigned int tag, return dhcpopt_fetch ( &dhcppkt->options, tag, data, len ); } -/** - * Initialise prepopulated DHCP packet +/**************************************************************************** * - * @v dhcppkt Uninitialised DHCP packet - * @v data Memory for DHCP packet data - * @v max_len Length of memory for DHCP packet data + * DHCP packet settings interface * - * The memory content must already be filled with valid DHCP options. - * A zeroed block counts as a block of valid DHCP options. */ -void dhcppkt_init ( struct dhcp_packet *dhcppkt, void *data, size_t len ) { + +/** + * Store value of DHCP 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 dhcppkt_settings_store ( struct settings *settings, + struct setting *setting, + const void *data, size_t len ) { + struct dhcp_packet *dhcppkt = + container_of ( settings, struct dhcp_packet, settings ); + + return dhcppkt_store ( dhcppkt, setting->tag, data, len ); +} + +/** + * Fetch value of DHCP 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 dhcppkt_settings_fetch ( struct settings *settings, + struct setting *setting, + void *data, size_t len ) { + struct dhcp_packet *dhcppkt = + container_of ( settings, struct dhcp_packet, settings ); + + return dhcppkt_fetch ( dhcppkt, setting->tag, data, len ); +} + +/** DHCP settings operations */ +static struct settings_operations dhcppkt_settings_operations = { + .store = dhcppkt_settings_store, + .fetch = dhcppkt_settings_fetch, +}; + +/**************************************************************************** + * + * Constructor + * + */ + +/** + * Initialise DHCP packet + * + * @v dhcppkt DHCP packet structure to fill in + * @v data DHCP packet raw data + * @v max_len Length of raw data buffer + * + * Initialise a DHCP packet structure from a data buffer containing a + * DHCP packet. + */ +void dhcppkt_init ( struct dhcp_packet *dhcppkt, struct dhcphdr *data, + size_t len ) { dhcppkt->dhcphdr = data; dhcppkt->max_len = len; dhcpopt_init ( &dhcppkt->options, &dhcppkt->dhcphdr->options, ( len - offsetof ( struct dhcphdr, options ) ) ); dhcppkt->len = ( offsetof ( struct dhcphdr, options ) + dhcppkt->options.len ); + settings_init ( &dhcppkt->settings, + &dhcppkt_settings_operations, &dhcppkt->refcnt, + DHCP_SETTINGS_NAME, 0 ); } diff --git a/src/net/fakedhcp.c b/src/net/fakedhcp.c index 2fb08c69..0518789c 100644 --- a/src/net/fakedhcp.c +++ b/src/net/fakedhcp.c @@ -108,12 +108,11 @@ 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 dummy_addr = { 0 }; + struct in_addr ciaddr = { 0 }; int rc; if ( ( rc = dhcp_create_request ( &dhcppkt, netdev, DHCPDISCOVER, - dummy_addr, dummy_addr, dummy_addr, - NULL, data, max_len ) ) != 0 ) { + ciaddr, data, max_len ) ) != 0 ) { DBG ( "Could not create DHCPDISCOVER: %s\n", strerror ( rc ) ); return rc; @@ -138,7 +137,7 @@ int create_fakedhcpack ( struct net_device *netdev, int rc; /* Create base DHCPACK packet */ - if ( ( rc = dhcp_create_packet ( &dhcppkt, netdev, DHCPACK, NULL, + if ( ( rc = dhcp_create_packet ( &dhcppkt, netdev, DHCPACK, NULL, 0, data, max_len ) ) != 0 ) { DBG ( "Could not create DHCPACK: %s\n", strerror ( rc ) ); return rc; @@ -164,7 +163,7 @@ int create_fakedhcpack ( struct net_device *netdev, } /** - * Create ProxyDHCPACK packet + * Create fake PXE Boot Server ACK packet * * @v netdev Network device * @v data Buffer for DHCP packet @@ -173,43 +172,43 @@ int create_fakedhcpack ( struct net_device *netdev, * * Used by external code. */ -int create_fakeproxydhcpack ( struct net_device *netdev, - void *data, size_t max_len ) { +int create_fakepxebsack ( struct net_device *netdev, + void *data, size_t max_len ) { struct dhcp_packet dhcppkt; - struct settings *settings; - struct settings *bs_settings; + struct settings *proxy_settings; + struct settings *pxebs_settings; int rc; - /* Identify ProxyDHCP settings */ - settings = find_settings ( PROXYDHCP_SETTINGS_NAME ); - - /* No ProxyDHCP settings => use normal DHCPACK */ - if ( ! settings ) + /* Identify available settings */ + proxy_settings = find_settings ( PROXYDHCP_SETTINGS_NAME ); + pxebs_settings = find_settings ( PXEBS_SETTINGS_NAME ); + if ( ( ! proxy_settings ) && ( ! pxebs_settings ) ) { + /* No PXE boot server; return the regular DHCPACK */ return create_fakedhcpack ( netdev, data, max_len ); + } /* Create base DHCPACK packet */ - if ( ( rc = dhcp_create_packet ( &dhcppkt, netdev, DHCPACK, NULL, + if ( ( rc = dhcp_create_packet ( &dhcppkt, netdev, DHCPACK, NULL, 0, data, max_len ) ) != 0 ) { - DBG ( "Could not create ProxyDHCPACK: %s\n", + DBG ( "Could not create PXE BS ACK: %s\n", strerror ( rc ) ); return rc; } /* Merge in ProxyDHCP options */ - if ( ( rc = copy_settings ( &dhcppkt, settings ) ) != 0 ) { - DBG ( "Could not set ProxyDHCPACK settings: %s\n", + if ( proxy_settings && + ( ( rc = copy_settings ( &dhcppkt, proxy_settings ) ) != 0 ) ) { + DBG ( "Could not copy ProxyDHCP settings: %s\n", strerror ( rc ) ); return rc; } /* Merge in BootServerDHCP options, if present */ - bs_settings = find_settings ( BSDHCP_SETTINGS_NAME ); - if ( bs_settings ) { - if ( ( rc = copy_settings ( &dhcppkt, bs_settings ) ) != 0 ) { - DBG ( "Could not set BootServerDHCPACK settings: " - "%s\n", strerror ( rc ) ); - return rc; - } + if ( pxebs_settings && + ( ( rc = copy_settings ( &dhcppkt, pxebs_settings ) ) != 0 ) ) { + DBG ( "Could not copy PXE BS settings: %s\n", + strerror ( rc ) ); + return rc; } return 0; diff --git a/src/net/udp/dhcp.c b/src/net/udp/dhcp.c index c7e1f886..3554b405 100644 --- a/src/net/udp/dhcp.c +++ b/src/net/udp/dhcp.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -34,14 +33,12 @@ #include #include #include -#include #include #include #include #include #include #include -#include /** @file * @@ -49,6 +46,9 @@ * */ +struct dhcp_session; +static int dhcp_tx ( struct dhcp_session *dhcp ); + /** * DHCP operation types * @@ -86,13 +86,6 @@ static uint8_t dhcp_request_options_data[] = { DHCP_END }; -/** Options common to all DHCP requests */ -static struct dhcp_options dhcp_request_options = { - .data = dhcp_request_options_data, - .max_len = sizeof ( dhcp_request_options_data ), - .len = sizeof ( dhcp_request_options_data ), -}; - /** DHCP feature codes */ 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 ); @@ -100,77 +93,13 @@ static uint8_t dhcp_features_end[0] __table_end ( uint8_t, dhcp_features ); /** Version number feature */ FEATURE_VERSION ( VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH ); -/** 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 - -/** 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" - * - * This field actually identifies the specific boot server (or - * cluster of boot servers offering identical boot files). - */ - uint16_t type; - /** "Layer" - * - * Just don't ask. - */ - uint16_t layer; -} __attribute__ (( packed )); - -/** Maximum allowed number of PXE boot menu items */ -#define PXE_BOOT_MENU_MAX_ITEMS 20 +/** DHCP server address setting */ +struct setting dhcp_server_setting __setting = { + .name = "dhcp-server", + .description = "DHCP server address", + .tag = DHCP_SERVER_IDENTIFIER, + .type = &setting_type_ipv4, +}; /** * Name a DHCP packet type @@ -210,149 +139,54 @@ static uint32_t dhcp_xid ( struct net_device *netdev ) { return xid; } -/**************************************************************************** - * - * DHCP settings - * - */ - -/** A DHCP settings block */ -struct dhcp_settings { - /** Reference counter */ - struct refcnt refcnt; - /** DHCP packet */ - struct dhcp_packet dhcppkt; - /** Setting interface */ - 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 __attribute__ (( always_inline )) void -dhcpset_put ( struct dhcp_settings *dhcpset ) { - ref_put ( &dhcpset->refcnt ); -} - -/** - * Store value of DHCP 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 dhcpset_store ( struct settings *settings, struct setting *setting, - const void *data, size_t len ) { - struct dhcp_settings *dhcpset = - container_of ( settings, struct dhcp_settings, settings ); - - return dhcppkt_store ( &dhcpset->dhcppkt, setting->tag, data, len ); -} - -/** - * Fetch value of DHCP 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 dhcpset_fetch ( struct settings *settings, struct setting *setting, - void *data, size_t len ) { - struct dhcp_settings *dhcpset = - container_of ( settings, struct dhcp_settings, settings ); - - return dhcppkt_fetch ( &dhcpset->dhcppkt, setting->tag, data, len ); -} - -/** DHCP settings operations */ -static struct settings_operations dhcpset_settings_operations = { - .store = dhcpset_store, - .fetch = dhcpset_fetch, -}; - -/** - * Create DHCP setting block - * - * @v dhcphdr DHCP packet - * @v len Length of DHCP packet - * @ret dhcpset DHCP settings block - */ -static struct dhcp_settings * dhcpset_create ( const struct dhcphdr *dhcphdr, - size_t len ) { - struct dhcp_settings *dhcpset; - void *data; - - dhcpset = zalloc ( sizeof ( *dhcpset ) + len ); - if ( dhcpset ) { - data = ( ( ( void * ) dhcpset ) + sizeof ( *dhcpset ) ); - memcpy ( data, dhcphdr, len ); - dhcppkt_init ( &dhcpset->dhcppkt, data, len ); - settings_init ( &dhcpset->settings, - &dhcpset_settings_operations, &dhcpset->refcnt, - DHCP_SETTINGS_NAME, 0 ); - } - return dhcpset; -} - -/** DHCP server address setting */ -struct setting dhcp_server_setting __setting = { - .name = "dhcp-server", - .description = "DHCP server address", - .tag = DHCP_SERVER_IDENTIFIER, - .type = &setting_type_ipv4, -}; - /**************************************************************************** * * 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, - /** Sending BootServerDHCPREQUESTs, waiting for BootServerDHCPACK */ - DHCP_STATE_BSREQUEST, +struct dhcp_session; + +/** DHCP session state operations */ +struct dhcp_session_state { + /** State name */ + const char *name; + /** + * Construct transmitted packet + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer Destination address + */ + int ( * tx ) ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer ); + /** Handle received packet + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer DHCP server address + * @v msgtype DHCP message type + */ + void ( * rx ) ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer, + uint8_t msgtype ); + /** Handle timer expiry + * + * @v dhcp DHCP session + */ + void ( * expired ) ( struct dhcp_session *dhcp ); + /** Transmitted message type */ + uint8_t tx_msgtype; + /** Apply minimum timeout */ + uint8_t apply_min_timeout; }; -/** - * 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"; - case DHCP_STATE_BSREQUEST: return "BootServerREQUEST"; - default: return ""; - } -} +static struct dhcp_session_state dhcp_state_discover; +static struct dhcp_session_state dhcp_state_request; +static struct dhcp_session_state dhcp_state_proxy; +static struct dhcp_session_state dhcp_state_pxebs; /** A DHCP session */ struct dhcp_session { @@ -365,27 +199,29 @@ struct dhcp_session { /** Network device being configured */ struct net_device *netdev; + /** Local socket address */ + struct sockaddr_in local; + /** State of the session */ + struct dhcp_session_state *state; - /** State of the session - * - * This is a value for the @c DHCP_MESSAGE_TYPE option - * (e.g. @c DHCPDISCOVER). - */ - enum dhcp_session_state state; - /** DHCPOFFER obtained during DHCPDISCOVER containing IP address */ - struct dhcp_settings *dhcpoffer; - /** DHCPOFFER obtained during DHCPDISCOVER containing "PXEClient" */ - struct dhcp_settings *pxedhcpoffer; - /** DHCPACK obtained during DHCPREQUEST containing IP address */ - struct dhcp_settings *dhcpack; - /** DHCPACK obtained during DHCPREQUEST or ProxyDHCPREQUEST - * containing "PXEClient" - */ - struct dhcp_settings *pxedhcpack; - /** BootServerDHCPACK obtained during BootServerDHCPREQUEST */ - struct dhcp_settings *bsdhcpack; - /** PXE boot menu item */ - struct dhcp_pxe_boot_menu_item menu_item; + /** Offered IP address */ + struct in_addr offer; + /** DHCP server */ + struct in_addr server; + /** DHCP offer priority */ + int priority; + + /** ProxyDHCP protocol extensions should be ignored */ + int no_pxedhcp; + /** ProxyDHCP server */ + struct in_addr proxy_server; + /** ProxyDHCP server priority */ + int proxy_priority; + + /** PXE Boot Server */ + struct in_addr pxe_server; + /** PXE Boot Server type */ + uint16_t pxe_type; /** Retransmission timer */ struct retry_timer timer; @@ -403,11 +239,6 @@ static void dhcp_free ( struct refcnt *refcnt ) { container_of ( refcnt, struct dhcp_session, refcnt ); netdev_put ( dhcp->netdev ); - dhcpset_put ( dhcp->dhcpoffer ); - dhcpset_put ( dhcp->pxedhcpoffer ); - dhcpset_put ( dhcp->dhcpack ); - dhcpset_put ( dhcp->pxedhcpack ); - dhcpset_put ( dhcp->bsdhcpack ); free ( dhcp ); } @@ -431,9 +262,495 @@ static void dhcp_finished ( struct dhcp_session *dhcp, int rc ) { job_done ( &dhcp->job, rc ); } +/** + * Transition to new DHCP session state + * + * @v dhcp DHCP session + * @v state New session state + */ +static void dhcp_set_state ( struct dhcp_session *dhcp, + struct dhcp_session_state *state ) { + + DBGC ( dhcp, "DHCP %p entering %s state\n", dhcp, state->name ); + dhcp->state = state; + dhcp->start = currticks(); + stop_timer ( &dhcp->timer ); + dhcp->timer.min_timeout = + ( state->apply_min_timeout ? DHCP_MIN_TIMEOUT : 0 ); + dhcp->timer.max_timeout = DHCP_MAX_TIMEOUT; + start_timer_nodelay ( &dhcp->timer ); +} + /**************************************************************************** * - * Data transfer interface + * DHCP state machine + * + */ + +/** + * Construct transmitted packet for DHCP discovery + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer Destination address + */ +static int dhcp_discovery_tx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt __unused, + struct sockaddr_in *peer ) { + + DBGC ( dhcp, "DHCP %p DHCPDISCOVER\n", dhcp ); + + /* Set server address */ + peer->sin_addr.s_addr = INADDR_BROADCAST; + peer->sin_port = htons ( BOOTPS_PORT ); + + return 0; +} + +/** + * Handle received packet during DHCP discovery + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer DHCP server address + * @v msgtype DHCP message type + */ +static void dhcp_discovery_rx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer, uint8_t msgtype ) { + struct in_addr server_id = { 0 }; + struct in_addr ip; + char vci[9]; /* "PXEClient" */ + int vci_len; + int has_pxeclient; + int8_t priority = 0; + uint8_t no_pxedhcp = 0; + unsigned long elapsed; + + DBGC ( dhcp, "DHCP %p %s from %s:%d", dhcp, + dhcp_msgtype_name ( msgtype ), inet_ntoa ( peer->sin_addr ), + ntohs ( peer->sin_port ) ); + + /* Identify server ID */ + dhcppkt_fetch ( dhcppkt, DHCP_SERVER_IDENTIFIER, + &server_id, sizeof ( server_id ) ); + if ( server_id.s_addr != peer->sin_addr.s_addr ) + DBGC ( dhcp, " (%s)", inet_ntoa ( server_id ) ); + + /* Identify offered IP address */ + ip = dhcppkt->dhcphdr->yiaddr; + if ( ip.s_addr ) + DBGC ( dhcp, " for %s", inet_ntoa ( ip ) ); + + /* Identify "PXEClient" vendor class */ + vci_len = dhcppkt_fetch ( dhcppkt, DHCP_VENDOR_CLASS_ID, + vci, sizeof ( vci ) ); + has_pxeclient = ( ( vci_len >= ( int ) sizeof ( vci ) ) && + ( strncmp ( "PXEClient", vci, sizeof (vci) ) == 0 )); + if ( has_pxeclient ) + DBGC ( dhcp, " pxe" ); + + /* Identify priority */ + dhcppkt_fetch ( dhcppkt, DHCP_EB_PRIORITY, &priority, + sizeof ( priority ) ); + if ( priority ) + DBGC ( dhcp, " pri %d", priority ); + + /* Identify ignore-PXE flag */ + dhcppkt_fetch ( dhcppkt, DHCP_EB_NO_PXEDHCP, &no_pxedhcp, + sizeof ( no_pxedhcp ) ); + if ( no_pxedhcp ) + DBGC ( dhcp, " nopxe" ); + DBGC ( dhcp, "\n" ); + + /* Select as DHCP offer, if applicable */ + if ( ip.s_addr && ( peer->sin_port == htons ( BOOTPS_PORT ) ) && + ( ( msgtype == DHCPOFFER ) || ( ! msgtype /* BOOTP */ ) ) && + ( priority >= dhcp->priority ) ) { + dhcp->offer = ip; + dhcp->server = server_id; + dhcp->priority = priority; + dhcp->no_pxedhcp = no_pxedhcp; + } + + /* Select as ProxyDHCP offer, if applicable */ + if ( has_pxeclient && ( msgtype == DHCPOFFER ) && + ( priority >= dhcp->proxy_priority ) ) { + dhcp->proxy_server = server_id; + dhcp->proxy_priority = priority; + } + + /* We can exit the discovery state when we have a valid + * DHCPOFFER, and either: + * + * o The DHCPOFFER instructs us to ignore ProxyDHCPOFFERs, 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->offer.s_addr ) + return; + + /* If we can't yet transition to DHCPREQUEST, do nothing */ + elapsed = ( currticks() - dhcp->start ); + if ( ! ( dhcp->no_pxedhcp || dhcp->proxy_server.s_addr || + ( elapsed > PROXYDHCP_MAX_TIMEOUT ) ) ) + return; + + /* Transition to DHCPREQUEST */ + dhcp_set_state ( dhcp, &dhcp_state_request ); +} + +/** + * Handle timer expiry during DHCP discovery + * + * @v dhcp DHCP session + */ +static void dhcp_discovery_expired ( struct dhcp_session *dhcp ) { + unsigned long elapsed = ( currticks() - dhcp->start ); + + /* Give up waiting for ProxyDHCP before we reach the failure point */ + if ( dhcp->offer.s_addr && ( elapsed > PROXYDHCP_MAX_TIMEOUT ) ) { + dhcp_set_state ( dhcp, &dhcp_state_request ); + return; + } + + /* Otherwise, retransmit current packet */ + dhcp_tx ( dhcp ); +} + +/** DHCP discovery state operations */ +static struct dhcp_session_state dhcp_state_discover = { + .name = "discovery", + .tx = dhcp_discovery_tx, + .rx = dhcp_discovery_rx, + .expired = dhcp_discovery_expired, + .tx_msgtype = DHCPDISCOVER, + .apply_min_timeout = 1, +}; + +/** + * Construct transmitted packet for DHCP request + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer Destination address + */ +static int dhcp_request_tx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer ) { + int rc; + + DBGC ( dhcp, "DHCP %p DHCPREQUEST to %s:%d", + dhcp, inet_ntoa ( dhcp->server ), BOOTPS_PORT ); + DBGC ( dhcp, " for %s\n", inet_ntoa ( dhcp->offer ) ); + + /* Set server ID */ + if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_SERVER_IDENTIFIER, + &dhcp->server, + sizeof ( dhcp->server ) ) ) != 0 ) + return rc; + + /* Set requested IP address */ + if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_REQUESTED_ADDRESS, + &dhcp->offer, + sizeof ( dhcp->offer ) ) ) != 0 ) + return rc; + + /* Set server address */ + peer->sin_addr.s_addr = INADDR_BROADCAST; + peer->sin_port = htons ( BOOTPS_PORT ); + + return 0; +} + +/** + * Handle received packet during DHCP request + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer DHCP server address + * @v msgtype DHCP message type + */ +static void dhcp_request_rx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer, uint8_t msgtype ) { + struct in_addr server_id = { 0 }; + struct in_addr ip; + struct settings *parent; + int rc; + + DBGC ( dhcp, "DHCP %p %s from %s:%d", dhcp, + dhcp_msgtype_name ( msgtype ), inet_ntoa ( peer->sin_addr ), + ntohs ( peer->sin_port ) ); + + /* Identify server ID */ + dhcppkt_fetch ( dhcppkt, DHCP_SERVER_IDENTIFIER, + &server_id, sizeof ( server_id ) ); + if ( server_id.s_addr != peer->sin_addr.s_addr ) + DBGC ( dhcp, " (%s)", inet_ntoa ( server_id ) ); + + /* Identify leased IP address */ + ip = dhcppkt->dhcphdr->yiaddr; + if ( ip.s_addr ) + DBGC ( dhcp, " for %s", inet_ntoa ( ip ) ); + DBGC ( dhcp, "\n" ); + + /* Filter out unacceptable responses */ + if ( peer->sin_port != htons ( BOOTPS_PORT ) ) + return; + if ( msgtype /* BOOTP */ && ( msgtype != DHCPACK ) ) + return; + if ( server_id.s_addr != dhcp->server.s_addr ) + return; + + /* Record assigned address */ + dhcp->local.sin_addr = ip; + + /* Register settings */ + parent = netdev_settings ( dhcp->netdev ); + if ( ( rc = register_settings ( &dhcppkt->settings, parent ) ) != 0 ){ + DBGC ( dhcp, "DHCP %p could not register settings: %s\n", + dhcp, strerror ( rc ) ); + dhcp_finished ( dhcp, rc ); + return; + } + + /* Start ProxyDHCPREQUEST if applicable */ + if ( dhcp->proxy_server.s_addr && ( ! dhcp->no_pxedhcp ) ) { + dhcp_set_state ( dhcp, &dhcp_state_proxy ); + return; + } + + /* Terminate DHCP */ + dhcp_finished ( dhcp, 0 ); +} + +/** + * Handle timer expiry during DHCP discovery + * + * @v dhcp DHCP session + */ +static void dhcp_request_expired ( struct dhcp_session *dhcp ) { + + /* Retransmit current packet */ + dhcp_tx ( dhcp ); +} + +/** DHCP request state operations */ +static struct dhcp_session_state dhcp_state_request = { + .name = "request", + .tx = dhcp_request_tx, + .rx = dhcp_request_rx, + .expired = dhcp_request_expired, + .tx_msgtype = DHCPREQUEST, + .apply_min_timeout = 0, +}; + +/** + * Construct transmitted packet for ProxyDHCP request + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer Destination address + */ +static int dhcp_proxy_tx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer ) { + int rc; + + DBGC ( dhcp, "DHCP %p ProxyDHCP REQUEST to %s:%d\n", + dhcp, inet_ntoa ( dhcp->proxy_server ), PXE_PORT ); + + /* Set server ID */ + if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_SERVER_IDENTIFIER, + &dhcp->proxy_server, + sizeof ( dhcp->proxy_server ) ) ) != 0 ) + return rc; + + /* Set server address */ + peer->sin_addr = dhcp->proxy_server; + peer->sin_port = htons ( PXE_PORT ); + + return 0; +} + +/** + * Handle received packet during ProxyDHCP request + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer DHCP server address + * @v msgtype DHCP message type + */ +static void dhcp_proxy_rx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer, uint8_t msgtype ) { + struct in_addr server_id = { 0 }; + int rc; + + DBGC ( dhcp, "DHCP %p %s from %s:%d", dhcp, + dhcp_msgtype_name ( msgtype ), inet_ntoa ( peer->sin_addr ), + ntohs ( peer->sin_port ) ); + + /* Identify server ID */ + dhcppkt_fetch ( dhcppkt, DHCP_SERVER_IDENTIFIER, + &server_id, sizeof ( server_id ) ); + if ( server_id.s_addr != peer->sin_addr.s_addr ) + DBGC ( dhcp, " (%s)", inet_ntoa ( server_id ) ); + DBGC ( dhcp, "\n" ); + + /* Filter out unacceptable responses */ + if ( peer->sin_port != htons ( PXE_PORT ) ) + return; + if ( msgtype != DHCPACK ) + return; + if ( server_id.s_addr /* Linux PXE server omits server ID */ && + ( server_id.s_addr != dhcp->proxy_server.s_addr ) ) + return; + + /* Register settings */ + dhcppkt->settings.name = PROXYDHCP_SETTINGS_NAME; + if ( ( rc = register_settings ( &dhcppkt->settings, NULL ) ) != 0 ) { + DBGC ( dhcp, "DHCP %p could not register settings: %s\n", + dhcp, strerror ( rc ) ); + dhcp_finished ( dhcp, rc ); + return; + } + + /* Terminate DHCP */ + dhcp_finished ( dhcp, 0 ); +} + +/** + * Handle timer expiry during ProxyDHCP request + * + * @v dhcp DHCP session + */ +static void dhcp_proxy_expired ( struct dhcp_session *dhcp ) { + unsigned long elapsed = ( currticks() - dhcp->start ); + + /* Give up waiting for ProxyDHCP before we reach the failure point */ + if ( elapsed > PROXYDHCP_MAX_TIMEOUT ) { + dhcp_finished ( dhcp, 0 ); + return; + } + + /* Retransmit current packet */ + dhcp_tx ( dhcp ); +} + +/** ProxyDHCP request state operations */ +static struct dhcp_session_state dhcp_state_proxy = { + .name = "ProxyDHCP", + .tx = dhcp_proxy_tx, + .rx = dhcp_proxy_rx, + .expired = dhcp_proxy_expired, + .tx_msgtype = DHCPREQUEST, + .apply_min_timeout = 0, +}; + +/** + * Construct transmitted packet for PXE Boot Server Discovery + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer Destination address + */ +static int dhcp_pxebs_tx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer ) { + struct dhcp_pxe_boot_menu_item menu_item = { 0, 0 }; + int rc; + + DBGC ( dhcp, "DHCP %p PXEBS REQUEST to %s:%d for type %d\n", + dhcp, inet_ntoa ( dhcp->pxe_server ), PXE_PORT, + ntohs ( dhcp->pxe_type ) ); + + /* Set boot menu item */ + menu_item.type = dhcp->pxe_type; + if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_PXE_BOOT_MENU_ITEM, + &menu_item, sizeof ( menu_item ) ) ) != 0 ) + return rc; + + /* Set server address */ + peer->sin_addr = dhcp->pxe_server; + peer->sin_port = htons ( PXE_PORT ); + + return 0; +} + +/** + * Handle received packet during PXE Boot Server Discovery + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer DHCP server address + * @v msgtype DHCP message type + */ +static void dhcp_pxebs_rx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer, uint8_t msgtype ) { + struct dhcp_pxe_boot_menu_item menu_item = { 0, 0 }; + int rc; + + DBGC ( dhcp, "DHCP %p %s from %s:%d", dhcp, + dhcp_msgtype_name ( msgtype ), inet_ntoa ( peer->sin_addr ), + ntohs ( peer->sin_port ) ); + + /* Identify boot menu item */ + dhcppkt_fetch ( dhcppkt, DHCP_PXE_BOOT_MENU_ITEM, + &menu_item, sizeof ( menu_item ) ); + if ( menu_item.type ) + DBGC ( dhcp, " for type %d", ntohs ( menu_item.type ) ); + DBGC ( dhcp, "\n" ); + + /* Filter out unacceptable responses */ + if ( peer->sin_port != htons ( PXE_PORT ) ) + return; + if ( msgtype != DHCPACK ) + return; + if ( menu_item.type != dhcp->pxe_type ) + return; + + /* Register settings */ + dhcppkt->settings.name = PXEBS_SETTINGS_NAME; + if ( ( rc = register_settings ( &dhcppkt->settings, NULL ) ) != 0 ) { + DBGC ( dhcp, "DHCP %p could not register settings: %s\n", + dhcp, strerror ( rc ) ); + dhcp_finished ( dhcp, rc ); + return; + } + + /* Terminate DHCP */ + dhcp_finished ( dhcp, 0 ); +} + +/** + * Handle timer expiry during PXE Boot Server Discovery + * + * @v dhcp DHCP session + */ +static void dhcp_pxebs_expired ( struct dhcp_session *dhcp ) { + + /* Retransmit current packet */ + dhcp_tx ( dhcp ); +} + +/** PXE Boot Server Discovery state operations */ +static struct dhcp_session_state dhcp_state_pxebs = { + .name = "PXEBS", + .tx = dhcp_pxebs_tx, + .rx = dhcp_pxebs_rx, + .expired = dhcp_pxebs_expired, + .tx_msgtype = DHCPREQUEST, + .apply_min_timeout = 1, +}; + +/**************************************************************************** + * + * Packet construction * */ @@ -444,24 +761,23 @@ static void dhcp_finished ( struct dhcp_session *dhcp, int rc ) { * @v netdev Network device * @v msgtype DHCP message type * @v options Initial options to include (or NULL) + * @v options_len Length of initial options * @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. + * Creates a DHCP packet in the specified buffer, and initialise a + * DHCP packet structure. */ int dhcp_create_packet ( struct dhcp_packet *dhcppkt, struct net_device *netdev, uint8_t msgtype, - struct dhcp_options *options, + const void *options, size_t options_len, 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; @@ -481,7 +797,7 @@ int dhcp_create_packet ( struct dhcp_packet *dhcppkt, } dhcphdr->hlen = hlen; memcpy ( dhcphdr->chaddr, netdev->ll_addr, hlen ); - memcpy ( dhcphdr->options, options->data, options_len ); + memcpy ( dhcphdr->options, options, options_len ); /* Initialise DHCP packet structure */ memset ( dhcppkt, 0, sizeof ( *dhcppkt ) ); @@ -501,20 +817,17 @@ int dhcp_create_packet ( struct dhcp_packet *dhcppkt, * @v dhcppkt DHCP packet structure to fill in * @v netdev Network device * @v msgtype DHCP message type - * @v ciaddr Client IP address, if applicable - * @v server Server identifier, if applicable - * @v requested_ip Requested address, if applicable - * @v menu_item PXE menu item, if applicable + * @v ciaddr Client IP address * @v data Buffer for DHCP packet * @v max_len Size of DHCP packet buffer * @ret rc Return status code + * + * Creates a DHCP request packet in the specified buffer, and + * initialise a DHCP packet structure. */ int dhcp_create_request ( struct dhcp_packet *dhcppkt, struct net_device *netdev, unsigned int msgtype, - struct in_addr ciaddr, struct in_addr server, - struct in_addr requested_ip, - struct dhcp_pxe_boot_menu_item *menu_item, - void *data, size_t max_len ) { + struct in_addr ciaddr, 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; @@ -525,8 +838,9 @@ int dhcp_create_request ( struct dhcp_packet *dhcppkt, /* Create DHCP packet */ if ( ( rc = dhcp_create_packet ( dhcppkt, netdev, msgtype, - &dhcp_request_options, data, - max_len ) ) != 0 ) { + dhcp_request_options_data, + sizeof ( dhcp_request_options_data ), + data, max_len ) ) != 0 ) { DBG ( "DHCP could not create DHCP packet: %s\n", strerror ( rc ) ); return rc; @@ -535,25 +849,6 @@ int dhcp_create_request ( struct dhcp_packet *dhcppkt, /* Set client IP address */ dhcppkt->dhcphdr->ciaddr = ciaddr; - /* Set server ID, if present */ - if ( server.s_addr && - ( ( rc = dhcppkt_store ( dhcppkt, DHCP_SERVER_IDENTIFIER, - &server, sizeof ( server ) ) ) != 0 ) ) { - DBG ( "DHCP could not set server ID: %s\n", - strerror ( rc ) ); - return rc; - } - - /* Set requested IP address, if present */ - if ( requested_ip.s_addr && - ( ( 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, @@ -601,19 +896,15 @@ int dhcp_create_request ( struct dhcp_packet *dhcppkt, } } - /* Set PXE boot menu item, if present */ - if ( menu_item && menu_item->type && - ( ( rc = dhcppkt_store ( dhcppkt, DHCP_PXE_BOOT_MENU_ITEM, - menu_item, - sizeof ( *menu_item ) ) ) != 0 ) ) { - DBG ( "DHCP could not set PXE menu item: %s\n", - strerror ( rc ) ); - return rc; - } - return 0; } +/**************************************************************************** + * + * Data transfer interface + * + */ + /** * Transmit DHCP request * @@ -621,23 +912,17 @@ int dhcp_create_request ( struct dhcp_packet *dhcppkt, * @ret rc Return status code */ static int dhcp_tx ( struct dhcp_session *dhcp ) { - static struct sockaddr_in dest = { + static struct sockaddr_in peer = { .sin_family = AF_INET, - .sin_port = htons ( PXE_PORT ), - }; - static struct sockaddr_in src = { - .sin_family = AF_INET, - .sin_port = htons ( BOOTPC_PORT ), }; struct xfer_metadata meta = { .netdev = dhcp->netdev, + .src = ( struct sockaddr * ) &dhcp->local, + .dest = ( struct sockaddr * ) &peer, }; struct io_buffer *iobuf; + uint8_t msgtype = dhcp->state->tx_msgtype; struct dhcp_packet dhcppkt; - struct in_addr ciaddr = { 0 }; - struct in_addr server = { 0 }; - struct in_addr requested_ip = { 0 }; - unsigned int msgtype; int rc; /* Start retry timer. Do this first so that failures to @@ -645,93 +930,25 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) { */ start_timer ( &dhcp->timer ); - /* Determine packet contents based on current state */ - switch ( dhcp->state ) { - case DHCP_STATE_DISCOVER: - msgtype = DHCPDISCOVER; - break; - case DHCP_STATE_REQUEST: - assert ( dhcp->dhcpoffer ); - msgtype = DHCPREQUEST; - dhcppkt_fetch ( &dhcp->dhcpoffer->dhcppkt, - DHCP_SERVER_IDENTIFIER, &server, - sizeof ( server ) ); - requested_ip = dhcp->dhcpoffer->dhcppkt.dhcphdr->yiaddr; - break; - case DHCP_STATE_PROXYREQUEST: - assert ( dhcp->dhcpoffer ); - assert ( dhcp->pxedhcpoffer ); - assert ( dhcp->dhcpack ); - msgtype = DHCPREQUEST; - ciaddr = dhcp->dhcpoffer->dhcppkt.dhcphdr->yiaddr; - dhcppkt_fetch ( &dhcp->pxedhcpoffer->dhcppkt, - DHCP_SERVER_IDENTIFIER, &dest.sin_addr, - sizeof ( dest.sin_addr ) ); - meta.dest = ( struct sockaddr * ) &dest; - server = dest.sin_addr; - assert ( dest.sin_addr.s_addr ); - assert ( ciaddr.s_addr ); - break; - case DHCP_STATE_BSREQUEST: - assert ( dhcp->dhcpoffer ); - assert ( dhcp->pxedhcpoffer ); - assert ( dhcp->dhcpack ); - assert ( dhcp->pxedhcpack ); - msgtype = DHCPREQUEST; - ciaddr = dhcp->dhcpoffer->dhcppkt.dhcphdr->yiaddr; - dhcppkt_fetch ( &dhcp->pxedhcpack->dhcppkt, - DHCP_PXE_BOOT_SERVER_MCAST, - &dest.sin_addr, sizeof ( dest.sin_addr ) ); - meta.dest = ( struct sockaddr * ) &dest; - assert ( dest.sin_addr.s_addr ); - assert ( ciaddr.s_addr ); - break; - default: - assert ( 0 ); - return -EINVAL; - } - - DBGC ( dhcp, "DHCP %p %s", dhcp, dhcp_msgtype_name ( msgtype ) ); - if ( server.s_addr ) - DBGC ( dhcp, " to %s", inet_ntoa ( server ) ); - if ( meta.dest ) { - if ( dest.sin_addr.s_addr == server.s_addr ) { - DBGC ( dhcp, ":%d (unicast)", - ntohs ( dest.sin_port ) ); - } else { - DBGC ( dhcp, " via %s:%d", inet_ntoa ( dest.sin_addr ), - ntohs ( dest.sin_port ) ); - } - } else { - DBGC ( dhcp, " (broadcast)" ); - } - if ( requested_ip.s_addr ) - DBGC ( dhcp, " for %s", inet_ntoa ( requested_ip ) ); - if ( dhcp->menu_item.type ) { - DBGC ( dhcp, " for item %04x", - ntohs ( dhcp->menu_item.type ) ); - } - DBGC ( dhcp, "\n" ); - /* Allocate buffer for packet */ iobuf = xfer_alloc_iob ( &dhcp->xfer, DHCP_MIN_LEN ); if ( ! iobuf ) return -ENOMEM; - /* Create DHCP packet in temporary buffer */ + /* Create basic DHCP packet in temporary buffer */ if ( ( rc = dhcp_create_request ( &dhcppkt, dhcp->netdev, msgtype, - ciaddr, server, requested_ip, - &dhcp->menu_item, iobuf->data, + dhcp->local.sin_addr, iobuf->data, iob_tailroom ( iobuf ) ) ) != 0 ) { DBGC ( dhcp, "DHCP %p could not construct DHCP request: %s\n", dhcp, strerror ( rc ) ); goto done; } - /* Explicitly specify source address, if available. */ - if ( ciaddr.s_addr ) { - src.sin_addr = ciaddr; - meta.src = ( struct sockaddr * ) &src; + /* Fill in packet based on current state */ + if ( ( rc = dhcp->state->tx ( dhcp, &dhcppkt, &peer ) ) != 0 ) { + DBGC ( dhcp, "DHCP %p could not fill DHCP request: %s\n", + dhcp, strerror ( rc ) ); + goto done; } /* Transmit the packet */ @@ -749,471 +966,6 @@ 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 - * - * @v dhcp DHCP session - * @v state New session state - */ -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(); - dhcp->timer.min_timeout = 0; - start_timer_nodelay ( &dhcp->timer ); -} - -/** - * Transition to next DHCP state - * - * @v dhcp DHCP session - */ -static void dhcp_next_state ( struct dhcp_session *dhcp ) { - - stop_timer ( &dhcp->timer ); - - switch ( dhcp->state ) { - case DHCP_STATE_DISCOVER: - dhcp_set_state ( dhcp, DHCP_STATE_REQUEST ); - break; - case DHCP_STATE_REQUEST: - if ( dhcp->pxedhcpoffer ) { - /* Store DHCPACK as PXEDHCPACK. This handles - * the case in which the DHCP server itself - * responds with "PXEClient" and PXE options - * but there is no ProxyDHCP server resident - * on the machine. - */ - dhcp->pxedhcpack = dhcpset_get ( dhcp->dhcpack ); - dhcp_set_state ( dhcp, DHCP_STATE_PROXYREQUEST ); - break; - } - /* Fall through */ - case DHCP_STATE_PROXYREQUEST: - if ( dhcp->pxedhcpack ) { - dhcp_pxe_boot_menu ( &dhcp->pxedhcpack->dhcppkt, - &dhcp->menu_item ); - if ( dhcp->menu_item.type ) { - dhcp_set_state ( dhcp, DHCP_STATE_BSREQUEST ); - break; - } - } - /* Fall through */ - case DHCP_STATE_BSREQUEST: - dhcp_finished ( dhcp, 0 ); - break; - default: - assert ( 0 ); - return; - } - -} - -/** - * 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 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 ) { - struct in_addr server_id = { 0 }; - char vci[9]; /* "PXEClient" */ - int len; - uint8_t ignore_pxe = 0; - unsigned long elapsed; - - /* Check for presence of DHCP server ID */ - if ( dhcppkt_fetch ( &dhcpoffer->dhcppkt, DHCP_SERVER_IDENTIFIER, - &server_id, sizeof ( server_id ) ) - != sizeof ( server_id ) ) { - DBGC ( dhcp, "DHCP %p DHCPOFFER %p missing server ID\n", - dhcp, dhcpoffer ); - /* Could be a valid BOOTP offer; do not abort processing */ - } - - /* If there is an IP address, it's a normal DHCPOFFER */ - if ( dhcpoffer->dhcppkt.dhcphdr->yiaddr.s_addr != 0 ) { - DBGC ( dhcp, "DHCP %p DHCPOFFER %p from %s", - dhcp, dhcpoffer, inet_ntoa ( server_id ) ); - DBGC ( dhcp, " has IP %s\n", - inet_ntoa ( dhcpoffer->dhcppkt.dhcphdr->yiaddr ) ); - dhcp_store_dhcpoffer ( dhcp, dhcpoffer, &dhcp->dhcpoffer ); - } - - /* If there is a "PXEClient" vendor class ID, it's a - * PXEDHCPOFFER. Note that it could be both a normal - * DHCPOFFER and a PXEDHCPOFFER. - */ - len = dhcppkt_fetch ( &dhcpoffer->dhcppkt, DHCP_VENDOR_CLASS_ID, - vci, sizeof ( vci ) ); - if ( ( server_id.s_addr != 0 ) && - ( len >= ( int ) sizeof ( vci ) ) && - ( strncmp ( "PXEClient", vci, sizeof ( vci ) ) == 0 ) ) { - DBGC ( dhcp, "DHCP %p DHCPOFFER %p from %s has PXE options\n", - dhcp, dhcpoffer, inet_ntoa ( server_id ) ); - dhcp_store_dhcpoffer ( dhcp, dhcpoffer, - &dhcp->pxedhcpoffer ); - } - - /* We can transition to making the DHCPREQUEST when we have a - * valid DHCPOFFER, and either: - * - * o The DHCPOFFER instructs us to ignore PXEDHCPOFFERs, or - * o We have a valid PXEDHCPOFFER, 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 PXEDHCP, discard - * any PXEDHCPOFFER - */ - dhcppkt_fetch ( &dhcp->dhcpoffer->dhcppkt, DHCP_EB_NO_PXEDHCP, - &ignore_pxe, sizeof ( ignore_pxe ) ); - if ( ignore_pxe && dhcp->pxedhcpoffer ) { - DBGC ( dhcp, "DHCP %p discarding PXEDHCPOFFER\n", dhcp ); - dhcpset_put ( dhcp->pxedhcpoffer ); - dhcp->pxedhcpoffer = NULL; - } - - /* If we can't yet transition to DHCPREQUEST, do nothing */ - elapsed = ( currticks() - dhcp->start ); - if ( ! ( ignore_pxe || dhcp->pxedhcpoffer || - ( elapsed > PROXYDHCP_WAIT_TIME ) ) ) - return; - - /* Transition to DHCPREQUEST */ - dhcp_next_state ( dhcp ); -} - -/** - * 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 " - "%s\n", dhcp, inet_ntoa ( ack_server_id ) ); - return; - } - - /* Record DHCPACK */ - assert ( dhcp->dhcpack == NULL ); - dhcp->dhcpack = dhcpset_get ( dhcpack ); - - /* Register settings */ - parent = netdev_settings ( dhcp->netdev ); - if ( ( rc = dhcp_store_dhcpack ( dhcp, dhcpack, parent ) ) != 0 ) - return; - - /* Transition to next state */ - dhcp_next_state ( dhcp ); -} - -/** - * Handle received ProxyDHCPACK - * - * @v dhcp DHCP session - * @v proxydhcpack Received ProxyDHCPACK - */ -static void dhcp_rx_proxydhcpack ( struct dhcp_session *dhcp, - struct dhcp_settings *proxydhcpack ) { - struct in_addr offer_server_id = { 0 }; - struct in_addr ack_server_id = { 0 }; - int rc; - - /* Verify server ID matches, if present */ - assert ( dhcp->pxedhcpoffer != NULL ); - if ( ( rc = dhcppkt_fetch ( &proxydhcpack->dhcppkt, - DHCP_SERVER_IDENTIFIER, &ack_server_id, - sizeof ( ack_server_id ) ) ) > 0 ) { - dhcppkt_fetch ( &dhcp->pxedhcpoffer->dhcppkt, - DHCP_SERVER_IDENTIFIER, &offer_server_id, - sizeof ( offer_server_id ) ); - if ( offer_server_id.s_addr != ack_server_id.s_addr ) { - DBGC ( dhcp, "DHCP %p ignoring ProxyDHCPACK with " - "wrong server ID %s\n", - dhcp, inet_ntoa ( ack_server_id ) ); - return; - } - } - - /* Rename settings */ - proxydhcpack->settings.name = PROXYDHCP_SETTINGS_NAME; - - /* Record ProxyDHCPACK as PXEDHCPACK */ - dhcpset_put ( dhcp->pxedhcpack ); - dhcp->pxedhcpack = dhcpset_get ( proxydhcpack ); - - /* Register settings */ - if ( ( rc = dhcp_store_dhcpack ( dhcp, proxydhcpack, NULL ) ) != 0 ) - return; - - /* Transition to next state */ - dhcp_next_state ( dhcp ); -} - -/** - * Handle received BootServerDHCPACK - * - * @v dhcp DHCP session - * @v bsdhcpack Received BootServerDHCPACK - */ -static void dhcp_rx_bsdhcpack ( struct dhcp_session *dhcp, - struct dhcp_settings *bsdhcpack ) { - int rc; - - /* Rename settings */ - bsdhcpack->settings.name = BSDHCP_SETTINGS_NAME; - - /* Record BootServerDHCPACK */ - assert ( dhcp->bsdhcpack == NULL ); - dhcp->bsdhcpack = dhcpset_get ( bsdhcpack ); - - /* Register settings */ - if ( ( rc = dhcp_store_dhcpack ( dhcp, bsdhcpack, NULL ) ) != 0 ) - return; - - /* Transition to next state */ - dhcp_next_state ( dhcp ); -} - /** * Receive new data * @@ -1227,9 +979,9 @@ static int dhcp_deliver_iob ( struct xfer_interface *xfer, struct xfer_metadata *meta ) { struct dhcp_session *dhcp = container_of ( xfer, struct dhcp_session, xfer ); - struct sockaddr_in *sin_src; - unsigned int src_port; - struct dhcp_settings *dhcpset; + struct sockaddr_in *peer; + size_t data_len; + struct dhcp_packet *dhcppkt; struct dhcphdr *dhcphdr; uint8_t msgtype = 0; int rc = 0; @@ -1247,63 +999,43 @@ static int dhcp_deliver_iob ( struct xfer_interface *xfer, rc = -EINVAL; goto err_no_src; } - sin_src = ( struct sockaddr_in * ) meta->src; - src_port = sin_src->sin_port; + peer = ( struct sockaddr_in * ) meta->src; - /* Convert packet into a DHCP settings block */ - dhcpset = dhcpset_create ( iobuf->data, iob_len ( iobuf ) ); - if ( ! dhcpset ) { - DBGC ( dhcp, "DHCP %p could not store DHCP packet\n", dhcp ); + /* Create a DHCP packet containing the I/O buffer contents. + * Whilst we could just use the original buffer in situ, that + * would waste the unused space in the packet buffer, and also + * waste a relatively scarce fully-aligned I/O buffer. + */ + data_len = iob_len ( iobuf ); + dhcppkt = zalloc ( sizeof ( *dhcppkt ) + data_len ); + if ( ! dhcppkt ) { rc = -ENOMEM; - goto err_dhcpset_create; + goto err_alloc_dhcppkt; } - dhcphdr = dhcpset->dhcppkt.dhcphdr; + dhcphdr = ( ( ( void * ) dhcppkt ) + sizeof ( *dhcppkt ) ); + memcpy ( dhcphdr, iobuf->data, data_len ); + dhcppkt_init ( dhcppkt, dhcphdr, data_len ); /* Identify message type */ - dhcppkt_fetch ( &dhcpset->dhcppkt, DHCP_MESSAGE_TYPE, &msgtype, + dhcppkt_fetch ( dhcppkt, DHCP_MESSAGE_TYPE, &msgtype, sizeof ( msgtype ) ); - DBGC ( dhcp, "DHCP %p %s %p from %s:%d\n", dhcp, - dhcp_msgtype_name ( msgtype ), dhcpset, - inet_ntoa ( sin_src->sin_addr ), ntohs ( src_port ) ); /* Check for matching transaction ID */ if ( dhcphdr->xid != dhcp_xid ( dhcp->netdev ) ) { - DBGC ( dhcp, "DHCP %p %s %p has bad transaction ID\n", - dhcp, dhcp_msgtype_name ( msgtype ), dhcpset ); + DBGC ( dhcp, "DHCP %p %s from %s:%d has bad transaction " + "ID\n", dhcp, dhcp_msgtype_name ( msgtype ), + inet_ntoa ( peer->sin_addr ), + ntohs ( peer->sin_port ) ); rc = -EINVAL; goto err_xid; }; /* Handle packet based on current state */ - switch ( dhcp->state ) { - case DHCP_STATE_DISCOVER: - if ( ( ( msgtype == DHCPOFFER ) || ( msgtype == DHCPNONE ) ) && - ( src_port == htons ( BOOTPS_PORT ) ) ) - dhcp_rx_dhcpoffer ( dhcp, dhcpset ); - break; - case DHCP_STATE_REQUEST: - if ( ( ( msgtype == DHCPACK ) || ( msgtype == DHCPNONE ) ) && - ( src_port == htons ( BOOTPS_PORT ) ) ) - dhcp_rx_dhcpack ( dhcp, dhcpset ); - break; - case DHCP_STATE_PROXYREQUEST: - if ( ( msgtype == DHCPACK ) && - ( src_port == htons ( PXE_PORT ) ) ) - dhcp_rx_proxydhcpack ( dhcp, dhcpset ); - break; - case DHCP_STATE_BSREQUEST: - if ( ( msgtype == DHCPACK ) && - ( src_port == htons ( PXE_PORT ) ) ) - dhcp_rx_bsdhcpack ( dhcp, dhcpset ); - break; - default: - assert ( 0 ); - break; - } + dhcp->state->rx ( dhcp, dhcppkt, peer, msgtype ); err_xid: - dhcpset_put ( dhcpset ); - err_dhcpset_create: + dhcppkt_put ( dhcppkt ); + err_alloc_dhcppkt: err_no_src: err_no_meta: free_iob ( iobuf ); @@ -1329,7 +1061,6 @@ static struct xfer_interface_operations dhcp_xfer_operations = { 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 ) { @@ -1337,14 +1068,8 @@ static void dhcp_timer_expired ( struct retry_timer *timer, int fail ) { return; } - /* Give up waiting for ProxyDHCP before we reach the failure point */ - if ( dhcp->dhcpoffer && ( elapsed > PROXYDHCP_WAIT_TIME ) ) { - dhcp_next_state ( dhcp ); - return; - } - - /* Otherwise, retransmit current packet */ - dhcp_tx ( dhcp ); + /* Handle timer expiry based on current state */ + dhcp->state->expired ( dhcp ); } /**************************************************************************** @@ -1375,32 +1100,33 @@ static struct job_interface_operations dhcp_job_operations = { /**************************************************************************** * - * Instantiator + * Instantiators * */ /** - * Start DHCP on a network device + * DHCP peer address for socket opening + * + * This is a dummy address; the only useful portion is the socket + * family (so that we get a UDP connection). The DHCP client will set + * the IP address and source port explicitly on each transmission. + */ +static struct sockaddr dhcp_peer = { + .sa_family = AF_INET, +}; + +/** + * Start DHCP state machine on a network device * * @v job Job control interface * @v netdev Network device - * @v register_options DHCP option block registration routine * @ret rc Return status code * - * Starts DHCP on the specified network device. If successful, the @c - * register_options() routine will be called with the acquired - * options. + * Starts DHCP on the specified network device. If successful, the + * DHCPACK (and ProxyDHCPACK, if applicable) will be registered as + * option sources. */ int start_dhcp ( struct job_interface *job, struct net_device *netdev ) { - static struct sockaddr_in server = { - .sin_family = AF_INET, - .sin_addr.s_addr = INADDR_BROADCAST, - .sin_port = htons ( BOOTPS_PORT ), - }; - static struct sockaddr_in client = { - .sin_family = AF_INET, - .sin_port = htons ( BOOTPC_PORT ), - }; struct dhcp_session *dhcp; int rc; @@ -1412,19 +1138,70 @@ int start_dhcp ( struct job_interface *job, struct net_device *netdev ) { job_init ( &dhcp->job, &dhcp_job_operations, &dhcp->refcnt ); xfer_init ( &dhcp->xfer, &dhcp_xfer_operations, &dhcp->refcnt ); dhcp->netdev = netdev_get ( netdev ); + dhcp->local.sin_family = AF_INET; + dhcp->local.sin_port = htons ( BOOTPC_PORT ); dhcp->timer.expired = dhcp_timer_expired; - dhcp->timer.min_timeout = DHCP_MIN_TIMEOUT; - dhcp->timer.max_timeout = DHCP_MAX_TIMEOUT; - dhcp->start = currticks(); /* Instantiate child objects and attach to our interfaces */ - if ( ( rc = xfer_open_socket ( &dhcp->xfer, SOCK_DGRAM, - ( struct sockaddr * ) &server, - ( struct sockaddr * ) &client ) ) != 0 ) + if ( ( rc = xfer_open_socket ( &dhcp->xfer, SOCK_DGRAM, &dhcp_peer, + ( struct sockaddr * ) &dhcp->local ) ) != 0 ) goto err; - /* Start timer to initiate initial DHCPREQUEST */ - start_timer_nodelay ( &dhcp->timer ); + /* Enter DHCPDISCOVER state */ + dhcp_set_state ( dhcp, &dhcp_state_discover ); + + /* Attach parent interface, mortalise self, and return */ + job_plug_plug ( &dhcp->job, job ); + ref_put ( &dhcp->refcnt ); + return 0; + + err: + dhcp_finished ( dhcp, rc ); + ref_put ( &dhcp->refcnt ); + return rc; +} + +/** + * Start PXE Boot Server Discovery on a network device + * + * @v job Job control interface + * @v netdev Network device + * @v pxe_server PXE server (may be a multicast address) + * @v pxe_type PXE server type + * @ret rc Return status code + * + * Starts PXE Boot Server Discovery on the specified network device. + * If successful, the Boot Server ACK will be registered as an option + * source. + */ +int start_pxebs ( struct job_interface *job, struct net_device *netdev, + struct in_addr pxe_server, unsigned int pxe_type ) { + struct dhcp_session *dhcp; + int rc; + + /* Allocate and initialise structure */ + dhcp = zalloc ( sizeof ( *dhcp ) ); + if ( ! dhcp ) + return -ENOMEM; + dhcp->refcnt.free = dhcp_free; + job_init ( &dhcp->job, &dhcp_job_operations, &dhcp->refcnt ); + xfer_init ( &dhcp->xfer, &dhcp_xfer_operations, &dhcp->refcnt ); + dhcp->netdev = netdev_get ( netdev ); + dhcp->local.sin_family = AF_INET; + fetch_ipv4_setting ( netdev_settings ( netdev ), &ip_setting, + &dhcp->local.sin_addr ); + dhcp->local.sin_port = htons ( BOOTPC_PORT ); + dhcp->pxe_server = pxe_server; + dhcp->pxe_type = htons ( pxe_type ); + dhcp->timer.expired = dhcp_timer_expired; + + /* Instantiate child objects and attach to our interfaces */ + if ( ( rc = xfer_open_socket ( &dhcp->xfer, SOCK_DGRAM, &dhcp_peer, + ( struct sockaddr * ) &dhcp->local ) ) != 0 ) + goto err; + + /* Enter PXEBS state */ + dhcp_set_state ( dhcp, &dhcp_state_pxebs ); /* Attach parent interface, mortalise self, and return */ job_plug_plug ( &dhcp->job, job ); diff --git a/src/usr/autoboot.c b/src/usr/autoboot.c index f5f7f7d1..41f13417 100644 --- a/src/usr/autoboot.c +++ b/src/usr/autoboot.c @@ -89,8 +89,8 @@ static int boot_embedded_image ( void ) { * @v filename Boot filename * @ret rc Return status code */ -static int boot_next_server_and_filename ( struct in_addr next_server, - const char *filename ) { +int boot_next_server_and_filename ( struct in_addr next_server, + const char *filename ) { struct uri *uri; struct image *image; char buf[ 23 /* tftp://xxx.xxx.xxx.xxx/ */ + strlen(filename) + 1 ]; @@ -167,6 +167,7 @@ int boot_root_path ( const char *root_path ) { * @ret rc Return status code */ static int netboot ( struct net_device *netdev ) { + struct setting tmp_setting = { .name = NULL }; char buf[256]; struct in_addr next_server; int rc; @@ -194,6 +195,16 @@ static int netboot ( struct net_device *netdev ) { if ( rc != ENOENT ) return rc; + /* Try PXE menu boot, if we have PXE menu options */ + tmp_setting.tag = DHCP_VENDOR_CLASS_ID; + fetch_string_setting ( NULL, &tmp_setting, buf, sizeof ( buf ) ); + tmp_setting.tag = DHCP_PXE_BOOT_MENU; + if ( ( strcmp ( buf, "PXEClient" ) == 0 ) && + setting_exists ( NULL, &tmp_setting ) ) { + printf ( "Booting from PXE menu\n" ); + return pxe_menu_boot ( netdev ); + } + /* Try to download and boot whatever we are given as a filename */ fetch_ipv4_setting ( NULL, &next_server_setting, &next_server ); fetch_string_setting ( NULL, &filename_setting, buf, sizeof ( buf ) ); diff --git a/src/usr/dhcpmgmt.c b/src/usr/dhcpmgmt.c index 2e429cd6..c68808b1 100644 --- a/src/usr/dhcpmgmt.c +++ b/src/usr/dhcpmgmt.c @@ -46,3 +46,17 @@ int dhcp ( struct net_device *netdev ) { return rc; } + +int pxebs ( struct net_device *netdev, struct in_addr pxe_server, + unsigned int pxe_type ) { + int rc; + + /* Perform PXE Boot Server Discovery */ + printf ( "PXEBS (%s %s type %d)", + netdev->name, inet_ntoa ( pxe_server ), pxe_type ); + if ( ( rc = start_pxebs ( &monojob, netdev, pxe_server, + pxe_type ) ) == 0 ) + rc = monojob_wait ( "" ); + + return rc; +} diff --git a/src/usr/pxemenu.c b/src/usr/pxemenu.c new file mode 100644 index 00000000..3f5bfc88 --- /dev/null +++ b/src/usr/pxemenu.c @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2009 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @file + * + * PXE Boot Menus + * + */ + +/* Colour pairs */ +#define CPAIR_NORMAL 1 +#define CPAIR_SELECT 2 + +/** A PXE boot menu item */ +struct pxe_menu_item { + /** Boot Server type */ + unsigned int type; + /** Description */ + char *desc; +}; + +/** + * A PXE boot menu + * + * This structure encapsulates the menu information provided via DHCP + * options. + */ +struct pxe_menu { + /** Boot Server address */ + struct in_addr server; + /** Timeout (in seconds) + * + * Negative indicates no timeout (i.e. wait indefinitely) + */ + int timeout; + /** Number of menu items */ + unsigned int num_items; + /** Selected menu item */ + unsigned int selection; + /** Menu items */ + struct pxe_menu_item items[0]; +}; + +/** + * Parse and allocate PXE boot menu + * + * @v menu PXE boot menu to fill in + * @ret rc Return status code + * + * It is the callers responsibility to eventually free the allocated + * boot menu. + */ +static int pxe_menu_parse ( struct pxe_menu **menu ) { + struct setting tmp_setting = { .name = NULL }; + struct in_addr server; + struct dhcp_pxe_boot_menu_prompt prompt = { .timeout = 0 }; + uint8_t raw_menu[256]; + int raw_menu_len; + struct dhcp_pxe_boot_menu *raw_menu_item; + void *raw_menu_end; + unsigned int num_menu_items; + unsigned int i; + int rc; + + /* Fetch relevant settings */ + tmp_setting.tag = DHCP_PXE_BOOT_SERVER_MCAST; + server.s_addr = INADDR_BROADCAST; + fetch_ipv4_setting ( NULL, &tmp_setting, &server ); + tmp_setting.tag = DHCP_PXE_BOOT_MENU_PROMPT; + fetch_setting ( NULL, &tmp_setting, &prompt, sizeof ( prompt ) ); + tmp_setting.tag = DHCP_PXE_BOOT_MENU; + memset ( raw_menu, 0, sizeof ( raw_menu ) ); + if ( ( raw_menu_len = fetch_setting ( NULL, &tmp_setting, raw_menu, + sizeof ( raw_menu ) ) ) < 0 ) { + rc = raw_menu_len; + DBG ( "Could not retrieve raw PXE boot menu: %s\n", + strerror ( rc ) ); + return rc; + } + if ( raw_menu_len >= ( int ) sizeof ( raw_menu ) ) { + DBG ( "Raw PXE boot menu too large for buffer\n" ); + return -ENOSPC; + } + raw_menu_end = ( raw_menu + raw_menu_len ); + + /* Count menu items */ + num_menu_items = 0; + raw_menu_item = ( ( void * ) raw_menu ); + while ( 1 ) { + if ( ( ( ( void * ) raw_menu_item ) + + sizeof ( *raw_menu_item ) ) > raw_menu_end ) + break; + if ( ( ( ( void * ) raw_menu_item ) + + sizeof ( *raw_menu_item ) + + raw_menu_item->desc_len ) > raw_menu_end ) + break; + num_menu_items++; + raw_menu_item = ( ( ( void * ) raw_menu_item ) + + sizeof ( *raw_menu_item ) + + raw_menu_item->desc_len ); + } + + /* Allocate space for parsed menu */ + *menu = zalloc ( sizeof ( **menu ) + + ( num_menu_items * sizeof ( (*menu)->items[0] ) ) + + raw_menu_len + 1 /* NUL */ ); + if ( ! *menu ) { + DBG ( "Could not allocate PXE boot menu\n" ); + return -ENOMEM; + } + + /* Fill in parsed menu */ + (*menu)->server = server; + (*menu)->timeout = + ( ( prompt.timeout == 0xff ) ? -1 : prompt.timeout ); + (*menu)->num_items = num_menu_items; + raw_menu_item = ( ( ( void * ) (*menu) ) + sizeof ( **menu ) + + ( num_menu_items * sizeof ( (*menu)->items[0] ) ) ); + memcpy ( raw_menu_item, raw_menu, raw_menu_len ); + for ( i = 0 ; i < num_menu_items ; i++ ) { + (*menu)->items[i].type = ntohs ( raw_menu_item->type ); + (*menu)->items[i].desc = raw_menu_item->desc; + /* Set type to 0; this ensures that the description + * for the previous menu item is NUL-terminated. + * (Final item is NUL-terminated anyway.) + */ + raw_menu_item->type = 0; + raw_menu_item = ( ( ( void * ) raw_menu_item ) + + sizeof ( *raw_menu_item ) + + raw_menu_item->desc_len ); + } + + return 0; +} + +/** + * Draw PXE boot menu item + * + * @v menu PXE boot menu + * @v index Index of item to draw + */ +static void pxe_menu_draw_item ( struct pxe_menu *menu, + unsigned int index ) { + int selected = ( menu->selection == index ); + char buf[COLS+1]; + char *tmp = buf; + ssize_t remaining = sizeof ( buf ); + size_t len; + unsigned int row; + + /* Prepare space-padded row content */ + len = ssnprintf ( tmp, remaining, " %c. %s", + ( 'A' + index ), menu->items[index].desc ); + tmp += len; + remaining -= len; + if ( selected && ( menu->timeout > 0 ) ) { + len = ssnprintf ( tmp, remaining, " (%d)", menu->timeout ); + tmp += len; + remaining -= len; + } + for ( ; remaining > 1 ; tmp++, remaining-- ) + *tmp = ' '; + *tmp = '\0'; + + /* Draw row */ + row = ( LINES - menu->num_items + index ); + color_set ( ( selected ? CPAIR_SELECT : CPAIR_NORMAL ), NULL ); + mvprintw ( row, 0, "%s", buf ); + move ( row, 1 ); +} + +/** + * Make selection from PXE boot menu + * + * @v menu PXE boot menu + * @ret rc Return status code + */ +int pxe_menu_select ( struct pxe_menu *menu ) { + unsigned long start = currticks(); + unsigned long now; + unsigned long elapsed; + unsigned int old_selection; + int key; + unsigned int key_selection; + unsigned int i; + int rc = 0; + + /* Initialise UI */ + initscr(); + start_color(); + init_pair ( CPAIR_NORMAL, COLOR_WHITE, COLOR_BLACK ); + init_pair ( CPAIR_SELECT, COLOR_BLACK, COLOR_WHITE ); + color_set ( CPAIR_NORMAL, NULL ); + + /* Draw initial menu */ + for ( i = 0 ; i < menu->num_items ; i++ ) + printf ( "\n" ); + for ( i = 0 ; i < menu->num_items ; i++ ) + pxe_menu_draw_item ( menu, ( menu->num_items - i - 1 ) ); + + while ( 1 ) { + + /* Decrease timeout if necessary */ + if ( menu->timeout > 0 ) { + now = currticks(); + elapsed = ( now - start ); + if ( elapsed >= TICKS_PER_SEC ) { + start = now; + menu->timeout--; + pxe_menu_draw_item ( menu, menu->selection ); + } + } + + /* Select current item if we have timed out */ + if ( menu->timeout == 0 ) + break; + + /* Check for keyboard input */ + if ( ! iskey() ) + continue; + key = getkey(); + + /* Any keyboard input cancels the timeout */ + menu->timeout = -1; + pxe_menu_draw_item ( menu, menu->selection ); + + /* Act upon key */ + old_selection = menu->selection; + if ( ( key == CR ) || ( key == LF ) ) { + break; + } else if ( key == CTRL_C ) { + rc = -ECANCELED; + break; + } else if ( key == KEY_UP ) { + if ( menu->selection > 0 ) + menu->selection--; + } else if ( key == KEY_DOWN ) { + if ( menu->selection < ( menu->num_items - 1 ) ) + menu->selection++; + } else if ( ( key < KEY_MIN ) && + ( ( key_selection = ( toupper ( key ) - 'A' ) ) + < menu->num_items ) ) { + menu->selection = key_selection; + menu->timeout = 0; + } + pxe_menu_draw_item ( menu, old_selection ); + pxe_menu_draw_item ( menu, menu->selection ); + } + + /* Shut down UI */ + endwin(); + + return rc; +} + +/** + * Boot using PXE boot menu + * + * @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. + */ +int pxe_menu_boot ( struct net_device *netdev ) { + struct pxe_menu *menu; + struct in_addr pxe_server; + unsigned int pxe_type; + struct settings *pxebs_settings; + struct in_addr next_server; + char filename[256]; + int rc; + + /* Parse and allocate boot menu */ + if ( ( rc = pxe_menu_parse ( &menu ) ) != 0 ) + return rc; + + /* Make selection from boot menu */ + if ( ( rc = pxe_menu_select ( menu ) ) != 0 ) { + free ( menu ); + return rc; + } + pxe_server = menu->server; + pxe_type = menu->items[menu->selection].type; + + /* Free boot menu */ + free ( menu ); + + /* Return immediately if local boot selected */ + if ( ! pxe_type ) + return 0; + + /* Attempt PXE Boot Server Discovery */ + if ( ( rc = pxebs ( netdev, pxe_server, pxe_type ) ) != 0 ) + return rc; + + /* Attempt boot */ + pxebs_settings = find_settings ( PXEBS_SETTINGS_NAME ); + assert ( pxebs_settings ); + fetch_ipv4_setting ( pxebs_settings, &next_server_setting, + &next_server ); + fetch_string_setting ( pxebs_settings, &filename_setting, + filename, sizeof ( filename ) ); + return boot_next_server_and_filename ( next_server, filename ); +}