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