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