Browse Source

[dhcp] Include support for PXE boot menus

PXE dictates a mechanism for boot menuing, involving prompting the
user with a variable message, waiting for a predefined keypress,
displaying a boot menu, and waiting for a selection.

This breaks the currently desirable abstraction that DHCP is a process
that can happen in the background without any user interaction.
tags/v0.9.7
Michael Brown 16 years ago
parent
commit
027c72e0d0
2 changed files with 197 additions and 14 deletions
  1. 4
    1
      src/include/gpxe/dhcp.h
  2. 193
    13
      src/net/udp/dhcp.c

+ 4
- 1
src/include/gpxe/dhcp.h View File

@@ -88,6 +88,9 @@ struct dhcp_pxe_boot_menu_item;
88 88
 /** PXE boot menu */
89 89
 #define DHCP_PXE_BOOT_MENU DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 9 )
90 90
 
91
+/** PXE boot menu prompt */
92
+#define DHCP_PXE_BOOT_MENU_PROMPT DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 10 )
93
+
91 94
 /** PXE boot menu item */
92 95
 #define DHCP_PXE_BOOT_MENU_ITEM DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 71 )
93 96
 
@@ -478,7 +481,7 @@ struct dhcphdr {
478 481
 #define DHCP_MIN_LEN 552
479 482
 
480 483
 /** Maximum time that we will wait for ProxyDHCP responses */
481
-#define PROXYDHCP_WAIT_TIME ( TICKS_PER_SEC * 1 )
484
+#define PROXYDHCP_WAIT_TIME ( 2 * TICKS_PER_SEC )
482 485
 
483 486
 /** Timeouts for sending DHCP packets */
484 487
 #define DHCP_MIN_TIMEOUT ( 1 * TICKS_PER_SEC )

+ 193
- 13
src/net/udp/dhcp.c View File

@@ -19,9 +19,11 @@
19 19
 #include <string.h>
20 20
 #include <stdlib.h>
21 21
 #include <stdio.h>
22
+#include <ctype.h>
22 23
 #include <errno.h>
23 24
 #include <assert.h>
24 25
 #include <byteswap.h>
26
+#include <console.h>
25 27
 #include <gpxe/if_ether.h>
26 28
 #include <gpxe/netdevice.h>
27 29
 #include <gpxe/device.h>
@@ -39,6 +41,7 @@
39 41
 #include <gpxe/dhcpopts.h>
40 42
 #include <gpxe/dhcppkt.h>
41 43
 #include <gpxe/features.h>
44
+#include <gpxe/keys.h>
42 45
 
43 46
 /** @file
44 47
  *
@@ -125,6 +128,32 @@ struct dhcp_client_uuid {
125 128
 
126 129
 #define DHCP_CLIENT_UUID_TYPE 0
127 130
 
131
+/** DHCP PXE boot prompt */
132
+struct dhcp_pxe_boot_prompt {
133
+	/** Timeout
134
+	 *
135
+	 * A value of 0 means "time out immediately and select first
136
+	 * boot item, without displaying the prompt".  A value of 255
137
+	 * means "display menu immediately with no timeout".  Any
138
+	 * other value means "display prompt, wait this many seconds
139
+	 * for keypress, if key is F8, display menu, otherwise select
140
+	 * first boot item".
141
+	 */
142
+	uint8_t timeout;
143
+	/** Prompt to press F8 */
144
+	char prompt[0];
145
+} __attribute__ (( packed ));
146
+
147
+/** DHCP PXE boot menu item description */
148
+struct dhcp_pxe_boot_menu_item_desc {
149
+	/** "Type" */
150
+	uint16_t type;
151
+	/** Description length */
152
+	uint8_t desc_len;
153
+	/** Description */
154
+	char desc[0];
155
+} __attribute__ (( packed ));
156
+
128 157
 /** DHCP PXE boot menu item */
129 158
 struct dhcp_pxe_boot_menu_item {
130 159
 	/** "Type"
@@ -140,6 +169,9 @@ struct dhcp_pxe_boot_menu_item {
140 169
 	uint16_t layer;
141 170
 } __attribute__ (( packed ));
142 171
 
172
+/** Maximum allowed number of PXE boot menu items */
173
+#define PXE_BOOT_MENU_MAX_ITEMS 20
174
+
143 175
 /**
144 176
  * Name a DHCP packet type
145 177
  *
@@ -352,6 +384,9 @@ struct dhcp_session {
352 384
 	struct dhcp_settings *pxedhcpack;
353 385
 	/** BootServerDHCPACK obtained during BootServerDHCPREQUEST */
354 386
 	struct dhcp_settings *bsdhcpack;
387
+	/** PXE boot menu item */
388
+	struct dhcp_pxe_boot_menu_item menu_item;
389
+
355 390
 	/** Retransmission timer */
356 391
 	struct retry_timer timer;
357 392
 	/** Start time of the current state (in ticks) */
@@ -602,7 +637,6 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) {
602 637
 	struct in_addr ciaddr = { 0 };
603 638
 	struct in_addr server = { 0 };
604 639
 	struct in_addr requested_ip = { 0 };
605
-	struct dhcp_pxe_boot_menu_item menu_item = { 0, 0 };
606 640
 	unsigned int msgtype;
607 641
 	int rc;
608 642
 
@@ -649,11 +683,7 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) {
649 683
 				DHCP_PXE_BOOT_SERVER_MCAST,
650 684
 				&dest.sin_addr, sizeof ( dest.sin_addr ) );
651 685
 		meta.dest = ( struct sockaddr * ) &dest;
652
-		dhcppkt_fetch ( &dhcp->pxedhcpack->dhcppkt,
653
-				DHCP_PXE_BOOT_MENU, &menu_item.type,
654
-				sizeof ( menu_item.type ) );
655 686
 		assert ( dest.sin_addr.s_addr );
656
-		assert ( menu_item.type );
657 687
 		assert ( ciaddr.s_addr );
658 688
 		break;
659 689
 	default:
@@ -677,8 +707,10 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) {
677 707
 	}
