blob: 77fa2a1424efe3895369ece2ddf84a7c5c698d97 [file] [log] [blame]
/** @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 void 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];
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);
mbedtls_md_setup(&ctx, md_info, true);
mbedtls_md_hmac_starts(&ctx, secret_key, sizeof(secret_key));
mbedtls_md_hmac_update(&ctx, (uint8_t *)&buf, sizeof(buf));
mbedtls_md_hmac_finish(&ctx, digest);
mbedtls_md_free(&ctx);
memcpy(temporary_iid, digest, MIN(sizeof(digest), temporary_iid_len));
}
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, 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 {
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);
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;
}