zephyr/subsys/net/ip/pmtu.c
Jukka Rissanen 3da2629715 net: ip: pmtu: Add generic IP PMTU Discovery support
This adds generic code that can be used by both IPv4 and IPv6
Path MTU Discovery mechanism. The actual PMTU support for each
protocol family is in subsequent commits.

Signed-off-by: Jukka Rissanen <jukka.rissanen@nordicsemi.no>
2024-11-18 07:26:07 -05:00

250 lines
4.5 KiB
C

/** @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 "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)
{
entry->mtu = mtu;
entry->last_update = k_uptime_get_32();
}
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);
}