Re: PXE-compliant boot server request?

Roland Kuhn <[email protected]> Tue, 28 Feb 2006 21:24:26 +0100
Newsgroups gmane.network.etherboot.devel
Message-ID <[email protected]>
Hi Marty!

On 6 Feb 2006, at 15:57, Marty Connor wrote:

> On Feb 5, 2006, at 3:28 PM, Roland Kuhn wrote:
>> Hi!
>> I'm struggling with the automatic network installation setup at  
>> our institute, which requires more PXE-compliance than currently  
>> offered by etherboot. To be specific, it requires the  
>> PXE_BOOT_ITEM selection to be present in the DHCPREQUEST, and it  
>> requests via DHCP_DISCOVERY_CONTROL that neither broadcast nor  
>> multicast be used for that packet. I'm thinking about adding that  
>> functionality, but there are two ways: hard-coding the specifics  
>> for this setup or handling the involved protocol. What do you  
>> think? Does this have a chance of inclusion?
>> Ciao,
>>                     Roland
>
> Improved PXE compatibility is certainly one of our goals, and if  
> your changes work toward that end, they are likely to be included.
> Handling the protocol would be more likely to be included, of  
> course (as I am sure you would do).
>
> Thanks for writing, and we look forward to seeing what you come up  
> with.
>
Here's my first try: it works for me (TM), I've successfully booted  
several machines with this patch. The PXE boot menu is printed, but  
I've not implemented a selection, it uses always the first entry. I  
trust that someone knowing etherboot better than me can easily add a  
small question prompt with a timeout.

The function principle is the following: If the DHCP reply contained  
vendor encapsulated options with a boot menu, we parse it and store  
it for later. In case of proxyDHCP only the packet from the proxy  
server is relevant. After that the server address given for the first  
boot menu item is queried on port 4011 using another DHCPREQUEST-type  
packet, including the selected boot item. The server sends back a  
DHCP reply with a filename and new options, which are copied to the  
KERNEL_BUFFER and BOOTP_DATA_ADDR respectively. From there on  
everything is as usual.

This is used to implement a three phase booting: after the procedure  
outlined above an image like pxelinux is loaded, which in turn loads  
the kernel and initrd as configured on the boot server. The initrd  
contains boot scripts which configure the network using normal DHCP  
(without PXE extensions) and receive in the filename field e.g. the  
name of a kickstart installation config file. PXE extensions are  
necessary to facilitate this distinction between booting and  
installation and enable the usage of the filename field with two  
different values in different phases of the boot procedure.

Ciao,
                     Roland


--
TU Muenchen, Physik-Department E18, James-Franck-Str., 85748 Garching
Telefon 089/289-12575; Telefax 089/289-12570
--
CERN office: 892-1-D23 phone: +41 22 7676540 mobile: +41 76 487 4482
--
UNIX was not designed to stop you from doing stupid things, because that
would also stop you from doing clever things.
	-Doug Gwyn
-----BEGIN GEEK CODE BLOCK-----
Version: 3.12
GS/CS/M/MU d-(++) s:+ a-> C+++ UL++++ P+++ L+++ E(+) W+ !N K- w--- M 
+ !V Y+
PGP++ t+(++) 5 R+ tv-- b+ DI++ e+++>++++ h---- y+++
------END GEEK CODE BLOCK------
pxe.patch (application/octet-stream, 12.6 KB)
diff -ur etherboot-5.4.1/src/core/nic.c etherboot-5.4.1-pxe2/src/core/nic.c
--- etherboot-5.4.1/src/core/nic.c	2005-08-29 03:37:33.000000000 +0200
+++ etherboot-5.4.1-pxe2/src/core/nic.c	2006-02-28 21:01:24.218751848 +0100
@@ -30,6 +30,7 @@
 char *hostname = "";
 int hostnamelen = 0;
 static uint32_t xid;
+static uint16_t sport;
 unsigned char *end_of_rfc1533 = NULL;
 static int vendorext_isvalid;
 static const unsigned char vendorext_magic[] = {0xE4,0x45,0x74,0x68}; /* äEth */
@@ -38,9 +39,20 @@
 
 struct bootpd_t bootp_data;
 
+#ifdef PXE_DHCP_STRICT
+#define MAX_BOOT_MENU 5
+#define MAX_BOOT_ENTRY_LENGTH 60
+struct {
+  uint16_t id;
+  unsigned char text[MAX_BOOT_ENTRY_LENGTH+1];
+  in_addr ip;
+} pxe_boot_menu[MAX_BOOT_MENU];
+static unsigned char num_boot_menu = 0;
+#endif /* PXE_DHCP_STRICT */
+
 #ifdef	NO_DHCP_SUPPORT
 static unsigned char	rfc1533_cookie[5] = { RFC1533_COOKIE, RFC1533_END };
