63f1a9
From 77e75c900368ef656c00d4c485eff7b1d66d58eb Mon Sep 17 00:00:00 2001
63f1a9
From: Michael Chang <mchang@suse.com>
63f1a9
Date: Sun, 10 Jul 2016 23:46:06 +0800
31cddd
Subject: [PATCH] New net_bootp6 command and UEFI IPv6 PXE support
63f1a9
63f1a9
When grub2 image is booted from UEFI IPv6 PXE, the DHCPv6 Reply packet is
63f1a9
cached in firmware buffer which can be obtained by PXE Base Code protocol. The
63f1a9
network interface can be setup through the parameters in that obtained packet.
63f1a9
63f1a9
Implement new net_bootp6 command for IPv6 network auto configuration via the
63f1a9
DHCPv6 protocol (RFC3315).
63f1a9
63f1a9
Signed-off-by: Michael Chang <mchang@suse.com>
63f1a9
Signed-off-by: Ken Lin <ken.lin@hpe.com>
63f1a9
---
63f1a9
 grub-core/net/bootp.c              | 1047 ++++++++++++++++++++++++++++++------
63f1a9
 grub-core/net/drivers/efi/efinet.c |   20 +-
63f1a9
 grub-core/net/ip.c                 |   39 ++
63f1a9
 include/grub/efi/api.h             |    2 +-
63f1a9
 include/grub/net.h                 |   80 +--
63f1a9
 5 files changed, 988 insertions(+), 200 deletions(-)
63f1a9
63f1a9
diff --git a/grub-core/net/bootp.c b/grub-core/net/bootp.c
63f1a9
index da3e454466b..6995f986744 100644
63f1a9
--- a/grub-core/net/bootp.c
63f1a9
+++ b/grub-core/net/bootp.c
63f1a9
@@ -25,6 +25,98 @@
63f1a9
 #include <grub/net/udp.h>
63f1a9
 #include <grub/net/url.h>
63f1a9
 #include <grub/datetime.h>
63f1a9
+#include <grub/time.h>
63f1a9
+#include <grub/list.h>
63f1a9
+
63f1a9
+static int
63f1a9
+dissect_url (const char *url, char **proto, char **host, char **path)
63f1a9
+{
63f1a9
+  const char *p, *ps;
63f1a9
+  grub_size_t l;
63f1a9
+
63f1a9
+  *proto = *host = *path = NULL;
63f1a9
+  ps = p = url;
63f1a9
+
63f1a9
+  while ((p = grub_strchr (p, ':')))
63f1a9
+    {
63f1a9
+      if (grub_strlen (p) < sizeof ("://") - 1)
63f1a9
+	break;
63f1a9
+      if (grub_memcmp (p, "://", sizeof ("://") - 1) == 0)
63f1a9
+	{
63f1a9
+	  l = p - ps;
63f1a9
+	  *proto = grub_malloc (l + 1);
63f1a9
+	  if (!*proto)
63f1a9
+	    {
63f1a9
+	      grub_print_error ();
63f1a9
+	      return 0;
63f1a9
+	    }
63f1a9
+
63f1a9
+	  grub_memcpy (*proto, ps, l);
63f1a9
+	  (*proto)[l] = '\0';
63f1a9
+	  p +=  sizeof ("://") - 1;
63f1a9
+	  break;
63f1a9
+	}
63f1a9
+      ++p;
63f1a9
+    }
63f1a9
+
63f1a9
+  if (!*proto)
63f1a9
+    {
63f1a9
+      grub_dprintf ("bootp", "url: %s is not valid, protocol not found\n", url);
63f1a9
+      return 0;
63f1a9
+    }
63f1a9
+
63f1a9
+  ps = p;
63f1a9
+  p = grub_strchr (p, '/');
63f1a9
+
63f1a9
+  if (!p)
63f1a9
+    {
63f1a9
+      grub_dprintf ("bootp", "url: %s is not valid, host/path not found\n", url);
63f1a9
+      grub_free (*proto);
63f1a9
+      *proto = NULL;
63f1a9
+      return 0;
63f1a9
+    }
63f1a9
+
63f1a9
+  l = p - ps;
63f1a9
+
63f1a9
+  if (l > 2 && ps[0] == '[' && ps[l - 1] == ']')
63f1a9
+    {
63f1a9
+      *host = grub_malloc (l - 1);
63f1a9
+      if (!*host)
63f1a9
+	{
63f1a9
+	  grub_print_error ();
63f1a9
+	  grub_free (*proto);
63f1a9
+	  *proto = NULL;
63f1a9
+	  return 0;
63f1a9
+	}
63f1a9
+      grub_memcpy (*host, ps + 1, l - 2);
63f1a9
+      (*host)[l - 2] = 0;
63f1a9
+    }
63f1a9
+  else
63f1a9
+    {
63f1a9
+      *host = grub_malloc (l + 1);
63f1a9
+      if (!*host)
63f1a9
+	{
63f1a9
+	  grub_print_error ();
63f1a9
+	  grub_free (*proto);
63f1a9
+	  *proto = NULL;
63f1a9
+	  return 0;
63f1a9
+	}
63f1a9
+      grub_memcpy (*host, ps, l);
63f1a9
+      (*host)[l] = 0;
63f1a9
+    }
63f1a9
+
63f1a9
+  *path = grub_strdup (p);
63f1a9
+  if (!*path)
63f1a9
+    {
63f1a9
+      grub_print_error ();
63f1a9
+      grub_free (*host);
63f1a9
+      grub_free (*proto);
63f1a9
+      *host = NULL;
63f1a9
+      *proto = NULL;
63f1a9
+      return 0;
63f1a9
+    }
63f1a9
+  return 1;
63f1a9
+}
63f1a9
 
63f1a9
 static char *
