14.10.2015 23:09, Andrei Borzenkov пишет:
> 14.10.2015 21:09, Rivard, Matthew T пишет:
>> Just going by these definitions of bootp and dhcp here:
>> https://technet.microsoft.com/en-us/library/cc781243%28v=ws.10%29.aspx
>>
>> The net_bootp only works with a bootp enabled scope on the dhcp server
>> after chaining from iPXE to grub2.   Without a "bootp" scope setup on
>> the dhcp, calling net_bootp on the adapter fails to get an IP address.
>>
>> So, I guess I could more clearly word that the bootp protocol works
>> when calling net_bootp, but standard dhcp isn't.
>>
> 
> Hmm ... you are right; we are actually doing BOOTP here, not DHCP.
> 

This patch adds support for native DHCPv4 and removes requirement for
BOOTP compatibility support in DHCP server.

This is work in progress, but this works for me in test environment.

Patch changes net_bootp to implement full DHCP transaction. it still
/should/ work with BOOTP pure server (untested, I do not have one). It
also re-implements option processing to support overloaded fields and
consolidates it in one place.

What is currently not implemented

- per interface, per transaction stage retransmit timer. Will be done.

- DHCP server selection. We take first DHCPOFFER or BOOTPREPLY. No plans
to implement.

- DHCP option concatenation (RFC3396). I do not expect to hit it in real
life; could be implemented relatively easy if needed.

- client identifier (RFC6842). I do not expect to hit it in real life;
could be added easily if needed.
diff --git a/grub-core/net/bootp.c b/grub-core/net/bootp.c
index 4fdeac3..4d5df64 100644
--- a/grub-core/net/bootp.c
+++ b/grub-core/net/bootp.c
@@ -25,20 +25,89 @@
 #include <grub/net/udp.h>
 #include <grub/datetime.h>
 
