Hi guys,

I configured something on my laptop and tried few requests with my
raspberry. I think I have found weird state. It checks only matching
dhcp-range context, not pxe-service, when adding PXE stuff to requests.
So I made it require also pxe-service. It seems it might do what you
have requested, at least on the first glance.

Patch #2 is just small tuning, unifying dhcp-boot and pxe-service way of
setting boot files. There is slight chance it might break something. In
that case, either dhcp-boot or pxe-service needs fixing.

Could you please try success with patched version? I made new COPR
builds [1] for Fedora, including these changes. But I think that is not
your distribution. Can you try local build? Pushed also to our fork [2].

1. https://copr.fedorainfracloud.org/coprs/pemensik/dnsmasq/

2. https://github.com/InfrastructureServices/dnsmasq/tree/pxe-services

On 10/6/21 07:37, Alkis Georgopoulos wrote:
> Hi all,
>
> I haven't been able to reproduce this issue with the hardware I have
> access to.
> I think the key question that Shrenik is asking is:
> 1) dnsmasq works fine with his NUC when he has no pxe-service lines in
> his config
> 2) When he adds pxe-service lines with tags that DO NOT match this
> client, it fails to boot
>
> ==> Is it possible for dnsmasq to behave like (1) when (2) happens?
> I.e. if pxe-service is not matched due to tags, to behave in the same
> way like if it wasn't there at all?
>
> Thank you,
> Alkis Georgopoulos
>
>
> On 10/5/21 9:05 PM, Petr Menšík wrote:
> I could not find any relevant difference between nuc-efi.pcapng and
> qemu-efi.pcapng responses. They seem very similar. Yet qemu continues
> with TFTP download and next step. Nuc does not react anyhow to boot
> offer. It seems to not download or start snponly.efi. It seems to me the
> answer lies only on its boot rom firmware. I just expect both used
> matching configuration and dnsmasq version.
>
>  From dnsmasq side, answer to DHCP discover and request contains
> expected value in expected format. Critical is any output of NUC when it
> receives the offer. Does it fail with any error code? Does it print any
> error? At least screenshot would be useful. It seems to me the problem
> is somewhere in its PXE implementation, which we cannot solve in
> dnsmasq. Because it even does not try to download and execute
> snponly.efi, even iPXE build with debug logs (which I am not sure how to
> enable btw) would not help.
>
> What kind machine it is? Does it have the latest bios firmware
> available? Would this machine boot at least snponly.efi if pxe-service
> were commented out? It seems similar to previous
> intel_nuc_efi_with_ltsp-pxeservice.pcapng file requests, but it does use
> proxyDHCP requests like the old one. How changed dnsmasq configuration
> to make this change? How does dnsmasq.conf look like for it?
>
> Option 43 suboption PXE discovery control (6) is different now. It seems
> not well received by PXE firmware this time. Used config file of dnsmasq
> is not attached this time, I can only guess. HW address is different,
> not sure how different is used machine.
>
> Was it this machine, which returned PXE-E21: Remote boot cancelled.?
> Returned control to LoadFile control seems suspicious. May it require
> non-efi image instead? If you can reach support for this machine, I
> think it might be reported to them. Especially about how PXE menu can
> specify Local Boot?
>
> I am afraid I don't know how to help now. If one machine can boot with
> the same setup that another cannot, it is up to you to find commonly
> working configuration.
>
> On 9/30/21 12:47, Shrenik Bhura wrote:
> > > 1. seems to have wrong pcap file or it does not use configuration
> attached in linked archive. It seems it offers
> > menu items from 2. archive with custom pxe-services.
> >>
> > Apologies, there was definitely some mistake.
> >>
> > We have applied the patch and tried with and without dhcp-no-override
> > but it still fails to boot. Herein are the pcap and the logs for this
> > case.
> >
> https://drive.google.com/file/d/1-GvsId99FC8f8B2I0YaTVuje5385u4LC/view?usp=sharing
> >>
> > Additionally, also included is the qemu pcap wherein it does boot
> > successfully.
> >>
> > On Wed, 29 Sept 2021 at 20:29, Petr Menšík <[email protected]> wrote:
> >>
> >     It is somehow hard to guess described results for each
> >     configuration (1. 2. 3.). It is unclear to me, what you saw for
> >     each variant printed by the computer.
> >>
> >     1. seems to have wrong pcap file or it does not use configuration
> >     attached in linked archive. It seems it offers menu items from 2.
> >     archive with custom pxe-services.
> >>
> >     Option 43 Suboption: (9) PXE boot menu
> >         Length: 41
> >         boot menu:
> >
> 8000155058454c494e555820285838362d36345f4546492980010e5058454c494e555820…
> >             Type: Unknown (32768)
> >             Length: 21
> >             Description: PXELINUX (X86-64_EFI)
> >             Type: Unknown (32769)
> >             Length: 14
> >             Description: PXELINUX (EFI)
> >>
> >     Above is not present in config file presented for it, but in 2.
> >     Are you sure you have killed dnsmasq and started it again?
> >>
> >     I think it might be difference between pxe-service served file
> >     chosen via menuboot. I have noticed there are two way to specify
> >     file to boot in DHCP for IPv4. One is in fixed header and first
> >     try chosen from menu is in that. pxe-service options makes it to
> >     request direct query to DHCP server, marked proxyDHCP in
> >     wireshark. This proxy ACK is followed by TFTP.
> >>
> >     I used filter in wireshark: "dhcp or (!tftp.destination_file &&
> tftp)"
> >>
> >     However following DHCP offers boot file path ONLY in option 67
> >     value. Fixed header boot file is all zeroed. It seems to me this
> >     is the part the snponly.efi firmware does not understand. It does
> >     not try to use path in option, but may insist only on file. Since
> >     option #52 overload is not in packet, I guess dnsmasq should have
> >     used mess->file for path and not option 67. But rules of
> >     rfc2131.c:2476 are simple. If client have requested option 67, it
> >     should handle it as option 67. I guess it is bug in snponly.efi.
> >     Either it should not include option 67 between requested options
> >     or it should actually handle the option. Dnsmasq would offer boot
> >     path in both cases.
> >>
> >     Interesting enough, dnsmasq is inconsistent with itself. It
> >     behaves a bit different way in PXE proxy mode, where file header
> >     part is always used. In normal mode unless --dhcp-no-override is
> >     used, option is used if requested.
> >>
> >     Can you please try if dhcp-no-override option would fix your
> >     issues? I think it should behave the same way in both situations.
> >>
> >     I attached patch, which would set boot file on pxe-service the
> >     same way as dhcp-boot. It may require dhcp-no-override where it
> >     did not before. Could you please try it?
> >>

