Discussion:
[Dnsmasq-discuss] ProxyDHCP with UEFI systems
Michael Kuron
2015-10-19 11:54:54 UTC
Permalink
I made some changes to dnsmasq (patch below) that remove the PXE menu system (the option 43 stuff) if there’s only one menu entry and put the boot file name and server address directly into the file and siaddr fields. This works fine for BIOS systems, but doesn’t work for UEFI either.
Next thing I tried was to copy the boot file name and server address into options 66 and 67, but that doesn’t work either.

So far, it really seems like proxyDHCP support in UEFI systems is completely missing. I used VMware Fusion 8.0.1 and a recent Asus laptop for testing. If anybody could supply a Wireshark of a different implementation that works (Windows Deployment Services maybe?), it should be easy to adapt my patch.

It’s really easy to set up a test system in any modern version of VMware: just create a new VM and add the following to the .vmx file:
firmware = "efi"



diff --git a/src/rfc2131.c b/src/rfc2131.c
index 9f69ed5..27b2573 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -859,6 +859,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,

if (tmp)
{
+ int num_services = 0;
struct dhcp_boot *boot;

if (tmp->netid.net)
@@ -890,13 +891,49 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if (boot->file)
strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1);
}
+ else
+ {
+ struct pxe_service *service;
+ for (service = daemon->pxe_services; service; service = service->next)
+ if (pxearch == service->CSA && match_netid(service->netid, netid, 1))
+ ++num_services;
+
+ if (num_services == 1)
+ {
+ for (service = daemon->pxe_services; service; service = service->next)
+ if (pxearch == service->CSA && match_netid(service->netid, netid, 1))
+ {
+ if (service->sname)
+ mess->siaddr = a_record_from_hosts(service->sname, now);
+ else if (service->server.s_addr != 0)
+ mess->siaddr = service->server;
+ else
+ mess->siaddr = tmp->local;
+
+ if (service->CSA == 0)
+ snprintf((char *)mess->file, sizeof(mess->file), "%s.0", service->basename);
+ else if (service->CSA == 6 || service->CSA == 7 || service->CSA == 8 || service->CSA == 9)
+ {
+ char sname[16];
+ snprintf((char *)mess->file, sizeof(mess->file), "%s.efi", service->basename);
+ inet_ntop(AF_INET, &mess->siaddr, &sname, 16);
+ // Option 66 and 67 necessary according to http://www-01.ibm.com/support/docview.wss?uid=swg27027022&aid=1
+ option_put_string(mess, end, OPTION_SNAME, sname, 0);
+ option_put_string(mess, end, OPTION_FILENAME, &mess->file, 0);
+ }
+ else
+ strncpy((char *)mess->file, service->basename, sizeof(mess->file)-1);
+ }
+ }
+ }

option_put(mess, end, OPTION_MESSAGE_TYPE, 1,
mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK);
option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr));
pxe_misc(mess, end, uuid);
prune_vendor_opts(tagif_netid);
- do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
+ if (num_services != 1)
+ do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);

log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid);
log_tags(tagif_netid, ntohl(mess->xid));
The problem in known, but not the solution. I did start working on that
about six months ago, but got bogged down in creating a test system.
What would be really useful would be to find an implementation that
works with UEFI and proxy DHCP, and getting for packet captures to show
what should be sent. Cheers, Simon.
Simon Kelley
2015-10-20 20:38:00 UTC
Permalink
Is there any sort of standards document that explains how this is
supposed to work in EFI?

Cheers,