678 708
 	if ( requested_ip.s_addr )
679 709
 		DBGC ( dhcp, " for %s", inet_ntoa ( requested_ip ) );
680
-	if ( menu_item.type )
681
-		DBGC ( dhcp, " for item %04x", ntohs ( menu_item.type ) );
710
+	if ( dhcp->menu_item.type ) {
711
+		DBGC ( dhcp, " for item %04x",
712
+		       ntohs ( dhcp->menu_item.type ) );
713
+	}
682 714
 	DBGC ( dhcp, "\n" );
683 715
 
684 716
 	/* Allocate buffer for packet */
@@ -689,7 +721,7 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) {
689 721
 	/* Create DHCP packet in temporary buffer */
690 722
 	if ( ( rc = dhcp_create_request ( &dhcppkt, dhcp->netdev, msgtype,
691 723
 					  ciaddr, server, requested_ip,
692
-					  &menu_item, iobuf->data,
724
+					  &dhcp->menu_item, iobuf->data,
693 725
 					  iob_tailroom ( iobuf ) ) ) != 0 ) {
694 726
 		DBGC ( dhcp, "DHCP %p could not construct DHCP request: %s\n",
695 727
 		       dhcp, strerror ( rc ) );
@@ -717,6 +749,154 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) {
717 749
 	return rc;
718 750
 }
719 751
 
752
+/**
753
+ * Prompt for PXE boot menu selection
754
+ *
755
+ * @v pxedhcpack	PXEDHCPACK packet
756
+ * @ret rc		Return status code
757
+ *
758
+ * Note that a success return status indicates that the PXE boot menu
759
+ * should be displayed.
760
+ */
761
+static int dhcp_pxe_boot_menu_prompt ( struct dhcp_packet *pxedhcpack ) {
762
+	union {
763
+		uint8_t buf[80];
764
+		struct dhcp_pxe_boot_prompt prompt;
765
+	} u;
766
+	ssize_t slen;
767
+	unsigned long start;
768
+	int key;
769
+
770
+	/* Parse menu prompt */
771
+	memset ( &u, 0, sizeof ( u ) );
772
+	if ( ( slen = dhcppkt_fetch ( pxedhcpack, DHCP_PXE_BOOT_MENU_PROMPT,
773
+				      &u, sizeof ( u ) ) ) <= 0 ) {
774
+		/* If prompt is not present, we should always display
775
+		 * the menu.
776
+		 */
777
+		return 0;
778
+	}
779
+
780
+	/* Display prompt, if applicable */
781
+	if ( u.prompt.timeout )
782
+		printf ( "\n%s\n", u.prompt.prompt );
783
+
784
+	/* Timeout==0xff means display menu immediately */
785
+	if ( u.prompt.timeout == 0xff )
786
+		return 0;
787
+
788
+	/* Wait for F8 or other key press */
789
+	start = currticks();
790
+	while ( ( currticks() - start ) <
791
+		( u.prompt.timeout * TICKS_PER_SEC ) ) {
792
+		if ( iskey() ) {
793
+			key = getkey();
794
+			return ( ( key == KEY_F8 ) ? 0 : -ECANCELED );
795
+		}
796
+	}
797
+
798
+	return -ECANCELED;
799
+}
800
+
801
+/**
802
+ * Perform PXE boot menu selection
803
+ *
804
+ * @v pxedhcpack	PXEDHCPACK packet
805
+ * @v menu_item		PXE boot menu item to fill in
806
+ * @ret rc		Return status code
807
+ *
808
+ * Note that a success return status indicates that a PXE boot menu
809
+ * item has been selected, and that the DHCP session should perform a
810
+ * boot server request/ack.
811
+ */
812
+static int dhcp_pxe_boot_menu ( struct dhcp_packet *pxedhcpack,
813
+				struct dhcp_pxe_boot_menu_item *menu_item ) {
814
+	uint8_t buf[256];
815
+	ssize_t slen;
816
+	size_t menu_len;
817
+	struct dhcp_pxe_boot_menu_item_desc *menu_item_desc;
818
+	size_t menu_item_desc_len;
819
+	struct {
820
+		uint16_t type;
821
+		char *desc;
822
+	} menu[PXE_BOOT_MENU_MAX_ITEMS];
823
+	size_t offset = 0;
824
+	unsigned int num_menu_items = 0;
825
+	unsigned int i;
826
+	unsigned int selected_menu_item;
827
+	int key;
828
+	int rc;
829
+
830
+	/* Check for boot menu */
831
+	memset ( &buf, 0, sizeof ( buf ) );
832
+	if ( ( slen = dhcppkt_fetch ( pxedhcpack, DHCP_PXE_BOOT_MENU,
833
+				      &buf, sizeof ( buf ) ) ) <= 0 ) {
834
+		DBGC2 ( pxedhcpack, "PXEDHCPACK %p has no boot menu\n",
835
+			pxedhcpack );
836
+		return slen;
837
+	}
838
+	menu_len = slen;
839
+
840
+	/* Parse boot menu */
841
+	while ( offset < menu_len ) {
842
+		menu_item_desc = ( ( void * ) ( buf + offset ) );
843
+		menu_item_desc_len = ( sizeof ( *menu_item_desc ) +
844
+				       menu_item_desc->desc_len );
845
+		if ( ( offset + menu_item_desc_len ) > menu_len ) {
846
+			DBGC ( pxedhcpack, "PXEDHCPACK %p has malformed "
847
+			       "boot menu\n", pxedhcpack );
848
+			return -EINVAL;
849
+		}
850
+		menu[num_menu_items].type = menu_item_desc->type;
851
+		menu[num_menu_items].desc = menu_item_desc->desc;
852
+		/* Set type to 0; this ensures that the description
853
+		 * for the previous menu item is NUL-terminated.
854
+		 * (Final item is NUL-terminated anyway.)
855
+		 */
856
+		menu_item_desc->type = 0;
857
+		offset += menu_item_desc_len;
858
+		num_menu_items++;
859
+		if ( num_menu_items == ( sizeof ( menu ) /
860
+					 sizeof ( menu[0] ) ) ) {
861
+			DBGC ( pxedhcpack, "PXEDHCPACK %p has too many "
862
+			       "menu items\n", pxedhcpack );
863
+			/* Silently ignore remaining items */
864
+			break;
865
+		}
866
+	}
867
+	if ( ! num_menu_items ) {
868
+		DBGC ( pxedhcpack, "PXEDHCPACK %p has no menu items\n",
869
+		       pxedhcpack );
870
+		return -EINVAL;
871
+	}
872
+
873
+	/* Default to first menu item */
874
+	menu_item->type = menu[0].type;
875
+
876
+	/* Prompt for menu, if necessary */
877
+	if ( ( rc = dhcp_pxe_boot_menu_prompt ( pxedhcpack ) ) != 0 ) {
878
+		/* Failure to display menu means we should just
879
+		 * continue with the boot.
880
+		 */
881
+		return 0;
882
+	}
883
+
884
+	/* Display menu */
885
+	for ( i = 0 ; i < num_menu_items ; i++ ) {
886
+		printf ( "%c. %s\n", ( 'A' + i ), menu[i].desc );
887
+	}
888
+
889
+	/* Obtain selection */
890
+	while ( 1 ) {
891
+		key = getkey();
892
+		selected_menu_item = ( toupper ( key ) - 'A' );
893
+		if ( selected_menu_item < num_menu_items ) {
894
+			menu_item->type = menu[selected_menu_item].type;
895
+			return 0;
896
+		}
897
+	}
898
+}
899
+
720 900
 /**
721 901
  * Transition to new DHCP session state
722 902
  *
@@ -739,7 +919,8 @@ static void dhcp_set_state ( struct dhcp_session *dhcp,
739 919
  * @v dhcp		DHCP session
740 920
  */
741 921
 static void dhcp_next_state ( struct dhcp_session *dhcp ) {
742
-	struct in_addr bs_mcast = { 0 };
922
+
923
+	stop_timer ( &dhcp->timer );
743 924
 
744 925
 	switch ( dhcp->state ) {
745 926
 	case DHCP_STATE_DISCOVER:
@@ -760,10 +941,9 @@ static void dhcp_next_state ( struct dhcp_session *dhcp ) {
760 941
 		/* Fall through */
761 942
 	case DHCP_STATE_PROXYREQUEST:
762 943
 		if ( dhcp->pxedhcpack ) {
763
-			dhcppkt_fetch ( &dhcp->pxedhcpack->dhcppkt,
764
-					DHCP_PXE_BOOT_SERVER_MCAST,
765
-					&bs_mcast, sizeof ( bs_mcast ) );
766
-			if ( bs_mcast.s_addr ) {
944
+			dhcp_pxe_boot_menu ( &dhcp->pxedhcpack->dhcppkt,
945
+					     &dhcp->menu_item );
946
+			if ( dhcp->menu_item.type ) {
767 947
 				dhcp_set_state ( dhcp, DHCP_STATE_BSREQUEST );
768 948
 				break;
769 949
 			}

Loading…
Cancel
Save