[syslinux] [PATCH 0/1] EFI PXE DHCP/proxyDHCP issues fix

jeff_sloan at selinc.com jeff_sloan at selinc.com
Wed Jun 3 12:25:40 PDT 2015


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


More information about the Syslinux mailing list