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

jeff_sloan at selinc.com
Wed, 03 Jun 2015 19:25:40 -0000

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