63f1a9
 grub_env_write_readonly (struct grub_env_var *var __attribute__ ((unused)),
63f1a9
@@ -350,178 +442,577 @@ grub_net_configure_by_dhcp_ack (const char *name,
63f1a9
   return inter;
63f1a9
 }
63f1a9
 
63f1a9
-struct grub_net_network_level_interface *
63f1a9
-grub_net_configure_by_dhcpv6_ack (const char *name,
63f1a9
-				  struct grub_net_card *card,
63f1a9
-				  grub_net_interface_flags_t flags
63f1a9
-				    __attribute__((__unused__)),
63f1a9
-				  const grub_net_link_level_address_t *hwaddr,
63f1a9
-				  const struct grub_net_dhcpv6_packet *packet,
63f1a9
-				  int is_def, char **device, char **path)
63f1a9
-{
63f1a9
-  struct grub_net_network_level_interface *inter = NULL;
63f1a9
-  struct grub_net_network_level_address addr;
63f1a9
-  int mask = -1;
63f1a9
-
63f1a9
-  if (!device || !path)
63f1a9
+/* The default netbuff size for sending DHCPv6 packets which should be
63f1a9
+   large enough to hold the information */
63f1a9
+#define GRUB_DHCP6_DEFAULT_NETBUFF_ALLOC_SIZE 512
63f1a9
+
63f1a9
+struct grub_dhcp6_options
63f1a9
+{
63f1a9
+  grub_uint8_t *client_duid;
63f1a9
+  grub_uint16_t client_duid_len;
63f1a9
+  grub_uint8_t *server_duid;
63f1a9
+  grub_uint16_t server_duid_len;
63f1a9
+  grub_uint32_t iaid;
63f1a9
+  grub_uint32_t t1;
63f1a9
+  grub_uint32_t t2;
63f1a9
+  grub_net_network_level_address_t *ia_addr;
63f1a9
+  grub_uint32_t preferred_lifetime;
63f1a9
+  grub_uint32_t valid_lifetime;
63f1a9
+  grub_net_network_level_address_t *dns_server_addrs;
63f1a9
+  grub_uint16_t num_dns_server;
63f1a9
+  char *boot_file_proto;
63f1a9
+  char *boot_file_server_ip;
63f1a9
+  char *boot_file_path;
63f1a9
+};
63f1a9
+
63f1a9
+typedef struct grub_dhcp6_options *grub_dhcp6_options_t;
63f1a9
+
63f1a9
+struct grub_dhcp6_session
63f1a9
+{
63f1a9
+  struct grub_dhcp6_session *next;
63f1a9
+  struct grub_dhcp6_session **prev;
63f1a9
+  grub_uint32_t iaid;
63f1a9
+  grub_uint32_t transaction_id:24;
63f1a9
+  grub_uint64_t start_time;
63f1a9
+  struct grub_net_dhcp6_option_duid_ll duid;
63f1a9
+  struct grub_net_network_level_interface *iface;
63f1a9
+
63f1a9
+  /* The associated dhcpv6 options */
63f1a9
+  grub_dhcp6_options_t adv;
63f1a9
+  grub_dhcp6_options_t reply;
63f1a9
+};
63f1a9
+
63f1a9
+typedef struct grub_dhcp6_session *grub_dhcp6_session_t;
63f1a9
+
63f1a9
+typedef void (*dhcp6_option_hook_fn) (const struct grub_net_dhcp6_option *opt, void *data);
63f1a9
+
63f1a9
+static void
63f1a9
+foreach_dhcp6_option (const struct grub_net_dhcp6_option *opt, grub_size_t size,
63f1a9
+		      dhcp6_option_hook_fn hook, void *hook_data);
63f1a9
+
63f1a9
+static void
63f1a9
+parse_dhcp6_iaaddr (const struct grub_net_dhcp6_option *opt, void *data)
63f1a9
+{
63f1a9
+  grub_dhcp6_options_t dhcp6 = (grub_dhcp6_options_t )data;
63f1a9
+
63f1a9
+  grub_uint16_t code = grub_be_to_cpu16 (opt->code);
63f1a9
+  grub_uint16_t len = grub_be_to_cpu16 (opt->len);
63f1a9
+
63f1a9
+  if (code == GRUB_NET_DHCP6_OPTION_IAADDR)
63f1a9
+    {
63f1a9
+      const struct grub_net_dhcp6_option_iaaddr *iaaddr;
63f1a9
+      iaaddr = (const struct grub_net_dhcp6_option_iaaddr *)opt->data;
63f1a9
+
63f1a9
+      if (len < sizeof (*iaaddr))
63f1a9
+	{
63f1a9
+	  grub_dprintf ("bootp", "DHCPv6: code %u with insufficient length %u\n", code, len);
63f1a9
+	  return;
63f1a9
+	}
63f1a9
+      if (!dhcp6->ia_addr)
63f1a9
+	{
63f1a9
+	  dhcp6->ia_addr = grub_malloc (sizeof(*dhcp6->ia_addr));
63f1a9
+	  dhcp6->ia_addr->type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6;
63f1a9
+	  dhcp6->ia_addr->ipv6[0] = grub_get_unaligned64 (iaaddr->addr);
63f1a9
+	  dhcp6->ia_addr->ipv6[1] = grub_get_unaligned64 (iaaddr->addr + 8);
63f1a9
+	  dhcp6->preferred_lifetime = grub_be_to_cpu32 (iaaddr->preferred_lifetime);
63f1a9
+	  dhcp6->valid_lifetime = grub_be_to_cpu32 (iaaddr->valid_lifetime);
63f1a9
+	}
63f1a9
+    }
63f1a9
+}
63f1a9
+
63f1a9
+static void
63f1a9
+parse_dhcp6_option (const struct grub_net_dhcp6_option *opt, void *data)
63f1a9
+{
63f1a9
+  grub_dhcp6_options_t dhcp6 = (grub_dhcp6_options_t)data;
63f1a9
+  grub_uint16_t code = grub_be_to_cpu16 (opt->code);
63f1a9
+  grub_uint16_t len = grub_be_to_cpu16 (opt->len);
63f1a9
+
63f1a9
+  switch (code)
63f1a9
+    {
63f1a9
+      case GRUB_NET_DHCP6_OPTION_CLIENTID:
63f1a9
+
63f1a9
+	if (dhcp6->client_duid || !len)
63f1a9
+	  {
63f1a9
+	    grub_dprintf ("bootp", "Skipped DHCPv6 CLIENTID with length %u\n", len);
63f1a9
+	    break;
63f1a9
+	  }
63f1a9
+	dhcp6->client_duid = grub_malloc (len);
63f1a9
+	grub_memcpy (dhcp6->client_duid, opt->data, len);
63f1a9
+	dhcp6->client_duid_len = len;
63f1a9
+	break;
63f1a9
+
63f1a9
+      case GRUB_NET_DHCP6_OPTION_SERVERID:
63f1a9
+
63f1a9
+	if (dhcp6->server_duid || !len)
63f1a9
+	  {
63f1a9
+	    grub_dprintf ("bootp", "Skipped DHCPv6 SERVERID with length %u\n", len);
63f1a9
+	    break;
63f1a9
+	  }
63f1a9
+	dhcp6->server_duid = grub_malloc (len);
63f1a9
+	grub_memcpy (dhcp6->server_duid, opt->data, len);
63f1a9
+	dhcp6->server_duid_len = len;
63f1a9
+	break;
63f1a9
+
63f1a9
+      case GRUB_NET_DHCP6_OPTION_IA_NA:
63f1a9
+	{
63f1a9
+	  const struct grub_net_dhcp6_option_iana *ia_na;
63f1a9
+	  grub_uint16_t data_len;
63f1a9
+
63f1a9
+	  if (dhcp6->iaid || len < sizeof (*ia_na))
63f1a9
+	    {
63f1a9
+	      grub_dprintf ("bootp", "Skipped DHCPv6 IA_NA with length %u\n", len);
63f1a9
+	      break;
63f1a9
+	    }
63f1a9
+	  ia_na = (const struct grub_net_dhcp6_option_iana *)opt->data;
63f1a9
+	  dhcp6->iaid = grub_be_to_cpu32 (ia_na->iaid);
63f1a9
+	  dhcp6->t1 = grub_be_to_cpu32 (ia_na->t1);
63f1a9
+	  dhcp6->t2 = grub_be_to_cpu32 (ia_na->t2);
63f1a9
+
63f1a9
+	  data_len = len - sizeof (*ia_na);
63f1a9
+	  if (data_len)
63f1a9
+	    foreach_dhcp6_option ((const struct grub_net_dhcp6_option *)ia_na->data, data_len, parse_dhcp6_iaaddr, dhcp6);
63f1a9
+	}
63f1a9
+	break;
63f1a9
+
63f1a9
+      case GRUB_NET_DHCP6_OPTION_DNS_SERVERS:
63f1a9
+	{
63f1a9
+	  const grub_uint8_t *po;
63f1a9
+	  grub_uint16_t ln;
63f1a9
+	  grub_net_network_level_address_t *la;
63f1a9
+
63f1a9
+	  if (!len || len & 0xf)
63f1a9
+	    {
63f1a9
+	      grub_dprintf ("bootp", "Skip invalid length DHCPv6 DNS_SERVERS \n");
63f1a9
+	      break;
63f1a9
+	    }
63f1a9
+	  dhcp6->num_dns_server = ln = len >> 4;
63f1a9
+	  dhcp6->dns_server_addrs = la = grub_zalloc (ln * sizeof (*la));
63f1a9
+
63f1a9
+	  for (po = opt->data; ln > 0; po += 0x10, la++, ln--)
63f1a9
+	    {
63f1a9
+	      la->type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6;
63f1a9
+	      la->ipv6[0] = grub_get_unaligned64 (po);
63f1a9
+	      la->ipv6[1] = grub_get_unaligned64 (po + 8);
63f1a9
+	      la->option = DNS_OPTION_PREFER_IPV6;
63f1a9
+	    }
63f1a9
+	}
63f1a9
+	break;
63f1a9
+
63f1a9
+      case GRUB_NET_DHCP6_OPTION_BOOTFILE_URL:
63f1a9
+	dissect_url ((const char *)opt->data,
63f1a9
+		      &dhcp6->boot_file_proto,
63f1a9
+		      &dhcp6->boot_file_server_ip,
63f1a9
+		      &dhcp6->boot_file_path);
63f1a9
+	break;
63f1a9
+
63f1a9
+      default:
63f1a9
+	break;
63f1a9
+    }
63f1a9
+}
63f1a9
+
63f1a9
+static void
63f1a9
+foreach_dhcp6_option (const struct grub_net_dhcp6_option *opt, grub_size_t size, dhcp6_option_hook_fn hook, void *hook_data)
63f1a9
+{
63f1a9
+  while (size)
63f1a9
+    {
63f1a9
+      grub_uint16_t code, len;
63f1a9
+
63f1a9
+      if (size < sizeof (*opt))
63f1a9
+	{
63f1a9
+	  grub_dprintf ("bootp", "DHCPv6: Options stopped with remaining size %" PRIxGRUB_SIZE "\n", size);
63f1a9
+	  break;
63f1a9
+	}
63f1a9
+      size -= sizeof (*opt);
63f1a9
+      len = grub_be_to_cpu16 (opt->len);
63f1a9
+      code = grub_be_to_cpu16 (opt->code);
63f1a9
+      if (size < len)
63f1a9
+	{
63f1a9
+	  grub_dprintf ("bootp", "DHCPv6: Options stopped at out of bound length %u for option %u\n", len, code);
63f1a9
+	  break;
63f1a9
+	}
63f1a9
+      if (!len)
63f1a9
+	{
63f1a9
+	  grub_dprintf ("bootp", "DHCPv6: Options stopped at zero length option %u\n", code);
63f1a9
+	  break;
63f1a9
+	}
63f1a9
+      else
63f1a9
+	{
63f1a9
+	  if (hook)
63f1a9
+	    hook (opt, hook_data);
63f1a9
+	  size -= len;
63f1a9
+	  opt = (const struct grub_net_dhcp6_option *)((grub_uint8_t *)opt + len + sizeof (*opt));
63f1a9
+	}
63f1a9
+    }
63f1a9
+}
63f1a9
+
63f1a9
+static grub_dhcp6_options_t
63f1a9
+grub_dhcp6_options_get (const struct grub_net_dhcp6_packet *v6h,
63f1a9
+			grub_size_t size)
63f1a9
+{
63f1a9
+  grub_dhcp6_options_t options;
63f1a9
+
63f1a9
+  if (size < sizeof (*v6h))
63f1a9
+    {
63f1a9
+      grub_error (GRUB_ERR_OUT_OF_RANGE, N_("DHCPv6 packet size too small"));
63f1a9
+      return NULL;
63f1a9
+    }
63f1a9
+
63f1a9
+  options = grub_zalloc (sizeof(*options));
63f1a9
+  if (!options)
63f1a9
     return NULL;
63f1a9
 
63f1a9
-  *device = 0;
63f1a9
-  *path = 0;
63f1a9
-
63f1a9
-  grub_dprintf ("net", "mac address is %02x:%02x:%02x:%02x:%02x:%02x\n",
63f1a9
-		hwaddr->mac[0], hwaddr->mac[1], hwaddr->mac[2],
63f1a9
-		hwaddr->mac[3], hwaddr->mac[4], hwaddr->mac[5]);
63f1a9
-
63f1a9
-  if (is_def)
63f1a9
-    grub_net_default_server = 0;
63f1a9
-
63f1a9
-  if (is_def && !grub_net_default_server && packet)
63f1a9
+  foreach_dhcp6_option ((const struct grub_net_dhcp6_option *)v6h->dhcp_options,
63f1a9
+		       size - sizeof (*v6h), parse_dhcp6_option, options);
63f1a9
+
63f1a9
+  return options;
63f1a9
+}
63f1a9
+
63f1a9
+static void
63f1a9
+grub_dhcp6_options_free (grub_dhcp6_options_t options)
63f1a9
+{
63f1a9
+  if (options->client_duid)
63f1a9
+    grub_free (options->client_duid);
63f1a9
+  if (options->server_duid)
63f1a9
+    grub_free (options->server_duid);
63f1a9
+  if (options->ia_addr)
63f1a9
+    grub_free (options->ia_addr);
63f1a9
+  if (options->dns_server_addrs)
63f1a9
+    grub_free (options->dns_server_addrs);
63f1a9
+  if (options->boot_file_proto)
63f1a9
+    grub_free (options->boot_file_proto);
63f1a9
+  if (options->boot_file_server_ip)
63f1a9
+    grub_free (options->boot_file_server_ip);
63f1a9
+  if (options->boot_file_path)
63f1a9
+    grub_free (options->boot_file_path);
63f1a9
+
63f1a9
+  grub_free (options);
63f1a9
+}
63f1a9
+
63f1a9
+static grub_dhcp6_session_t grub_dhcp6_sessions;
63f1a9
+#define FOR_DHCP6_SESSIONS(var) FOR_LIST_ELEMENTS (var, grub_dhcp6_sessions)
63f1a9
+
63f1a9
+static void
63f1a9
+grub_net_configure_by_dhcp6_info (const char *name,
63f1a9
+	  struct grub_net_card *card,
63f1a9
+	  grub_dhcp6_options_t dhcp6,
63f1a9
+	  int is_def,
63f1a9
+	  int flags,
63f1a9
+	  struct grub_net_network_level_interface **ret_inf)
63f1a9
+{
63f1a9
+  grub_net_network_level_netaddress_t netaddr;
63f1a9
+  struct grub_net_network_level_interface *inf;
63f1a9
+
63f1a9
+  if (dhcp6->ia_addr)
63f1a9
     {
63f1a9
-      const grub_uint8_t *options = packet->dhcp_options;
63f1a9
-      unsigned int option_max = 1024 - OFFSET_OF (dhcp_options, packet);
63f1a9
-      unsigned int i;
63f1a9
-
63f1a9
-      for (i = 0; i < option_max - sizeof (grub_net_dhcpv6_option_t); )
63f1a9
-	{
63f1a9
-	  grub_uint16_t num, len;
63f1a9
-	  grub_net_dhcpv6_option_t *opt =
63f1a9
-	    (grub_net_dhcpv6_option_t *)(options + i);
63f1a9
-
63f1a9
-	  num = grub_be_to_cpu16(opt->option_num);
63f1a9
-	  len = grub_be_to_cpu16(opt->option_len);
63f1a9
-
63f1a9
-	  grub_dprintf ("net", "got dhcpv6 option %d len %d\n", num, len);
63f1a9
-
63f1a9
-	  if (len == 0)
63f1a9
-	    break;
63f1a9
-
63f1a9
-	  if (len + i > 1024)
63f1a9
-	    break;
63f1a9
-
63f1a9
-	  if (num == GRUB_NET_DHCP6_BOOTFILE_URL)
63f1a9
-	    {
63f1a9
-	      char *scheme, *userinfo, *host, *file;
63f1a9
-	      char *tmp;
63f1a9
-	      int hostlen;
63f1a9
-	      int port;
63f1a9
-	      int rc = extract_url_info ((const char *)opt->option_data,
63f1a9
-					 (grub_size_t)len,
63f1a9
-					 &scheme, &userinfo, &host, &port,
63f1a9
-					 &file;;
63f1a9
-	      if (rc < 0)
63f1a9
-		continue;
63f1a9
-
63f1a9
-	      /* right now this only handles tftp. */
63f1a9
-	      if (grub_strcmp("tftp", scheme))
63f1a9
-		{
63f1a9
-		  grub_free (scheme);
63f1a9
-		  grub_free (userinfo);
63f1a9
-		  grub_free (host);
63f1a9
-		  grub_free (file);
63f1a9
-		  continue;
63f1a9
-		}
63f1a9
-	      grub_free (userinfo);
63f1a9
-
63f1a9
-	      hostlen = grub_strlen (host);
63f1a9
-	      if (hostlen > 2 && host[0] == '[' && host[hostlen-1] == ']')
63f1a9
-		{
63f1a9
-		  tmp = host+1;
63f1a9
-		  host[hostlen-1] = '\0';
63f1a9
-		}
63f1a9
-	      else
63f1a9
-		tmp = host;
63f1a9
+      inf = grub_net_add_addr (name, card, dhcp6->ia_addr, &card->default_address, flags);
63f1a9
 
63f1a9
-	      *device = grub_xasprintf ("%s,%s", scheme, tmp);
63f1a9
-	      grub_free (scheme);
63f1a9
-	      grub_free (host);
63f1a9
+      netaddr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6;
63f1a9
+      netaddr.ipv6.base[0] = dhcp6->ia_addr->ipv6[0];
63f1a9
+      netaddr.ipv6.base[1] = 0;
63f1a9
+      netaddr.ipv6.masksize = 64;
63f1a9
+      grub_net_add_route (name, netaddr, inf);
63f1a9
 
63f1a9
-	      if (file && *file)
63f1a9
-		{
63f1a9
-		  tmp = grub_strrchr (file, '/');
63f1a9
-		  if (tmp)
63f1a9
-		    *(tmp+1) = '\0';
63f1a9
-		  else
63f1a9
-		    file[0] = '\0';
63f1a9
-		}
63f1a9
-	      else if (!file)
63f1a9
-		file = grub_strdup ("");
63f1a9
-
63f1a9
-	      if (file[0] == '/')
63f1a9
-		{
63f1a9
-		  *path = grub_strdup (file+1);
63f1a9
-		  grub_free (file);
63f1a9
-		}
63f1a9
-	      else
63f1a9
-		*path = file;
63f1a9
-	    }
63f1a9
-	  else if (num == GRUB_NET_DHCP6_IA_NA)
63f1a9
-	    {
63f1a9
-	      const grub_net_dhcpv6_option_t *ia_na_opt;
63f1a9
-	      const grub_net_dhcpv6_opt_ia_na_t *ia_na =
63f1a9
-		(const grub_net_dhcpv6_opt_ia_na_t *)opt;
63f1a9
-	      unsigned int left = len - OFFSET_OF (options, ia_na);
63f1a9
-	      unsigned int j;
63f1a9
-
63f1a9
-	      if ((grub_uint8_t *)ia_na + left >
63f1a9
-		  (grub_uint8_t *)options + option_max)
63f1a9
-		left -= ((grub_uint8_t *)ia_na + left)
63f1a9
-		        - ((grub_uint8_t *)options + option_max);
63f1a9
-
63f1a9
-	      if (len < OFFSET_OF (option_data, opt)
63f1a9
-			+ sizeof (grub_net_dhcpv6_option_t))
63f1a9
-		{
63f1a9
-		  grub_dprintf ("net",
63f1a9
-				"found dhcpv6 ia_na option with no address\n");
63f1a9
-		  continue;
63f1a9
-		}
63f1a9
-
63f1a9
-	      for (j = 0; left > sizeof (grub_net_dhcpv6_option_t); )
63f1a9
-		{
63f1a9
-		  ia_na_opt = (const grub_net_dhcpv6_option_t *)
63f1a9
-			       (ia_na->options + j);
63f1a9
-		  grub_uint16_t ia_na_opt_num, ia_na_opt_len;
63f1a9
-
63f1a9
-		  ia_na_opt_num = grub_be_to_cpu16 (ia_na_opt->option_num);
63f1a9
-		  ia_na_opt_len = grub_be_to_cpu16 (ia_na_opt->option_len);
63f1a9
-		  if (ia_na_opt_len == 0)
63f1a9
-		    break;
63f1a9
-		  if (j + ia_na_opt_len > left)
63f1a9
-		    break;
63f1a9
-		  if (ia_na_opt_num == GRUB_NET_DHCP6_IA_ADDRESS)
63f1a9
-		    {
63f1a9
-		      const grub_net_dhcpv6_opt_ia_address_t *ia_addr;
63f1a9
-
63f1a9
-		      ia_addr = (const grub_net_dhcpv6_opt_ia_address_t *)
63f1a9
-				 ia_na_opt;
63f1a9
-		      addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6;
63f1a9
-		      grub_memcpy(addr.ipv6, ia_addr->ipv6_address,
63f1a9
-				  sizeof (ia_addr->ipv6_address));
63f1a9
-		      inter = grub_net_add_addr (name, card, &addr, hwaddr, 0);
63f1a9
-		    }
63f1a9
-
63f1a9
-		  j += ia_na_opt_len;
63f1a9
-		  left -= ia_na_opt_len;
63f1a9
-		}
63f1a9
-	    }
63f1a9
+      if (ret_inf)
63f1a9
+	*ret_inf = inf;
63f1a9
+    }
63f1a9
 
63f1a9
-	  i += len + 4;
63f1a9
-	}
63f1a9
+  if (dhcp6->dns_server_addrs)
63f1a9
+    {
63f1a9
+      grub_uint16_t i;
63f1a9
 
63f1a9
-      grub_print_error ();
63f1a9
+      for (i = 0; i < dhcp6->num_dns_server; ++i)
63f1a9
+	grub_net_add_dns_server (dhcp6->dns_server_addrs + i);
63f1a9
     }
63f1a9
 
63f1a9
-  if (is_def)
63f1a9
+  if (dhcp6->boot_file_path)
63f1a9
+    grub_env_set_net_property (name, "boot_file", dhcp6->boot_file_path,
63f1a9
+			  grub_strlen (dhcp6->boot_file_path));
63f1a9
+
63f1a9
+  if (is_def && dhcp6->boot_file_server_ip)
63f1a9
     {
63f1a9
+      grub_net_default_server = grub_strdup (dhcp6->boot_file_server_ip);
63f1a9
       grub_env_set ("net_default_interface", name);
63f1a9
       grub_env_export ("net_default_interface");
63f1a9
     }
63f1a9
+}
63f1a9
+
63f1a9
+static void
63f1a9
+grub_dhcp6_session_add (struct grub_net_network_level_interface *iface,
63f1a9
+			grub_uint32_t iaid)
63f1a9
+{
63f1a9
+  grub_dhcp6_session_t se;
63f1a9
+  struct grub_datetime date;
63f1a9
+  grub_err_t err;
63f1a9
+  grub_int32_t t = 0;
63f1a9
+
63f1a9
+  se = grub_malloc (sizeof (*se));
63f1a9
+
63f1a9
+  err = grub_get_datetime (&date);
63f1a9
+  if (err || !grub_datetime2unixtime (&date, &t))
63f1a9
+    {
63f1a9
+      grub_errno = GRUB_ERR_NONE;
63f1a9
+      t = 0;
63f1a9
+    }
63f1a9
+
63f1a9
+  se->iface = iface;
63f1a9
+  se->iaid = iaid;
63f1a9
+  se->transaction_id = t;
63f1a9
+  se->start_time = grub_get_time_ms ();
63f1a9
+  se->duid.type = grub_cpu_to_be16_compile_time (3) ;
63f1a9
+  se->duid.hw_type = grub_cpu_to_be16_compile_time (1);
63f1a9
+  grub_memcpy (&se->duid.hwaddr, &iface->hwaddress.mac, sizeof (se->duid.hwaddr));
63f1a9
+  se->adv = NULL;
63f1a9
+  se->reply = NULL;
63f1a9
+  grub_list_push (GRUB_AS_LIST_P (&grub_dhcp6_sessions), GRUB_AS_LIST (se));
63f1a9
+}
63f1a9
+
63f1a9
+static void
63f1a9
+grub_dhcp6_session_remove (grub_dhcp6_session_t se)
63f1a9
+{
63f1a9
+  grub_list_remove (GRUB_AS_LIST (se));
63f1a9
+  if (se->adv)
63f1a9
+    grub_dhcp6_options_free (se->adv);
63f1a9
+  if (se->reply)
63f1a9
+    grub_dhcp6_options_free (se->reply);
63f1a9
+  grub_free (se);
63f1a9
+}
63f1a9
+
63f1a9
+static void
63f1a9
+grub_dhcp6_session_remove_all (void)
63f1a9
+{
63f1a9
+  grub_dhcp6_session_t se;
63f1a9
+
63f1a9
+  FOR_DHCP6_SESSIONS (se)
63f1a9
+    {
63f1a9
+      grub_dhcp6_session_remove (se);
63f1a9
+      se = grub_dhcp6_sessions;
63f1a9
+    }
63f1a9
+}
63f1a9
+
63f1a9
+static grub_err_t
63f1a9
+grub_dhcp6_session_configure_network (grub_dhcp6_session_t se)
63f1a9
+{
63f1a9
+  char *name;
63f1a9
 
63f1a9
-    if (inter)
63f1a9
-      grub_net_add_ipv6_local (inter, mask);
63f1a9
-    return inter;
63f1a9
+  name = grub_xasprintf ("%s:dhcp6", se->iface->card->name);
63f1a9
+  if (!name)
63f1a9
+    return grub_errno;
63f1a9
+
63f1a9
+  grub_net_configure_by_dhcp6_info (name, se->iface->card, se->reply, 1, 0, 0);
63f1a9
+  grub_free (name);
63f1a9
+
63f1a9
+  return GRUB_ERR_NONE;
63f1a9
 }
63f1a9
 
63f1a9
+static grub_err_t
63f1a9
+grub_dhcp6_session_send_request (grub_dhcp6_session_t se)
63f1a9
+{
63f1a9
+  struct grub_net_buff *nb;
63f1a9
+  struct grub_net_dhcp6_option *opt;
63f1a9
+  struct grub_net_dhcp6_packet *v6h;
63f1a9
+  struct grub_net_dhcp6_option_iana *ia_na;
63f1a9
+  struct grub_net_dhcp6_option_iaaddr *iaaddr;
63f1a9
+  struct udphdr *udph;
63f1a9
+  grub_net_network_level_address_t multicast;
63f1a9
+  grub_net_link_level_address_t ll_multicast;
63f1a9
+  grub_uint64_t elapsed;
63f1a9
+  struct grub_net_network_level_interface *inf = se->iface;
63f1a9
+  grub_dhcp6_options_t dhcp6 = se->adv;
63f1a9
+  grub_err_t err = GRUB_ERR_NONE;
63f1a9
+
63f1a9
+  multicast.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6;
63f1a9
+  multicast.ipv6[0] = grub_cpu_to_be64_compile_time (0xff02ULL << 48);
63f1a9
+  multicast.ipv6[1] = grub_cpu_to_be64_compile_time (0x10002ULL);
63f1a9
+
63f1a9
+  err = grub_net_link_layer_resolve (inf, &multicast, &ll_multicast);
63f1a9
+  if (err)
63f1a9
+    return err;
63f1a9
+
63f1a9
+  nb = grub_netbuff_alloc (GRUB_DHCP6_DEFAULT_NETBUFF_ALLOC_SIZE);
63f1a9
+
63f1a9
+  if (!nb)
63f1a9
+    return grub_errno;
63f1a9
+
63f1a9
+  err = grub_netbuff_reserve (nb, GRUB_DHCP6_DEFAULT_NETBUFF_ALLOC_SIZE);
63f1a9
+  if (err)
63f1a9
+    {
63f1a9
+      grub_netbuff_free (nb);
63f1a9
+      return err;
63f1a9
+    }
63f1a9
+
63f1a9
+  err = grub_netbuff_push (nb, dhcp6->client_duid_len + sizeof (*opt));
63f1a9
+  if (err)
63f1a9
+    {
63f1a9
+      grub_netbuff_free (nb);
63f1a9
+      return err;
63f1a9
+    }
63f1a9
+  opt = (struct grub_net_dhcp6_option *)nb->data;
63f1a9
+  opt->code = grub_cpu_to_be16_compile_time (GRUB_NET_DHCP6_OPTION_CLIENTID);
63f1a9
+  opt->len = grub_cpu_to_be16 (dhcp6->client_duid_len);
63f1a9
+  grub_memcpy (opt->data, dhcp6->client_duid , dhcp6->client_duid_len);
63f1a9
+
63f1a9
+  err = grub_netbuff_push (nb, dhcp6->server_duid_len + sizeof (*opt));
63f1a9
+  if (err)
63f1a9
+    {
63f1a9
+      grub_netbuff_free (nb);
63f1a9
+      return err;
63f1a9
+    }
63f1a9
+  opt = (struct grub_net_dhcp6_option *)nb->data;
63f1a9
+  opt->code = grub_cpu_to_be16_compile_time (GRUB_NET_DHCP6_OPTION_SERVERID);
63f1a9
+  opt->len = grub_cpu_to_be16 (dhcp6->server_duid_len);
63f1a9
+  grub_memcpy (opt->data, dhcp6->server_duid , dhcp6->server_duid_len);
63f1a9
+
63f1a9
+  err = grub_netbuff_push (nb, sizeof (*ia_na) + sizeof (*opt));
63f1a9
+  if (err)
63f1a9
+    {
63f1a9
+      grub_netbuff_free (nb);
63f1a9
+      return err;
63f1a9
+    }
63f1a9
+
63f1a9
+  if (dhcp6->ia_addr)
63f1a9
+    {
63f1a9
+      err = grub_netbuff_push (nb, sizeof(*iaaddr) + sizeof (*opt));
63f1a9
+      if (err)
63f1a9
+	{
63f1a9
+	  grub_netbuff_free (nb);
63f1a9
+	  return err;
63f1a9
+	}
63f1a9
+    }
63f1a9
+  opt = (struct grub_net_dhcp6_option *)nb->data;
63f1a9
+  opt->code = grub_cpu_to_be16_compile_time (GRUB_NET_DHCP6_OPTION_IA_NA);
63f1a9
+  opt->len = grub_cpu_to_be16 (sizeof (*ia_na));
63f1a9
+  if (dhcp6->ia_addr)
63f1a9
+    opt->len += grub_cpu_to_be16 (sizeof(*iaaddr) + sizeof (*opt));
63f1a9
+
63f1a9
+  ia_na = (struct grub_net_dhcp6_option_iana *)opt->data;
63f1a9
+  ia_na->iaid = grub_cpu_to_be32 (dhcp6->iaid);
63f1a9
+
63f1a9
+  ia_na->t1 = grub_cpu_to_be32 (dhcp6->t1);
63f1a9
+  ia_na->t2 = grub_cpu_to_be32 (dhcp6->t2);
63f1a9
+
63f1a9
+  if (dhcp6->ia_addr)
63f1a9
+    {
63f1a9
+      opt = (struct grub_net_dhcp6_option *)ia_na->data;
63f1a9
+      opt->code = grub_cpu_to_be16_compile_time (GRUB_NET_DHCP6_OPTION_IAADDR);
63f1a9
+      opt->len = grub_cpu_to_be16 (sizeof (*iaaddr));
63f1a9
+      iaaddr = (struct grub_net_dhcp6_option_iaaddr *)opt->data;
63f1a9
+      grub_set_unaligned64 (iaaddr->addr, dhcp6->ia_addr->ipv6[0]);
63f1a9
+      grub_set_unaligned64 (iaaddr->addr + 8, dhcp6->ia_addr->ipv6[1]);
63f1a9
+
63f1a9
+      iaaddr->preferred_lifetime = grub_cpu_to_be32 (dhcp6->preferred_lifetime);
63f1a9
+      iaaddr->valid_lifetime = grub_cpu_to_be32 (dhcp6->valid_lifetime);
63f1a9
+    }
63f1a9
+
63f1a9
+  err = grub_netbuff_push (nb, sizeof (*opt) + 2 * sizeof (grub_uint16_t));
63f1a9
+  if (err)
63f1a9
+    {
63f1a9
+      grub_netbuff_free (nb);
63f1a9
+      return err;
63f1a9
+    }
63f1a9
+
63f1a9
+  opt = (struct grub_net_dhcp6_option*) nb->data;
63f1a9
+  opt->code = grub_cpu_to_be16_compile_time (GRUB_NET_DHCP6_OPTION_ORO);
63f1a9
+  opt->len = grub_cpu_to_be16_compile_time (2 * sizeof (grub_uint16_t));
63f1a9
+  grub_set_unaligned16 (opt->data, grub_cpu_to_be16_compile_time (GRUB_NET_DHCP6_OPTION_BOOTFILE_URL));
63f1a9
+  grub_set_unaligned16 (opt->data + 2, grub_cpu_to_be16_compile_time (GRUB_NET_DHCP6_OPTION_DNS_SERVERS));
63f1a9
+
63f1a9
+  err = grub_netbuff_push (nb, sizeof (*opt) + sizeof (grub_uint16_t));
63f1a9
+  if (err)
63f1a9
+    {
63f1a9
+      grub_netbuff_free (nb);
63f1a9
+      return err;
63f1a9
+    }
63f1a9
+  opt = (struct grub_net_dhcp6_option*) nb->data;
63f1a9
+  opt->code = grub_cpu_to_be16_compile_time (GRUB_NET_DHCP6_OPTION_ELAPSED_TIME);
63f1a9
+  opt->len = grub_cpu_to_be16_compile_time (sizeof (grub_uint16_t));
63f1a9
+
63f1a9
+  /* the time is expressed in hundredths of a second */
63f1a9
+  elapsed = grub_divmod64 (grub_get_time_ms () - se->start_time, 10, 0);
63f1a9
+
63f1a9
+  if (elapsed > 0xffff)
63f1a9
+    elapsed = 0xffff;
63f1a9
+
63f1a9
+  grub_set_unaligned16 (opt->data,  grub_cpu_to_be16 ((grub_uint16_t)elapsed));
63f1a9
+
63f1a9
+  err = grub_netbuff_push (nb, sizeof (*v6h));
63f1a9
+  if (err)
63f1a9
+    {
63f1a9
+      grub_netbuff_free (nb);
63f1a9
+      return err;
63f1a9
+    }
63f1a9
+
63f1a9
+  v6h = (struct grub_net_dhcp6_packet *) nb->data;
63f1a9
+  v6h->message_type = GRUB_NET_DHCP6_REQUEST;
63f1a9
+  v6h->transaction_id = se->transaction_id;
63f1a9
+
63f1a9
+  err = grub_netbuff_push (nb, sizeof (*udph));
63f1a9
+  if (err)
63f1a9
+    {
63f1a9
+      grub_netbuff_free (nb);
63f1a9
+      return err;
63f1a9
+    }
63f1a9
+
63f1a9
+  udph = (struct udphdr *) nb->data;
63f1a9
+  udph->src = grub_cpu_to_be16_compile_time (DHCP6_CLIENT_PORT);
63f1a9
+  udph->dst = grub_cpu_to_be16_compile_time (DHCP6_SERVER_PORT);
63f1a9
+  udph->chksum = 0;
63f1a9
+  udph->len = grub_cpu_to_be16 (nb->tail - nb->data);
63f1a9
+
63f1a9
+  udph->chksum = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_UDP,
63f1a9
+						 &inf->address,
63f1a9
+						 &multicast);
63f1a9
+  err = grub_net_send_ip_packet (inf, &multicast, &ll_multicast, nb,
63f1a9
+				 GRUB_NET_IP_UDP);
63f1a9
+
63f1a9
+  grub_netbuff_free (nb);
63f1a9
+
63f1a9
+  return err;
63f1a9
+}
63f1a9
+
63f1a9
+struct grub_net_network_level_interface *
63f1a9
+grub_net_configure_by_dhcpv6_reply (const char *name,
63f1a9
+	struct grub_net_card *card,
63f1a9
+	grub_net_interface_flags_t flags,
63f1a9
+	const struct grub_net_dhcp6_packet *v6h,
63f1a9
+	grub_size_t size,
63f1a9
+	int is_def,
63f1a9
+	char **device, char **path)
63f1a9
+{
63f1a9
+  struct grub_net_network_level_interface *inf;
63f1a9
+  grub_dhcp6_options_t dhcp6;
63f1a9
+
63f1a9
+  dhcp6 = grub_dhcp6_options_get (v6h, size);
63f1a9
+  if (!dhcp6)
63f1a9
+    {
63f1a9
+      grub_print_error ();
63f1a9
+      return NULL;
63f1a9
+    }
63f1a9
+
63f1a9
+  grub_net_configure_by_dhcp6_info (name, card, dhcp6, is_def, flags, &inf);
63f1a9
+
63f1a9
+  if (device && dhcp6->boot_file_proto && dhcp6->boot_file_server_ip)
63f1a9
+    {
63f1a9
+      *device = grub_xasprintf ("%s,%s", dhcp6->boot_file_proto, dhcp6->boot_file_server_ip);
63f1a9
+      grub_print_error ();
63f1a9
+    }
63f1a9
+  if (path && dhcp6->boot_file_path)
63f1a9
+    {
63f1a9
+      *path = grub_strdup (dhcp6->boot_file_path);
63f1a9
+      grub_print_error ();
63f1a9
+      if (*path)
63f1a9
+	{
63f1a9
+	  char *slash;
63f1a9
+	  slash = grub_strrchr (*path, '/');
63f1a9
+	  if (slash)
63f1a9
+	    *slash = 0;
63f1a9
+	  else
63f1a9
+	    **path = 0;
63f1a9
+	}
63f1a9
+    }
63f1a9
+
63f1a9
+  grub_dhcp6_options_free (dhcp6);
63f1a9
+  return inf;
63f1a9
+}
63f1a9
 
63f1a9
 void
63f1a9
 grub_net_process_dhcp (struct grub_net_buff *nb,
63f1a9
@@ -555,6 +1046,77 @@ grub_net_process_dhcp (struct grub_net_buff *nb,
63f1a9
     }
63f1a9
 }
63f1a9
 
63f1a9
+grub_err_t
63f1a9
+grub_net_process_dhcp6 (struct grub_net_buff *nb,
63f1a9
+			struct grub_net_card *card __attribute__ ((unused)))
63f1a9
+{
63f1a9
+  const struct grub_net_dhcp6_packet *v6h;
63f1a9
+  grub_dhcp6_session_t se;
63f1a9
+  grub_size_t size;
63f1a9
+  grub_dhcp6_options_t options;
63f1a9
+
63f1a9
+  v6h = (const struct grub_net_dhcp6_packet *) nb->data;
63f1a9
+  size = nb->tail - nb->data;
63f1a9
+
63f1a9
+  options = grub_dhcp6_options_get (v6h, size);
63f1a9
+  if (!options)
63f1a9
+    return grub_errno;
63f1a9
+
63f1a9
+  if (!options->client_duid || !options->server_duid || !options->ia_addr)
63f1a9
+    {
63f1a9
+      grub_dhcp6_options_free (options);
63f1a9
+      return grub_error (GRUB_ERR_BAD_ARGUMENT, "Bad DHCPv6 Packet");
63f1a9
+    }
63f1a9
+
63f1a9
+  FOR_DHCP6_SESSIONS (se)
63f1a9
+    {
63f1a9
+      if (se->transaction_id == v6h->transaction_id &&
63f1a9
+	  grub_memcmp (options->client_duid, &se->duid, sizeof (se->duid)) == 0 &&
63f1a9
+	  se->iaid == options->iaid)
63f1a9
+	break;
63f1a9
+    }
63f1a9
+
63f1a9
+  if (!se)
63f1a9
+    {
63f1a9
+      grub_dprintf ("bootp", "DHCPv6 session not found\n");
63f1a9
+      grub_dhcp6_options_free (options);
63f1a9
+      return GRUB_ERR_NONE;
63f1a9
+    }
63f1a9
+
63f1a9
+  if (v6h->message_type == GRUB_NET_DHCP6_ADVERTISE)
63f1a9
+    {
63f1a9
+      if (se->adv)
63f1a9
+	{
63f1a9
+	  grub_dprintf ("bootp", "Skipped DHCPv6 Advertised .. \n");
63f1a9
+	  grub_dhcp6_options_free (options);
63f1a9
+	  return GRUB_ERR_NONE;
63f1a9
+	}
63f1a9
+
63f1a9
+      se->adv = options;
63f1a9
+      return grub_dhcp6_session_send_request (se);
63f1a9
+    }
63f1a9
+  else if (v6h->message_type == GRUB_NET_DHCP6_REPLY)
63f1a9
+    {
63f1a9
+      if (!se->adv)
63f1a9
+	{
63f1a9
+	  grub_dprintf ("bootp", "Skipped DHCPv6 Reply .. \n");
63f1a9
+	  grub_dhcp6_options_free (options);
63f1a9
+	  return GRUB_ERR_NONE;
63f1a9
+	}
63f1a9
+
63f1a9
+      se->reply = options;
63f1a9
+      grub_dhcp6_session_configure_network (se);
63f1a9
+      grub_dhcp6_session_remove (se);
63f1a9
+      return GRUB_ERR_NONE;
63f1a9
+    }
63f1a9
+  else
63f1a9
+    {
63f1a9
+      grub_dhcp6_options_free (options);
63f1a9
+    }
63f1a9
+
63f1a9
+  return GRUB_ERR_NONE;
63f1a9
+}
63f1a9
+
63f1a9
 static grub_err_t
63f1a9
 grub_cmd_dhcpopt (struct grub_command *cmd __attribute__ ((unused)),
63f1a9
 		  int argc, char **args)
63f1a9
@@ -827,7 +1389,174 @@ grub_cmd_bootp (struct grub_command *cmd __attribute__ ((unused)),
63f1a9
   return err;
63f1a9
 }
63f1a9
 
63f1a9
-static grub_command_t cmd_getdhcp, cmd_bootp;
63f1a9
+static grub_err_t
63f1a9
+grub_cmd_bootp6 (struct grub_command *cmd __attribute__ ((unused)),
63f1a9
+		  int argc, char **args)
63f1a9
+{
63f1a9
+  struct grub_net_card *card;
63f1a9
+  grub_uint32_t iaid = 0;
63f1a9
+  int interval;
63f1a9
+  grub_err_t err;
63f1a9
+  grub_dhcp6_session_t se;
63f1a9
+
63f1a9
+  err = GRUB_ERR_NONE;
63f1a9
+
63f1a9
+  FOR_NET_CARDS (card)
63f1a9
+  {
63f1a9
+    struct grub_net_network_level_interface *iface;
63f1a9
+
63f1a9
+    if (argc > 0 && grub_strcmp (card->name, args[0]) != 0)
63f1a9
+      continue;
63f1a9
+
63f1a9
+    iface = grub_net_ipv6_get_link_local (card, &card->default_address);
63f1a9
+    if (!iface)
63f1a9
+      {
63f1a9
+	grub_dhcp6_session_remove_all ();
63f1a9
+	return grub_errno;
63f1a9
+      }
63f1a9
+
63f1a9
+    grub_dhcp6_session_add (iface, iaid++);
63f1a9
+  }
63f1a9
+
63f1a9
+  for (interval = 200; interval < 10000; interval *= 2)
63f1a9
+    {
63f1a9
+      int done = 1;
63f1a9
+
63f1a9
+      FOR_DHCP6_SESSIONS (se)
63f1a9
+	{
63f1a9
+	  struct grub_net_buff *nb;
63f1a9
+	  struct grub_net_dhcp6_option *opt;
63f1a9
+	  struct grub_net_dhcp6_packet *v6h;
63f1a9
+	  struct grub_net_dhcp6_option_duid_ll *duid;
63f1a9
+	  struct grub_net_dhcp6_option_iana *ia_na;
63f1a9
+	  grub_net_network_level_address_t multicast;
63f1a9
+	  grub_net_link_level_address_t ll_multicast;
63f1a9
+	  struct udphdr *udph;
63f1a9
+
63f1a9
+	  multicast.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6;
63f1a9
+	  multicast.ipv6[0] = grub_cpu_to_be64_compile_time (0xff02ULL << 48);
63f1a9
+	  multicast.ipv6[1] = grub_cpu_to_be64_compile_time (0x10002ULL);
63f1a9
+
63f1a9
+	  err = grub_net_link_layer_resolve (se->iface,
63f1a9
+		    &multicast, &ll_multicast);
63f1a9
+	  if (err)
63f1a9
+	    {
63f1a9
+	      grub_dhcp6_session_remove_all ();
63f1a9
+	      return err;
63f1a9
+	    }
63f1a9
+
63f1a9
+	  nb = grub_netbuff_alloc (GRUB_DHCP6_DEFAULT_NETBUFF_ALLOC_SIZE);
63f1a9
+
63f1a9
+	  if (!nb)
63f1a9
+	    {
63f1a9
+	      grub_dhcp6_session_remove_all ();
63f1a9
+	      return grub_errno;
63f1a9
+	    }
63f1a9
+
63f1a9
+	  err = grub_netbuff_reserve (nb, GRUB_DHCP6_DEFAULT_NETBUFF_ALLOC_SIZE);
63f1a9
+	  if (err)
63f1a9
+	    {
63f1a9
+	      grub_dhcp6_session_remove_all ();
63f1a9
+	      grub_netbuff_free (nb);
63f1a9
+	      return err;
63f1a9
+	    }
63f1a9
+
63f1a9
+	  err = grub_netbuff_push (nb, sizeof (*opt) + sizeof (grub_uint16_t));
63f1a9
+	  if (err)
63f1a9
+	    {
63f1a9
+	      grub_dhcp6_session_remove_all ();
63f1a9
+	      grub_netbuff_free (nb);
63f1a9
+	      return err;
63f1a9
+	    }
63f1a9
+
63f1a9
+	  opt = (struct grub_net_dhcp6_option *)nb->data;
63f1a9
+	  opt->code = grub_cpu_to_be16_compile_time (GRUB_NET_DHCP6_OPTION_ELAPSED_TIME);
63f1a9
+	  opt->len = grub_cpu_to_be16_compile_time (sizeof (grub_uint16_t));
63f1a9
+	  grub_set_unaligned16 (opt->data, 0);
63f1a9
+
63f1a9
+	  err = grub_netbuff_push (nb, sizeof (*opt) + sizeof (*duid));
63f1a9
+	  if (err)
63f1a9
+	    {
63f1a9
+	      grub_dhcp6_session_remove_all ();
63f1a9
+	      grub_netbuff_free (nb);
63f1a9
+	      return err;
63f1a9
+	    }
63f1a9
+
63f1a9
+	  opt = (struct grub_net_dhcp6_option *)nb->data;
63f1a9
+	  opt->code = grub_cpu_to_be16_compile_time (GRUB_NET_DHCP6_OPTION_CLIENTID);
63f1a9
+	  opt->len = grub_cpu_to_be16 (sizeof (*duid));
63f1a9
+
63f1a9
+	  duid = (struct grub_net_dhcp6_option_duid_ll *) opt->data;
63f1a9
+	  grub_memcpy (duid, &se->duid, sizeof (*duid));
63f1a9
+
63f1a9
+	  err = grub_netbuff_push (nb, sizeof (*opt) + sizeof (*ia_na));
63f1a9
+	  if (err)
63f1a9
+	    {
63f1a9
+	      grub_dhcp6_session_remove_all ();
63f1a9
+	      grub_netbuff_free (nb);
63f1a9
+	      return err;
63f1a9
+	    }
63f1a9
+
63f1a9
+	  opt = (struct grub_net_dhcp6_option *)nb->data;
63f1a9
+	  opt->code = grub_cpu_to_be16_compile_time (GRUB_NET_DHCP6_OPTION_IA_NA);
63f1a9
+	  opt->len = grub_cpu_to_be16 (sizeof (*ia_na));
63f1a9
+	  ia_na = (struct grub_net_dhcp6_option_iana *)opt->data;
63f1a9
+	  ia_na->iaid = grub_cpu_to_be32 (se->iaid);
63f1a9
+	  ia_na->t1 = 0;
63f1a9
+	  ia_na->t2 = 0;
63f1a9
+
63f1a9
+	  err = grub_netbuff_push (nb, sizeof (*v6h));
63f1a9
+	  if (err)
63f1a9
+	    {
63f1a9
+	      grub_dhcp6_session_remove_all ();
63f1a9
+	      grub_netbuff_free (nb);
63f1a9
+	      return err;
63f1a9
+	    }
63f1a9
+
63f1a9
+	  v6h = (struct grub_net_dhcp6_packet *)nb->data;
63f1a9
+	  v6h->message_type = GRUB_NET_DHCP6_SOLICIT;
63f1a9
+	  v6h->transaction_id = se->transaction_id;
63f1a9
+
63f1a9
+	  grub_netbuff_push (nb, sizeof (*udph));
63f1a9
+
63f1a9
+	  udph = (struct udphdr *) nb->data;
63f1a9
+	  udph->src = grub_cpu_to_be16_compile_time (DHCP6_CLIENT_PORT);
63f1a9
+	  udph->dst = grub_cpu_to_be16_compile_time (DHCP6_SERVER_PORT);
63f1a9
+	  udph->chksum = 0;
63f1a9
+	  udph->len = grub_cpu_to_be16 (nb->tail - nb->data);
63f1a9
+
63f1a9
+	  udph->chksum = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_UDP,
63f1a9
+			    &se->iface->address, &multicast);
63f1a9
+
63f1a9
+	  err = grub_net_send_ip_packet (se->iface, &multicast,
63f1a9
+		    &ll_multicast, nb, GRUB_NET_IP_UDP);
63f1a9
+	  done = 0;
63f1a9
+	  grub_netbuff_free (nb);
63f1a9
+
63f1a9
+	  if (err)
63f1a9
+	    {
63f1a9
+	      grub_dhcp6_session_remove_all ();
63f1a9
+	      return err;
63f1a9
+	    }
63f1a9
+	}
63f1a9
+      if (!done)
63f1a9
+	grub_net_poll_cards (interval, 0);
63f1a9
+    }
63f1a9
+
63f1a9
+  FOR_DHCP6_SESSIONS (se)
63f1a9
+    {
63f1a9
+      grub_error_push ();
63f1a9
+      err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
63f1a9
+			N_("couldn't autoconfigure %s"),
63f1a9
+			se->iface->card->name);
63f1a9
+    }
63f1a9
+
63f1a9
+  grub_dhcp6_session_remove_all ();
63f1a9
+
63f1a9
+  return err;
63f1a9
+}
63f1a9
+
63f1a9
+static grub_command_t cmd_getdhcp, cmd_bootp, cmd_bootp6;
63f1a9
 
