|
@@ -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
|
}
|