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 sockaddr_in addr4;
29 struct sockaddr_in6 addr6;
30 struct 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 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 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 (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 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 ntohs(icmp_echo->sequence),
174 ip_hdr->ttl,
175 time_buf);
176
177 if (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);
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)289 static void ping_bypass(const struct shell *sh, uint8_t *data, size_t len)
290 {
291 ARG_UNUSED(sh);
292
293 for (size_t i = 0; i < len; i++) {
294 if (data[i] == ASCII_CTRL_C) {
295 k_work_cancel_delayable(&ping_ctx.work);
296 ping_cleanup(&ping_ctx);
297 break;
298 }
299 }
300 }
301
ping_select_iface(int id,struct sockaddr * target)302 static struct net_if *ping_select_iface(int id, struct sockaddr *target)
303 {
304 struct net_if *iface = net_if_get_by_index(id);
305
306 if (iface != NULL) {
307 goto out;
308 }
309
310 if (IS_ENABLED(CONFIG_NET_IPV4) && target->sa_family == AF_INET) {
311 iface = net_if_ipv4_select_src_iface(&net_sin(target)->sin_addr);
312 if (iface != NULL) {
313 goto out;
314 }
315
316 iface = net_if_get_default();
317 goto out;
318 }
319
320 if (IS_ENABLED(CONFIG_NET_IPV6) && target->sa_family == AF_INET6) {
321 struct net_nbr *nbr;
322 #if defined(CONFIG_NET_ROUTE)
323 struct net_route_entry *route;
324 #endif
325
326 iface = net_if_ipv6_select_src_iface(&net_sin6(target)->sin6_addr);
327 if (iface != NULL) {
328 goto out;
329 }
330
331 nbr = net_ipv6_nbr_lookup(NULL, &net_sin6(target)->sin6_addr);
332 if (nbr) {
333 iface = nbr->iface;
334 goto out;
335 }
336
337 #if defined(CONFIG_NET_ROUTE)
338 route = net_route_lookup(NULL, &net_sin6(target)->sin6_addr);
339 if (route) {
340 iface = route->iface;
341 goto out;
342 }
343 #endif
344
345 iface = net_if_get_default();
346 }
347
348 out:
349 return iface;
350 }
351
352 #endif /* CONFIG_NET_IP */
353
cmd_net_ping(const struct shell * sh,size_t argc,char * argv[])354 static int cmd_net_ping(const struct shell *sh, size_t argc, char *argv[])
355 {
356 #if !defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6)
357 ARG_UNUSED(sh);
358 ARG_UNUSED(argc);
359 ARG_UNUSED(argv);
360
361 return -EOPNOTSUPP;
362 #else
363 char *host = NULL;
364
365 int count = 3;
366 int interval = 1000;
367 int iface_idx = -1;
368 int tos = 0;
369 int payload_size = 4;
370 int priority = -1;
371 int ret;
372
373 for (size_t i = 1; i < argc; ++i) {
374
375 if (*argv[i] != '-') {
376 host = argv[i];
377 continue;
378 }
379
380 switch (argv[i][1]) {
381 case 'c':
382 count = parse_arg(&i, argc, argv);
383 if (count < 0) {
384 PR_WARNING("Parse error: %s\n", argv[i]);
385 return -ENOEXEC;
386 }
387
388
389 break;
390 case 'i':
391 interval = parse_arg(&i, argc, argv);
392 if (interval < 0) {
393 PR_WARNING("Parse error: %s\n", argv[i]);
394 return -ENOEXEC;
395 }
396
397 break;
398
399 case 'I':
400 iface_idx = parse_arg(&i, argc, argv);
401 if (iface_idx < 0 || !net_if_get_by_index(iface_idx)) {
402 PR_WARNING("Parse error: %s\n", argv[i]);
403 return -ENOEXEC;
404 }
405 break;
406
407 case 'p':
408 priority = parse_arg(&i, argc, argv);
409 if (priority < 0 || priority > UINT8_MAX) {
410 PR_WARNING("Parse error: %s\n", argv[i]);
411 return -ENOEXEC;
412 }
413 break;
414
415 case 'Q':
416 tos = parse_arg(&i, argc, argv);
417 if (tos < 0 || tos > UINT8_MAX) {
418 PR_WARNING("Parse error: %s\n", argv[i]);
419 return -ENOEXEC;
420 }
421
422 break;
423
424 case 's':
425 payload_size = parse_arg(&i, argc, argv);
426 if (payload_size < 0 || payload_size > UINT16_MAX) {
427 PR_WARNING("Parse error: %s\n", argv[i]);
428 return -ENOEXEC;
429 }
430
431 break;
432
433 default:
434 PR_WARNING("Unrecognized argument: %s\n", argv[i]);
435 return -ENOEXEC;
436 }
437 }
438
439 if (!host) {
440 PR_WARNING("Target host missing\n");
441 return -ENOEXEC;
442 }
443
444 memset(&ping_ctx, 0, sizeof(ping_ctx));
445
446 k_work_init_delayable(&ping_ctx.work, ping_work);
447
448 ping_ctx.sh = sh;
449 ping_ctx.count = count;
450 ping_ctx.interval = interval;
451 ping_ctx.priority = priority;
452 ping_ctx.tos = tos;
453 ping_ctx.payload_size = payload_size;
454
455 if (IS_ENABLED(CONFIG_NET_IPV6) &&
456 net_addr_pton(AF_INET6, host, &ping_ctx.addr6.sin6_addr) == 0) {
457 ping_ctx.addr6.sin6_family = AF_INET6;
458
459 ret = net_icmp_init_ctx(&ping_ctx.icmp, NET_ICMPV6_ECHO_REPLY, 0,
460 handle_ipv6_echo_reply);
461 if (ret < 0) {
462 PR_WARNING("Cannot initialize ICMP context for %s\n", "IPv6");
463 return 0;
464 }
465 } else if (IS_ENABLED(CONFIG_NET_IPV4) &&
466 net_addr_pton(AF_INET, host, &ping_ctx.addr4.sin_addr) == 0) {
467 ping_ctx.addr4.sin_family = AF_INET;
468
469 ret = net_icmp_init_ctx(&ping_ctx.icmp, NET_ICMPV4_ECHO_REPLY, 0,
470 handle_ipv4_echo_reply);
471 if (ret < 0) {
472 PR_WARNING("Cannot initialize ICMP context for %s\n", "IPv4");
473 return 0;
474 }
475 } else {
476 PR_WARNING("Invalid IP address\n");
477 return 0;
478 }
479
480 ping_ctx.iface = ping_select_iface(iface_idx, &ping_ctx.addr);
481
482 PR("PING %s\n", host);
483
484 shell_set_bypass(sh, ping_bypass);
485 k_work_reschedule(&ping_ctx.work, K_NO_WAIT);
486
487 return 0;
488 #endif
489 }
490
491 SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_ping,
492 SHELL_CMD(--help, NULL,
493 "'net ping [-c count] [-i interval ms] [-I <iface index>] "
494 "[-Q tos] [-s payload size] [-p priority] <host>' "
495 "Send ICMPv4 or ICMPv6 Echo-Request to a network host.",
496 cmd_net_ping),
497 SHELL_SUBCMD_SET_END
498 );
499
500 SHELL_SUBCMD_ADD((net), ping, &net_cmd_ping,
501 "Ping a network host.",
502 cmd_net_ping, 2, 12);
503