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