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 (void)net_linkaddr_clear(net_pkt_lladdr_dst(reply));
149 (void)net_linkaddr_clear(net_pkt_lladdr_src(reply));
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_try_send_data(reply, K_NO_WAIT) < 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 memcpy(net_pkt_lladdr_dst(pkt)->addr, pkt->buffer->data,
268 net_pkt_lladdr_dst(orig)->len);
269
270 ret = net_pkt_write(pkt, net_pkt_lladdr_dst(orig)->addr,
271 net_pkt_lladdr_dst(orig)->len);
272 if (ret < 0) {
273 err = ret;
274 goto drop;
275 }
276
277 net_buf_pull_mem(pkt->buffer, net_pkt_lladdr_dst(orig)->len);
278
279 memcpy(net_pkt_lladdr_src(pkt)->addr, pkt->buffer->data,
280 net_pkt_lladdr_src(orig)->len);
281
282 net_buf_pull_mem(pkt->buffer, net_pkt_lladdr_src(orig)->len);
283
284 net_pkt_lladdr_src(pkt)->len = net_pkt_lladdr_dst(orig)->len;
285 net_pkt_lladdr_dst(pkt)->len = net_pkt_lladdr_src(orig)->len;
286
287 if (net_ipv6_is_addr_mcast((struct in6_addr *)ip_hdr->dst)) {
288 src = net_if_ipv6_select_src_addr(net_pkt_iface(pkt),
289 (struct in6_addr *)ip_hdr->dst);
290 } else {
291 src = (struct in6_addr *)ip_hdr->dst;
292 }
293
294 if (net_ipv6_create(pkt, src, (struct in6_addr *)ip_hdr->src) ||
295 net_icmpv6_create(pkt, type, code)) {
296 goto drop;
297 }
298
299 /* Depending on error option, we store the param into the ICMP message.
300 */
301 if (type == NET_ICMPV6_PARAM_PROBLEM) {
302 err = net_pkt_write_be32(pkt, param);
303 } else {
304 err = net_pkt_memset(pkt, 0, NET_ICMPV6_UNUSED_LEN);
305 }
306
307 /* Allocator might not have been able to allocate all requested space,
308 * so let's copy as much as we can.
309 */
310 copy_len = net_pkt_available_buffer(pkt);
311
312 if (err || net_pkt_copy(pkt, orig, copy_len)) {
313 goto drop;
314 }
315
316 net_pkt_cursor_init(pkt);
317 net_ipv6_finalize(pkt, IPPROTO_ICMPV6);
318
319 NET_DBG("Sending ICMPv6 Error Message type %d code %d param %d"
320 " from %s to %s", type, code, param,
321 net_sprint_ipv6_addr(src),
322 net_sprint_ipv6_addr(&ip_hdr->src));
323
324 if (net_try_send_data(pkt, K_NO_WAIT) >= 0) {
325 net_stats_update_icmp_sent(net_pkt_iface(pkt));
326 return 0;
327 }
328
329 drop:
330 net_pkt_unref(pkt);
331
332 drop_no_pkt:
333 net_stats_update_icmp_drop(net_pkt_iface(orig));
334
335 return err;
336 }
337
net_icmpv6_input(struct net_pkt * pkt,struct net_ipv6_hdr * ip_hdr)338 enum net_verdict net_icmpv6_input(struct net_pkt *pkt,
339 struct net_ipv6_hdr *ip_hdr)
340 {
341 NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmp_access,
342 struct net_icmp_hdr);
343 struct net_icmp_hdr *icmp_hdr;
344 int ret;
345
346 icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(pkt, &icmp_access);
347 if (!icmp_hdr) {
348 NET_DBG("DROP: NULL ICMPv6 header");
349 return NET_DROP;
350 }
351
352
353 if (net_if_need_calc_rx_checksum(net_pkt_iface(pkt), NET_IF_CHECKSUM_IPV6_ICMP) ||
354 net_pkt_is_ip_reassembled(pkt)) {
355 if (net_calc_chksum_icmpv6(pkt) != 0U) {
356 NET_DBG("DROP: invalid checksum");
357 goto drop;
358 }
359 }
360
361 net_pkt_acknowledge_data(pkt, &icmp_access);
362
363 NET_DBG("ICMPv6 %s received type %d code %d",
364 net_icmpv6_type2str(icmp_hdr->type),
365 icmp_hdr->type, icmp_hdr->code);
366
367 net_stats_update_icmp_recv(net_pkt_iface(pkt));
368
369 ret = net_icmp_call_ipv6_handlers(pkt, ip_hdr, icmp_hdr);
370 if (ret < 0 && ret != -ENOENT) {
371 NET_ERR("ICMPv6 handling failure (%d)", ret);
372 }
373
374 net_pkt_unref(pkt);
375
376 return NET_OK;
377
378 drop:
379 net_stats_update_icmp_drop(net_pkt_iface(pkt));
380
381 return NET_DROP;
382 }
383
net_icmpv6_init(void)384 void net_icmpv6_init(void)
385 {
386 static struct net_icmp_ctx ctx;
387 int ret;
388
389 ret = net_icmp_init_ctx(&ctx, NET_ICMPV6_ECHO_REQUEST, 0, icmpv6_handle_echo_request);
390 if (ret < 0) {
391 NET_ERR("Cannot register %s handler (%d)", STRINGIFY(NET_ICMPV6_ECHO_REQUEST),
392 ret);
393 }
394 }
395