Instead of using 32 bit enum values for event numbers, convert the code to use 64 bit long bit fields. This means that the user API is changed to use 64 bit event values instead of 32 bit event values. Signed-off-by: Jukka Rissanen <jukka.rissanen@nordicsemi.no>
789 lines
19 KiB
C
789 lines
19 KiB
C
/** @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,
|
|
uint64_t 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;
|
|
}
|