1 /*
2 * Copyright (c) 2016 Intel Corporation
3 * Copyright (c) 2023 Nordic Semiconductor ASA
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <zephyr/logging/log.h>
9 LOG_MODULE_DECLARE(net_shell);
10
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <zephyr/random/random.h>
14 #include <zephyr/net/icmp.h>
15
16 #include "net_shell_private.h"
17
18 #include "../ip/icmpv6.h"
19 #include "../ip/icmpv4.h"
20 #include "../ip/route.h"
21
22 #if defined(CONFIG_NET_IP)
23
24 static struct ping_context {
25 struct k_work_delayable work;
26 struct net_icmp_ctx icmp;
27 union {
28 struct net_sockaddr_in addr4;
29 struct net_sockaddr_in6 addr6;
30 struct net_sockaddr addr;
31 };
32 struct net_if *iface;
33 const struct shell *sh;
34
35 /* Ping parameters */
36 uint32_t count;
37 uint32_t interval;
38 uint32_t sequence;
39 uint16_t payload_size;
40 uint8_t tos;
41 int priority;
42 } ping_ctx;
43
44 static void ping_done(struct ping_context *ctx);
45
46 #if defined(CONFIG_NET_NATIVE_IPV6)
47
handle_ipv6_echo_reply(struct net_icmp_ctx * ctx,struct net_pkt * pkt,struct net_icmp_ip_hdr * hdr,struct net_icmp_hdr * icmp_hdr,void * user_data)48 static int handle_ipv6_echo_reply(struct net_icmp_ctx *ctx,
49 struct net_pkt *pkt,
50 struct net_icmp_ip_hdr *hdr,
51 struct net_icmp_hdr *icmp_hdr,
52 void *user_data)
53 {
54 NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmp_access,
55 struct net_icmpv6_echo_req);
56 struct net_ipv6_hdr *ip_hdr = hdr->ipv6;
57 struct net_icmpv6_echo_req *icmp_echo;
58 uint32_t cycles;
59 char time_buf[16] = { 0 };
60
61 icmp_echo = (struct net_icmpv6_echo_req *)net_pkt_get_data(pkt,
62 &icmp_access);
63 if (icmp_echo == NULL) {
64 return -EIO;
65 }
66
67 net_pkt_skip(pkt, sizeof(*icmp_echo));
68
69 if (net_pkt_remaining_data(pkt) >= sizeof(uint32_t)) {
70 if (net_pkt_read_be32(pkt, &cycles)) {
71 return -EIO;
72 }
73
74 cycles = k_cycle_get_32() - cycles;
75
76 snprintf(time_buf, sizeof(time_buf),
77 #ifdef CONFIG_FPU
78 "time=%.2f ms",
79 (double)((uint32_t)k_cyc_to_ns_floor64(cycles) / 1000000.f)
80 #else
81 "time=%d ms",
82 ((uint32_t)k_cyc_to_ns_floor64(cycles) / 1000000)
83 #endif
84 );
85 }
86
87 PR_SHELL(ping_ctx.sh, "%d bytes from %s to %s: icmp_seq=%d ttl=%d "
88 #ifdef CONFIG_IEEE802154
89 "rssi=%d "
90 #endif
91 "%s\n",
92 net_ntohs(ip_hdr->len) - net_pkt_ipv6_ext_len(pkt) -
93 NET_ICMPH_LEN,
94 net_sprint_ipv6_addr(&ip_hdr->src),
95 net_sprint_ipv6_addr(&ip_hdr->dst),
96 net_ntohs(icmp_echo->sequence),
97 ip_hdr->hop_limit,
98 #ifdef CONFIG_IEEE802154
99 net_pkt_ieee802154_rssi_dbm(pkt),
100 #endif
101 time_buf);
102
103 if (net_ntohs(icmp_echo->sequence) == ping_ctx.count) {
104 ping_done(&ping_ctx);
105 }
106
107 return 0;
108 }
109 #else
handle_ipv6_echo_reply(struct net_icmp_ctx * ctx,struct net_pkt * pkt,struct net_icmp_ip_hdr * hdr,struct net_icmp_hdr * icmp_hdr,void * user_data)110 static int handle_ipv6_echo_reply(struct net_icmp_ctx *ctx,
111 struct net_pkt *pkt,
112 struct net_icmp_ip_hdr *hdr,
113 struct net_icmp_hdr *icmp_hdr,
114 void *user_data)
115 {
116 ARG_UNUSED(ctx);
117 ARG_UNUSED(pkt);
118 ARG_UNUSED(hdr);
119 ARG_UNUSED(icmp_hdr);
120 ARG_UNUSED(user_data);
121
122 return -ENOTSUP;
123 }
124 #endif /* CONFIG_NET_IPV6 */
125
126 #if defined(CONFIG_NET_NATIVE_IPV4)
127
handle_ipv4_echo_reply(struct net_icmp_ctx * ctx,struct net_pkt * pkt,struct net_icmp_ip_hdr * hdr,struct net_icmp_hdr * icmp_hdr,void * user_data)128 static int handle_ipv4_echo_reply(struct net_icmp_ctx *ctx,
129 struct net_pkt *pkt,
130 struct net_icmp_ip_hdr *hdr,
131 struct net_icmp_hdr *icmp_hdr,
132 void *user_data)
133 {
134 NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmp_access,
135 struct net_icmpv4_echo_req);
136 struct net_ipv4_hdr *ip_hdr = hdr->ipv4;
137 uint32_t cycles;
138 struct net_icmpv4_echo_req *icmp_echo;
139 char time_buf[16] = { 0 };
140
141 icmp_echo = (struct net_icmpv4_echo_req *)net_pkt_get_data(pkt,
142 &icmp_access);
143 if (icmp_echo == NULL) {
144 return -EIO;
145 }
146
147 net_pkt_skip(pkt, sizeof(*icmp_echo));
148
149 if (net_pkt_remaining_data(pkt) >= sizeof(uint32_t)) {
150 if (net_pkt_read_be32(pkt, &cycles)) {
151 return -EIO;
152 }
153
154 cycles = k_cycle_get_32() - cycles;
155
156 snprintf(time_buf, sizeof(time_buf),
157 #ifdef CONFIG_FPU
158 "time=%.2f ms",
159 (double)((uint32_t)k_cyc_to_ns_floor64(cycles) / 1000000.f)
160 #else
161 "time=%d ms",
162 ((uint32_t)k_cyc_to_ns_floor64(cycles) / 1000000)
163 #endif
164 );
165 }
166
167 PR_SHELL(ping_ctx.sh, "%d bytes from %s to %s: icmp_seq=%d ttl=%d "
168 "%s\n",
169 net_ntohs(ip_hdr->len) - net_pkt_ipv6_ext_len(pkt) -
170 NET_ICMPH_LEN,
171 net_sprint_ipv4_addr(&ip_hdr->src),
172 net_sprint_ipv4_addr(&ip_hdr->dst),
173 net_ntohs(icmp_echo->sequence),
174 ip_hdr->ttl,
175 time_buf);
176
177 if (net_ntohs(icmp_echo->sequence) == ping_ctx.count) {
178 ping_done(&ping_ctx);
179 }
180
181 return 0;
182 }
183 #else
handle_ipv4_echo_reply(struct net_icmp_ctx * ctx,struct net_pkt * pkt,struct net_icmp_ip_hdr * hdr,struct net_icmp_hdr * icmp_hdr,void * user_data)184 static int handle_ipv4_echo_reply(struct net_icmp_ctx *ctx,
185 struct net_pkt *pkt,
186 struct net_icmp_ip_hdr *hdr,
187 struct net_icmp_hdr *icmp_hdr,
188 void *user_data)
189 {
190 ARG_UNUSED(ctx);
191 ARG_UNUSED(pkt);
192 ARG_UNUSED(hdr);
193 ARG_UNUSED(icmp_hdr);
194 ARG_UNUSED(user_data);
195
196 return -ENOTSUP;
197 }
198 #endif /* CONFIG_NET_IPV4 */
199
parse_arg(size_t * i,size_t argc,char * argv[])200 static int parse_arg(size_t *i, size_t argc, char *argv[])
201 {
202 int res;
203 int err;
204 int base;
205 const char *str = argv[*i] + 2;
206
207 if (*str == 0) {
208 if (*i + 1 >= argc) {
209 return -1;
210 }
211
212 *i += 1;
213 str = argv[*i];
214 }
215
216 if (strncmp(str, "0x", 2) == 0) {
217 base = 16;
218 } else {
219 base = 10;
220 }
221
222 err = 0;
223 res = shell_strtol(str, base, &err);
224 if (err != 0) {
225 return -1;
226 }
227
228 return res;
229 }
230
ping_cleanup(struct ping_context * ctx)231 static void ping_cleanup(struct ping_context *ctx)
232 {
233 (void)net_icmp_cleanup_ctx(&ctx->icmp);
234 shell_set_bypass(ctx->sh, NULL, NULL);
235 }
236
ping_done(struct ping_context * ctx)237 static void ping_done(struct ping_context *ctx)
238 {
239 k_work_cancel_delayable(&ctx->work);
240 ping_cleanup(ctx);
241 /* Dummy write to refresh the prompt. */
242 shell_fprintf(ctx->sh, SHELL_NORMAL, "");
243 }
244
ping_work(struct k_work * work)245 static void ping_work(struct k_work *work)
246 {
247 struct k_work_delayable *dwork = k_work_delayable_from_work(work);
248 struct ping_context *ctx =
249 CONTAINER_OF(dwork, struct ping_context, work);
250 const struct shell *sh = ctx->sh;
251 struct net_icmp_ping_params params;
252 int ret;
253
254 ctx->sequence++;
255
256 if (ctx->sequence > ctx->count) {
257 PR_INFO("Ping timeout\n");
258 ping_done(ctx);
259 return;
260 }
261
262 if (ctx->sequence < ctx->count) {
263 k_work_reschedule(&ctx->work, K_MSEC(ctx->interval));
264 } else {
265 k_work_reschedule(&ctx->work, K_SECONDS(2));
266 }
267
268 params.identifier = sys_rand32_get();
269 params.sequence = ctx->sequence;
270 params.tc_tos = ctx->tos;
271 params.priority = ctx->priority;
272 params.data = NULL;
273 params.data_size = ctx->payload_size;
274
275 ret = net_icmp_send_echo_request_no_wait(&ctx->icmp,
276 ctx->iface,
277 &ctx->addr,
278 ¶ms,
279 ctx);
280 if (ret != 0) {
281 PR_WARNING("Failed to send ping, err: %d", ret);
282 ping_done(ctx);
283 return;
284 }
285 }
286
287 #define ASCII_CTRL_C 0x03
288
ping_bypass(const struct shell * sh,uint8_t * data,size_t len,void * user_data)289 static void ping_bypass(const struct shell *sh, uint8_t *data, size_t len, void *user_data)
290 {
291 ARG_UNUSED(sh);
292 ARG_UNUSED(user_data);
293
294 for (size_t i = 0; i < len; i++) {
295 if (data[i] == ASCII_CTRL_C) {
296 k_work_cancel_delayable(&ping_ctx.work);
297 ping_cleanup(&ping_ctx);
298 break;
299 }
300 }
301 }
302
ping_select_iface(int id,struct net_sockaddr * target)303 static struct net_if *ping_select_iface(int id, struct net_sockaddr *target)
304 {
305 struct net_if *iface = net_if_get_by_index(id);
306
307 if (iface != NULL) {
308 goto out;
309 }
310
311 if (IS_ENABLED(CONFIG_NET_IPV4) && target->sa_family == NET_AF_INET) {
312 iface = net_if_ipv4_select_src_iface(&net_sin(target)->sin_addr);
313 if (iface != NULL) {
314 goto out;
315 }
316
317 iface = net_if_get_default();
318 goto out;
319 }
320
321 if (IS_ENABLED(CONFIG_NET_IPV6) && target->sa_family == NET_AF_INET6) {
322 struct net_nbr *nbr;
323 #if defined(CONFIG_NET_ROUTE)
324 struct net_route_entry *route;
325 #endif
326
327 iface = net_if_ipv6_select_src_iface(&net_sin6(target)->sin6_addr);
328 if (iface != NULL) {
329 goto out;
330 }
331
332 nbr = net_ipv6_nbr_lookup(NULL, &net_sin6(target)->sin6_addr);
333 if (nbr) {
334 iface = nbr->iface;
335 goto out;
336 }
337
338 #if defined(CONFIG_NET_ROUTE)
339 route = net_route_lookup(NULL, &net_sin6(target)->sin6_addr);
340 if (route) {
341 iface = route->iface;
342 goto out;
343 }
344 #endif
345
346 iface = net_if_get_default();
347 }
348
349 out:
350 return iface;
351 }
352
353 #endif /* CONFIG_NET_IP */
354
cmd_net_ping(const struct shell * sh,size_t argc,char * argv[])355 static int cmd_net_ping(const struct shell *sh, size_t argc, char *argv[])
356 {
357 #if !defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6)
358 ARG_UNUSED(sh);
359 ARG_UNUSED(argc);
360 ARG_UNUSED(argv);
361
362 return -EOPNOTSUPP;
363 #else
364 char *host = NULL;
365
366 int count = 3;
367 int interval = 1000;
368 int iface_idx = -1;
369 int tos = 0;
370 int payload_size = 4;
371 int priority = -1;
372 int ret;
373
374 for (size_t i = 1; i < argc; ++i) {
375
376 if (*argv[i] != '-') {
377 host = argv[i];
378 continue;
379 }
380
381 switch (argv[i][1]) {
382 case 'c':
383 count = parse_arg(&i, argc, argv);
384 if (count < 0) {
385 PR_WARNING("Parse error: %s\n", argv[i]);
386 return -ENOEXEC;
387 }
388
389
390 break;
391 case 'i':
392 interval = parse_arg(&i, argc, argv);
393 if (interval < 0) {
394 PR_WARNING("Parse error: %s\n", argv[i]);
395 return -ENOEXEC;
396 }
397
398 break;
399
400 case 'I':
401 iface_idx = parse_arg(&i, argc, argv);
402 if (iface_idx < 0 || !net_if_get_by_index(iface_idx)) {
403 PR_WARNING("Parse error: %s\n", argv[i]);
404 return -ENOEXEC;
405 }
406 break;
407
408 case 'p':
409 priority = parse_arg(&i, argc, argv);
410 if (priority < 0 || priority > UINT8_MAX) {
411 PR_WARNING("Parse error: %s\n", argv[i]);
412 return -ENOEXEC;
413 }
414 break;
415
416 case 'Q':
417 tos = parse_arg(&i, argc, argv);
418 if (tos < 0 || tos > UINT8_MAX) {
419 PR_WARNING("Parse error: %s\n", argv[i]);
420 return -ENOEXEC;
421 }
422
423 break;
424
425 case 's':
426 payload_size = parse_arg(&i, argc, argv);
427 if (payload_size < 0 || payload_size > UINT16_MAX) {
428 PR_WARNING("Parse error: %s\n", argv[i]);
429 return -ENOEXEC;
430 }
431
432 break;
433
434 default:
435 PR_WARNING("Unrecognized argument: %s\n", argv[i]);
436 return -ENOEXEC;
437 }
438 }
439
440 if (!host) {
441 PR_WARNING("Target host missing\n");
442 return -ENOEXEC;
443 }
444
445 memset(&ping_ctx, 0, sizeof(ping_ctx));
446
447 k_work_init_delayable(&ping_ctx.work, ping_work);
448
449 ping_ctx.sh = sh;
450 ping_ctx.count = count;
451 ping_ctx.interval = interval;
452 ping_ctx.priority = priority;
453 ping_ctx.tos = tos;
454 ping_ctx.payload_size = payload_size;
455
456 if (IS_ENABLED(CONFIG_NET_IPV6) &&
457 net_addr_pton(NET_AF_INET6, host, &ping_ctx.addr6.sin6_addr) == 0) {
458 ping_ctx.addr6.sin6_family = NET_AF_INET6;
459
460 ret = net_icmp_init_ctx(&ping_ctx.icmp, NET_AF_INET6, NET_ICMPV6_ECHO_REPLY, 0,
461 handle_ipv6_echo_reply);
462 if (ret < 0) {
463 PR_WARNING("Cannot initialize ICMP context for %s\n", "IPv6");
464 return 0;
465 }
466 } else if (IS_ENABLED(CONFIG_NET_IPV4) &&
467 net_addr_pton(NET_AF_INET, host, &ping_ctx.addr4.sin_addr) == 0) {
468 ping_ctx.addr4.sin_family = NET_AF_INET;
469
470 ret = net_icmp_init_ctx(&ping_ctx.icmp, NET_AF_INET, NET_ICMPV4_ECHO_REPLY, 0,
471 handle_ipv4_echo_reply);
472 if (ret < 0) {
473 PR_WARNING("Cannot initialize ICMP context for %s\n", "IPv4");
474 return 0;
475 }
476 } else {
477 PR_WARNING("Invalid IP address\n");
478 return 0;
479 }
480
481 ping_ctx.iface = ping_select_iface(iface_idx, &ping_ctx.addr);
482
483 PR("PING %s\n", host);
484
485 shell_set_bypass(sh, ping_bypass, NULL);
486 k_work_reschedule(&ping_ctx.work, K_NO_WAIT);
487
488 return 0;
489 #endif
490 }
491
492 SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_ping,
493 SHELL_CMD(--help, NULL,
494 "'net ping [-c count] [-i interval ms] [-I <iface index>] "
495 "[-Q tos] [-s payload size] [-p priority] <host>' "
496 "Send ICMPv4 or ICMPv6 Echo-Request to a network host.",
497 cmd_net_ping),
498 SHELL_SUBCMD_SET_END
499 );
500
501 SHELL_SUBCMD_ADD((net), ping, &net_cmd_ping,
502 "Ping a network host.",
503 cmd_net_ping, 2, 12);
504