-static void
-parse_dhcp_vendor (const char *name, const void *vend, int limit, int *mask)
+struct grub_dhcp_discover_options
+  {
+  grub_uint8_t magic[4];
+  struct
+    {
+      grub_uint8_t code;
+      grub_uint8_t len;
+      grub_uint8_t data;
+    } GRUB_PACKED  message_type;
+    grub_uint8_t end;
+  } GRUB_PACKED;
+
+struct grub_dhcp_request_options
+  {
+    grub_uint8_t magic[4];
+    struct
+      {
+	grub_uint8_t code;
+	grub_uint8_t len;
+	grub_uint8_t data;
+      } GRUB_PACKED  message_type;
+    struct
+      {
+	grub_uint8_t type;
+	grub_uint8_t len;
+	grub_uint32_t data;
+      } GRUB_PACKED 	server_identifier;
+    struct
+      {
+	grub_uint8_t type;
+	grub_uint8_t len;
+	grub_uint32_t data;
+      } GRUB_PACKED 	requested_ip;
+    struct
+      {
+	grub_uint8_t type;
+	grub_uint8_t len;
+	grub_uint8_t data[7];
+      } GRUB_PACKED 	parameter_request;
+    grub_uint8_t end;
+  } GRUB_PACKED;
+
+enum
+  {
+    GRUB_DHCP_MESSAGE_UNKNOWN,
+    GRUB_DHCP_MESSAGE_DISCOVER,
+    GRUB_DHCP_MESSAGE_OFFER,
+    GRUB_DHCP_MESSAGE_REQUEST,
+    GRUB_DHCP_MESSAGE_DECLINE,
+    GRUB_DHCP_MESSAGE_ACK,
+    GRUB_DHCP_MESSAGE_NAK,
+    GRUB_DHCP_MESSAGE_RELEASE,
+    GRUB_DHCP_MESSAGE_INFORM,
+  };
+
+#define GRUB_BOOTP_MAX_OPTIONS_SIZE 64
+#define OFFSET_OF(x, y) ((grub_size_t)((grub_uint8_t *)((y)->x) - (grub_uint8_t *)(y)))
+
+static const void *
+find_dhcp_option (const struct grub_net_bootp_packet *bp, unsigned size,
+		 grub_uint8_t opt_code, grub_uint8_t *opt_len)
 {
   const grub_uint8_t *ptr, *ptr0;
+  grub_uint8_t overload = 0;
+  int end = 0;
+
+  /* Magic + message type option */
+  if (size < sizeof (*bp) + sizeof (grub_uint32_t))
+    goto out;
 
-  ptr = ptr0 = vend;
+  ptr = ptr0 = (grub_uint8_t *) (bp + 1);
 
   if (ptr[0] != GRUB_NET_BOOTP_RFC1048_MAGIC_0
       || ptr[1] != GRUB_NET_BOOTP_RFC1048_MAGIC_1
       || ptr[2] != GRUB_NET_BOOTP_RFC1048_MAGIC_2
       || ptr[3] != GRUB_NET_BOOTP_RFC1048_MAGIC_3)
-    return;
+    goto out;
+
   ptr = ptr + sizeof (grub_uint32_t);
-  while (ptr - ptr0 < limit)
+  size -= (sizeof (*bp) + sizeof (grub_uint32_t));
+
+again:
+  while (ptr - ptr0 < size)
     {
       grub_uint8_t tagtype;
       grub_uint8_t taglength;
@@ -51,84 +120,75 @@ parse_dhcp_vendor (const char *name, const void *vend, int limit, int *mask)
 
       /* End tag.  */
       if (tagtype == GRUB_NET_BOOTP_END)
-	return;
+	{
+	  end = 1;
+	  break;
+	}
+
+      if (ptr - ptr0 >= size)
+	goto out;
 
       taglength = *ptr++;
 
-      switch (tagtype)
-	{
-	case GRUB_NET_BOOTP_NETMASK:
-	  if (taglength == 4)
-	    {
-	      int i;
-	      for (i = 0; i < 32; i++)
-		if (!(ptr[i / 8] & (1 << (7 - (i % 8)))))
-		  break;
-	      *mask = i;
-	    }
-	  break;
+      if (ptr + taglength - ptr0 >= size)
+	goto out;
 
-	case GRUB_NET_BOOTP_ROUTER:
-	  if (taglength == 4)
-	    {
-	      grub_net_network_level_netaddress_t target;
-	      grub_net_network_level_address_t gw;
-	      char *rname;
-	      
-	      target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
-	      target.ipv4.base = 0;
-	      target.ipv4.masksize = 0;
-	      gw.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
-	      grub_memcpy (&gw.ipv4, ptr, sizeof (gw.ipv4));
-	      rname = grub_xasprintf ("%s:default", name);
-	      if (rname)
-		grub_net_add_route_gw (rname, target, gw);
-	      grub_free (rname);
-	    }
-	  break;
-	case GRUB_NET_BOOTP_DNS:
-	  {
-	    int i;
-	    for (i = 0; i < taglength / 4; i++)
-	      {
-		struct grub_net_network_level_address s;
-		s.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
-		s.ipv4 = grub_get_unaligned32 (ptr);
-		s.option = DNS_OPTION_PREFER_IPV4;
-		grub_net_add_dns_server (&s);
-		ptr += 4;
-	      }
-	  }
-	  continue;
-	case GRUB_NET_BOOTP_HOSTNAME:
-          grub_env_set_net_property (name, "hostname", (const char *) ptr,
-                                     taglength);
-          break;
-
-	case GRUB_NET_BOOTP_DOMAIN:
-          grub_env_set_net_property (name, "domain", (const char *) ptr,
-                                     taglength);
-          break;
-
-	case GRUB_NET_BOOTP_ROOT_PATH:
-          grub_env_set_net_property (name, "rootpath", (const char *) ptr,
-                                     taglength);
-          break;
-
-	case GRUB_NET_BOOTP_EXTENSIONS_PATH:
-          grub_env_set_net_property (name, "extensionspath", (const char *) ptr,
-                                     taglength);
-          break;
-
-	  /* If you need any other options please contact GRUB
-	     development team.  */
+      if (tagtype == opt_code)
+	{
+	  if (opt_len)
+	    *opt_len = taglength;
+	  return ptr;
 	}
 
+      if (tagtype == GRUB_NET_DHCP_OVERLOAD)
+	overload = *ptr;
+
       ptr += taglength;
     }
-}
 
-#define OFFSET_OF(x, y) ((grub_size_t)((grub_uint8_t *)((y)->x) - (grub_uint8_t *)(y)))
+    /* RFC2131, 4.1, 23ff:
+       If the options in a DHCP message extend into the 'sname' and 'file'
+       fields, the 'option overload' option MUST appear in the 'options'
+       field, with value 1, 2 or 3, as specified in RFC 1533.  If the
+       'option overload' option is present in the 'options' field, the
+       options in the 'options' field MUST be terminated by an 'end' option,
+       and MAY contain one or more 'pad' options to fill the options field.
+       The options in the 'sname' and 'file' fields (if in use as indicated
+       by the 'options overload' option) MUST begin with the first octet of
+       the field, MUST be terminated by an 'end' option, and MUST be
+       followed by 'pad' options to fill the remainder of the field.  Any
+       individual option in the 'options', 'sname' and 'file' fields MUST be
+       entirely contained in that field.  The options in the 'options' field
+       MUST be interpreted first, so that any 'option overload' options may
+       be interpreted.  The 'file' field MUST be interpreted next (if the
+       'option overload' option indicates that the 'file' field contains
+       DHCP options), followed by the 'sname' field.
+
+       FIXME: We do not explicitly check for trailing 'pad' options here.
+     */
+    if (end)
+      {
+	end = 0;
+	if (overload & 1U)
+	  {
+	    overload &= ~1U;
+	    ptr = ptr0 = (grub_uint8_t *) &bp->boot_file[0];
+	    size = sizeof (bp->boot_file);
+	    goto again;
+	  }
+
+	if (overload & 2U)
+	  {
+	    overload &= ~2U;
+	    ptr = ptr0 = (grub_uint8_t *) &bp->server_name[0];
+	    size = sizeof (bp->server_name);
+	    goto again;
+	  }
+      }
+
+out:
+  return 0;
+}
 
 struct grub_net_network_level_interface *
 grub_net_configure_by_dhcp_ack (const char *name,
@@ -142,6 +202,10 @@ grub_net_configure_by_dhcp_ack (const char *name,
   grub_net_link_level_address_t hwaddr;
   struct grub_net_network_level_interface *inter;
   int mask = -1;
+  const grub_uint8_t *opt;
+  grub_uint8_t opt_len = 0;
+  const char *boot_file = 0, *server_name = 0;
+  grub_size_t boot_file_len, server_name_len;
 
   addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
   addr.ipv4 = bp->your_ip;
@@ -157,38 +221,31 @@ grub_net_configure_by_dhcp_ack (const char *name,
   hwaddr.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
 
   inter = grub_net_add_addr (name, card, &addr, &hwaddr, flags);
-#if 0
-  /* This is likely based on misunderstanding. gateway_ip refers to
-     address of BOOTP relay and should not be used after BOOTP transaction
-     is complete.
-     See RFC1542, 3.4 Interpretation of the 'giaddr' field
-   */
-  if (bp->gateway_ip)
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_DHCP_TFTP_SERVER_NAME, &opt_len);
+  if (opt && opt_len)
     {
-      grub_net_network_level_netaddress_t target;
-      grub_net_network_level_address_t gw;
-      char *rname;
-	  
-      target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
-      target.ipv4.base = bp->server_ip;
-      target.ipv4.masksize = 32;
-      gw.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
-      gw.ipv4 = bp->gateway_ip;
-      rname = grub_xasprintf ("%s:gw", name);
-      if (rname)
-	grub_net_add_route_gw (rname, target, gw);
-      grub_free (rname);
+      server_name = (const char *) opt;
+      server_name_len = opt_len;
+    }
+  else if (size > OFFSET_OF (server_name, bp) && bp->server_name[0])
+    {
+      server_name = bp->server_name;
+      server_name_len = sizeof (bp->server_name);
+    }
 
-      target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
-      target.ipv4.base = bp->gateway_ip;
-      target.ipv4.masksize = 32;
-      grub_net_add_route (name, target, inter);
+  opt = find_dhcp_option (bp, size, GRUB_NET_DHCP_BOOTFILE_NAME, &opt_len);
+  if (opt && opt_len)
+    {
+      boot_file = (const char *) opt;
+      boot_file_len = opt_len;
+    }
+  else if (size > OFFSET_OF (boot_file, bp) && bp->boot_file[0])
+    {
+      boot_file = bp->boot_file;
+      boot_file_len = sizeof (bp->boot_file);
     }
-#endif
 
-  if (size > OFFSET_OF (boot_file, bp))
-    grub_env_set_net_property (name, "boot_file", bp->boot_file,
-                               sizeof (bp->boot_file));
   if (is_def)
     grub_net_default_server = 0;
   if (is_def && !grub_net_default_server && bp->server_ip)
@@ -216,40 +273,104 @@ grub_net_configure_by_dhcp_ack (const char *name,
 				((grub_uint8_t *) &bp->server_ip)[3]);
       grub_print_error ();
     }
-  if (size > OFFSET_OF (server_name, bp)
-      && bp->server_name[0])
+
+  if (server_name)
     {
-      grub_env_set_net_property (name, "dhcp_server_name", bp->server_name,
-                                 sizeof (bp->server_name));
+      /* FIXME this is actually TFTP server name; should not we change it? */
+      grub_env_set_net_property (name, "dhcp_server_name", server_name, server_name_len);
       if (is_def && !grub_net_default_server)
 	{
-	  grub_net_default_server = grub_strdup (bp->server_name);
+	  grub_net_default_server = grub_strdup (server_name);
 	  grub_print_error ();
 	}
       if (device && !*device)
 	{
-	  *device = grub_xasprintf ("tftp,%s", bp->server_name);
+	  *device = grub_xasprintf ("tftp,%s", server_name);
 	  grub_print_error ();
 	}
     }
 
-  if (size > OFFSET_OF (boot_file, bp) && path)
+  if (boot_file)
     {
-      *path = grub_strndup (bp->boot_file, sizeof (bp->boot_file));
-      grub_print_error ();
-      if (*path)
+      grub_env_set_net_property (name, "boot_file", boot_file, boot_file_len);
+      if (path)
 	{
-	  char *slash;
-	  slash = grub_strrchr (*path, '/');
-	  if (slash)
-	    *slash = 0;
-	  else
-	    **path = 0;
+	  *path = grub_strndup (boot_file, boot_file_len);
+	  grub_print_error ();
+	  if (*path)
+	    {
+	      char *slash;
+	      slash = grub_strrchr (*path, '/');
+	      if (slash)
+		*slash = 0;
+	      else
+		**path = 0;
+	    }
 	}
     }
-  if (size > OFFSET_OF (vendor, bp))
-    parse_dhcp_vendor (name, &bp->vendor, size - OFFSET_OF (vendor, bp), &mask);
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_NETMASK, &opt_len);
+  if (opt && opt_len == 4)
+    {
+      int i;
+      for (i = 0; i < 32; i++)
+	if (!(opt[i / 8] & (1 << (7 - (i % 8)))))
+	  break;
+      mask = i;
+    }
   grub_net_add_ipv4_local (inter, mask);
