/* * Copyright (c) 2023 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ /** @file * @brief DHCPv6 client implementation */ #include LOG_MODULE_REGISTER(net_dhcpv6, CONFIG_NET_DHCPV6_LOG_LEVEL); #include #include #include #include #include "dhcpv6_internal.h" #include "ipv6.h" #include "net_private.h" #include "udp_internal.h" /* Maximum number of options client can request. */ #define DHCPV6_MAX_OPTION_REQUEST 2 struct dhcpv6_options_include { bool clientid : 1; bool serverid : 1; bool elapsed_time : 1; bool ia_na : 1; bool iaaddr : 1; bool ia_pd : 1; bool iaprefix : 1; uint16_t oro[DHCPV6_MAX_OPTION_REQUEST]; }; static K_MUTEX_DEFINE(lock); /* All_DHCP_Relay_Agents_and_Servers (ff02::1:2) */ static const struct in6_addr all_dhcpv6_ra_and_servers = { { { 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, 0, 0x02 } } }; static sys_slist_t dhcpv6_ifaces = SYS_SLIST_STATIC_INIT(&dhcpv6_ifaces); static struct k_work_delayable dhcpv6_timeout_work; static struct net_mgmt_event_callback dhcpv6_mgmt_cb; const char *net_dhcpv6_state_name(enum net_dhcpv6_state state) { static const char * const name[] = { "disabled", "init", "soliciting", "requesting", "confirming", "renewing", "rebinding", "information requesting", "bound", }; __ASSERT_NO_MSG(state >= 0 && state < sizeof(name)); return name[state]; } static void dhcpv6_generate_tid(struct net_if *iface) { sys_rand_get(iface->config.dhcpv6.tid, sizeof(iface->config.dhcpv6.tid)); } static void dhcvp6_update_deadlines(struct net_if *iface, int64_t now, uint32_t t1, uint32_t t2, uint32_t preferred_lifetime, uint32_t valid_lifetime) { uint64_t t1_abs, t2_abs, expire_abs; /* In case server does not set T1/T2 values, the time choice is left to * the client discretion. * Here, we use recommendations for the servers, where it's advised to * set T1/T2 as 0.5 and 0.8 of the preferred lifetime. */ if (t1 == 0 && t2 == 0) { if (preferred_lifetime == DHCPV6_INFINITY) { t1 = DHCPV6_INFINITY; t2 = DHCPV6_INFINITY; } else { t1 = preferred_lifetime * 0.5; t2 = preferred_lifetime * 0.8; } } else if (t1 == 0) { if (t2 == DHCPV6_INFINITY) { t1 = DHCPV6_INFINITY; } else { t1 = t2 * 0.625; /* 0.5 / 0.8 */ } } else if (t2 == 0) { if (t1 == DHCPV6_INFINITY) { t2 = DHCPV6_INFINITY; } else { t2 = t1 * 1.6; /* 0.8 / 0.5 */ /* Overflow check. */ if (t2 < t1) { t2 = DHCPV6_INFINITY; } } } else if (t1 >= t2) { NET_ERR("Invalid T1(%u)/T2(%u) values.", t1, t2); return; } if (t1 == DHCPV6_INFINITY || u64_add_overflow(now, 1000ULL * t1, &t1_abs)) { t1_abs = UINT64_MAX; } if (t2 == DHCPV6_INFINITY || u64_add_overflow(now, 1000ULL * t2, &t2_abs)) { t2_abs = UINT64_MAX; } if (valid_lifetime == DHCPV6_INFINITY || u64_add_overflow(now, 1000ULL * valid_lifetime, &expire_abs)) { expire_abs = UINT64_MAX; } if (iface->config.dhcpv6.t1 > t1_abs) { iface->config.dhcpv6.t1 = t1_abs; } if (iface->config.dhcpv6.t2 > t2_abs) { iface->config.dhcpv6.t2 = t2_abs; } if (iface->config.dhcpv6.expire < expire_abs) { iface->config.dhcpv6.expire = expire_abs; } } static void dhcpv6_set_timeout(struct net_if *iface, uint64_t timeout) { int64_t now = k_uptime_get(); NET_DBG("sched dhcpv6 timeout iface=%p timeout=%llums", iface, timeout); if (u64_add_overflow(now, timeout, &iface->config.dhcpv6.timeout)) { iface->config.dhcpv6.timeout = UINT64_MAX; } } static void dhcpv6_reschedule(void) { k_work_reschedule(&dhcpv6_timeout_work, K_NO_WAIT); } static int randomize_timeout(int multiplier, int timeout) { int factor; /* DHCPv6 RFC8415, ch. 15. the randomization factor should be a random * number between -0.1 nand +0.1. As we operate on integers here, we * scale it to -100 and +100, and divide the result by 1000. */ factor = (int)(sys_rand32_get() % 201) - 100; return (multiplier * timeout) + ((factor * timeout) / 1000); } static int dhcpv6_initial_retransmit_time(int init_retransmit_time) { /* DHCPv6 RFC8415, ch. 15. Retransmission time for the first msg. */ return randomize_timeout(1, init_retransmit_time); } static uint32_t dhcpv6_next_retransmit_time(int prev_retransmit_time, int max_retransmit_time) { int retransmit_time; /* DHCPv6 RFC8415, ch. 15. Retransmission time for the subsequent msg. */ retransmit_time = randomize_timeout(2, prev_retransmit_time); if (max_retransmit_time == 0) { return retransmit_time; } if (retransmit_time > max_retransmit_time) { retransmit_time = randomize_timeout(1, max_retransmit_time); } return retransmit_time; } /* DHCPv6 packet encoding functions */ static int dhcpv6_add_header(struct net_pkt *pkt, enum dhcpv6_msg_type type, uint8_t *tid) { int ret; ret = net_pkt_write_u8(pkt, type); if (ret < 0) { return ret; } ret = net_pkt_write(pkt, tid, DHCPV6_TID_SIZE); return ret; } static int dhcpv6_add_option_header(struct net_pkt *pkt, enum dhcpv6_option_code code, uint16_t length) { int ret; ret = net_pkt_write_be16(pkt, code); if (ret < 0) { return ret; } ret = net_pkt_write_be16(pkt, length); return ret; } static int dhcpv6_add_option_clientid(struct net_pkt *pkt, struct net_dhcpv6_duid_storage *clientid) { int ret; ret = dhcpv6_add_option_header(pkt, DHCPV6_OPTION_CODE_CLIENTID, clientid->length); if (ret < 0) { return ret; } ret = net_pkt_write(pkt, &clientid->duid, clientid->length); return ret; } static int dhcpv6_add_option_serverid(struct net_pkt *pkt, struct net_dhcpv6_duid_storage *serverid) { int ret; ret = dhcpv6_add_option_header(pkt, DHCPV6_OPTION_CODE_SERVERID, serverid->length); if (ret < 0) { return ret; } ret = net_pkt_write(pkt, &serverid->duid, serverid->length); return ret; } static int dhcpv6_add_option_elapsed_time(struct net_pkt *pkt, uint64_t since) { uint64_t elapsed; int ret; ret = dhcpv6_add_option_header(pkt, DHCPV6_OPTION_CODE_ELAPSED_TIME, DHCPV6_OPTION_ELAPSED_TIME_SIZE); if (ret < 0) { return ret; } /* Elapsed time should be expressed in hundredths of a second. */ elapsed = (k_uptime_get() - since) / 10ULL; if (elapsed > 0xFFFF) { elapsed = 0xFFFF; } ret = net_pkt_write_be16(pkt, (uint16_t)elapsed); return ret; } static int dhcpv6_add_option_ia_na(struct net_pkt *pkt, struct dhcpv6_ia_na *ia_na, bool include_addr) { uint16_t optlen; int ret; if (!include_addr) { optlen = DHCPV6_OPTION_IA_NA_HEADER_SIZE; } else { optlen = DHCPV6_OPTION_IA_NA_HEADER_SIZE + DHCPV6_OPTION_HEADER_SIZE + DHCPV6_OPTION_IAADDR_HEADER_SIZE; } ret = dhcpv6_add_option_header(pkt, DHCPV6_OPTION_CODE_IA_NA, optlen); if (ret < 0) { return ret; } ret = net_pkt_write_be32(pkt, ia_na->iaid); if (ret < 0) { return ret; } ret = net_pkt_write_be32(pkt, ia_na->t1); if (ret < 0) { return ret; } ret = net_pkt_write_be32(pkt, ia_na->t2); if (ret < 0) { return ret; } if (!include_addr) { return 0; } ret = dhcpv6_add_option_header(pkt, DHCPV6_OPTION_CODE_IAADDR, DHCPV6_OPTION_IAADDR_HEADER_SIZE); if (ret < 0) { return ret; } ret = net_pkt_write(pkt, &ia_na->iaaddr.addr, sizeof(ia_na->iaaddr.addr)); if (ret < 0) { return ret; } ret = net_pkt_write_be32(pkt, ia_na->iaaddr.preferred_lifetime); if (ret < 0) { return ret; } ret = net_pkt_write_be32(pkt, ia_na->iaaddr.valid_lifetime); return ret; } static int dhcpv6_add_option_ia_pd(struct net_pkt *pkt, struct dhcpv6_ia_pd *ia_pd, bool include_prefix) { uint16_t optlen; int ret; if (!include_prefix) { optlen = DHCPV6_OPTION_IA_PD_HEADER_SIZE; } else { optlen = DHCPV6_OPTION_IA_PD_HEADER_SIZE + DHCPV6_OPTION_HEADER_SIZE + DHCPV6_OPTION_IAPREFIX_HEADER_SIZE; } ret = dhcpv6_add_option_header(pkt, DHCPV6_OPTION_CODE_IA_PD, optlen); if (ret < 0) { return ret; } ret = net_pkt_write_be32(pkt, ia_pd->iaid); if (ret < 0) { return ret; } ret = net_pkt_write_be32(pkt, ia_pd->t1); if (ret < 0) { return ret; } ret = net_pkt_write_be32(pkt, ia_pd->t2); if (ret < 0) { return ret; } if (!include_prefix) { return 0; } ret = dhcpv6_add_option_header(pkt, DHCPV6_OPTION_CODE_IAPREFIX, DHCPV6_OPTION_IAPREFIX_HEADER_SIZE); if (ret < 0) { return ret; } ret = net_pkt_write_be32(pkt, ia_pd->iaprefix.preferred_lifetime); if (ret < 0) { return ret; } ret = net_pkt_write_be32(pkt, ia_pd->iaprefix.valid_lifetime); if (ret < 0) { return ret; } ret = net_pkt_write_u8(pkt, ia_pd->iaprefix.prefix_len); if (ret < 0) { return ret; } ret = net_pkt_write(pkt, &ia_pd->iaprefix.prefix, sizeof(ia_pd->iaprefix.prefix)); return ret; } static int dhcpv6_add_option_oro(struct net_pkt *pkt, uint16_t *codes, int code_cnt) { int ret; ret = dhcpv6_add_option_header(pkt, DHCPV6_OPTION_CODE_ORO, sizeof(uint16_t) * code_cnt); if (ret < 0) { return ret; } for (int i = 0; i < code_cnt; i++) { ret = net_pkt_write_be16(pkt, codes[i]); if (ret < 0) { return ret; } } return ret; } static size_t dhcpv6_calculate_message_size(struct dhcpv6_options_include *options) { size_t msg_size = sizeof(struct dhcpv6_msg_hdr); uint8_t oro_cnt = 0; if (options->clientid) { msg_size += DHCPV6_OPTION_HEADER_SIZE; msg_size += sizeof(struct net_dhcpv6_duid_storage); } if (options->serverid) { msg_size += DHCPV6_OPTION_HEADER_SIZE; msg_size += sizeof(struct net_dhcpv6_duid_storage); } if (options->elapsed_time) { msg_size += DHCPV6_OPTION_HEADER_SIZE; msg_size += DHCPV6_OPTION_ELAPSED_TIME_SIZE; } if (options->ia_na) { msg_size += DHCPV6_OPTION_HEADER_SIZE; msg_size += DHCPV6_OPTION_IA_NA_HEADER_SIZE; } if (options->iaaddr) { msg_size += DHCPV6_OPTION_HEADER_SIZE; msg_size += DHCPV6_OPTION_IAADDR_HEADER_SIZE; } if (options->ia_pd) { msg_size += DHCPV6_OPTION_HEADER_SIZE; msg_size += DHCPV6_OPTION_IA_PD_HEADER_SIZE; } if (options->iaprefix) { msg_size += DHCPV6_OPTION_HEADER_SIZE; msg_size += DHCPV6_OPTION_IAPREFIX_HEADER_SIZE; } for (uint8_t i = 0; i < ARRAY_SIZE(options->oro); i++) { if (options->oro[i] == 0) { break; } oro_cnt++; } if (oro_cnt > 0) { msg_size += DHCPV6_OPTION_HEADER_SIZE; msg_size += oro_cnt * sizeof(uint16_t); } return msg_size; } static int dhcpv6_add_options(struct net_if *iface, struct net_pkt *pkt, struct dhcpv6_options_include *options) { uint8_t oro_cnt = 0; int ret; if (options->clientid) { ret = dhcpv6_add_option_clientid( pkt, &iface->config.dhcpv6.clientid); if (ret < 0) { goto fail; } } if (options->serverid) { ret = dhcpv6_add_option_serverid( pkt, &iface->config.dhcpv6.serverid); if (ret < 0) { goto fail; } } if (options->elapsed_time) { ret = dhcpv6_add_option_elapsed_time( pkt, iface->config.dhcpv6.exchange_start); if (ret < 0) { goto fail; } } if (options->ia_na) { struct dhcpv6_ia_na ia_na = { .iaid = iface->config.dhcpv6.addr_iaid, }; if (options->iaaddr) { memcpy(&ia_na.iaaddr.addr, &iface->config.dhcpv6.addr, sizeof(ia_na.iaaddr.addr)); } ret = dhcpv6_add_option_ia_na(pkt, &ia_na, options->iaaddr); if (ret < 0) { goto fail; } } if (options->ia_pd) { struct dhcpv6_ia_pd ia_pd = { .iaid = iface->config.dhcpv6.prefix_iaid, }; if (options->iaprefix) { memcpy(&ia_pd.iaprefix.prefix, &iface->config.dhcpv6.prefix, sizeof(ia_pd.iaprefix.prefix)); ia_pd.iaprefix.prefix_len = iface->config.dhcpv6.prefix_len; } ret = dhcpv6_add_option_ia_pd(pkt, &ia_pd, options->iaprefix); if (ret < 0) { goto fail; } } for (uint8_t i = 0; i < ARRAY_SIZE(options->oro); i++) { if (options->oro[i] == 0) { break; } oro_cnt++; } if (oro_cnt > 0) { ret = dhcpv6_add_option_oro(pkt, options->oro, oro_cnt); if (ret < 0) { goto fail; } } return 0; fail: return ret; } static struct net_pkt *dhcpv6_create_message(struct net_if *iface, enum dhcpv6_msg_type msg_type, struct dhcpv6_options_include *options) { struct in6_addr *local_addr; struct net_pkt *pkt; size_t msg_size; local_addr = net_if_ipv6_get_ll(iface, NET_ADDR_ANY_STATE); if (local_addr == NULL) { NET_ERR("No LL address"); return NULL; } msg_size = dhcpv6_calculate_message_size(options); pkt = net_pkt_alloc_with_buffer(iface, msg_size, AF_INET6, IPPROTO_UDP, K_FOREVER); if (pkt == NULL) { return NULL; } if (net_ipv6_create(pkt, local_addr, &all_dhcpv6_ra_and_servers) < 0 || net_udp_create(pkt, htons(DHCPV6_CLIENT_PORT), htons(DHCPV6_SERVER_PORT)) < 0) { goto fail; } dhcpv6_generate_tid(iface); if (dhcpv6_add_header(pkt, msg_type, iface->config.dhcpv6.tid) < 0) { goto fail; } if (dhcpv6_add_options(iface, pkt, options) < 0) { goto fail; } net_pkt_cursor_init(pkt); net_ipv6_finalize(pkt, IPPROTO_UDP); return pkt; fail: net_pkt_unref(pkt); return NULL; } static int dhcpv6_send_solicit(struct net_if *iface) { int ret; struct net_pkt *pkt; struct dhcpv6_options_include options = { .clientid = true, .elapsed_time = true, .ia_na = iface->config.dhcpv6.params.request_addr, .ia_pd = iface->config.dhcpv6.params.request_prefix, .oro = { DHCPV6_OPTION_CODE_SOL_MAX_RT }, }; pkt = dhcpv6_create_message(iface, DHCPV6_MSG_TYPE_SOLICIT, &options); if (pkt == NULL) { return -ENOMEM; } ret = net_send_data(pkt); if (ret < 0) { net_pkt_unref(pkt); } return ret; } static int dhcpv6_send_request(struct net_if *iface) { int ret; struct net_pkt *pkt; struct dhcpv6_options_include options = { .clientid = true, .serverid = true, .elapsed_time = true, .ia_na = iface->config.dhcpv6.params.request_addr, .ia_pd = iface->config.dhcpv6.params.request_prefix, .oro = { DHCPV6_OPTION_CODE_SOL_MAX_RT }, }; pkt = dhcpv6_create_message(iface, DHCPV6_MSG_TYPE_REQUEST, &options); if (pkt == NULL) { return -ENOMEM; } ret = net_send_data(pkt); if (ret < 0) { net_pkt_unref(pkt); } return ret; } static int dhcpv6_send_renew(struct net_if *iface) { int ret; struct net_pkt *pkt; struct dhcpv6_options_include options = { .clientid = true, .serverid = true, .elapsed_time = true, .ia_na = iface->config.dhcpv6.params.request_addr, .iaaddr = iface->config.dhcpv6.params.request_addr, .ia_pd = iface->config.dhcpv6.params.request_prefix, .iaprefix = iface->config.dhcpv6.params.request_prefix, .oro = { DHCPV6_OPTION_CODE_SOL_MAX_RT }, }; pkt = dhcpv6_create_message(iface, DHCPV6_MSG_TYPE_RENEW, &options); if (pkt == NULL) { return -ENOMEM; } ret = net_send_data(pkt); if (ret < 0) { net_pkt_unref(pkt); } return ret; } static int dhcpv6_send_rebind(struct net_if *iface) { int ret; struct net_pkt *pkt; struct dhcpv6_options_include options = { .clientid = true, .elapsed_time = true, .ia_na = iface->config.dhcpv6.params.request_addr, .iaaddr = iface->config.dhcpv6.params.request_addr, .ia_pd = iface->config.dhcpv6.params.request_prefix, .iaprefix = iface->config.dhcpv6.params.request_prefix, .oro = { DHCPV6_OPTION_CODE_SOL_MAX_RT }, }; pkt = dhcpv6_create_message(iface, DHCPV6_MSG_TYPE_REBIND, &options); if (pkt == NULL) { return -ENOMEM; } ret = net_send_data(pkt); if (ret < 0) { net_pkt_unref(pkt); } return ret; } static int dhcpv6_send_confirm(struct net_if *iface) { int ret; struct net_pkt *pkt; struct dhcpv6_options_include options = { .clientid = true, .elapsed_time = true, .ia_na = true, .iaaddr = true, }; pkt = dhcpv6_create_message(iface, DHCPV6_MSG_TYPE_CONFIRM, &options); if (pkt == NULL) { return -ENOMEM; } ret = net_send_data(pkt); if (ret < 0) { net_pkt_unref(pkt); } return ret; } /* DHCPv6 packet parsing functions */ static int dhcpv6_parse_option_clientid(struct net_pkt *pkt, uint16_t length, struct net_dhcpv6_duid_storage *clientid) { struct net_dhcpv6_duid_raw duid; int ret; if (length > sizeof(struct net_dhcpv6_duid_raw)) { NET_ERR("DUID too large to handle"); return -EMSGSIZE; } ret = net_pkt_read(pkt, &duid, length); if (ret < 0) { return ret; } clientid->length = length; memcpy(&clientid->duid, &duid, length); return 0; } static int dhcpv6_parse_option_serverid(struct net_pkt *pkt, uint16_t length, struct net_dhcpv6_duid_storage *serverid) { struct net_dhcpv6_duid_raw duid; int ret; if (length > sizeof(struct net_dhcpv6_duid_raw)) { NET_ERR("DUID too large to handle"); return -EMSGSIZE; } ret = net_pkt_read(pkt, &duid, length); if (ret < 0) { return ret; } serverid->length = length; memcpy(&serverid->duid, &duid, length); return 0; } static int dhcpv6_parse_option_preference(struct net_pkt *pkt, uint16_t length, uint8_t *preference) { if (length != DHCPV6_OPTION_PREFERENCE_SIZE) { return -EBADMSG; } if (net_pkt_read_u8(pkt, preference) < 0) { return -EBADMSG; } return 0; } static int dhcpv6_parse_option_status_code(struct net_pkt *pkt, uint16_t length, uint16_t *status) { int ret; if (length < DHCPV6_OPTION_STATUS_CODE_HEADER_SIZE) { NET_ERR("Invalid IAADDR option size"); return -EMSGSIZE; } ret = net_pkt_read_be16(pkt, status); if (ret < 0) { return ret; } NET_DBG("status code %d", *status); length -= DHCPV6_OPTION_STATUS_CODE_HEADER_SIZE; if (length > 0) { /* Ignore status message */ ret = net_pkt_skip(pkt, length); } return ret; } static int dhcpv6_parse_option_iaaddr(struct net_pkt *pkt, uint16_t length, struct dhcpv6_iaaddr *iaaddr) { int ret; if (length < DHCPV6_OPTION_IAADDR_HEADER_SIZE) { NET_ERR("Invalid IAADDR option size"); return -EMSGSIZE; } ret = net_pkt_read(pkt, &iaaddr->addr, sizeof(iaaddr->addr)); if (ret < 0) { return ret; } ret = net_pkt_read_be32(pkt, &iaaddr->preferred_lifetime); if (ret < 0) { return ret; } ret = net_pkt_read_be32(pkt, &iaaddr->valid_lifetime); if (ret < 0) { return ret; } /* DHCPv6 RFC8415, ch. 21.6 The client MUST discard any addresses for * which the preferred lifetime is greater than the valid lifetime. */ if (iaaddr->preferred_lifetime > iaaddr->valid_lifetime) { return -EBADMSG; } NET_DBG("addr %s preferred_lifetime %d valid_lifetime %d", net_sprint_ipv6_addr(&iaaddr->addr), iaaddr->preferred_lifetime, iaaddr->valid_lifetime); iaaddr->status = DHCPV6_STATUS_SUCCESS; length -= DHCPV6_OPTION_IAADDR_HEADER_SIZE; while (length > 0) { uint16_t code, sublen; ret = net_pkt_read_be16(pkt, &code); if (ret < 0) { return ret; } ret = net_pkt_read_be16(pkt, &sublen); if (ret < 0) { return ret; } switch (code) { case DHCPV6_OPTION_CODE_STATUS_CODE: ret = dhcpv6_parse_option_status_code(pkt, sublen, &iaaddr->status); if (ret < 0) { return ret; } break; default: NET_DBG("Unexpected option %d length %d", code, sublen); ret = net_pkt_skip(pkt, sublen); if (ret < 0) { return ret; } break; } length -= (sublen + 4); } return 0; } static int dhcpv6_parse_option_ia_na(struct net_pkt *pkt, uint16_t length, struct dhcpv6_ia_na *ia_na) { int ret; if (length < DHCPV6_OPTION_IA_NA_HEADER_SIZE) { NET_ERR("Invalid IA_NA option size"); return -EMSGSIZE; } ret = net_pkt_read_be32(pkt, &ia_na->iaid); if (ret < 0) { return ret; } ret = net_pkt_read_be32(pkt, &ia_na->t1); if (ret < 0) { return ret; } ret = net_pkt_read_be32(pkt, &ia_na->t2); if (ret < 0) { return ret; } /* DHCPv6 RFC8415, ch. 21.4 If a client receives an IA_NA with T1 * greater than T2 and both T1 and T2 are greater than 0, the client * discards the IA_NA option and processes the remainder of the message * as though the server had not included the invalid IA_NA option. */ if (ia_na->t1 != 0 && ia_na->t2 != 0 && ia_na->t1 > ia_na->t2) { return -ENOENT; } NET_DBG("iaid %d t1 %d t2 %d", ia_na->iaid, ia_na->t1, ia_na->t2); /* In case there's no IAADDR option, make this visible be setting * error status. If the option is present, option parser will overwrite * the value. */ ia_na->iaaddr.status = DHCPV6_STATUS_NO_ADDR_AVAIL; ia_na->status = DHCPV6_STATUS_SUCCESS; length -= DHCPV6_OPTION_IA_NA_HEADER_SIZE; while (length > 0) { uint16_t code, sublen; ret = net_pkt_read_be16(pkt, &code); if (ret < 0) { return ret; } ret = net_pkt_read_be16(pkt, &sublen); if (ret < 0) { return ret; } switch (code) { case DHCPV6_OPTION_CODE_IAADDR: ret = dhcpv6_parse_option_iaaddr(pkt, sublen, &ia_na->iaaddr); if (ret < 0) { return ret; } break; case DHCPV6_OPTION_CODE_STATUS_CODE: ret = dhcpv6_parse_option_status_code(pkt, sublen, &ia_na->status); if (ret < 0) { return ret; } break; default: NET_DBG("Unexpected option %d length %d", code, sublen); ret = net_pkt_skip(pkt, sublen); if (ret < 0) { return ret; } break; } length -= (sublen + 4); } return 0; } static int dhcpv6_parse_option_iaprefix(struct net_pkt *pkt, uint16_t length, struct dhcpv6_iaprefix *iaprefix) { int ret; if (length < DHCPV6_OPTION_IAPREFIX_HEADER_SIZE) { NET_ERR("Invalid IAPREFIX option size"); return -EMSGSIZE; } ret = net_pkt_read_be32(pkt, &iaprefix->preferred_lifetime); if (ret < 0) { return ret; } ret = net_pkt_read_be32(pkt, &iaprefix->valid_lifetime); if (ret < 0) { return ret; } ret = net_pkt_read_u8(pkt, &iaprefix->prefix_len); if (ret < 0) { return ret; } ret = net_pkt_read(pkt, &iaprefix->prefix, sizeof(iaprefix->prefix)); if (ret < 0) { return ret; } /* DHCPv6 RFC8415, ch. 21.22 The client MUST discard any prefixes for * which the preferred lifetime is greater than the valid lifetime. */ if (iaprefix->preferred_lifetime > iaprefix->valid_lifetime) { return -EBADMSG; } NET_DBG("prefix %s/%u preferred_lifetime %d valid_lifetime %d", net_sprint_ipv6_addr(&iaprefix->prefix), iaprefix->prefix_len, iaprefix->preferred_lifetime, iaprefix->valid_lifetime); iaprefix->status = DHCPV6_STATUS_SUCCESS; length -= DHCPV6_OPTION_IAPREFIX_HEADER_SIZE; while (length > 0) { uint16_t code, sublen; ret = net_pkt_read_be16(pkt, &code); if (ret < 0) { return ret; } ret = net_pkt_read_be16(pkt, &sublen); if (ret < 0) { return ret; } switch (code) { case DHCPV6_OPTION_CODE_STATUS_CODE: ret = dhcpv6_parse_option_status_code(pkt, sublen, &iaprefix->status); if (ret < 0) { return ret; } break; default: NET_DBG("Unexpected option %d length %d", code, sublen); ret = net_pkt_skip(pkt, sublen); if (ret < 0) { return ret; } break; } length -= (sublen + 4); } return 0; } static int dhcpv6_parse_option_ia_pd(struct net_pkt *pkt, uint16_t length, struct dhcpv6_ia_pd *ia_pd) { int ret; if (length < DHCPV6_OPTION_IA_PD_HEADER_SIZE) { NET_ERR("Invalid IA_PD option size"); return -EMSGSIZE; } ret = net_pkt_read_be32(pkt, &ia_pd->iaid); if (ret < 0) { return ret; } ret = net_pkt_read_be32(pkt, &ia_pd->t1); if (ret < 0) { return ret; } ret = net_pkt_read_be32(pkt, &ia_pd->t2); if (ret < 0) { return ret; } /* DHCPv6 RFC8415, ch. 21.21 If a client receives an IA_PD with T1 * greater than T2 and both T1 and T2 are greater than 0, the client * discards the IA_PD option and processes the remainder of the message * as though the server had not included the IA_PD option. */ if (ia_pd->t1 != 0 && ia_pd->t2 != 0 && ia_pd->t1 > ia_pd->t2) { return -ENOENT; } NET_DBG("iaid %d t1 %d t2 %d", ia_pd->iaid, ia_pd->t1, ia_pd->t2); /* In case there's no IAPREFIX option, make this visible be setting * error status. If the option is present, option parser will overwrite * the value. */ ia_pd->iaprefix.status = DHCPV6_STATUS_NO_PREFIX_AVAIL; ia_pd->status = DHCPV6_STATUS_SUCCESS; length -= DHCPV6_OPTION_IA_PD_HEADER_SIZE; while (length > 0) { uint16_t code, sublen; ret = net_pkt_read_be16(pkt, &code); if (ret < 0) { return ret; } ret = net_pkt_read_be16(pkt, &sublen); if (ret < 0) { return ret; } switch (code) { case DHCPV6_OPTION_CODE_IAPREFIX: ret = dhcpv6_parse_option_iaprefix(pkt, sublen, &ia_pd->iaprefix); if (ret < 0) { return ret; } break; case DHCPV6_OPTION_CODE_STATUS_CODE: ret = dhcpv6_parse_option_status_code(pkt, sublen, &ia_pd->status); if (ret < 0) { return ret; } break; default: NET_DBG("Unexpected option %d length %d", code, sublen); ret = net_pkt_skip(pkt, sublen); if (ret < 0) { return ret; } break; } length -= (sublen + 4); } return 0; } static int dhcpv6_find_option(struct net_pkt *pkt, enum dhcpv6_option_code opt_code, uint16_t *opt_len) { uint16_t length; uint16_t code; int ret; while (net_pkt_read_be16(pkt, &code) == 0) { if (net_pkt_read_be16(pkt, &length) < 0) { return -EBADMSG; } if (code == opt_code) { *opt_len = length; return 0; } ret = net_pkt_skip(pkt, length); if (ret < 0) { return ret; } } return -ENOENT; } static int dhcpv6_find_clientid(struct net_pkt *pkt, struct net_dhcpv6_duid_storage *clientid) { struct net_pkt_cursor backup; uint16_t length; int ret; net_pkt_cursor_backup(pkt, &backup); ret = dhcpv6_find_option(pkt, DHCPV6_OPTION_CODE_CLIENTID, &length); if (ret == 0) { ret = dhcpv6_parse_option_clientid(pkt, length, clientid); } net_pkt_cursor_restore(pkt, &backup); return ret; } static int dhcpv6_find_serverid(struct net_pkt *pkt, struct net_dhcpv6_duid_storage *serverid) { struct net_pkt_cursor backup; uint16_t length; int ret; net_pkt_cursor_backup(pkt, &backup); ret = dhcpv6_find_option(pkt, DHCPV6_OPTION_CODE_SERVERID, &length); if (ret == 0) { ret = dhcpv6_parse_option_serverid(pkt, length, serverid); } net_pkt_cursor_restore(pkt, &backup); return ret; } static int dhcpv6_find_server_preference(struct net_pkt *pkt, uint8_t *preference) { struct net_pkt_cursor backup; uint16_t length; int ret; net_pkt_cursor_backup(pkt, &backup); ret = dhcpv6_find_option(pkt, DHCPV6_OPTION_CODE_PREFERENCE, &length); if (ret == 0) { ret = dhcpv6_parse_option_preference(pkt, length, preference); } else if (ret == -ENOENT) { /* In case no preference option is present, default to 0. * DHCPv6 RFC8415, ch. 18.2.1. */ *preference = 0; ret = 0; } net_pkt_cursor_restore(pkt, &backup); return ret; } static int dhcpv6_find_ia_na(struct net_pkt *pkt, struct dhcpv6_ia_na *ia_na) { struct net_pkt_cursor backup; uint16_t length; int ret; net_pkt_cursor_backup(pkt, &backup); ret = dhcpv6_find_option(pkt, DHCPV6_OPTION_CODE_IA_NA, &length); if (ret == 0) { ret = dhcpv6_parse_option_ia_na(pkt, length, ia_na); } net_pkt_cursor_restore(pkt, &backup); return ret; } static int dhcpv6_find_ia_pd(struct net_pkt *pkt, struct dhcpv6_ia_pd *ia_pd) { struct net_pkt_cursor backup; uint16_t length; int ret; net_pkt_cursor_backup(pkt, &backup); ret = dhcpv6_find_option(pkt, DHCPV6_OPTION_CODE_IA_PD, &length); if (ret == 0) { ret = dhcpv6_parse_option_ia_pd(pkt, length, ia_pd); } net_pkt_cursor_restore(pkt, &backup); return ret; } static int dhcpv6_find_status_code(struct net_pkt *pkt, uint16_t *status) { struct net_pkt_cursor backup; uint16_t length; int ret; net_pkt_cursor_backup(pkt, &backup); ret = dhcpv6_find_option(pkt, DHCPV6_OPTION_CODE_STATUS_CODE, &length); if (ret == 0) { ret = dhcpv6_parse_option_status_code(pkt, length, status); } else if (ret == -ENOENT) { /* In case no status option is present, default to success. * DHCPv6 RFC8415, ch. 21.13. */ *status = DHCPV6_STATUS_SUCCESS; ret = 0; } net_pkt_cursor_restore(pkt, &backup); return ret; } /* DHCPv6 state changes */ static void dhcpv6_enter_init(struct net_if *iface) { uint32_t timeout; /* RFC8415 requires to wait a random period up to 1 second before * sending the initial solicit/information request/confirm. */ timeout = sys_rand32_get() % DHCPV6_SOL_MAX_DELAY; dhcpv6_set_timeout(iface, timeout); } static void dhcpv6_enter_soliciting(struct net_if *iface) { iface->config.dhcpv6.retransmit_timeout = dhcpv6_initial_retransmit_time(DHCPV6_SOL_TIMEOUT); iface->config.dhcpv6.retransmissions = 0; iface->config.dhcpv6.server_preference = -1; iface->config.dhcpv6.exchange_start = k_uptime_get(); (void)dhcpv6_send_solicit(iface); dhcpv6_set_timeout(iface, iface->config.dhcpv6.retransmit_timeout); } static void dhcpv6_enter_requesting(struct net_if *iface) { iface->config.dhcpv6.retransmit_timeout = dhcpv6_initial_retransmit_time(DHCPV6_REQ_TIMEOUT); iface->config.dhcpv6.retransmissions = 0; iface->config.dhcpv6.exchange_start = k_uptime_get(); (void)dhcpv6_send_request(iface); dhcpv6_set_timeout(iface, iface->config.dhcpv6.retransmit_timeout); } static void dhcpv6_enter_renewing(struct net_if *iface) { iface->config.dhcpv6.retransmit_timeout = dhcpv6_initial_retransmit_time(DHCPV6_REN_TIMEOUT); iface->config.dhcpv6.retransmissions = 0; iface->config.dhcpv6.exchange_start = k_uptime_get(); (void)dhcpv6_send_renew(iface); dhcpv6_set_timeout(iface, iface->config.dhcpv6.retransmit_timeout); } static void dhcpv6_enter_rebinding(struct net_if *iface) { iface->config.dhcpv6.retransmit_timeout = dhcpv6_initial_retransmit_time(DHCPV6_REB_TIMEOUT); iface->config.dhcpv6.retransmissions = 0; iface->config.dhcpv6.exchange_start = k_uptime_get(); (void)dhcpv6_send_rebind(iface); dhcpv6_set_timeout(iface, iface->config.dhcpv6.retransmit_timeout); } static void dhcpv6_enter_confirming(struct net_if *iface) { iface->config.dhcpv6.retransmit_timeout = dhcpv6_initial_retransmit_time(DHCPV6_CNF_TIMEOUT); iface->config.dhcpv6.retransmissions = 0; iface->config.dhcpv6.exchange_start = k_uptime_get(); (void)dhcpv6_send_confirm(iface); dhcpv6_set_timeout(iface, iface->config.dhcpv6.retransmit_timeout); } static void dhcpv6_enter_bound(struct net_if *iface) { iface->config.dhcpv6.timeout = iface->config.dhcpv6.t1; net_mgmt_event_notify_with_info(NET_EVENT_IPV6_DHCP_BOUND, iface, &iface->config.dhcpv6, sizeof(iface->config.dhcpv6)); } static void dhcpv6_enter_state(struct net_if *iface, enum net_dhcpv6_state state) { iface->config.dhcpv6.state = state; NET_DBG("enter state=%s", net_dhcpv6_state_name(iface->config.dhcpv6.state)); switch (iface->config.dhcpv6.state) { case NET_DHCPV6_DISABLED: break; case NET_DHCPV6_INIT: return dhcpv6_enter_init(iface); case NET_DHCPV6_SOLICITING: return dhcpv6_enter_soliciting(iface); case NET_DHCPV6_REQUESTING: return dhcpv6_enter_requesting(iface); case NET_DHCPV6_CONFIRMING: return dhcpv6_enter_confirming(iface); case NET_DHCPV6_RENEWING: return dhcpv6_enter_renewing(iface); case NET_DHCPV6_REBINDING: return dhcpv6_enter_rebinding(iface); case NET_DHCPV6_INFO_REQUESTING: break; case NET_DHCPV6_BOUND: return dhcpv6_enter_bound(iface); } } /* DHCPv6 input processing */ static int dhcpv6_handle_advertise(struct net_if *iface, struct net_pkt *pkt, uint8_t *tid) { struct net_dhcpv6_duid_storage duid = { 0 }; struct dhcpv6_ia_pd ia_pd = { 0 }; struct dhcpv6_ia_na ia_na = { 0 }; uint8_t server_preference = 0; uint16_t status = 0; int ret; if (iface->config.dhcpv6.state != NET_DHCPV6_SOLICITING) { return -EINVAL; } /* Verify client ID. */ ret = dhcpv6_find_clientid(pkt, &duid); if (ret < 0) { NET_ERR("Client ID missing"); return ret; } if (iface->config.dhcpv6.clientid.length != duid.length || memcmp(&iface->config.dhcpv6.clientid.duid, &duid.duid, iface->config.dhcpv6.clientid.length) != 0) { NET_ERR("Client ID mismatch"); return -EBADMSG; } /* Verify server ID is present. */ memset(&duid, 0, sizeof(duid)); ret = dhcpv6_find_serverid(pkt, &duid); if (ret < 0) { NET_ERR("Server ID missing"); return ret; } /* Verify TID. */ if (memcmp(iface->config.dhcpv6.tid, tid, sizeof(iface->config.dhcpv6.tid)) != 0) { NET_INFO("TID mismatch"); return -EBADMSG; } /* Verify status code. */ ret = dhcpv6_find_status_code(pkt, &status); if (ret < 0) { return ret; } if (status != DHCPV6_STATUS_SUCCESS) { /* Ignore. */ return 0; } /* TODO Process SOL_MAX_RT/INF_MAX_RT options. */ /* Verify server preference. */ ret = dhcpv6_find_server_preference(pkt, &server_preference); if (ret < 0) { return ret; } if ((int16_t)server_preference < iface->config.dhcpv6.server_preference) { /* Ignore. */ return 0; } /* Find/verify address. */ if (iface->config.dhcpv6.params.request_addr) { ret = dhcpv6_find_ia_na(pkt, &ia_na); if (ret < 0) { NET_ERR("Address missing"); return ret; } if (ia_na.status != DHCPV6_STATUS_SUCCESS || ia_na.iaaddr.status != DHCPV6_STATUS_SUCCESS) { /* Ignore. */ return 0; } } /* Find/verify prefix. */ if (iface->config.dhcpv6.params.request_prefix) { ret = dhcpv6_find_ia_pd(pkt, &ia_pd); if (ret < 0) { NET_ERR("Prefix missing"); return ret; } if (ia_pd.status != DHCPV6_STATUS_SUCCESS || ia_pd.iaprefix.status != DHCPV6_STATUS_SUCCESS) { /* Ignore. */ return 0; } } /* Valid advertisement received, store received offer. */ memcpy(&iface->config.dhcpv6.serverid, &duid, sizeof(iface->config.dhcpv6.serverid)); iface->config.dhcpv6.server_preference = server_preference; /* DHCPv6 RFC8415, ch. 18.2.1, if client received Advertise * message with maximum preference, or after the first * retransmission period, it should proceed with the exchange, * w/o further wait. */ if (server_preference == DHCPV6_MAX_SERVER_PREFERENCE || iface->config.dhcpv6.retransmissions > 0) { /* Reschedule immediately */ dhcpv6_enter_state(iface, NET_DHCPV6_REQUESTING); dhcpv6_reschedule(); } return 0; } static int dhcpv6_handle_reply(struct net_if *iface, struct net_pkt *pkt, uint8_t *tid) { struct net_dhcpv6_duid_storage duid = { 0 }; struct dhcpv6_ia_pd ia_pd = { 0 }; struct dhcpv6_ia_na ia_na = { 0 }; int64_t now = k_uptime_get(); uint16_t status = 0; bool rediscover = false; int ret; if (iface->config.dhcpv6.state != NET_DHCPV6_REQUESTING && iface->config.dhcpv6.state != NET_DHCPV6_CONFIRMING && iface->config.dhcpv6.state != NET_DHCPV6_RENEWING && iface->config.dhcpv6.state != NET_DHCPV6_REBINDING) { return -EINVAL; } /* Verify client ID. */ ret = dhcpv6_find_clientid(pkt, &duid); if (ret < 0) { NET_ERR("Client ID missing"); return ret; } if (iface->config.dhcpv6.clientid.length != duid.length || memcmp(&iface->config.dhcpv6.clientid.duid, &duid.duid, iface->config.dhcpv6.clientid.length) != 0) { NET_ERR("Client ID mismatch"); return -EBADMSG; } /* Verify server ID is present. */ memset(&duid, 0, sizeof(duid)); ret = dhcpv6_find_serverid(pkt, &duid); if (ret < 0) { NET_ERR("Server ID missing"); return ret; } /* Verify TID. */ if (memcmp(iface->config.dhcpv6.tid, tid, sizeof(iface->config.dhcpv6.tid)) != 0) { NET_INFO("TID mismatch"); return -EBADMSG; } /* TODO Process SOL_MAX_RT/INF_MAX_RT options. */ /* Verify status code. */ ret = dhcpv6_find_status_code(pkt, &status); if (ret < 0) { return ret; } if (status == DHCPV6_STATUS_UNSPEC_FAIL) { /* Ignore and try again later. */ return 0; } /* DHCPv6 RFC8415, ch. 18.2.10.1. If the client receives a NotOnLink * status from the server in response to (...) Request, the client can * either reissue the message without specifying any addresses or * restart the DHCP server discovery process. * * Restart discovery for our case. */ if (iface->config.dhcpv6.state == NET_DHCPV6_REQUESTING && status == DHCPV6_STATUS_NOT_ON_LINK) { rediscover = true; goto out; } /* In case of Confirm Reply, status success indicates the client can * still use the address. */ if (iface->config.dhcpv6.state == NET_DHCPV6_CONFIRMING) { if (status != DHCPV6_STATUS_SUCCESS) { rediscover = true; } goto out; } /* Find/verify address. */ if (iface->config.dhcpv6.params.request_addr) { ret = dhcpv6_find_ia_na(pkt, &ia_na); if (ret < 0) { NET_ERR("Address missing"); return ret; } if (iface->config.dhcpv6.addr_iaid != ia_na.iaid) { return -EBADMSG; } } /* Find/verify prefix. */ if (iface->config.dhcpv6.params.request_prefix) { ret = dhcpv6_find_ia_pd(pkt, &ia_pd); if (ret < 0) { NET_ERR("Prefix missing"); return ret; } if (iface->config.dhcpv6.prefix_iaid != ia_pd.iaid) { return -EBADMSG; } } /* Valid response received, store received data. */ iface->config.dhcpv6.t1 = UINT64_MAX; iface->config.dhcpv6.t2 = UINT64_MAX; iface->config.dhcpv6.expire = now; if (iface->config.dhcpv6.params.request_addr) { struct net_if_addr *ifaddr; if (ia_na.status == DHCPV6_STATUS_NO_ADDR_AVAIL || ia_na.iaaddr.status == DHCPV6_STATUS_NO_ADDR_AVAIL || ia_na.iaaddr.valid_lifetime == 0) { /* Remove old lease. */ net_if_ipv6_addr_rm(iface, &iface->config.dhcpv6.addr); memset(&iface->config.dhcpv6.addr, 0, sizeof(struct in6_addr)); rediscover = true; goto prefix; } /* TODO On nobiding (renew/rebind) go to requesting */ if (!net_ipv6_addr_cmp(&iface->config.dhcpv6.addr, net_ipv6_unspecified_address()) && !net_ipv6_addr_cmp(&iface->config.dhcpv6.addr, &ia_na.iaaddr.addr)) { /* Remove old lease. */ net_if_ipv6_addr_rm(iface, &iface->config.dhcpv6.addr); } memcpy(&iface->config.dhcpv6.addr, &ia_na.iaaddr.addr, sizeof(iface->config.dhcpv6.addr)); dhcvp6_update_deadlines(iface, now, ia_na.t1, ia_na.t2, ia_na.iaaddr.preferred_lifetime, ia_na.iaaddr.valid_lifetime); ifaddr = net_if_ipv6_addr_lookup_by_iface(iface, &ia_na.iaaddr.addr); if (ifaddr != NULL) { net_if_ipv6_addr_update_lifetime( ifaddr, ia_na.iaaddr.valid_lifetime); } else if (net_if_ipv6_addr_add(iface, &ia_na.iaaddr.addr, NET_ADDR_DHCP, ia_na.iaaddr.valid_lifetime) == NULL) { NET_ERR("Failed to configure DHCPv6 address"); net_dhcpv6_stop(iface); return -EFAULT; } } prefix: if (iface->config.dhcpv6.params.request_prefix) { struct net_if_ipv6_prefix *ifprefix; if (ia_pd.status == DHCPV6_STATUS_NO_PREFIX_AVAIL || ia_pd.iaprefix.status == DHCPV6_STATUS_NO_PREFIX_AVAIL || ia_pd.iaprefix.valid_lifetime == 0) { /* Remove old lease. */ net_if_ipv6_prefix_rm(iface, &iface->config.dhcpv6.prefix, iface->config.dhcpv6.prefix_len); memset(&iface->config.dhcpv6.prefix, 0, sizeof(struct in6_addr)); iface->config.dhcpv6.prefix_len = 0; rediscover = true; goto out; } if (!net_ipv6_addr_cmp(&iface->config.dhcpv6.prefix, net_ipv6_unspecified_address()) && (!net_ipv6_addr_cmp(&iface->config.dhcpv6.prefix, &ia_pd.iaprefix.prefix) || iface->config.dhcpv6.prefix_len != ia_pd.iaprefix.prefix_len)) { /* Remove old lease. */ net_if_ipv6_prefix_rm(iface, &iface->config.dhcpv6.prefix, iface->config.dhcpv6.prefix_len); } iface->config.dhcpv6.prefix_len = ia_pd.iaprefix.prefix_len; memcpy(&iface->config.dhcpv6.prefix, &ia_pd.iaprefix.prefix, sizeof(iface->config.dhcpv6.prefix)); dhcvp6_update_deadlines(iface, now, ia_pd.t1, ia_pd.t2, ia_pd.iaprefix.preferred_lifetime, ia_pd.iaprefix.valid_lifetime); ifprefix = net_if_ipv6_prefix_lookup(iface, &ia_pd.iaprefix.prefix, ia_pd.iaprefix.prefix_len); if (ifprefix != NULL) { net_if_ipv6_prefix_set_timer(ifprefix, ia_pd.iaprefix.valid_lifetime); } else if (net_if_ipv6_prefix_add(iface, &ia_pd.iaprefix.prefix, ia_pd.iaprefix.prefix_len, ia_pd.iaprefix.valid_lifetime) == NULL) { NET_ERR("Failed to configure DHCPv6 prefix"); net_dhcpv6_stop(iface); return -EFAULT; } } out: if (rediscover) { dhcpv6_enter_state(iface, NET_DHCPV6_SOLICITING); } else { dhcpv6_enter_state(iface, NET_DHCPV6_BOUND); } dhcpv6_reschedule(); return 0; } static int dhcpv6_handle_reconfigure(struct net_if *iface, struct net_pkt *pkt) { /* Reconfigure not supported yet. */ return -ENOTSUP; } static enum net_verdict dhcpv6_input(struct net_conn *conn, struct net_pkt *pkt, union net_ip_header *ip_hdr, union net_proto_header *proto_hdr, void *user_data) { struct net_if *iface; uint8_t msg_type; uint8_t tid[DHCPV6_TID_SIZE]; int ret; if (!conn) { NET_ERR("Invalid connection"); return NET_DROP; } if (!pkt) { NET_ERR("Invalid packet"); return NET_DROP; } iface = net_pkt_iface(pkt); if (!iface) { NET_ERR("No interface"); return NET_DROP; } net_pkt_cursor_init(pkt); if (net_pkt_skip(pkt, NET_IPV6UDPH_LEN)) { NET_ERR("Missing IPv6/UDP header"); return NET_DROP; } if (net_pkt_read_u8(pkt, &msg_type) < 0) { NET_ERR("Missing message type"); return NET_DROP; } if (net_pkt_read(pkt, tid, sizeof(tid)) < 0) { NET_ERR("Missing transaction ID"); return NET_DROP; } NET_DBG("Received DHCPv6 packet [type=%d, tid=0x%02x%02x%02x]", msg_type, tid[0], tid[1], tid[2]); switch (msg_type) { case DHCPV6_MSG_TYPE_ADVERTISE: ret = dhcpv6_handle_advertise(iface, pkt, tid); break; case DHCPV6_MSG_TYPE_REPLY: ret = dhcpv6_handle_reply(iface, pkt, tid); break; case DHCPV6_MSG_TYPE_RECONFIGURE: ret = dhcpv6_handle_reconfigure(iface, pkt); break; case DHCPV6_MSG_TYPE_SOLICIT: case DHCPV6_MSG_TYPE_REQUEST: case DHCPV6_MSG_TYPE_CONFIRM: case DHCPV6_MSG_TYPE_RENEW: case DHCPV6_MSG_TYPE_REBIND: case DHCPV6_MSG_TYPE_RELEASE: case DHCPV6_MSG_TYPE_DECLINE: case DHCPV6_MSG_TYPE_INFORMATION_REQUEST: case DHCPV6_MSG_TYPE_RELAY_FORW: case DHCPV6_MSG_TYPE_RELAY_REPL: default: goto drop; } if (ret < 0) { goto drop; } net_pkt_unref(pkt); return NET_OK; drop: return NET_DROP; } /* DHCPv6 timer management */ static uint64_t dhcpv6_timeleft(struct net_if *iface, int64_t now) { uint64_t timeout = iface->config.dhcpv6.timeout; if (timeout > now) { return timeout - now; } return 0; } static uint64_t dhcpv6_manage_timers(struct net_if *iface, int64_t now) { uint64_t timeleft = dhcpv6_timeleft(iface, now); NET_DBG("iface %p state=%s timeleft=%llu", iface, net_dhcpv6_state_name(iface->config.dhcpv6.state), timeleft); if (timeleft != 0U) { return iface->config.dhcpv6.timeout; } if (!net_if_is_up(iface)) { /* An interface is down, the registered event handler will * restart DHCP procedure when the interface is back up. */ return UINT64_MAX; } switch (iface->config.dhcpv6.state) { case NET_DHCPV6_DISABLED: break; case NET_DHCPV6_INIT: { bool have_addr = false; bool have_prefix = false; if (iface->config.dhcpv6.params.request_addr && !net_ipv6_addr_cmp(&iface->config.dhcpv6.addr, net_ipv6_unspecified_address())) { have_addr = true; } if (iface->config.dhcpv6.params.request_prefix && !net_ipv6_addr_cmp(&iface->config.dhcpv6.prefix, net_ipv6_unspecified_address())) { have_prefix = true; } if ((have_addr || have_prefix) && now < iface->config.dhcpv6.expire) { /* Try to confirm the address/prefix. In case * prefix is requested, Rebind is used with * Confirm timings. */ iface->config.dhcpv6.expire = now + DHCPV6_CNF_MAX_RD; if (!iface->config.dhcpv6.params.request_prefix) { dhcpv6_enter_state(iface, NET_DHCPV6_CONFIRMING); } else { dhcpv6_enter_state(iface, NET_DHCPV6_REBINDING); } } else { dhcpv6_enter_state(iface, NET_DHCPV6_SOLICITING); } return iface->config.dhcpv6.timeout; } case NET_DHCPV6_SOLICITING: if (iface->config.dhcpv6.server_preference >= 0) { dhcpv6_enter_state(iface, NET_DHCPV6_REQUESTING); return iface->config.dhcpv6.timeout; } iface->config.dhcpv6.retransmissions++; iface->config.dhcpv6.retransmit_timeout = dhcpv6_next_retransmit_time( iface->config.dhcpv6.retransmit_timeout, DHCPV6_SOL_MAX_RT); (void)dhcpv6_send_solicit(iface); dhcpv6_set_timeout(iface, iface->config.dhcpv6.retransmit_timeout); return iface->config.dhcpv6.timeout; case NET_DHCPV6_REQUESTING: if (iface->config.dhcpv6.retransmissions >= DHCPV6_REQ_MAX_RC) { /* Back to soliciting. */ dhcpv6_enter_state(iface, NET_DHCPV6_SOLICITING); return iface->config.dhcpv6.timeout; } iface->config.dhcpv6.retransmissions++; iface->config.dhcpv6.retransmit_timeout = dhcpv6_next_retransmit_time( iface->config.dhcpv6.retransmit_timeout, DHCPV6_REQ_MAX_RT); (void)dhcpv6_send_request(iface); dhcpv6_set_timeout(iface, iface->config.dhcpv6.retransmit_timeout); return iface->config.dhcpv6.timeout; case NET_DHCPV6_CONFIRMING: if (now >= iface->config.dhcpv6.expire) { dhcpv6_enter_state(iface, NET_DHCPV6_SOLICITING); return iface->config.dhcpv6.timeout; } iface->config.dhcpv6.retransmissions++; iface->config.dhcpv6.retransmit_timeout = dhcpv6_next_retransmit_time( iface->config.dhcpv6.retransmit_timeout, DHCPV6_CNF_MAX_RT); (void)dhcpv6_send_confirm(iface); dhcpv6_set_timeout(iface, iface->config.dhcpv6.retransmit_timeout); if (iface->config.dhcpv6.timeout > iface->config.dhcpv6.expire) { iface->config.dhcpv6.timeout = iface->config.dhcpv6.expire; } return iface->config.dhcpv6.timeout; case NET_DHCPV6_RENEWING: if (now >= iface->config.dhcpv6.t2) { dhcpv6_enter_state(iface, NET_DHCPV6_REBINDING); return iface->config.dhcpv6.timeout; } iface->config.dhcpv6.retransmissions++; iface->config.dhcpv6.retransmit_timeout = dhcpv6_next_retransmit_time( iface->config.dhcpv6.retransmit_timeout, DHCPV6_REN_MAX_RT); (void)dhcpv6_send_renew(iface); dhcpv6_set_timeout(iface, iface->config.dhcpv6.retransmit_timeout); if (iface->config.dhcpv6.timeout > iface->config.dhcpv6.t2) { iface->config.dhcpv6.timeout = iface->config.dhcpv6.t2; } return iface->config.dhcpv6.timeout; case NET_DHCPV6_REBINDING: if (now >= iface->config.dhcpv6.expire) { dhcpv6_enter_state(iface, NET_DHCPV6_SOLICITING); return iface->config.dhcpv6.timeout; } iface->config.dhcpv6.retransmissions++; iface->config.dhcpv6.retransmit_timeout = dhcpv6_next_retransmit_time( iface->config.dhcpv6.retransmit_timeout, DHCPV6_REB_MAX_RT); (void)dhcpv6_send_rebind(iface); dhcpv6_set_timeout(iface, iface->config.dhcpv6.retransmit_timeout); if (iface->config.dhcpv6.timeout > iface->config.dhcpv6.expire) { iface->config.dhcpv6.timeout = iface->config.dhcpv6.expire; } return iface->config.dhcpv6.timeout; case NET_DHCPV6_INFO_REQUESTING: break; case NET_DHCPV6_BOUND: dhcpv6_enter_state(iface, NET_DHCPV6_RENEWING); return iface->config.dhcpv6.timeout; } return UINT64_MAX; } static void dhcpv6_timeout(struct k_work *work) { uint64_t timeout_update = UINT64_MAX; int64_t now = k_uptime_get(); struct net_if_dhcpv6 *current, *next; ARG_UNUSED(work); k_mutex_lock(&lock, K_FOREVER); SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&dhcpv6_ifaces, current, next, node) { struct net_if *iface = CONTAINER_OF( CONTAINER_OF(current, struct net_if_config, dhcpv6), struct net_if, config); uint64_t next_timeout; next_timeout = dhcpv6_manage_timers(iface, now); if (next_timeout < timeout_update) { timeout_update = next_timeout; } } k_mutex_unlock(&lock); if (timeout_update != UINT64_MAX) { if (now > timeout_update) { timeout_update = 0ULL; } else { timeout_update -= now; } NET_DBG("Waiting for %llums", timeout_update); k_work_reschedule(&dhcpv6_timeout_work, K_MSEC(timeout_update)); } } static void dhcpv6_iface_event_handler(struct net_mgmt_event_callback *cb, uint32_t mgmt_event, struct net_if *iface) { sys_snode_t *node = NULL; k_mutex_lock(&lock, K_FOREVER); SYS_SLIST_FOR_EACH_NODE(&dhcpv6_ifaces, node) { if (node == &iface->config.dhcpv6.node) { break; } } if (node == NULL) { goto out; } if (mgmt_event == NET_EVENT_IF_DOWN) { NET_DBG("Interface %p going down", iface); dhcpv6_set_timeout(iface, UINT64_MAX); } else if (mgmt_event == NET_EVENT_IF_UP) { NET_DBG("Interface %p coming up", iface); dhcpv6_enter_state(iface, NET_DHCPV6_INIT); } dhcpv6_reschedule(); out: k_mutex_unlock(&lock); } static void dhcpv6_generate_client_duid(struct net_if *iface) { struct net_linkaddr *lladdr = net_if_get_link_addr(iface); struct net_dhcpv6_duid_storage *clientid = &iface->config.dhcpv6.clientid; struct dhcpv6_duid_ll *duid_ll = (struct dhcpv6_duid_ll *)&clientid->duid.buf; memset(clientid, 0, sizeof(*clientid)); UNALIGNED_PUT(htons(DHCPV6_DUID_TYPE_LL), &clientid->duid.type); UNALIGNED_PUT(htons(DHCPV6_HARDWARE_ETHERNET_TYPE), &duid_ll->hw_type); memcpy(duid_ll->ll_addr, lladdr->addr, lladdr->len); clientid->length = DHCPV6_DUID_LL_HEADER_SIZE + lladdr->len; } /* DHCPv6 public API */ void net_dhcpv6_start(struct net_if *iface, struct net_dhcpv6_params *params) { k_mutex_lock(&lock, K_FOREVER); if (iface->config.dhcpv6.state != NET_DHCPV6_DISABLED) { NET_ERR("DHCPv6 already running on iface %p, state %s", iface, net_dhcpv6_state_name(iface->config.dhcpv6.state)); goto out; } if (!params->request_addr && !params->request_prefix) { NET_ERR("Information Request not supported yet"); goto out; } net_mgmt_event_notify(NET_EVENT_IPV6_DHCP_START, iface); NET_DBG("Starting DHCPv6 on iface %p", iface); iface->config.dhcpv6.params = *params; if (sys_slist_is_empty(&dhcpv6_ifaces)) { net_mgmt_add_event_callback(&dhcpv6_mgmt_cb); } sys_slist_append(&dhcpv6_ifaces, &iface->config.dhcpv6.node); if (params->request_addr) { iface->config.dhcpv6.addr_iaid = net_if_get_by_iface(iface); } if (params->request_prefix) { iface->config.dhcpv6.prefix_iaid = net_if_get_by_iface(iface); } dhcpv6_generate_client_duid(iface); dhcpv6_enter_state(iface, NET_DHCPV6_INIT); dhcpv6_reschedule(); out: k_mutex_unlock(&lock); } void net_dhcpv6_stop(struct net_if *iface) { k_mutex_lock(&lock, K_FOREVER); switch (iface->config.dhcpv6.state) { case NET_DHCPV6_DISABLED: NET_INFO("DHCPv6 already disabled on iface %p", iface); break; case NET_DHCPV6_INIT: case NET_DHCPV6_SOLICITING: case NET_DHCPV6_REQUESTING: case NET_DHCPV6_CONFIRMING: case NET_DHCPV6_RENEWING: case NET_DHCPV6_REBINDING: case NET_DHCPV6_INFO_REQUESTING: case NET_DHCPV6_BOUND: NET_DBG("Stopping DHCPv6 on iface %p, state %s", iface, net_dhcpv6_state_name(iface->config.dhcpv6.state)); (void)dhcpv6_enter_state(iface, NET_DHCPV6_DISABLED); sys_slist_find_and_remove(&dhcpv6_ifaces, &iface->config.dhcpv6.node); if (sys_slist_is_empty(&dhcpv6_ifaces)) { (void)k_work_cancel_delayable(&dhcpv6_timeout_work); net_mgmt_del_event_callback(&dhcpv6_mgmt_cb); } break; } net_mgmt_event_notify(NET_EVENT_IPV6_DHCP_STOP, iface); k_mutex_unlock(&lock); } void net_dhcpv6_restart(struct net_if *iface) { struct net_dhcpv6_params params = iface->config.dhcpv6.params; net_dhcpv6_stop(iface); net_dhcpv6_start(iface, ¶ms); } int net_dhcpv6_init(void) { struct sockaddr unspec_addr; int ret; net_ipaddr_copy(&net_sin6(&unspec_addr)->sin6_addr, net_ipv6_unspecified_address()); unspec_addr.sa_family = AF_INET6; ret = net_udp_register(AF_INET6, NULL, &unspec_addr, DHCPV6_SERVER_PORT, DHCPV6_CLIENT_PORT, NULL, dhcpv6_input, NULL, NULL); if (ret < 0) { NET_DBG("UDP callback registration failed"); return ret; } k_work_init_delayable(&dhcpv6_timeout_work, dhcpv6_timeout); net_mgmt_init_event_callback(&dhcpv6_mgmt_cb, dhcpv6_iface_event_handler, NET_EVENT_IF_DOWN | NET_EVENT_IF_UP); return 0; }