f8c3af3b61
Network boot autoconfiguration sets default server to next server IP (siaddr) from BOOTP/DHCP reply, but manual configuration using net_bootp exports only server name. Unfortunately semantic of server name is not clearly defined. BOOTP RFC 951 defines it only for client request, and DHCP RFC 1541 only mentions it, without any implied usage. It looks like this field is mostly empty in server replies. Export next server IP as net_<interface>_next_server variable. This allows grub configuration script to set $root/$prefix based on information obtained by net_bootp. Reported and tested by: Nikunj A Dadhania <nikunj@linux.vnet.ibm.com> Cc: nikunj@linux.vnet.ibm.com v2: change variable name to net_<interface>_next_server as discussed on the list
603 lines
16 KiB
C
603 lines
16 KiB
C
/*
|
|
* GRUB -- GRand Unified Bootloader
|
|
* Copyright (C) 2010,2011 Free Software Foundation, Inc.
|
|
*
|
|
* GRUB is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* GRUB is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <grub/net.h>
|
|
#include <grub/env.h>
|
|
#include <grub/i18n.h>
|
|
#include <grub/command.h>
|
|
#include <grub/net/ip.h>
|
|
#include <grub/net/netbuff.h>
|
|
#include <grub/net/udp.h>
|
|
#include <grub/datetime.h>
|
|
|
|
static void
|
|
parse_dhcp_vendor (const char *name, const void *vend, int limit, int *mask)
|
|
{
|
|
const grub_uint8_t *ptr, *ptr0;
|
|
|
|
ptr = ptr0 = vend;
|
|
|
|
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)
|
|
{
|
|
grub_uint8_t tagtype;
|
|
grub_uint8_t taglength;
|
|
|
|
tagtype = *ptr++;
|
|
|
|
/* Pad tag. */
|
|
if (tagtype == GRUB_NET_BOOTP_PAD)
|
|
continue;
|
|
|
|
/* 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;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
#define OFFSET_OF(x, y) ((grub_size_t)((grub_uint8_t *)((y)->x) - (grub_uint8_t *)(y)))
|
|
|
|
struct grub_net_network_level_interface *
|
|
grub_net_configure_by_dhcp_ack (const char *name,
|
|
struct grub_net_card *card,
|
|
grub_net_interface_flags_t flags,
|
|
const struct grub_net_bootp_packet *bp,
|
|
grub_size_t size,
|
|
int is_def, char **device, char **path)
|
|
{
|
|
grub_net_network_level_address_t addr;
|
|
grub_net_link_level_address_t hwaddr;
|
|
struct grub_net_network_level_interface *inter;
|
|
int mask = -1;
|
|
char server_ip[sizeof ("xxx.xxx.xxx.xxx")];
|
|
|
|
addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
|
|
addr.ipv4 = bp->your_ip;
|
|
|
|
if (device)
|
|
*device = 0;
|
|
if (path)
|
|
*path = 0;
|
|
|
|
grub_memcpy (hwaddr.mac, bp->mac_addr,
|
|
bp->hw_len < sizeof (hwaddr.mac) ? bp->hw_len
|
|
: sizeof (hwaddr.mac));
|
|
hwaddr.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
|
|
|
|
inter = grub_net_add_addr (name, card, &addr, &hwaddr, flags);
|
|
if (!inter)
|
|
return 0;
|
|
|
|
#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)
|
|
{
|
|
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);
|
|
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
if (size > OFFSET_OF (boot_file, bp))
|
|
grub_env_set_net_property (name, "boot_file", bp->boot_file,
|
|
sizeof (bp->boot_file));
|
|
if (bp->server_ip)
|
|
{
|
|
grub_snprintf (server_ip, sizeof (server_ip), "%d.%d.%d.%d",
|
|
((grub_uint8_t *) &bp->server_ip)[0],
|
|
((grub_uint8_t *) &bp->server_ip)[1],
|
|
((grub_uint8_t *) &bp->server_ip)[2],
|
|
((grub_uint8_t *) &bp->server_ip)[3]);
|
|
grub_env_set_net_property (name, "next_server", server_ip, sizeof (server_ip));
|
|
grub_print_error ();
|
|
}
|
|
|
|
if (is_def)
|
|
grub_net_default_server = 0;
|
|
if (is_def && !grub_net_default_server && bp->server_ip)
|
|
{
|
|
grub_net_default_server = grub_strdup (server_ip);
|
|
grub_print_error ();
|
|
}
|
|
|
|
if (is_def)
|
|
{
|
|
grub_env_set ("net_default_interface", name);
|
|
grub_env_export ("net_default_interface");
|
|
}
|
|
|
|
if (device && !*device && bp->server_ip)
|
|
{
|
|
*device = grub_xasprintf ("tftp,%s", server_ip);
|
|
grub_print_error ();
|
|
}
|
|
if (size > OFFSET_OF (server_name, bp)
|
|
&& bp->server_name[0])
|
|
{
|
|
grub_env_set_net_property (name, "dhcp_server_name", bp->server_name,
|
|
sizeof (bp->server_name));
|
|
if (is_def && !grub_net_default_server)
|
|
{
|
|
grub_net_default_server = grub_strdup (bp->server_name);
|
|
grub_print_error ();
|
|
}
|
|
if (device && !*device)
|
|
{
|
|
*device = grub_xasprintf ("tftp,%s", bp->server_name);
|
|
grub_print_error ();
|
|
}
|
|
}
|
|
|
|
if (size > OFFSET_OF (boot_file, bp) && path)
|
|
{
|
|
*path = grub_strndup (bp->boot_file, sizeof (bp->boot_file));
|
|
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);
|
|
grub_net_add_ipv4_local (inter, mask);
|
|
|
|
inter->dhcp_ack = grub_malloc (size);
|
|
if (inter->dhcp_ack)
|
|
{
|
|
grub_memcpy (inter->dhcp_ack, bp, size);
|
|
inter->dhcp_acklen = size;
|
|
}
|
|
else
|
|
grub_errno = GRUB_ERR_NONE;
|
|
|
|
return inter;
|
|
}
|
|
|
|
void
|
|
grub_net_process_dhcp (struct grub_net_buff *nb,
|
|
struct grub_net_card *card)
|
|
{
|
|
char *name;
|
|
struct grub_net_network_level_interface *inf;
|
|
|
|
name = grub_xasprintf ("%s:dhcp", card->name);
|
|
if (!name)
|
|
{
|
|
grub_print_error ();
|
|
return;
|
|
}
|
|
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
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
static char
|
|
hexdigit (grub_uint8_t val)
|
|
{
|
|
if (val < 10)
|
|
return val + '0';
|
|
return val + 'a' - 10;
|
|
}
|
|
|
|
static grub_err_t
|
|
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;
|
|
grub_uint8_t taglength;
|
|
|
|
if (argc < 4)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT,
|
|
N_("four arguments expected"));
|
|
|
|
FOR_NET_NETWORK_LEVEL_INTERFACES (inter)
|
|
if (grub_strcmp (inter->name, args[1]) == 0)
|
|
break;
|
|
|
|
if (!inter)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT,
|
|
N_("unrecognised network interface `%s'"), args[1]);
|
|
|
|
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
|
|
|| 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;
|
|
|
|
/* 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)
|
|
{
|
|
grub_err_t err = GRUB_ERR_NONE;
|
|
char *val = grub_malloc (taglength + 1);
|
|
if (!val)
|
|
return grub_errno;
|
|
grub_memcpy (val, ptr, taglength);
|
|
val[taglength] = 0;
|
|
if (args[0][0] == '-' && args[0][1] == 0)
|
|
grub_printf ("%s\n", val);
|
|
else
|
|
err = grub_env_set (args[0], val);
|
|
grub_free (val);
|
|
return err;
|
|
}
|
|
|
|
if (grub_strcmp (args[3], "number") == 0)
|
|
{
|
|
grub_uint64_t val = 0;
|
|
int i;
|
|
for (i = 0; i < taglength; i++)
|
|
val = (val << 8) | ptr[i];
|
|
if (args[0][0] == '-' && args[0][1] == 0)
|
|
grub_printf ("%llu\n", (unsigned long long) val);
|
|
else
|
|
{
|
|
char valn[64];
|
|
grub_snprintf (valn, sizeof (valn), "%lld\n", (unsigned long long) val);
|
|
return grub_env_set (args[0], valn);
|
|
}
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
if (grub_strcmp (args[3], "hex") == 0)
|
|
{
|
|
grub_err_t err = GRUB_ERR_NONE;
|
|
char *val = grub_malloc (2 * taglength + 1);
|
|
int i;
|
|
if (!val)
|
|
return grub_errno;
|
|
for (i = 0; i < taglength; i++)
|
|
{
|
|
val[2 * i] = hexdigit (ptr[i] >> 4);
|
|
val[2 * i + 1] = hexdigit (ptr[i] & 0xf);
|
|
}
|
|
val[2 * taglength] = 0;
|
|
if (args[0][0] == '-' && args[0][1] == 0)
|
|
grub_printf ("%s\n", val);
|
|
else
|
|
err = grub_env_set (args[0], val);
|
|
grub_free (val);
|
|
return err;
|
|
}
|
|
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT,
|
|
N_("unrecognised DHCP option format specification `%s'"),
|
|
args[3]);
|
|
}
|
|
|
|
/* FIXME: allow to specify mac address. */
|
|
static grub_err_t
|
|
grub_cmd_bootp (struct grub_command *cmd __attribute__ ((unused)),
|
|
int argc, char **args)
|
|
{
|
|
struct grub_net_card *card;
|
|
struct grub_net_network_level_interface *ifaces;
|
|
grub_size_t ncards = 0;
|
|
unsigned j = 0;
|
|
int interval;
|
|
grub_err_t err;
|
|
|
|
FOR_NET_CARDS (card)
|
|
{
|
|
if (argc > 0 && grub_strcmp (card->name, args[0]) != 0)
|
|
continue;
|
|
ncards++;
|
|
}
|
|
|
|
if (ncards == 0)
|
|
return grub_error (GRUB_ERR_NET_NO_CARD, N_("no network card found"));
|
|
|
|
ifaces = grub_zalloc (ncards * sizeof (ifaces[0]));
|
|
if (!ifaces)
|
|
return grub_errno;
|
|
|
|
j = 0;
|
|
FOR_NET_CARDS (card)
|
|
{
|
|
if (argc > 0 && grub_strcmp (card->name, args[0]) != 0)
|
|
continue;
|
|
ifaces[j].card = card;
|
|
ifaces[j].next = &ifaces[j+1];
|
|
if (j)
|
|
ifaces[j].prev = &ifaces[j-1].next;
|
|
ifaces[j].name = grub_xasprintf ("%s:dhcp_tmp", card->name);
|
|
card->num_ifaces++;
|
|
if (!ifaces[j].name)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < j; i++)
|
|
grub_free (ifaces[i].name);
|
|
grub_free (ifaces);
|
|
return grub_errno;
|
|
}
|
|
ifaces[j].address.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV;
|
|
grub_memcpy (&ifaces[j].hwaddress, &card->default_address,
|
|
sizeof (ifaces[j].hwaddress));
|
|
j++;
|
|
}
|
|
ifaces[ncards - 1].next = grub_net_network_level_interfaces;
|
|
if (grub_net_network_level_interfaces)
|
|
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)
|
|
{
|
|
int done = 0;
|
|
for (j = 0; j < ncards; j++)
|
|
{
|
|
struct grub_net_bootp_packet *pack;
|
|
struct grub_datetime date;
|
|
grub_int32_t t = 0;
|
|
struct grub_net_buff *nb;
|
|
struct udphdr *udph;
|
|
grub_net_network_level_address_t target;
|
|
grub_net_link_level_address_t ll_target;
|
|
|
|
if (!ifaces[j].prev)
|
|
continue;
|
|
nb = grub_netbuff_alloc (sizeof (*pack) + 64 + 128);
|
|
if (!nb)
|
|
{
|
|
grub_netbuff_free (nb);
|
|
return grub_errno;
|
|
}
|
|
err = grub_netbuff_reserve (nb, sizeof (*pack) + 64 + 128);
|
|
if (err)
|
|
{
|
|
grub_netbuff_free (nb);
|
|
return err;
|
|
}
|
|
err = grub_netbuff_push (nb, sizeof (*pack) + 64);
|
|
if (err)
|
|
{
|
|
grub_netbuff_free (nb);
|
|
return err;
|
|
}
|
|
pack = (void *) nb->data;
|
|
done = 1;
|
|
grub_memset (pack, 0, sizeof (*pack) + 64);
|
|
pack->opcode = 1;
|
|
pack->hw_type = 1;
|
|
pack->hw_len = 6;
|
|
err = grub_get_datetime (&date);
|
|
if (err || !grub_datetime2unixtime (&date, &t))
|
|
{
|
|
grub_errno = GRUB_ERR_NONE;
|
|
t = 0;
|
|
}
|
|
pack->ident = grub_cpu_to_be32 (t);
|
|
pack->seconds = grub_cpu_to_be16 (t);
|
|
|
|
grub_memcpy (&pack->mac_addr, &ifaces[j].hwaddress.mac, 6);
|
|
|
|
grub_netbuff_push (nb, sizeof (*udph));
|
|
|
|
udph = (struct udphdr *) nb->data;
|
|
udph->src = grub_cpu_to_be16_compile_time (68);
|
|
udph->dst = grub_cpu_to_be16_compile_time (67);
|
|
udph->chksum = 0;
|
|
udph->len = grub_cpu_to_be16 (nb->tail - nb->data);
|
|
target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
|
|
target.ipv4 = 0xffffffff;
|
|
err = grub_net_link_layer_resolve (&ifaces[j], &target, &ll_target);
|
|
if (err)
|
|
return err;
|
|
|
|
udph->chksum = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_UDP,
|
|
&ifaces[j].address,
|
|
&target);
|
|
|
|
err = grub_net_send_ip_packet (&ifaces[j], &target, &ll_target, nb,
|
|
GRUB_NET_IP_UDP);
|
|
grub_netbuff_free (nb);
|
|
if (err)
|
|
return err;
|
|
}
|
|
if (!done)
|
|
break;
|
|
grub_net_poll_cards (interval, 0);
|
|
}
|
|
|
|
err = GRUB_ERR_NONE;
|
|
for (j = 0; j < ncards; j++)
|
|
{
|
|
grub_free (ifaces[j].name);
|
|
if (!ifaces[j].prev)
|
|
continue;
|
|
grub_error_push ();
|
|
grub_net_network_level_interface_unregister (&ifaces[j]);
|
|
err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
|
|
N_("couldn't autoconfigure %s"),
|
|
ifaces[j].card->name);
|
|
}
|
|
|
|
grub_free (ifaces);
|
|
return err;
|
|
}
|
|
|
|
static grub_command_t cmd_getdhcp, cmd_bootp;
|
|
|
|
void
|
|
grub_bootp_init (void)
|
|
{
|
|
cmd_bootp = grub_register_command ("net_bootp", grub_cmd_bootp,
|
|
N_("[CARD]"),
|
|
N_("perform a bootp 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."));
|
|
}
|
|
|
|
void
|
|
grub_bootp_fini (void)
|
|
{
|
|
grub_unregister_command (cmd_getdhcp);
|
|
grub_unregister_command (cmd_bootp);
|
|
}
|