-#else	/* !NO_DHCP_SUPPORT */
+#else	/* NO_DHCP_SUPPORT */
 static int dhcp_reply;
 static in_addr dhcp_server = { 0L };
 static in_addr dhcp_addr = { 0L };
@@ -216,6 +228,17 @@
 };
 #endif
 
+#ifdef PXE_DHCP_STRICT
+static const unsigned char pxedhcprequest [] = {
+	RFC2132_MSG_TYPE,1,DHCPREQUEST,
+	RFC1533_VENDOR,6,PXE_BOOT_ITEM,4,0,0,0,0,
+	RFC3679_PXE_CLIENT_UUID,RFC3679_PXE_CLIENT_UUID_LENGTH,RFC3679_PXE_CLIENT_UUID_DEFAULT,
+	RFC3679_PXE_CLIENT_ARCH,RFC3679_PXE_CLIENT_ARCH_LENGTH,RFC3679_PXE_CLIENT_ARCH_IAX86PC,
+	RFC3679_PXE_CLIENT_NDI, RFC3679_PXE_CLIENT_NDI_LENGTH, RFC3679_PXE_CLIENT_NDI_21,
+	RFC2132_VENDOR_CLASS_ID,RFC2132_VENDOR_CLASS_ID_PXE_LENGTH,RFC2132_VENDOR_CLASS_ID_PXE
+};
+#endif /* PXE_DHCP_STRICT */
+
 #ifdef	REQUIRE_VCI_ETHERBOOT
 int	vci_etherboot;
 #endif
@@ -572,6 +595,7 @@
 int udp_transmit(unsigned long destip, unsigned int srcsock,
 	unsigned int destsock, int len, const void *buf)
 {
+	sport = srcsock;
 	build_udp_hdr(destip, srcsock, destsock, 60, len, buf);
 	return ip_transmit(len, buf);
 }
@@ -852,6 +876,21 @@
 
 #else
 
+#ifdef PXE_DHCP_STRICT
+/**************************************************************************
+FIND_VCI_PXECLIENT - Looks for "PXEClient" Vendor Class Identifier
+Upon entry p points after rfc1533_cookie.
+**************************************************************************/
+static int find_vci_pxeclient(unsigned char*p)
+{
+	for (; *p != RFC1533_END; p += TAG_LEN(p) + 2)
+		if (*p == RFC2132_VENDOR_CLASS_ID)
+			if (memcmp(p + 2, "PXEClient", 9) == 0)
+				return 1;
+	return 0;
+}
+#endif
+
 /**************************************************************************
 BOOTP - Get my IP address and load information
 **************************************************************************/
@@ -875,12 +914,30 @@
 		) {
 		return 0;
 	}
-	if (udp->dest != htons(BOOTP_CLIENT))
+	if (udp->dest != htons(sport))
 		return 0;
 	if (bootpreply->bp_op != BOOTP_REPLY)
 		return 0;
 	if (bootpreply->bp_xid != xid)
 		return 0;
