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 						 &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)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