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 15 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
 /** PXE boot menu */
88
 /** PXE boot menu */
89
 #define DHCP_PXE_BOOT_MENU DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 9 )
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
 /** PXE boot menu item */
94
 /** PXE boot menu item */
92
 #define DHCP_PXE_BOOT_MENU_ITEM DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 71 )
95
 #define DHCP_PXE_BOOT_MENU_ITEM DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 71 )
93
 
96
 
478
 #define DHCP_MIN_LEN 552
481
 #define DHCP_MIN_LEN 552
479
 
482
 
480
 /** Maximum time that we will wait for ProxyDHCP responses */
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
 /** Timeouts for sending DHCP packets */
486
 /** Timeouts for sending DHCP packets */
484
 #define DHCP_MIN_TIMEOUT ( 1 * TICKS_PER_SEC )
487
 #define DHCP_MIN_TIMEOUT ( 1 * TICKS_PER_SEC )

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

19
 #include <string.h>
19
 #include <string.h>
20
 #include <stdlib.h>
20
 #include <stdlib.h>
21
 #include <stdio.h>
21
 #include <stdio.h>
22
+#include <ctype.h>
22
 #include <errno.h>
23
 #include <errno.h>
23
 #include <assert.h>
24
 #include <assert.h>
24
 #include <byteswap.h>
25
 #include <byteswap.h>
26
+#include <console.h>
25
 #include <gpxe/if_ether.h>
27
 #include <gpxe/if_ether.h>
26
 #include <gpxe/netdevice.h>
28
 #include <gpxe/netdevice.h>
27
 #include <gpxe/device.h>
29
 #include <gpxe/device.h>
39
 #include <gpxe/dhcpopts.h>
41
 #include <gpxe/dhcpopts.h>
40
 #include <gpxe/dhcppkt.h>
42
 #include <gpxe/dhcppkt.h>
41
 #include <gpxe/features.h>
43
 #include <gpxe/features.h>
44
+#include <gpxe/keys.h>
42
 
45
 
43
 /** @file
46
 /** @file
44
  *
47
  *
125
 
128
 
126
 #define DHCP_CLIENT_UUID_TYPE 0
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
 /** DHCP PXE boot menu item */
157
 /** DHCP PXE boot menu item */
129
 struct dhcp_pxe_boot_menu_item {
158
 struct dhcp_pxe_boot_menu_item {
130
 	/** "Type"
159
 	/** "Type"
140
 	uint16_t layer;
169
 	uint16_t layer;
141
 } __attribute__ (( packed ));
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
  * Name a DHCP packet type
176
  * Name a DHCP packet type
145
  *
177
  *
352
 	struct dhcp_settings *pxedhcpack;
384
 	struct dhcp_settings *pxedhcpack;
353
 	/** BootServerDHCPACK obtained during BootServerDHCPREQUEST */
385
 	/** BootServerDHCPACK obtained during BootServerDHCPREQUEST */
354
 	struct dhcp_settings *bsdhcpack;
386
 	struct dhcp_settings *bsdhcpack;
387
+	/** PXE boot menu item */
388
+	struct dhcp_pxe_boot_menu_item menu_item;
389
+
355
 	/** Retransmission timer */
390
 	/** Retransmission timer */
356
 	struct retry_timer timer;
391
 	struct retry_timer timer;
357
 	/** Start time of the current state (in ticks) */
392
 	/** Start time of the current state (in ticks) */
602
 	struct in_addr ciaddr = { 0 };
637
 	struct in_addr ciaddr = { 0 };
603
 	struct in_addr server = { 0 };
638
 	struct in_addr server = { 0 };
604
 	struct in_addr requested_ip = { 0 };
639
 	struct in_addr requested_ip = { 0 };
605
-	struct dhcp_pxe_boot_menu_item menu_item = { 0, 0 };
606
 	unsigned int msgtype;
640
 	unsigned int msgtype;
607
 	int rc;
641
 	int rc;
608
 
642
 
649
 				DHCP_PXE_BOOT_SERVER_MCAST,
683
 				DHCP_PXE_BOOT_SERVER_MCAST,
650
 				&dest.sin_addr, sizeof ( dest.sin_addr ) );
684
 				&dest.sin_addr, sizeof ( dest.sin_addr ) );
651
 		meta.dest = ( struct sockaddr * ) &dest;
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
 		assert ( dest.sin_addr.s_addr );
686
 		assert ( dest.sin_addr.s_addr );
656
-		assert ( menu_item.type );
657
 		assert ( ciaddr.s_addr );
687
 		assert ( ciaddr.s_addr );
658
 		break;
688
 		break;
659
 	default:
689
 	default:
677
 	}
707
 	}
678
 	if ( requested_ip.s_addr )
708
 	if ( requested_ip.s_addr )
679
 		DBGC ( dhcp, " for %s", inet_ntoa ( requested_ip ) );
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
 	DBGC ( dhcp, "\n" );
714
 	DBGC ( dhcp, "\n" );
683
 
715
 
684
 	/* Allocate buffer for packet */
716
 	/* Allocate buffer for packet */
689
 	/* Create DHCP packet in temporary buffer */
721
 	/* Create DHCP packet in temporary buffer */
690
 	if ( ( rc = dhcp_create_request ( &dhcppkt, dhcp->netdev, msgtype,
722
 	if ( ( rc = dhcp_create_request ( &dhcppkt, dhcp->netdev, msgtype,
691
 					  ciaddr, server, requested_ip,
723
 					  ciaddr, server, requested_ip,
692
-					  &menu_item, iobuf->data,
724
+					  &dhcp->menu_item, iobuf->data,
693
 					  iob_tailroom ( iobuf ) ) ) != 0 ) {
725
 					  iob_tailroom ( iobuf ) ) ) != 0 ) {
694
 		DBGC ( dhcp, "DHCP %p could not construct DHCP request: %s\n",
726
 		DBGC ( dhcp, "DHCP %p could not construct DHCP request: %s\n",
695
 		       dhcp, strerror ( rc ) );
727
 		       dhcp, strerror ( rc ) );
717
 	return rc;
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
  * Transition to new DHCP session state
901
  * Transition to new DHCP session state
722
  *
902
  *
739
  * @v dhcp		DHCP session
919
  * @v dhcp		DHCP session
740
  */
920
  */
741
 static void dhcp_next_state ( struct dhcp_session *dhcp ) {
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
 	switch ( dhcp->state ) {
925
 	switch ( dhcp->state ) {
745
 	case DHCP_STATE_DISCOVER:
926
 	case DHCP_STATE_DISCOVER:
760
 		/* Fall through */
941
 		/* Fall through */
761
 	case DHCP_STATE_PROXYREQUEST:
942
 	case DHCP_STATE_PROXYREQUEST:
762
 		if ( dhcp->pxedhcpack ) {
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
 				dhcp_set_state ( dhcp, DHCP_STATE_BSREQUEST );
947
 				dhcp_set_state ( dhcp, DHCP_STATE_BSREQUEST );
768
 				break;
948
 				break;
769
 			}
949
 			}

Loading…
Cancel
Save