[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH v2 2/9] net: dhcp: replace parse_dhcp_vendor() with find_dhcp_opt
From: |
Andre Przywara |
Subject: |
[PATCH v2 2/9] net: dhcp: replace parse_dhcp_vendor() with find_dhcp_option() |
Date: |
Tue, 12 Feb 2019 17:46:53 +0000 |
From: Andrei Borzenkov <address@hidden>
For proper DHCP support we will need to parse DHCP options from a packet
more often and at various places.
Refactor the option parsing into a new function, which will scan a
packet to find *a particular* option field.
Use that new function in places where we were dealing with DHCP options
before.
Signed-off-by: Andre Przywara <address@hidden>
---
grub-core/net/bootp.c | 285 ++++++++++++++++++++++++++----------------
include/grub/net.h | 1 +
2 files changed, 176 insertions(+), 110 deletions(-)
diff --git a/grub-core/net/bootp.c b/grub-core/net/bootp.c
index c92dfbd3a..c11038483 100644
--- a/grub-core/net/bootp.c
+++ b/grub-core/net/bootp.c
@@ -25,25 +25,50 @@
#include <grub/net/udp.h>
#include <grub/datetime.h>
-static void
-parse_dhcp_vendor (const char *name, const void *vend, int limit, int *mask)
+enum
{
- const grub_uint8_t *ptr, *ptr0;
+ GRUB_DHCP_OPT_OVERLOAD_FILE = 1,
+ GRUB_DHCP_OPT_OVERLOAD_SNAME = 2,
+};
- ptr = ptr0 = vend;
+static const void *
+find_dhcp_option (const struct grub_net_bootp_packet *bp, grub_size_t size,
+ grub_uint8_t opt_code, grub_uint8_t *opt_len)
+{
+ const grub_uint8_t *ptr;
+ grub_uint8_t overload = 0;
+ int end = 0;
+ grub_size_t i;
+
+ if (opt_len)
+ *opt_len = 0;
+
+ /* Is the packet big enough to hold at least the magic cookie? */
+ if (size < sizeof (*bp) + sizeof (grub_uint32_t))
+ goto out;
+
+ /*
+ * Pointer arithmetic to point behind the common stub packet, where
+ * the options start.
+ */
+ ptr = (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;
- ptr = ptr + sizeof (grub_uint32_t);
- while (ptr - ptr0 < limit)
+ goto out;
+
+ size -= sizeof (*bp);
+ i = sizeof (grub_uint32_t);
+
+again:
+ while (i < size)
{
grub_uint8_t tagtype;
grub_uint8_t taglength;
- tagtype = *ptr++;
+ tagtype = ptr[i++];
/* Pad tag. */
if (tagtype == GRUB_NET_BOOTP_PAD)
@@ -51,81 +76,76 @@ parse_dhcp_vendor (const char *name, const void *vend, int
limit, int *mask)
/* End tag. */
if (tagtype == GRUB_NET_BOOTP_END)
- return;
-
- 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;
- }
+ end = 1;
break;
+ }
- 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. */
+ if (i >= size)
+ goto out;
+
+ taglength = ptr[i++];
+ if (i + taglength >= size)
+ goto out;
+
+ /* FIXME RFC 3396 options concatentation */
+ if (tagtype == opt_code)
+ {
+ if (opt_len)
+ *opt_len = taglength;
+ return &ptr[i];
}
- ptr += taglength;
+ if (tagtype == GRUB_NET_DHCP_OVERLOAD && taglength == 1)
+ overload = ptr[i];
+
+ i += taglength;
}
+
+ if (!end)
+ return 0;
+
+ /* 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.
+ */
+ end = 0;
+ if (overload & GRUB_DHCP_OPT_OVERLOAD_FILE)
+ {
+ overload &= ~GRUB_DHCP_OPT_OVERLOAD_FILE;
+ ptr = (grub_uint8_t *) &bp->boot_file[0];
+ size = sizeof (bp->boot_file);
+ i = 0;
+ goto again;
+ }
+
+ if (overload & GRUB_DHCP_OPT_OVERLOAD_SNAME)
+ {
+ overload &= ~GRUB_DHCP_OPT_OVERLOAD_SNAME;
+ ptr = (grub_uint8_t *) &bp->server_name[0];
+ size = sizeof (bp->server_name);
+ i = 0;
+ goto again;
+ }
+
+out:
+ return 0;
}
#define OFFSET_OF(x, y) ((grub_size_t)((grub_uint8_t *)((y)->x) -
(grub_uint8_t *)(y)))
@@ -143,6 +163,8 @@ grub_net_configure_by_dhcp_ack (const char *name,
struct grub_net_network_level_interface *inter;
int mask = -1;
char server_ip[sizeof ("xxx.xxx.xxx.xxx")];
+ const grub_uint8_t *opt;
+ grub_uint8_t opt_len;
addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
addr.ipv4 = bp->your_ip;
@@ -225,9 +247,69 @@ grub_net_configure_by_dhcp_ack (const char *name,
**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)
@@ -286,8 +368,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)
@@ -305,44 +387,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)
{
diff --git a/include/grub/net.h b/include/grub/net.h
index 1096b2432..0c7286bd2 100644
--- a/include/grub/net.h
+++ b/include/grub/net.h
@@ -457,6 +457,7 @@ enum
GRUB_NET_BOOTP_DOMAIN = 0x0f,
GRUB_NET_BOOTP_ROOT_PATH = 0x11,
GRUB_NET_BOOTP_EXTENSIONS_PATH = 0x12,
+ GRUB_NET_DHCP_OVERLOAD = 52,
GRUB_NET_BOOTP_END = 0xff
};
--
2.17.1
- [PATCH v2 0/9] net: bootp: add native DHCPv4 support, Andre Przywara, 2019/02/12
- [PATCH v2 1/9] net: dhcp: remove dead code, Andre Przywara, 2019/02/12
- [PATCH v2 2/9] net: dhcp: replace parse_dhcp_vendor() with find_dhcp_option(),
Andre Przywara <=
- [PATCH v2 3/9] net: dhcp: refactor DHCP packet transmission into separate function, Andre Przywara, 2019/02/12
- [PATCH v2 4/9] net: dhcp: make grub_net_process_dhcp take an interface, Andre Przywara, 2019/02/12
- [PATCH v2 5/9] net: dhcp: introduce per-interface timeout, Andre Przywara, 2019/02/12
- [PATCH v2 6/9] net: dhcp: use DHCP options for name and bootfile, Andre Przywara, 2019/02/12
- [PATCH v2 8/9] net: dhcp: actually send out DHCPv4 DISCOVER and REQUEST messages, Andre Przywara, 2019/02/12