[PATCH v2 0/3] *** net: DHCPv6 protocol and commands ***

From: Sean Edmond seanedmond@microsoft.com
The recently integrated IPv6 patch series relies on the link-local address, or a statically assigned IPv6 address for network operations. This patch series adds IPv6 address assignment through DHCPv6.
The implementation meets the requirements in RFC 8415 for "Client/Server Exchanges Involving Four Messages": https://www.rfc-editor.org/rfc/rfc8415
The implementation sends/receives the minimum required DHCPv6 options to network boot.
A new command (dhcp6) will execute the protocol. In addition, IPv6 functionality has been extended to the existing pxe commands ("pxe get" and "pxe boot").
changes in v2: - Add sandbox test in test_net.py - Add CONFIG_CMD_DHCP6 to sandbox_defconfig - fix comment style (/**/ instead of //) - move addition of Kconfig from 1st patch to 2nd patch - Fix warning (warning: label ‘error_exit’ defined but not used") when CONFIG_DHCP6_PXE_DHCP_OPTION not configured - Fix dhcp6 command help - Use net_set_timeout_handler(0, NULL) in dhcpv6.c - Move USE_IP6_CMD_PARAM back to net6.h
Sean Edmond (3): net: dhcp6: Add DHCPv6 (DHCP for IPv6) net: dhcp6: pxe: Add DHCP/PXE commands for IPv6 net: dhcp6: Add a sandbox test for dhcp6
boot/bootmeth_distro.c | 2 +- boot/bootmeth_pxe.c | 4 +- boot/pxe_utils.c | 3 +- cmd/Kconfig | 26 ++ cmd/net.c | 23 ++ cmd/pxe.c | 86 ++++- cmd/sysboot.c | 2 +- configs/sandbox_defconfig | 1 + include/net.h | 8 +- include/pxe_utils.h | 10 +- net/Makefile | 1 + net/dhcpv6.c | 735 ++++++++++++++++++++++++++++++++++++++ net/dhcpv6.h | 212 +++++++++++ net/net.c | 12 + test/py/tests/test_net.py | 25 ++ 15 files changed, 1132 insertions(+), 18 deletions(-) create mode 100644 net/dhcpv6.c create mode 100644 net/dhcpv6.h

From: Sean Edmond seanedmond@microsoft.com
Adds DHCPv6 protocol to u-boot.
Allows for address assignement with DHCPv6 4-message exchange (SOLICIT->ADVERTISE->REQUEST->REPLY). Includes DHCPv6 options required by RFC 8415. Also adds DHCPv6 options required for PXE boot.
Possible enhancements: - Duplicate address detection on DHCPv6 assigned address - IPv6 address assignement through SLAAC - Sending/parsing other DHCPv6 options (NTP, DNS, etc...)
Signed-off-by: Sean Edmond seanedmond@microsoft.com --- include/net.h | 8 +- net/Makefile | 1 + net/dhcpv6.c | 735 ++++++++++++++++++++++++++++++++++++++++++++++++++ net/dhcpv6.h | 212 +++++++++++++++ net/net.c | 12 + 5 files changed, 966 insertions(+), 2 deletions(-) create mode 100644 net/dhcpv6.c create mode 100644 net/dhcpv6.h
diff --git a/include/net.h b/include/net.h index 399af5e064..cac818e292 100644 --- a/include/net.h +++ b/include/net.h @@ -484,6 +484,10 @@ extern char net_hostname[32]; /* Our hostname */ #ifdef CONFIG_NET extern char net_root_path[CONFIG_BOOTP_MAX_ROOT_PATH_LEN]; /* Our root path */ #endif +#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION) +/* Indicates whether the pxe path prefix / config file was specified in dhcp option */ +extern char *pxelinux_configfile; +#endif /** END OF BOOTP EXTENTIONS **/ extern u8 net_ethaddr[ARP_HLEN]; /* Our ethernet address */ extern u8 net_server_ethaddr[ARP_HLEN]; /* Boot server enet address */ @@ -504,8 +508,8 @@ extern ushort net_native_vlan; /* Our Native VLAN */ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t { - BOOTP, RARP, ARP, TFTPGET, DHCP, PING, PING6, DNS, NFS, CDP, NETCONS, - SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET + BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP, + NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET };
extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/net/Makefile b/net/Makefile index bea000b206..5968110170 100644 --- a/net/Makefile +++ b/net/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_IPV6) += net6.o obj-$(CONFIG_CMD_NFS) += nfs.o obj-$(CONFIG_CMD_PING) += ping.o obj-$(CONFIG_CMD_PING6) += ping6.o +obj-$(CONFIG_CMD_DHCP6) += dhcpv6.o obj-$(CONFIG_CMD_PCAP) += pcap.o obj-$(CONFIG_CMD_RARP) += rarp.o obj-$(CONFIG_CMD_SNTP) += sntp.o diff --git a/net/dhcpv6.c b/net/dhcpv6.c new file mode 100644 index 0000000000..9204909c1f --- /dev/null +++ b/net/dhcpv6.c @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) Microsoft Corporation + * Author: Sean Edmond seanedmond@microsoft.com + * + */ + +/* Simple DHCP6 network layer implementation. */ + +#include <common.h> +#include <bootstage.h> +#include <command.h> +#include <env.h> +#include <efi_loader.h> +#include <log.h> +#include <net.h> +#include <rand.h> +#include <uuid.h> +#include <linux/delay.h> +#include <net/tftp.h> +#include "dhcpv6.h" +#include <net6.h> +#include <malloc.h> +#include "net_rand.h" + +#define PORT_DHCP6_S 547 /* DHCP6 server UDP port */ +#define PORT_DHCP6_C 546 /* DHCP6 client UDP port */ + +/* default timeout parameters (in ms) */ +#define SOL_MAX_DELAY_MS 1000 +#define SOL_TIMEOUT_MS 1000 +#define SOL_MAX_RT_MS 3600000 +#define REQ_TIMEOUT_MS 1000 +#define REQ_MAX_RT_MS 30000 +#define REQ_MAX_RC 10 +#define MAX_WAIT_TIME_MS 60000 + +/* global variable to track any updates from DHCP6 server */ +int updated_sol_max_rt_ms = SOL_MAX_RT_MS; + +static void dhcp6_timeout_handler(void); +static void dhcp6_state_machine(bool timeout, uchar *rx_pkt, unsigned int len); +static void dhcp6_parse_options(uchar *rx_pkt, unsigned int len); + +struct dhcp6_sm_params sm_params; + +/* + * Handle DHCP received packets (set as UDP handler) + */ +static void dhcp6_handler(uchar *pkt, unsigned int dest, struct in_addr sip, + unsigned int src, unsigned int len) +{ + /* return if ports don't match DHCPv6 ports */ + if (dest != PORT_DHCP6_C || src != PORT_DHCP6_S) + return; + + dhcp6_state_machine(false, pkt, len); +} + +/** + * dhcp6_add_option() - Adds DHCP6 option to a packet + * @option_id: The option ID to add (See DHCP6_OPTION_* definitions) + * @pkt: A pointer to the current write location of the TX packet + * + * Return: The number of bytes written into "*pkt" + */ +static int dhcp6_add_option(int option_id, uchar *pkt) +{ + struct dhcp6_option_duid_ll *duid_opt; + struct dhcp6_option_elapsed_time *elapsed_time_opt; + struct dhcp6_option_ia_ta *ia_ta_opt; + struct dhcp6_option_ia_na *ia_na_opt; + struct dhcp6_option_oro *oro_opt; + struct dhcp6_option_client_arch *client_arch_opt; + struct dhcp6_option_vendor_class *vendor_class_opt; + int opt_len; + long elapsed_time; + size_t vci_strlen; + int num_oro = 0; + int num_client_arch = 0; + int num_vc_data = 0; + struct dhcp6_option_hdr *dhcp_option = (struct dhcp6_option_hdr *)pkt; + uchar *dhcp_option_start = pkt + sizeof(struct dhcp6_option_hdr); + + dhcp_option->option_id = htons(option_id); + + switch (option_id) { + case DHCP6_OPTION_CLIENTID: + /* Only support for DUID-LL in Client ID option for now */ + duid_opt = (struct dhcp6_option_duid_ll *)dhcp_option_start; + duid_opt->duid_type = htons(DUID_TYPE_LL); + duid_opt->hw_type = htons(DUID_HW_TYPE_ENET); + memcpy(duid_opt->ll_addr, net_ethaddr, ETH_ALEN); + opt_len = sizeof(struct dhcp6_option_duid_ll) + ETH_ALEN; + + /* Save DUID for comparison later */ + memcpy(sm_params.duid, duid_opt, opt_len); + break; + case DHCP6_OPTION_ELAPSED_TIME: + /* calculate elapsed time in 1/100th of a second */ + elapsed_time = (sm_params.dhcp6_retry_ms - + sm_params.dhcp6_start_ms) / 10; + if (elapsed_time > 0xFFFF) + elapsed_time = 0xFFFF; + + elapsed_time_opt = (struct dhcp6_option_elapsed_time *)dhcp_option_start; + elapsed_time_opt->elapsed_time = htons(elapsed_time); + + opt_len = sizeof(struct dhcp6_option_elapsed_time); + break; + case DHCP6_OPTION_IA_TA: + ia_ta_opt = (struct dhcp6_option_ia_ta *)dhcp_option_start; + ia_ta_opt->iaid = htonl(sm_params.ia_id); + + opt_len = sizeof(struct dhcp6_option_ia_ta); + break; + case DHCP6_OPTION_IA_NA: + ia_na_opt = (struct dhcp6_option_ia_na *)dhcp_option_start; + ia_na_opt->iaid = htonl(sm_params.ia_id); + /* In a message sent by a client to a server, + * the T1 and T2 fields SHOULD be set to 0 + */ + ia_na_opt->t1 = 0; + ia_na_opt->t2 = 0; + + opt_len = sizeof(struct dhcp6_option_ia_na); + break; + case DHCP6_OPTION_ORO: + oro_opt = (struct dhcp6_option_oro *)dhcp_option_start; + oro_opt->req_option_code[num_oro++] = htons(DHCP6_OPTION_OPT_BOOTFILE_URL); + oro_opt->req_option_code[num_oro++] = htons(DHCP6_OPTION_SOL_MAX_RT); + if (IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION)) { + oro_opt->req_option_code[num_oro++] = + htons(DHCP6_OPTION_OPT_BOOTFILE_PARAM); + } + + opt_len = sizeof(__be16) * num_oro; + break; + case DHCP6_OPTION_CLIENT_ARCH_TYPE: + client_arch_opt = (struct dhcp6_option_client_arch *)dhcp_option_start; + client_arch_opt->arch_type[num_client_arch++] = htons(CONFIG_DHCP6_PXE_CLIENTARCH); + + opt_len = sizeof(__be16) * num_client_arch; + break; + case DHCP6_OPTION_VENDOR_CLASS: + vendor_class_opt = (struct dhcp6_option_vendor_class *)dhcp_option_start; + vendor_class_opt->enterprise_number = htonl(CONFIG_DHCP6_ENTERPRISE_ID); + + vci_strlen = strlen(DHCP6_VCI_STRING); + vendor_class_opt->vendor_class_data[num_vc_data].vendor_class_len = + htons(vci_strlen); + memcpy(vendor_class_opt->vendor_class_data[num_vc_data].opaque_data, + DHCP6_VCI_STRING, vci_strlen); + num_vc_data++; + + opt_len = sizeof(struct dhcp6_option_vendor_class) + + sizeof(struct vendor_class_data) * num_vc_data + + vci_strlen; + break; + case DHCP6_OPTION_NII: + dhcp_option_start[0] = 1; + dhcp_option_start[1] = 0; + dhcp_option_start[2] = 0; + + opt_len = 3; + break; + default: + printf("***Warning unknown DHCP6 option %d. Not adding to message\n", option_id); + return 0; + } + dhcp_option->option_len = htons(opt_len); + + return opt_len + sizeof(struct dhcp6_option_hdr); +} + +/** + * dhcp6_send_solicit_packet() - Send a SOLICIT packet + * + * Implements RFC 8415: + * - 16.2. Solicit Message + * - 18.2.1. Creation and Transmission of Solicit Messages + * + * Adds DHCP6 header and DHCP6 options. Sends the UDP packet + * and sets the UDP handler. + */ +static void dhcp6_send_solicit_packet(void) +{ + struct in6_addr dhcp_bcast_ip6; + int len = 0; + uchar *pkt; + uchar *dhcp_pkt_start_ptr; + struct dhcp6_hdr *dhcp_hdr; + + pkt = net_tx_packet + net_eth_hdr_size() + IP6_HDR_SIZE + UDP_HDR_SIZE; + dhcp_pkt_start_ptr = pkt; + + /* Add the DHCP6 header */ + dhcp_hdr = (struct dhcp6_hdr *)pkt; + dhcp_hdr->msg_type = DHCP6_MSG_SOLICIT; + dhcp_hdr->trans_id = htons(sm_params.trans_id); + pkt += sizeof(struct dhcp6_hdr); + + /* Add the options */ + pkt += dhcp6_add_option(DHCP6_OPTION_CLIENTID, pkt); + pkt += dhcp6_add_option(DHCP6_OPTION_ELAPSED_TIME, pkt); + pkt += dhcp6_add_option(DHCP6_OPTION_IA_NA, pkt); + pkt += dhcp6_add_option(DHCP6_OPTION_ORO, pkt); + if (CONFIG_DHCP6_PXE_CLIENTARCH != 0xFF) + pkt += dhcp6_add_option(DHCP6_OPTION_CLIENT_ARCH_TYPE, pkt); + pkt += dhcp6_add_option(DHCP6_OPTION_VENDOR_CLASS, pkt); + pkt += dhcp6_add_option(DHCP6_OPTION_NII, pkt); + + /* calculate packet length */ + len = pkt - dhcp_pkt_start_ptr; + + /* send UDP packet to DHCP6 multicast address */ + string_to_ip6(DHCP6_MULTICAST_ADDR, sizeof(DHCP6_MULTICAST_ADDR), &dhcp_bcast_ip6); + net_set_udp_handler(dhcp6_handler); + net_send_udp_packet6((uchar *)net_bcast_ethaddr, &dhcp_bcast_ip6, + PORT_DHCP6_S, PORT_DHCP6_C, len); +} + +/** + * dhcp6_send_request_packet() - Send a REQUEST packet + * + * * Implements RFC 8415: + * - 16.4. Request Message + * - 18.2.2. Creation and Transmission of Request Messages + * + * Adds DHCP6 header and DHCP6 options. Sends the UDP packet + * and sets the UDP handler. + */ +static void dhcp6_send_request_packet(void) +{ + struct in6_addr dhcp_bcast_ip6; + int len = 0; + uchar *pkt; + uchar *dhcp_pkt_start_ptr; + struct dhcp6_hdr *dhcp_hdr; + + pkt = net_tx_packet + net_eth_hdr_size() + IP6_HDR_SIZE + UDP_HDR_SIZE; + dhcp_pkt_start_ptr = pkt; + + /* Add the DHCP6 header */ + dhcp_hdr = (struct dhcp6_hdr *)pkt; + dhcp_hdr->msg_type = DHCP6_MSG_REQUEST; + dhcp_hdr->trans_id = htons(sm_params.trans_id); + pkt += sizeof(struct dhcp6_hdr); + + /* add the options */ + pkt += dhcp6_add_option(DHCP6_OPTION_CLIENTID, pkt); + pkt += dhcp6_add_option(DHCP6_OPTION_ELAPSED_TIME, pkt); + pkt += dhcp6_add_option(DHCP6_OPTION_IA_NA, pkt); + pkt += dhcp6_add_option(DHCP6_OPTION_ORO, pkt); + /* copy received IA_TA/IA_NA into the REQUEST packet */ + if (sm_params.server_uid.uid_ptr) { + memcpy(pkt, sm_params.server_uid.uid_ptr, sm_params.server_uid.uid_size); + pkt += sm_params.server_uid.uid_size; + } + if (CONFIG_DHCP6_PXE_CLIENTARCH != 0xFF) + pkt += dhcp6_add_option(DHCP6_OPTION_CLIENT_ARCH_TYPE, pkt); + pkt += dhcp6_add_option(DHCP6_OPTION_VENDOR_CLASS, pkt); + pkt += dhcp6_add_option(DHCP6_OPTION_NII, pkt); + + /* calculate packet length */ + len = pkt - dhcp_pkt_start_ptr; + + /* send UDP packet to DHCP6 multicast address */ + string_to_ip6(DHCP6_MULTICAST_ADDR, strlen(DHCP6_MULTICAST_ADDR), &dhcp_bcast_ip6); + net_set_udp_handler(dhcp6_handler); + net_send_udp_packet6((uchar *)net_bcast_ethaddr, &dhcp_bcast_ip6, + PORT_DHCP6_S, PORT_DHCP6_C, len); +} + +static void dhcp6_parse_ia_options(struct dhcp6_option_hdr *ia_ptr, uchar *ia_option_ptr) +{ + struct dhcp6_option_hdr *ia_option_hdr; + + ia_option_hdr = (struct dhcp6_option_hdr *)ia_option_ptr; + + /* Search for options encapsulated in IA_NA/IA_TA (DHCP6_OPTION_IAADDR + * or DHCP6_OPTION_STATUS_CODE) + */ + while (ia_option_ptr < ((uchar *)ia_ptr + ntohs(ia_ptr->option_len))) { + switch (ntohs(ia_option_hdr->option_id)) { + case DHCP6_OPTION_IAADDR: + sm_params.rx_status.ia_addr_found = true; + net_copy_ip6(&sm_params.rx_status.ia_addr_ipv6, + (ia_option_ptr + sizeof(struct dhcp6_hdr))); + debug("DHCP6_OPTION_IAADDR FOUND\n"); + break; + case DHCP6_OPTION_STATUS_CODE: + sm_params.rx_status.ia_status_code = + ntohs(*((u16 *)(ia_option_ptr + sizeof(struct dhcp6_hdr)))); + printf("ERROR : IA STATUS %d\n", sm_params.rx_status.ia_status_code); + break; + default: + debug("Unknown Option in IA, skipping\n"); + break; + } + + ia_option_ptr += ntohs(((struct dhcp6_option_hdr *)ia_option_ptr)->option_len); + } +} + +/** + * dhcp6_parse_options() - Parse the DHCP6 options + * + * @rx_pkt: pointer to beginning of received DHCP6 packet + * @len: Total length of the DHCP6 packet + * + * Parses the DHCP options from a received DHCP packet. Perform error checking + * on the options received. Any relevant status is available in: + * "sm_params.rx_status" + * + */ +static void dhcp6_parse_options(uchar *rx_pkt, unsigned int len) +{ + uchar *option_ptr; + int sol_max_rt_sec, option_len; + char *s, *e; + struct dhcp6_option_hdr *option_hdr; + + memset(&sm_params.rx_status, 0, sizeof(struct dhcp6_rx_pkt_status)); + + option_hdr = (struct dhcp6_option_hdr *)(rx_pkt + sizeof(struct dhcp6_hdr)); + /* check that required options exist */ + while (option_hdr < (struct dhcp6_option_hdr *)(rx_pkt + len)) { + option_ptr = ((uchar *)option_hdr) + sizeof(struct dhcp6_hdr); + option_len = ntohs(option_hdr->option_len); + + switch (ntohs(option_hdr->option_id)) { + case DHCP6_OPTION_CLIENTID: + if (memcmp(option_ptr, sm_params.duid, option_len) + != 0) { + debug("CLIENT ID DOESN'T MATCH\n"); + } else { + debug("CLIENT ID FOUND and MATCHES\n"); + sm_params.rx_status.client_id_match = true; + } + break; + case DHCP6_OPTION_SERVERID: + sm_params.rx_status.server_id_found = true; + sm_params.rx_status.server_uid_ptr = (uchar *)option_hdr; + sm_params.rx_status.server_uid_size = option_len + + sizeof(struct dhcp6_option_hdr); + debug("SERVER ID FOUND\n"); + break; + case DHCP6_OPTION_IA_TA: + case DHCP6_OPTION_IA_NA: + /* check the IA_ID */ + if (*((u32 *)option_ptr) != htonl(sm_params.ia_id)) { + debug("IA_ID mismatch 0x%08x 0x%08x\n", + *((u32 *)option_ptr), htonl(sm_params.ia_id)); + break; + } + + if (ntohs(option_hdr->option_id) == DHCP6_OPTION_IA_NA) { + /* skip past IA_ID/T1/T2 */ + option_ptr += 3 * sizeof(u32); + } else if (ntohs(option_hdr->option_id) == DHCP6_OPTION_IA_TA) { + /* skip past IA_ID */ + option_ptr += sizeof(u32); + } + /* parse the IA_NA/IA_TA encapsulated options */ + dhcp6_parse_ia_options(option_hdr, option_ptr); + break; + case DHCP6_OPTION_STATUS_CODE: + debug("DHCP6_OPTION_STATUS_CODE FOUND\n"); + sm_params.rx_status.status_code = ntohs(*((u16 *)option_ptr)); + debug("DHCP6 top-level status code %d\n", sm_params.rx_status.status_code); + debug("DHCP6 status message: %.*s\n", len, option_ptr + 2); + break; + case DHCP6_OPTION_SOL_MAX_RT: + debug("DHCP6_OPTION_SOL_MAX_RT FOUND\n"); + sol_max_rt_sec = ntohl(*((u32 *)option_ptr)); + + /* A DHCP client MUST ignore any SOL_MAX_RT option values that are less + * than 60 or more than 86400 + */ + if (sol_max_rt_sec >= 60 && sol_max_rt_sec <= 86400) { + updated_sol_max_rt_ms = sol_max_rt_sec * 1000; + if (sm_params.curr_state == DHCP6_SOLICIT) + sm_params.mrt_ms = updated_sol_max_rt_ms; + } + break; + case DHCP6_OPTION_OPT_BOOTFILE_URL: + debug("DHCP6_OPTION_OPT_BOOTFILE_URL FOUND\n"); + copy_filename(net_boot_file_name, option_ptr, option_len + 1); + debug("net_boot_file_name: %s\n", net_boot_file_name); + + /* copy server_ip6 (required for PXE) */ + s = strchr(net_boot_file_name, '['); + e = strchr(net_boot_file_name, ']'); + if (s && e && e > s) + string_to_ip6(s + 1, e - s - 1, &net_server_ip6); + break; +#if IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION) + case DHCP6_OPTION_OPT_BOOTFILE_PARAM: + debug("DHCP6_OPTION_OPT_BOOTFILE_PARAM FOUND\n"); + + if (pxelinux_configfile) + free(pxelinux_configfile); + + pxelinux_configfile = (char *)malloc((option_len + 1) * sizeof(char)); + if (pxelinux_configfile) { + memcpy(pxelinux_configfile, option_ptr, option_len); + pxelinux_configfile[option_len] = '\0'; + } + + debug("PXE CONFIG FILE %s\n", pxelinux_configfile); + break; +#endif + case DHCP6_OPTION_PREFERENCE: + debug("DHCP6_OPTION_PREFERENCE FOUND\n"); + sm_params.rx_status.preference = *option_ptr; + break; + default: + debug("Unknown Option ID: %d, skipping parsing\n", + ntohs(option_hdr->option_id)); + break; + } + /* Increment to next option header */ + option_hdr = (struct dhcp6_option_hdr *)(((uchar *)option_hdr) + + sizeof(struct dhcp6_option_hdr) + option_len); + } +} + +/** + * dhcp6_check_advertise_packet() - Perform error checking on an expected + * ADVERTISE packet. + * + * @rx_pkt: pointer to beginning of received DHCP6 packet + * @len: Total length of the DHCP6 packet + * + * Implements RFC 8415: + * - 16.3. Advertise Message + * - 18.2.10. Receipt of Reply Messages + * + * Return : 0 : ADVERTISE packet was received with no errors. + * State machine can progress + * 1 : - packet received is not an ADVERTISE packet + * - there were errors in the packet received, + * - this is the first SOLICIT packet, but + * received preference is not 255, so we have + * to wait for more server responses. + */ +static int dhcp6_check_advertise_packet(uchar *rx_pkt, unsigned int len) +{ + u16 rx_uid_size; + struct dhcp6_hdr *dhcp6_hdr = (struct dhcp6_hdr *)rx_pkt; + + /* Ignore message if msg-type != advertise */ + if (dhcp6_hdr->msg_type != DHCP6_MSG_ADVERTISE) + return 1; + /* Ignore message if transaction ID doesn't match */ + if (dhcp6_hdr->trans_id != htons(sm_params.trans_id)) + return 1; + + dhcp6_parse_options(rx_pkt, len); + + /* Ignore advertise if any of these conditions met */ + if (!sm_params.rx_status.server_id_found || + !sm_params.rx_status.client_id_match || + sm_params.rx_status.status_code != DHCP6_SUCCESS) { + return 1; + } + + if (sm_params.rx_status.server_id_found) { + /* if no server UID has been received yet, or if the server UID + * received has a higher preference value than the currently saved + * server UID, save the new server UID and preference + */ + if (!sm_params.server_uid.uid_ptr || + (sm_params.server_uid.uid_ptr && + sm_params.server_uid.preference < sm_params.rx_status.preference)) { + rx_uid_size = sm_params.rx_status.server_uid_size; + if (sm_params.server_uid.uid_ptr) + free(sm_params.server_uid.uid_ptr); + sm_params.server_uid.uid_ptr = malloc(rx_uid_size * sizeof(uchar)); + if (sm_params.server_uid.uid_ptr) + memcpy(sm_params.server_uid.uid_ptr, + sm_params.rx_status.server_uid_ptr, rx_uid_size); + + sm_params.server_uid.uid_size = rx_uid_size; + sm_params.server_uid.preference = sm_params.rx_status.preference; + } + + /* If the first SOLICIT and preference code is 255, use right away. + * Otherwise, wait for the first SOLICIT period for more + * DHCP6 servers to respond. + */ + if (sm_params.retry_cnt == 1 && + sm_params.server_uid.preference != 255) { + debug("valid ADVERTISE, waiting for first SOLICIT period\n"); + return 1; + } + } + + return 0; +} + +/** + * dhcp6_check_reply_packet() - Perform error checking on an expected + * REPLY packet. + * + * @rx_pkt: pointer to beginning of received DHCP6 packet + * @len: Total length of the DHCP6 packet + * + * Implements RFC 8415: + * - 16.10. Reply Message + * - 18.2.10. Receipt of Reply Messages + * + * Return : 0 - REPLY packet was received with no errors + * 1 - packet received is not an REPLY packet, + * or there were errors in the packet received + */ +static int dhcp6_check_reply_packet(uchar *rx_pkt, unsigned int len) +{ + struct dhcp6_hdr *dhcp6_hdr = (struct dhcp6_hdr *)rx_pkt; + + /* Ignore message if msg-type != reply */ + if (dhcp6_hdr->msg_type != DHCP6_MSG_REPLY) + return 1; + /* check that transaction ID matches */ + if (dhcp6_hdr->trans_id != htons(sm_params.trans_id)) + return 1; + + dhcp6_parse_options(rx_pkt, len); + + /* if no addresses found, restart DHCP */ + if (!sm_params.rx_status.ia_addr_found || + sm_params.rx_status.ia_status_code == DHCP6_NO_ADDRS_AVAIL || + sm_params.rx_status.status_code == DHCP6_NOT_ON_LINK) { + /* restart DHCP */ + debug("No address found in reply. Restarting DHCP\n"); + dhcp6_start(); + } + + /* ignore reply if any of these conditions met */ + if (!sm_params.rx_status.server_id_found || + !sm_params.rx_status.client_id_match || + sm_params.rx_status.status_code == DHCP6_UNSPEC_FAIL) { + return 1; + } + + return 0; +} + +/** + * dhcp6_state_machine() - DHCP6 state machine + * + * @timeout: TRUE : timeout waiting for response from + * DHCP6 server + * FALSE : init or received response from DHCP6 server + * @rx_pkt: Pointer to the beginning of received DHCP6 packet. + * Will be NULL if called as part of init + * or timeout==TRUE + * @len: Total length of the DHCP6 packet if rx_pkt != NULL + * + * Implements RFC 8415: + * - 5.2. Client/Server Exchanges Involving Four Messages + * - 15. Reliability of Client-Initiated Message Exchanges + * + * Handles: + * - transmission of SOLICIT and REQUEST packets + * - retransmission of SOLICIT and REQUEST packets if no + * response is received within the timeout window + * - checking received ADVERTISE and REPLY packets to + * assess if the DHCP state machine can progress + */ +static void dhcp6_state_machine(bool timeout, uchar *rx_pkt, unsigned int len) +{ + int rand_minus_plus_100; + + switch (sm_params.curr_state) { + case DHCP6_INIT: + sm_params.next_state = DHCP6_SOLICIT; + break; + case DHCP6_SOLICIT: + if (!timeout) { + /* check the rx packet and determine if we can transition to next + * state. + */ + if (dhcp6_check_advertise_packet(rx_pkt, len)) + return; + + debug("ADVERTISE good, transition to REQUEST\n"); + sm_params.next_state = DHCP6_REQUEST; + } else if (sm_params.retry_cnt == 1) { + /* If a server UID was received in the first SOLICIT period + * transition to REQUEST + */ + if (sm_params.server_uid.uid_ptr) + sm_params.next_state = DHCP6_REQUEST; + } + break; + case DHCP6_REQUEST: + if (!timeout) { + /* check the rx packet and determine if we can transition to next state */ + if (dhcp6_check_reply_packet(rx_pkt, len)) + return; + + debug("REPLY good, transition to DONE\n"); + sm_params.next_state = DHCP6_DONE; + } + break; + case DHCP6_DONE: + case DHCP6_FAIL: + /* Shouldn't get here, as state machine should exit + * immediately when DHCP6_DONE or DHCP6_FAIL is entered. + * Proceed anyway to proceed DONE/FAIL actions + */ + debug("Unexpected DHCP6 state : %d\n", sm_params.curr_state); + break; + } + /* re-seed the RNG */ + srand(get_ticks() + rand()); + + /* handle state machine entry conditions */ + if (sm_params.curr_state != sm_params.next_state) { + sm_params.retry_cnt = 0; + + if (sm_params.next_state == DHCP6_SOLICIT) { + /* delay a random ammount (special for SOLICIT) */ + udelay((rand() % SOL_MAX_DELAY_MS) * 1000); + /* init timestamp variables after SOLICIT delay */ + sm_params.dhcp6_start_ms = get_timer(0); + sm_params.dhcp6_retry_start_ms = sm_params.dhcp6_start_ms; + sm_params.dhcp6_retry_ms = sm_params.dhcp6_start_ms; + /* init transaction and ia_id */ + sm_params.trans_id = rand() & 0xFFFFFF; + sm_params.ia_id = rand(); + /* initialize retransmission parameters */ + sm_params.irt_ms = SOL_TIMEOUT_MS; + sm_params.mrt_ms = updated_sol_max_rt_ms; + /* RFCs default MRC is be 0 (try infinitely) + * give up after CONFIG_NET_RETRY_COUNT number of tries (same as DHCPv4) + */ + sm_params.mrc = CONFIG_NET_RETRY_COUNT; + sm_params.mrd_ms = 0; + + } else if (sm_params.next_state == DHCP6_REQUEST) { + /* init timestamp variables */ + sm_params.dhcp6_retry_start_ms = get_timer(0); + sm_params.dhcp6_retry_ms = sm_params.dhcp6_start_ms; + /* initialize retransmission parameters */ + sm_params.irt_ms = REQ_TIMEOUT_MS; + sm_params.mrt_ms = REQ_MAX_RT_MS; + sm_params.mrc = REQ_MAX_RC; + sm_params.mrd_ms = 0; + } + } + + if (timeout) + sm_params.dhcp6_retry_ms = get_timer(0); + + /* Check if MRC or MRD have been passed */ + if ((sm_params.mrc != 0 && + sm_params.retry_cnt >= sm_params.mrc) || + (sm_params.mrd_ms != 0 && + ((sm_params.dhcp6_retry_ms - sm_params.dhcp6_retry_start_ms) >= sm_params.mrd_ms))) { + sm_params.next_state = DHCP6_FAIL; + } + + /* calculate retransmission timeout (RT) */ + rand_minus_plus_100 = ((rand() % 200) - 100); + if (sm_params.retry_cnt == 0) { + sm_params.rt_ms = sm_params.irt_ms + + ((sm_params.irt_ms * rand_minus_plus_100) / 1000); + } else { + sm_params.rt_ms = (2 * sm_params.rt_prev_ms) + + ((sm_params.rt_prev_ms * rand_minus_plus_100) / 1000); + } + + if (sm_params.rt_ms > sm_params.mrt_ms) { + sm_params.rt_ms = sm_params.mrt_ms + + ((sm_params.mrt_ms * rand_minus_plus_100) / 1000); + } + + sm_params.rt_prev_ms = sm_params.rt_ms; + + net_set_timeout_handler(sm_params.rt_ms, dhcp6_timeout_handler); + + /* send transmit/retransmit message or fail */ + sm_params.curr_state = sm_params.next_state; + + if (sm_params.curr_state == DHCP6_SOLICIT) { + /* send solicit packet */ + dhcp6_send_solicit_packet(); + printf("DHCP6 SOLICIT %d\n", sm_params.retry_cnt); + } else if (sm_params.curr_state == DHCP6_REQUEST) { + /* send request packet */ + dhcp6_send_request_packet(); + printf("DHCP6 REQUEST %d\n", sm_params.retry_cnt); + } else if (sm_params.curr_state == DHCP6_DONE) { + net_set_timeout_handler(0, NULL); + + /* Duplicate address detection (DAD) should be + * performed here before setting net_ip6 + * (enhancement should be considered) + */ + net_copy_ip6(&net_ip6, &sm_params.rx_status.ia_addr_ipv6); + printf("DHCP6 client bound to %pI6c\n", &net_ip6); + /* will load with TFTP6 */ + net_auto_load(); + } else if (sm_params.curr_state == DHCP6_FAIL) { + printf("DHCP6 FAILED, TERMINATING\n"); + net_set_state(NETLOOP_FAIL); + } + sm_params.retry_cnt++; +} + +/* + * Timeout for DHCP6 SOLICIT/REQUEST. + */ +static void dhcp6_timeout_handler(void) +{ + /* call state machine with the timeout flag */ + dhcp6_state_machine(true, NULL, 0); +} + +/* + * Start or restart DHCP6 + */ +void dhcp6_start(void) +{ + memset(&sm_params, 0, sizeof(struct dhcp6_sm_params)); + + /* seed the RNG with MAC address */ + srand_mac(); + + sm_params.curr_state = DHCP6_INIT; + dhcp6_state_machine(false, NULL, 0); +} diff --git a/net/dhcpv6.h b/net/dhcpv6.h new file mode 100644 index 0000000000..6a0158127a --- /dev/null +++ b/net/dhcpv6.h @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) Microsoft Corporation + * Author: Sean Edmond seanedmond@microsoft.com + * + */ + +#ifndef __DHCP6_H__ +#define __DHCP6_H__ + +#include <net6.h> +#include <net.h> + +/* Message types */ +#define DHCP6_MSG_SOLICIT 1 +#define DHCP6_MSG_ADVERTISE 2 +#define DHCP6_MSG_REQUEST 3 +#define DHCP6_MSG_REPLY 7 + +/* Option Codes */ +#define DHCP6_OPTION_CLIENTID 1 +#define DHCP6_OPTION_SERVERID 2 +#define DHCP6_OPTION_IA_NA 3 +#define DHCP6_OPTION_IA_TA 4 +#define DHCP6_OPTION_IAADDR 5 +#define DHCP6_OPTION_ORO 6 +#define DHCP6_OPTION_PREFERENCE 7 +#define DHCP6_OPTION_ELAPSED_TIME 8 +#define DHCP6_OPTION_STATUS_CODE 13 +#define DHCP6_OPTION_OPT_BOOTFILE_URL 59 +#define DHCP6_OPTION_OPT_BOOTFILE_PARAM 60 +#define DHCP6_OPTION_SOL_MAX_RT 82 +#define DHCP6_OPTION_CLIENT_ARCH_TYPE 61 +#define DHCP6_OPTION_VENDOR_CLASS 16 +#define DHCP6_OPTION_NII 62 + +/* DUID */ +#define DUID_TYPE_LL 3 +#define DUID_HW_TYPE_ENET 1 +#define DUID_LL_SIZE (sizeof(struct dhcp6_option_duid_ll) + ETH_ALEN) +#define DUID_MAX_SIZE DUID_LL_SIZE /* only supports DUID-LL currently */ + +/* vendor-class-data to send in vendor clas option */ +#define DHCP6_VCI_STRING "U-boot" + +#define DHCP6_MULTICAST_ADDR "ff02::1:2" /* DHCP multicast address */ + +/* DHCP6 States supported */ +enum dhcp6_state { + DHCP6_INIT, + DHCP6_SOLICIT, + DHCP6_REQUEST, + DHCP6_DONE, + DHCP6_FAIL, +}; + +/* DHCP6 Status codes */ +enum dhcp6_status { + DHCP6_SUCCESS = 0, + DHCP6_UNSPEC_FAIL = 1, + DHCP6_NO_ADDRS_AVAIL = 2, + DHCP6_NO_BINDING = 3, + DHCP6_NOT_ON_LINK = 4, + DHCP6_USE_MULTICAST = 5, + DHCP6_NO_PREFIX_AVAIL = 6, +}; + +/* DHCP6 message header format */ +struct dhcp6_hdr { + unsigned int msg_type : 8; /* message type */ + unsigned int trans_id : 24; /* transaction ID */ +} __packed; + +/* DHCP6 option header format */ +struct dhcp6_option_hdr { + __be16 option_id; /* option id */ + __be16 option_len; /* Option length */ + u8 option_data[0]; /* Option data */ +} __packed; + +/* DHCP6_OPTION_CLIENTID option (DUID-LL) */ +struct dhcp6_option_duid_ll { + __be16 duid_type; + __be16 hw_type; + u8 ll_addr[0]; +} __packed; + +/* DHCP6_OPTION_ELAPSED_TIME option */ +struct dhcp6_option_elapsed_time { + __be16 elapsed_time; +} __packed; + +/* DHCP6_OPTION_IA_TA option */ +struct dhcp6_option_ia_ta { + __be32 iaid; + u8 ia_ta_options[0]; +} __packed; + +/* DHCP6_OPTION_IA_NA option */ +struct dhcp6_option_ia_na { + __be32 iaid; + __be32 t1; + __be32 t2; + u8 ia_na_options[0]; +} __packed; + +/* OPTION_ORO option */ +struct dhcp6_option_oro { + __be16 req_option_code[0]; +} __packed; + +/* DHCP6_OPTION_CLIENT_ARCH_TYPE option */ +struct dhcp6_option_client_arch { + __be16 arch_type[0]; +} __packed; + +/* vendor-class-data inside OPTION_VENDOR_CLASS option */ +struct vendor_class_data { + __be16 vendor_class_len; + u8 opaque_data[0]; +} __packed; + +/* DHCP6_OPTION_VENDOR_CLASS option */ +struct dhcp6_option_vendor_class { + __be32 enterprise_number; + struct vendor_class_data vendor_class_data[0]; +} __packed; + +/** + * struct dhcp6_rx_pkt_status - Structure that holds status + * from a received message + * @client_id_match: Client ID was found and matches DUID sent + * @server_id_found: Server ID was found in the message + * @server_uid_ptr: Pointer to received server ID + * @server_uid_size: Size of received server ID + * @ia_addr_found: IA addr option was found in received message + * @ia_addr_ipv6: The IPv6 address received in IA + * @ia_status_code: Status code received in the IA + * @status_code: Top-level status code received + * @preference: Preference code received + */ +struct dhcp6_rx_pkt_status { + bool client_id_match; + bool server_id_found; + uchar *server_uid_ptr; + u16 server_uid_size; + bool ia_addr_found; + struct in6_addr ia_addr_ipv6; + enum dhcp6_status ia_status_code; + enum dhcp6_status status_code; + u8 preference; +}; + +/** + * struct dhcp6_server_uid - Structure that holds the server UID + * received from an ADVERTISE and saved + * given the server selection criteria. + * @uid_ptr: Dynamically allocated and copied server UID + * @uid_size: Size of the server UID in uid_ptr (in bytes) + * @preference: Preference code associated with this server UID + */ +struct dhcp6_server_uid { + uchar *uid_ptr; + u16 uid_size; + u8 preference; +}; + +/** + * struct dhcp6_sm_params - Structure that holds DHCP6 + * state machine parameters + * @curr_state: current DHCP6 state + * @next_state: next DHCP6 state + * @dhcp6_start_ms: timestamp DHCP6 start + * @dhcp6_retry_start_ms: timestamp of current TX message start + * @dhcp6_retry_ms: timestamp of last retransmission + * @retry_cnt: retry count + * @trans_id: transaction ID + * @ia_id: transmitted IA ID + * @irt_ms: Initial retransmission time (in ms) + * @mrt_ms: Maximum retransmission time (in ms) + * @mrc: Maximum retransmission count + * @mrd_ms: Maximum retransmission duration (in ms) + * @rt_ms: retransmission timeout (is ms) + * @rt_prev_ms: previous retransmission timeout + * @rx_status: Status from received message + * @server_uid: Saved Server UID for selected server + * @duid: pointer to transmitted Client DUID + */ +struct dhcp6_sm_params { + enum dhcp6_state curr_state; + enum dhcp6_state next_state; + ulong dhcp6_start_ms; + ulong dhcp6_retry_start_ms; + ulong dhcp6_retry_ms; + u32 retry_cnt; + u32 trans_id; + u32 ia_id; + int irt_ms; + int mrt_ms; + int mrc; + int mrd_ms; + int rt_ms; + int rt_prev_ms; + struct dhcp6_rx_pkt_status rx_status; + struct dhcp6_server_uid server_uid; + char duid[DUID_MAX_SIZE]; +}; + +/* Send a DHCPv6 request */ +void dhcp6_start(void); + +#endif /* __DHCP6_H__ */ diff --git a/net/net.c b/net/net.c index c9a749f6cc..73d5b2bc80 100644 --- a/net/net.c +++ b/net/net.c @@ -122,6 +122,9 @@ #endif #include <net/tcp.h> #include <net/wget.h> +#if defined(CONFIG_CMD_DHCP6) +#include "dhcpv6.h" +#endif
/** BOOTP EXTENTIONS **/
@@ -135,6 +138,10 @@ struct in_addr net_dns_server; /* Our 2nd DNS IP address */ struct in_addr net_dns_server2; #endif +#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION) +/* Indicates whether the pxe path prefix / config file was specified in dhcp option */ +char *pxelinux_configfile; +#endif
/** END OF BOOTP EXTENTIONS **/
@@ -510,6 +517,11 @@ restart: dhcp_request(); /* Basically same as BOOTP */ break; #endif +#if defined(CONFIG_CMD_DHCP6) + case DHCP6: + dhcp6_start(); + break; +#endif #if defined(CONFIG_CMD_BOOTP) case BOOTP: bootp_reset();

