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