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