grub-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] bootp: add native DHCPv4 support


From: Andrei Borzenkov
Subject: [PATCH] bootp: add native DHCPv4 support
Date: Sun, 20 Mar 2016 10:29:43 +0300

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

There is no provision for selecting preferred server. We take the first
OFFER and try to REQUEST configuration from it. If NAK was received,
transaction is restarted, but if we hit timeout, configuration fails.

It also handles pure BOOTP reply (detected by lack of DHCP message type
option) and proceeds with configuration immedately. I could not test it
because I do not have pure BOOTP server.

Because we need access to DHCP options in multiple places now, it also
factors out DHCP option processing, adding support for option overload.

Timeout handling is now per-interface, with independent timeouts for
both DISCOVER and REQUEST. It should make it more responsive if answer
was delayed initially. Total timeout remains the same as originally, as
well as backoff algorithm, but we poll in fixed 200ms ticks so notice
(successful) reply earlier.

Failure to send packet to interface now does not terminate command (and
leaks memory) but rather simply ignores this interface and continues with
remaining ones if present.

Finally it adds net_dhcp alias with intention to deprecate net_bootp
completely (it would be possible to make net_bootp to actually send BOOTP
packet if someone thinks it is required).

Features not implements:

- 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 and it rather complicates implementation, so let's wait for actual
use case.

- client identifier (RFC6842). So far we expect valid MAC address, which
should be enough to uniquely identify client. It is also not clear how to
generate unique client identifier if we ever need one.

---
 grub-core/net/bootp.c | 747 ++++++++++++++++++++++++++++++++++----------------
 grub-core/net/ip.c    |   2 +-
 include/grub/net.h    |  14 +-
 3 files changed, 520 insertions(+), 243 deletions(-)

diff --git a/grub-core/net/bootp.c b/grub-core/net/bootp.c
index 09c6f76..2fd8b2b 100644
--- a/grub-core/net/bootp.c
+++ b/grub-core/net/bootp.c
@@ -25,20 +25,98 @@
 #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,
+  };
+
+enum
+  {
+    GRUB_DHCP_OPT_OVERLOAD_FILE = 1,
+    GRUB_DHCP_OPT_OVERLOAD_SNAME = 2,
+  };
+
+#define GRUB_BOOTP_MAX_OPTIONS_SIZE 64
+#define OFFSET_OF(x, y) ((grub_size_t)((grub_uint8_t *)((y)->x) - 
(grub_uint8_t *)(y)))
+
+/* Max timeout when waiting for BOOTP/DHCP reply */
+#define GRUB_DHCP_MAX_PACKET_TIMEOUT 32
+
+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 */
+  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 +129,76 @@ 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, NULL);
-             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.  */
+      /* FIXME RFC 3396 options concatentation */
+      if (tagtype == opt_code)
+       {
+         if (opt_len)
+           *opt_len = taglength;
+         return ptr;
        }
 
+      if (tagtype == GRUB_NET_DHCP_OVERLOAD && taglength == 1)
+       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 & GRUB_DHCP_OPT_OVERLOAD_FILE)
+         {
+           overload &= ~GRUB_DHCP_OPT_OVERLOAD_FILE;
+           ptr = ptr0 = (grub_uint8_t *) &bp->boot_file[0];
+           size = sizeof (bp->boot_file);
+           goto again;
+         }
+
+       if (overload & GRUB_DHCP_OPT_OVERLOAD_SNAME)
+         {
+           overload &= ~GRUB_DHCP_OPT_OVERLOAD_SNAME;
+           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 +212,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, overload = 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 +231,37 @@ 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_OVERLOAD, &opt_len);
+  if (opt && opt_len == 1)
+    overload = *opt;
+
+  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) && !(overload & 
GRUB_DHCP_OPT_OVERLOAD_SNAME) &&
+          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) && !(overload && 
GRUB_DHCP_OPT_OVERLOAD_FILE) &&
+          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 +289,103 @@ 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));
+      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, 0);
+      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,35 +399,208 @@ 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));
+      /* my_ip and srv_id are stored in network order so do not need 
conversion. */
+      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.
+     xid and srv_id are stored in network order so do not need conversion. */
+  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
+       grub_net_network_level_interface_unregister (iface);
     }
-  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 && srv_id)
     {
-      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;
+      /* Reset retransmission timer */
+      iface->dhcp_tmo = iface->dhcp_tmo_left = 1;
+    }
+  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;
+      /* Reset retransmission timer */
+      iface->dhcp_tmo = iface->dhcp_tmo_left = 1;
     }
 }
 
@@ -308,8 +617,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 +636,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++;
-
-      /* Pad tag.  */
-      if (tagtype == 0)
-       continue;
+  num = grub_strtoul (args[2], 0, 0);
+  if (grub_errno)
+    return grub_errno;
 
