You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
287 lines
5.5 KiB
287 lines
5.5 KiB
/** @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_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); |
|
}
|
|
|