diff --git a/src/rfc2131.c b/src/rfc2131.c index f3c88951..42d1ba89 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -63,7 +63,7 @@ static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char static int prune_vendor_opts(struct dhcp_netid *netid); static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now); struct dhcp_boot *find_boot(struct dhcp_netid *netid); - +static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now); size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, size_t sz, time_t now, int unicast_dest, int *is_inform, int pxe, struct in_addr fallback) @@ -851,6 +851,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if ((mess_type == DHCPDISCOVER || (pxe && mess_type == DHCPREQUEST))) { struct dhcp_context *tmp; + int workaround = 0; for (tmp = context; tmp; tmp = tmp->current) if ((tmp->flags & CONTEXT_PROXY) && @@ -878,10 +879,16 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, clear_packet(mess, end); - /* Provide the bootfile here, for gPXE, and in case we have no menu items - and set discovery_control = 8 */ - if (boot) + /* Only do workaround for replies to 4011 */ + if (!pxe) + mess->siaddr = tmp->local; + else + workaround = pxe_uefi_workaround(pxearch, tagif_netid, mess, tmp->local, now); + + if (!workaround && boot) { + /* Provide the bootfile here, for gPXE, and in case we have no menu items + and set discovery_control = 8 */ if (boot->next_server.s_addr) mess->siaddr = boot->next_server; else if (boot->tftp_sname) @@ -896,8 +903,9 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, 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 (!workaround) + 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)); return ignore ? 0 : dhcp_packet_size(mess, agent_id, real_end); @@ -1975,6 +1983,52 @@ static int prune_vendor_opts(struct dhcp_netid *netid) return force; } + +/* Many UEFI PXE implementations have badly broken menu code. + If there's exactly one relevant menu item, we abandon the menu system, + and jamb the data direct into the DHCP file, siaddr and sname fields. + Note that in this case, we have to assume that layer zero would be requested + by the client PXE stack. */ +static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now) +{ + struct pxe_service *service, *found; + + /* Only workaround UEFI archs. */ + if (pxe_arch != 6 && pxe_arch != 7 && pxe_arch != 8 && pxe_arch != 9) + return 0; + + for (found = NULL, service = daemon->pxe_services; service; service = service->next) + if (pxe_arch == service->CSA && match_netid(service->netid, netid, 1)) + { + if (found) + return 0; /* More than one relevant menu item */ + + found = service; + } + + if (!found) + return 0; /* No relevant menu items. */ + + if (found->sname) + { + mess->siaddr = a_record_from_hosts(found->sname, now); + snprintf((char *)mess->sname, sizeof(mess->sname), "%s", found->sname); + } + else + { + if (found->server.s_addr != 0) + mess->siaddr = found->server; + else + mess->siaddr = local; + + inet_ntop(AF_INET, &mess->siaddr, (char *)mess->sname, INET_ADDRSTRLEN); + } + + snprintf((char *)mess->file, sizeof(mess->file), "%s.0", found->basename); + + return 1; +} + static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now) { #define NUM_OPTS 4 @@ -2509,7 +2563,8 @@ static void do_options(struct dhcp_context *context, if (context && pxe_arch != -1) { pxe_misc(mess, end, uuid); - config_opts = pxe_opts(pxe_arch, tagif, context->local, now); + if (!pxe_uefi_workaround(pxe_arch, tagif, mess, context->local, now)) + config_opts = pxe_opts(pxe_arch, tagif, context->local, now); } if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT)) &&