Simon.
Post by Michael Kuron
I made some changes to dnsmasq (patch below) that remove the PXE menu system (the option 43 stuff) if there’s only one menu entry and put the boot file name and server address directly into the file and siaddr fields. This works fine for BIOS systems, but doesn’t work for UEFI either.
Next thing I tried was to copy the boot file name and server address into options 66 and 67, but that doesn’t work either.
So far, it really seems like proxyDHCP support in UEFI systems is completely missing. I used VMware Fusion 8.0.1 and a recent Asus laptop for testing. If anybody could supply a Wireshark of a different implementation that works (Windows Deployment Services maybe?), it should be easy to adapt my patch.
firmware = "efi"
diff --git a/src/rfc2131.c b/src/rfc2131.c
index 9f69ed5..27b2573 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -859,6 +859,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if (tmp)
{
+ int num_services = 0;
struct dhcp_boot *boot;
if (tmp->netid.net)
@@ -890,13 +891,49 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if (boot->file)
strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1);
}
+ else
+ {
+ struct pxe_service *service;
+ for (service = daemon->pxe_services; service; service = service->next)
+ if (pxearch == service->CSA && match_netid(service->netid, netid, 1))
+ ++num_services;
+
+ if (num_services == 1)
+ {
+ for (service = daemon->pxe_services; service; service = service->next)
+ if (pxearch == service->CSA && match_netid(service->netid, netid, 1))
+ {
+ if (service->sname)
+ mess->siaddr = a_record_from_hosts(service->sname, now);
+ else if (service->server.s_addr != 0)
+ mess->siaddr = service->server;
+ else
+ mess->siaddr = tmp->local;
+
+ if (service->CSA == 0)
+ snprintf((char *)mess->file, sizeof(mess->file), "%s.0", service->basename);
+ else if (service->CSA == 6 || service->CSA == 7 || service->CSA == 8 || service->CSA == 9)
+ {
+ char sname[16];
+ snprintf((char *)mess->file, sizeof(mess->file), "%s.efi", service->basename);
+ inet_ntop(AF_INET, &mess->siaddr, &sname, 16);
+ // Option 66 and 67 necessary according to http://www-01.ibm.com/support/docview.wss?uid=swg27027022&aid=1
+ option_put_string(mess, end, OPTION_SNAME, sname, 0);
+ option_put_string(mess, end, OPTION_FILENAME, &mess->file, 0);
+ }
+ else
+ strncpy((char *)mess->file, service->basename, sizeof(mess->file)-1);
+ }
+ }
+ }
option_put(mess, end, OPTION_MESSAGE_TYPE, 1,
mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK);
option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr));
pxe_misc(mess, end, uuid);
prune_vendor_opts(tagif_netid);
- do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
+ if (num_services != 1)
+ do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid);
log_tags(tagif_netid, ntohl(mess->xid));
The problem in known, but not the solution. I did start working on that
about six months ago, but got bogged down in creating a test system.
What would be really useful would be to find an implementation that
works with UEFI and proxy DHCP, and getting for packet captures to show
what should be sent. Cheers, Simon.
_______________________________________________
Dnsmasq-discuss mailing list
http://lists.thekelleys.org.uk/mailman/listinfo/dnsmasq-discuss
Michael Kuron
2015-10-24 13:21:22 UTC
Permalink
More wiresharking helped me figure this out. So when UEFI receives a DHCP Offer or Proxy DHCP Offer with Vendor Class Identifier (option 60) set to PXEClient, it sends a DHCP Request to the siaddr from the offer, but on port 4011. If the server then sends a DHCP ACK back to port 4011, containing an siaddr and file name, that file is then booted. The PXE menu system does not appear to be supported by UEFI.

So here’s a new patch. It does two things if there is only one applicable --pxe-service specified:
- If it receives a Discover on port 68 with a Vendor class identifier equal to PXEClient, it sets the siaddr in the Offer to the local address.
- If it receives a Request on port 4011 with a Vendor class identifier equal to PXEClient, it sets the siaddr and file as specified using the --pxe-service option.

This is actually working for me with VMware Fusion 8 and with a recent Asus laptop. This is also backwards compatible with BIOS PXE booting (the port 4011 stuff was specified a long time ago).

Regards,
Michael



diff --git a/src/rfc2131.c b/src/rfc2131.c
index 9f69ed5..32f18d1 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -859,6 +859,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,

