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 						 &params,
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