diff --git a/src/hci/commands/dhcp_cmd.c b/src/hci/commands/dhcp_cmd.c index b44433e7d..9b577c86e 100644 --- a/src/hci/commands/dhcp_cmd.c +++ b/src/hci/commands/dhcp_cmd.c @@ -109,7 +109,7 @@ static int dhcp_exec ( int argc, char **argv ) { */ static void pxebs_syntax ( char **argv ) { printf ( "Usage:\n" - " %s \n" + " %s \n" "\n" "Perform PXE Boot Server discovery\n", argv[0] ); @@ -128,10 +128,8 @@ static int pxebs_exec ( int argc, char **argv ) { { 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; @@ -148,15 +146,12 @@ static int pxebs_exec ( int argc, char **argv ) { return 1; } } - - /* Need exactly one interface name remaining after the options */ - if ( optind != ( argc - 3 ) ) { + if ( optind != ( argc - 2 ) ) { pxebs_syntax ( argv ); return 1; } netdev_txt = argv[optind]; - pxe_server_txt = argv[ optind + 1 ]; - pxe_type_txt = argv[ optind + 2 ]; + pxe_type_txt = argv[ optind + 1 ]; /* Parse arguments */ netdev = find_netdev ( netdev_txt ); @@ -164,10 +159,6 @@ static int pxebs_exec ( int argc, char **argv ) { 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 ); @@ -175,7 +166,7 @@ static int pxebs_exec ( int argc, char **argv ) { } /* Perform Boot Server Discovery */ - if ( ( rc = pxebs ( netdev, pxe_server, pxe_type ) ) != 0 ) { + if ( ( rc = pxebs ( netdev, pxe_type ) ) != 0 ) { printf ( "Could not discover boot server on %s: %s\n", netdev->name, strerror ( rc ) ); return 1; diff --git a/src/include/gpxe/dhcp.h b/src/include/gpxe/dhcp.h index 44edd6458..33e0c5d47 100644 --- a/src/include/gpxe/dhcp.h +++ b/src/include/gpxe/dhcp.h @@ -100,6 +100,19 @@ enum dhcp_pxe_discovery_control { /** PXE boot server multicast address */ #define DHCP_PXE_BOOT_SERVER_MCAST DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 7 ) +/** PXE boot servers */ +#define DHCP_PXE_BOOT_SERVERS DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 8 ) + +/** PXE boot server */ +struct dhcp_pxe_boot_server { + /** "Type" */ + uint16_t type; + /** Number of IPv4 addresses */ + uint8_t num_ip; + /** IPv4 addresses */ + struct in_addr ip[0]; +} __attribute__ (( packed )); + /** PXE boot menu */ #define DHCP_PXE_BOOT_MENU DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 9 ) @@ -574,6 +587,9 @@ struct dhcphdr { /** Maximum time that we will wait for ProxyDHCP responses */ #define PROXYDHCP_MAX_TIMEOUT ( 2 * TICKS_PER_SEC ) +/** Maximum time that we will wait for Boot Server responses */ +#define PXEBS_MAX_TIMEOUT ( 3 * TICKS_PER_SEC ) + /** Settings block name used for DHCP responses */ #define DHCP_SETTINGS_NAME "dhcp" @@ -593,6 +609,6 @@ extern int dhcp_create_request ( struct dhcp_packet *dhcppkt, 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 ); + unsigned int pxe_type ); #endif /* _GPXE_DHCP_H */ diff --git a/src/include/usr/dhcpmgmt.h b/src/include/usr/dhcpmgmt.h index dc9de7bb0..0f275600d 100644 --- a/src/include/usr/dhcpmgmt.h +++ b/src/include/usr/dhcpmgmt.h @@ -10,7 +10,6 @@ struct net_device; extern int dhcp ( struct net_device *netdev ); -extern int pxebs ( struct net_device *netdev, struct in_addr pxe_server, - unsigned int pxe_type ); +extern int pxebs ( struct net_device *netdev, unsigned int pxe_type ); #endif /* _USR_DHCPMGMT_H */ diff --git a/src/net/udp/dhcp.c b/src/net/udp/dhcp.c index 6ebf60217..d1492146b 100644 --- a/src/net/udp/dhcp.c +++ b/src/net/udp/dhcp.c @@ -175,11 +175,12 @@ struct dhcp_session_state { * @v dhcppkt DHCP packet * @v peer DHCP server address * @v msgtype DHCP message type + * @v server_id DHCP server ID */ void ( * rx ) ( struct dhcp_session *dhcp, struct dhcp_packet *dhcppkt, struct sockaddr_in *peer, - uint8_t msgtype ); + uint8_t msgtype, struct in_addr server_id ); /** Handle timer expiry * * @v dhcp DHCP session @@ -226,10 +227,12 @@ struct dhcp_session { /** ProxyDHCP server priority */ int proxy_priority; - /** PXE Boot Server */ - struct in_addr pxe_server; /** PXE Boot Server type */ uint16_t pxe_type; + /** List of PXE Boot Servers to attempt */ + struct in_addr *pxe_attempt; + /** List of PXE Boot Servers to accept */ + struct in_addr *pxe_accept; /** Retransmission timer */ struct retry_timer timer; @@ -322,11 +325,12 @@ static int dhcp_discovery_tx ( struct dhcp_session *dhcp, * @v dhcppkt DHCP packet * @v peer DHCP server address * @v msgtype DHCP message type + * @v server_id DHCP server ID */ 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 sockaddr_in *peer, uint8_t msgtype, + struct in_addr server_id ) { struct in_addr ip; char vci[9]; /* "PXEClient" */ int vci_len; @@ -338,10 +342,6 @@ static void dhcp_discovery_rx ( struct dhcp_session *dhcp, 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 ) ); @@ -480,11 +480,12 @@ static int dhcp_request_tx ( struct dhcp_session *dhcp, * @v dhcppkt DHCP packet * @v peer DHCP server address * @v msgtype DHCP message type + * @v server_id DHCP server ID */ 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 sockaddr_in *peer, uint8_t msgtype, + struct in_addr server_id ) { struct in_addr ip; struct settings *parent; int rc; @@ -492,10 +493,6 @@ static void dhcp_request_rx ( struct dhcp_session *dhcp, 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 ) ); @@ -591,20 +588,17 @@ static int dhcp_proxy_tx ( struct dhcp_session *dhcp, * @v dhcppkt DHCP packet * @v peer DHCP server address * @v msgtype DHCP message type + * @v server_id DHCP server ID */ 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 }; + struct sockaddr_in *peer, uint8_t msgtype, + struct in_addr server_id ) { 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" ); @@ -673,7 +667,7 @@ static int dhcp_pxebs_tx ( struct dhcp_session *dhcp, int rc; DBGC ( dhcp, "DHCP %p PXEBS REQUEST to %s:%d for type %d\n", - dhcp, inet_ntoa ( dhcp->pxe_server ), PXE_PORT, + dhcp, inet_ntoa ( *(dhcp->pxe_attempt) ), PXE_PORT, ntohs ( dhcp->pxe_type ) ); /* Set boot menu item */ @@ -683,12 +677,38 @@ static int dhcp_pxebs_tx ( struct dhcp_session *dhcp, return rc; /* Set server address */ - peer->sin_addr = dhcp->pxe_server; + peer->sin_addr = *(dhcp->pxe_attempt); peer->sin_port = htons ( PXE_PORT ); return 0; } +/** + * Check to see if PXE Boot Server address is acceptable + * + * @v dhcp DHCP session + * @v bs Boot Server address + * @ret accept Boot Server is acceptable + */ +static int dhcp_pxebs_accept ( struct dhcp_session *dhcp, + struct in_addr bs ) { + struct in_addr *accept; + + /* Accept if we have no acceptance filter */ + if ( ! dhcp->pxe_accept ) + return 1; + + /* Scan through acceptance list */ + for ( accept = dhcp->pxe_accept ; accept->s_addr ; accept++ ) { + if ( accept->s_addr == bs.s_addr ) + return 1; + } + + DBGC ( dhcp, "DHCP %p rejecting server %s\n", + dhcp, inet_ntoa ( bs ) ); + return 0; +} + /** * Handle received packet during PXE Boot Server Discovery * @@ -696,16 +716,20 @@ static int dhcp_pxebs_tx ( struct dhcp_session *dhcp, * @v dhcppkt DHCP packet * @v peer DHCP server address * @v msgtype DHCP message type + * @v server_id DHCP server ID */ static void dhcp_pxebs_rx ( struct dhcp_session *dhcp, struct dhcp_packet *dhcppkt, - struct sockaddr_in *peer, uint8_t msgtype ) { + struct sockaddr_in *peer, uint8_t msgtype, + struct in_addr server_id ) { 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 ) ); + if ( server_id.s_addr != peer->sin_addr.s_addr ) + DBGC ( dhcp, " (%s)", inet_ntoa ( server_id ) ); /* Identify boot menu item */ dhcppkt_fetch ( dhcppkt, DHCP_PXE_BOOT_MENU_ITEM, @@ -721,6 +745,9 @@ static void dhcp_pxebs_rx ( struct dhcp_session *dhcp, return; if ( menu_item.type != dhcp->pxe_type ) return; + if ( ! dhcp_pxebs_accept ( dhcp, ( server_id.s_addr ? + server_id : peer->sin_addr ) ) ) + return; /* Register settings */ dhcppkt->settings.name = PXEBS_SETTINGS_NAME; @@ -741,6 +768,21 @@ static void dhcp_pxebs_rx ( struct dhcp_session *dhcp, * @v dhcp DHCP session */ static void dhcp_pxebs_expired ( struct dhcp_session *dhcp ) { + unsigned long elapsed = ( currticks() - dhcp->start ); + + /* Give up waiting before we reach the failure point, and fail + * over to the next server in the attempt list + */ + if ( elapsed > PXEBS_MAX_TIMEOUT ) { + dhcp->pxe_attempt++; + if ( dhcp->pxe_attempt->s_addr ) { + dhcp_set_state ( dhcp, &dhcp_state_pxebs ); + return; + } else { + dhcp_finished ( dhcp, -ETIMEDOUT ); + return; + } + } /* Retransmit current packet */ dhcp_tx ( dhcp ); @@ -1006,6 +1048,7 @@ static int dhcp_deliver_iob ( struct xfer_interface *xfer, struct dhcp_packet *dhcppkt; struct dhcphdr *dhcphdr; uint8_t msgtype = 0; + struct in_addr server_id = { 0 }; int rc = 0; /* Sanity checks */ @@ -1042,6 +1085,10 @@ static int dhcp_deliver_iob ( struct xfer_interface *xfer, dhcppkt_fetch ( dhcppkt, DHCP_MESSAGE_TYPE, &msgtype, sizeof ( msgtype ) ); + /* Identify server ID */ + dhcppkt_fetch ( dhcppkt, DHCP_SERVER_IDENTIFIER, + &server_id, sizeof ( server_id ) ); + /* Check for matching transaction ID */ if ( dhcphdr->xid != dhcp_xid ( dhcp->netdev ) ) { DBGC ( dhcp, "DHCP %p %s from %s:%d has bad transaction " @@ -1053,7 +1100,7 @@ static int dhcp_deliver_iob ( struct xfer_interface *xfer, }; /* Handle packet based on current state */ - dhcp->state->rx ( dhcp, dhcppkt, peer, msgtype ); + dhcp->state->rx ( dhcp, dhcppkt, peer, msgtype, server_id ); err_xid: dhcppkt_put ( dhcppkt ); @@ -1183,12 +1230,50 @@ int start_dhcp ( struct job_interface *job, struct net_device *netdev ) { return rc; } +/** + * Retrieve list of PXE boot servers for a given server type + * + * @v dhcp DHCP session + * @v raw DHCP PXE boot server list + * @v raw_len Length of DHCP PXE boot server list + * @v ip IP address list to fill in + * + * The caller must ensure that the IP address list has sufficient + * space. + */ +static void pxebs_list ( struct dhcp_session *dhcp, void *raw, + size_t raw_len, struct in_addr *ip ) { + struct dhcp_pxe_boot_server *server = raw; + size_t server_len; + unsigned int i; + + while ( raw_len ) { + if ( raw_len < sizeof ( *server ) ) { + DBGC ( dhcp, "DHCP %p malformed PXE server list\n", + dhcp ); + break; + } + server_len = offsetof ( typeof ( *server ), + ip[ server->num_ip ] ); + if ( raw_len < server_len ) { + DBGC ( dhcp, "DHCP %p malformed PXE server list\n", + dhcp ); + break; + } + if ( server->type == dhcp->pxe_type ) { + for ( i = 0 ; i < server->num_ip ; i++ ) + *(ip++) = server->ip[i]; + } + server = ( ( ( void * ) server ) + server_len ); + raw_len -= server_len; + } +} + /** * 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 * @@ -1197,12 +1282,28 @@ int start_dhcp ( struct job_interface *job, struct net_device *netdev ) { * source. */ int start_pxebs ( struct job_interface *job, struct net_device *netdev, - struct in_addr pxe_server, unsigned int pxe_type ) { + unsigned int pxe_type ) { + struct setting pxe_discovery_control_setting = + { .tag = DHCP_PXE_DISCOVERY_CONTROL }; + struct setting pxe_boot_servers_setting = + { .tag = DHCP_PXE_BOOT_SERVERS }; + struct setting pxe_boot_server_mcast_setting = + { .tag = DHCP_PXE_BOOT_SERVER_MCAST }; + ssize_t pxebs_list_len; struct dhcp_session *dhcp; + struct in_addr *ip; + unsigned int pxe_discovery_control; int rc; + /* Get upper bound for PXE boot server IP address list */ + pxebs_list_len = fetch_setting_len ( NULL, &pxe_boot_servers_setting ); + if ( pxebs_list_len < 0 ) + pxebs_list_len = 0; + /* Allocate and initialise structure */ - dhcp = zalloc ( sizeof ( *dhcp ) ); + dhcp = zalloc ( sizeof ( *dhcp ) + sizeof ( *ip ) /* mcast */ + + sizeof ( *ip ) /* bcast */ + pxebs_list_len + + sizeof ( *ip ) /* terminator */ ); if ( ! dhcp ) return -ENOMEM; dhcp->refcnt.free = dhcp_free; @@ -1213,10 +1314,49 @@ int start_pxebs ( struct job_interface *job, struct net_device *netdev, 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; + /* Construct PXE boot server IP address lists */ + pxe_discovery_control = + fetch_uintz_setting ( NULL, &pxe_discovery_control_setting ); + ip = ( ( ( void * ) dhcp ) + sizeof ( *dhcp ) ); + dhcp->pxe_attempt = ip; + if ( ! ( pxe_discovery_control & PXEBS_NO_MULTICAST ) ) { + fetch_ipv4_setting ( NULL, &pxe_boot_server_mcast_setting, ip); + if ( ip->s_addr ) + ip++; + } + if ( ! ( pxe_discovery_control & PXEBS_NO_BROADCAST ) ) + (ip++)->s_addr = INADDR_BROADCAST; + if ( pxe_discovery_control & PXEBS_NO_UNKNOWN_SERVERS ) + dhcp->pxe_accept = ip; + if ( pxebs_list_len ) { + uint8_t buf[pxebs_list_len]; + + fetch_setting ( NULL, &pxe_boot_servers_setting, + buf, sizeof ( buf ) ); + pxebs_list ( dhcp, buf, sizeof ( buf ), ip ); + } + if ( ! dhcp->pxe_attempt->s_addr ) { + DBGC ( dhcp, "DHCP %p has no PXE boot servers for type %04x\n", + dhcp, pxe_type ); + rc = -EINVAL; + goto err; + } + + /* Dump out PXE server lists */ + DBGC ( dhcp, "DHCP %p attempting", dhcp ); + for ( ip = dhcp->pxe_attempt ; ip->s_addr ; ip++ ) + DBGC ( dhcp, " %s", inet_ntoa ( *ip ) ); + DBGC ( dhcp, "\n" ); + if ( dhcp->pxe_accept ) { + DBGC ( dhcp, "DHCP %p accepting", dhcp ); + for ( ip = dhcp->pxe_accept ; ip->s_addr ; ip++ ) + DBGC ( dhcp, " %s", inet_ntoa ( *ip ) ); + DBGC ( dhcp, "\n" ); + } + /* Instantiate child objects and attach to our interfaces */ if ( ( rc = xfer_open_socket ( &dhcp->xfer, SOCK_DGRAM, &dhcp_peer, ( struct sockaddr * ) &dhcp->local ) ) != 0 ) diff --git a/src/usr/dhcpmgmt.c b/src/usr/dhcpmgmt.c index c68808b12..6acf7f6da 100644 --- a/src/usr/dhcpmgmt.c +++ b/src/usr/dhcpmgmt.c @@ -47,15 +47,12 @@ int dhcp ( struct net_device *netdev ) { return rc; } -int pxebs ( struct net_device *netdev, struct in_addr pxe_server, - unsigned int pxe_type ) { +int pxebs ( struct net_device *netdev, 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 ) + printf ( "PXEBS (%s type %d)", netdev->name, pxe_type ); + if ( ( rc = start_pxebs ( &monojob, netdev, pxe_type ) ) == 0 ) rc = monojob_wait ( "" ); return rc; diff --git a/src/usr/pxemenu.c b/src/usr/pxemenu.c index ae971088e..4cc40a653 100644 --- a/src/usr/pxemenu.c +++ b/src/usr/pxemenu.c @@ -57,8 +57,6 @@ struct pxe_menu_item { * options. */ struct pxe_menu { - /** Boot Server address */ - struct in_addr server; /** Timeout (in seconds) * * Negative indicates no timeout (i.e. wait indefinitely) @@ -83,7 +81,6 @@ struct pxe_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; @@ -94,10 +91,6 @@ static int pxe_menu_parse ( struct pxe_menu **menu ) { int rc; /* Fetch relevant settings */ - tmp_setting.tag = DHCP_PXE_BOOT_SERVER_MCAST; - fetch_ipv4_setting ( NULL, &tmp_setting, &server ); - if ( ! server.s_addr ) - server.s_addr = INADDR_BROADCAST; tmp_setting.tag = DHCP_PXE_BOOT_MENU_PROMPT; fetch_setting ( NULL, &tmp_setting, &prompt, sizeof ( prompt ) ); tmp_setting.tag = DHCP_PXE_BOOT_MENU; @@ -142,7 +135,6 @@ static int pxe_menu_parse ( struct pxe_menu **menu ) { } /* Fill in parsed menu */ - (*menu)->server = server; (*menu)->timeout = ( ( prompt.timeout == 0xff ) ? -1 : prompt.timeout ); (*menu)->num_items = num_menu_items; @@ -296,7 +288,6 @@ int pxe_menu_select ( struct pxe_menu *menu ) { */ 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; @@ -312,7 +303,6 @@ int pxe_menu_boot ( struct net_device *netdev ) { free ( menu ); return rc; } - pxe_server = menu->server; pxe_type = menu->items[menu->selection].type; /* Free boot menu */ @@ -323,7 +313,7 @@ int pxe_menu_boot ( struct net_device *netdev ) { return 0; /* Attempt PXE Boot Server Discovery */ - if ( ( rc = pxebs ( netdev, pxe_server, pxe_type ) ) != 0 ) + if ( ( rc = pxebs ( netdev, pxe_type ) ) != 0 ) return rc; /* Attempt boot */