From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Peter Jones Date: Tue, 9 Jul 2019 11:47:37 +0200 Subject: [PATCH] efinet and bootp: add support for dhcpv6 Signed-off-by: Peter Jones --- grub-core/net/bootp.c | 173 +++++++++++++++++++++++++++++++++++++ grub-core/net/drivers/efi/efinet.c | 53 ++++++++++-- grub-core/net/net.c | 72 +++++++++++++++ grub-core/net/tftp.c | 4 + include/grub/efi/api.h | 129 +++++++++++++++++++++++++-- include/grub/net.h | 60 +++++++++++++ 6 files changed, 477 insertions(+), 14 deletions(-) diff --git a/grub-core/net/bootp.c b/grub-core/net/bootp.c index 6fb562702..e28fb6a09 100644 --- a/grub-core/net/bootp.c +++ b/grub-core/net/bootp.c @@ -902,6 +902,179 @@ grub_cmd_bootp (struct grub_command *cmd __attribute__ ((unused)), static grub_command_t cmd_getdhcp, cmd_bootp, cmd_dhcp; +struct grub_net_network_level_interface * +grub_net_configure_by_dhcpv6_ack (const char *name, + struct grub_net_card *card, + grub_net_interface_flags_t flags + __attribute__((__unused__)), + const grub_net_link_level_address_t *hwaddr, + const struct grub_net_dhcpv6_packet *packet, + int is_def, char **device, char **path) +{ + struct grub_net_network_level_interface *inter = NULL; + struct grub_net_network_level_address addr; + int mask = -1; + + if (!device || !path) + return NULL; + + *device = 0; + *path = 0; + + grub_dprintf ("net", "mac address is %02x:%02x:%02x:%02x:%02x:%02x\n", + hwaddr->mac[0], hwaddr->mac[1], hwaddr->mac[2], + hwaddr->mac[3], hwaddr->mac[4], hwaddr->mac[5]); + + if (is_def) + grub_net_default_server = 0; + + if (is_def && !grub_net_default_server && packet) + { + const grub_uint8_t *options = packet->dhcp_options; + unsigned int option_max = 1024 - OFFSET_OF (dhcp_options, packet); + unsigned int i; + + for (i = 0; i < option_max - sizeof (grub_net_dhcpv6_option_t); ) + { + grub_uint16_t num, len; + grub_net_dhcpv6_option_t *opt = + (grub_net_dhcpv6_option_t *)(options + i); + + num = grub_be_to_cpu16(opt->option_num); + len = grub_be_to_cpu16(opt->option_len); + + grub_dprintf ("net", "got dhcpv6 option %d len %d\n", num, len); + + if (len == 0) + break; + + if (len + i > 1024) + break; + + if (num == GRUB_NET_DHCP6_BOOTFILE_URL) + { + char *scheme, *userinfo, *host, *file; + char *tmp; + int hostlen; + int port; + int rc = extract_url_info ((const char *)opt->option_data, + (grub_size_t)len, + &scheme, &userinfo, &host, &port, + &file); + if (rc < 0) + continue; + + /* right now this only handles tftp. */ + if (grub_strcmp("tftp", scheme)) + { + grub_free (scheme); + grub_free (userinfo); + grub_free (host); + grub_free (file); + continue; + } + grub_free (userinfo); + + hostlen = grub_strlen (host); + if (hostlen > 2 && host[0] == '[' && host[hostlen-1] == ']') + { + tmp = host+1; + host[hostlen-1] = '\0'; + } + else + tmp = host; + + *device = grub_xasprintf ("%s,%s", scheme, tmp); + grub_free (scheme); + grub_free (host); + + if (file && *file) + { + tmp = grub_strrchr (file, '/'); + if (tmp) + *(tmp+1) = '\0'; + else + file[0] = '\0'; + } + else if (!file) + file = grub_strdup (""); + + if (file[0] == '/') + { + *path = grub_strdup (file+1); + grub_free (file); + } + else + *path = file; + } + else if (num == GRUB_NET_DHCP6_IA_NA) + { + const grub_net_dhcpv6_option_t *ia_na_opt; + const grub_net_dhcpv6_opt_ia_na_t *ia_na = + (const grub_net_dhcpv6_opt_ia_na_t *)opt; + unsigned int left = len - OFFSET_OF (options, ia_na); + unsigned int j; + + if ((grub_uint8_t *)ia_na + left > + (grub_uint8_t *)options + option_max) + left -= ((grub_uint8_t *)ia_na + left) + - ((grub_uint8_t *)options + option_max); + + if (len < OFFSET_OF (option_data, opt) + + sizeof (grub_net_dhcpv6_option_t)) + { + grub_dprintf ("net", + "found dhcpv6 ia_na option with no address\n"); + continue; + } + + for (j = 0; left > sizeof (grub_net_dhcpv6_option_t); ) + { + ia_na_opt = (const grub_net_dhcpv6_option_t *) + (ia_na->options + j); + grub_uint16_t ia_na_opt_num, ia_na_opt_len; + + ia_na_opt_num = grub_be_to_cpu16 (ia_na_opt->option_num); + ia_na_opt_len = grub_be_to_cpu16 (ia_na_opt->option_len); + if (ia_na_opt_len == 0) + break; + if (j + ia_na_opt_len > left) + break; + if (ia_na_opt_num == GRUB_NET_DHCP6_IA_ADDRESS) + { + const grub_net_dhcpv6_opt_ia_address_t *ia_addr; + + ia_addr = (const grub_net_dhcpv6_opt_ia_address_t *) + ia_na_opt; + addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6; + grub_memcpy(addr.ipv6, ia_addr->ipv6_address, + sizeof (ia_addr->ipv6_address)); + inter = grub_net_add_addr (name, card, &addr, hwaddr, 0); + } + + j += ia_na_opt_len; + left -= ia_na_opt_len; + } + } + + i += len + 4; + } + + grub_print_error (); + } + + if (is_def) + { + grub_env_set ("net_default_interface", name); + grub_env_export ("net_default_interface"); + } + + if (inter) + grub_net_add_ipv6_local (inter, mask); + return inter; +} + + void grub_bootp_init (void) { diff --git a/grub-core/net/drivers/efi/efinet.c b/grub-core/net/drivers/efi/efinet.c index 5388f952b..173fb6315 100644 --- a/grub-core/net/drivers/efi/efinet.c +++ b/grub-core/net/drivers/efi/efinet.c @@ -18,11 +18,14 @@ #include #include +#include #include #include #include #include #include +#include +#include GRUB_MOD_LICENSE ("GPLv3+"); @@ -329,7 +332,7 @@ grub_efi_net_config_real (grub_efi_handle_t hnd, char **device, char **path) { struct grub_net_card *card; - grub_efi_device_path_t *dp; + grub_efi_device_path_t *dp, *ldp = NULL; dp = grub_efi_get_device_path (hnd); if (! dp) @@ -340,14 +343,19 @@ grub_efi_net_config_real (grub_efi_handle_t hnd, char **device, grub_efi_device_path_t *cdp; struct grub_efi_pxe *pxe; struct grub_efi_pxe_mode *pxe_mode; + if (card->driver != &efidriver) continue; + cdp = grub_efi_get_device_path (card->efi_handle); if (! cdp) continue; + + ldp = grub_efi_find_last_device_path (dp); + if (grub_efi_compare_device_paths (dp, cdp) != 0) { - grub_efi_device_path_t *ldp, *dup_dp, *dup_ldp; + grub_efi_device_path_t *dup_dp, *dup_ldp; int match; /* EDK2 UEFI PXE driver creates pseudo devices with type IPv4/IPv6 @@ -356,7 +364,6 @@ grub_efi_net_config_real (grub_efi_handle_t hnd, char **device, devices. We skip them when enumerating cards, so here we need to find matching MAC device. */ - ldp = grub_efi_find_last_device_path (dp); if (GRUB_EFI_DEVICE_PATH_TYPE (ldp) != GRUB_EFI_MESSAGING_DEVICE_PATH_TYPE || (GRUB_EFI_DEVICE_PATH_SUBTYPE (ldp) != GRUB_EFI_IPV4_DEVICE_PATH_SUBTYPE && GRUB_EFI_DEVICE_PATH_SUBTYPE (ldp) != GRUB_EFI_IPV6_DEVICE_PATH_SUBTYPE)) @@ -373,16 +380,46 @@ grub_efi_net_config_real (grub_efi_handle_t hnd, char **device, if (!match) continue; } + pxe = grub_efi_open_protocol (hnd, &pxe_io_guid, GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (! pxe) continue; + pxe_mode = pxe->mode; - grub_net_configure_by_dhcp_ack (card->name, card, 0, - (struct grub_net_bootp_packet *) - &pxe_mode->dhcp_ack, - sizeof (pxe_mode->dhcp_ack), - 1, device, path); + if (pxe_mode->using_ipv6) + { + grub_net_link_level_address_t hwaddr; + struct grub_net_network_level_interface *intf; + + grub_dprintf ("efinet", "using ipv6 and dhcpv6\n"); + grub_dprintf ("efinet", "dhcp_ack_received: %s%s\n", + pxe_mode->dhcp_ack_received ? "yes" : "no", + pxe_mode->dhcp_ack_received ? "" : " cannot continue"); + if (!pxe_mode->dhcp_ack_received) + continue; + + hwaddr.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; + grub_memcpy (hwaddr.mac, + card->efi_net->mode->current_address, + sizeof (hwaddr.mac)); + + intf = grub_net_configure_by_dhcpv6_ack (card->name, card, 0, &hwaddr, + (const struct grub_net_dhcpv6_packet *)&pxe_mode->dhcp_ack.dhcpv6, + 1, device, path); + if (intf && device && path) + grub_dprintf ("efinet", "device: `%s' path: `%s'\n", *device, *path); + } + else + { + grub_dprintf ("efinet", "using ipv4 and dhcp\n"); + grub_net_configure_by_dhcp_ack (card->name, card, 0, + (struct grub_net_bootp_packet *) + &pxe_mode->dhcp_ack, + sizeof (pxe_mode->dhcp_ack), + 1, device, path); + grub_dprintf ("efinet", "device: `%s' path: `%s'\n", *device, *path); + } return; } } diff --git a/grub-core/net/net.c b/grub-core/net/net.c index 0ef148f4a..22f2689aa 100644 --- a/grub-core/net/net.c +++ b/grub-core/net/net.c @@ -960,6 +960,78 @@ grub_net_network_level_interface_register (struct grub_net_network_level_interfa grub_net_network_level_interfaces = inter; } +int +grub_ipv6_get_masksize (grub_uint16_t be_mask[8]) +{ + grub_uint8_t *mask; + grub_uint16_t mask16[8]; + int x, y; + int ret = 128; + + grub_memcpy (mask16, be_mask, sizeof (mask16)); + for (x = 0; x < 8; x++) + mask16[x] = grub_be_to_cpu16 (mask16[x]); + + mask = (grub_uint8_t *)mask16; + + for (x = 15; x >= 0; x--) + { + grub_uint8_t octet = mask[x]; + if (!octet) + { + ret -= 8; + continue; + } + for (y = 0; y < 8; y++) + { + if (octet & (1 << y)) + break; + else + ret--; + } + break; + } + + return ret; +} + +grub_err_t +grub_net_add_ipv6_local (struct grub_net_network_level_interface *inter, + int mask) +{ + struct grub_net_route *route; + + if (inter->address.type != GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6) + return 0; + + if (mask == -1) + mask = grub_ipv6_get_masksize ((grub_uint16_t *)inter->address.ipv6); + + if (mask == -1) + return 0; + + route = grub_zalloc (sizeof (*route)); + if (!route) + return grub_errno; + + route->name = grub_xasprintf ("%s:local", inter->name); + if (!route->name) + { + grub_free (route); + return grub_errno; + } + + route->target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6; + grub_memcpy (route->target.ipv6.base, inter->address.ipv6, + sizeof (inter->address.ipv6)); + route->target.ipv6.masksize = mask; + route->is_gateway = 0; + route->interface = inter; + + grub_net_route_register (route); + + return 0; +} grub_err_t grub_net_add_ipv4_local (struct grub_net_network_level_interface *inter, diff --git a/grub-core/net/tftp.c b/grub-core/net/tftp.c index 7f44b30f5..4ab2f5c73 100644 --- a/grub-core/net/tftp.c +++ b/grub-core/net/tftp.c @@ -358,18 +358,22 @@ tftp_open (struct grub_file *file, const char *filename) file->not_easily_seekable = 1; file->data = data; + grub_dprintf("tftp", "resolving address for %s\n", file->device->net->server); err = grub_net_resolve_address (file->device->net->server, &addr); if (err) { + grub_dprintf("tftp", "Address resolution failed: %d\n", err); grub_free (data); return err; } + grub_dprintf("tftp", "opening connection\n"); data->sock = grub_net_udp_open (addr, TFTP_SERVER_PORT, tftp_receive, file); if (!data->sock) { + grub_dprintf("tftp", "connection failed\n"); grub_free (data); return grub_errno; } diff --git a/include/grub/efi/api.h b/include/grub/efi/api.h index f1a52210c..117469450 100644 --- a/include/grub/efi/api.h +++ b/include/grub/efi/api.h @@ -592,10 +592,16 @@ typedef void *grub_efi_handle_t; typedef void *grub_efi_event_t; typedef grub_efi_uint64_t grub_efi_lba_t; typedef grub_efi_uintn_t grub_efi_tpl_t; -typedef grub_uint8_t grub_efi_mac_address_t[32]; -typedef grub_uint8_t grub_efi_ipv4_address_t[4]; -typedef grub_uint16_t grub_efi_ipv6_address_t[8]; -typedef grub_uint8_t grub_efi_ip_address_t[8] __attribute__ ((aligned(4))); +typedef grub_efi_uint8_t grub_efi_mac_address_t[32]; +typedef grub_efi_uint8_t grub_efi_ipv4_address_t[4]; +typedef grub_efi_uint8_t grub_efi_ipv6_address_t[16]; +typedef union +{ + grub_efi_uint32_t addr[4]; + grub_efi_ipv4_address_t v4; + grub_efi_ipv6_address_t v6; +} grub_efi_ip_address_t __attribute__ ((aligned(4))); + typedef grub_efi_uint64_t grub_efi_physical_address_t; typedef grub_efi_uint64_t grub_efi_virtual_address_t; @@ -1474,16 +1480,127 @@ struct grub_efi_simple_text_output_interface }; typedef struct grub_efi_simple_text_output_interface grub_efi_simple_text_output_interface_t; -typedef grub_uint8_t grub_efi_pxe_packet_t[1472]; +typedef struct grub_efi_pxe_dhcpv4_packet +{ + grub_efi_uint8_t bootp_opcode; + grub_efi_uint8_t bootp_hwtype; + grub_efi_uint8_t bootp_hwaddr_len; + grub_efi_uint8_t bootp_gate_hops; + grub_efi_uint32_t bootp_ident; + grub_efi_uint16_t bootp_seconds; + grub_efi_uint16_t bootp_flags; + grub_efi_uint8_t bootp_ci_addr[4]; + grub_efi_uint8_t bootp_yi_addr[4]; + grub_efi_uint8_t bootp_si_addr[4]; + grub_efi_uint8_t bootp_gi_addr[4]; + grub_efi_uint8_t bootp_hw_addr[16]; + grub_efi_uint8_t bootp_srv_name[64]; + grub_efi_uint8_t bootp_boot_file[128]; + grub_efi_uint32_t dhcp_magik; + grub_efi_uint8_t dhcp_options[56]; +} grub_efi_pxe_dhcpv4_packet_t; + +struct grub_efi_pxe_dhcpv6_packet +{ + grub_efi_uint32_t message_type:8; + grub_efi_uint32_t transaction_id:24; + grub_efi_uint8_t dhcp_options[1024]; +} GRUB_PACKED; +typedef struct grub_efi_pxe_dhcpv6_packet grub_efi_pxe_dhcpv6_packet_t; + +typedef union +{ + grub_efi_uint8_t raw[1472]; + grub_efi_pxe_dhcpv4_packet_t dhcpv4; + grub_efi_pxe_dhcpv6_packet_t dhcpv6; +} grub_efi_pxe_packet_t; + +#define GRUB_EFI_PXE_MAX_IPCNT 8 +#define GRUB_EFI_PXE_MAX_ARP_ENTRIES 8 +#define GRUB_EFI_PXE_MAX_ROUTE_ENTRIES 8 + +typedef struct grub_efi_pxe_ip_filter +{ + grub_efi_uint8_t filters; + grub_efi_uint8_t ip_count; + grub_efi_uint8_t reserved; + grub_efi_ip_address_t ip_list[GRUB_EFI_PXE_MAX_IPCNT]; +} grub_efi_pxe_ip_filter_t; + +typedef struct grub_efi_pxe_arp_entry +{ + grub_efi_ip_address_t ip_addr; + grub_efi_mac_address_t mac_addr; +} grub_efi_pxe_arp_entry_t; + +typedef struct grub_efi_pxe_route_entry +{ + grub_efi_ip_address_t ip_addr; + grub_efi_ip_address_t subnet_mask; + grub_efi_ip_address_t gateway_addr; +} grub_efi_pxe_route_entry_t; + +typedef struct grub_efi_pxe_icmp_error +{ + grub_efi_uint8_t type; + grub_efi_uint8_t code; + grub_efi_uint16_t checksum; + union + { + grub_efi_uint32_t reserved; + grub_efi_uint32_t mtu; + grub_efi_uint32_t pointer; + struct + { + grub_efi_uint16_t identifier; + grub_efi_uint16_t sequence; + } echo; + } u; + grub_efi_uint8_t data[494]; +} grub_efi_pxe_icmp_error_t; + +typedef struct grub_efi_pxe_tftp_error +{ + grub_efi_uint8_t error_code; + grub_efi_char8_t error_string[127]; +} grub_efi_pxe_tftp_error_t; typedef struct grub_efi_pxe_mode { - grub_uint8_t unused[52]; + grub_efi_boolean_t started; + grub_efi_boolean_t ipv6_available; + grub_efi_boolean_t ipv6_supported; + grub_efi_boolean_t using_ipv6; + grub_efi_boolean_t bis_supported; + grub_efi_boolean_t bis_detected; + grub_efi_boolean_t auto_arp; + grub_efi_boolean_t send_guid; + grub_efi_boolean_t dhcp_discover_valid; + grub_efi_boolean_t dhcp_ack_received; + grub_efi_boolean_t proxy_offer_received; + grub_efi_boolean_t pxe_discover_valid; + grub_efi_boolean_t pxe_reply_received; + grub_efi_boolean_t pxe_bis_reply_received; + grub_efi_boolean_t icmp_error_received; + grub_efi_boolean_t tftp_error_received; + grub_efi_boolean_t make_callbacks; + grub_efi_uint8_t ttl; + grub_efi_uint8_t tos; + grub_efi_ip_address_t station_ip; + grub_efi_ip_address_t subnet_mask; grub_efi_pxe_packet_t dhcp_discover; grub_efi_pxe_packet_t dhcp_ack; grub_efi_pxe_packet_t proxy_offer; grub_efi_pxe_packet_t pxe_discover; grub_efi_pxe_packet_t pxe_reply; + grub_efi_pxe_packet_t pxe_bis_reply; + grub_efi_pxe_ip_filter_t ip_filter; + grub_efi_uint32_t arp_cache_entries; + grub_efi_pxe_arp_entry_t arp_cache[GRUB_EFI_PXE_MAX_ARP_ENTRIES]; + grub_efi_uint32_t route_table_entries; + grub_efi_pxe_route_entry_t route_table[GRUB_EFI_PXE_MAX_ROUTE_ENTRIES]; + grub_efi_pxe_icmp_error_t icmp_error; + grub_efi_pxe_tftp_error_t tftp_error; } grub_efi_pxe_mode_t; typedef struct grub_efi_pxe diff --git a/include/grub/net.h b/include/grub/net.h index 7ae4b6bd8..8a05ec4fe 100644 --- a/include/grub/net.h +++ b/include/grub/net.h @@ -447,6 +447,51 @@ struct grub_net_bootp_packet grub_uint8_t vendor[0]; } GRUB_PACKED; +enum + { + GRUB_NET_DHCP6_IA_NA = 3, + GRUB_NET_DHCP6_IA_ADDRESS = 5, + GRUB_NET_DHCP6_BOOTFILE_URL = 59, + }; + +struct grub_net_dhcpv6_option +{ + grub_uint16_t option_num; + grub_uint16_t option_len; + grub_uint8_t option_data[]; +} GRUB_PACKED; +typedef struct grub_net_dhcpv6_option grub_net_dhcpv6_option_t; + +struct grub_net_dhcpv6_opt_ia_na +{ + grub_uint16_t option_num; + grub_uint16_t option_len; + grub_uint32_t iaid; + grub_uint32_t t1; + grub_uint32_t t2; + grub_uint8_t options[]; +} GRUB_PACKED; +typedef struct grub_net_dhcpv6_opt_ia_na grub_net_dhcpv6_opt_ia_na_t; + +struct grub_net_dhcpv6_opt_ia_address +{ + grub_uint16_t option_num; + grub_uint16_t option_len; + grub_uint64_t ipv6_address[2]; + grub_uint32_t preferred_lifetime; + grub_uint32_t valid_lifetime; + grub_uint8_t options[]; +} GRUB_PACKED; +typedef struct grub_net_dhcpv6_opt_ia_address grub_net_dhcpv6_opt_ia_address_t; + +struct grub_net_dhcpv6_packet +{ + grub_uint32_t message_type:8; + grub_uint32_t transaction_id:24; + grub_uint8_t dhcp_options[1024]; +} GRUB_PACKED; +typedef struct grub_net_dhcpv6_packet grub_net_dhcpv6_packet_t; + #define GRUB_NET_BOOTP_RFC1048_MAGIC_0 0x63 #define GRUB_NET_BOOTP_RFC1048_MAGIC_1 0x82 #define GRUB_NET_BOOTP_RFC1048_MAGIC_2 0x53 @@ -482,6 +527,21 @@ grub_net_configure_by_dhcp_ack (const char *name, grub_size_t size, int is_def, char **device, char **path); +struct grub_net_network_level_interface * +grub_net_configure_by_dhcpv6_ack (const char *name, + struct grub_net_card *card, + grub_net_interface_flags_t flags, + const grub_net_link_level_address_t *hwaddr, + const struct grub_net_dhcpv6_packet *packet, + int is_def, char **device, char **path); + +int +grub_ipv6_get_masksize(grub_uint16_t *mask); + +grub_err_t +grub_net_add_ipv6_local (struct grub_net_network_level_interface *inf, + int mask); + grub_err_t grub_net_add_ipv4_local (struct grub_net_network_level_interface *inf, int mask);