diff options
Diffstat (limited to 'src/net/udp/dhcp.c')
-rw-r--r-- | src/net/udp/dhcp.c | 1581 |
1 files changed, 679 insertions, 902 deletions
diff --git a/src/net/udp/dhcp.c b/src/net/udp/dhcp.c index c7e1f88..3554b40 100644 --- a/src/net/udp/dhcp.c +++ b/src/net/udp/dhcp.c @@ -23,7 +23,6 @@ #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> @@ -34,14 +33,12 @@ #include <gpxe/tcpip.h> #include <gpxe/ip.h> #include <gpxe/uuid.h> -#include <gpxe/dhcp.h> #include <gpxe/timer.h> #include <gpxe/settings.h> #include <gpxe/dhcp.h> #include <gpxe/dhcpopts.h> #include <gpxe/dhcppkt.h> #include <gpxe/features.h> -#include <gpxe/keys.h> /** @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 @@ -212,228 +141,616 @@ static uint32_t dhcp_xid ( struct net_device *netdev ) { /**************************************************************************** * - * DHCP settings + * DHCP session * */ -/** A DHCP settings block */ -struct dhcp_settings { +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; +}; + +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 { /** Reference counter */ struct refcnt refcnt; - /** DHCP packet */ - struct dhcp_packet dhcppkt; - /** Setting interface */ - struct settings settings; + /** Job control interface */ + struct job_interface job; + /** Data transfer interface */ + struct xfer_interface xfer; + + /** Network device being configured */ + struct net_device *netdev; + /** Local socket address */ + struct sockaddr_in local; + /** State of the session */ + struct dhcp_session_state *state; + + /** 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; + /** Start time of the current state (in ticks) */ + unsigned long start; }; /** - * Increment reference count on DHCP settings block + * Free DHCP session * - * @v dhcpset DHCP settings block - * @ret dhcpset DHCP settings block + * @v refcnt Reference counter */ -static inline __attribute__ (( always_inline )) struct dhcp_settings * -dhcpset_get ( struct dhcp_settings *dhcpset ) { - ref_get ( &dhcpset->refcnt ); - return dhcpset; +static void dhcp_free ( struct refcnt *refcnt ) { + struct dhcp_session *dhcp = + container_of ( refcnt, struct dhcp_session, refcnt ); + + netdev_put ( dhcp->netdev ); + free ( dhcp ); } /** - * Decrement reference count on DHCP settings block + * Mark DHCP session as complete * - * @v dhcpset DHCP settings block + * @v dhcp DHCP session + * @v rc Return status code */ -static inline __attribute__ (( always_inline )) void -dhcpset_put ( struct dhcp_settings *dhcpset ) { - ref_put ( &dhcpset->refcnt ); +static void dhcp_finished ( struct dhcp_session *dhcp, int rc ) { + + /* Block futher incoming messages */ + job_nullify ( &dhcp->job ); + xfer_nullify ( &dhcp->xfer ); + + /* Stop retry timer */ + stop_timer ( &dhcp->timer ); + + /* Free resources and close interfaces */ + xfer_close ( &dhcp->xfer, rc ); + job_done ( &dhcp->job, rc ); } /** - * Store value of DHCP setting + * Transition to new DHCP session state * - * @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 + * @v dhcp DHCP session + * @v state New session state */ -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 ); +static void dhcp_set_state ( struct dhcp_session *dhcp, + struct dhcp_session_state *state ) { - return dhcppkt_store ( &dhcpset->dhcppkt, setting->tag, data, len ); + 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 ); } +/**************************************************************************** + * + * DHCP state machine + * + */ + /** - * Fetch value of DHCP setting + * Construct transmitted packet for DHCP discovery * - * @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 + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer Destination address */ -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 ); +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 dhcppkt_fetch ( &dhcpset->dhcppkt, setting->tag, data, len ); + return 0; } -/** DHCP settings operations */ -static struct settings_operations dhcpset_settings_operations = { - .store = dhcpset_store, - .fetch = dhcpset_fetch, -}; +/** + * 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 ); +} /** - * Create DHCP setting block + * Handle timer expiry during DHCP discovery * - * @v dhcphdr DHCP packet - * @v len Length of DHCP packet - * @ret dhcpset DHCP settings block + * @v dhcp DHCP session */ -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 ); +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; } - return dhcpset; + + /* Otherwise, retransmit current packet */ + dhcp_tx ( dhcp ); } -/** 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 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 * - * DHCP session + * @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 ) { -/** 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, + /* 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, }; /** - * Name a DHCP session state + * 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 state DHCP session state - * @ret string DHCP session state name + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer DHCP server address + * @v msgtype DHCP message type */ -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 "<invalid>"; +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 ); } -/** A DHCP session */ -struct dhcp_session { - /** Reference counter */ - struct refcnt refcnt; - /** Job control interface */ - struct job_interface job; - /** Data transfer interface */ - struct xfer_interface xfer; +/** + * 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 ); - /** Network device being configured */ - struct net_device *netdev; + /* Give up waiting for ProxyDHCP before we reach the failure point */ + if ( elapsed > PROXYDHCP_MAX_TIMEOUT ) { + dhcp_finished ( dhcp, 0 ); + return; + } - /** 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; + /* Retransmit current packet */ + dhcp_tx ( dhcp ); +} - /** Retransmission timer */ - struct retry_timer timer; - /** Start time of the current state (in ticks) */ - unsigned long start; +/** 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, }; /** - * Free DHCP session + * Construct transmitted packet for PXE Boot Server Discovery * - * @v refcnt Reference counter + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer Destination address */ -static void dhcp_free ( struct refcnt *refcnt ) { - struct dhcp_session *dhcp = - container_of ( refcnt, struct dhcp_session, refcnt ); +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; - 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 ); + 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; } /** - * Mark DHCP session as complete + * Handle received packet during PXE Boot Server Discovery * * @v dhcp DHCP session - * @v rc Return status code + * @v dhcppkt DHCP packet + * @v peer DHCP server address + * @v msgtype DHCP message type */ -static void dhcp_finished ( struct dhcp_session *dhcp, int rc ) { +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; - /* Block futher incoming messages */ - job_nullify ( &dhcp->job ); - xfer_nullify ( &dhcp->xfer ); + DBGC ( dhcp, "DHCP %p %s from %s:%d", dhcp, + dhcp_msgtype_name ( msgtype ), inet_ntoa ( peer->sin_addr ), + ntohs ( peer->sin_port ) ); - /* Stop retry timer */ - stop_timer ( &dhcp->timer ); + /* 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" ); - /* Free resources and close interfaces */ - xfer_close ( &dhcp->xfer, rc ); - job_done ( &dhcp->job, rc ); + /* 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, +}; + /**************************************************************************** * - * Data transfer interface + * 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 */ @@ -750,471 +967,6 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) { } /** - * 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 * * @v xfer Data transfer interface @@ -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 ); |