-      /* End tag.  */
-      if (tagtype == 0xff)
-       return grub_error (GRUB_ERR_IO, N_("no DHCP option %d found"), num);
+  /* 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"));
 
-      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)
     {
@@ -434,8 +726,8 @@ grub_cmd_bootp (struct grub_command *cmd __attribute__ 
((unused)),
   struct grub_net_network_level_interface *ifaces;
   grub_size_t ncards = 0;
   unsigned j = 0;
-  int interval;
   grub_err_t err;
+  unsigned i;
 
   FOR_NET_CARDS (card)
   {
@@ -464,7 +756,6 @@ grub_cmd_bootp (struct grub_command *cmd __attribute__ 
((unused)),
     card->num_ifaces++;
     if (!ifaces[j].name)
       {
-       unsigned i;
        for (i = 0; i < j; i++)
          grub_free (ifaces[i].name);
        grub_free (ifaces);
@@ -473,6 +764,7 @@ grub_cmd_bootp (struct grub_command *cmd __attribute__ 
((unused)),
     ifaces[j].address.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV;
     grub_memcpy (&ifaces[j].hwaddress, &card->default_address, 
                 sizeof (ifaces[j].hwaddress));
+    ifaces[j].dhcp_tmo = ifaces[j].dhcp_tmo_left = 1;
     j++;
   }
   ifaces[ncards - 1].next = grub_net_network_level_interfaces;
@@ -480,85 +772,54 @@ grub_cmd_bootp (struct grub_command *cmd __attribute__ 
((unused)),
     grub_net_network_level_interfaces->prev = & ifaces[ncards - 1].next;
   grub_net_network_level_interfaces = &ifaces[0];
   ifaces[0].prev = &grub_net_network_level_interfaces;
-  for (interval = 200; interval < 10000; interval *= 2)
+
+  /* Running DHCP restransmission timer is kept per interface in dhcp_tmo_left.
+     When it runs off, dhcp_tmo is increased exponentionally and dhcp_tmo_left
+     initialized to it. Max value is 32 which gives approximately 12s total per
+     packet timeout assuming 200ms poll tick. Timeout is reset when DHCP OFFER
+     is received, so total timeout is 25s in the worst case.
+
+     DHCP NAK also resets timer and transaction starts again.
+     
+     Total wait time is limited to ~25s to prevent endless loop in case of
+     permanent NAK
+   */
+  for (i = 0; i < GRUB_DHCP_MAX_PACKET_TIMEOUT * 4; i++)
     {
-      int done = 0;
+      int need_poll = 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)
+         if (!ifaces[j].prev || ifaces[j].dhcp_tmo > 
GRUB_DHCP_MAX_PACKET_TIMEOUT)
            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))
+
+         if (--ifaces[j].dhcp_tmo_left)
            {
-             grub_errno = GRUB_ERR_NONE;
-             t = 0;
+             need_poll = 1;
+             continue;
            }
-         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);
+         ifaces[j].dhcp_tmo *= 2;
+         if (ifaces[j].dhcp_tmo > GRUB_DHCP_MAX_PACKET_TIMEOUT)
+           continue;
 
-         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;
+           {
+             grub_print_error ();
+             /* To ignore it during next poll */
+             ifaces[j].dhcp_tmo = GRUB_DHCP_MAX_PACKET_TIMEOUT + 1;
+             continue;
+           }
+         ifaces[j].dhcp_tmo_left = ifaces[j].dhcp_tmo;
+         need_poll = 1;
        }
-      if (!done)
+      if (!need_poll)
        break;
-      grub_net_poll_cards (interval, 0);
+      grub_net_poll_cards (200, 0);
     }
 
   err = GRUB_ERR_NONE;
+
   for (j = 0; j < ncards; j++)
     {
       grub_free (ifaces[j].name);
@@ -575,7 +836,7 @@ grub_cmd_bootp (struct grub_command *cmd __attribute__ 
((unused)),
   return err;
 }
 
-static grub_command_t cmd_getdhcp, cmd_bootp;
+static grub_command_t cmd_getdhcp, cmd_bootp, cmd_dhcp;
 
 void
 grub_bootp_init (void)
@@ -583,6 +844,9 @@ grub_bootp_init (void)
   cmd_bootp = grub_register_command ("net_bootp", grub_cmd_bootp,
                                     N_("[CARD]"),
                                     N_("perform a bootp autoconfiguration"));
+  cmd_dhcp = grub_register_command ("net_dhcp", grub_cmd_bootp,
+                                    N_("[CARD]"),
+                                    N_("perform a DHCP autoconfiguration"));
   cmd_getdhcp = grub_register_command ("net_get_dhcp_option", grub_cmd_dhcpopt,
                                       N_("VAR INTERFACE NUMBER DESCRIPTION"),
                                       N_("retrieve DHCP option and save it 
into VAR. If VAR is - then print the value."));
@@ -593,4 +857,5 @@ grub_bootp_fini (void)
 {
   grub_unregister_command (cmd_getdhcp);
   grub_unregister_command (cmd_bootp);
+  grub_unregister_command (cmd_dhcp);
 }
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 2192fa1..6d4a7e5 100644
--- a/include/grub/net.h
+++ b/include/grub/net.h
@@ -291,6 +291,11 @@ 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 transaction id */
+  grub_uint32_t srv_id;   /* DHCPv4 server_identifier */
+  grub_uint32_t my_ip;    /* DHCPv4 offered IP address */
+  unsigned dhcp_tmo_left; /* DHCPv4 running retransmission timeout */
+  unsigned dhcp_tmo;      /* DHCPv4 current retransmission timeout */
   void *data;
 };
 
@@ -456,6 +461,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
   };
 
@@ -473,7 +485,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,
-- 
tg: (5b8ddf6..) u/dhcpv4 (depends on: master)



reply via email to

[Prev in Thread] Current Thread [Next in Thread]