jeff_sloan at selinc.com
2015-Jun-03 19:25 UTC
[syslinux] [PATCH 0/1] EFI PXE DHCP/proxyDHCP issues fix
The UEFI PXE boot DHCP/proxyDHCP issue is very timely. I am working on that very topic now. Our scenario is a manufacturing environment set up to format and load/install custom hard drive images in our systems. Our environment: This is a manufacturing floor setup where we build computers for our customers. We have two servers and a client in the mix, all connected via Ethernet (IPv4 only). There is a ?real? DHCP server maintained by a different group, a proxyDHCP server running CentOS 6 that we control and the client systems are custom built for customers. We offer multiple OSes and options so the hard drive has to be custom configured. The real DHCP server supplies client ip address only. Everything else comes from proxyDHCP. The proxyDHCP server supplies boot file name, boot file (syslinux.efi), boot loader (ldlinux.e64), config file, tinycore and OS image files. My actual test setup mimics the factory environment. I have a test network, client system, proxyDHCP, an off-site real DHCP server and a managed eth switch so I can mirror the client port for WireShark. The client is connected to an in-target debugger so I can step through the code easily. Other than that, nothing special. Problem 1: The first problem is an issue in TianoCore BIOS that has been there since the beginning. When responding to a proxyDHCP and attempting to grab the boot filename, BIOS specifies an incorrect sending/listening (source/destination) port pair. It should be 68/4011 but sends 4011/4011. I updated this for IPv4 but noticed that the problem has been carried over to IPv6 code in our code base. Failing scenario/behavior for problem 1: You can determine if you have this issue by setting up a WireShark system and snooping. The failing scenario is in the UDP request for boot filename to the proxyDHCP server, both ports are 4011 so the proxy server ignores the packet and does not reply. Resolution for problem 1: If you have access to your BIOS source code, change boot request source port to 68. If you don?t have access to the source, contact your BIOS provider. I will alert TianoCore to this issue but it may also be present in some OEM BIOS provider?s code. Problem 2: The next problem is packet parsing and incomplete ip address sets on the syslinux side. The basic issue is that the packets are incomplete or not used in a proxyDHCP environment. The real DHCP Offer/Ack contains the client ip. The proxy Offer contains the server ip. We also have to grab the gateway and subnet mask. There is no single packet or location where all the addresses are contained. The most complete struct that I found is the IPInfo but even there the server ip is incorrect. So we need to either build and populate a complete struct for proxy or pull from several different packets when needed. Failing scenario/behavior for problem 2: After syslinux.efi is downloaded and execution begins, no further Ethernet communication occurs. This is caused by incomplete packets that are dropped in the client instead of being sent. The return code is EFI_UNSUPPORTED but the error is ignored after the return from the UDP send command in BIOS. Should this be handled differently? Resolution for problem 2: This is my implementation. Hopefully it is a good start to help us get to the correct approach. In pxe.c, I populate DhcpAck packet with all of the correct ip addresses and netmask. In udp.c, at the end, I disable UseDefaultAddress and populate missing correct ip addresses. Details and code below. My approach: In efi/pxe.c: #include <syslinux/firmware.h> #include <syslinux/pxe_api.h> #include "efi.h" #include "net.h" #include "fs/pxe/pxe.h" ++ extern struct syslinux_ipinfo IPInfo; const struct url_scheme url_schemes[] = { { "tftp", tftp_open, 0 }, { "http", http_open, O_DIRECTORY }, { "ftp", ftp_open, O_DIRECTORY }, { NULL, NULL, 0 }, }; ? void net_parse_dhcp(void) { EFI_PXE_BASE_CODE_MODE *mode; EFI_PXE_BASE_CODE *bc; EFI_PXE_BASE_CODE_DHCPV4_PACKET* pkt_v4; unsigned int pkt_len = sizeof(EFI_PXE_BASE_CODE_PACKET); EFI_STATUS status; EFI_HANDLE *handles = NULL; UINTN nr_handles = 0; uint8_t hardlen; uint32_t ip; char dst[256]; status = LibLocateHandle(ByProtocol, &PxeBaseCodeProtocol, NULL, &nr_handles, &handles); if (status != EFI_SUCCESS) return; /* Probably want to use IPv4 protocol to decide which handle to use */ status = uefi_call_wrapper(BS->HandleProtocol, 3, handles[0], &PxeBaseCodeProtocol, (void **)&bc); if (status != EFI_SUCCESS) { Print(L"Failed to lookup PxeBaseCodeProtocol\n"); } mode = bc->Mode; /* * Parse any "before" hardcoded options */ parse_dhcp_options(embedded_dhcp_options.dhcp_data, embedded_dhcp_options.bdhcp_len, 0); /* * Get the DHCP client identifiers (query info 1) */ Print(L"Getting cached packet "); parse_dhcp(&mode->DhcpAck.Dhcpv4, pkt_len); Print(L"\n"); /* * We don't use flags from the request packet, so * this is a good time to initialize DHCPMagic... * Initialize it to 1 meaning we will accept options found; * in earlier versions of PXELINUX bit 0 was used to indicate * we have found option 208 with the appropriate magic number; * we no longer require that, but MAY want to re-introduce * it in the future for vendor encapsulated options. */ *(char *)&DHCPMagic = 1; /* * Get the boot file and other info. This lives in the CACHED_REPLY * packet (query info 3) */ if(mode->PxeReplyReceived) pkt_v4 = &mode->PxeReply.Dhcpv4; else if(mode->ProxyOfferReceived) { //I decided to populate DhcpAck with all proxy ip addresses but //any of the saved packets can be used. ++ ip = IPInfo.myip; ++ mode->PxeReply.Dhcpv4.BootpYiAddr[0] = ip & 0xff; ++ mode->PxeReply.Dhcpv4.BootpYiAddr[1] = ip >> 8 & 0xff; ++ mode->PxeReply.Dhcpv4.BootpYiAddr[2] = ip >> 16 & 0xff; ++ mode->PxeReply.Dhcpv4.BootpYiAddr[3] = ip >> 24 & 0xff; // For our needs, we can hard-code server ip since it is fixed. // Others may need to search in a packet somewhere. ++ IPInfo.serverip = 0xFA1F270A; ++ ip = IPInfo.serverip; ++ mode->DhcpAck.Dhcpv4.BootpSiAddr[0] = ip & 0xff; ++ mode->DhcpAck.Dhcpv4.BootpSiAddr[1] = ip >> 8 & 0xff; ++ mode->DhcpAck.Dhcpv4.BootpSiAddr[2] = ip >> 16 & 0xff; ++ mode->DhcpAck.Dhcpv4.BootpSiAddr[3] = ip >> 24 & 0xff; ++ ip = IPInfo.gateway; ++ mode->DhcpAck.Dhcpv4.BootpGiAddr[0] = ip & 0xff; ++ mode->DhcpAck.Dhcpv4.BootpGiAddr[1] = ip >> 8 & 0xff; ++ mode->DhcpAck.Dhcpv4.BootpGiAddr[2] = ip >> 16 & 0xff; ++ mode->DhcpAck.Dhcpv4.BootpGiAddr[3] = ip >> 24 & 0xff; ++ pkt_v4 = &mode->DhcpAck.Dhcpv4; } else pkt_v4 = &mode->DhcpAck.Dhcpv4; parse_dhcp(pkt_v4, pkt_len); /* * Save away MAC address (assume this is in query info 2. If this * turns out to be problematic it might be better getting it from * the query info 1 packet */ -- hardlen = mode->DhcpAck.Dhcpv4.BootpHwAddrLen; -- MAC_len = hardlen > 16 ? 0 : hardlen; -- MAC_type = mode->DhcpAck.Dhcpv4.BootpHwType; -- memcpy(MAC, mode->DhcpAck.Dhcpv4.BootpHwAddr, MAC_len); ++ hardlen = pkt_v4->BootpHwAddrLen; ++ MAC_len = hardlen > 16 ? 0 : hardlen; ++ MAC_type = pkt_v4->BootpHwType; ++ memcpy(MAC, pkt_v4->BootpHwAddr, MAC_len); Print(L"\n"); /* * Parse any "after" hardcoded options */ parse_dhcp_options(embedded_dhcp_options.dhcp_data + embedded_dhcp_options.bdhcp_len, embedded_dhcp_options.adhcp_len, 0); As mentioned above, another aspect to this scenario is that UseDefaultAddress is set to TRUE in two different methods in udp.c however, the defaults do not work. In that case, the packets were not parsed to get any of the ip addresses so I used IPInfo to get client ip and netmask instead of using defaults. In efi/udp.c: void core_udp_connect(struct pxe_pvt_inode *socket, uint32_t ip, uint16_t port) { EFI_UDP4_CONFIG_DATA udata; EFI_STATUS status; EFI_UDP4 *udp; udp = (EFI_UDP4 *)socket->net.efi.binding->this; memset(&udata, 0, sizeof(udata)); /* Re-use the existing local port number */ udata.StationPort = socket->net.efi.localport; -- udata.UseDefaultAddress = TRUE; ++ udata.UseDefaultAddress = FALSE; memcpy(&udata.RemoteAddress, &ip, sizeof(ip)); udata.RemotePort = port; udata.AcceptPromiscuous = TRUE; udata.TimeToLive = 64; ++ ip = IPInfo.myip; ++ memcpy(&udata.StationAddress, &ip, sizeof(ip)); ++ ip = IPInfo.netmask; ++ memcpy(&udata.SubnetMask, &ip, sizeof(ip)); status = core_udp_configure(udp, &udata, L"core_udp_connect"); if (status != EFI_SUCCESS) { Print(L"Failed to configure UDP: %d\n", status); return; } } ? void core_udp_sendto(struct pxe_pvt_inode *socket, const void *data, size_t len, uint32_t ip, uint16_t port) { EFI_UDP4_COMPLETION_TOKEN *token; EFI_UDP4_TRANSMIT_DATA *txdata; EFI_UDP4_FRAGMENT_DATA *frag; EFI_UDP4_CONFIG_DATA udata; EFI_STATUS status; struct efi_binding *b; EFI_UDP4 *udp; (void)socket; b = efi_create_binding(&Udp4ServiceBindingProtocol, &Udp4Protocol); if (!b) return; udp = (EFI_UDP4 *)b->this; token = zalloc(sizeof(*token)); if (!token) goto out; txdata = zalloc(sizeof(*txdata)); if (!txdata) goto bail; memset(&udata, 0, sizeof(udata)); /* Re-use the existing local port number */ udata.StationPort = socket->net.efi.localport; -- udata.UseDefaultAddress = TRUE; ++ udata.UseDefaultAddress = FALSE; memcpy(&udata.RemoteAddress, &ip, sizeof(ip)); udata.RemotePort = port; udata.AcceptPromiscuous = TRUE; udata.TimeToLive = 64; ++ ip = IPInfo.myip; ++ memcpy(&udata.StationAddress, &ip, sizeof(ip)); ++ ip = IPInfo.netmask; ++ memcpy(&udata.SubnetMask, &ip, sizeof(ip)); status = core_udp_configure(udp, &udata, L"core_udp_sendto"); if (status != EFI_SUCCESS) goto bail; ? Thoughts? Thanks! Jeff Sloan
Patrick Masotta
2015-Jun-04 10:00 UTC
[syslinux] [PATCH 0/1] EFI PXE DHCP/proxyDHCP issues fix
>>>The real DHCP server supplies client ip address only. Everything else comes from proxyDHCP. The proxyDHCP server supplies boot file name, boot file (syslinux.efi), boot loader (ldlinux.e64), config file, tinycore and OS image files. <<< This is not completely correct: your DHCP server provides an IP and your proxyDHCP server only processes a complementary DHCP transaction providing the "NBP name" and "TFTP server IP". Actual files are really offered by a "TFTP Server" that in your case surely is running with your proxyDHCP under the same OS instance.>>>Problem 1: The first problem is an issue in TianoCore BIOS that has been there since the beginning. When responding to a proxyDHCP and attempting to grab the boot filename, BIOS specifies an incorrect sending/listening (source/destination) port pair. It should be 68/4011 but sends 4011/4011. I updated this for IPv4 but noticed that the problem has been carried over to IPv6 code in our code base. <<< RFC2131 says: "DHCP uses UDP as its transport protocol. DHCP messages from a client to a server are sent to the 'DHCP server' port (67), and DHCP messages from a server to a client are sent to the 'DHCP client' port (68)." but RFC2131 does not define a proxyDHCP service; the PXE v2.1 standard does it PXE v2.1 says: "Step 6. The Boot Server unicasts a DHCPACK packet back to the client on the client source port." Then it's not really specified that the DHCPREQUEST to a proxyDHCP server must use 68 as source port... As I can see this "issue" (if there's one here) is all about the agreement between the client FW and the proxyDHCP server; syslinux has nothing to do here. >>> Failing scenario/behavior for problem 1: You can determine if you have this issue by setting up a WireShark system and snooping. The failing scenario is in the UDP request for boot filename to the proxyDHCP server, both ports are 4011 so the proxy server ignores the packet and does not reply. <<< This depends on the proxyDHCP; if you use i.e Serva you will have no problem at all, the proxyDHCP ACK will be send to the source port used by the client on its previous DHCPREQUEST. That's it. What failing proxyDHCP are you using?>>>Resolution for problem 1: If you have access to your BIOS source code, change boot request source port to 68. If you don?t have access to the source, contact your BIOS provider. I will alert TianoCore to this issue but it may also be present in some OEM BIOS provider?s code. <<< I think you should probably delve deeper in the standards before trying to change this. just my 2 cents.>>>Problem 2: The next problem is packet parsing and incomplete ip address sets on the syslinux side. The basic issue is that the packets are incomplete or not used in a proxyDHCP environment. The real DHCP Offer/Ack contains the client ip. The proxy Offer contains the server ip. We also have to grab the gateway and subnet mask. There is no single packet or location where all the addresses are contained. The most complete struct that I found is the IPInfo but even there the server ip is incorrect. So we need to either build and populate a complete struct for proxy or pull from several different packets when needed. <<< The DHCP transaction has several stages; in EFI firmware every stage populates a different packet. the packets are: DhcpAck,ProxyOffer,PxeReply. DhcpAck: usually includes only the DHCP parameters ProxyOffer: includes redirection parameters (PXE stuff) PxeReply: is populated after receiving an answer when using PXE menu capabilities. Tianocore copies PxeReply to ProxyOffer because many NBPs do not parse PxeReply This is not really new stuff BIOS based PXE was also having several packets cached...>>>Failing scenario/behavior for problem 2: After syslinux.efi is downloaded and execution begins, no further Ethernet communication occurs. This is caused by incomplete packets that are dropped in the client instead of being sent. The return code is EFI_UNSUPPORTED but the error is ignored after the return from the UDP send command in BIOS. Should this be handled differently? <<< Have you applied the patch mentioned on this thread??? Best, Patrick
On Wed, Jun 3, 2015 at 3:25 PM, Jeff via Syslinux <syslinux at zytor.com> wrote:> The UEFI PXE boot DHCP/proxyDHCP issue is very timely. I am working on > that very topic now.> Our environment:> The real DHCP server supplies client ip address only. Everything else > comes from proxyDHCP.> Problem 2: > > The next problem is packet parsing and incomplete ip address sets on the > syslinux side. The basic issue is that the packets are incomplete or not > used in a proxyDHCP environment. The real DHCP Offer/Ack contains the > client ip. The proxy Offer contains the server ip. We also have to grab > the gateway and subnet mask. There is no single packet or location where > all the addresses are contained. The most complete struct that I found is > the IPInfo but even there the server ip is incorrect. So we need to either > build and populate a complete struct for proxy or pull from several > different packets when needed. > > Failing scenario/behavior for problem 2: > > After syslinux.efi is downloaded and execution begins, no further Ethernet > communication occurs. This is caused by incomplete packets that are > dropped in the client instead of being sent. The return code is > EFI_UNSUPPORTED but the error is ignored after the return from the UDP > send command in BIOS. Should this be handled differently? > > Resolution for problem 2: > > This is my implementation. Hopefully it is a good start to help us get to > the correct approach. In pxe.c, I populate DhcpAck packet with all of the > correct ip addresses and netmask. In udp.c, at the end, I disable > UseDefaultAddress and populate missing correct ip addresses. Details and > code below. > > My approach:> Thoughts?1) It's not a diff. 2) What exact version or commit ID is this patch attempt based on? 3) If you have a system that successfully can build Syslinux EFI, have you tried commit ID 81eeaa3? -- -Gene
On Mon, Jun 8, 2015 at 4:07 PM, <jeff_sloan at selinc.com> wrote:> Gene,> Where do you keep the development tree? It's probably staring me in the face > but I can't find any updates beyond initial 6.03.Officially listed at http://www.syslinux.org/wiki/index.php/Development The primary repo at kernel.org is behind. The development repo at zytor.com is up to date but inaccessible. There's been recent discussion about moving the primary repo. My repo on github is up to date and accessible. https://github.com/geneC/syslinux.git git://github.com/geneC1/syslinux.git -- -Gene
On Sat, Jun 6, 2015 at 6:19 AM, Gene Cumm <gene.cumm at gmail.com> wrote:> On Wed, Jun 3, 2015 at 3:25 PM, Jeff via Syslinux <syslinux at zytor.com> wrote: >> The UEFI PXE boot DHCP/proxyDHCP issue is very timely. I am working on >> that very topic now. > >> Our environment: > >> The real DHCP server supplies client ip address only. Everything else >> comes from proxyDHCP. > >> Problem 2: >> >> The next problem is packet parsing and incomplete ip address sets on the >> syslinux side. The basic issue is that the packets are incomplete or not >> used in a proxyDHCP environment. The real DHCP Offer/Ack contains the >> client ip. The proxy Offer contains the server ip. We also have to grab >> the gateway and subnet mask. There is no single packet or location where >> all the addresses are contained. The most complete struct that I found is >> the IPInfo but even there the server ip is incorrect. So we need to either >> build and populate a complete struct for proxy or pull from several >> different packets when needed. >> >> Failing scenario/behavior for problem 2: >> >> After syslinux.efi is downloaded and execution begins, no further Ethernet >> communication occurs. This is caused by incomplete packets that are >> dropped in the client instead of being sent. The return code is >> EFI_UNSUPPORTED but the error is ignored after the return from the UDP >> send command in BIOS. Should this be handled differently? >> >> Resolution for problem 2: >> >> This is my implementation. Hopefully it is a good start to help us get to >> the correct approach. In pxe.c, I populate DhcpAck packet with all of the >> correct ip addresses and netmask. In udp.c, at the end, I disable >> UseDefaultAddress and populate missing correct ip addresses. Details and >> code below. >> >> My approach: > >> Thoughts? > > 1) It's not a diff. > > 2) What exact version or commit ID is this patch attempt based on? > > 3) If you have a system that successfully can build Syslinux EFI, have > you tried commit ID 81eeaa3?Jeff, Could you try commit 8a00e49 and let us know what results you get? -- -Gene
jeff_sloan at selinc.com
2015-Jun-18 12:24 UTC
[syslinux] [PATCH 0/1] EFI PXE DHCP/proxyDHCP issues fix
Patrick, I wanted to follow up with your earlier questions.>>>>>> The real DHCP server supplies client ip address only. Everything else comes from proxyDHCP. The proxyDHCP server supplies boot file name, boot file (syslinux.efi), boot loader (ldlinux.e64), config file, tinycore and OS image files. <<< This is not completely correct: your DHCP server provides an IP and your proxyDHCP server only processes a complementary DHCP transaction providing the "NBP name" and "TFTP server IP". Actual files are really offered by a "TFTP Server" that in your case surely is running with your proxyDHCP under the same OS instance. <<< I was thinking in terms of the physical devices/systems but should be describing my setup in terms of the applications. The actual hardware is not germaine. With that frame of reference, my setup is exactly as you describe: DHCP, proxyDHCP and TFTP servers. DHCP supplies client IP address in a DHCPOffer packet proxyDHCP provides TFTP server IP in DHCPOffer packet with NULL client IP as well as NBP filename TFTP server provides all files requested Sorry for the confusion.>>>>>> Problem 1: The first problem is an issue in TianoCore BIOS that has been there since the beginning. When responding to a proxyDHCP and attempting to grab the boot filename, BIOS specifies an incorrect sending/listening (source/destination) port pair. It should be 68/4011 but sends 4011/4011. I updated this for IPv4 but noticed that the problem has been carried over to IPv6 code in our code base. <<< RFC2131 says: "DHCP uses UDP as its transport protocol. DHCP messages from a client to a server are sent to the 'DHCP server' port (67), and DHCP messages from a server to a client are sent to the 'DHCP client' port (68)." but RFC2131 does not define a proxyDHCP service; the PXE v2.1 standard does it PXE v2.1 says: "Step 6. The Boot Server unicasts a DHCPACK packet back to the client on the client source port." Then it's not really specified that the DHCPREQUEST to a proxyDHCP server must use 68 as source port... As I can see this "issue" (if there's one here) is all about the agreement between the client FW and the proxyDHCP server; syslinux has nothing to do here. <<< As you correctly point out, d.o.r.a. with the DHCP server must use port 68 since there is no client IP at that point. After that, any valid port is acceptable since none are defined by the pxe or uefi specs. Good call! Digging deeper into this issue, we have a problem with our proxyDHCP server. It is not handling any client listening port except for 68 by ignoring the packet so the BIOS fix was actually a band-aid. I have verified that the Boot Request UDP packets are identical using port 68 and 4011 other than the checksums so BIOS is sending correct packets. I originally mentioned this issue here to help anyone who might be seeing the same problem. It is not a syslinux problem in any way.>>>>>> Failing scenario/behavior for problem 1: You can determine if you have this issue by setting up a WireShark system and snooping. The failing scenario is in the UDP request for boot filename to the proxyDHCP server, both ports are 4011 so the proxy server ignores the packet and does not reply. <<< This depends on the proxyDHCP; if you use i.e Serva you will have no problem at all, the proxyDHCP ACK will be send to the source port used by the client on its previous DHCPREQUEST. That's it. What failing proxyDHCP are you using? <<< Our customer was not able to give me details on proxy and tftp server software. He is currently on vacation but I will work with him when he returns to figure out what exactly he is using. As soon I know the answer I will post it here so everyone knows which proxyserver is failing and I will work on a fix.>>>>>> Resolution for problem 1: If you have access to your BIOS source code, change boot request source port to 68. If you don?t have access to the source, contact your BIOS provider. I will alert TianoCore to this issue but it may also be present in some OEM BIOS provider?s code. <<< I think you should probably delve deeper in the standards before trying to change this. just my 2 cents. <<< Good advice and done. It is not a BIOS problem so I will not do anything as far as TianoCore.>>>>>> Problem 2: The next problem is packet parsing and incomplete ip address sets on the syslinux side. The basic issue is that the packets are incomplete or not used in a proxyDHCP environment. The real DHCP Offer/Ack contains the client ip. The proxy Offer contains the server ip. We also have to grab the gateway and subnet mask. There is no single packet or location where all the addresses are contained. The most complete struct that I found is the IPInfo but even there the server ip is incorrect. So we need to either build and populate a complete struct for proxy or pull from several different packets when needed. <<< The DHCP transaction has several stages; in EFI firmware every stage populates a different packet. the packets are: DhcpAck,ProxyOffer,PxeReply. DhcpAck: usually includes only the DHCP parameters ProxyOffer: includes redirection parameters (PXE stuff) PxeReply: is populated after receiving an answer when using PXE menu capabilities. Tianocore copies PxeReply to ProxyOffer because many NBPs do not parse PxeReply This is not really new stuff BIOS based PXE was also having several packets cached... <<< I definitely did not make myself clear here. You are correct in the contents of each of the cached packets so no one packet contains all of the necessary data. I was thinking that we should pull it all together into some struct but in the actual patch that I posted instead of updating any of the packets, I pulled the TFTP server ip address into pkt4_v4, a temporary packet in function net_parse_dhcp, before parsing. However, if there is a better way of handling this, I would be happy to implement it.>>>Failing scenario/behavior for problem 2: After syslinux.efi is downloaded and execution begins, no further Ethernet communication occurs. This is caused by incomplete packets that are dropped in the client instead of being sent. The return code is EFI_UNSUPPORTED but the error is ignored after the return from the UDP send command in BIOS. Should this be handled differently? <<<>>>Have you applied the patch mentioned on this thread??? <<< I cleaned up the patch and packaged it in a different thread with the subject: [PATCH 0/1] Network UEFI PXE DHCP/proxyDHCP fix. Now that zytor is back up, I will push it. Thanks again for the feedback! Jeff