Hi,
On Fri, 7 Apr 2023 at 18:56, seanedmond@linux.microsoft.com wrote:
From: Sean Edmond seanedmond@microsoft.com
Adds DHCPv6 protocol to u-boot.
Allows for address assignement with DHCPv6 4-message exchange (SOLICIT->ADVERTISE->REQUEST->REPLY). Includes DHCPv6 options required by RFC 8415. Also adds DHCPv6 options required for PXE boot.
Possible enhancements:
- Duplicate address detection on DHCPv6 assigned address
- IPv6 address assignement through SLAAC
- Sending/parsing other DHCPv6 options (NTP, DNS, etc...)
Signed-off-by: Sean Edmond seanedmond@microsoft.com
include/net.h | 8 +- net/Makefile | 1 + net/dhcpv6.c | 735 ++++++++++++++++++++++++++++++++++++++++++++++++++ net/dhcpv6.h | 212 +++++++++++++++ net/net.c | 12 + 5 files changed, 966 insertions(+), 2 deletions(-) create mode 100644 net/dhcpv6.c create mode 100644 net/dhcpv6.h
This looks good to me. I just have a few nits below. With those fixed:
Reviewed-by: Simon Glass sjg@chromium.org
diff --git a/include/net.h b/include/net.h index 399af5e064..cac818e292 100644 --- a/include/net.h +++ b/include/net.h @@ -484,6 +484,10 @@ extern char net_hostname[32]; /* Our hostname */ #ifdef CONFIG_NET extern char net_root_path[CONFIG_BOOTP_MAX_ROOT_PATH_LEN]; /* Our root path */ #endif +#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION)
You can drop this #ifdef as any reference to a non-existent var will give a build error.
+/* Indicates whether the pxe path prefix / config file was specified in dhcp option */ +extern char *pxelinux_configfile; +#endif /** END OF BOOTP EXTENTIONS **/ extern u8 net_ethaddr[ARP_HLEN]; /* Our ethernet address */ extern u8 net_server_ethaddr[ARP_HLEN]; /* Boot server enet address */ @@ -504,8 +508,8 @@ extern ushort net_native_vlan; /* Our Native VLAN */ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t {
BOOTP, RARP, ARP, TFTPGET, DHCP, PING, PING6, DNS, NFS, CDP, NETCONS,
SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET
BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP,
NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET
};
extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/net/Makefile b/net/Makefile index bea000b206..5968110170 100644 --- a/net/Makefile +++ b/net/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_IPV6) += net6.o obj-$(CONFIG_CMD_NFS) += nfs.o obj-$(CONFIG_CMD_PING) += ping.o obj-$(CONFIG_CMD_PING6) += ping6.o +obj-$(CONFIG_CMD_DHCP6) += dhcpv6.o obj-$(CONFIG_CMD_PCAP) += pcap.o obj-$(CONFIG_CMD_RARP) += rarp.o obj-$(CONFIG_CMD_SNTP) += sntp.o diff --git a/net/dhcpv6.c b/net/dhcpv6.c new file mode 100644 index 0000000000..9204909c1f --- /dev/null +++ b/net/dhcpv6.c @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- Copyright (C) Microsoft Corporation
- Author: Sean Edmond seanedmond@microsoft.com
- */
+/* Simple DHCP6 network layer implementation. */
+#include <common.h> +#include <bootstage.h> +#include <command.h> +#include <env.h> +#include <efi_loader.h> +#include <log.h> +#include <net.h> +#include <rand.h> +#include <uuid.h> +#include <linux/delay.h> +#include <net/tftp.h> +#include "dhcpv6.h" +#include <net6.h> +#include <malloc.h> +#include "net_rand.h"
Please fix header order: https://u-boot.readthedocs.io/en/latest/develop/codingstyle.html#include-fil...
+#define PORT_DHCP6_S 547 /* DHCP6 server UDP port */ +#define PORT_DHCP6_C 546 /* DHCP6 client UDP port */
+/* default timeout parameters (in ms) */ +#define SOL_MAX_DELAY_MS 1000 +#define SOL_TIMEOUT_MS 1000 +#define SOL_MAX_RT_MS 3600000 +#define REQ_TIMEOUT_MS 1000 +#define REQ_MAX_RT_MS 30000 +#define REQ_MAX_RC 10 +#define MAX_WAIT_TIME_MS 60000
+/* global variable to track any updates from DHCP6 server */ +int updated_sol_max_rt_ms = SOL_MAX_RT_MS;
+static void dhcp6_timeout_handler(void); +static void dhcp6_state_machine(bool timeout, uchar *rx_pkt, unsigned int len); +static void dhcp6_parse_options(uchar *rx_pkt, unsigned int len);
Rather than forward decls can you reorder the functions?
+struct dhcp6_sm_params sm_params;
+/*
- Handle DHCP received packets (set as UDP handler)
- */
Please check single-line comment style
+static void dhcp6_handler(uchar *pkt, unsigned int dest, struct in_addr sip,
unsigned int src, unsigned int len)
+{
/* return if ports don't match DHCPv6 ports */
if (dest != PORT_DHCP6_C || src != PORT_DHCP6_S)
return;
dhcp6_state_machine(false, pkt, len);
+}
[..]
case DHCP6_OPTION_OPT_BOOTFILE_URL:
debug("DHCP6_OPTION_OPT_BOOTFILE_URL FOUND\n");
copy_filename(net_boot_file_name, option_ptr, option_len + 1);
debug("net_boot_file_name: %s\n", net_boot_file_name);
/* copy server_ip6 (required for PXE) */
s = strchr(net_boot_file_name, '[');
e = strchr(net_boot_file_name, ']');
if (s && e && e > s)
string_to_ip6(s + 1, e - s - 1, &net_server_ip6);
break;
+#if IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION)
case DHCP6_OPTION_OPT_BOOTFILE_PARAM:
Can you do something like this to avoid the #ifdef ?
case DHCP6_OPTION_OPT_BOOTFILE_PARAM: if (IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION) { ... }
You could even add a 'fallthough' if you move it to last position in the switch().
debug("DHCP6_OPTION_OPT_BOOTFILE_PARAM FOUND\n");
if (pxelinux_configfile)
free(pxelinux_configfile);
pxelinux_configfile = (char *)malloc((option_len + 1) * sizeof(char));
if (pxelinux_configfile) {
memcpy(pxelinux_configfile, option_ptr, option_len);
pxelinux_configfile[option_len] = '\0';
Does strlcpy() with option_len + 1 work here?
}
If malloc() fails it needs to be reported.
debug("PXE CONFIG FILE %s\n", pxelinux_configfile);
break;
+#endif
case DHCP6_OPTION_PREFERENCE:
debug("DHCP6_OPTION_PREFERENCE FOUND\n");
sm_params.rx_status.preference = *option_ptr;
break;
default:
debug("Unknown Option ID: %d, skipping parsing\n",
ntohs(option_hdr->option_id));
break;
}
/* Increment to next option header */
option_hdr = (struct dhcp6_option_hdr *)(((uchar *)option_hdr) +
sizeof(struct dhcp6_option_hdr) + option_len);
}
+}
[..]
+/*
- Timeout for DHCP6 SOLICIT/REQUEST.
Fix single-line comment format (globally)
- */
+static void dhcp6_timeout_handler(void) +{
/* call state machine with the timeout flag */
dhcp6_state_machine(true, NULL, 0);
+}
+/*
- Start or restart DHCP6
- */
+void dhcp6_start(void) +{
memset(&sm_params, 0, sizeof(struct dhcp6_sm_params));
/* seed the RNG with MAC address */
srand_mac();
sm_params.curr_state = DHCP6_INIT;
dhcp6_state_machine(false, NULL, 0);
+} diff --git a/net/dhcpv6.h b/net/dhcpv6.h new file mode 100644 index 0000000000..6a0158127a --- /dev/null +++ b/net/dhcpv6.h @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/*
- Copyright (C) Microsoft Corporation
- Author: Sean Edmond seanedmond@microsoft.com
- */
+#ifndef __DHCP6_H__ +#define __DHCP6_H__
+#include <net6.h>
Is this needed? If so it might be better to split out the bits you need into a net6_defs.h or something like that.
+#include <net.h>
Please don't include net.h as it brings in heaps of stuff.
+/* Message types */ +#define DHCP6_MSG_SOLICIT 1 +#define DHCP6_MSG_ADVERTISE 2 +#define DHCP6_MSG_REQUEST 3 +#define DHCP6_MSG_REPLY 7
[..]
+/* Send a DHCPv6 request */
Can you add a bit more detail here, e.g. mention the state machine and what happens next?
+void dhcp6_start(void);
+#endif /* __DHCP6_H__ */ diff --git a/net/net.c b/net/net.c index c9a749f6cc..73d5b2bc80 100644 --- a/net/net.c +++ b/net/net.c @@ -122,6 +122,9 @@ #endif #include <net/tcp.h> #include <net/wget.h> +#if defined(CONFIG_CMD_DHCP6) +#include "dhcpv6.h"
No need to exclude the header so you can drop the #ifdef
+#endif
/** BOOTP EXTENTIONS **/
@@ -135,6 +138,10 @@ struct in_addr net_dns_server; /* Our 2nd DNS IP address */ struct in_addr net_dns_server2; #endif +#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION) +/* Indicates whether the pxe path prefix / config file was specified in dhcp option */ +char *pxelinux_configfile; +#endif
/** END OF BOOTP EXTENTIONS **/
@@ -510,6 +517,11 @@ restart: dhcp_request(); /* Basically same as BOOTP */ break; #endif +#if defined(CONFIG_CMD_DHCP6)
case DHCP6:
Can we use the if (IS_ENABLED()) thing again here to drop this #ifdef?
dhcp6_start();
break;
+#endif #if defined(CONFIG_CMD_BOOTP) case BOOTP: bootp_reset(); -- 2.40.0
Regards, Simon

