net/dhcp: Replace parse_dhcp_vendor() with find_dhcp_option()

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 <andre.przywara@arm.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
This commit is contained in:
Andrei Borzenkov 2019-03-07 15:14:08 +00:00 committed by Vincent Batts
parent 526fa11df7
commit 6c38b920c8

View file

@ -43,25 +43,41 @@ static grub_uint8_t grub_userclass[] = {0x4D, 0x06, 0x05, 'G', 'R', 'U', 'B', '2
static grub_uint8_t grub_dhcpdiscover[] = {0x35, 0x01, 0x01}; static grub_uint8_t grub_dhcpdiscover[] = {0x35, 0x01, 0x01};
static grub_uint8_t grub_dhcptime[] = {0x33, 0x04, 0x00, 0x00, 0x0e, 0x10}; static grub_uint8_t grub_dhcptime[] = {0x33, 0x04, 0x00, 0x00, 0x0e, 0x10};
static void static const void *
parse_dhcp_vendor (const char *name, const void *vend, int limit, int *mask) 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, *ptr0; const grub_uint8_t *ptr;
grub_size_t i;
ptr = ptr0 = vend; 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))
return NULL;
/*
* 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 if (ptr[0] != GRUB_NET_BOOTP_RFC1048_MAGIC_0
|| ptr[1] != GRUB_NET_BOOTP_RFC1048_MAGIC_1 || ptr[1] != GRUB_NET_BOOTP_RFC1048_MAGIC_1
|| ptr[2] != GRUB_NET_BOOTP_RFC1048_MAGIC_2 || ptr[2] != GRUB_NET_BOOTP_RFC1048_MAGIC_2
|| ptr[3] != GRUB_NET_BOOTP_RFC1048_MAGIC_3) || ptr[3] != GRUB_NET_BOOTP_RFC1048_MAGIC_3)
return; return NULL;
ptr = ptr + sizeof (grub_uint32_t);
while (ptr - ptr0 < limit) size -= sizeof (*bp);
i = sizeof (grub_uint32_t);
while (i < size)
{ {
grub_uint8_t tagtype; grub_uint8_t tagtype;
grub_uint8_t taglength; grub_uint8_t taglength;
tagtype = *ptr++; tagtype = ptr[i++];
/* Pad tag. */ /* Pad tag. */
if (tagtype == GRUB_NET_BOOTP_PAD) if (tagtype == GRUB_NET_BOOTP_PAD)
@ -69,81 +85,27 @@ parse_dhcp_vendor (const char *name, const void *vend, int limit, int *mask)
/* End tag. */ /* End tag. */
if (tagtype == GRUB_NET_BOOTP_END) if (tagtype == GRUB_NET_BOOTP_END)
return; break;
taglength = *ptr++; if (i >= size)
return NULL;
switch (tagtype) taglength = ptr[i++];
if (i + taglength >= size)
return NULL;
/* FIXME RFC 3396 options concatentation */
if (tagtype == opt_code)
{ {
case GRUB_NET_BOOTP_NETMASK: if (opt_len)
if (taglength == 4) *opt_len = taglength;
{ return &ptr[i];
int i;
for (i = 0; i < 32; i++)
if (!(ptr[i / 8] & (1 << (7 - (i % 8)))))
break;
*mask = i;
}
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. */
} }
ptr += taglength; i += taglength;
} }
return NULL;
} }
#define OFFSET_OF(x, y) ((grub_size_t)((grub_uint8_t *)((y)->x) - (grub_uint8_t *)(y))) #define OFFSET_OF(x, y) ((grub_size_t)((grub_uint8_t *)((y)->x) - (grub_uint8_t *)(y)))
@ -161,6 +123,8 @@ grub_net_configure_by_dhcp_ack (const char *name,
struct grub_net_network_level_interface *inter; struct grub_net_network_level_interface *inter;
int mask = -1; int mask = -1;
char server_ip[sizeof ("xxx.xxx.xxx.xxx")]; 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.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
addr.ipv4 = bp->your_ip; addr.ipv4 = bp->your_ip;
@ -243,10 +207,70 @@ grub_net_configure_by_dhcp_ack (const char *name,
**path = 0; **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); 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); inter->dhcp_ack = grub_malloc (size);
if (inter->dhcp_ack) if (inter->dhcp_ack)
{ {
@ -304,8 +328,8 @@ grub_cmd_dhcpopt (struct grub_command *cmd __attribute__ ((unused)),
int argc, char **args) int argc, char **args)
{ {
struct grub_net_network_level_interface *inter; struct grub_net_network_level_interface *inter;
int num; unsigned num;
grub_uint8_t *ptr; const grub_uint8_t *ptr;
grub_uint8_t taglength; grub_uint8_t taglength;
if (argc < 4) if (argc < 4)
@ -323,44 +347,27 @@ grub_cmd_dhcpopt (struct grub_command *cmd __attribute__ ((unused)),
if (!inter->dhcp_ack) if (!inter->dhcp_ack)
return grub_error (GRUB_ERR_IO, N_("no DHCP info found")); return grub_error (GRUB_ERR_IO, N_("no DHCP info found"));
if (inter->dhcp_acklen <= OFFSET_OF (vendor, inter->dhcp_ack)) ptr = inter->dhcp_ack->vendor;
/* 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")); return grub_error (GRUB_ERR_IO, N_("no DHCP options found"));
num = grub_strtoul (args[2], 0, 0); num = grub_strtoul (args[2], 0, 0);
if (grub_errno) if (grub_errno)
return grub_errno; return grub_errno;
ptr = inter->dhcp_ack->vendor; /* 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"));
if (ptr[0] != GRUB_NET_BOOTP_RFC1048_MAGIC_0 ptr = find_dhcp_option (inter->dhcp_ack, inter->dhcp_acklen, num, &taglength);
|| ptr[1] != GRUB_NET_BOOTP_RFC1048_MAGIC_1 if (!ptr)
|| ptr[2] != GRUB_NET_BOOTP_RFC1048_MAGIC_2 return grub_error (GRUB_ERR_IO, N_("no DHCP option %u found"), num);
|| 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;
/* End tag. */
if (tagtype == 0xff)
return grub_error (GRUB_ERR_IO, N_("no DHCP option %d found"), num);
taglength = *ptr++;
if (tagtype == num)
break;
ptr += taglength;
}
if (grub_strcmp (args[3], "string") == 0) if (grub_strcmp (args[3], "string") == 0)
{ {