+
+  /* We do not implement dead gateway detection and the first entry SHOULD
+     be preferred one */
+  opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_ROUTER, &opt_len);
+  if (opt && opt_len && !(opt_len & 3))
+    {
+      grub_net_network_level_netaddress_t target;
+      grub_net_network_level_address_t gw;
+      char *rname;
+
+      target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+      target.ipv4.base = 0;
+      target.ipv4.masksize = 0;
+      gw.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+      gw.ipv4 = grub_get_unaligned32 (opt);
+      rname = grub_xasprintf ("%s:default", name);
+      if (rname)
+	grub_net_add_route_gw (rname, target, gw);
+      grub_free (rname);
+    }
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_DNS, &opt_len);
+  if (opt && opt_len && !(opt_len & 3))
+    {
+      int i;
+      for (i = 0; i < opt_len / 4; i++)
+	{
+	  struct grub_net_network_level_address s;
+
+	  s.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+	  s.ipv4 = grub_get_unaligned32 (opt);
+	  s.option = DNS_OPTION_PREFER_IPV4;
+	  grub_net_add_dns_server (&s);
+	  opt += 4;
+	}
+    }
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_HOSTNAME, &opt_len);
+  if (opt && opt_len)
+    grub_env_set_net_property (name, "hostname", (const char *) opt, opt_len);
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_DOMAIN, &opt_len);
+  if (opt && opt_len)
+    grub_env_set_net_property (name, "domain", (const char *) opt, opt_len);
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_ROOT_PATH, &opt_len);
+  if (opt && opt_len)
+    grub_env_set_net_property (name, "rootpath", (const char *) opt, opt_len);
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_EXTENSIONS_PATH, &opt_len);
+  if (opt && opt_len)
+    grub_env_set_net_property (name, "extensionspath", (const char *) opt, opt_len);
   
   inter->dhcp_ack = grub_malloc (size);
   if (inter->dhcp_ack)
