// 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); if (option_ptr + option_len > rx_pkt + len) { debug("Invalid option length\n"); return; } 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); }