+#ifdef PXE_DHCP_STRICT
+	/* in principle one could check here:
+	 *
+	 * - DHCPACK
+	 * - BOOT_ITEM
+	 * - RFC2132_SRV_ID
+	 * - UUID
+	 */
+	if (sport == PXE_BOOT_CLIENT) {
+		if (!find_vci_pxeclient(bootpreply->bp_vend + sizeof rfc1533_cookie)) {
+			return 0;
+		}
+		/* we only solicited this packet for the options and the bp_file */
+		memcpy((char*)BOOTP_DATA_ADDR, (char*)bootpreply, sizeof(struct bootpd_t));
+		memcpy(KERNEL_BUF, bootpreply->bp_file, sizeof(KERNEL_BUF));
+		return 1;
+	}			
+#endif /* PXE_DHCP_STRICT */
 	if (memcmp(&bootpreply->bp_siaddr, &zeroIP, sizeof(in_addr)) == 0)
 		return 0;
 	if ((memcmp(broadcast, bootpreply->bp_hwaddr, ETH_ALEN) != 0) &&
@@ -900,7 +957,7 @@
 		arptable[ARP_CLIENT].ipaddr.s_addr = bootpreply->bp_yiaddr.s_addr;
 #ifndef	NO_DHCP_SUPPORT
 		dhcp_addr.s_addr = bootpreply->bp_yiaddr.s_addr;
-#endif	/* NO_DHCP_SUPPORT */
+#endif	/* !NO_DHCP_SUPPORT */
 		netmask = default_netmask();
 		/* bootpreply->bp_file will be copied to KERNEL_BUF in the memcpy */
 		memcpy((char *)BOOTP_DATA_ADDR, (char *)bootpreply, sizeof(struct bootpd_t));
@@ -932,7 +989,7 @@
 	int retry;
 #ifndef	NO_DHCP_SUPPORT
 	int reqretry;
-#endif	/* NO_DHCP_SUPPORT */
+#endif	/* !NO_DHCP_SUPPORT */
 	struct bootpip_t ip;
 	unsigned long  starttime;
 	unsigned char *bp_vend;
@@ -943,7 +1000,7 @@
 	dhcp_machine_info[6] = ((nic.dev.devid.vendor_id) >> 8) & 0xff;
 	dhcp_machine_info[7] = nic.dev.devid.device_id & 0xff;
 	dhcp_machine_info[8] = ((nic.dev.devid.device_id) >> 8) & 0xff;
-#endif	/* NO_DHCP_SUPPORT */
+#endif	/* !NO_DHCP_SUPPORT */
 	memset(&ip, 0, sizeof(struct bootpip_t));
 	ip.bp.bp_op = BOOTP_REQUEST;
 	ip.bp.bp_htype = 1;
@@ -980,8 +1037,9 @@
 
 		udp_transmit(IP_BROADCAST, BOOTP_CLIENT, BOOTP_SERVER,
 			sizeof(struct bootpip_t), &ip);
-		remaining_time = rfc2131_sleep_interval(BOOTP_TIMEOUT, retry++);
+		remaining_time = rfc2131_sleep_interval(BOOTP_TIMEOUT, retry++/10);
 		stop_time = currticks() + remaining_time;
+		num_boot_menu = 0;
 #ifdef	NO_DHCP_SUPPORT
 		if (await_reply(await_bootp, 0, NULL, remaining_time))
 			return(1);
@@ -1018,7 +1076,7 @@
 			udp_transmit(IP_BROADCAST, BOOTP_CLIENT, BOOTP_SERVER,
 				     sizeof(struct bootpip_t), &ip);
 			dhcp_reply=0;
-			timeout = rfc2131_sleep_interval(TIMEOUT, reqretry++);
+			timeout = rfc2131_sleep_interval(TIMEOUT, reqretry++/10);
 			if (!await_reply(await_bootp, 0, NULL, timeout))
 				continue;
 			if (dhcp_reply != DHCPACK)
@@ -1026,6 +1084,10 @@
 			dhcp_reply = 0;
 #ifdef PXE_EXPORT			
 			if ( arptable[ARP_PROXYDHCP].ipaddr.s_addr ) {
+#ifdef PXE_DHCP_STRICT
+				/* boot menu items must not be mixed from different servers */
+				num_boot_menu = 0;
+#endif /* PXE_DHCP_STRICT */
 				/* Construct the ProxyDHCPREQUEST packet */
 				memcpy(ip.bp.bp_vend, rfc1533_cookie, sizeof rfc1533_cookie);
 				memcpy(ip.bp.bp_vend + sizeof rfc1533_cookie, proxydhcprequest, sizeof proxydhcprequest);
@@ -1039,6 +1101,36 @@
 					}
 				}
 			}
+#ifdef PXE_DHCP_STRICT
+			if (num_boot_menu) {
+				int i;
+				printf("\nreceived PXE boot menu:\n");
+				for (i = 0; i < num_boot_menu; ++i) {
+					printf("%d %s %@\n", pxe_boot_menu[i].id, pxe_boot_menu[i].text, pxe_boot_menu[i].ip.s_addr);
+				}
+				ip.bp.bp_siaddr.s_addr = zeroIP.s_addr;
+				ip.bp.bp_yiaddr.s_addr = zeroIP.s_addr;
+				ip.bp.bp_ciaddr.s_addr = arptable[ARP_CLIENT].ipaddr.s_addr;
+				memcpy(ip.bp.bp_vend, rfc1533_cookie, sizeof rfc1533_cookie);
+				bp_vend = ip.bp.bp_vend + sizeof rfc1533_cookie;
+				/* for now simply select the first entry */
+				*(uint16_t*)(pxedhcprequest + 7) = htons(pxe_boot_menu[0].id);
+				memcpy(bp_vend, pxedhcprequest, sizeof pxedhcprequest);
+				bp_vend += sizeof pxedhcprequest;
+				*bp_vend = RFC1533_END;
+				for (reqretry = 0; reqretry < MAX_BOOTP_RETRIES; ) {
+					printf("\nselecting boot item [%s] ... ", pxe_boot_menu[0].text);
+					arptable[ARP_SERVER].ipaddr.s_addr = pxe_boot_menu[0].ip.s_addr;
+					memset(&arptable[ARP_SERVER].node, 0, ETH_ALEN);
+					udp_transmit(pxe_boot_menu[0].ip.s_addr, PXE_BOOT_CLIENT,
+						     PXE_BOOT_SERVER, sizeof(struct bootpip_t), &ip);
+					timeout = rfc2131_sleep_interval(TIMEOUT, reqretry++/10);
+					if (await_reply(await_bootp, 0, NULL, timeout)) {
+						break;
+					}
+				}
+			}
+#endif /* PXE_DHCP_STRICT */
 #endif /* PXE_EXPORT */
 			return(1);
 		}