@@ -263,36 +384,211 @@ grub_net_configure_by_dhcp_ack (const char *name,
   return inter;
 }
 
+static grub_err_t
+send_dhcp_packet (struct grub_net_network_level_interface *iface)
+{
+  grub_err_t err;
+  struct grub_net_bootp_packet *pack;
+  struct grub_datetime date;
+  grub_int32_t t = 0;
+  struct grub_net_buff *nb;
+  struct udphdr *udph;
+  grub_net_network_level_address_t target;
+  grub_net_link_level_address_t ll_target;
+
+  static struct grub_dhcp_discover_options discover_options =
+    {
+      {
+	GRUB_NET_BOOTP_RFC1048_MAGIC_0,
+	GRUB_NET_BOOTP_RFC1048_MAGIC_1,
+	GRUB_NET_BOOTP_RFC1048_MAGIC_2,
+	GRUB_NET_BOOTP_RFC1048_MAGIC_3,
+      },
+      {
+	GRUB_NET_DHCP_MESSAGE_TYPE,
+	sizeof (discover_options.message_type.data),
+	GRUB_DHCP_MESSAGE_DISCOVER,
+      },
+      GRUB_NET_BOOTP_END,
+    };
+
+  static struct grub_dhcp_request_options request_options =
+    {
+      {
+	GRUB_NET_BOOTP_RFC1048_MAGIC_0,
+	GRUB_NET_BOOTP_RFC1048_MAGIC_1,
+	GRUB_NET_BOOTP_RFC1048_MAGIC_2,
+	GRUB_NET_BOOTP_RFC1048_MAGIC_3,
+      },
+      {
+	GRUB_NET_DHCP_MESSAGE_TYPE,
+	sizeof (request_options.message_type.data),
+	GRUB_DHCP_MESSAGE_REQUEST,
+      },
+      {
+	GRUB_NET_DHCP_SERVER_IDENTIFIER,
+	sizeof (request_options.server_identifier.data),
+	0,
+      },
+      {
+	GRUB_NET_DHCP_REQUESTED_IP_ADDRESS,
+	sizeof (request_options.requested_ip.data),
+	0,
+      },
+      {
+	GRUB_NET_DHCP_PARAMETER_REQUEST_LIST,
+	sizeof (request_options.parameter_request.data),
+	{
+	  GRUB_NET_BOOTP_NETMASK,
+	  GRUB_NET_BOOTP_ROUTER,
+	  GRUB_NET_BOOTP_DNS,
+	  GRUB_NET_BOOTP_DOMAIN,
+	  GRUB_NET_BOOTP_HOSTNAME,
+	  GRUB_NET_BOOTP_ROOT_PATH,
+	  GRUB_NET_BOOTP_EXTENSIONS_PATH,
+	},
+      },
+      GRUB_NET_BOOTP_END,
+    };
+
+  COMPILE_TIME_ASSERT (sizeof (discover_options) <= GRUB_BOOTP_MAX_OPTIONS_SIZE);
+  COMPILE_TIME_ASSERT (sizeof (request_options) <= GRUB_BOOTP_MAX_OPTIONS_SIZE);
+
+  nb = grub_netbuff_alloc (sizeof (*pack) + GRUB_BOOTP_MAX_OPTIONS_SIZE + 128);
+  if (!nb)
+    return grub_errno;
+
+  err = grub_netbuff_reserve (nb, sizeof (*pack) + GRUB_BOOTP_MAX_OPTIONS_SIZE + 128);
+  if (err)
+    goto out;
+
+  err = grub_netbuff_push (nb, GRUB_BOOTP_MAX_OPTIONS_SIZE);
+  if (err)
+    goto out;
+
+  grub_memset (nb->data, 0, GRUB_BOOTP_MAX_OPTIONS_SIZE);
+  if (!iface->srv_id)
+    {
+      grub_memcpy (nb->data, &discover_options, sizeof (discover_options));
+    }
+  else
+    {
+      struct grub_dhcp_request_options *ro = (struct grub_dhcp_request_options *) nb->data;
+
+      grub_memcpy (nb->data, &request_options, sizeof (request_options));
+      grub_set_unaligned32 (&ro->server_identifier.data, iface->srv_id);
+      grub_set_unaligned32 (&ro->requested_ip.data, iface->my_ip);
+    }
+
+  err = grub_netbuff_push (nb, sizeof (*pack));
+  if (err)
+    goto out;
+
+  pack = (void *) nb->data;
+  grub_memset (pack, 0, sizeof (*pack));
+  pack->opcode = 1;
+  pack->hw_type = 1;
+  pack->hw_len = 6;
+  err = grub_get_datetime (&date);
+  if (err || !grub_datetime2unixtime (&date, &t))
+    {
+      grub_errno = GRUB_ERR_NONE;
+      t = 0;
+    }
+  pack->seconds = grub_cpu_to_be16 (t);
+  if (!iface->srv_id)
+    iface->xid = pack->ident = grub_cpu_to_be32 (t);
+  else
+    pack->ident = iface->xid;
+
+  grub_memcpy (&pack->mac_addr, &iface->hwaddress.mac, 6);
+
+  grub_netbuff_push (nb, sizeof (*udph));
+
+  udph = (struct udphdr *) nb->data;
+  udph->src = grub_cpu_to_be16_compile_time (68);
+  udph->dst = grub_cpu_to_be16_compile_time (67);
+  udph->chksum = 0;
+  udph->len = grub_cpu_to_be16 (nb->tail - nb->data);
+  target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+  target.ipv4 = 0xffffffff;
+  err = grub_net_link_layer_resolve (iface, &target, &ll_target);
+  if (err)
+    goto out;
+
+  udph->chksum = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_UDP,
+						 &iface->address,
+						 &target);
+
+  err = grub_net_send_ip_packet (iface, &target, &ll_target, nb,
+				 GRUB_NET_IP_UDP);
+
+out:
+  grub_netbuff_free (nb);
+  return err;
+}
+
 void
 grub_net_process_dhcp (struct grub_net_buff *nb,
-		       struct grub_net_card *card)
+		       struct grub_net_network_level_interface *iface)
 {
   char *name;
   struct grub_net_network_level_interface *inf;
-
-  name = grub_xasprintf ("%s:dhcp", card->name);
-  if (!name)
+  struct grub_net_card *card = iface->card;
+  const struct grub_net_bootp_packet *bp = (const struct grub_net_bootp_packet *) nb->data;
+  grub_size_t size = nb->tail - nb->data;
+  const grub_uint8_t *opt;
+  grub_uint8_t optlen = 0, type;
+  grub_uint32_t srv_id = 0;
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_DHCP_MESSAGE_TYPE, &optlen);
+  if (opt && optlen == 1)
+    type = *opt;
+  else
+    type = GRUB_DHCP_MESSAGE_UNKNOWN;
+
+  opt = find_dhcp_option (bp, size, GRUB_NET_DHCP_SERVER_IDENTIFIER, &optlen);
+  if (opt && optlen == sizeof (srv_id))
+    srv_id = grub_get_unaligned32 (opt);
+
+  /* If we received BOOTP reply or DHCPACK, proceed with configuration. Otherwise
+     store offered address and server id for later processing of DHCPACK */
+  if ((!iface->srv_id && type == GRUB_DHCP_MESSAGE_UNKNOWN)
+      || (iface->srv_id && type == GRUB_DHCP_MESSAGE_ACK
+	  && bp->ident == iface->xid
+	  && srv_id == iface->srv_id))
     {
-      grub_print_error ();
-      return;
+      name = grub_xasprintf ("%s:dhcp", card->name);
+      if (!name)
+	{
+	  grub_print_error ();
+	  return;
+	}
+      grub_net_configure_by_dhcp_ack (name, card, 0, bp, size, 0, 0, 0);
+      grub_free (name);
+      if (grub_errno)
+	grub_print_error ();
+      else
+	{
+	  FOR_NET_NETWORK_LEVEL_INTERFACES(inf)
+	    if (grub_memcmp (inf->name, card->name, grub_strlen (card->name)) == 0
+		&& grub_memcmp (inf->name + grub_strlen (card->name),
+				":dhcp_tmp", sizeof (":dhcp_tmp") - 1) == 0)
+	      {
+		grub_net_network_level_interface_unregister (inf);
+		break;
+	      }
+	}
     }