if (tmp)
{
+ int num_services = 0;
struct dhcp_boot *boot;

if (tmp->netid.net)
@@ -890,13 +891,44 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if (boot->file)
strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1);
}
+ else
+ {
+ struct pxe_service *service;
+ for (service = daemon->pxe_services; service; service = service->next)
+ if (pxearch == service->CSA && match_netid(service->netid, netid, 1))
+ ++num_services;
+
+ if (num_services == 1 && !pxe)
+ mess->siaddr = tmp->local;
+ else if (num_services == 1)
+ {
+ for (service = daemon->pxe_services; service; service = service->next)
+ if (pxearch == service->CSA && match_netid(service->netid, netid, 1))
+ {
+ if (service->sname)
+ mess->siaddr = a_record_from_hosts(service->sname, now);
+ else if (service->server.s_addr != 0)
+ mess->siaddr = service->server;
+ else
+ mess->siaddr = tmp->local;
+
+ if (service->CSA == 0)
+ snprintf((char *)mess->file, sizeof(mess->file), "%s.0", service->basename);
+ else if (service->CSA == 6 || service->CSA == 7 || service->CSA == 8 || service->CSA == 9)
+ snprintf((char *)mess->file, sizeof(mess->file), "%s.efi", service->basename);
+ else
+ strncpy((char *)mess->file, service->basename, sizeof(mess->file)-1);
+ }
+ }
+ }

option_put(mess, end, OPTION_MESSAGE_TYPE, 1,
mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK);
option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr));
pxe_misc(mess, end, uuid);
prune_vendor_opts(tagif_netid);
- do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
+ if (num_services != 1)
+ do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);

log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid);
log_tags(tagif_netid, ntohl(mess->xid));
Michael Kuron
2015-10-24 14:51:07 UTC
Permalink
Actually, I shouldn’t set the siaddr in the initial Offer. It’s fine for VMware UEFI and BIOS, but the Asus UEFI will end up trying to download the boot file from the Offer’s siaddr instead of the ACK’s siaddr if it’s present. So the small additional modification below will also allow the TFTP server to be on a different machine than the proxy DHCP server.

--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -898,9 +898,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if (pxearch == service->CSA && match_netid(service->netid, netid, 1))
++num_services;

- if (num_services == 1 && !pxe)
- mess->siaddr = tmp->local;
- else if (num_services == 1)
+ if (num_services == 1 && pxe)
{
for (service = daemon->pxe_services; service; service = service->next)
if (pxearch == service->CSA && match_netid(service->netid, netid, 1))
Post by Michael Kuron
More wiresharking helped me figure this out. So when UEFI receives a DHCP Offer or Proxy DHCP Offer with Vendor Class Identifier (option 60) set to PXEClient, it sends a DHCP Request to the siaddr from the offer, but on port 4011. If the server then sends a DHCP ACK back to port 4011, containing an siaddr and file name, that file is then booted. The PXE menu system does not appear to be supported by UEFI.
- If it receives a Discover on port 68 with a Vendor class identifier equal to PXEClient, it sets the siaddr in the Offer to the local address.
- If it receives a Request on port 4011 with a Vendor class identifier equal to PXEClient, it sets the siaddr and file as specified using the --pxe-service option.
This is actually working for me with VMware Fusion 8 and with a recent Asus laptop. This is also backwards compatible with BIOS PXE booting (the port 4011 stuff was specified a long time ago).
Regards,
Michael
diff --git a/src/rfc2131.c b/src/rfc2131.c
index 9f69ed5..32f18d1 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -859,6 +859,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if (tmp)
{
+ int num_services = 0;
struct dhcp_boot *boot;
if (tmp->netid.net)
@@ -890,13 +891,44 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if (boot->file)
strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1);
}
+ else
+ {
+ struct pxe_service *service;
+ for (service = daemon->pxe_services; service; service = service->next)
+ if (pxearch == service->CSA && match_netid(service->netid, netid, 1))
+ ++num_services;
+
+ if (num_services == 1 && !pxe)
+ mess->siaddr = tmp->local;
+ else if (num_services == 1)
+ {
+ for (service = daemon->pxe_services; service; service = service->next)
+ if (pxearch == service->CSA && match_netid(service->netid, netid, 1))
+ {
+ if (service->sname)
+ mess->siaddr = a_record_from_hosts(service->sname, now);
+ else if (service->server.s_addr != 0)
+ mess->siaddr = service->server;
+ else
+ mess->siaddr = tmp->local;
+
+ if (service->CSA == 0)
+ snprintf((char *)mess->file, sizeof(mess->file), "%s.0", service->basename);
+ else if (service->CSA == 6 || service->CSA == 7 || service->CSA == 8 || service->CSA == 9)
+ snprintf((char *)mess->file, sizeof(mess->file), "%s.efi", service->basename);
+ else
+ strncpy((char *)mess->file, service->basename, sizeof(mess->file)-1);
+ }
+ }
+ }
option_put(mess, end, OPTION_MESSAGE_TYPE, 1,
mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK);
option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr));
pxe_misc(mess, end, uuid);
prune_vendor_opts(tagif_netid);
- do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
+ if (num_services != 1)
+ do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid);
log_tags(tagif_netid, ntohl(mess->xid));
_______________________________________________
Dnsmasq-discuss mailing list
http://lists.thekelleys.org.uk/mailman/listinfo/dnsmasq-discuss
Michael Kuron
2015-10-31 10:39:00 UTC
Permalink
As it turns out, UEFI does support PXE menus, but the implementations are rather buggy in that regard. VMware often does not render the menu on the screen, but you can blindly select the menu entry using the arrow keys and boot it with the return key. A recent Asus laptop renders the menu, but ignores the TFTP server IP specified in the PXE service and instead tries to open a TFTP connection to the DHCP server’s IP. There probably are some fully-working implementations out there as well.
Below is a patch that combines the work from my previous emails with this new discovery. It always redirects to port 4011. If only one service is specified, it puts that into the siaddr and file fields directly, which should work for all UEFI implementations. If more than one service is specified, it sends a menu, which might reveal bugs in the UEFI implementation. All of this is backwards compatible with BIOS because the port 4011 redirect is part of the PXE spec.

