blob: be2c3355a6b402201ba8bca501fd1b26b9e2b1f2 [file] [log] [blame]
/** @file
* @brief IPv4/6 PMTU related functions
*/
/*
* Copyright (c) 2024 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_pmtu, CONFIG_NET_PMTU_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <zephyr/net/net_event.h>
#include <zephyr/net/net_mgmt.h>
#include <zephyr/net/net_if.h>
#include "pmtu.h"
#if defined(CONFIG_NET_IPV4_PMTU)
#define NET_IPV4_PMTU_ENTRIES CONFIG_NET_IPV4_PMTU_DESTINATION_CACHE_ENTRIES
#else
#define NET_IPV4_PMTU_ENTRIES 0
#endif
#if defined(CONFIG_NET_IPV6_PMTU)
#define NET_IPV6_PMTU_ENTRIES CONFIG_NET_IPV6_PMTU_DESTINATION_CACHE_ENTRIES
#else
#define NET_IPV6_PMTU_ENTRIES 0
#endif
#define NET_PMTU_MAX_ENTRIES (NET_IPV4_PMTU_ENTRIES + NET_IPV6_PMTU_ENTRIES)
static struct net_pmtu_entry pmtu_entries[NET_PMTU_MAX_ENTRIES];
static K_MUTEX_DEFINE(lock);
static struct net_pmtu_entry *get_pmtu_entry(const struct sockaddr *dst)
{
struct net_pmtu_entry *entry = NULL;
int i;
k_mutex_lock(&lock, K_FOREVER);
for (i = 0; i < ARRAY_SIZE(pmtu_entries); i++) {
switch (dst->sa_family) {
case AF_INET:
if (IS_ENABLED(CONFIG_NET_IPV4_PMTU) &&
pmtu_entries[i].dst.family == AF_INET &&
net_ipv4_addr_cmp(&pmtu_entries[i].dst.in_addr,
&net_sin(dst)->sin_addr)) {
entry = &pmtu_entries[i];
goto out;
}
break;
case AF_INET6:
if (IS_ENABLED(CONFIG_NET_IPV6_PMTU) &&
pmtu_entries[i].dst.family == AF_INET6 &&
net_ipv6_addr_cmp(&pmtu_entries[i].dst.in6_addr,
&net_sin6(dst)->sin6_addr)) {
entry = &pmtu_entries[i];
goto out;
}
break;
default:
break;
}
}
out:
k_mutex_unlock(&lock);
return entry;
}
static struct net_pmtu_entry *get_free_pmtu_entry(void)
{
struct net_pmtu_entry *entry = NULL;
uint32_t oldest = 0U;
int i;
k_mutex_lock(&lock, K_FOREVER);
for (i = 0; i < ARRAY_SIZE(pmtu_entries); i++) {
if (!pmtu_entries[i].in_use) {
pmtu_entries[i].in_use = true;
pmtu_entries[i].last_update = k_uptime_get_32();
entry = &pmtu_entries[i];
goto out;
}
if (oldest == 0U || pmtu_entries[i].last_update < oldest) {
oldest = pmtu_entries[i].last_update;
entry = &pmtu_entries[i];
}
}
out:
k_mutex_unlock(&lock);
return entry;
}
static void update_pmtu_entry(struct net_pmtu_entry *entry, uint16_t mtu)
{
bool changed = false;
if (entry->mtu != mtu) {
changed = true;
entry->mtu = mtu;
}
entry->last_update = k_uptime_get_32();
if (changed) {
struct net_if *iface;
if (IS_ENABLED(CONFIG_NET_IPV4_PMTU) && entry->dst.family == AF_INET) {
struct net_event_ipv4_pmtu_info info;
net_ipaddr_copy(&info.dst, &entry->dst.in_addr);
info.mtu = mtu;
iface = net_if_ipv4_select_src_iface(&info.dst);
net_mgmt_event_notify_with_info(NET_EVENT_IPV4_PMTU_CHANGED,
iface,
(const void *)&info,
sizeof(struct net_event_ipv4_pmtu_info));
} else if (IS_ENABLED(CONFIG_NET_IPV6_PMTU) && entry->dst.family == AF_INET6) {
struct net_event_ipv6_pmtu_info info;
net_ipaddr_copy(&info.dst, &entry->dst.in6_addr);
info.mtu = mtu;
iface = net_if_ipv6_select_src_iface(&info.dst);
net_mgmt_event_notify_with_info(NET_EVENT_IPV6_PMTU_CHANGED,
iface,
(const void *)&info,
sizeof(struct net_event_ipv6_pmtu_info));
}
}
}
struct net_pmtu_entry *net_pmtu_get_entry(const struct sockaddr *dst)
{
struct net_pmtu_entry *entry;
entry = get_pmtu_entry(dst);
return entry;
}
int net_pmtu_get_mtu(const struct sockaddr *dst)
{
struct net_pmtu_entry *entry;
entry = get_pmtu_entry(dst);
if (entry == NULL) {
return -ENOENT;
}
return entry->mtu;
}
static struct net_pmtu_entry *add_entry(const struct sockaddr *dst, bool *old_entry)
{
struct net_pmtu_entry *entry;
entry = get_pmtu_entry(dst);
if (entry != NULL) {
*old_entry = true;
return entry;
}
entry = get_free_pmtu_entry();
if (entry == NULL) {
return NULL;
}
k_mutex_lock(&lock, K_FOREVER);
switch (dst->sa_family) {
case AF_INET:
if (IS_ENABLED(CONFIG_NET_IPV4_PMTU)) {
entry->dst.family = AF_INET;
net_ipaddr_copy(&entry->dst.in_addr, &net_sin(dst)->sin_addr);
} else {
entry->in_use = false;
goto unlock_fail;
}
break;
case AF_INET6:
if (IS_ENABLED(CONFIG_NET_IPV6_PMTU)) {
entry->dst.family = AF_INET6;
net_ipaddr_copy(&entry->dst.in6_addr, &net_sin6(dst)->sin6_addr);
} else {
entry->in_use = false;
goto unlock_fail;
}
break;
default:
entry->in_use = false;
goto unlock_fail;
}
k_mutex_unlock(&lock);
return entry;
unlock_fail:
*old_entry = false;
k_mutex_unlock(&lock);
return NULL;
}
int net_pmtu_update_mtu(const struct sockaddr *dst, uint16_t mtu)
{
struct net_pmtu_entry *entry;
uint16_t old_mtu = 0U;
bool updated = false;
entry = add_entry(dst, &updated);
if (entry == NULL) {
return -ENOMEM;
}
if (updated) {
old_mtu = entry->mtu;
}
update_pmtu_entry(entry, mtu);
return (int)old_mtu;
}
int net_pmtu_update_entry(struct net_pmtu_entry *entry, uint16_t mtu)
{
uint16_t old_mtu;
if (entry == NULL) {
return -EINVAL;
}
if (entry->mtu == mtu) {
return -EALREADY;
}
old_mtu = entry->mtu;
update_pmtu_entry(entry, mtu);
return (int)old_mtu;
}
int net_pmtu_foreach(net_pmtu_cb_t cb, void *user_data)
{
int ret = 0;
k_mutex_lock(&lock, K_FOREVER);
ARRAY_FOR_EACH(pmtu_entries, i) {
ret++;
cb(&pmtu_entries[i], user_data);
}
k_mutex_unlock(&lock);
return ret;
}
void net_pmtu_init(void)
{
k_mutex_lock(&lock, K_FOREVER);
ARRAY_FOR_EACH(pmtu_entries, i) {
pmtu_entries[i].in_use = false;
}
k_mutex_unlock(&lock);
}