-- 
Petr Menšík
Software Engineer
Red Hat, http://www.redhat.com/
email: [email protected]
PGP: DFCF908DB7C87E8E529925BC4931CA5B6C9FC5CB
From 35201f489a0afb2336c6700f649453f4b9580f74 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <[email protected]>
Date: Wed, 29 Sep 2021 16:51:46 +0200
Subject: [PATCH 2/2] Send boot file the same way on pxe-service and dhcp-boot

Header fixed path might be used for boot file name, unless override is
DHCP required. Merge behaviour from single matching pxe-service entry
and dhcp-boot to set boot file the same way. May need dhcp-no-override
option for some incompatible boot roms.
---
 src/rfc2131.c | 63 ++++++++++++++++++++++++++-------------------------
 1 file changed, 32 insertions(+), 31 deletions(-)

diff --git a/src/rfc2131.c b/src/rfc2131.c
index d1c0c15..85dcb04 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -79,6 +79,36 @@ static struct pxe_service *pxe_service_find(int pxe_arch, struct dhcp_netid *net
   return NULL;
 }
 
+static void set_boot(struct dhcp_packet *mess, unsigned char *end,
+		     unsigned char *req_options, struct dhcp_boot *boot,
+		     time_t now)
+{
+  if (boot->sname)
+    {
+      if (!option_bool(OPT_NO_OVERRIDE) &&
+          req_options &&
+          in_list(req_options, OPTION_SNAME))
+        option_put_string(mess, end, OPTION_SNAME, boot->sname, 1);
+      else
+        safe_strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname));
+    }
+
+  if (boot->file)
+    {
+      if (!option_bool(OPT_NO_OVERRIDE) &&
+          req_options &&
+          in_list(req_options, OPTION_FILENAME))
+        option_put_string(mess, end, OPTION_FILENAME, boot->file, 1);
+      else
+        safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file));
+    }
+
+  if (boot->next_server.s_addr)
+    mess->siaddr = boot->next_server;
+  else if (boot->tftp_sname)
+    mess->siaddr = a_record_from_hosts(boot->tftp_sname, 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 loopback,
 		  int *is_inform, int pxe, struct in_addr fallback, time_t recvtime)