How can I submit this patch for inclusion in dnsmasq?


diff --git a/src/rfc2131.c b/src/rfc2131.c
index 9f69ed5..bdc0f78 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -824,7 +824,10 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
else
mess->siaddr = context->local;

- snprintf((char *)mess->file, sizeof(mess->file), "%s.%d", service->basename, layer);
+ if (service->CSA == 6 || service->CSA == 7 || service->CSA == 8 || service->CSA == 9)
+ snprintf((char *)mess->file, sizeof(mess->file), "%s.efi", service->basename);
+ else
+ snprintf((char *)mess->file, sizeof(mess->file), "%s.%d", service->basename, layer);
option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr));
pxe_misc(mess, end, uuid);
@@ -859,6 +862,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,

if (tmp)
{
+ int num_services = 0;
struct dhcp_boot *boot;

if (tmp->netid.net)
@@ -890,13 +894,42 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if (boot->file)
strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1);
}
+ else
+ {
+ struct pxe_service *service;
+ for (service = daemon->pxe_services; service; service = service->next)
+ if (pxearch == service->CSA && match_netid(service->netid, netid, 1))
+ ++num_services;
+
+ if (num_services == 1 && pxe)
+ {
+ for (service = daemon->pxe_services; service; service = service->next)
+ if (pxearch == service->CSA && match_netid(service->netid, netid, 1))
+ {
+ if (service->sname)
+ mess->siaddr = a_record_from_hosts(service->sname, now);
+ else if (service->server.s_addr != 0)
+ mess->siaddr = service->server;
+ else
+ mess->siaddr = tmp->local;
+
+ if (service->CSA == 0)
+ snprintf((char *)mess->file, sizeof(mess->file), "%s.0", service->basename);
+ else if (service->CSA == 6 || service->CSA == 7 || service->CSA == 8 || service->CSA == 9)
+ snprintf((char *)mess->file, sizeof(mess->file), "%s.efi", service->basename);
+ else
+ strncpy((char *)mess->file, service->basename, sizeof(mess->file)-1);
+ }
+ }
+ }

option_put(mess, end, OPTION_MESSAGE_TYPE, 1,
mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK);
option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr));
pxe_misc(mess, end, uuid);
prune_vendor_opts(tagif_netid);
- do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
+ if (num_services != 1 && pxe)
+ do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);

log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid);
log_tags(tagif_netid, ntohl(mess->xid));

Loading...