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 	const struct in6_addr *src;
108 	int16_t payload_len;
109 
110 	ARG_UNUSED(user_data);
111 	ARG_UNUSED(icmp_hdr);
112 
113 	NET_DBG("Received Echo Request from %s to %s",
114 		net_sprint_ipv6_addr(&ip_hdr->src),
115 		net_sprint_ipv6_addr(&ip_hdr->dst));
116 
117 	payload_len = ntohs(ip_hdr->len) -
118 		net_pkt_ipv6_ext_len(pkt) - NET_ICMPH_LEN;
119 	if (payload_len < NET_ICMPV6_UNUSED_LEN) {
120 		/* No identifier or sequence number present */
121 		goto drop;
122 	}
123 
124 	reply = net_pkt_alloc_with_buffer(net_pkt_iface(pkt), payload_len,
125 					  AF_INET6, IPPROTO_ICMPV6,
126 					  PKT_WAIT_TIME);
127 	if (!reply) {
128 		NET_DBG("DROP: No buffer");
129 		goto drop;
130 	}
131 
132 	if (net_ipv6_is_addr_mcast((struct in6_addr *)ip_hdr->dst)) {
133 		src = net_if_ipv6_select_src_addr(net_pkt_iface(pkt),
134 						  (struct in6_addr *)ip_hdr->src);
135 
136 		if (net_ipv6_is_addr_unspecified(src)) {
137 			NET_DBG("DROP: No src address match");
138 			goto drop;
139 		}
140 	} else {
141 		src = (struct in6_addr *)ip_hdr->dst;
142 	}
143 
144 	/* We must not set the destination ll address here but trust
145 	 * that it is set properly using a value from neighbor cache.
146 	 * Same for source as it points to original pkt ll src address.
147 	 */
148 	net_pkt_lladdr_dst(reply)->addr = NULL;
149 	net_pkt_lladdr_src(reply)->addr = NULL;
150 
151 	net_pkt_set_ip_dscp(reply, net_pkt_ip_dscp(pkt));
152 	net_pkt_set_ip_ecn(reply, net_pkt_ip_ecn(pkt));
153 
154 	if (net_ipv6_create(reply, src, (struct in6_addr *)ip_hdr->src)) {
155 		NET_DBG("DROP: wrong buffer");
156 		goto drop;
157 	}
158 
159 	if (net_icmpv6_create(reply, NET_ICMPV6_ECHO_REPLY, 0) ||
160 	    net_pkt_copy(reply, pkt, payload_len)) {
161 		NET_DBG("DROP: wrong buffer");
162 		goto drop;
163 	}
164 
165 	net_pkt_cursor_init(reply);
166 	net_ipv6_finalize(reply, IPPROTO_ICMPV6);
167 
168 	NET_DBG("Sending Echo Reply from %s to %s",
169 		net_sprint_ipv6_addr(src),
170 		net_sprint_ipv6_addr(&ip_hdr->src));
171 
172 	if (net_send_data(reply) < 0) {
173 		goto drop;
174 	}
175 
176 	net_stats_update_icmp_sent(net_pkt_iface(reply));
177 
178 	return 0;
179 
180 drop:
181 	if (reply) {
182 		net_pkt_unref(reply);
183 	}
184 
185 	net_stats_update_icmp_drop(net_pkt_iface(pkt));
186 
187 	return -EIO;
188 }
189 
net_icmpv6_send_error(struct net_pkt * orig,uint8_t type,uint8_t code,uint32_t param)190 int net_icmpv6_send_error(struct net_pkt *orig, uint8_t type, uint8_t code,
191 			  uint32_t param)
192 {
193 	NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv6_access, struct net_ipv6_hdr);
194 	int err = -EIO;
195 	struct net_ipv6_hdr *ip_hdr;
196 	const struct in6_addr *src;
197 	struct net_pkt *pkt;
198 	size_t copy_len;
199 	int ret;
200 
201 	net_pkt_cursor_init(orig);
202 
203 	ip_hdr = (struct net_ipv6_hdr *)net_pkt_get_data(orig, &ipv6_access);
204 	if (!ip_hdr) {
205 		goto drop_no_pkt;
206 	}
207 
208 	if (ip_hdr->nexthdr == IPPROTO_ICMPV6) {
209 		NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmpv6_access,
210 						      struct net_icmp_hdr);
211 		struct net_icmp_hdr *icmp_hdr;
212 
213 		net_pkt_acknowledge_data(orig, &ipv6_access);
214 
215 		icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(
216 							orig, &icmpv6_access);
217 		if (!icmp_hdr || icmp_hdr->type < 128) {
218 			/* We must not send ICMP errors back */
219 			err = -EINVAL;
220 			goto drop_no_pkt;
221 		}
222 
223 		net_pkt_cursor_init(orig);
224 	}
225 
226 	if (ip_hdr->nexthdr == IPPROTO_UDP) {
227 		copy_len = sizeof(struct net_ipv6_hdr) +
228 			sizeof(struct net_udp_hdr);
229 	} else if (ip_hdr->nexthdr == IPPROTO_TCP) {
230 		copy_len = sizeof(struct net_ipv6_hdr) +
231 			sizeof(struct net_tcp_hdr);
232 	} else {
233 		copy_len = net_pkt_get_len(orig);
234 	}
235 
236 	pkt = net_pkt_alloc_with_buffer(net_pkt_iface(orig),
237 					net_pkt_lladdr_src(orig)->len * 2 +
238 					copy_len + NET_ICMPV6_UNUSED_LEN,
239 					AF_INET6, IPPROTO_ICMPV6,
240 					PKT_WAIT_TIME);
241 	if (!pkt) {
242 		err = -ENOMEM;
243 		goto drop_no_pkt;
244 	}
245 
246 	/* We created above a new packet that contains some extra space that we
247 	 * will use to store the destination and source link addresses. This is
248 	 * needed because we cannot use the original pkt, which contains the
249 	 * link address where the new packet will be sent, as that pkt might
250 	 * get re-used before we have managed to set the link addresses in L2
251 	 * as that (link address setting) happens in a different thread (TX)
252 	 * than this one.
253 	 * So we copy the destination and source link addresses here, then set
254 	 * the link address pointers correctly, and skip the needed space
255 	 * as the link address will be set in the pkt when the packet is
256 	 * constructed in L2. So basically all this for just to create some
257 	 * extra space for link addresses so that we can set the lladdr
258 	 * pointers in net_pkt.
259 	 */
260 	ret = net_pkt_write(pkt, net_pkt_lladdr_src(orig)->addr,
261 			    net_pkt_lladdr_src(orig)->len);
262 	if (ret < 0) {
263 		err = ret;
264 		goto drop;
265 	}
266 
267 	net_pkt_lladdr_dst(pkt)->addr = pkt->buffer->data;
268 
269 	ret = net_pkt_write(pkt, net_pkt_lladdr_dst(orig)->addr,
270 			    net_pkt_lladdr_dst(orig)->len);
271 	if (ret < 0) {
272 		err = ret;
273 		goto drop;
274 	}
275 
276 	net_buf_pull_mem(pkt->buffer, net_pkt_lladdr_dst(orig)->len);
277 
278 	net_pkt_lladdr_src(pkt)->addr = pkt->buffer->data;
279 
280 	net_buf_pull_mem(pkt->buffer, net_pkt_lladdr_src(orig)->len);
281 
282 	net_pkt_lladdr_src(pkt)->len = net_pkt_lladdr_dst(orig)->len;
283 	net_pkt_lladdr_dst(pkt)->len = net_pkt_lladdr_src(orig)->len;
284 
285 	if (net_ipv6_is_addr_mcast((struct in6_addr *)ip_hdr->dst)) {
286 		src = net_if_ipv6_select_src_addr(net_pkt_iface(pkt),
287 						  (struct in6_addr *)ip_hdr->dst);
288 	} else {
289 		src = (struct in6_addr *)ip_hdr->dst;
290 	}
291 
292 	if (net_ipv6_create(pkt, src, (struct in6_addr *)ip_hdr->src) ||
293 	    net_icmpv6_create(pkt, type, code)) {
294 		goto drop;
295 	}
296 
297 	/* Depending on error option, we store the param into the ICMP message.
298 	 */
299 	if (type == NET_ICMPV6_PARAM_PROBLEM) {
300 		err = net_pkt_write_be32(pkt, param);
301 	} else {
302 		err = net_pkt_memset(pkt, 0, NET_ICMPV6_UNUSED_LEN);
303 	}
304 
305 	/* Allocator might not have been able to allocate all requested space,
306 	 * so let's copy as much as we can.
307 	 */
308 	copy_len = net_pkt_available_buffer(pkt);
309 
310 	if (err || net_pkt_copy(pkt, orig, copy_len)) {
311 		goto drop;
312 	}
313 
314 	net_pkt_cursor_init(pkt);
315 	net_ipv6_finalize(pkt, IPPROTO_ICMPV6);
316 
317 	NET_DBG("Sending ICMPv6 Error Message type %d code %d param %d"
318 		" from %s to %s", type, code, param,
319 		net_sprint_ipv6_addr(src),
320 		net_sprint_ipv6_addr(&ip_hdr->src));
321 
322 	if (net_send_data(pkt) >= 0) {
323 		net_stats_update_icmp_sent(net_pkt_iface(pkt));
324 		return 0;
325 	}
326 
327 drop:
328 	net_pkt_unref(pkt);
329 
330 drop_no_pkt:
331 	net_stats_update_icmp_drop(net_pkt_iface(orig));
332 
333 	return err;
334 }
335 
net_icmpv6_input(struct net_pkt * pkt,struct net_ipv6_hdr * ip_hdr)336 enum net_verdict net_icmpv6_input(struct net_pkt *pkt,
337 				  struct net_ipv6_hdr *ip_hdr)
338 {
339 	NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmp_access,
340 					      struct net_icmp_hdr);
341 	struct net_icmp_hdr *icmp_hdr;
342 	int ret;
343 
344 	icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(pkt, &icmp_access);
345 	if (!icmp_hdr) {
346 		NET_DBG("DROP: NULL ICMPv6 header");
347 		return NET_DROP;
348 	}
349 
350 
351 	if (net_if_need_calc_rx_checksum(net_pkt_iface(pkt), NET_IF_CHECKSUM_IPV6_ICMP) ||
352 	    net_pkt_is_ip_reassembled(pkt)) {
353 		if (net_calc_chksum_icmpv6(pkt) != 0U) {
354 			NET_DBG("DROP: invalid checksum");
355 			goto drop;
356 		}
357 	}
358 
359 	net_pkt_acknowledge_data(pkt, &icmp_access);
360 
361 	NET_DBG("ICMPv6 %s received type %d code %d",
362 		net_icmpv6_type2str(icmp_hdr->type),
363 		icmp_hdr->type, icmp_hdr->code);
364 
365 	net_stats_update_icmp_recv(net_pkt_iface(pkt));
366 
367 	ret = net_icmp_call_ipv6_handlers(pkt, ip_hdr, icmp_hdr);
368 	if (ret < 0 && ret != -ENOENT) {
369 		NET_ERR("ICMPv6 handling failure (%d)", ret);
370 	}
371 
372 	net_pkt_unref(pkt);
373 
374 	return NET_OK;
375 
376 drop:
377 	net_stats_update_icmp_drop(net_pkt_iface(pkt));
378 
379 	return NET_DROP;
380 }
381 
net_icmpv6_init(void)382 void net_icmpv6_init(void)
383 {
384 	static struct net_icmp_ctx ctx;
385 	int ret;
386 
387 	ret = net_icmp_init_ctx(&ctx, NET_ICMPV6_ECHO_REQUEST, 0, icmpv6_handle_echo_request);
388 	if (ret < 0) {
389 		NET_ERR("Cannot register %s handler (%d)", STRINGIFY(NET_ICMPV6_ECHO_REQUEST),
390 			ret);
391 	}
392 }
393