On Fri, Apr 7, 2023 at 9:55 PM Simon Glass sjg@chromium.org wrote:
Hi,
On Fri, 7 Apr 2023 at 18:56, seanedmond@linux.microsoft.com wrote:
From: Sean Edmond seanedmond@microsoft.com
Adds DHCPv6 protocol to u-boot.
Allows for address assignement with DHCPv6 4-message exchange (SOLICIT->ADVERTISE->REQUEST->REPLY). Includes DHCPv6 options required by RFC 8415. Also adds DHCPv6 options required for PXE boot.
Possible enhancements:
- Duplicate address detection on DHCPv6 assigned address
- IPv6 address assignement through SLAAC
- Sending/parsing other DHCPv6 options (NTP, DNS, etc...)
Signed-off-by: Sean Edmond seanedmond@microsoft.com
include/net.h | 8 +- net/Makefile | 1 + net/dhcpv6.c | 735 ++++++++++++++++++++++++++++++++++++++++++++++++++ net/dhcpv6.h | 212 +++++++++++++++ net/net.c | 12 + 5 files changed, 966 insertions(+), 2 deletions(-) create mode 100644 net/dhcpv6.c create mode 100644 net/dhcpv6.h
This looks good to me. I just have a few nits below. With those fixed:
Reviewed-by: Simon Glass sjg@chromium.org
diff --git a/include/net.h b/include/net.h index 399af5e064..cac818e292 100644 --- a/include/net.h +++ b/include/net.h @@ -484,6 +484,10 @@ extern char net_hostname[32]; /* Our hostname */ #ifdef CONFIG_NET extern char net_root_path[CONFIG_BOOTP_MAX_ROOT_PATH_LEN]; /* Our root path */ #endif +#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION)
You can drop this #ifdef as any reference to a non-existent var will give a build error.
+/* Indicates whether the pxe path prefix / config file was specified in dhcp option */ +extern char *pxelinux_configfile; +#endif /** END OF BOOTP EXTENTIONS **/ extern u8 net_ethaddr[ARP_HLEN]; /* Our ethernet address */ extern u8 net_server_ethaddr[ARP_HLEN]; /* Boot server enet address */ @@ -504,8 +508,8 @@ extern ushort net_native_vlan; /* Our Native VLAN */ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t {
BOOTP, RARP, ARP, TFTPGET, DHCP, PING, PING6, DNS, NFS, CDP, NETCONS,
SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET
BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP,
NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET
};
extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/net/Makefile b/net/Makefile index bea000b206..5968110170 100644 --- a/net/Makefile +++ b/net/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_IPV6) += net6.o obj-$(CONFIG_CMD_NFS) += nfs.o obj-$(CONFIG_CMD_PING) += ping.o obj-$(CONFIG_CMD_PING6) += ping6.o +obj-$(CONFIG_CMD_DHCP6) += dhcpv6.o obj-$(CONFIG_CMD_PCAP) += pcap.o obj-$(CONFIG_CMD_RARP) += rarp.o obj-$(CONFIG_CMD_SNTP) += sntp.o diff --git a/net/dhcpv6.c b/net/dhcpv6.c new file mode 100644 index 0000000000..9204909c1f --- /dev/null +++ b/net/dhcpv6.c @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- Copyright (C) Microsoft Corporation
- Author: Sean Edmond seanedmond@microsoft.com
- */
+/* Simple DHCP6 network layer implementation. */
+#include <common.h> +#include <bootstage.h> +#include <command.h> +#include <env.h> +#include <efi_loader.h> +#include <log.h> +#include <net.h> +#include <rand.h> +#include <uuid.h> +#include <linux/delay.h> +#include <net/tftp.h> +#include "dhcpv6.h" +#include <net6.h> +#include <malloc.h> +#include "net_rand.h"
Please fix header order: https://u-boot.readthedocs.io/en/latest/develop/codingstyle.html#include-fil...
+#define PORT_DHCP6_S 547 /* DHCP6 server UDP port */ +#define PORT_DHCP6_C 546 /* DHCP6 client UDP port */
+/* default timeout parameters (in ms) */ +#define SOL_MAX_DELAY_MS 1000 +#define SOL_TIMEOUT_MS 1000 +#define SOL_MAX_RT_MS 3600000 +#define REQ_TIMEOUT_MS 1000 +#define REQ_MAX_RT_MS 30000 +#define REQ_MAX_RC 10 +#define MAX_WAIT_TIME_MS 60000
+/* global variable to track any updates from DHCP6 server */ +int updated_sol_max_rt_ms = SOL_MAX_RT_MS;
+static void dhcp6_timeout_handler(void); +static void dhcp6_state_machine(bool timeout, uchar *rx_pkt, unsigned int len); +static void dhcp6_parse_options(uchar *rx_pkt, unsigned int len);
Rather than forward decls can you reorder the functions?
+struct dhcp6_sm_params sm_params;
+/*
- Handle DHCP received packets (set as UDP handler)
- */
Please check single-line comment style
+static void dhcp6_handler(uchar *pkt, unsigned int dest, struct in_addr sip,
unsigned int src, unsigned int len)
+{
/* return if ports don't match DHCPv6 ports */
if (dest != PORT_DHCP6_C || src != PORT_DHCP6_S)
return;
dhcp6_state_machine(false, pkt, len);
+}
[..]
case DHCP6_OPTION_OPT_BOOTFILE_URL:
debug("DHCP6_OPTION_OPT_BOOTFILE_URL FOUND\n");
copy_filename(net_boot_file_name, option_ptr, option_len + 1);
debug("net_boot_file_name: %s\n", net_boot_file_name);
/* copy server_ip6 (required for PXE) */
s = strchr(net_boot_file_name, '[');
e = strchr(net_boot_file_name, ']');
if (s && e && e > s)
string_to_ip6(s + 1, e - s - 1, &net_server_ip6);
break;
+#if IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION)
case DHCP6_OPTION_OPT_BOOTFILE_PARAM:
Can you do something like this to avoid the #ifdef ?
case DHCP6_OPTION_OPT_BOOTFILE_PARAM: if (IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION) { ... }
You could even add a 'fallthough' if you move it to last position in the switch().
debug("DHCP6_OPTION_OPT_BOOTFILE_PARAM FOUND\n");
if (pxelinux_configfile)
free(pxelinux_configfile);
pxelinux_configfile = (char *)malloc((option_len + 1) * sizeof(char));
if (pxelinux_configfile) {
memcpy(pxelinux_configfile, option_ptr, option_len);
pxelinux_configfile[option_len] = '\0';
Does strlcpy() with option_len + 1 work here?
}
If malloc() fails it needs to be reported.
debug("PXE CONFIG FILE %s\n", pxelinux_configfile);
break;
+#endif
case DHCP6_OPTION_PREFERENCE:
debug("DHCP6_OPTION_PREFERENCE FOUND\n");
sm_params.rx_status.preference = *option_ptr;
break;
default:
debug("Unknown Option ID: %d, skipping parsing\n",
ntohs(option_hdr->option_id));
break;
}
/* Increment to next option header */
option_hdr = (struct dhcp6_option_hdr *)(((uchar *)option_hdr) +
sizeof(struct dhcp6_option_hdr) + option_len);
}
+}
[..]
+/*
- Timeout for DHCP6 SOLICIT/REQUEST.
Fix single-line comment format (globally)
- */
+static void dhcp6_timeout_handler(void) +{
/* call state machine with the timeout flag */
dhcp6_state_machine(true, NULL, 0);
+}
+/*
- Start or restart DHCP6
- */
+void dhcp6_start(void) +{
memset(&sm_params, 0, sizeof(struct dhcp6_sm_params));
/* seed the RNG with MAC address */
srand_mac();
sm_params.curr_state = DHCP6_INIT;
dhcp6_state_machine(false, NULL, 0);
+} diff --git a/net/dhcpv6.h b/net/dhcpv6.h new file mode 100644 index 0000000000..6a0158127a --- /dev/null +++ b/net/dhcpv6.h @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/*
- Copyright (C) Microsoft Corporation
- Author: Sean Edmond seanedmond@microsoft.com
- */
+#ifndef __DHCP6_H__ +#define __DHCP6_H__
+#include <net6.h>
Is this needed? If so it might be better to split out the bits you need into a net6_defs.h or something like that.
+#include <net.h>
Please don't include net.h as it brings in heaps of stuff.
+/* Message types */ +#define DHCP6_MSG_SOLICIT 1 +#define DHCP6_MSG_ADVERTISE 2 +#define DHCP6_MSG_REQUEST 3 +#define DHCP6_MSG_REPLY 7
[..]
+/* Send a DHCPv6 request */
Can you add a bit more detail here, e.g. mention the state machine and what happens next?
+void dhcp6_start(void);
+#endif /* __DHCP6_H__ */ diff --git a/net/net.c b/net/net.c index c9a749f6cc..73d5b2bc80 100644 --- a/net/net.c +++ b/net/net.c @@ -122,6 +122,9 @@ #endif #include <net/tcp.h> #include <net/wget.h> +#if defined(CONFIG_CMD_DHCP6) +#include "dhcpv6.h"
No need to exclude the header so you can drop the #ifdef
+#endif
/** BOOTP EXTENTIONS **/
@@ -135,6 +138,10 @@ struct in_addr net_dns_server; /* Our 2nd DNS IP address */ struct in_addr net_dns_server2; #endif +#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION) +/* Indicates whether the pxe path prefix / config file was specified in dhcp option */ +char *pxelinux_configfile; +#endif
/** END OF BOOTP EXTENTIONS **/
@@ -510,6 +517,11 @@ restart: dhcp_request(); /* Basically same as BOOTP */ break; #endif +#if defined(CONFIG_CMD_DHCP6)
case DHCP6:
Can we use the if (IS_ENABLED()) thing again here to drop this #ifdef?
dhcp6_start();
break;
+#endif #if defined(CONFIG_CMD_BOOTP) case BOOTP: bootp_reset(); -- 2.40.0
Regards, Simon
Nicely done, please address Simon comments and I'll accept.

