1 /** @file
2  * @brief ICMPv6 related functions
3  */
4 
5 /*
6  * Copyright (c) 2016 Intel Corporation
7  *
8  * SPDX-License-Identifier: Apache-2.0
9  */
10 
11 #include <zephyr/logging/log.h>
12 LOG_MODULE_REGISTER(net_icmpv6, CONFIG_NET_ICMPV6_LOG_LEVEL);
13 
14 #include <errno.h>
15 #include <zephyr/sys/slist.h>
16 #include <zephyr/sys/byteorder.h>
17 #include <zephyr/net/net_core.h>
18 #include <zephyr/net/net_pkt.h>
19 #include <zephyr/net/net_if.h>
20 #include <zephyr/net/icmp.h>
21 #include "net_private.h"
22 #include "icmpv6.h"
23 #include "ipv6.h"
24 #include "net_stats.h"
25 
26 #define PKT_WAIT_TIME K_SECONDS(1)
27 
net_icmpv6_type2str(int icmpv6_type)28 const char *net_icmpv6_type2str(int icmpv6_type)
29 {
30 	switch (icmpv6_type) {
31 	case NET_ICMPV6_DST_UNREACH:
32 		return "Destination Unreachable";
33 	case NET_ICMPV6_PACKET_TOO_BIG:
34 		return "Packet Too Big";
35 	case NET_ICMPV6_TIME_EXCEEDED:
36 		return "Time Exceeded";
37 	case NET_ICMPV6_PARAM_PROBLEM:
38 		return "IPv6 Bad Header";
39 	case NET_ICMPV6_ECHO_REQUEST:
40 		return "Echo Request";
41 	case NET_ICMPV6_ECHO_REPLY:
42 		return "Echo Reply";
43 	case NET_ICMPV6_MLD_QUERY:
44 		return "Multicast Listener Query";
45 	case NET_ICMPV6_RS:
46 		return "Router Solicitation";
47 	case NET_ICMPV6_RA:
48 		return "Router Advertisement";
49 	case NET_ICMPV6_NS:
50 		return "Neighbor Solicitation";
51 	case NET_ICMPV6_NA:
52 		return "Neighbor Advertisement";
53 	case NET_ICMPV6_MLDv2:
54 		return "Multicast Listener Report v2";
55 	}
56 
57 	return "?";
58 }
59 
net_icmpv6_finalize(struct net_pkt * pkt,bool force_chksum)60 int net_icmpv6_finalize(struct net_pkt *pkt, bool force_chksum)
61 {
62 	NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmp_access,
63 					      struct net_icmp_hdr);
64 	struct net_icmp_hdr *icmp_hdr;
65 
66 	icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(pkt, &icmp_access);
67 	if (!icmp_hdr) {
68 		return -ENOBUFS;
69 	}
70 
71 	icmp_hdr->chksum = 0U;
72 	if (net_if_need_calc_tx_checksum(net_pkt_iface(pkt), NET_IF_CHECKSUM_IPV6_ICMP) ||
73 		force_chksum) {
74 		icmp_hdr->chksum = net_calc_chksum_icmpv6(pkt);
75 		net_pkt_set_chksum_done(pkt, true);
76 	}
77 
78 	return net_pkt_set_data(pkt, &icmp_access);
79 }
80 
net_icmpv6_create(struct net_pkt * pkt,uint8_t icmp_type,uint8_t icmp_code)81 int net_icmpv6_create(struct net_pkt *pkt, uint8_t icmp_type, uint8_t icmp_code)
82 {
83 	NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmp_access,
84 					      struct net_icmp_hdr);
85 	struct net_icmp_hdr *icmp_hdr;
86 
87 	icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(pkt, &icmp_access);
88 	if (!icmp_hdr) {
89 		return -ENOBUFS;
90 	}
91 
92 	icmp_hdr->type   = icmp_type;
93 	icmp_hdr->code   = icmp_code;
94 	icmp_hdr->chksum = 0U;
95 
96 	return net_pkt_set_data(pkt, &icmp_access);
97 }
98 
icmpv6_handle_echo_request(struct net_icmp_ctx * ctx,struct net_pkt * pkt,struct net_icmp_ip_hdr * hdr,struct net_icmp_hdr * icmp_hdr,void * user_data)99 static int icmpv6_handle_echo_request(struct net_icmp_ctx *ctx,
100 				      struct net_pkt *pkt,
101 				      struct net_icmp_ip_hdr *hdr,
102 				      struct net_icmp_hdr *icmp_hdr,
103 				      void *user_data)
104 {
105 	struct net_pkt *reply = NULL;
106 	struct net_ipv6_hdr *ip_hdr = hdr->ipv6;
107 	struct in6_addr req_src, req_dst;
108 	const struct in6_addr *src;
109 	int16_t payload_len;
110 
111 	ARG_UNUSED(user_data);
112 	ARG_UNUSED(icmp_hdr);
113 
114 	net_ipv6_addr_copy_raw(req_src.s6_addr, ip_hdr->src);
115 	net_ipv6_addr_copy_raw(req_dst.s6_addr, ip_hdr->dst);
116 
117 	NET_DBG("Received Echo Request from %s to %s",
118 		net_sprint_ipv6_addr(&req_src),
119 		net_sprint_ipv6_addr(&req_dst));
120 
121 	payload_len = ntohs(ip_hdr->len) -
122 		net_pkt_ipv6_ext_len(pkt) - NET_ICMPH_LEN;
123 	if (payload_len < NET_ICMPV6_UNUSED_LEN) {
124 		/* No identifier or sequence number present */
125 		goto drop;
126 	}
127 
128 	reply = net_pkt_alloc_with_buffer(net_pkt_iface(pkt), payload_len,
129 					  AF_INET6, IPPROTO_ICMPV6,
130 					  PKT_WAIT_TIME);
131 	if (!reply) {
132 		NET_DBG("DROP: No buffer");
133 		goto drop;
134 	}
135 
136 	if (net_ipv6_is_addr_mcast_raw(ip_hdr->dst)) {
137 		src = net_if_ipv6_select_src_addr(net_pkt_iface(pkt),
138 						  &req_src);
139 
140 		if (net_ipv6_is_addr_unspecified(src)) {
141 			NET_DBG("DROP: No src address match");
142 			goto drop;
143 		}
144 	} else {
145 		src = &req_dst;
146 	}
147 
148 	/* We must not set the destination ll address here but trust
149 	 * that it is set properly using a value from neighbor cache.
150 	 * Same for source as it points to original pkt ll src address.
151 	 */
152 	(void)net_linkaddr_clear(net_pkt_lladdr_dst(reply));
153 	(void)net_linkaddr_clear(net_pkt_lladdr_src(reply));
154 
155 	net_pkt_set_ip_dscp(reply, net_pkt_ip_dscp(pkt));
156 	net_pkt_set_ip_ecn(reply, net_pkt_ip_ecn(pkt));
157 
158 	if (net_ipv6_create(reply, src, &req_src)) {
159 		NET_DBG("DROP: wrong buffer");
160 		goto drop;
161 	}
162 
163 	if (net_icmpv6_create(reply, NET_ICMPV6_ECHO_REPLY, 0) ||
164 	    net_pkt_copy(reply, pkt, payload_len)) {
165 		NET_DBG("DROP: wrong buffer");
166 		goto drop;
167 	}
168 
169 	net_pkt_cursor_init(reply);
170 	net_ipv6_finalize(reply, IPPROTO_ICMPV6);
171 
172 	NET_DBG("Sending Echo Reply from %s to %s",
173 		net_sprint_ipv6_addr(src),
174 		net_sprint_ipv6_addr(&req_src));
175 
176 	if (net_try_send_data(reply, K_NO_WAIT) < 0) {
177 		goto drop;
178 	}
179 
180 	net_stats_update_icmp_sent(net_pkt_iface(reply));
181 
182 	return 0;
183 
184 drop:
185 	if (reply) {
186 		net_pkt_unref(reply);
187 	}
188 
189 	net_stats_update_icmp_drop(net_pkt_iface(pkt));
190 
191 	return -EIO;
192 }
193 
net_icmpv6_send_error(struct net_pkt * orig,uint8_t type,uint8_t code,uint32_t param)194 int net_icmpv6_send_error(struct net_pkt *orig, uint8_t type, uint8_t code,
195 			  uint32_t param)
196 {
197 	NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv6_access, struct net_ipv6_hdr);
198 	int err = -EIO;
199 	struct in6_addr orig_src, orig_dst;
200 	struct net_ipv6_hdr *ip_hdr;
201 	const struct in6_addr *src;
202 	struct net_pkt *pkt;
203 	size_t copy_len;
204 	int ret;
205 
206 	net_pkt_cursor_init(orig);
207 
208 	ip_hdr = (struct net_ipv6_hdr *)net_pkt_get_data(orig, &ipv6_access);
209 	if (!ip_hdr) {
210 		goto drop_no_pkt;
211 	}
212 
213 	if (ip_hdr->nexthdr == IPPROTO_ICMPV6) {
214 		NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmpv6_access,
215 						      struct net_icmp_hdr);
216 		struct net_icmp_hdr *icmp_hdr;
217 
218 		net_pkt_acknowledge_data(orig, &ipv6_access);
219 
220 		icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(
221 							orig, &icmpv6_access);
222 		if (!icmp_hdr || icmp_hdr->type < 128) {
223 			/* We must not send ICMP errors back */
224 			err = -EINVAL;
225 			goto drop_no_pkt;
226 		}
227 
228 		net_pkt_cursor_init(orig);
229 	}
230 
231 	net_ipv6_addr_copy_raw(orig_src.s6_addr, ip_hdr->src);
232 	net_ipv6_addr_copy_raw(orig_dst.s6_addr, ip_hdr->dst);
233 
234 	if (ip_hdr->nexthdr == IPPROTO_UDP) {
235 		copy_len = sizeof(struct net_ipv6_hdr) +
236 			sizeof(struct net_udp_hdr);
237 	} else if (ip_hdr->nexthdr == IPPROTO_TCP) {
238 		copy_len = sizeof(struct net_ipv6_hdr) +
239 			sizeof(struct net_tcp_hdr);
240 	} else {
241 		copy_len = net_pkt_get_len(orig);
242 	}
243 
244 	pkt = net_pkt_alloc_with_buffer(net_pkt_iface(orig),
245 					net_pkt_lladdr_src(orig)->len * 2 +
246 					copy_len + NET_ICMPV6_UNUSED_LEN,
247 					AF_INET6, IPPROTO_ICMPV6,
248 					PKT_WAIT_TIME);
249 	if (!pkt) {
250 		err = -ENOMEM;
251 		goto drop_no_pkt;
252 	}
253 
254 	/* We created above a new packet that contains some extra space that we
255 	 * will use to store the destination and source link addresses. This is
256 	 * needed because we cannot use the original pkt, which contains the
257 	 * link address where the new packet will be sent, as that pkt might
258 	 * get re-used before we have managed to set the link addresses in L2
259 	 * as that (link address setting) happens in a different thread (TX)
260 	 * than this one.
261 	 * So we copy the destination and source link addresses here, then set
262 	 * the link address pointers correctly, and skip the needed space
263 	 * as the link address will be set in the pkt when the packet is
264 	 * constructed in L2. So basically all this for just to create some
265 	 * extra space for link addresses so that we can set the lladdr
266 	 * pointers in net_pkt.
267 	 */
268 	ret = net_pkt_write(pkt, net_pkt_lladdr_src(orig)->addr,
269 			    net_pkt_lladdr_src(orig)->len);
270 	if (ret < 0) {
271 		err = ret;
272 		goto drop;
273 	}
274 
275 	memcpy(net_pkt_lladdr_dst(pkt)->addr, pkt->buffer->data,
276 	       net_pkt_lladdr_dst(orig)->len);
277 
278 	ret = net_pkt_write(pkt, net_pkt_lladdr_dst(orig)->addr,
279 			    net_pkt_lladdr_dst(orig)->len);
280 	if (ret < 0) {
281 		err = ret;
282 		goto drop;
283 	}
284 
285 	net_buf_pull_mem(pkt->buffer, net_pkt_lladdr_dst(orig)->len);
286 
287 	memcpy(net_pkt_lladdr_src(pkt)->addr, pkt->buffer->data,
288 	       net_pkt_lladdr_src(orig)->len);
289 
290 	net_buf_pull_mem(pkt->buffer, net_pkt_lladdr_src(orig)->len);
291 
292 	net_pkt_lladdr_src(pkt)->len = net_pkt_lladdr_dst(orig)->len;
293 	net_pkt_lladdr_dst(pkt)->len = net_pkt_lladdr_src(orig)->len;
294 
295 	if (net_ipv6_is_addr_mcast_raw(ip_hdr->dst)) {
296 		src = net_if_ipv6_select_src_addr(net_pkt_iface(pkt),
297 						  &orig_dst);
298 	} else {
299 		src = &orig_dst;
300 	}
301 
302 	if (net_ipv6_create(pkt, src, &orig_src) ||
303 	    net_icmpv6_create(pkt, type, code)) {
304 		goto drop;
305 	}
306 
307 	/* Depending on error option, we store the param into the ICMP message.
308 	 */
309 	if (type == NET_ICMPV6_PARAM_PROBLEM) {
310 		err = net_pkt_write_be32(pkt, param);
311 	} else {
312 		err = net_pkt_memset(pkt, 0, NET_ICMPV6_UNUSED_LEN);
313 	}
314 
315 	/* Allocator might not have been able to allocate all requested space,
316 	 * so let's copy as much as we can.
317 	 */
318 	copy_len = net_pkt_available_buffer(pkt);
319 
320 	if (err || net_pkt_copy(pkt, orig, copy_len)) {
321 		goto drop;
322 	}
323 
324 	net_pkt_cursor_init(pkt);
325 	net_ipv6_finalize(pkt, IPPROTO_ICMPV6);
326 
327 	NET_DBG("Sending ICMPv6 Error Message type %d code %d param %d"
328 		" from %s to %s", type, code, param,
329 		net_sprint_ipv6_addr(src),
330 		net_sprint_ipv6_addr(&orig_src));
331 
332 	if (net_try_send_data(pkt, K_NO_WAIT) >= 0) {
333 		net_stats_update_icmp_sent(net_pkt_iface(pkt));
334 		return 0;
335 	}
336 
337 drop:
338 	net_pkt_unref(pkt);
339 
340 drop_no_pkt:
341 	net_stats_update_icmp_drop(net_pkt_iface(orig));
342 
343 	return err;
344 }
345 
net_icmpv6_input(struct net_pkt * pkt,struct net_ipv6_hdr * ip_hdr)346 enum net_verdict net_icmpv6_input(struct net_pkt *pkt,
347 				  struct net_ipv6_hdr *ip_hdr)
348 {
349 	NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmp_access,
350 					      struct net_icmp_hdr);
351 	struct net_icmp_hdr *icmp_hdr;
352 	int ret;
353 
354 	icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(pkt, &icmp_access);
355 	if (!icmp_hdr) {
356 		NET_DBG("DROP: NULL ICMPv6 header");
357 		return NET_DROP;
358 	}
359 
360 
361 	if (net_if_need_calc_rx_checksum(net_pkt_iface(pkt), NET_IF_CHECKSUM_IPV6_ICMP) ||
362 	    net_pkt_is_ip_reassembled(pkt)) {
363 		if (net_calc_chksum_icmpv6(pkt) != 0U) {
364 			NET_DBG("DROP: invalid checksum");
365 			goto drop;
366 		}
367 	}
368 
369 	net_pkt_acknowledge_data(pkt, &icmp_access);
370 
371 	NET_DBG("ICMPv6 %s received type %d code %d",
372 		net_icmpv6_type2str(icmp_hdr->type),
373 		icmp_hdr->type, icmp_hdr->code);
374 
375 	net_stats_update_icmp_recv(net_pkt_iface(pkt));
376 
377 	ret = net_icmp_call_ipv6_handlers(pkt, ip_hdr, icmp_hdr);
378 	if (ret < 0 && ret != -ENOENT) {
379 		NET_ERR("ICMPv6 handling failure (%d)", ret);
380 	}
381 
382 	net_pkt_unref(pkt);
383 
384 	return NET_OK;
385 
386 drop:
387 	net_stats_update_icmp_drop(net_pkt_iface(pkt));
388 
389 	return NET_DROP;
390 }
391 
net_icmpv6_init(void)392 void net_icmpv6_init(void)
393 {
394 	static struct net_icmp_ctx ctx;
395 	int ret;
396 
397 	ret = net_icmp_init_ctx(&ctx, NET_ICMPV6_ECHO_REQUEST, 0, icmpv6_handle_echo_request);
398 	if (ret < 0) {
399 		NET_ERR("Cannot register %s handler (%d)", STRINGIFY(NET_ICMPV6_ECHO_REQUEST),
400 			ret);
401 	}
402 }
403