@@ -998,13 +1028,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 		{
 		  /* Provide the bootfile here, for iPXE, 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) 
-			mess->siaddr = a_record_from_hosts(boot->tftp_sname, now);
-		      
-		      if (boot->file)
-			safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file));
+		      set_boot(mess, end, req_options, boot, now);
 		}
 
 	      option_put(mess, end, OPTION_MESSAGE_TYPE, 1,
@@ -2442,30 +2466,7 @@ static void do_options(struct dhcp_context *context,
      names, so we always send those.  */
   if ((boot = find_boot(tagif)))
     {
-      if (boot->sname)
-	{	  
-	  if (!option_bool(OPT_NO_OVERRIDE) &&
-	      req_options && 
-	      in_list(req_options, OPTION_SNAME))
-	    option_put_string(mess, end, OPTION_SNAME, boot->sname, 1);
-	  else
-	    safe_strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname));
-	}
-      
-      if (boot->file)
-	{
-	  if (!option_bool(OPT_NO_OVERRIDE) &&
-	      req_options && 
-	      in_list(req_options, OPTION_FILENAME))
-	    option_put_string(mess, end, OPTION_FILENAME, boot->file, 1);
-	  else
-	    safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file));
-	}
-      
-      if (boot->next_server.s_addr) 
-	mess->siaddr = boot->next_server;
-      else if (boot->tftp_sname)
-	mess->siaddr = a_record_from_hosts(boot->tftp_sname, now);
+      set_boot(mess, end, req_options, boot, now);
     }
   else
     /* Use the values of the relevant options if no dhcp-boot given and
-- 
2.31.1

From 0082586ecd610ba7e64dd7b0b26593d95e3ae757 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <[email protected]>
Date: Thu, 7 Oct 2021 00:40:54 +0200
Subject: [PATCH 1/2] Offer PXE services to if tags match

When pxe-service appeared in configuration, PXE structures were offered
in DHCPOFFER always. Even if those pxe-services were set for selected
tags only. Try to improve it and require matching pxe_service first,
before anything is offered.

Require matching architecture also in direct PXE requests to PXE port,
do not rely just on passed type.
---
 src/rfc2131.c | 178 +++++++++++++++++++++++++-------------------------
 1 file changed, 88 insertions(+), 90 deletions(-)

diff --git a/src/rfc2131.c b/src/rfc2131.c
index c902eb7..d1c0c15 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -64,10 +64,21 @@ 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, int pxe);
+static int pxe_uefi_workaround(struct pxe_service *found, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe);
 static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid);
 static int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_vendor);
 
+static struct pxe_service *pxe_service_find(int pxe_arch, struct dhcp_netid *netid, struct pxe_service *start)
+{
+  struct pxe_service *service;
+
+  for (service = start; service; service = service->next)
+    if (pxe_arch == service->CSA && service->basename && match_netid(service->netid, netid, 1))
+      return service;
+
+  return NULL;
+}
+
 size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 		  size_t sz, time_t now, int unicast_dest, int loopback,
 		  int *is_inform, int pxe, struct in_addr fallback, time_t recvtime)
@@ -858,8 +869,10 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
           
   /* Check if client is PXE client. */
   if (daemon->enable_pxe &&
-      is_pxe_client(mess, sz, &pxevendor))
+      is_pxe_client(mess, sz, &pxevendor) &&
+      (opt = option_find(mess, sz, OPTION_ARCH, 2)))
     {
+      pxearch = option_uint(opt, 0, 2);
       if ((opt = option_find(mess, sz, OPTION_PXE_UUID, 17)))
 	{
 	  memcpy(pxe_uuid, option_ptr(opt, 0), 17);
@@ -871,7 +884,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 	  (opt = option_find(mess, sz, OPTION_VENDOR_CLASS_OPT, 1)) &&
 	  (opt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_PXE_BOOT_ITEM, 4)))
 	{
-	  struct pxe_service *service;
+	  struct pxe_service *service = daemon->pxe_services;
 	  int type = option_uint(opt, 0, 2);
 	  int layer = option_uint(opt, 2, 2);
 	  unsigned char save71[4];
@@ -886,12 +899,12 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 	      return 0;
 	    }
 
