1 /** @file
2 * @brief IPv4/6 PMTU related functions
3 */
4
5 /*
6 * Copyright (c) 2024 Nordic Semiconductor
7 *
8 * SPDX-License-Identifier: Apache-2.0
9 */
10
11 #include <zephyr/logging/log.h>
12 LOG_MODULE_REGISTER(net_pmtu, CONFIG_NET_PMTU_LOG_LEVEL);
13
14 #include <zephyr/kernel.h>
15 #include <zephyr/net/net_event.h>
16 #include <zephyr/net/net_mgmt.h>
17 #include <zephyr/net/net_if.h>
18 #include "pmtu.h"
19
20 #if defined(CONFIG_NET_IPV4_PMTU)
21 #define NET_IPV4_PMTU_ENTRIES CONFIG_NET_IPV4_PMTU_DESTINATION_CACHE_ENTRIES
22 #else
23 #define NET_IPV4_PMTU_ENTRIES 0
24 #endif
25
26 #if defined(CONFIG_NET_IPV6_PMTU)
27 #define NET_IPV6_PMTU_ENTRIES CONFIG_NET_IPV6_PMTU_DESTINATION_CACHE_ENTRIES
28 #else
29 #define NET_IPV6_PMTU_ENTRIES 0
30 #endif
31
32 #define NET_PMTU_MAX_ENTRIES (NET_IPV4_PMTU_ENTRIES + NET_IPV6_PMTU_ENTRIES)
33
34 static struct net_pmtu_entry pmtu_entries[NET_PMTU_MAX_ENTRIES];
35
36 static K_MUTEX_DEFINE(lock);
37
get_pmtu_entry(const struct sockaddr * dst)38 static struct net_pmtu_entry *get_pmtu_entry(const struct sockaddr *dst)
39 {
40 struct net_pmtu_entry *entry = NULL;
41 int i;
42
43 k_mutex_lock(&lock, K_FOREVER);
44
45 for (i = 0; i < ARRAY_SIZE(pmtu_entries); i++) {
46 switch (dst->sa_family) {
47 case AF_INET:
48 if (IS_ENABLED(CONFIG_NET_IPV4_PMTU) &&
49 pmtu_entries[i].dst.family == AF_INET &&
50 net_ipv4_addr_cmp(&pmtu_entries[i].dst.in_addr,
51 &net_sin(dst)->sin_addr)) {
52 entry = &pmtu_entries[i];
53 goto out;
54 }
55 break;
56
57 case AF_INET6:
58 if (IS_ENABLED(CONFIG_NET_IPV6_PMTU) &&
59 pmtu_entries[i].dst.family == AF_INET6 &&
60 net_ipv6_addr_cmp(&pmtu_entries[i].dst.in6_addr,
61 &net_sin6(dst)->sin6_addr)) {
62 entry = &pmtu_entries[i];
63 goto out;
64 }
65 break;
66
67 default:
68 break;
69 }
70 }
71
72 out:
73 k_mutex_unlock(&lock);
74
75 return entry;
76 }
77
get_free_pmtu_entry(void)78 static struct net_pmtu_entry *get_free_pmtu_entry(void)
79 {
80 struct net_pmtu_entry *entry = NULL;
81 uint32_t oldest = 0U;
82 int i;
83
84 k_mutex_lock(&lock, K_FOREVER);
85
86 for (i = 0; i < ARRAY_SIZE(pmtu_entries); i++) {
87 if (!pmtu_entries[i].in_use) {
88 pmtu_entries[i].in_use = true;
89 pmtu_entries[i].last_update = k_uptime_get_32();
90
91 entry = &pmtu_entries[i];
92 goto out;
93 }
94
95 if (oldest == 0U || pmtu_entries[i].last_update < oldest) {
96 oldest = pmtu_entries[i].last_update;
97 entry = &pmtu_entries[i];
98 }
99 }
100
101 out:
102 k_mutex_unlock(&lock);
103
104 return entry;
105 }
106
update_pmtu_entry(struct net_pmtu_entry * entry,uint16_t mtu)107 static void update_pmtu_entry(struct net_pmtu_entry *entry, uint16_t mtu)
108 {
109 bool changed = false;
110
111 if (entry->mtu != mtu) {
112 changed = true;
113 entry->mtu = mtu;
114 }
115
116 entry->last_update = k_uptime_get_32();
117
118 if (changed) {
119 struct net_if *iface;
120
121 if (IS_ENABLED(CONFIG_NET_IPV4_PMTU) && entry->dst.family == AF_INET) {
122 struct net_event_ipv4_pmtu_info info;
123
124 net_ipaddr_copy(&info.dst, &entry->dst.in_addr);
125 info.mtu = mtu;
126
127 iface = net_if_ipv4_select_src_iface(&info.dst);
128
129 net_mgmt_event_notify_with_info(NET_EVENT_IPV4_PMTU_CHANGED,
130 iface,
131 (const void *)&info,
132 sizeof(struct net_event_ipv4_pmtu_info));
133
134 } else if (IS_ENABLED(CONFIG_NET_IPV6_PMTU) && entry->dst.family == AF_INET6) {
135 struct net_event_ipv6_pmtu_info info;
136
137 net_ipaddr_copy(&info.dst, &entry->dst.in6_addr);
138 info.mtu = mtu;
139
140 iface = net_if_ipv6_select_src_iface(&info.dst);
141
142 net_mgmt_event_notify_with_info(NET_EVENT_IPV6_PMTU_CHANGED,
143 iface,
144 (const void *)&info,
145 sizeof(struct net_event_ipv6_pmtu_info));
146 }
147 }
148 }
149
net_pmtu_get_entry(const struct sockaddr * dst)150 struct net_pmtu_entry *net_pmtu_get_entry(const struct sockaddr *dst)
151 {
152 struct net_pmtu_entry *entry;
153
154 entry = get_pmtu_entry(dst);
155
156 return entry;
157 }
158
net_pmtu_get_mtu(const struct sockaddr * dst)159 int net_pmtu_get_mtu(const struct sockaddr *dst)
160 {
161 struct net_pmtu_entry *entry;
162
163 entry = get_pmtu_entry(dst);
164 if (entry == NULL) {
165 return -ENOENT;
166 }
167
168 return entry->mtu;
169 }
170
add_entry(const struct sockaddr * dst,bool * old_entry)171 static struct net_pmtu_entry *add_entry(const struct sockaddr *dst, bool *old_entry)
172 {
173 struct net_pmtu_entry *entry;
174
175 entry = get_pmtu_entry(dst);
176 if (entry != NULL) {
177 *old_entry = true;
178 return entry;
179 }
180
181 entry = get_free_pmtu_entry();
182 if (entry == NULL) {
183 return NULL;
184 }
185
186 k_mutex_lock(&lock, K_FOREVER);
187
188 switch (dst->sa_family) {
189 case AF_INET:
190 if (IS_ENABLED(CONFIG_NET_IPV4_PMTU)) {
191 entry->dst.family = AF_INET;
192 net_ipaddr_copy(&entry->dst.in_addr, &net_sin(dst)->sin_addr);
193 } else {
194 entry->in_use = false;
195 goto unlock_fail;
196 }
197 break;
198
199 case AF_INET6:
200 if (IS_ENABLED(CONFIG_NET_IPV6_PMTU)) {
201 entry->dst.family = AF_INET6;
202 net_ipaddr_copy(&entry->dst.in6_addr, &net_sin6(dst)->sin6_addr);
203 } else {
204 entry->in_use = false;
205 goto unlock_fail;
206 }
207 break;
208
209 default:
210 entry->in_use = false;
211 goto unlock_fail;
212 }
213
214 k_mutex_unlock(&lock);
215 return entry;
216
217 unlock_fail:
218 *old_entry = false;
219
220 k_mutex_unlock(&lock);
221 return NULL;
222 }
223
net_pmtu_update_mtu(const struct sockaddr * dst,uint16_t mtu)224 int net_pmtu_update_mtu(const struct sockaddr *dst, uint16_t mtu)
225 {
226 struct net_pmtu_entry *entry;
227 uint16_t old_mtu = 0U;
228 bool updated = false;
229
230 entry = add_entry(dst, &updated);
231 if (entry == NULL) {
232 return -ENOMEM;
233 }
234
235 if (updated) {
236 old_mtu = entry->mtu;
237 }
238
239 update_pmtu_entry(entry, mtu);
240
241 return (int)old_mtu;
242 }
243
net_pmtu_update_entry(struct net_pmtu_entry * entry,uint16_t mtu)244 int net_pmtu_update_entry(struct net_pmtu_entry *entry, uint16_t mtu)
245 {
246 uint16_t old_mtu;
247
248 if (entry == NULL) {
249 return -EINVAL;
250 }
251
252 if (entry->mtu == mtu) {
253 return -EALREADY;
254 }
255
256 old_mtu = entry->mtu;
257
258 update_pmtu_entry(entry, mtu);
259
260 return (int)old_mtu;
261 }
262
net_pmtu_foreach(net_pmtu_cb_t cb,void * user_data)263 int net_pmtu_foreach(net_pmtu_cb_t cb, void *user_data)
264 {
265 int ret = 0;
266
267 k_mutex_lock(&lock, K_FOREVER);
268
269 ARRAY_FOR_EACH(pmtu_entries, i) {
270 ret++;
271 cb(&pmtu_entries[i], user_data);
272 }
273
274 k_mutex_unlock(&lock);
275
276 return ret;
277 }
278
net_pmtu_init(void)279 void net_pmtu_init(void)
280 {
281 k_mutex_lock(&lock, K_FOREVER);
282
283 ARRAY_FOR_EACH(pmtu_entries, i) {
284 pmtu_entries[i].in_use = false;
285 }
286
287 k_mutex_unlock(&lock);
288 }
289