@@ -1609,6 +1701,62 @@
 }
 #endif	/* REQUIRE_VCI_ETHERBOOT */
 
+#ifdef PXE_DHCP_STRICT
+/**************************************************************************
+DECODE_PXE_BOOT_SERVERS - Decodes the IP addresses of boot servers
+**************************************************************************/
+static void decode_pxe_boot_servers(unsigned char*p)
+{
+	unsigned short item = ntohs(*(unsigned short*)p);
+	unsigned char i;
+
+	for (i = 0; i < num_boot_menu; ++i) {
+		if (pxe_boot_menu[i].id == item)
+			break;
+	}
+	if (i == MAX_BOOT_MENU) return;
+	if (i == num_boot_menu) {
+		++num_boot_menu;
+		pxe_boot_menu[i].id = item;
+	}
+	memcpy(&pxe_boot_menu[i].ip, p + 3, sizeof(in_addr));
+}
+
+/**************************************************************************
+DECODE_PXE_BOOT_MENU - Decodes the boot menu items
+**************************************************************************/
+static void decode_pxe_boot_menu(unsigned char*p)
+{
+	unsigned short item = ntohs(*(unsigned short*)p);
+	unsigned char i, size;
+
+	for (i = 0; i < num_boot_menu; ++i) {
+		if (pxe_boot_menu[i].id == item)
+			break;
+	}
+	if (i == MAX_BOOT_MENU) return;
+	if (i == num_boot_menu) {
+		++num_boot_menu;
+		pxe_boot_menu[i].id = item;
+	}
+	memset(pxe_boot_menu[i].text, 0, sizeof(pxe_boot_menu[i].text));
+	size = p[2];
+	if (size > MAX_BOOT_ENTRY_LENGTH)
+		size = MAX_BOOT_ENTRY_LENGTH;
+	memcpy(pxe_boot_menu[i].text, p + 3, size);
+}
+#endif /* PXE_DHCP_STRICT */
+
+static void dump(unsigned char*p, unsigned char len)
+{
+	unsigned char*end = p+len;
+	printf("\n");
+	while (p<end) {
+		printf("%hhX ",*p);
+		++p;
+	}
+}
+
 /**************************************************************************
 DECODE_RFC1533 - Decodes RFC1533 header
 **************************************************************************/
@@ -1618,6 +1766,10 @@
 	unsigned char	     *extpath = NULL;
 	unsigned char	     *endp;
 	static unsigned char in_encapsulated_options = 0;
+#ifdef PXE_DHCP_STRICT
+	static unsigned char in_pxe_encapsulated_options = 0;
+	unsigned char        pxeclient;
+#endif
 
 	if (eof == -1) {
 		/* Encapsulated option block */
@@ -1668,6 +1820,9 @@
 	}
 	if (!eof)
 		return 1;