On 2023-04-25 12:03 p.m., Ramon Fried wrote:
On Fri, Apr 7, 2023 at 9:55 PM Simon Glass sjg@chromium.org wrote:
Hi,
On Fri, 7 Apr 2023 at 18:56, seanedmond@linux.microsoft.com wrote:
From: Sean Edmond seanedmond@microsoft.com
Adds DHCPv6 protocol to u-boot.
Allows for address assignement with DHCPv6 4-message exchange (SOLICIT->ADVERTISE->REQUEST->REPLY). Includes DHCPv6 options required by RFC 8415. Also adds DHCPv6 options required for PXE boot.
Possible enhancements:
- Duplicate address detection on DHCPv6 assigned address
- IPv6 address assignement through SLAAC
- Sending/parsing other DHCPv6 options (NTP, DNS, etc...)
Signed-off-by: Sean Edmond seanedmond@microsoft.com
include/net.h | 8 +- net/Makefile | 1 + net/dhcpv6.c | 735 ++++++++++++++++++++++++++++++++++++++++++++++++++ net/dhcpv6.h | 212 +++++++++++++++ net/net.c | 12 + 5 files changed, 966 insertions(+), 2 deletions(-) create mode 100644 net/dhcpv6.c create mode 100644 net/dhcpv6.h
This looks good to me. I just have a few nits below. With those fixed:
Reviewed-by: Simon Glass sjg@chromium.org
diff --git a/include/net.h b/include/net.h index 399af5e064..cac818e292 100644 --- a/include/net.h +++ b/include/net.h @@ -484,6 +484,10 @@ extern char net_hostname[32]; /* Our hostname */ #ifdef CONFIG_NET extern char net_root_path[CONFIG_BOOTP_MAX_ROOT_PATH_LEN]; /* Our root path */ #endif +#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION)
You can drop this #ifdef as any reference to a non-existent var will give a build error.
+/* Indicates whether the pxe path prefix / config file was specified in dhcp option */ +extern char *pxelinux_configfile; +#endif /** END OF BOOTP EXTENTIONS **/ extern u8 net_ethaddr[ARP_HLEN]; /* Our ethernet address */ extern u8 net_server_ethaddr[ARP_HLEN]; /* Boot server enet address */ @@ -504,8 +508,8 @@ extern ushort net_native_vlan; /* Our Native VLAN */ extern int net_restart_wrap; /* Tried all network devices */
enum proto_t {
BOOTP, RARP, ARP, TFTPGET, DHCP, PING, PING6, DNS, NFS, CDP, NETCONS,
SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET
BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP,
NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET
};
extern char net_boot_file_name[1024];/* Boot File name */
diff --git a/net/Makefile b/net/Makefile index bea000b206..5968110170 100644 --- a/net/Makefile +++ b/net/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_IPV6) += net6.o obj-$(CONFIG_CMD_NFS) += nfs.o obj-$(CONFIG_CMD_PING) += ping.o obj-$(CONFIG_CMD_PING6) += ping6.o +obj-$(CONFIG_CMD_DHCP6) += dhcpv6.o obj-$(CONFIG_CMD_PCAP) += pcap.o obj-$(CONFIG_CMD_RARP) += rarp.o obj-$(CONFIG_CMD_SNTP) += sntp.o diff --git a/net/dhcpv6.c b/net/dhcpv6.c new file mode 100644 index 0000000000..9204909c1f --- /dev/null +++ b/net/dhcpv6.c @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- Copyright (C) Microsoft Corporation
- Author: Sean Edmond seanedmond@microsoft.com
- */
+/* Simple DHCP6 network layer implementation. */
+#include <common.h> +#include <bootstage.h> +#include <command.h> +#include <env.h> +#include <efi_loader.h> +#include <log.h> +#include <net.h> +#include <rand.h> +#include <uuid.h> +#include <linux/delay.h> +#include <net/tftp.h> +#include "dhcpv6.h" +#include <net6.h> +#include <malloc.h> +#include "net_rand.h"
Please fix header order: https://u-boot.readthedocs.io/en/latest/develop/codingstyle.html#include-fil...
+#define PORT_DHCP6_S 547 /* DHCP6 server UDP port */ +#define PORT_DHCP6_C 546 /* DHCP6 client UDP port */
+/* default timeout parameters (in ms) */ +#define SOL_MAX_DELAY_MS 1000 +#define SOL_TIMEOUT_MS 1000 +#define SOL_MAX_RT_MS 3600000 +#define REQ_TIMEOUT_MS 1000 +#define REQ_MAX_RT_MS 30000 +#define REQ_MAX_RC 10 +#define MAX_WAIT_TIME_MS 60000
+/* global variable to track any updates from DHCP6 server */ +int updated_sol_max_rt_ms = SOL_MAX_RT_MS;
+static void dhcp6_timeout_handler(void); +static void dhcp6_state_machine(bool timeout, uchar *rx_pkt, unsigned int len); +static void dhcp6_parse_options(uchar *rx_pkt, unsigned int len);
Rather than forward decls can you reorder the functions?
+struct dhcp6_sm_params sm_params;
+/*
- Handle DHCP received packets (set as UDP handler)
- */
Please check single-line comment style
+static void dhcp6_handler(uchar *pkt, unsigned int dest, struct in_addr sip,
unsigned int src, unsigned int len)
+{
/* return if ports don't match DHCPv6 ports */
if (dest != PORT_DHCP6_C || src != PORT_DHCP6_S)
return;
dhcp6_state_machine(false, pkt, len);
+}
[..]
case DHCP6_OPTION_OPT_BOOTFILE_URL:
debug("DHCP6_OPTION_OPT_BOOTFILE_URL FOUND\n");
copy_filename(net_boot_file_name, option_ptr, option_len + 1);
debug("net_boot_file_name: %s\n", net_boot_file_name);
/* copy server_ip6 (required for PXE) */
s = strchr(net_boot_file_name, '[');
e = strchr(net_boot_file_name, ']');
if (s && e && e > s)
string_to_ip6(s + 1, e - s - 1, &net_server_ip6);
break;
+#if IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION)
case DHCP6_OPTION_OPT_BOOTFILE_PARAM:
Can you do something like this to avoid the #ifdef ?
case DHCP6_OPTION_OPT_BOOTFILE_PARAM: if (IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION) { ... }
You could even add a 'fallthough' if you move it to last position in the switch().
debug("DHCP6_OPTION_OPT_BOOTFILE_PARAM FOUND\n");
if (pxelinux_configfile)
free(pxelinux_configfile);
pxelinux_configfile = (char *)malloc((option_len + 1) * sizeof(char));
if (pxelinux_configfile) {
memcpy(pxelinux_configfile, option_ptr, option_len);
pxelinux_configfile[option_len] = '\0';
Does strlcpy() with option_len + 1 work here?
}
If malloc() fails it needs to be reported.
debug("PXE CONFIG FILE %s\n", pxelinux_configfile);
break;
+#endif
case DHCP6_OPTION_PREFERENCE:
debug("DHCP6_OPTION_PREFERENCE FOUND\n");
sm_params.rx_status.preference = *option_ptr;
break;
default:
debug("Unknown Option ID: %d, skipping parsing\n",
ntohs(option_hdr->option_id));
break;
}
/* Increment to next option header */
option_hdr = (struct dhcp6_option_hdr *)(((uchar *)option_hdr) +
sizeof(struct dhcp6_option_hdr) + option_len);
}
+}
[..]
+/*
- Timeout for DHCP6 SOLICIT/REQUEST.
Fix single-line comment format (globally)
- */
+static void dhcp6_timeout_handler(void) +{
/* call state machine with the timeout flag */
dhcp6_state_machine(true, NULL, 0);
+}
+/*
- Start or restart DHCP6
- */
+void dhcp6_start(void) +{
memset(&sm_params, 0, sizeof(struct dhcp6_sm_params));
/* seed the RNG with MAC address */
srand_mac();
sm_params.curr_state = DHCP6_INIT;
dhcp6_state_machine(false, NULL, 0);
+} diff --git a/net/dhcpv6.h b/net/dhcpv6.h new file mode 100644 index 0000000000..6a0158127a --- /dev/null +++ b/net/dhcpv6.h @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/*
- Copyright (C) Microsoft Corporation
- Author: Sean Edmond seanedmond@microsoft.com
- */
+#ifndef __DHCP6_H__ +#define __DHCP6_H__
+#include <net6.h>
Is this needed? If so it might be better to split out the bits you need into a net6_defs.h or something like that.
+#include <net.h>
Please don't include net.h as it brings in heaps of stuff.
+/* Message types */ +#define DHCP6_MSG_SOLICIT 1 +#define DHCP6_MSG_ADVERTISE 2 +#define DHCP6_MSG_REQUEST 3 +#define DHCP6_MSG_REPLY 7
[..]
+/* Send a DHCPv6 request */
Can you add a bit more detail here, e.g. mention the state machine and what happens next?
+void dhcp6_start(void);
+#endif /* __DHCP6_H__ */ diff --git a/net/net.c b/net/net.c index c9a749f6cc..73d5b2bc80 100644 --- a/net/net.c +++ b/net/net.c @@ -122,6 +122,9 @@ #endif #include <net/tcp.h> #include <net/wget.h> +#if defined(CONFIG_CMD_DHCP6) +#include "dhcpv6.h"
No need to exclude the header so you can drop the #ifdef
+#endif
/** BOOTP EXTENTIONS **/
@@ -135,6 +138,10 @@ struct in_addr net_dns_server; /* Our 2nd DNS IP address */ struct in_addr net_dns_server2; #endif +#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION) +/* Indicates whether the pxe path prefix / config file was specified in dhcp option */ +char *pxelinux_configfile; +#endif
/** END OF BOOTP EXTENTIONS **/
@@ -510,6 +517,11 @@ restart: dhcp_request(); /* Basically same as BOOTP */ break; #endif +#if defined(CONFIG_CMD_DHCP6)
case DHCP6:
Can we use the if (IS_ENABLED()) thing again here to drop this #ifdef?
dhcp6_start();
break;
+#endif #if defined(CONFIG_CMD_BOOTP) case BOOTP: bootp_reset(); -- 2.40.0
Regards, Simon
Nicely done, please address Simon comments and I'll accept.
All of Simon's comments were addressed in v3 of the patch series. Let me know if you need me to add more detail.

