|  | /** @file | 
|  | * @brief IPv6 privacy extension (RFC 8981) | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * Copyright (c) 2018 Intel Corporation | 
|  | * Copyright (c) 2024 Nordic Semiconductor | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(net_ipv6_pe, CONFIG_NET_IPV6_PE_LOG_LEVEL); | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <stdlib.h> | 
|  |  | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/random/random.h> | 
|  |  | 
|  | #include <mbedtls/md.h> | 
|  |  | 
|  | #include <zephyr/net/net_core.h> | 
|  | #include <zephyr/net/net_pkt.h> | 
|  | #include <zephyr/net/net_if.h> | 
|  |  | 
|  | #include "net_private.h" | 
|  | #include "ipv6.h" | 
|  |  | 
|  | /* From RFC 5453 */ | 
|  | static const struct in6_addr reserved_anycast_subnet = { { { | 
|  | 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, | 
|  | 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff | 
|  | } } }; | 
|  |  | 
|  | /* RFC 8981 ch 3.8 preferred lifetime must be smaller than valid lifetime */ | 
|  | BUILD_ASSERT(CONFIG_NET_IPV6_PE_TEMP_PREFERRED_LIFETIME < | 
|  | CONFIG_NET_IPV6_PE_TEMP_VALID_LIFETIME); | 
|  |  | 
|  | /* IPv6 privacy extension (RFC 8981) constants. Note that the code uses | 
|  | * seconds value internally for applicable options. These are also values | 
|  | * that can be changed at runtime if needed as recommended in RFC 8981 | 
|  | * chapter 3.6. | 
|  | */ | 
|  | static uint32_t temp_valid_lifetime = | 
|  | CONFIG_NET_IPV6_PE_TEMP_VALID_LIFETIME * SEC_PER_MIN; | 
|  | #define TEMP_VALID_LIFETIME temp_valid_lifetime | 
|  |  | 
|  | static uint32_t temp_preferred_lifetime = | 
|  | CONFIG_NET_IPV6_PE_TEMP_PREFERRED_LIFETIME * SEC_PER_MIN; | 
|  | #define TEMP_PREFERRED_LIFETIME temp_preferred_lifetime | 
|  |  | 
|  | /* This is the upper bound on DESYNC_FACTOR. The value is in seconds. | 
|  | * See RFC 8981 ch 3.8 for details. | 
|  | * | 
|  | * RFC says the DESYNC_FACTOR should be 0.4 times the preferred lifetime. | 
|  | * This is too short for Zephyr as it means that the address is very long | 
|  | * time in deprecated state and not being used. Make this 7% of the preferred | 
|  | * time to deprecate the addresses later. | 
|  | */ | 
|  | #define MAX_DESYNC_FACTOR   ((uint32_t)((uint64_t)TEMP_PREFERRED_LIFETIME * \ | 
|  | (uint64_t)7U) / (uint64_t)100U) | 
|  | #define DESYNC_FACTOR(ipv6) ((ipv6)->desync_factor) | 
|  |  | 
|  | #define TEMP_IDGEN_RETRIES      CONFIG_NET_IPV6_PE_TEMP_IDGEN_RETRIES | 
|  |  | 
|  | /* The REGEN_ADVANCE is in seconds | 
|  | * retrans_timer (in ms) is specified in RFC 4861 | 
|  | * dup_addr_detect_transmits (in ms) is specified in RFC 4862 | 
|  | */ | 
|  | static inline uint32_t REGEN_ADVANCE(uint32_t retrans_timer, | 
|  | uint32_t dup_addr_detect_transmits) | 
|  | { | 
|  | return (2U + (uint32_t)((uint64_t)TEMP_IDGEN_RETRIES * | 
|  | (uint64_t)retrans_timer * | 
|  | (uint64_t)dup_addr_detect_transmits / | 
|  | (uint64_t)1000U)); | 
|  | } | 
|  |  | 
|  | #if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 | 
|  | /* Is this denylisting filter or not */ | 
|  | static bool ipv6_pe_denylist; | 
|  | static struct in6_addr ipv6_pe_filter[CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT]; | 
|  |  | 
|  | static K_MUTEX_DEFINE(lock); | 
|  | #endif | 
|  |  | 
|  | /* We need to periodically update the private address. */ | 
|  | static struct k_work_delayable temp_lifetime; | 
|  |  | 
|  | static bool ipv6_pe_use_this_prefix(const struct in6_addr *prefix) | 
|  | { | 
|  | #if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 | 
|  | int filter_found = false; | 
|  | bool ret = true; | 
|  |  | 
|  | k_mutex_lock(&lock, K_FOREVER); | 
|  |  | 
|  | ARRAY_FOR_EACH(ipv6_pe_filter, i) { | 
|  | if (net_ipv6_is_addr_unspecified(&ipv6_pe_filter[i])) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | filter_found = true; | 
|  |  | 
|  | if (net_ipv6_addr_cmp(prefix, &ipv6_pe_filter[i])) { | 
|  | if (ipv6_pe_denylist) { | 
|  | ret = false; | 
|  | } | 
|  |  | 
|  | goto out; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (filter_found) { | 
|  | /* There was no match so if we are deny listing, then this | 
|  | * address must be acceptable. | 
|  | */ | 
|  | if (!ipv6_pe_denylist) { | 
|  | ret = false; | 
|  | goto out; | 
|  | } | 
|  | } | 
|  |  | 
|  | out: | 
|  | k_mutex_unlock(&lock); | 
|  |  | 
|  | return ret; | 
|  | #else | 
|  | ARG_UNUSED(prefix); | 
|  |  | 
|  | return true; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | static bool ipv6_pe_prefix_already_exists(struct net_if_ipv6 *ipv6, | 
|  | const struct in6_addr *prefix) | 
|  | { | 
|  | ARRAY_FOR_EACH(ipv6->unicast, i) { | 
|  | if (!ipv6->unicast[i].is_used || | 
|  | ipv6->unicast[i].address.family != AF_INET6 || | 
|  | !ipv6->unicast[i].is_temporary || | 
|  | ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (net_ipv6_is_prefix( | 
|  | (uint8_t *)&ipv6->unicast[i].address.in6_addr, | 
|  | (uint8_t *)prefix, 64)) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static int ipv6_pe_prefix_remove(struct net_if *iface, | 
|  | struct net_if_ipv6 *ipv6, | 
|  | const struct in6_addr *prefix) | 
|  | { | 
|  | int count = 0; | 
|  |  | 
|  | ARRAY_FOR_EACH(ipv6->unicast, i) { | 
|  | if (ipv6->unicast[i].is_used && | 
|  | ipv6->unicast[i].address.family == AF_INET6 && | 
|  | ipv6->unicast[i].is_temporary && | 
|  | net_ipv6_is_prefix( | 
|  | (uint8_t *)&ipv6->unicast[i].address.in6_addr, | 
|  | (uint8_t *)prefix, 64)) { | 
|  | net_if_ipv6_addr_rm(iface, | 
|  | &ipv6->unicast[i].address.in6_addr); | 
|  | count++; | 
|  | } | 
|  | } | 
|  |  | 
|  | return count; | 
|  | } | 
|  |  | 
|  | static bool ipv6_pe_prefix_update_lifetimes(struct net_if_ipv6 *ipv6, | 
|  | const struct in6_addr *prefix, | 
|  | uint32_t vlifetime) | 
|  | { | 
|  | int32_t addr_age, new_age; | 
|  |  | 
|  | ARRAY_FOR_EACH(ipv6->unicast, i) { | 
|  | if (!(ipv6->unicast[i].is_used && | 
|  | ipv6->unicast[i].address.family == AF_INET6 && | 
|  | ipv6->unicast[i].is_temporary && | 
|  | ipv6->unicast[i].addr_state == NET_ADDR_PREFERRED && | 
|  | net_ipv6_is_prefix( | 
|  | (uint8_t *)&ipv6->unicast[i].address.in6_addr, | 
|  | (uint8_t *)prefix, 64))) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | addr_age = k_uptime_seconds() - ipv6->unicast[i].addr_create_time; | 
|  | new_age = abs(addr_age) + vlifetime; | 
|  |  | 
|  | if ((new_age >= TEMP_VALID_LIFETIME) || | 
|  | (new_age >= (TEMP_PREFERRED_LIFETIME - | 
|  | DESYNC_FACTOR(ipv6)))) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | net_if_ipv6_addr_update_lifetime(&ipv6->unicast[i], vlifetime); | 
|  |  | 
|  | /* RFC 8981 ch 3.5, "... at most one temporary address per | 
|  | * prefix should be in a non-deprecated state at any given | 
|  | * time on a given interface." | 
|  | * Because of this there is no need to continue the loop. | 
|  | */ | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* RFC 8981 ch 3.3.2 */ | 
|  | static int gen_temporary_iid(struct net_if *iface, | 
|  | const struct in6_addr *prefix, | 
|  | uint8_t *network_id, size_t network_id_len, | 
|  | uint8_t dad_counter, | 
|  | uint8_t *temporary_iid, | 
|  | size_t temporary_iid_len) | 
|  | { | 
|  | const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); | 
|  | mbedtls_md_context_t ctx; | 
|  | uint8_t digest[32]; | 
|  | int ret; | 
|  | static bool once; | 
|  | static uint8_t secret_key[16]; /* Min 128 bits, RFC 8981 ch 3.3.2 */ | 
|  | struct { | 
|  | struct in6_addr prefix; | 
|  | uint32_t current_time; | 
|  | uint8_t network_id[16]; | 
|  | uint8_t mac[6]; | 
|  | uint8_t dad_counter; | 
|  | } buf = { | 
|  | .current_time = k_uptime_get_32(), | 
|  | .dad_counter = dad_counter, | 
|  | }; | 
|  |  | 
|  | memcpy(&buf.prefix, prefix, sizeof(struct in6_addr)); | 
|  |  | 
|  | if (network_id != NULL && network_id_len > 0) { | 
|  | memcpy(buf.network_id, network_id, | 
|  | MIN(network_id_len, sizeof(buf.network_id))); | 
|  | } | 
|  |  | 
|  | memcpy(buf.mac, net_if_get_link_addr(iface)->addr, | 
|  | MIN(sizeof(buf.mac), net_if_get_link_addr(iface)->len)); | 
|  |  | 
|  | if (!once) { | 
|  | sys_rand_get(&secret_key, sizeof(secret_key)); | 
|  | once = true; | 
|  | } | 
|  |  | 
|  | mbedtls_md_init(&ctx); | 
|  | ret = mbedtls_md_setup(&ctx, md_info, true); | 
|  | if (ret != 0) { | 
|  | NET_DBG("Cannot %s hmac (%d)", "setup", ret); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | ret = mbedtls_md_hmac_starts(&ctx, secret_key, sizeof(secret_key)); | 
|  | if (ret != 0) { | 
|  | NET_DBG("Cannot %s hmac (%d)", "start", ret); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | ret = mbedtls_md_hmac_update(&ctx, (uint8_t *)&buf, sizeof(buf)); | 
|  | if (ret != 0) { | 
|  | NET_DBG("Cannot %s hmac (%d)", "update", ret); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | ret = mbedtls_md_hmac_finish(&ctx, digest); | 
|  | if (ret != 0) { | 
|  | NET_DBG("Cannot %s hmac (%d)", "finish", ret); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | memcpy(temporary_iid, digest, MIN(sizeof(digest), temporary_iid_len)); | 
|  |  | 
|  | err: | 
|  | mbedtls_md_free(&ctx); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void net_ipv6_pe_start(struct net_if *iface, const struct in6_addr *prefix, | 
|  | uint32_t vlifetime, uint32_t preferred_lifetime) | 
|  | { | 
|  | struct net_if_addr *ifaddr; | 
|  | struct net_if_ipv6 *ipv6; | 
|  | struct in6_addr addr; | 
|  | k_ticks_t remaining; | 
|  | k_timeout_t vlifetimeout; | 
|  | int i, ret, dad_count = 1; | 
|  | int32_t lifetime; | 
|  | bool valid = false; | 
|  |  | 
|  | net_if_lock(iface); | 
|  |  | 
|  | if (net_if_config_ipv6_get(iface, &ipv6) < 0) { | 
|  | NET_WARN("Cannot do DAD IPv6 config is not valid."); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (!ipv6) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* Check if user agrees to use this prefix */ | 
|  | if (!ipv6_pe_use_this_prefix(prefix)) { | 
|  | NET_DBG("Prefix %s/64 is not to be used", | 
|  | net_sprint_ipv6_addr(prefix)); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* If the prefix is already added and it is still valid and is not | 
|  | * deprecated, then we do not try to add it again. | 
|  | */ | 
|  | if (ipv6_pe_prefix_already_exists(ipv6, prefix)) { | 
|  | if (vlifetime == 0) { | 
|  | i = ipv6_pe_prefix_remove(iface, ipv6, prefix); | 
|  |  | 
|  | NET_DBG("Removed %d addresses using prefix %s/64", | 
|  | i, net_sprint_ipv6_addr(prefix)); | 
|  | } else { | 
|  | ipv6_pe_prefix_update_lifetimes(ipv6, prefix, vlifetime); | 
|  | } | 
|  |  | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | preferred_lifetime = MIN(preferred_lifetime, | 
|  | TEMP_PREFERRED_LIFETIME - | 
|  | DESYNC_FACTOR(ipv6)); | 
|  | if (preferred_lifetime == 0 || | 
|  | preferred_lifetime <= REGEN_ADVANCE(ipv6->retrans_timer, 1U)) { | 
|  | NET_DBG("Too short preferred lifetime (%u <= %u), temp address not " | 
|  | "created for prefix %s/64", preferred_lifetime, | 
|  | REGEN_ADVANCE(ipv6->retrans_timer, 1U), | 
|  | net_sprint_ipv6_addr(prefix)); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | NET_DBG("Starting PE process for prefix %s/64", | 
|  | net_sprint_ipv6_addr(prefix)); | 
|  |  | 
|  | net_ipaddr_copy(&addr, prefix); | 
|  |  | 
|  | do { | 
|  | ret = gen_temporary_iid(iface, prefix, | 
|  | COND_CODE_1(CONFIG_NET_INTERFACE_NAME, | 
|  | (iface->config.name, | 
|  | sizeof(iface->config.name)), | 
|  | (net_if_get_device(iface)->name, | 
|  | strlen(net_if_get_device(iface)->name))), | 
|  | dad_count, | 
|  | &addr.s6_addr[8], 8U); | 
|  | if (ret == 0) { | 
|  | ifaddr = net_if_ipv6_addr_lookup(&addr, NULL); | 
|  | if (ifaddr == NULL && !net_ipv6_is_addr_unspecified(&addr) && | 
|  | memcmp(&addr, &reserved_anycast_subnet, | 
|  | sizeof(struct in6_addr)) != 0) { | 
|  | valid = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | } while (dad_count++ < TEMP_IDGEN_RETRIES); | 
|  |  | 
|  | if (valid == false) { | 
|  | NET_WARN("Could not create a valid iid for prefix %s/64 for interface %d", | 
|  | net_sprint_ipv6_addr(prefix), | 
|  | net_if_get_by_iface(iface)); | 
|  | NET_WARN("Disabling IPv6 PE for interface %d", | 
|  | net_if_get_by_iface(iface)); | 
|  | net_mgmt_event_notify(NET_EVENT_IPV6_PE_DISABLED, iface); | 
|  | iface->pe_enabled = false; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | vlifetime = MIN(TEMP_VALID_LIFETIME, vlifetime); | 
|  |  | 
|  | ifaddr = net_if_ipv6_addr_add(iface, &addr, NET_ADDR_AUTOCONF, vlifetime); | 
|  | if (!ifaddr) { | 
|  | NET_ERR("Cannot add %s address to interface %d", | 
|  | net_sprint_ipv6_addr(&addr), | 
|  | net_if_get_by_iface(iface)); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | lifetime = TEMP_VALID_LIFETIME - | 
|  | REGEN_ADVANCE(net_if_ipv6_get_retrans_timer(iface), 1U); | 
|  |  | 
|  | DESYNC_FACTOR(ipv6) = sys_rand32_get() % MAX_DESYNC_FACTOR; | 
|  |  | 
|  | /* Make sure that the address timeout happens at least two seconds | 
|  | * after the deprecation. | 
|  | */ | 
|  | DESYNC_FACTOR(ipv6) = MIN(DESYNC_FACTOR(ipv6) + 2U, lifetime); | 
|  |  | 
|  | ifaddr->is_temporary = true; | 
|  | ifaddr->addr_preferred_lifetime = preferred_lifetime; | 
|  | ifaddr->addr_timeout = ifaddr->addr_preferred_lifetime - DESYNC_FACTOR(ipv6); | 
|  | ifaddr->addr_create_time = k_uptime_seconds(); | 
|  |  | 
|  | NET_DBG("Lifetime %d desync %d timeout %d preferred %d valid %d", | 
|  | lifetime, DESYNC_FACTOR(ipv6), ifaddr->addr_timeout, | 
|  | ifaddr->addr_preferred_lifetime, vlifetime); | 
|  |  | 
|  | NET_DBG("Starting DAD for %s iface %d", net_sprint_ipv6_addr(&addr), | 
|  | net_if_get_by_iface(iface)); | 
|  |  | 
|  | net_if_ipv6_start_dad(iface, ifaddr); | 
|  |  | 
|  | vlifetimeout = K_SECONDS(ifaddr->addr_timeout); | 
|  |  | 
|  | remaining = k_work_delayable_remaining_get(&temp_lifetime); | 
|  | if (remaining == 0 || remaining > vlifetimeout.ticks) { | 
|  | NET_DBG("Next check for temp addresses in %d seconds", | 
|  | ifaddr->addr_timeout); | 
|  | k_work_schedule(&temp_lifetime, vlifetimeout); | 
|  | } | 
|  |  | 
|  | out: | 
|  | net_if_unlock(iface); | 
|  | } | 
|  |  | 
|  | #if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 | 
|  |  | 
|  | static void iface_cb(struct net_if *iface, void *user_data) | 
|  | { | 
|  | bool is_new_filter_denylist = !ipv6_pe_denylist; | 
|  | struct in6_addr *prefix = user_data; | 
|  | struct net_if_ipv6 *ipv6; | 
|  | int ret; | 
|  |  | 
|  | net_if_lock(iface); | 
|  |  | 
|  | if (net_if_config_ipv6_get(iface, &ipv6) < 0) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (!ipv6) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | ARRAY_FOR_EACH(ipv6->unicast, i) { | 
|  | if (!ipv6->unicast[i].is_used || | 
|  | ipv6->unicast[i].address.family != AF_INET6 || | 
|  | !ipv6->unicast[i].is_temporary) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | ret = net_ipv6_is_prefix( | 
|  | (uint8_t *)&ipv6->unicast[i].address.in6_addr, | 
|  | (uint8_t *)prefix, 64); | 
|  |  | 
|  | /* TODO: Do this removal gracefully so that applications | 
|  | * have time to cope with this change. | 
|  | */ | 
|  | if (is_new_filter_denylist) { | 
|  | if (ret) { | 
|  | net_if_ipv6_addr_rm(iface, | 
|  | &ipv6->unicast[i].address.in6_addr); | 
|  | } | 
|  | } else { | 
|  | if (!ret) { | 
|  | net_if_ipv6_addr_rm(iface, | 
|  | &ipv6->unicast[i].address.in6_addr); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | out: | 
|  | net_if_unlock(iface); | 
|  | } | 
|  |  | 
|  | /* If we change filter value, then check if existing IPv6 prefixes will | 
|  | * conflict with the new filter. | 
|  | */ | 
|  | static void ipv6_pe_recheck_filters(bool is_denylist) | 
|  | { | 
|  | k_mutex_lock(&lock, K_FOREVER); | 
|  |  | 
|  | ARRAY_FOR_EACH(ipv6_pe_filter, i) { | 
|  | if (net_ipv6_is_addr_unspecified(&ipv6_pe_filter[i])) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | net_if_foreach(iface_cb, &ipv6_pe_filter[i]); | 
|  | } | 
|  |  | 
|  | k_mutex_unlock(&lock); | 
|  | } | 
|  | #endif /* CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 */ | 
|  |  | 
|  | #if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 | 
|  | static void send_filter_event(struct in6_addr *addr, bool is_denylist, | 
|  | int event_type) | 
|  | { | 
|  | if (IS_ENABLED(CONFIG_NET_MGMT_EVENT_INFO)) { | 
|  | struct net_event_ipv6_pe_filter info; | 
|  |  | 
|  | net_ipaddr_copy(&info.prefix, addr); | 
|  | info.is_deny_list = is_denylist; | 
|  |  | 
|  | net_mgmt_event_notify_with_info(event_type, | 
|  | NULL, | 
|  | (const void *)&info, | 
|  | sizeof(info)); | 
|  | } else { | 
|  | net_mgmt_event_notify(event_type, NULL); | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | int net_ipv6_pe_add_filter(struct in6_addr *addr, bool is_denylist) | 
|  | { | 
|  | #if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 | 
|  | bool found = false; | 
|  | int free_slot = -1; | 
|  | int ret = 0; | 
|  |  | 
|  | k_mutex_lock(&lock, K_FOREVER); | 
|  |  | 
|  | ARRAY_FOR_EACH(ipv6_pe_filter, i) { | 
|  | if (free_slot < 0 && | 
|  | net_ipv6_is_addr_unspecified(&ipv6_pe_filter[i])) { | 
|  | free_slot = i; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (net_ipv6_is_prefix((uint8_t *)addr, | 
|  | (uint8_t *)&ipv6_pe_filter[i], | 
|  | 64)) { | 
|  | found = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (found) { | 
|  | NET_DBG("Filter %s already in the list", | 
|  | net_sprint_ipv6_addr(addr)); | 
|  | ret = -EALREADY; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (free_slot < 0) { | 
|  | NET_DBG("All filters in use"); | 
|  | ret = -ENOMEM; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | net_ipaddr_copy(&ipv6_pe_filter[free_slot], addr); | 
|  |  | 
|  | if (ipv6_pe_denylist != is_denylist) { | 
|  | ipv6_pe_recheck_filters(is_denylist); | 
|  | } | 
|  |  | 
|  | ipv6_pe_denylist = is_denylist ? true : false; | 
|  |  | 
|  | NET_DBG("Adding %s list filter %s", | 
|  | ipv6_pe_denylist ? "deny" : "allow", | 
|  | net_sprint_ipv6_addr(&ipv6_pe_filter[free_slot])); | 
|  |  | 
|  | send_filter_event(&ipv6_pe_filter[free_slot], | 
|  | is_denylist, | 
|  | NET_EVENT_IPV6_PE_FILTER_ADD); | 
|  | out: | 
|  | k_mutex_unlock(&lock); | 
|  |  | 
|  | return ret; | 
|  | #else | 
|  | ARG_UNUSED(addr); | 
|  | ARG_UNUSED(is_denylist); | 
|  |  | 
|  | return -ENOTSUP; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | int net_ipv6_pe_del_filter(struct in6_addr *addr) | 
|  | { | 
|  | #if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 | 
|  | int ret = -ENOENT; | 
|  |  | 
|  | k_mutex_lock(&lock, K_FOREVER); | 
|  |  | 
|  | ARRAY_FOR_EACH(ipv6_pe_filter, i) { | 
|  | if (net_ipv6_addr_cmp(&ipv6_pe_filter[i], addr)) { | 
|  | NET_DBG("Removing %s list filter %s", | 
|  | ipv6_pe_denylist ? "deny" : "allow", | 
|  | net_sprint_ipv6_addr(&ipv6_pe_filter[i])); | 
|  |  | 
|  | send_filter_event(&ipv6_pe_filter[i], | 
|  | ipv6_pe_denylist, | 
|  | NET_EVENT_IPV6_PE_FILTER_DEL); | 
|  |  | 
|  | net_ipaddr_copy(&ipv6_pe_filter[i], | 
|  | net_ipv6_unspecified_address()); | 
|  |  | 
|  | ret = 0; | 
|  | goto out; | 
|  | } | 
|  | } | 
|  |  | 
|  | out: | 
|  | k_mutex_unlock(&lock); | 
|  |  | 
|  | return ret; | 
|  | #else | 
|  | ARG_UNUSED(addr); | 
|  |  | 
|  | return -ENOTSUP; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | bool net_ipv6_pe_check_dad(int count) | 
|  | { | 
|  | return count <= TEMP_IDGEN_RETRIES; | 
|  | } | 
|  |  | 
|  | int net_ipv6_pe_filter_foreach(net_ipv6_pe_filter_cb_t cb, void *user_data) | 
|  | { | 
|  | #if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 | 
|  | int i, count = 0; | 
|  |  | 
|  | k_mutex_lock(&lock, K_FOREVER); | 
|  |  | 
|  | for (i = 0; i < CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT; i++) { | 
|  | if (net_ipv6_is_addr_unspecified(&ipv6_pe_filter[i])) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | cb(&ipv6_pe_filter[i], ipv6_pe_denylist, user_data); | 
|  |  | 
|  | count++; | 
|  | } | 
|  |  | 
|  | k_mutex_unlock(&lock); | 
|  |  | 
|  | return count; | 
|  | #else | 
|  | ARG_UNUSED(cb); | 
|  | ARG_UNUSED(user_data); | 
|  |  | 
|  | return 0; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | struct deprecated_work { | 
|  | struct k_work_delayable work; | 
|  | struct net_if *iface; | 
|  | struct in6_addr addr; | 
|  | }; | 
|  |  | 
|  | static struct deprecated_work trigger_deprecated_event; | 
|  |  | 
|  | static void send_deprecated_event(struct k_work *work) | 
|  | { | 
|  | struct k_work_delayable *dwork = k_work_delayable_from_work(work); | 
|  | struct deprecated_work *dw = CONTAINER_OF(dwork, | 
|  | struct deprecated_work, | 
|  | work); | 
|  |  | 
|  | net_mgmt_event_notify_with_info(NET_EVENT_IPV6_ADDR_DEPRECATED, | 
|  | dw->iface, &dw->addr, | 
|  | sizeof(struct in6_addr)); | 
|  | } | 
|  |  | 
|  | static void renewal_cb(struct net_if *iface, void *user_data) | 
|  | { | 
|  | struct net_if_ipv6 *ipv6; | 
|  | struct in6_addr prefix; | 
|  |  | 
|  | if (net_if_config_ipv6_get(iface, &ipv6) < 0) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!ipv6) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | ARRAY_FOR_EACH(ipv6->unicast, i) { | 
|  | int32_t diff; | 
|  |  | 
|  | if (!ipv6->unicast[i].is_used || | 
|  | ipv6->unicast[i].address.family != AF_INET6 || | 
|  | !ipv6->unicast[i].is_temporary || | 
|  | ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* If the address is too old, then generate a new one | 
|  | * and remove the old address. | 
|  | */ | 
|  | diff = (int32_t)(ipv6->unicast[i].addr_create_time - k_uptime_seconds()); | 
|  | diff = abs(diff); | 
|  |  | 
|  | if (diff < (ipv6->unicast[i].addr_preferred_lifetime - | 
|  | REGEN_ADVANCE(ipv6->retrans_timer, 1U) - | 
|  | DESYNC_FACTOR(ipv6))) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | net_ipaddr_copy(&prefix, &ipv6->unicast[i].address.in6_addr); | 
|  | memset(prefix.s6_addr + 8, 0, sizeof(prefix) - 8); | 
|  |  | 
|  | NET_DBG("IPv6 address %s is deprecated", | 
|  | net_sprint_ipv6_addr(&ipv6->unicast[i].address.in6_addr)); | 
|  |  | 
|  | ipv6->unicast[i].addr_state = NET_ADDR_DEPRECATED; | 
|  |  | 
|  | /* Create a new temporary address and then notify users | 
|  | * that the old address is deprecated so that they can | 
|  | * re-connect to use the new address. We cannot send deprecated | 
|  | * event immediately because the new IPv6 will need to do DAD | 
|  | * and that takes a bit time. | 
|  | */ | 
|  | net_ipv6_pe_start(iface, &prefix, | 
|  | TEMP_VALID_LIFETIME, | 
|  | TEMP_PREFERRED_LIFETIME); | 
|  |  | 
|  | /* It is very unlikely but still a small possibility that there | 
|  | * could be another work already pending. | 
|  | * Fixing this would require allocating space for the work | 
|  | * somewhere. Currently this does not look like worth fixing. | 
|  | * Print a warning if there is a work already pending. | 
|  | */ | 
|  | if (k_work_delayable_is_pending(&trigger_deprecated_event.work)) { | 
|  | NET_WARN("Work already pending for deprecated event sending."); | 
|  | } | 
|  |  | 
|  | trigger_deprecated_event.iface = iface; | 
|  | memcpy(&trigger_deprecated_event.addr, | 
|  | &ipv6->unicast[i].address.in6_addr, | 
|  | sizeof(struct in6_addr)); | 
|  |  | 
|  | /* 500ms should be enough for DAD to pass */ | 
|  | k_work_schedule(&trigger_deprecated_event.work, K_MSEC(500)); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void ipv6_pe_renew(struct k_work *work) | 
|  | { | 
|  | ARG_UNUSED(work); | 
|  |  | 
|  | net_if_foreach(renewal_cb, NULL); | 
|  | } | 
|  |  | 
|  | int net_ipv6_pe_init(struct net_if *iface) | 
|  | { | 
|  | int32_t lifetime; | 
|  | int ret = 0; | 
|  |  | 
|  | net_mgmt_event_notify(NET_EVENT_IPV6_PE_ENABLED, iface); | 
|  |  | 
|  | lifetime = TEMP_VALID_LIFETIME - | 
|  | REGEN_ADVANCE(net_if_ipv6_get_retrans_timer(iface), 1U); | 
|  |  | 
|  | if (lifetime <= 0) { | 
|  | iface->pe_enabled = false; | 
|  | net_mgmt_event_notify(NET_EVENT_IPV6_PE_DISABLED, iface); | 
|  | ret = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | iface->pe_enabled = true; | 
|  | iface->pe_prefer_public = | 
|  | IS_ENABLED(CONFIG_NET_IPV6_PE_PREFER_PUBLIC_ADDRESSES) ? | 
|  | true : false; | 
|  |  | 
|  | k_work_init_delayable(&temp_lifetime, ipv6_pe_renew); | 
|  | k_work_init_delayable(&trigger_deprecated_event.work, | 
|  | send_deprecated_event); | 
|  |  | 
|  | out: | 
|  | NET_DBG("pe %s prefer %s lifetime %d sec", | 
|  | iface->pe_enabled ? "enabled" : "disabled", | 
|  | iface->pe_prefer_public ? "public" : "temporary", | 
|  | lifetime); | 
|  |  | 
|  | return ret; | 
|  | } |