-	  memcpy(save71, option_ptr(opt, 0), 4);
-	  
-	  for (service = daemon->pxe_services; service; service = service->next)
-	    if (service->type == type)
-	      break;
+	  do {
+	    service = pxe_service_find(pxearch, tagif_netid, service);
 	  
+	  } while (service && service->type != type);
+	  memcpy(save71, option_ptr(opt, 0), 4);
+
 	  for (; context; context = context->current)
 	    if (match_netid(context->filter, tagif_netid, 1) &&
 		is_same_net(mess->ciaddr, context->start, context->netmask))
@@ -936,58 +949,55 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 	  return dhcp_packet_size(mess, agent_id, real_end);	  
 	}
       
-      if ((opt = option_find(mess, sz, OPTION_ARCH, 2)))
+      /* proxy DHCP here. */
+      if ((mess_type == DHCPDISCOVER || (pxe && mess_type == DHCPREQUEST)))
 	{
-	  pxearch = option_uint(opt, 0, 2);
-
-	  /* proxy DHCP here. */
-	  if ((mess_type == DHCPDISCOVER || (pxe && mess_type == DHCPREQUEST)))
-	    {
-	      struct dhcp_context *tmp;
-	      int workaround = 0;
+	  struct dhcp_context *tmp;
+	  int workaround = 0;
 	      
-	      for (tmp = context; tmp; tmp = tmp->current)
-		if ((tmp->flags & CONTEXT_PROXY) &&
-		    match_netid(tmp->filter, tagif_netid, 1))
-		  break;
+	  for (tmp = context; tmp; tmp = tmp->current)
+	    if ((tmp->flags & CONTEXT_PROXY) &&
+		match_netid(tmp->filter, tagif_netid, 1))
+	      break;
 	      
-	      if (tmp)
+	  if (tmp)
+	    {
+	      struct dhcp_boot *boot;
+	      int redirect4011 = 0;
+	      struct pxe_service *service;
+
+	      if (tmp->netid.net)
 		{
-		  struct dhcp_boot *boot;
-		  int redirect4011 = 0;
+		  tmp->netid.next = netid;
+		  tagif_netid = run_tag_if(&tmp->netid);
+		}
 
-		  if (tmp->netid.net)
-		    {
-		      tmp->netid.next = netid;
-		      tagif_netid = run_tag_if(&tmp->netid);
-		    }
-		  
-		  boot = find_boot(tagif_netid);
-		  
-		  mess->yiaddr.s_addr = 0;
-		  if  (mess_type == DHCPDISCOVER || mess->ciaddr.s_addr == 0)
-		    {
-		      mess->ciaddr.s_addr = 0;
-		      mess->flags |= htons(0x8000); /* broadcast */
-		    }
-		  
-		  clear_packet(mess, end);
-		  
-		  /* Redirect EFI clients to port 4011 */
-		  if (pxearch >= 6)
-		    {
-		      redirect4011 = 1;
-		      mess->siaddr = tmp->local;
-		    }
-		  
-		  /* Returns true if only one matching service is available. On port 4011, 
-		     it also inserts the boot file and server name. */
-		  workaround = pxe_uefi_workaround(pxearch, tagif_netid, mess, tmp->local, now, pxe);
-		  
-		  if (!workaround && boot)
-		    {
-		      /* Provide the bootfile here, for iPXE, and in case we have no menu items
-			 and set discovery_control = 8 */
+	      boot = find_boot(tagif_netid);
+
+	      mess->yiaddr.s_addr = 0;
+	      if  (mess_type == DHCPDISCOVER || mess->ciaddr.s_addr == 0)
+		{
+		  mess->ciaddr.s_addr = 0;
+		  mess->flags |= htons(0x8000); /* broadcast */
+		}
+
+	      clear_packet(mess, end);
+
+	      /* Redirect EFI clients to port 4011 */
+	      if (pxearch >= 6)
+		{
+		  redirect4011 = 1;
+		  mess->siaddr = tmp->local;
+		}
+
+	      service = pxe_service_find(pxearch, tagif_netid, daemon->pxe_services);
+	      /* Returns true if only one matching service is available. On port 4011,
+		  it also inserts the boot file and server name. */
+	      if (boot && service &&
+		  !(workaround = pxe_uefi_workaround(service, tagif_netid, mess, tmp->local, now, pxe)))
+		{
+		  /* Provide the bootfile here, for iPXE, 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) 
@@ -995,23 +1005,22 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 		      
 		      if (boot->file)
 			safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file));
-		    }
-		  
-		  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, pxevendor);
-		  prune_vendor_opts(tagif_netid);
-		  if ((pxe && !workaround) || !redirect4011)
-		    do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
-	    
-		  daemon->metrics[METRIC_PXE]++;
-		  log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid);
-		  log_tags(tagif_netid, ntohl(mess->xid));
-		  if (!ignore)
-		    apply_delay(mess->xid, recvtime, tagif_netid);
-		  return ignore ? 0 : dhcp_packet_size(mess, agent_id, real_end);	  
 		}
+
+	      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, pxevendor);
+	      prune_vendor_opts(tagif_netid);
+	      if ((pxe && !workaround) || !redirect4011)
+		do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
+
+	      daemon->metrics[METRIC_PXE]++;
+	      log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid);
+	      log_tags(tagif_netid, ntohl(mess->xid));
+	      if (!ignore)
+		apply_delay(mess->xid, recvtime, tagif_netid);
+	      return ignore ? 0 : dhcp_packet_size(mess, agent_id, real_end);
 	    }
 	}
     }
@@ -2156,31 +2165,19 @@ 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, int pxe)
+static int pxe_uefi_workaround(struct pxe_service *found, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe)
 {
-  struct pxe_service *service, *found;
-
-  /* Only workaround UEFI archs. */
-  if (pxe_arch < 6)
-    return 0;
-  
-  for (found = NULL, service = daemon->pxe_services; service; service = service->next)
-    if (pxe_arch == service->CSA && service->basename && 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. */
+  else if (found->CSA < 6)
+    return 0; /* Only workaround UEFI archs. */
+  else if (pxe_service_find(found->CSA, netid, found))
+    return 0; /* More than one relevant menu item */
   
   if (!pxe)
      return 1;
@@ -2400,6 +2397,7 @@ static void do_options(struct dhcp_context *context,
   int done_vendor_class = 0;
   struct dhcp_netid *tagif;
   struct dhcp_netid_list *id_list;
+  struct pxe_service *pxe_service;
 
   /* filter options based on tags, those we want get DHOPT_TAGOK bit set */
   if (context)
@@ -2761,10 +2759,10 @@ static void do_options(struct dhcp_context *context,
 
   force_encap = prune_vendor_opts(tagif);
   
-  if (context && pxe_arch != -1)
+  if (context && pxe_arch != -1 && (pxe_service = pxe_service_find(pxe_arch, tagif, daemon->pxe_services)))
     {
       pxe_misc(mess, end, uuid, pxevendor);
-      if (!pxe_uefi_workaround(pxe_arch, tagif, mess, context->local, now, 0))
+      if (!pxe_uefi_workaround(pxe_service, tagif, mess, context->local, now, 0))
 	config_opts = pxe_opts(pxe_arch, tagif, context->local, now);
     }
 
-- 
2.31.1

_______________________________________________
Dnsmasq-discuss mailing list
[email protected]
https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss

Reply via email to