From: Sean Edmond seanedmond@microsoft.com
Adds commands to support DHCP and PXE with IPv6.
New configs added: - CMD_DHCP6 - DHCP6_PXE_CLIENTARCH - DHCP6_PXE_DHCP_OPTION - DHCP6_ENTERPRISE_ID
New commands added (when IPv6 is enabled): - dhcp6 - pxe get -ipv6 - pxe boot -ipv6
Signed-off-by: Sean Edmond seanedmond@microsoft.com --- boot/bootmeth_distro.c | 2 +- boot/bootmeth_pxe.c | 4 +- boot/pxe_utils.c | 3 +- cmd/Kconfig | 26 +++++++++++++ cmd/net.c | 23 +++++++++++ cmd/pxe.c | 86 +++++++++++++++++++++++++++++++++++++----- cmd/sysboot.c | 2 +- include/pxe_utils.h | 10 ++++- 8 files changed, 140 insertions(+), 16 deletions(-)
diff --git a/boot/bootmeth_distro.c b/boot/bootmeth_distro.c index 356929828b..b4b73ecbf5 100644 --- a/boot/bootmeth_distro.c +++ b/boot/bootmeth_distro.c @@ -150,7 +150,7 @@ static int distro_boot(struct udevice *dev, struct bootflow *bflow) info.dev = dev; info.bflow = bflow; ret = pxe_setup_ctx(&ctx, &cmdtp, distro_getfile, &info, true, - bflow->subdir); + bflow->subdir, false); if (ret) return log_msg_ret("ctx", -EINVAL);
diff --git a/boot/bootmeth_pxe.c b/boot/bootmeth_pxe.c index ecf8557af8..5a8af2bbd0 100644 --- a/boot/bootmeth_pxe.c +++ b/boot/bootmeth_pxe.c @@ -70,7 +70,7 @@ static int distro_pxe_read_bootflow(struct udevice *dev, struct bootflow *bflow) addr = simple_strtoul(addr_str, NULL, 16);
log_debug("calling pxe_get()\n"); - ret = pxe_get(addr, &bootdir, &size); + ret = pxe_get(addr, &bootdir, &size, false); log_debug("pxe_get() returned %d\n", ret); if (ret) return log_msg_ret("pxeb", ret); @@ -146,7 +146,7 @@ static int distro_pxe_boot(struct udevice *dev, struct bootflow *bflow) info.bflow = bflow; info.cmdtp = &cmdtp; ret = pxe_setup_ctx(ctx, &cmdtp, distro_pxe_getfile, &info, false, - bflow->subdir); + bflow->subdir, false); if (ret) return log_msg_ret("ctx", -EINVAL);
diff --git a/boot/pxe_utils.c b/boot/pxe_utils.c index 3a1e50f2b1..d13c47dd94 100644 --- a/boot/pxe_utils.c +++ b/boot/pxe_utils.c @@ -1578,7 +1578,7 @@ void handle_pxe_menu(struct pxe_context *ctx, struct pxe_menu *cfg)
int pxe_setup_ctx(struct pxe_context *ctx, struct cmd_tbl *cmdtp, pxe_getfile_func getfile, void *userdata, - bool allow_abs_path, const char *bootfile) + bool allow_abs_path, const char *bootfile, bool use_ipv6) { const char *last_slash; size_t path_len = 0; @@ -1588,6 +1588,7 @@ int pxe_setup_ctx(struct pxe_context *ctx, struct cmd_tbl *cmdtp, ctx->getfile = getfile; ctx->userdata = userdata; ctx->allow_abs_path = allow_abs_path; + ctx->use_ipv6 = use_ipv6;
/* figure out the boot directory, if there is one */ if (bootfile && strlen(bootfile) >= MAX_TFTP_PATH_LEN) diff --git a/cmd/Kconfig b/cmd/Kconfig index bab35fc667..8448cf3400 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1672,6 +1672,15 @@ config CMD_DHCP help Boot image via network using DHCP/TFTP protocol
+config CMD_DHCP6 + bool "dhcp6" + depends on IPV6 + help + Boot image via network using DHCPv6/TFTP protocol using IPv6. + + Will perform 4-message exchange with DHCPv6 server, requesting + the minimum required options to TFTP boot. Complies with RFC 8415. + config BOOTP_MAY_FAIL bool "Allow for the BOOTP/DHCP server to not be found" depends on CMD_BOOTP @@ -1785,6 +1794,23 @@ config BOOTP_VCI_STRING default "U-Boot.arm" if ARM default "U-Boot"
+if CMD_DHCP6 + +config DHCP6_PXE_CLIENTARCH + hex + default 0x16 if ARM64 + default 0x15 if ARM + default 0xFF + +config DHCP6_PXE_DHCP_OPTION + bool "Request & store 'pxe_configfile' from DHCP6 server" + +config DHCP6_ENTERPRISE_ID + int "Enterprise ID to send in DHCPv6 Vendor Class Option" + default 0 + +endif + config CMD_TFTPBOOT bool "tftpboot" default y diff --git a/cmd/net.c b/cmd/net.c index d5e20843dd..95529a9d12 100644 --- a/cmd/net.c +++ b/cmd/net.c @@ -111,6 +111,29 @@ U_BOOT_CMD( ); #endif
+#if defined(CONFIG_CMD_DHCP6) +static int do_dhcp6(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int i; + int dhcp_argc; + char *dhcp_argv[] = {NULL, NULL, NULL, NULL}; + + /* Add -ipv6 flag for autoload */ + for (i = 0; i < argc; i++) + dhcp_argv[i] = argv[i]; + dhcp_argc = argc + 1; + dhcp_argv[dhcp_argc - 1] = USE_IP6_CMD_PARAM; + + return netboot_common(DHCP6, cmdtp, dhcp_argc, dhcp_argv); +} + +U_BOOT_CMD(dhcp6, 3, 1, do_dhcp6, + "boot image via network using DHCPv6/TFTP protocol. \n" + "Use IPv6 hostIPaddr framed with [] brackets", + "[loadAddress] [[hostIPaddr:]bootfilename]"); +#endif + #if defined(CONFIG_CMD_DHCP) static int do_dhcp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) diff --git a/cmd/pxe.c b/cmd/pxe.c index db8e4697f2..71e6fd9633 100644 --- a/cmd/pxe.c +++ b/cmd/pxe.c @@ -8,6 +8,7 @@ #include <command.h> #include <fs.h> #include <net.h> +#include <net6.h>
#include "pxe_utils.h"
@@ -29,12 +30,20 @@ static int do_get_tftp(struct pxe_context *ctx, const char *file_path, { char *tftp_argv[] = {"tftp", NULL, NULL, NULL}; int ret; + int num_args;
tftp_argv[1] = file_addr; tftp_argv[2] = (void *)file_path; + if (ctx->use_ipv6) { + tftp_argv[3] = USE_IP6_CMD_PARAM; + num_args = 4; + } else { + num_args = 3; + }
- if (do_tftpb(ctx->cmdtp, 0, 3, tftp_argv)) + if (do_tftpb(ctx->cmdtp, 0, num_args, tftp_argv)) return -ENOENT; + ret = pxe_get_file_size(sizep); if (ret) return log_msg_ret("tftp", ret); @@ -43,6 +52,23 @@ static int do_get_tftp(struct pxe_context *ctx, const char *file_path, return 1; }
+#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION) +/* + * Looks for a pxe file with specified config file name, + * which is received from DHCPv4 option 209 or + * DHCPv6 option 60. + * + * Returns 1 on success or < 0 on error. + */ +static inline int pxe_dhcp_option_path(struct pxe_context *ctx, unsigned long pxefile_addr_r) +{ + int ret = get_pxe_file(ctx, pxelinux_configfile, pxefile_addr_r); + + free(pxelinux_configfile); + + return ret; +} +#endif /* * Looks for a pxe file with a name based on the pxeuuid environment variable. * @@ -105,15 +131,25 @@ static int pxe_ipaddr_paths(struct pxe_context *ctx, unsigned long pxefile_addr_ return -ENOENT; }
-int pxe_get(ulong pxefile_addr_r, char **bootdirp, ulong *sizep) +int pxe_get(ulong pxefile_addr_r, char **bootdirp, ulong *sizep, bool use_ipv6) { struct cmd_tbl cmdtp[] = {}; /* dummy */ struct pxe_context ctx; int i;
if (pxe_setup_ctx(&ctx, cmdtp, do_get_tftp, NULL, false, - env_get("bootfile"))) + env_get("bootfile"), use_ipv6)) return -ENOMEM; + +#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION) + if (pxelinux_configfile && use_ipv6) { + if (pxe_dhcp_option_path(&ctx, pxefile_addr_r) > 0) + goto done; + + goto error_exit; + } +#endif + /* * Keep trying paths until we successfully get a file we're looking * for. @@ -131,9 +167,12 @@ int pxe_get(ulong pxefile_addr_r, char **bootdirp, ulong *sizep) i++; }
+#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION) +error_exit: pxe_destroy_ctx(&ctx);
return -ENOENT; +#endif done: *bootdirp = env_get("bootfile");
@@ -169,9 +208,17 @@ do_pxe_get(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) char *fname; ulong size; int ret; + bool use_ipv6 = false;
- if (argc != 1) - return CMD_RET_USAGE; + if (IS_ENABLED(CONFIG_IPV6)) { + if (argc != 1 && argc != 2) + return CMD_RET_USAGE; + if (!strcmp(argv[argc - 1], USE_IP6_CMD_PARAM)) + use_ipv6 = true; + } else { + if (argc != 1) + return CMD_RET_USAGE; + }
pxefile_addr_str = from_env("pxefile_addr_r");
@@ -183,7 +230,7 @@ do_pxe_get(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) if (ret < 0) return 1;
- ret = pxe_get(pxefile_addr_r, &fname, &size); + ret = pxe_get(pxefile_addr_r, &fname, &size, use_ipv6); switch (ret) { case 0: printf("Config file '%s' found\n", fname); @@ -211,13 +258,19 @@ do_pxe_boot(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) char *pxefile_addr_str; struct pxe_context ctx; int ret; + bool use_ipv6 = false; + + if (IS_ENABLED(CONFIG_IPV6)) { + if (!strcmp(argv[argc - 1], USE_IP6_CMD_PARAM)) + use_ipv6 = true; + }
- if (argc == 1) { + if (argc == 1 || (argc == 2 && use_ipv6)) { pxefile_addr_str = from_env("pxefile_addr_r"); if (!pxefile_addr_str) return 1;
- } else if (argc == 2) { + } else if (argc == 2 || (argc == 3 && use_ipv6)) { pxefile_addr_str = argv[1]; } else { return CMD_RET_USAGE; @@ -229,7 +282,7 @@ do_pxe_boot(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) }
if (pxe_setup_ctx(&ctx, cmdtp, do_get_tftp, NULL, false, - env_get("bootfile"))) { + env_get("bootfile"), use_ipv6)) { printf("Out of memory\n"); return CMD_RET_FAILURE; } @@ -244,8 +297,13 @@ do_pxe_boot(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) }
static struct cmd_tbl cmd_pxe_sub[] = { +#if IS_ENABLED(CONFIG_IPV6) + U_BOOT_CMD_MKENT(get, 2, 1, do_pxe_get, "", ""), + U_BOOT_CMD_MKENT(boot, 3, 1, do_pxe_boot, "", "") +#else U_BOOT_CMD_MKENT(get, 1, 1, do_pxe_get, "", ""), U_BOOT_CMD_MKENT(boot, 2, 1, do_pxe_boot, "", "") +#endif };
static void __maybe_unused pxe_reloc(void) @@ -281,9 +339,19 @@ static int do_pxe(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) return CMD_RET_USAGE; }
+#if IS_ENABLED(CONFIG_IPV6) +U_BOOT_CMD(pxe, 4, 1, do_pxe, + "commands to get and boot from pxe files\n" + "To use IPv6 add -ipv6 parameter", + "get [" USE_IP6_CMD_PARAM "] - try to retrieve a pxe file using tftp\n" + "pxe boot [pxefile_addr_r] [-ipv6] - boot from the pxe file at pxefile_addr_r\n" +); +#else U_BOOT_CMD(pxe, 3, 1, do_pxe, "commands to get and boot from pxe files", "get - try to retrieve a pxe file using tftp\n" "pxe boot [pxefile_addr_r] - boot from the pxe file at pxefile_addr_r\n" ); #endif + +#endif /* CONFIG_CMD_NET */ diff --git a/cmd/sysboot.c b/cmd/sysboot.c index 04c0702026..63a7806deb 100644 --- a/cmd/sysboot.c +++ b/cmd/sysboot.c @@ -101,7 +101,7 @@ static int do_sysboot(struct cmd_tbl *cmdtp, int flag, int argc, }
if (pxe_setup_ctx(&ctx, cmdtp, sysboot_read_file, &info, true, - filename)) { + filename, false)) { printf("Out of memory\n"); return CMD_RET_FAILURE; } diff --git a/include/pxe_utils.h b/include/pxe_utils.h index 1e5e8424f5..9f19593048 100644 --- a/include/pxe_utils.h +++ b/include/pxe_utils.h @@ -93,6 +93,7 @@ typedef int (*pxe_getfile_func)(struct pxe_context *ctx, const char *file_path, * @bootdir: Directory that files are loaded from ("" if no directory). This is * allocated * @pxe_file_size: Size of the PXE file + * @use_ipv6: TRUE : use IPv6 addressing, FALSE : use IPv4 addressing */ struct pxe_context { struct cmd_tbl *cmdtp; @@ -112,6 +113,7 @@ struct pxe_context { bool allow_abs_path; char *bootdir; ulong pxe_file_size; + bool use_ipv6; };
/** @@ -209,12 +211,14 @@ int format_mac_pxe(char *outbuf, size_t outbuf_len); * @allow_abs_path: true to allow absolute paths * @bootfile: Bootfile whose directory loaded files are relative to, NULL if * none + * @use_ipv6: TRUE : use IPv6 addressing + * FALSE : use IPv4 addressing * Return: 0 if OK, -ENOMEM if out of memory, -E2BIG if bootfile is larger than * MAX_TFTP_PATH_LEN bytes */ int pxe_setup_ctx(struct pxe_context *ctx, struct cmd_tbl *cmdtp, pxe_getfile_func getfile, void *userdata, - bool allow_abs_path, const char *bootfile); + bool allow_abs_path, const char *bootfile, bool use_ipv6);
/** * pxe_destroy_ctx() - Destroy a PXE context @@ -251,7 +255,9 @@ int pxe_get_file_size(ulong *sizep); * "rpi/info", which indicates that all files should be fetched from the * "rpi/" subdirectory * @sizep: Size of the PXE file (not bootfile) + * @use_ipv6: TRUE : use IPv6 addressing + * FALSE : use IPv4 addressing */ -int pxe_get(ulong pxefile_addr_r, char **bootdirp, ulong *sizep); +int pxe_get(ulong pxefile_addr_r, char **bootdirp, ulong *sizep, bool use_ipv6);
#endif /* __PXE_UTILS_H */

Hi Sean,
On Fri, 7 Apr 2023 at 18:56, seanedmond@linux.microsoft.com wrote:
From: Sean Edmond seanedmond@microsoft.com
Adds commands to support DHCP and PXE with IPv6.
New configs added:
- CMD_DHCP6
- DHCP6_PXE_CLIENTARCH
- DHCP6_PXE_DHCP_OPTION
- DHCP6_ENTERPRISE_ID
New commands added (when IPv6 is enabled):
- dhcp6
- pxe get -ipv6
- pxe boot -ipv6
Signed-off-by: Sean Edmond seanedmond@microsoft.com
boot/bootmeth_distro.c | 2 +- boot/bootmeth_pxe.c | 4 +- boot/pxe_utils.c | 3 +- cmd/Kconfig | 26 +++++++++++++ cmd/net.c | 23 +++++++++++ cmd/pxe.c | 86 +++++++++++++++++++++++++++++++++++++----- cmd/sysboot.c | 2 +- include/pxe_utils.h | 10 ++++- 8 files changed, 140 insertions(+), 16 deletions(-)
With nits below:
Reviewed-by: Simon Glass sjg@chromium.org
[..]
+if CMD_DHCP6
+config DHCP6_PXE_CLIENTARCH
hex
default 0x16 if ARM64
default 0x15 if ARM
default 0xFF
Do we need a separate option or could we use BOOTP_PXE_CLIENTARCH ?
+config DHCP6_PXE_DHCP_OPTION
bool "Request & store 'pxe_configfile' from DHCP6 server"
+config DHCP6_ENTERPRISE_ID
int "Enterprise ID to send in DHCPv6 Vendor Class Option"
default 0
+endif
config CMD_TFTPBOOT bool "tftpboot" default y diff --git a/cmd/net.c b/cmd/net.c index d5e20843dd..95529a9d12 100644 --- a/cmd/net.c +++ b/cmd/net.c @@ -111,6 +111,29 @@ U_BOOT_CMD( ); #endif
+#if defined(CONFIG_CMD_DHCP6) +static int do_dhcp6(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
+{
int i;
int dhcp_argc;
char *dhcp_argv[] = {NULL, NULL, NULL, NULL};
/* Add -ipv6 flag for autoload */
for (i = 0; i < argc; i++)
dhcp_argv[i] = argv[i];
dhcp_argc = argc + 1;
dhcp_argv[dhcp_argc - 1] = USE_IP6_CMD_PARAM;
return netboot_common(DHCP6, cmdtp, dhcp_argc, dhcp_argv);
+}
+U_BOOT_CMD(dhcp6, 3, 1, do_dhcp6,
"boot image via network using DHCPv6/TFTP protocol. \n"
"Use IPv6 hostIPaddr framed with [] brackets",
"[loadAddress] [[hostIPaddr:]bootfilename]");
+#endif
#if defined(CONFIG_CMD_DHCP) static int do_dhcp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) diff --git a/cmd/pxe.c b/cmd/pxe.c index db8e4697f2..71e6fd9633 100644 --- a/cmd/pxe.c +++ b/cmd/pxe.c @@ -8,6 +8,7 @@ #include <command.h> #include <fs.h> #include <net.h> +#include <net6.h>
#include "pxe_utils.h"
@@ -29,12 +30,20 @@ static int do_get_tftp(struct pxe_context *ctx, const char *file_path, { char *tftp_argv[] = {"tftp", NULL, NULL, NULL}; int ret;
int num_args; tftp_argv[1] = file_addr; tftp_argv[2] = (void *)file_path;
if (ctx->use_ipv6) {
tftp_argv[3] = USE_IP6_CMD_PARAM;
num_args = 4;
} else {
num_args = 3;
}
if (do_tftpb(ctx->cmdtp, 0, 3, tftp_argv))
if (do_tftpb(ctx->cmdtp, 0, num_args, tftp_argv)) return -ENOENT;
ret = pxe_get_file_size(sizep); if (ret) return log_msg_ret("tftp", ret);
@@ -43,6 +52,23 @@ static int do_get_tftp(struct pxe_context *ctx, const char *file_path, return 1; }
+#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION) +/*
- Looks for a pxe file with specified config file name,
- which is received from DHCPv4 option 209 or
- DHCPv6 option 60.
- Returns 1 on success or < 0 on error.
- */
+static inline int pxe_dhcp_option_path(struct pxe_context *ctx, unsigned long pxefile_addr_r)
Please drop the inline as the compiler can handle that.
+{
int ret = get_pxe_file(ctx, pxelinux_configfile, pxefile_addr_r);
free(pxelinux_configfile);
return ret;
+} +#endif /*
- Looks for a pxe file with a name based on the pxeuuid environment variable.
@@ -105,15 +131,25 @@ static int pxe_ipaddr_paths(struct pxe_context *ctx, unsigned long pxefile_addr_ return -ENOENT; }
-int pxe_get(ulong pxefile_addr_r, char **bootdirp, ulong *sizep) +int pxe_get(ulong pxefile_addr_r, char **bootdirp, ulong *sizep, bool use_ipv6) { struct cmd_tbl cmdtp[] = {}; /* dummy */ struct pxe_context ctx; int i;
if (pxe_setup_ctx(&ctx, cmdtp, do_get_tftp, NULL, false,
env_get("bootfile")))
env_get("bootfile"), use_ipv6)) return -ENOMEM;
+#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION)
It's a bit annoying that pxelinux_configfile causes these #ifdefs. How about always having pxelinux_configfile and then you don't need to worry. It can just be NULL when not used. It is only BSS space, after all...
if (pxelinux_configfile && use_ipv6) {
if (pxe_dhcp_option_path(&ctx, pxefile_addr_r) > 0)
goto done;
goto error_exit;
}
+#endif
/* * Keep trying paths until we successfully get a file we're looking * for.
@@ -131,9 +167,12 @@ int pxe_get(ulong pxefile_addr_r, char **bootdirp, ulong *sizep) i++; }
+#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION)
...then you can use if (IS_ENABLED(... here
+error_exit: pxe_destroy_ctx(&ctx);
return -ENOENT;
+#endif done: *bootdirp = env_get("bootfile");
@@ -169,9 +208,17 @@ do_pxe_get(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) char *fname; ulong size; int ret;
bool use_ipv6 = false;
if (argc != 1)
return CMD_RET_USAGE;
if (IS_ENABLED(CONFIG_IPV6)) {
if (argc != 1 && argc != 2)
return CMD_RET_USAGE;
if (!strcmp(argv[argc - 1], USE_IP6_CMD_PARAM))
use_ipv6 = true;
} else {
if (argc != 1)
return CMD_RET_USAGE;
} pxefile_addr_str = from_env("pxefile_addr_r");
@@ -183,7 +230,7 @@ do_pxe_get(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) if (ret < 0) return 1;
ret = pxe_get(pxefile_addr_r, &fname, &size);
ret = pxe_get(pxefile_addr_r, &fname, &size, use_ipv6); switch (ret) { case 0: printf("Config file '%s' found\n", fname);
@@ -211,13 +258,19 @@ do_pxe_boot(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) char *pxefile_addr_str; struct pxe_context ctx; int ret;
bool use_ipv6 = false;
if (IS_ENABLED(CONFIG_IPV6)) {
if (!strcmp(argv[argc - 1], USE_IP6_CMD_PARAM))
use_ipv6 = true;
}
if (argc == 1) {
if (argc == 1 || (argc == 2 && use_ipv6)) { pxefile_addr_str = from_env("pxefile_addr_r"); if (!pxefile_addr_str) return 1;
} else if (argc == 2) {
} else if (argc == 2 || (argc == 3 && use_ipv6)) { pxefile_addr_str = argv[1]; } else { return CMD_RET_USAGE;
@@ -229,7 +282,7 @@ do_pxe_boot(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) }
if (pxe_setup_ctx(&ctx, cmdtp, do_get_tftp, NULL, false,
env_get("bootfile"))) {
env_get("bootfile"), use_ipv6)) { printf("Out of memory\n"); return CMD_RET_FAILURE; }
@@ -244,8 +297,13 @@ do_pxe_boot(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) }
static struct cmd_tbl cmd_pxe_sub[] = { +#if IS_ENABLED(CONFIG_IPV6)
U_BOOT_CMD_MKENT(get, 2, 1, do_pxe_get, "", ""),
U_BOOT_CMD_MKENT(boot, 3, 1, do_pxe_boot, "", "")
+#else U_BOOT_CMD_MKENT(get, 1, 1, do_pxe_get, "", ""), U_BOOT_CMD_MKENT(boot, 2, 1, do_pxe_boot, "", "") +#endif
That's a bit ugly. How about using the max and then checking it in the C code?
};
static void __maybe_unused pxe_reloc(void) @@ -281,9 +339,19 @@ static int do_pxe(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) return CMD_RET_USAGE; }
+#if IS_ENABLED(CONFIG_IPV6) +U_BOOT_CMD(pxe, 4, 1, do_pxe,
"commands to get and boot from pxe files\n"
"To use IPv6 add -ipv6 parameter",
"get [" USE_IP6_CMD_PARAM "] - try to retrieve a pxe file using tftp\n"
"pxe boot [pxefile_addr_r] [-ipv6] - boot from the pxe file at pxefile_addr_r\n"
+); +#else U_BOOT_CMD(pxe, 3, 1, do_pxe, "commands to get and boot from pxe files", "get - try to retrieve a pxe file using tftp\n" "pxe boot [pxefile_addr_r] - boot from the pxe file at pxefile_addr_r\n" ); #endif
same here
+#endif /* CONFIG_CMD_NET */
[..]
Regards, Simon

On 2023-04-07 11:55 a.m., Simon Glass wrote:
Hi Sean,
On Fri, 7 Apr 2023 at 18:56, seanedmond@linux.microsoft.com wrote:
From: Sean Edmond seanedmond@microsoft.com
Adds commands to support DHCP and PXE with IPv6.
New configs added:
- CMD_DHCP6
- DHCP6_PXE_CLIENTARCH
- DHCP6_PXE_DHCP_OPTION
- DHCP6_ENTERPRISE_ID
New commands added (when IPv6 is enabled):
- dhcp6
- pxe get -ipv6
- pxe boot -ipv6
Signed-off-by: Sean Edmond seanedmond@microsoft.com
boot/bootmeth_distro.c | 2 +- boot/bootmeth_pxe.c | 4 +- boot/pxe_utils.c | 3 +- cmd/Kconfig | 26 +++++++++++++ cmd/net.c | 23 +++++++++++ cmd/pxe.c | 86 +++++++++++++++++++++++++++++++++++++----- cmd/sysboot.c | 2 +- include/pxe_utils.h | 10 ++++- 8 files changed, 140 insertions(+), 16 deletions(-)
With nits below:
Reviewed-by: Simon Glass sjg@chromium.org
[..]
+if CMD_DHCP6
+config DHCP6_PXE_CLIENTARCH
hex
default 0x16 if ARM64
default 0x15 if ARM
default 0xFF
Do we need a separate option or could we use BOOTP_PXE_CLIENTARCH ?
I created a new option because I wanted to change the default to 0xFF ("undefined" Processor Architecture Types according to https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xml). I wanted to do this without changing BOOTP_PXE_CLIENTARCH , and potentially disrupting exisiting DHCPv4 implementations.
+config DHCP6_PXE_DHCP_OPTION
bool "Request & store 'pxe_configfile' from DHCP6 server"
+config DHCP6_ENTERPRISE_ID
int "Enterprise ID to send in DHCPv6 Vendor Class Option"
default 0
+endif
- config CMD_TFTPBOOT bool "tftpboot" default y
diff --git a/cmd/net.c b/cmd/net.c index d5e20843dd..95529a9d12 100644 --- a/cmd/net.c +++ b/cmd/net.c @@ -111,6 +111,29 @@ U_BOOT_CMD( ); #endif
+#if defined(CONFIG_CMD_DHCP6) +static int do_dhcp6(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
+{
int i;
int dhcp_argc;
char *dhcp_argv[] = {NULL, NULL, NULL, NULL};
/* Add -ipv6 flag for autoload */
for (i = 0; i < argc; i++)
dhcp_argv[i] = argv[i];
dhcp_argc = argc + 1;
dhcp_argv[dhcp_argc - 1] = USE_IP6_CMD_PARAM;
return netboot_common(DHCP6, cmdtp, dhcp_argc, dhcp_argv);
+}
+U_BOOT_CMD(dhcp6, 3, 1, do_dhcp6,
"boot image via network using DHCPv6/TFTP protocol. \n"
"Use IPv6 hostIPaddr framed with [] brackets",
"[loadAddress] [[hostIPaddr:]bootfilename]");
+#endif
- #if defined(CONFIG_CMD_DHCP) static int do_dhcp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
diff --git a/cmd/pxe.c b/cmd/pxe.c index db8e4697f2..71e6fd9633 100644 --- a/cmd/pxe.c +++ b/cmd/pxe.c @@ -8,6 +8,7 @@ #include <command.h> #include <fs.h> #include <net.h> +#include <net6.h>
#include "pxe_utils.h"
@@ -29,12 +30,20 @@ static int do_get_tftp(struct pxe_context *ctx, const char *file_path, { char *tftp_argv[] = {"tftp", NULL, NULL, NULL}; int ret;
int num_args; tftp_argv[1] = file_addr; tftp_argv[2] = (void *)file_path;
if (ctx->use_ipv6) {
tftp_argv[3] = USE_IP6_CMD_PARAM;
num_args = 4;
} else {
num_args = 3;
}
if (do_tftpb(ctx->cmdtp, 0, 3, tftp_argv))
if (do_tftpb(ctx->cmdtp, 0, num_args, tftp_argv)) return -ENOENT;
ret = pxe_get_file_size(sizep); if (ret) return log_msg_ret("tftp", ret);
@@ -43,6 +52,23 @@ static int do_get_tftp(struct pxe_context *ctx, const char *file_path, return 1; }
+#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION) +/*
- Looks for a pxe file with specified config file name,
- which is received from DHCPv4 option 209 or
- DHCPv6 option 60.
- Returns 1 on success or < 0 on error.
- */
+static inline int pxe_dhcp_option_path(struct pxe_context *ctx, unsigned long pxefile_addr_r)
Please drop the inline as the compiler can handle that.
+{
int ret = get_pxe_file(ctx, pxelinux_configfile, pxefile_addr_r);
free(pxelinux_configfile);
return ret;
+} +#endif /*
- Looks for a pxe file with a name based on the pxeuuid environment variable.
@@ -105,15 +131,25 @@ static int pxe_ipaddr_paths(struct pxe_context *ctx, unsigned long pxefile_addr_ return -ENOENT; }
-int pxe_get(ulong pxefile_addr_r, char **bootdirp, ulong *sizep) +int pxe_get(ulong pxefile_addr_r, char **bootdirp, ulong *sizep, bool use_ipv6) { struct cmd_tbl cmdtp[] = {}; /* dummy */ struct pxe_context ctx; int i;
if (pxe_setup_ctx(&ctx, cmdtp, do_get_tftp, NULL, false,
env_get("bootfile")))
env_get("bootfile"), use_ipv6)) return -ENOMEM;
+#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION)
It's a bit annoying that pxelinux_configfile causes these #ifdefs. How about always having pxelinux_configfile and then you don't need to worry. It can just be NULL when not used. It is only BSS space, after all...
if (pxelinux_configfile && use_ipv6) {
if (pxe_dhcp_option_path(&ctx, pxefile_addr_r) > 0)
goto done;
goto error_exit;
}
+#endif
/* * Keep trying paths until we successfully get a file we're looking * for.
@@ -131,9 +167,12 @@ int pxe_get(ulong pxefile_addr_r, char **bootdirp, ulong *sizep) i++; }
+#if defined(CONFIG_DHCP6_PXE_DHCP_OPTION)
...then you can use if (IS_ENABLED(... here
+error_exit: pxe_destroy_ctx(&ctx);
return -ENOENT;
+#endif done: *bootdirp = env_get("bootfile");
@@ -169,9 +208,17 @@ do_pxe_get(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) char *fname; ulong size; int ret;
bool use_ipv6 = false;
if (argc != 1)
return CMD_RET_USAGE;
if (IS_ENABLED(CONFIG_IPV6)) {
if (argc != 1 && argc != 2)
return CMD_RET_USAGE;
if (!strcmp(argv[argc - 1], USE_IP6_CMD_PARAM))
use_ipv6 = true;
} else {
if (argc != 1)
return CMD_RET_USAGE;
} pxefile_addr_str = from_env("pxefile_addr_r");
@@ -183,7 +230,7 @@ do_pxe_get(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) if (ret < 0) return 1;
ret = pxe_get(pxefile_addr_r, &fname, &size);
ret = pxe_get(pxefile_addr_r, &fname, &size, use_ipv6); switch (ret) { case 0: printf("Config file '%s' found\n", fname);
@@ -211,13 +258,19 @@ do_pxe_boot(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) char *pxefile_addr_str; struct pxe_context ctx; int ret;
bool use_ipv6 = false;
if (IS_ENABLED(CONFIG_IPV6)) {
if (!strcmp(argv[argc - 1], USE_IP6_CMD_PARAM))
use_ipv6 = true;
}
if (argc == 1) {
if (argc == 1 || (argc == 2 && use_ipv6)) { pxefile_addr_str = from_env("pxefile_addr_r"); if (!pxefile_addr_str) return 1;
} else if (argc == 2) {
} else if (argc == 2 || (argc == 3 && use_ipv6)) { pxefile_addr_str = argv[1]; } else { return CMD_RET_USAGE;
@@ -229,7 +282,7 @@ do_pxe_boot(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) }
if (pxe_setup_ctx(&ctx, cmdtp, do_get_tftp, NULL, false,
env_get("bootfile"))) {
env_get("bootfile"), use_ipv6)) { printf("Out of memory\n"); return CMD_RET_FAILURE; }
@@ -244,8 +297,13 @@ do_pxe_boot(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) }
static struct cmd_tbl cmd_pxe_sub[] = { +#if IS_ENABLED(CONFIG_IPV6)
U_BOOT_CMD_MKENT(get, 2, 1, do_pxe_get, "", ""),
U_BOOT_CMD_MKENT(boot, 3, 1, do_pxe_boot, "", "")
+#else U_BOOT_CMD_MKENT(get, 1, 1, do_pxe_get, "", ""), U_BOOT_CMD_MKENT(boot, 2, 1, do_pxe_boot, "", "") +#endif
That's a bit ugly. How about using the max and then checking it in the C code?
};
static void __maybe_unused pxe_reloc(void) @@ -281,9 +339,19 @@ static int do_pxe(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) return CMD_RET_USAGE; }
+#if IS_ENABLED(CONFIG_IPV6) +U_BOOT_CMD(pxe, 4, 1, do_pxe,
"commands to get and boot from pxe files\n"
"To use IPv6 add -ipv6 parameter",
"get [" USE_IP6_CMD_PARAM "] - try to retrieve a pxe file using tftp\n"
"pxe boot [pxefile_addr_r] [-ipv6] - boot from the pxe file at pxefile_addr_r\n"
+); +#else U_BOOT_CMD(pxe, 3, 1, do_pxe, "commands to get and boot from pxe files", "get - try to retrieve a pxe file using tftp\n" "pxe boot [pxefile_addr_r] - boot from the pxe file at pxefile_addr_r\n" ); #endif
same here
+#endif /* CONFIG_CMD_NET */
[..]
Regards, Simon

Hi Sean,
On Mon, 10 Apr 2023 at 18:03, Sean Edmond seanedmond@linux.microsoft.com wrote:
On 2023-04-07 11:55 a.m., Simon Glass wrote:
Hi Sean,
On Fri, 7 Apr 2023 at 18:56, seanedmond@linux.microsoft.com wrote:
From: Sean Edmond seanedmond@microsoft.com
Adds commands to support DHCP and PXE with IPv6.
New configs added:
- CMD_DHCP6
- DHCP6_PXE_CLIENTARCH
- DHCP6_PXE_DHCP_OPTION
- DHCP6_ENTERPRISE_ID
New commands added (when IPv6 is enabled):
- dhcp6
- pxe get -ipv6
- pxe boot -ipv6
Signed-off-by: Sean Edmond seanedmond@microsoft.com
boot/bootmeth_distro.c | 2 +- boot/bootmeth_pxe.c | 4 +- boot/pxe_utils.c | 3 +- cmd/Kconfig | 26 +++++++++++++ cmd/net.c | 23 +++++++++++ cmd/pxe.c | 86 +++++++++++++++++++++++++++++++++++++----- cmd/sysboot.c | 2 +- include/pxe_utils.h | 10 ++++- 8 files changed, 140 insertions(+), 16 deletions(-)
With nits below:
Reviewed-by: Simon Glass sjg@chromium.org
[..]
+if CMD_DHCP6
+config DHCP6_PXE_CLIENTARCH
hex
default 0x16 if ARM64
default 0x15 if ARM
default 0xFF
Do we need a separate option or could we use BOOTP_PXE_CLIENTARCH ?
I created a new option because I wanted to change the default to 0xFF ("undefined" Processor Architecture Types according to https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xml). I wanted to do this without changing BOOTP_PXE_CLIENTARCH , and potentially disrupting exisiting DHCPv4 implementations.
OK, I see.
Regards, Simon

From: Sean Edmond seanedmond@microsoft.com
Requires proper environment with DHCP6 server provisioned.
Signed-off-by: Sean Edmond seanedmond@microsoft.com --- configs/sandbox_defconfig | 1 + test/py/tests/test_net.py | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+)
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 3a1f14c60f..f65965f864 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -340,3 +340,4 @@ CONFIG_TEST_FDTDEC=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y +CONFIG_CMD_DHCP6=y diff --git a/test/py/tests/test_net.py b/test/py/tests/test_net.py index 9ca6743afd..0447c0b2e0 100644 --- a/test/py/tests/test_net.py +++ b/test/py/tests/test_net.py @@ -29,6 +29,11 @@ env__net_uses_pci = True # set to False. env__net_dhcp_server = True
+# True if a DHCPv6 server is attached to the network, and should be tested. +# If DHCPv6 testing is not possible or desired, this variable may be omitted or +# set to False. +env__net_dhcp6_server = True + # A list of environment variables that should be set in order to configure a # static IP. If solely relying on DHCP, this variable may be omitted or set to # an empty list. @@ -58,6 +63,7 @@ env__net_nfs_readable_file = { """
net_set_up = False +net6_set_up = False
def test_net_pre_commands(u_boot_console): """Execute any commands required to enable network hardware. @@ -93,6 +99,25 @@ def test_net_dhcp(u_boot_console): global net_set_up net_set_up = True
+@pytest.mark.buildconfigspec('cmd_dhcp6') +def test_net_dhcp6(u_boot_console): + """Test the dhcp6 command. + + The boardenv_* file may be used to enable/disable this test; see the + comment at the beginning of this file. + """ + + test_dhcp6 = u_boot_console.config.env.get('env__net_dhcp6_server', False) + if not test_dhcp6: + pytest.skip('No DHCP6 server available') + + u_boot_console.run_command('setenv autoload no') + output = u_boot_console.run_command('dhcp6') + assert 'DHCP6 client bound to ' in output + + global net6_set_up + net6_set_up = True + @pytest.mark.buildconfigspec('net') def test_net_setup_static(u_boot_console): """Set up a static IP configuration.

On Fri, 7 Apr 2023 at 18:56, seanedmond@linux.microsoft.com wrote:
From: Sean Edmond seanedmond@microsoft.com
Requires proper environment with DHCP6 server provisioned.
Signed-off-by: Sean Edmond seanedmond@microsoft.com
configs/sandbox_defconfig | 1 + test/py/tests/test_net.py | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+)
Reviewed-by: Simon Glass sjg@chromium.org

On Fri, Apr 7, 2023 at 9:55 PM Simon Glass sjg@chromium.org wrote:
On Fri, 7 Apr 2023 at 18:56, seanedmond@linux.microsoft.com wrote:
From: Sean Edmond seanedmond@microsoft.com
Requires proper environment with DHCP6 server provisioned.
Signed-off-by: Sean Edmond seanedmond@microsoft.com
configs/sandbox_defconfig | 1 + test/py/tests/test_net.py | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+)
Reviewed-by: Simon Glass sjg@chromium.org
Acked-by: Ramon Fried rfried.dev@gmail.com
participants (4)
-
Ramon Fried
-
Sean Edmond
-
seanedmond@linux.microsoft.com
-
Simon Glass