-  grub_net_configure_by_dhcp_ack (name, card,
-				  0, (const struct grub_net_bootp_packet *) nb->data,
-				  (nb->tail - nb->data), 0, 0, 0);
-  grub_free (name);
-  if (grub_errno)
-    grub_print_error ();
-  else
+  else if (!iface->srv_id && type == GRUB_DHCP_MESSAGE_OFFER)
     {
-      FOR_NET_NETWORK_LEVEL_INTERFACES(inf)
-	if (grub_memcmp (inf->name, card->name, grub_strlen (card->name)) == 0
-	    && grub_memcmp (inf->name + grub_strlen (card->name),
-			    ":dhcp_tmp", sizeof (":dhcp_tmp") - 1) == 0)
-	  {
-	    grub_net_network_level_interface_unregister (inf);
-	    break;
-	  }
+      iface->srv_id = srv_id;
+      iface->my_ip = bp->your_ip;
     }
+  else if (iface->srv_id && type == GRUB_DHCP_MESSAGE_NAK
+	   && bp->ident == iface->xid
+	   && srv_id == iface->srv_id)
+    iface->xid = iface->srv_id = iface->my_ip = 0;
 }
 
 static char
@@ -308,8 +604,8 @@ grub_cmd_dhcpopt (struct grub_command *cmd __attribute__ ((unused)),
 		  int argc, char **args)
 {
   struct grub_net_network_level_interface *inter;
-  int num;
-  grub_uint8_t *ptr;
+  unsigned num;
+  const grub_uint8_t *ptr;
   grub_uint8_t taglength;
 
   if (argc < 4)
@@ -327,44 +623,27 @@ grub_cmd_dhcpopt (struct grub_command *cmd __attribute__ ((unused)),
   if (!inter->dhcp_ack)
     return grub_error (GRUB_ERR_IO, N_("no DHCP info found"));
 
-  if (inter->dhcp_acklen <= OFFSET_OF (vendor, inter->dhcp_ack))
-    return grub_error (GRUB_ERR_IO, N_("no DHCP options found"));
-
-  num = grub_strtoul (args[2], 0, 0);
-  if (grub_errno)
-    return grub_errno;
-
   ptr = inter->dhcp_ack->vendor;
 
-  if (ptr[0] != GRUB_NET_BOOTP_RFC1048_MAGIC_0
+  /* This duplicates check in find_dhcp_option to preserve previous error return */
+  if (inter->dhcp_acklen < OFFSET_OF (vendor, inter->dhcp_ack) + sizeof (grub_uint32_t)
+      || ptr[0] != GRUB_NET_BOOTP_RFC1048_MAGIC_0
       || ptr[1] != GRUB_NET_BOOTP_RFC1048_MAGIC_1
       || ptr[2] != GRUB_NET_BOOTP_RFC1048_MAGIC_2
       || ptr[3] != GRUB_NET_BOOTP_RFC1048_MAGIC_3)
     return grub_error (GRUB_ERR_IO, N_("no DHCP options found"));
-  ptr = ptr + sizeof (grub_uint32_t);
-  while (1)
-    {
-      grub_uint8_t tagtype;
-
-      if (ptr >= ((grub_uint8_t *) inter->dhcp_ack) + inter->dhcp_acklen)
-	return grub_error (GRUB_ERR_IO, N_("no DHCP option %d found"), num);
 
-      tagtype = *ptr++;
+  num = grub_strtoul (args[2], 0, 0);
+  if (grub_errno)
+    return grub_errno;
 
-      /* Pad tag.  */
-      if (tagtype == 0)
-	continue;
+  /* Exclude PAD (0) and END (255) option codes */
+  if (num == 0 || num > 254)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("invalid DHCP option code"));
 
-      /* End tag.  */
-      if (tagtype == 0xff)
-	return grub_error (GRUB_ERR_IO, N_("no DHCP option %d found"), num);
-
-      taglength = *ptr++;
-	
-      if (tagtype == num)
-	break;
-      ptr += taglength;
-    }
+  ptr = find_dhcp_option (inter->dhcp_ack, inter->dhcp_acklen, num, &taglength);
+  if (!ptr)
+    return grub_error (GRUB_ERR_IO, N_("no DHCP option %u found"), num);
 
   if (grub_strcmp (args[3], "string") == 0)
     {
@@ -481,71 +760,12 @@ grub_cmd_bootp (struct grub_command *cmd __attribute__ ((unused)),
       int done = 0;
       for (j = 0; j < ncards; j++)
 	{
-	  struct grub_net_bootp_packet *pack;
-	  struct grub_datetime date;
-	  grub_int32_t t = 0;
-	  struct grub_net_buff *nb;
-	  struct udphdr *udph;
-	  grub_net_network_level_address_t target;
-	  grub_net_link_level_address_t ll_target;
-
 	  if (!ifaces[j].prev)
 	    continue;
-	  nb = grub_netbuff_alloc (sizeof (*pack) + 64 + 128);
-	  if (!nb)
-	    {
-	      grub_netbuff_free (nb);
-	      return grub_errno;
-	    }
-	  err = grub_netbuff_reserve (nb, sizeof (*pack) + 64 + 128);
-	  if (err)
-	    {
-	      grub_netbuff_free (nb);
-	      return err;
-	    }
-	  err = grub_netbuff_push (nb, sizeof (*pack) + 64);
-	  if (err)
-	    {
-	      grub_netbuff_free (nb);
-	      return err;
-	    }
-	  pack = (void *) nb->data;
-	  done = 1;
-	  grub_memset (pack, 0, sizeof (*pack) + 64);
-	  pack->opcode = 1;
-	  pack->hw_type = 1;
-	  pack->hw_len = 6;
-	  err = grub_get_datetime (&date);
-	  if (err || !grub_datetime2unixtime (&date, &t))
-	    {
-	      grub_errno = GRUB_ERR_NONE;
-	      t = 0;
-	    }
-	  pack->ident = grub_cpu_to_be32 (t);
-	  pack->seconds = grub_cpu_to_be16 (t);
-
-	  grub_memcpy (&pack->mac_addr, &ifaces[j].hwaddress.mac, 6); 
 
-	  grub_netbuff_push (nb, sizeof (*udph));
-
-	  udph = (struct udphdr *) nb->data;
-	  udph->src = grub_cpu_to_be16_compile_time (68);
-	  udph->dst = grub_cpu_to_be16_compile_time (67);
-	  udph->chksum = 0;
-	  udph->len = grub_cpu_to_be16 (nb->tail - nb->data);
-	  target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
-	  target.ipv4 = 0xffffffff;
-	  err = grub_net_link_layer_resolve (&ifaces[j], &target, &ll_target);
-	  if (err)
-	    return err;
-
-	  udph->chksum = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_UDP,
-							 &ifaces[j].address,
-							 &target);
+	  done = 1;
 
-	  err = grub_net_send_ip_packet (&ifaces[j], &target, &ll_target, nb,
-					 GRUB_NET_IP_UDP);
-	  grub_netbuff_free (nb);
+	  err = send_dhcp_packet (&ifaces[j]);
 	  if (err)
 	    return err;
 	}
diff --git a/grub-core/net/ip.c b/grub-core/net/ip.c
index 8c56baa..162c70d 100644
--- a/grub-core/net/ip.c
+++ b/grub-core/net/ip.c
@@ -278,7 +278,7 @@ handle_dgram (struct grub_net_buff *nb,
 	      && grub_memcmp (inf->hwaddress.mac, &bootp->mac_addr,
 			      sizeof (inf->hwaddress.mac)) == 0)
 	    {
-	      grub_net_process_dhcp (nb, inf->card);
+	      grub_net_process_dhcp (nb, inf);
 	      grub_netbuff_free (nb);
 	      return GRUB_ERR_NONE;
 	    }
diff --git a/include/grub/net.h b/include/grub/net.h
index 538baa3..073a33b 100644
--- a/include/grub/net.h
+++ b/include/grub/net.h
@@ -279,6 +279,9 @@ struct grub_net_network_level_interface
   grub_net_interface_flags_t flags;
   struct grub_net_bootp_packet *dhcp_ack;
   grub_size_t dhcp_acklen;
+  grub_uint32_t xid;    /* DHCPv4 transation id */
+  grub_uint32_t srv_id; /* DHCPv4 server_identifier */
+  grub_uint32_t my_ip;  /* DHCPv4 offered IP address */
   void *data;
 };
 
@@ -433,6 +436,13 @@ enum
     GRUB_NET_BOOTP_DOMAIN = 0x0f,
     GRUB_NET_BOOTP_ROOT_PATH = 0x11,
     GRUB_NET_BOOTP_EXTENSIONS_PATH = 0x12,
+    GRUB_NET_DHCP_REQUESTED_IP_ADDRESS = 50,
+    GRUB_NET_DHCP_OVERLOAD = 52,
+    GRUB_NET_DHCP_MESSAGE_TYPE = 53,
+    GRUB_NET_DHCP_SERVER_IDENTIFIER = 54,
+    GRUB_NET_DHCP_PARAMETER_REQUEST_LIST = 55,
+    GRUB_NET_DHCP_TFTP_SERVER_NAME = 66,
+    GRUB_NET_DHCP_BOOTFILE_NAME = 67,
     GRUB_NET_BOOTP_END = 0xff
   };
 
@@ -450,7 +460,7 @@ grub_net_add_ipv4_local (struct grub_net_network_level_interface *inf,
 
 void
 grub_net_process_dhcp (struct grub_net_buff *nb,
-		       struct grub_net_card *card);
+		       struct grub_net_network_level_interface *iface);
 
 int
 grub_net_hwaddr_cmp (const grub_net_link_level_address_t *a,
_______________________________________________
Grub-devel mailing list
Grub-devel@gnu.org
https://lists.gnu.org/mailman/listinfo/grub-devel

Reply via email to