63f1a9
 void
63f1a9
 grub_bootp_init (void)
63f1a9
@@ -838,6 +1567,9 @@ grub_bootp_init (void)
63f1a9
   cmd_getdhcp = grub_register_command ("net_get_dhcp_option", grub_cmd_dhcpopt,
63f1a9
 				       N_("VAR INTERFACE NUMBER DESCRIPTION"),
63f1a9
 				       N_("retrieve DHCP option and save it into VAR. If VAR is - then print the value."));
63f1a9
+  cmd_bootp6 = grub_register_command ("net_bootp6", grub_cmd_bootp6,
63f1a9
+				     N_("[CARD]"),
63f1a9
+				     N_("perform a DHCPv6 autoconfiguration"));
63f1a9
 }
63f1a9
 
63f1a9
 void
63f1a9
@@ -845,4 +1577,5 @@ grub_bootp_fini (void)
63f1a9
 {
63f1a9
   grub_unregister_command (cmd_getdhcp);
63f1a9
   grub_unregister_command (cmd_bootp);
63f1a9
+  grub_unregister_command (cmd_bootp6);
63f1a9
 }
63f1a9
diff --git a/grub-core/net/drivers/efi/efinet.c b/grub-core/net/drivers/efi/efinet.c
63f1a9
index 329024b6f2c..6cc139fb8fc 100644
63f1a9
--- a/grub-core/net/drivers/efi/efinet.c
63f1a9
+++ b/grub-core/net/drivers/efi/efinet.c
63f1a9
@@ -473,9 +473,6 @@ grub_efi_net_config_real (grub_efi_handle_t hnd, char **device,
63f1a9
     pxe_mode = pxe->mode;
63f1a9
     if (pxe_mode->using_ipv6)
63f1a9
       {
63f1a9
-	grub_net_link_level_address_t hwaddr;
63f1a9
-	struct grub_net_network_level_interface *intf;
63f1a9
-
63f1a9
 	grub_dprintf ("efinet", "using ipv6 and dhcpv6\n");
63f1a9
 	grub_dprintf ("efinet", "dhcp_ack_received: %s%s\n",
63f1a9
 		      pxe_mode->dhcp_ack_received ? "yes" : "no",
63f1a9
@@ -483,15 +480,14 @@ grub_efi_net_config_real (grub_efi_handle_t hnd, char **device,
63f1a9
 	if (!pxe_mode->dhcp_ack_received)
63f1a9
 	  continue;
63f1a9
 
63f1a9
-	hwaddr.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
63f1a9
-	grub_memcpy (hwaddr.mac,
63f1a9
-		     card->efi_net->mode->current_address,
63f1a9
-		     sizeof (hwaddr.mac));
63f1a9
-
63f1a9
-	intf = grub_net_configure_by_dhcpv6_ack (card->name, card, 0, &hwaddr,
63f1a9
-	      (const struct grub_net_dhcpv6_packet *)&pxe_mode->dhcp_ack.dhcpv6,
63f1a9
-	      1, device, path);
63f1a9
-	if (intf && device && path)
63f1a9
+	grub_net_configure_by_dhcpv6_reply (card->name, card, 0,
63f1a9
+					    (struct grub_net_dhcp6_packet *)
63f1a9
+					    &pxe_mode->dhcp_ack,
63f1a9
+					    sizeof (pxe_mode->dhcp_ack),
63f1a9
+					    1, device, path);
63f1a9
+	if (grub_errno)
63f1a9
+	  grub_print_error ();
63f1a9
+	if (device && path)
63f1a9
 	  grub_dprintf ("efinet", "device: `%s' path: `%s'\n", *device, *path);
63f1a9
       }
63f1a9
     else
63f1a9
diff --git a/grub-core/net/ip.c b/grub-core/net/ip.c
63f1a9
index 7c95cc7464a..fd641b2410d 100644
63f1a9
--- a/grub-core/net/ip.c
63f1a9
+++ b/grub-core/net/ip.c
63f1a9
@@ -239,6 +239,45 @@ handle_dgram (struct grub_net_buff *nb,
63f1a9
   {
63f1a9
     struct udphdr *udph;
63f1a9
     udph = (struct udphdr *) nb->data;
63f1a9
+
63f1a9
+    if (proto == GRUB_NET_IP_UDP && udph->dst == grub_cpu_to_be16_compile_time (DHCP6_CLIENT_PORT))
63f1a9
+      {
63f1a9
+	if (udph->chksum)
63f1a9
+	  {
63f1a9
+	    grub_uint16_t chk, expected;
63f1a9
+	    chk = udph->chksum;
63f1a9
+	    udph->chksum = 0;
63f1a9
+	    expected = grub_net_ip_transport_checksum (nb,
63f1a9
+						       GRUB_NET_IP_UDP,
63f1a9
+						       source,
63f1a9
+						       dest);
63f1a9
+	    if (expected != chk)
63f1a9
+	      {
63f1a9
+		grub_dprintf ("net", "Invalid UDP checksum. "
63f1a9
+			      "Expected %x, got %x\n",
63f1a9
+			      grub_be_to_cpu16 (expected),
63f1a9
+			      grub_be_to_cpu16 (chk));
63f1a9
+		grub_netbuff_free (nb);
63f1a9
+		return GRUB_ERR_NONE;
63f1a9
+	      }
63f1a9
+	    udph->chksum = chk;
63f1a9
+	  }
63f1a9
+
63f1a9
+	err = grub_netbuff_pull (nb, sizeof (*udph));
63f1a9
+	if (err)
63f1a9
+	  {
63f1a9
+	    grub_netbuff_free (nb);
63f1a9
+	    return err;
63f1a9
+	  }
63f1a9
+
63f1a9
+	err = grub_net_process_dhcp6 (nb, card);
63f1a9
+	if (err)
63f1a9
+	  grub_print_error ();
63f1a9
+
63f1a9
+	grub_netbuff_free (nb);
63f1a9
+	return GRUB_ERR_NONE;
63f1a9
+      }
63f1a9
+
63f1a9
     if (proto == GRUB_NET_IP_UDP && grub_be_to_cpu16 (udph->dst) == 68)
63f1a9
       {
63f1a9
 	const struct grub_net_bootp_packet *bootp;
63f1a9
diff --git a/include/grub/efi/api.h b/include/grub/efi/api.h
63f1a9
index ddc5ecfb03d..6a545cc6d5d 100644
63f1a9
--- a/include/grub/efi/api.h
63f1a9
+++ b/include/grub/efi/api.h
63f1a9
@@ -1511,7 +1511,7 @@ typedef struct grub_efi_pxe_ip_filter
63f1a9
 {
63f1a9
   grub_efi_uint8_t filters;
63f1a9
   grub_efi_uint8_t ip_count;
63f1a9
-  grub_efi_uint8_t reserved;
63f1a9
+  grub_efi_uint16_t reserved;
63f1a9
   grub_efi_ip_address_t ip_list[GRUB_EFI_PXE_MAX_IPCNT];
63f1a9
 } grub_efi_pxe_ip_filter_t;
63f1a9
 
63f1a9
diff --git a/include/grub/net.h b/include/grub/net.h
63f1a9
index 24232ec71fc..373ad1080cf 100644
63f1a9
--- a/include/grub/net.h
63f1a9
+++ b/include/grub/net.h
63f1a9
@@ -445,46 +445,62 @@ struct grub_net_bootp_packet
63f1a9
 
63f1a9
 enum
63f1a9
   {
63f1a9
-    GRUB_NET_DHCP6_IA_NA = 3,
63f1a9
-    GRUB_NET_DHCP6_IA_ADDRESS = 5,
63f1a9
-    GRUB_NET_DHCP6_BOOTFILE_URL = 59,
63f1a9
+    GRUB_NET_DHCP6_OPTION_CLIENTID = 1,
63f1a9
+    GRUB_NET_DHCP6_OPTION_SERVERID = 2,
63f1a9
+    GRUB_NET_DHCP6_OPTION_IA_NA = 3,
63f1a9
+    GRUB_NET_DHCP6_OPTION_IAADDR = 5,
63f1a9
+    GRUB_NET_DHCP6_OPTION_ORO = 6,
63f1a9
+    GRUB_NET_DHCP6_OPTION_ELAPSED_TIME = 8,
63f1a9
+    GRUB_NET_DHCP6_OPTION_DNS_SERVERS = 23,
63f1a9
+    GRUB_NET_DHCP6_OPTION_BOOTFILE_URL = 59
63f1a9
   };
63f1a9
 
63f1a9
-struct grub_net_dhcpv6_option
63f1a9
-{
63f1a9
-  grub_uint16_t option_num;
63f1a9
-  grub_uint16_t option_len;
63f1a9
-  grub_uint8_t option_data[];
63f1a9
+struct grub_net_dhcp6_option {
63f1a9
+  grub_uint16_t code;
63f1a9
+  grub_uint16_t len;
63f1a9
+  grub_uint8_t data[0];
63f1a9
 } GRUB_PACKED;
63f1a9
-typedef struct grub_net_dhcpv6_option grub_net_dhcpv6_option_t;
63f1a9
 
63f1a9
-struct grub_net_dhcpv6_opt_ia_na
63f1a9
-{
63f1a9
-  grub_uint16_t option_num;
63f1a9
-  grub_uint16_t option_len;
63f1a9
+struct grub_net_dhcp6_option_iana {
63f1a9
   grub_uint32_t iaid;
63f1a9
   grub_uint32_t t1;
63f1a9
   grub_uint32_t t2;
63f1a9
-  grub_uint8_t options[];
63f1a9
+  grub_uint8_t data[0];
63f1a9
 } GRUB_PACKED;
63f1a9
-typedef struct grub_net_dhcpv6_opt_ia_na grub_net_dhcpv6_opt_ia_na_t;
63f1a9
 
63f1a9
-struct grub_net_dhcpv6_opt_ia_address
63f1a9
-{
63f1a9
-  grub_uint16_t option_num;
63f1a9
-  grub_uint16_t option_len;
63f1a9
-  grub_uint64_t ipv6_address[2];
63f1a9
+struct grub_net_dhcp6_option_iaaddr {
63f1a9
+  grub_uint8_t addr[16];
63f1a9
   grub_uint32_t preferred_lifetime;
63f1a9
   grub_uint32_t valid_lifetime;
63f1a9
-  grub_uint8_t options[];
63f1a9
+  grub_uint8_t data[0];
63f1a9
 } GRUB_PACKED;
63f1a9
-typedef struct grub_net_dhcpv6_opt_ia_address grub_net_dhcpv6_opt_ia_address_t;
63f1a9
 
63f1a9
-struct grub_net_dhcpv6_packet
63f1a9
+struct grub_net_dhcp6_option_duid_ll
63f1a9
+{
63f1a9
+  grub_uint16_t type;
63f1a9
+  grub_uint16_t hw_type;
63f1a9
+  grub_uint8_t hwaddr[6];
63f1a9
+} GRUB_PACKED;
63f1a9
+
63f1a9
+enum
63f1a9
+  {
63f1a9
+    GRUB_NET_DHCP6_SOLICIT = 1,
63f1a9
+    GRUB_NET_DHCP6_ADVERTISE = 2,
63f1a9
+    GRUB_NET_DHCP6_REQUEST = 3,
63f1a9
+    GRUB_NET_DHCP6_REPLY = 7
63f1a9
+  };
63f1a9
+
63f1a9
+enum
63f1a9
+  {
63f1a9
+    DHCP6_CLIENT_PORT = 546,
63f1a9
+    DHCP6_SERVER_PORT = 547
63f1a9
+  };
63f1a9
+
63f1a9
+struct grub_net_dhcp6_packet
63f1a9
 {
63f1a9
   grub_uint32_t message_type:8;
63f1a9
   grub_uint32_t transaction_id:24;
63f1a9
-  grub_uint8_t dhcp_options[1024];
63f1a9
+  grub_uint8_t dhcp_options[0];
63f1a9
 } GRUB_PACKED;
63f1a9
 typedef struct grub_net_dhcpv6_packet grub_net_dhcpv6_packet_t;
63f1a9
 
63f1a9
@@ -517,12 +533,12 @@ grub_net_configure_by_dhcp_ack (const char *name,
63f1a9
 				int is_def, char **device, char **path);
63f1a9
 
63f1a9
 struct grub_net_network_level_interface *
63f1a9
-grub_net_configure_by_dhcpv6_ack (const char *name,
63f1a9
-				 struct grub_net_card *card,
63f1a9
-				 grub_net_interface_flags_t flags,
63f1a9
-				 const grub_net_link_level_address_t *hwaddr,
63f1a9
-				 const struct grub_net_dhcpv6_packet *packet,
63f1a9
-				 int is_def, char **device, char **path);
63f1a9
+grub_net_configure_by_dhcpv6_reply (const char *name,
63f1a9
+				    struct grub_net_card *card,
63f1a9
+				    grub_net_interface_flags_t flags,
63f1a9
+				    const struct grub_net_dhcp6_packet *v6,
63f1a9
+				    grub_size_t size,
63f1a9
+				    int is_def, char **device, char **path);
63f1a9
 
63f1a9
 int
63f1a9
 grub_ipv6_get_masksize(grub_uint16_t *mask);
63f1a9
@@ -539,6 +555,10 @@ void
63f1a9
 grub_net_process_dhcp (struct grub_net_buff *nb,
63f1a9
 		       struct grub_net_card *card);
63f1a9
 
63f1a9
+grub_err_t
63f1a9
+grub_net_process_dhcp6 (struct grub_net_buff *nb,
63f1a9
+			struct grub_net_card *card);
63f1a9
+
63f1a9
 int
63f1a9
 grub_net_hwaddr_cmp (const grub_net_link_level_address_t *a,
63f1a9
 		     const grub_net_link_level_address_t *b);