/** @file * @brief IPv4/6 PMTU related functions */ /* * Copyright (c) 2024 Nordic Semiconductor * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(net_pmtu, CONFIG_NET_PMTU_LOG_LEVEL); #include #include #include #include #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); }