From a0245818f7f8e375abc00f36ff88326331e4e2f9 Mon Sep 17 00:00:00 2001 From: Sean Edmond Date: Tue, 11 Apr 2023 10:48:46 -0700 Subject: [PATCH] net: dhcp6: Add DHCPv6 (DHCP for IPv6) 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 Reviewed-by: Ramon Fried --- include/net.h | 6 +- net/Makefile | 1 + net/dhcpv6.c | 719 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ net/dhcpv6.h | 256 +++++++++++++++++++++ net/net.c | 11 +- 5 files changed, 989 insertions(+), 4 deletions(-) create mode 100644 net/dhcpv6.c create mode 100644 net/dhcpv6.h diff --git a/include/net.h b/include/net.h index 399af5e..181a6e3 100644 --- a/include/net.h +++ b/include/net.h @@ -484,6 +484,8 @@ 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 +/* Indicates whether the pxe path prefix / config file was specified in dhcp option */ +extern char *pxelinux_configfile; /** 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 +506,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 bea000b..5968110 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 0000000..0d1c600 --- /dev/null +++ b/net/dhcpv6.c @@ -0,0 +1,719 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) Microsoft Corporation + * Author: Sean Edmond + * + */ + +/* Simple DHCP6 network layer implementation. */ + +#include +#include +#include +#include +#include "net_rand.h" +#include "dhcpv6.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; +/* state machine parameters/variables */ +struct dhcp6_sm_params sm_params; + +static void dhcp6_state_machine(bool timeout, uchar *rx_pkt, unsigned int len); + +/* 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; + case DHCP6_OPTION_OPT_BOOTFILE_PARAM: + if (IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION)) { + 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) + strlcpy(pxelinux_configfile, option_ptr, option_len + 1); + else + printf("Error: Failed to allocate pxelinux_configfile\n"); + + debug("PXE CONFIG FILE %s\n", pxelinux_configfile); + } + break; + 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; +} + +/* Timeout for DHCP6 SOLICIT/REQUEST */ +static void dhcp6_timeout_handler(void) +{ + /* call state machine with the timeout flag */ + dhcp6_state_machine(true, NULL, 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++; +} + +/* 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 0000000..80ca520 --- /dev/null +++ b/net/dhcpv6.h @@ -0,0 +1,256 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) Microsoft Corporation + * Author: Sean Edmond + * + */ + +#ifndef __DHCP6_H__ +#define __DHCP6_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]; +}; + +/* Starts a DHCPv6 4-message exchange as a DHCPv6 client. On successful exchange, + * the DHCPv6 state machine will transition from internal states: + * DHCP6_INIT->DHCP6_SOLICIT->DHCP6_REQUEST->DHCP6_DONE + * + * Transmitted SOLICIT and REQUEST packets will set/request the minimum required + * DHCPv6 options to PXE boot. + * + * After a successful exchange, the DHCPv6 assigned address will be set in net_ip6 + * + * Additionally, the following will be set after receiving these options: + * DHCP6_OPTION_OPT_BOOTFILE_URL (option 59) -> net_server_ip6, net_boot_file_name + * DHCP6_OPTION_OPT_BOOTFILE_PARAM (option 60) - > pxelinux_configfile + * + * Illustration of a 4-message exchange with 2 servers (copied from + * https://www.rfc-editor.org/rfc/rfc8415): + * + * Server Server + * (not selected) Client (selected) + * + * v v v + * | | | + * | Begins initialization | + * | | | + * start of | _____________/|\_____________ | + * 4-message |/ Solicit | Solicit \| + * exchange | | | + * Determines | Determines + * configuration | configuration + * | | | + * |\ | ____________/| + * | \________ | /Advertise | + * | Advertise\ |/ | + * | \ | | + * | Collects Advertises | + * | \ | | + * | Selects configuration | + * | | | + * | _____________/|\_____________ | + * |/ Request | Request \| + * | | | + * | | Commits configuration + * | | | + * end of | | _____________/| + * 4-message | |/ Reply | + * exchange | | | + * | Initialization complete | + * | | | + */ +void dhcp6_start(void); + +#endif /* __DHCP6_H__ */ diff --git a/net/net.c b/net/net.c index c9a749f..8cb8b4b 100644 --- a/net/net.c +++ b/net/net.c @@ -107,6 +107,8 @@ #include #include #include +#include +#include #include "arp.h" #include "bootp.h" #include "cdp.h" @@ -120,8 +122,7 @@ #if defined(CONFIG_CMD_WOL) #include "wol.h" #endif -#include -#include +#include "dhcpv6.h" /** BOOTP EXTENTIONS **/ @@ -135,6 +136,8 @@ struct in_addr net_dns_server; /* Our 2nd DNS IP address */ struct in_addr net_dns_server2; #endif +/* Indicates whether the pxe path prefix / config file was specified in dhcp option */ +char *pxelinux_configfile; /** END OF BOOTP EXTENTIONS **/ @@ -510,6 +513,10 @@ restart: dhcp_request(); /* Basically same as BOOTP */ break; #endif + case DHCP6: + if (IS_ENABLED(CONFIG_CMD_DHCP6)) + dhcp6_start(); + break; #if defined(CONFIG_CMD_BOOTP) case BOOTP: bootp_reset(); -- 2.7.4