+#ifdef PXE_DHCP_STRICT
+	pxeclient = find_vci_pxeclient(p);
+#endif
 	while (p < endp) {
 		unsigned char c = *p;
 		if (c == RFC1533_PAD) {
@@ -1702,11 +1857,34 @@
 			dhcp_reply=*(p+2);
 		else if (NON_ENCAP_OPT c == RFC2132_SRV_ID)
 			memcpy(&dhcp_server, p+2, sizeof(in_addr));
-#endif	/* NO_DHCP_SUPPORT */
+#endif	/* !NO_DHCP_SUPPORT */
 		else if (NON_ENCAP_OPT c == RFC1533_HOSTNAME) {
 			hostname = p + 2;
 			hostnamelen = *(p + 1);
 		}
+#ifdef PXE_DHCP_STRICT
+		else if (PXE_ENCAP_OPT c == PXE_BOOT_SERVERS) {
+			unsigned char *q = p + 2;
+			unsigned char *end = q + TAG_LEN(p);
+			while (q < end) {
+				decode_pxe_boot_servers(q);
+				q += q[2] * 4 + 3;
+			}
+		}
+		else if (PXE_ENCAP_OPT c == PXE_BOOT_MENU) {
+			unsigned char *q = p + 2;
+			unsigned char *end = q + TAG_LEN(p);
+			while (q < end) {
+				decode_pxe_boot_menu(q);
+				q += q[2] + 3;
+			}
+		}
+		else if (NON_ENCAP_OPT c == RFC1533_VENDOR && pxeclient) {
+			in_pxe_encapsulated_options = 1;
+			decode_rfc1533(p+2, 0, TAG_LEN(p), -1);
+			in_pxe_encapsulated_options = 0;
+		}
+#endif /* PXE_DHCP_STRICT */
 		else if (ENCAP_OPT c == RFC1533_VENDOR_MAGIC
 			 && TAG_LEN(p) >= 6 &&
 			  !memcmp(p+2,vendorext_magic,4) &&
diff -ur etherboot-5.4.1/src/include/bootp.h etherboot-5.4.1-pxe2/src/include/bootp.h
--- etherboot-5.4.1/src/include/bootp.h	2005-04-02 12:50:53.000000000 +0200
+++ etherboot-5.4.1-pxe2/src/include/bootp.h	2006-02-25 17:17:03.000000000 +0100
@@ -19,6 +19,8 @@
 #define BOOTP_CLIENT	68
 #endif
 #define PROXYDHCP_SERVER	4011 /* For PXE */
+#define PXE_BOOT_SERVER 4011
+#define PXE_BOOT_CLIENT 4011
 
 #define BOOTP_REQUEST	1
 #define BOOTP_REPLY	2
@@ -106,6 +108,23 @@
 #define RFC3679_PXE_CLIENT_UUID_LENGTH 17
 
 /*
+ * The following define the encapsulated vendor-specific PXE options
+ */
+#define PXE_MTFTP_IP 1
+#define PXE_MTFTP_CPORT 2
+#define PXE_MTFTP_SPORT 3
+#define PXE_MTFTP_TMOUT 4
+#define PXE_MTFTP_DELAY 5
+#define PXE_DISCOVERY_CONTROL 6
+#define PXE_MCAST_ADDR 7
+#define PXE_BOOT_SERVERS 8
+#define PXE_BOOT_MENU 9
+#define PXE_MENU_PROMPT 10
+#define PXE_MCAST_ADDRS_ALLOC 11
+#define PXE_CREDENTIAL_TYPES 12
+#define PXE_BOOT_ITEM 71
+
+/*
  * Values of RFC3679_PXE_CLIENT_ARCH can apparently be one of the
  * following, according to the PXE spec. The spec only actually
  * described the 2nd octet, not the first. Duh... assume 0.
diff -ur etherboot-5.4.1/src/include/etherboot.h etherboot-5.4.1-pxe2/src/include/etherboot.h
--- etherboot-5.4.1/src/include/etherboot.h	2005-04-02 12:50:51.000000000 +0200
+++ etherboot-5.4.1-pxe2/src/include/etherboot.h	2006-02-07 10:00:15.000000000 +0100
@@ -155,11 +155,20 @@
 #define MULTICAST_NETWORK 0xE0000000
 
 /* Helper macros used to identify when DHCP options are valid/invalid in/outside of encapsulation */
-#define NON_ENCAP_OPT in_encapsulated_options == 0 &&
+/* helpers for decoding RFC1533_VENDOR encapsulated options */
+#ifdef PXE_DHCP_STRICT
+#define PXE_ENCAP_OPT in_pxe_encapsulated_options == 1 &&
+#define PXE_NONENCAP_OPT in_pxe_encapsulated_options == 0 &&
+#else
+#define PXE_ENCAP_OPT
+#define PXE_NONENCAP_OPT
+#endif /* PXE_DHCP_STRICT */
+
+#define NON_ENCAP_OPT in_encapsulated_options == 0 && PXE_NONENCAP_OPT
 #ifdef ALLOW_ONLY_ENCAPSULATED
 #define ENCAP_OPT in_encapsulated_options == 1 &&
 #else
-#define ENCAP_OPT
+#define ENCAP_OPT PXE_NONENCAP_OPT
 #endif
 
 #include	"if_arp.h"
PGP.sig (application/pgp-signature, 186 B) - not displayed