From c1ec7db8a9420217365dd7c2ff27b15bb09272e5 Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Mon, 23 Oct 2017 08:50:01 -0600 Subject: [PATCH] This commite eliminates the ping command from NSH and replaces it with a ping 'built-in' at apps/system/ping. The original NSH version of ping violated the portable POSIX interface and, hence, had to be removed. The new system/ping command uses the new IPPROTO_ICMP AF_INET datagram sockets to implement ping. Squashed commit of the following: apps/system/ping: Fix some timing issues. apps/system/ping: A few timing related corrections. apps/nshlib: Remove support for the NSH 'ping' command. The implementation of that command violated the portable POSIX interface and has been replaced with a ping 'built-in' command at apps/system/ping. apps/system/ping: Add larger payload. Verify content of echoed payload. apps/system/ping: Various fixes for a clean compile system/ping: Add new build structure for system ping command. --- nshlib/Kconfig | 9 +- nshlib/README.txt | 88 +++++--- nshlib/nsh.h | 6 - nshlib/nsh_command.c | 6 - nshlib/nsh_netcmds.c | 218 ++---------------- nshlib/nsh_syscmds.c | 2 +- system/ping/.gitignore | 11 + system/ping/Kconfig | 31 +++ system/ping/Make.defs | 40 ++++ system/ping/Makefile | 147 ++++++++++++ system/ping/ping.c | 500 +++++++++++++++++++++++++++++++++++++++++ 11 files changed, 812 insertions(+), 246 deletions(-) create mode 100644 system/ping/.gitignore create mode 100644 system/ping/Kconfig create mode 100644 system/ping/Make.defs create mode 100644 system/ping/Makefile create mode 100644 system/ping/ping.c diff --git a/nshlib/Kconfig b/nshlib/Kconfig index 291c4275f..6af994c16 100644 --- a/nshlib/Kconfig +++ b/nshlib/Kconfig @@ -424,11 +424,6 @@ config NSH_DISABLE_PSSTACKUSAGE ---help--- Disable to save space and not pull in floating point routines -config NSH_DISABLE_PING - bool "Disable ping" - default n - depends on NET_ICMP - config NSH_DISABLE_PUT bool "Disable put" default y if DEFAULT_SMALL @@ -1551,9 +1546,9 @@ endmenu # WAPI Configuration endif # NSH_NETINIT config NSH_MAX_ROUNDTRIP - int "Max Ping Round-Trip (DSEC)" + int "Max Ping6 Round-Trip (DSEC)" default 20 - depends on NSH_LIBRARY && NET && !NSH_DISABLE_PING + depends on NSH_LIBRARY && NET && !NSH_DISABLE_PING6 ---help--- This is the maximum round trip for a response to a ICMP ECHO request. It is in units of deciseconds. The default is 20 (2 seconds). diff --git a/nshlib/README.txt b/nshlib/README.txt index 3cc7de43e..a05e4a55e 100644 --- a/nshlib/README.txt +++ b/nshlib/README.txt @@ -13,8 +13,10 @@ apps/nshlib Environment Variables - NSH Start-Up Script - Simple Commands + - Built-In Applications - NSH Configuration Settings Command Dependencies on Configuration Settings + Built-in Application Configuration Settings NSH-Specific Configuration Settings - Common Problems @@ -887,26 +889,7 @@ o passwd Set the password for the existing user to -o ping [-c ] [-i ] - ping6 [-c ] [-i ] - - Test the network communication with a remote peer. Example, - - nsh> 10.0.0.1 - PING 10.0.0.1 56 bytes of data - 56 bytes from 10.0.0.1: icmp_seq=1 time=0 ms - 56 bytes from 10.0.0.1: icmp_seq=2 time=0 ms - 56 bytes from 10.0.0.1: icmp_seq=3 time=0 ms - 56 bytes from 10.0.0.1: icmp_seq=4 time=0 ms - 56 bytes from 10.0.0.1: icmp_seq=5 time=0 ms - 56 bytes from 10.0.0.1: icmp_seq=6 time=0 ms - 56 bytes from 10.0.0.1: icmp_seq=7 time=0 ms - 56 bytes from 10.0.0.1: icmp_seq=8 time=0 ms - 56 bytes from 10.0.0.1: icmp_seq=9 time=0 ms - 56 bytes from 10.0.0.1: icmp_seq=10 time=0 ms - 10 packets transmitted, 10 received, 0% packet loss, time 10190 ms - nsh> - +o ping6 [-c ] [-i ] ping6 differs from ping in that it uses IPv6 addressing. o poweroff @@ -1238,6 +1221,42 @@ o xd 01f0: 08 3a 0b 3b 0b 49 13 00 00 04 13 01 01 13 03 08 .:.;.I.......... nsh> +Built-In Commands +^^^^^^^^^^^^^^^^^ +In addition to the commands that are part of NSH listed above, there can be +additional, external "built-in" applications that can be added to NSH. +These are separately excecuble programs but will appear much like the the +commands that are a part of NSH. The primary difference is that help +information about the built-in applications is not available from NSH. Most +built-in applicRather, you will need to execute the application with the -h +option to get help about using the built-in applications. + +There are several built-in appliations in the apps/ repository. No attempt +is made here to enumerate all of them. But a few of the more common built- +in applications are listed below. + +o ping [-c ] [-i ] + ping6 [-c ] [-i ] + + Test the network communication with a remote peer. Example, + + nsh> 10.0.0.1 + PING 10.0.0.1 56 bytes of data + 56 bytes from 10.0.0.1: icmp_seq=1 time=0 ms + 56 bytes from 10.0.0.1: icmp_seq=2 time=0 ms + 56 bytes from 10.0.0.1: icmp_seq=3 time=0 ms + 56 bytes from 10.0.0.1: icmp_seq=4 time=0 ms + 56 bytes from 10.0.0.1: icmp_seq=5 time=0 ms + 56 bytes from 10.0.0.1: icmp_seq=6 time=0 ms + 56 bytes from 10.0.0.1: icmp_seq=7 time=0 ms + 56 bytes from 10.0.0.1: icmp_seq=8 time=0 ms + 56 bytes from 10.0.0.1: icmp_seq=9 time=0 ms + 56 bytes from 10.0.0.1: icmp_seq=10 time=0 ms + 10 packets transmitted, 10 received, 0% packet loss, time 10190 ms + nsh> + + ping6 differs from ping in that it uses IPv6 addressing. + NSH Configuration Settings ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1294,7 +1313,6 @@ Command Dependencies on Configuration Settings nfsmount !CONFIG_DISABLE_MOUNTPOINT && CONFIG_NFILE_DESCRIPTORS > 0 && CONFIG_NET && CONFIG_NFS nslookup CONFIG_LIBC_NETDB && CONFIG_NETDB_DNSCLIENT password !CONFIG_DISABLE_MOUNTPOINT && CONFIG_NFILE_DESCRIPTORS > 0 && CONFIG_FS_WRITABLE && CONFIG_NSH_LOGIN_PASSWD - ping CONFIG_NET && CONFIG_NET_ICMP && CONFIG_NET_ICMP_PING && !CONFIG_DISABLE_SIGNALS ping6 CONFIG_NET && CONFIG_NET_ICMPv6 && CONFIG_NET_ICMPv6_PING && !CONFIG_DISABLE_SIGNALS poweroff CONFIG_BOARDCTL_POWEROFF ps CONFIG_FS_PROCFS && !CONFIG_FS_PROCFS_EXCLUDE_PROC @@ -1353,20 +1371,28 @@ also allow it to squeeze into very small memory footprints. CONFIG_NSH_DISABLE_MKFATFS, CONFIG_NSH_DISABLE_MKFIFO, CONFIG_NSH_DISABLE_MKRD, CONFIG_NSH_DISABLE_MH, CONFIG_NSH_DISABLE_MODCMDS, CONFIG_NSH_DISABLE_MOUNT, CONFIG_NSH_DISABLE_MW, CONFIG_NSH_DISABLE_MV, CONFIG_NSH_DISABLE_NFSMOUNT, - CONFIG_NSH_DISABLE_NSLOOKUP, CONFIG_NSH_DISABLE_PASSWD, CONFIG_NSH_DISABLE_PING, - CONFIG_NSH_DISABLE_PING6, CONFIG_NSH_DISABLE_POWEROFF, CONFIG_NSH_DISABLE_PS, - CONFIG_NSH_DISABLE_PUT, CONFIG_NSH_DISABLE_PWD, CONFIG_NSH_DISABLE_READLINK, - CONFIG_NSH_DISABLE_REBOOT, CONFIG_NSH_DISABLE_RM, CONFIG_NSH_DISABLE_RMDIR, - CONFIG_NSH_DISABLE_ROUTE, CONFIG_NSH_DISABLE_SET, CONFIG_NSH_DISABLE_SH, - CONFIG_NSH_DISABLE_SHUTDOWN, CONFIG_NSH_DISABLE_SLEEP, CONFIG_NSH_DISABLE_TEST, - CONFIG_NSH_DIABLE_TIME, CONFIG_NSH_DISABLE_UMOUNT, CONFIG_NSH_DISABLE_UNSET, - CONFIG_NSH_DISABLE_URLDECODE, CONFIG_NSH_DISABLE_URLENCODE, CONFIG_NSH_DISABLE_USERADD, - CONFIG_NSH_DISABLE_USERDEL, CONFIG_NSH_DISABLE_USLEEP, CONFIG_NSH_DISABLE_WGET, - CONFIG_NSH_DISABLE_XD + CONFIG_NSH_DISABLE_NSLOOKUP, CONFIG_NSH_DISABLE_PASSWD, CONFIG_NSH_DISABLE_PING6, + CONFIG_NSH_DISABLE_POWEROFF, CONFIG_NSH_DISABLE_PS, CONFIG_NSH_DISABLE_PUT, + CONFIG_NSH_DISABLE_PWD, CONFIG_NSH_DISABLE_READLINK, CONFIG_NSH_DISABLE_REBOOT, + CONFIG_NSH_DISABLE_RM, CONFIG_NSH_DISABLE_RMDIR, CONFIG_NSH_DISABLE_ROUTE, + CONFIG_NSH_DISABLE_SET, CONFIG_NSH_DISABLE_SH, CONFIG_NSH_DISABLE_SHUTDOWN, + CONFIG_NSH_DISABLE_SLEEP, CONFIG_NSH_DISABLE_TEST, CONFIG_NSH_DIABLE_TIME, + CONFIG_NSH_DISABLE_UMOUNT, CONFIG_NSH_DISABLE_UNSET, CONFIG_NSH_DISABLE_URLDECODE, + CONFIG_NSH_DISABLE_URLENCODE, CONFIG_NSH_DISABLE_USERADD, CONFIG_NSH_DISABLE_USERDEL, + CONFIG_NSH_DISABLE_USLEEP, CONFIG_NSH_DISABLE_WGET, CONFIG_NSH_DISABLE_XD Verbose help output can be suppressed by defining CONFIG_NSH_HELP_TERSE. In that case, the help command is still available but will be slightly smaller. +Built-in Application Configuration Settings +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +All built-in applications require that support for NSH built-in applications has been enabled. This support is enabled with CONFIG_BUILTIN=y and CONFIG_NSH_BUILTIN_APPS=y. + + Application Depends on Configuration + ----------- -------------------------- + ping CONFIG_NET && CONFIG_NET_ICMP && CONFIG_NET_ICMP_SOCKET && + CONFIG_SYSTEM_PING && !CONFIG_DISABLE_POLL && !CONFIG_DISABLE_SIGNALS + NSH-Specific Configuration Settings ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/nshlib/nsh.h b/nshlib/nsh.h index 2062e76da..fb928d6ed 100644 --- a/nshlib/nsh.h +++ b/nshlib/nsh.h @@ -1175,12 +1175,6 @@ int cmd_lsmod(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv); int cmd_nfsmount(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv); # endif # endif -# if defined(CONFIG_NET_ICMP) && defined(CONFIG_NET_ICMP_PING) && \ - !defined(CONFIG_DISABLE_SIGNALS) -# ifndef CONFIG_NSH_DISABLE_PING - int cmd_ping(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv); -# endif -# endif # if defined(CONFIG_NET_ICMPv6) && defined(CONFIG_NET_ICMPv6_PING) && \ !defined(CONFIG_DISABLE_SIGNALS) # ifndef CONFIG_NSH_DISABLE_PING6 diff --git a/nshlib/nsh_command.c b/nshlib/nsh_command.c index bb4b18a02..b52e29fc8 100644 --- a/nshlib/nsh_command.c +++ b/nshlib/nsh_command.c @@ -379,12 +379,6 @@ static const struct cmdmap_s g_cmdmap[] = # endif #endif -#if defined(CONFIG_NET) && defined(CONFIG_NET_ICMP) && defined(CONFIG_NET_ICMP_PING) && !defined(CONFIG_DISABLE_SIGNALS) -# ifndef CONFIG_NSH_DISABLE_PING - { "ping", cmd_ping, 2, 6, "[-c ] [-i ] " }, -# endif -#endif - #if defined(CONFIG_NET) && defined(CONFIG_NET_ICMPv6) && defined(CONFIG_NET_ICMPv6_PING) && !defined(CONFIG_DISABLE_SIGNALS) # ifndef CONFIG_NSH_DISABLE_PING6 { "ping6", cmd_ping6, 2, 6, "[-c ] [-i ] " }, diff --git a/nshlib/nsh_netcmds.c b/nshlib/nsh_netcmds.c index 9fa87c0c1..3fdde4976 100644 --- a/nshlib/nsh_netcmds.c +++ b/nshlib/nsh_netcmds.c @@ -114,17 +114,11 @@ * Pre-processor Definitions ****************************************************************************/ -#undef HAVE_PING #undef HAVE_PING6 #undef HAVE_HWADDR #undef HAVE_EADDR #undef HAVE_RADIOADDR -#if defined(CONFIG_NET_ICMP) && defined(CONFIG_NET_ICMP_PING) && \ - !defined(CONFIG_DISABLE_SIGNALS) && !defined(CONFIG_NSH_DISABLE_PING) -# define HAVE_PING 1 -#endif - #if defined(CONFIG_NET_ICMPv6) && defined(CONFIG_NET_ICMPv6_PING) && \ !defined(CONFIG_DISABLE_SIGNALS) && !defined(CONFIG_NSH_DISABLE_PING6) # define HAVE_PING6 1 @@ -144,7 +138,7 @@ /* Size of the ECHO data */ -#define DEFAULT_PING_DATALEN 56 +#define DEFAULT_PING6_DATALEN 56 /* Get the larger value */ @@ -190,7 +184,7 @@ typedef int (*nsh_netdev_callback_t)(FAR struct nsh_vtbl_s *vtbl, * Private Data ****************************************************************************/ -#if defined(HAVE_PING) || defined(HAVE_PING6) +#ifdef HAVE_PING6 static uint16_t g_pingid = 0; #endif @@ -199,34 +193,34 @@ static uint16_t g_pingid = 0; ****************************************************************************/ /**************************************************************************** - * Name: ping_newid + * Name: ping6_newid ****************************************************************************/ -#if defined(HAVE_PING) || defined(HAVE_PING6) -static uint16_t ping_newid(void) +#ifdef HAVE_PING6 +static uint16_t ping6_newid(void) { irqstate_t save = enter_critical_section(); uint16_t ret = ++g_pingid; leave_critical_section(save); return ret; } -#endif /* HAVE_PING || HAVE_PINg */ +#endif /* HAVE_PIN6 */ /**************************************************************************** - * Name: ping_options + * Name: ping6_options ****************************************************************************/ -#if defined(HAVE_PING) || defined(HAVE_PING6) -static int ping_options(FAR struct nsh_vtbl_s *vtbl, - int argc, FAR char **argv, - FAR int *count, FAR uint32_t *dsec, FAR char **staddr) +#ifdef HAVE_PING6 +static int ping6_options(FAR struct nsh_vtbl_s *vtbl, + int argc, FAR char **argv, + FAR int *count, FAR uint32_t *dsec, FAR char **staddr) { FAR const char *fmt = g_fmtarginvalid; bool badarg = false; int option; int tmp; - /* Get the ping options */ + /* Get the ping6 options */ while ((option = getopt(argc, argv, ":c:i:")) != ERROR) { @@ -303,7 +297,7 @@ errout: nsh_output(vtbl, fmt, argv[0]); return ERROR; } -#endif /* HAVE_PING || HAVE_PING6 */ +#endif /* HAVE_PING6 */ /**************************************************************************** * Name: net_statistics @@ -352,7 +346,7 @@ int tftpc_parseargs(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv, bool badarg = false; int option; - /* Get the ping options */ + /* Get the ping6 options */ memset(args, 0, sizeof(struct tftpc_args_s)); while ((option = getopt(argc, argv, ":bnf:h:")) != ERROR) @@ -492,7 +486,7 @@ errout: * ****************************************************************************/ -#if defined(HAVE_PING) || defined(HAVE_PING6) +#ifdef HAVE_PING6 static int nsh_gethostip(FAR char *hostname, FAR union ip_addr_u *ipaddr, int addrtype) { @@ -508,9 +502,6 @@ static int nsh_gethostip(FAR char *hostname, FAR union ip_addr_u *ipaddr, nerr("ERROR: gethostbyname failed: %d\n", h_errno); return -ENOENT; } - -#if defined(HAVE_PING) && defined(HAVE_PING6) - else if (he->h_addrtype != addrtype) { nerr("ERROR: gethostbyname returned an address of type: %d\n", @@ -526,22 +517,7 @@ static int nsh_gethostip(FAR char *hostname, FAR union ip_addr_u *ipaddr, memcpy(ipaddr->ipv6, he->h_addr, sizeof(net_ipv6addr_t)); } -#elif defined(HAVE_PING) - - else if (he->h_addrtype != AF_INET) - { - nerr("ERROR: gethostbyname returned an address of type: %d\n", - he->h_addrtype); - return -ENOEXEC; - } - else - { - memcpy(&ipaddr->ipv4, he->h_addr, sizeof(in_addr_t)); - } - -#else /* if defined(HAVE_PING6) */ - - else if (he->h_addrtype != AF_INET6) + if (he->h_addrtype != AF_INET6) { nerr("ERROR: gethostbyname returned an address of type: %d\n", he->h_addrtype); @@ -552,37 +528,14 @@ static int nsh_gethostip(FAR char *hostname, FAR union ip_addr_u *ipaddr, memcpy(ipaddr->ipv6, he->h_addr, sizeof(net_ipv6addr_t)); } -#endif - return OK; #else /* CONFIG_LIBC_NETDB */ /* No host name support */ - - int ret; - -#ifdef HAVE_PING - /* Convert strings to numeric IPv4 address */ - -#ifdef HAVE_PING6 - if (addrtype == AF_INET) -#endif - { - ret = inet_pton(AF_INET, hostname, &ipaddr->ipv4); - } -#endif - -#ifdef HAVE_PING6 /* Convert strings to numeric IPv6 address */ -#ifdef HAVE_PING - else -#endif - { - ret = inet_pton(AF_INET6, hostname, ipaddr->ipv6); - } -#endif + int ret = inet_pton(AF_INET6, hostname, ipaddr->ipv6); /* The inet_pton() function returns 1 if the conversion succeeds. It will * return 0 if the input is not a valid IPv4 dotted-decimal string or a @@ -1379,131 +1332,6 @@ errout_invalid: } #endif -/**************************************************************************** - * Name: cmd_ping - ****************************************************************************/ - -#ifdef HAVE_PING -int cmd_ping(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv) -{ - FAR char *staddr; - in_addr_t ipaddr; - systime_t start; - systime_t next; - int32_t elapsed; - uint32_t dsec = 10; - uint32_t maxwait; - uint16_t id; - int count = 10; - int seqno; - int replies = 0; - int ret; - int tmp; - int i; - - /* Get the ping options */ - - ret = ping_options(vtbl, argc, argv, &count, &dsec, &staddr); - if (ret < 0) - { - return ERROR; - } - - /* Get the IP address in binary form */ - - ret = nsh_gethostip(staddr, (FAR union ip_addr_u *)&ipaddr, AF_INET); - if (ret < 0) - { - nsh_output(vtbl, "nsh: %s: unable to resolve hostname '%s'\n", argv[0], staddr); - return ERROR; - } - - /* Get the ID to use */ - - id = ping_newid(); - - /* The maximum wait for a response will be the larger of the inter-ping - * time and the configured maximum round-trip time. - */ - - maxwait = MAX(dsec, CONFIG_NSH_MAX_ROUNDTRIP); - - /* Loop for the specified count */ - - nsh_output(vtbl, "PING %d.%d.%d.%d %d bytes of data\n", - (ipaddr ) & 0xff, (ipaddr >> 8 ) & 0xff, - (ipaddr >> 16 ) & 0xff, (ipaddr >> 24 ) & 0xff, - DEFAULT_PING_DATALEN); - - start = clock_systimer(); - for (i = 1; i <= count; i++) - { - /* Send the ECHO request and wait for the response */ - - next = clock_systimer(); - seqno = icmp_ping(ipaddr, id, i, DEFAULT_PING_DATALEN, maxwait); - - /* Was any response returned? We can tell if a non-negative sequence - * number was returned. - */ - - if (seqno >= 0 && seqno <= i) - { - /* Get the elapsed time from the time that the request was - * sent until the response was received. If we got a response - * to an earlier request, then fudge the elapsed time. - */ - - elapsed = (int32_t)TICK2MSEC(clock_systimer() - next); - if (seqno < i) - { - elapsed += 100 * dsec * (i - seqno); - } - - /* Report the receipt of the reply */ - - nsh_output(vtbl, "%d bytes from %s: icmp_seq=%d time=%ld ms\n", - DEFAULT_PING_DATALEN, staddr, seqno, (long)elapsed); - replies++; - } - else if (seqno < 0) - { - if (seqno == -ETIMEDOUT) - { - nsh_output(vtbl, "icmp_seq=%d Request timeout\n", i); - } - else if (seqno == -ENETUNREACH) - { - nsh_output(vtbl, "icmp_seq=%d Network is unreachable\n", i); - } - } - - /* Wait for the remainder of the interval. If the last seqno> 1)) / count; - - nsh_output(vtbl, "%d packets transmitted, %d received, %d%% packet loss, time %ld ms\n", - count, replies, tmp, elapsed); - return OK; -} -#endif /* HAVE_PING */ - /**************************************************************************** * Name: cmd_ping6 ****************************************************************************/ @@ -1526,9 +1354,9 @@ int cmd_ping6(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv) int tmp; int i; - /* Get the ping options */ + /* Get the ping6 options */ - ret = ping_options(vtbl, argc, argv, &count, &dsec, &staddr); + ret = ping6_options(vtbl, argc, argv, &count, &dsec, &staddr); if (ret < 0) { return ERROR; @@ -1545,7 +1373,7 @@ int cmd_ping6(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv) /* Get the ID to use */ - id = ping_newid(); + id = ping6_newid(); /* The maximum wait for a response will be the larger of the inter-ping * time and the configured maximum round-trip time. @@ -1561,7 +1389,7 @@ int cmd_ping6(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv) ntohs(ipaddr.s6_addr16[2]), ntohs(ipaddr.s6_addr16[3]), ntohs(ipaddr.s6_addr16[4]), ntohs(ipaddr.s6_addr16[5]), ntohs(ipaddr.s6_addr16[6]), ntohs(ipaddr.s6_addr16[7]), - DEFAULT_PING_DATALEN); + DEFAULT_PING6_DATALEN); start = clock_systimer(); for (i = 1; i <= count; i++) @@ -1569,7 +1397,7 @@ int cmd_ping6(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv) /* Send the ECHO request and wait for the response */ next = clock_systimer(); - seqno = icmpv6_ping(ipaddr.s6_addr16, id, i, DEFAULT_PING_DATALEN, maxwait); + seqno = icmpv6_ping(ipaddr.s6_addr16, id, i, DEFAULT_PING6_DATALEN, maxwait); /* Was any response returned? We can tell if a non-negative sequence * number was returned. @@ -1591,7 +1419,7 @@ int cmd_ping6(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv) /* Report the receipt of the reply */ nsh_output(vtbl, "%d bytes from %s: icmp_seq=%d time=%ld ms\n", - DEFAULT_PING_DATALEN, staddr, seqno, (long)elapsed); + DEFAULT_PING6_DATALEN, staddr, seqno, (long)elapsed); replies++; } diff --git a/nshlib/nsh_syscmds.c b/nshlib/nsh_syscmds.c index 0458861ae..322a7f709 100644 --- a/nshlib/nsh_syscmds.c +++ b/nshlib/nsh_syscmds.c @@ -240,7 +240,7 @@ int cmd_uname(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv) int ret; int i; - /* Get the ping options */ + /* Get the uname options */ set = 0; badarg = false; diff --git a/system/ping/.gitignore b/system/ping/.gitignore new file mode 100644 index 000000000..83bd7b811 --- /dev/null +++ b/system/ping/.gitignore @@ -0,0 +1,11 @@ +/Make.dep +/.depend +/.built +/*.asm +/*.rel +/*.lst +/*.sym +/*.adb +/*.lib +/*.src +/*.obj diff --git a/system/ping/Kconfig b/system/ping/Kconfig new file mode 100644 index 000000000..07e339dc6 --- /dev/null +++ b/system/ping/Kconfig @@ -0,0 +1,31 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config SYSTEM_PING + bool "ICMP Ping command" + default n + depends on NET_ICMP_SOCKET + ---help--- + Enable support for the ICMP 'ping' command. + +if SYSTEM_PING +config SYSTEM_PING_PROGNAME + string "Ping program name" + default "nxhello" + depends on BUILD_KERNEL + ---help--- + This is the name of the program that will be use when the NSH ELF + program is installed. + +config SYSTEM_PING_PRIORITY + int "Ping task priority" + default 100 + +config SYSTEM_PING_STACKSIZE + int "Ping stack size" + default 2048 + +endif + diff --git a/system/ping/Make.defs b/system/ping/Make.defs new file mode 100644 index 000000000..bfa131c0c --- /dev/null +++ b/system/ping/Make.defs @@ -0,0 +1,40 @@ +############################################################################ +# apps/system/ping/Make.defs +# Adds selected applications to apps/ build +# +# Copyright (C) 2017 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +ifeq ($(CONFIG_SYSTEM_PING),y) +CONFIGURED_APPS += system/ping +endif + diff --git a/system/ping/Makefile b/system/ping/Makefile new file mode 100644 index 000000000..2db18a1bd --- /dev/null +++ b/system/ping/Makefile @@ -0,0 +1,147 @@ +############################################################################ +# apps/system/ping/Makefile +# +# Copyright (C) 2017 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +-include $(TOPDIR)/.config +-include $(TOPDIR)/Make.defs +include $(APPDIR)/Make.defs + +# ICMP ping command + +CONFIG_SYSTEM_PING_PRIORITY ?= SCHED_PRIORITY_DEFAULT +CONFIG_SYSTEM_PING_STACKSIZE ?= 2048 + +APPNAME = ping +PRIORITY = $(CONFIG_SYSTEM_PING_PRIORITY) +STACKSIZE = $(CONFIG_SYSTEM_PING_STACKSIZE) + +CONFIG_SYSTEM_PING_PROGNAME ?= ping$(EXEEXT) +PROGNAME = $(CONFIG_SYSTEM_PING_PROGNAME) + +# Files + +ASRCS = +CSRCS = +MAINSRC = ping.c + +AOBJS = $(ASRCS:.S=$(OBJEXT)) +COBJS = $(CSRCS:.c=$(OBJEXT)) +MAINOBJ = $(MAINSRC:.c=$(OBJEXT)) + +SRCS = $(ASRCS) $(CSRCS) $(MAINSRC) +OBJS = $(AOBJS) $(COBJS) + +ifneq ($(CONFIG_BUILD_KERNEL),y) + OBJS += $(MAINOBJ) +endif + +ifeq ($(CONFIG_WINDOWS_NATIVE),y) + BIN = ..\..\libapps$(LIBEXT) +else +ifeq ($(WINTOOL),y) + BIN = ..\\..\\libapps$(LIBEXT) +else + BIN = ../../libapps$(LIBEXT) +endif +endif + +ifeq ($(WINTOOL),y) + INSTALL_DIR = "${shell cygpath -w $(BIN_DIR)}" +else + INSTALL_DIR = $(BIN_DIR) +endif + +ROOTDEPPATH = --dep-path . + +# Common build + +VPATH = + +all: .built +.PHONY: context depend clean distclean preconfig +.PRECIOUS: ../../libapps$(LIBEXT) + +$(AOBJS): %$(OBJEXT): %.S + $(call ASSEMBLE, $<, $@) + +$(COBJS) $(MAINOBJ): %$(OBJEXT): %.c + $(call COMPILE, $<, $@) + +.built: $(OBJS) + $(call ARCHIVE, $(BIN), $(OBJS)) + $(Q) touch .built + +ifeq ($(CONFIG_BUILD_KERNEL),y) +$(BIN_DIR)$(DELIM)$(PROGNAME): $(OBJS) $(MAINOBJ) + @echo "LD: $(PROGNAME)" + $(Q) $(LD) $(LDELFFLAGS) $(LDLIBPATH) -o $(INSTALL_DIR)$(DELIM)$(PROGNAME) $(ARCHCRT0OBJ) $(MAINOBJ) $(LDLIBS) + $(Q) $(NM) -u $(INSTALL_DIR)$(DELIM)$(PROGNAME) + +install: $(BIN_DIR)$(DELIM)$(PROGNAME) + +else +install: + +endif + +# Register application + +ifeq ($(CONFIG_NSH_BUILTIN_APPS),y) +$(BUILTIN_REGISTRY)$(DELIM)$(APPNAME)_main.bdat: $(DEPCONFIG) Makefile + $(call REGISTER,$(APPNAME),$(PRIORITY),$(STACKSIZE),$(APPNAME)_main) + +context: $(BUILTIN_REGISTRY)$(DELIM)$(APPNAME)_main.bdat +else +context: +endif + +# Create dependencies + +.depend: Makefile $(SRCS) + $(Q) $(MKDEP) $(ROOTDEPPATH) "$(CC)" -- $(CFLAGS) -- $(SRCS) >Make.dep + $(Q) touch $@ + +depend: .depend + +clean: + $(call DELFILE, .built) + $(call CLEAN) + +distclean: clean + $(call DELFILE, Make.dep) + $(call DELFILE, .depend) + +preconfig: + +-include Make.dep diff --git a/system/ping/ping.c b/system/ping/ping.c new file mode 100644 index 000000000..d010cc45d --- /dev/null +++ b/system/ping/ping.c @@ -0,0 +1,500 @@ +/**************************************************************************** + * apps/system/ping/ping.c + * + * Copyright (C) 2017 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define ICMP_PING_DATALEN 56 +#define ICMP_IOBUFFER_SIZE sizeof(struct icmp_hdr_s) + ICMP_PING_DATALEN + +#define ICMP_NPINGS 10 /* Default number of pings */ +#define ICMP_POLL_DELAY 1000 /* 1 second in milliseconds */ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct ping_info_s +{ + int sockfd; /* Open IPPROTO_ICMP socket */ + FAR struct in_addr dest; /* Target address to ping */ + uint16_t count; /* Number of pings requested */ + uint16_t nrequests; /* Number of ICMP ECHO requests sent */ + uint16_t nreplies; /* Number of matching ICMP ECHO replies received */ + int16_t delay; /* Deciseconds to delay between pings */ + + /* I/O buffer for data transfers */ + + uint8_t iobuffer[ICMP_IOBUFFER_SIZE]; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* NOTE: This will not work in the kernel build where there will be a + * separate instance of g_pingid in every process space. + */ + +static uint16_t g_pingid = 0; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ping_newid + ****************************************************************************/ + +static inline uint16_t ping_newid(void) +{ + /* Revisit: No thread safe */ + + return ++g_pingid; +} + +/**************************************************************************** + * Name: do_ping + ****************************************************************************/ + +void do_ping(FAR struct ping_info_s *info) +{ + struct sockaddr_in destaddr; + struct sockaddr_in fromaddr; + struct icmp_hdr_s outhdr; + FAR struct icmp_hdr_s *inhdr; + struct pollfd recvfd; + FAR uint8_t *ptr; + int32_t elapsed; + systime_t start; + socklen_t addrlen; + ssize_t nsent; + ssize_t nrecvd; + size_t outsize; + bool retry; + int delay; + int ret; + int ch; + int i; + + memset(&destaddr, 0, sizeof destaddr); + destaddr.sin_family = AF_INET; + destaddr.sin_addr.s_addr = info->dest.s_addr; + + memset(&outhdr, 0, sizeof outhdr); + outhdr.type = ICMP_ECHO_REQUEST; + outhdr.icode = ping_newid(); + outhdr.seqno = 0; + + printf("PING %u.%u.%u.%u %d bytes of data\n", + (info->dest.s_addr ) & 0xff, + (info->dest.s_addr >> 8 ) & 0xff, + (info->dest.s_addr >> 16) & 0xff, + (info->dest.s_addr >> 24) & 0xff, + ICMP_PING_DATALEN); + + while (info->nrequests < info->count) + { + /* Copy the ICMP header into the I/O buffer */ + + memcpy(info->iobuffer, &outhdr, sizeof(struct icmp_hdr_s)); + + /* Add some easily verifiable payload data */ + + ptr = &info->iobuffer[sizeof(struct icmp_hdr_s)]; + ch = 0x20; + + for (i = 0; i < ICMP_PING_DATALEN; i++) + { + *ptr++ = ch; + if (++ch > 0x7e) + { + ch = 0x20; + } + } + + start = clock_systimer(); + outsize = sizeof(struct icmp_hdr_s) + ICMP_PING_DATALEN; + nsent = sendto(info->sockfd, info->iobuffer, outsize, 0, + (FAR struct sockaddr*)&destaddr, + sizeof(struct sockaddr_in)); + if (nsent < 0) + { + fprintf(stderr, "ERROR: sendto failed at seqno %u: %d\n", + outhdr.seqno, errno); + return; + } + else if (nsent != outsize) + { + fprintf(stderr, "ERROR: sendto returned %ld, expected %lu\n", + (long)nsent, (unsigned long)outsize); + return; + } + + info->nrequests++; + + delay = info->delay; + do + { + /* Wait for a reply with a timeout */ + + retry = false; + + recvfd.fd = info->sockfd; + recvfd.events = POLLIN; + recvfd.revents = 0; + + ret = poll(&recvfd, 1, delay); + if (ret < 0) + { + fprintf(stderr, "ERROR: poll failed: %d\n", errno); + return; + } + else if (ret == 0) + { + printf("No response from %u.%u.%u.%u: icmp_seq=%u time=%u ms\n", + (info->dest.s_addr ) & 0xff, + (info->dest.s_addr >> 8 ) & 0xff, + (info->dest.s_addr >> 16) & 0xff, + (info->dest.s_addr >> 24) & 0xff, + outhdr.seqno, info->delay); + continue; + } + + /* Get the ICMP response (ignoring the sender) */ + + addrlen = sizeof(struct sockaddr_in); + nrecvd = recvfrom(info->sockfd, info->iobuffer, + ICMP_IOBUFFER_SIZE, 0, + (FAR struct sockaddr *)&fromaddr, &addrlen); + if (nrecvd < 0) + { + fprintf(stderr, "ERROR: recvfrom failed: %d\n", errno); + return; + } + else if (nrecvd < sizeof(struct icmp_hdr_s)) + { + fprintf(stderr, "ERROR: short ICMP packet: %ld\n", (long)nrecvd); + return; + } + + elapsed = (unsigned int)TICK2MSEC(clock_systimer() - start); + inhdr = (FAR struct icmp_hdr_s *)info->iobuffer; + + if (inhdr->type == ICMP_ECHO_REPLY) + { + if (inhdr->icode != outhdr.icode) + { + fprintf(stderr, + "WARNING: Ignoring ICMP reply with ID %u. " + "Expected %u\n", + inhdr->icode, outhdr.icode); + retry = true; + } + else if (inhdr->seqno > outhdr.seqno) + { + fprintf(stderr, + "WARNING: Ignoring ICMP reply to sequence %u. " + "Expected <= &u\n", + inhdr->seqno, outhdr.seqno); + retry = true; + } + else + { + bool verified = true; + int32_t pktdelay = elapsed; + + if (inhdr->seqno < outhdr.seqno) + { + fprintf(stderr, "WARNING: Received after timeout\n"); + pktdelay += info->delay; + retry = true; + } + + printf("%ld bytes from %u.%u.%u.%u: icmp_seq=%u time=%u ms\n", + nrecvd - sizeof(struct icmp_hdr_s), + (info->dest.s_addr ) & 0xff, + (info->dest.s_addr >> 8 ) & 0xff, + (info->dest.s_addr >> 16) & 0xff, + (info->dest.s_addr >> 24) & 0xff, + inhdr->seqno, pktdelay); + + /* Verify the payload data */ + + if (nrecvd != outsize) + { + fprintf(stderr, + "WARNING: Ignoring ICMP reply with different payload " + "size: %ld vs %lu\n", + (long)nrecvd, (unsigned long)outsize); + verified = false; + } + else + { + ptr = &info->iobuffer[sizeof(struct icmp_hdr_s)]; + ch = 0x20; + + for (i = 0; i < ICMP_PING_DATALEN; i++, ptr++) + { + if (*ptr != ch) + { + fprintf(stderr, "WARNING: Echoed data corrupted\n"); + verified = false; + break; + } + + if (++ch > 0x7e) + { + ch = 0x20; + } + } + } + + /* Only count the number of good replies */ + + if (verified) + { + info->nreplies++; + } + } + } + else + { + fprintf(stderr, "WARNING: ICMP packet with unknown type: %u\n", + inhdr->type); + } + + delay -= elapsed; + } + while (retry && delay > 0); + + /* Wait if necessary to preserved the requested ping rate */ + + elapsed = (unsigned int)TICK2MSEC(clock_systimer() - start); + if (elapsed < info->delay) + { + struct timespec rqt; + unsigned int remaining; + unsigned int sec; + unsigned int frac; /* In deciseconds */ + + remaining = info->delay - elapsed; + sec = remaining / MSEC_PER_SEC; + frac = remaining - MSEC_PER_SEC * sec; + + rqt.tv_sec = sec; + rqt.tv_nsec = frac * NSEC_PER_MSEC; + + (void)nanosleep(&rqt, NULL); + } + + outhdr.seqno++; + } +} + +/**************************************************************************** + * Name: show_usage + ****************************************************************************/ + +static void show_usage(FAR const char *progname, int exitcode) noreturn_function; +static void show_usage(FAR const char *progname, int exitcode) +{ + printf("\nUsage: %s [-c ] [-i ] \n", progname); + printf(" %s -h\n", progname); + printf("\nWhere:\n"); + printf(" is the IP address request the ICMP ECHO replay.\n"); + printf(" -c determines the number of pings. Default %u.\n", + ICMP_NPINGS); + printf(" -i is the default delay between pings (milliseconds).\n"); + printf(" Default %d.\n", ICMP_POLL_DELAY); + printf(" -h shows this text an exits.\n"); + exit(exitcode); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#ifdef CONFIG_BUILD_KERNEL +int main(int argc, FAR char *argv[]) +#else +int ping_main(int argc, char **argv) +#endif +{ + FAR struct ping_info_s *info; + FAR char *endptr; + systime_t start; + int32_t elapsed; + int exitcode; + int option; + + /* Allocate memory to hold ping information */ + + info = (FAR struct ping_info_s *)zalloc(sizeof(struct ping_info_s)); + if (info == NULL) + { + fprintf(stderr, "ERROR: Failed to allocate memory\n", argv[1]); + return EXIT_FAILURE; + } + + info->count = ICMP_NPINGS; + info->delay = ICMP_POLL_DELAY; + + /* Parse command line options */ + + exitcode = EXIT_FAILURE; + + while ((option = getopt(argc, argv, ":c:i:h")) != ERROR) + { + switch (option) + { + case 'c': + { + long count = strtol(optarg, &endptr, 10); + if (count < 1 || count > UINT16_MAX) + { + fprintf(stderr, "ERROR: out of range: %ld\n", count); + goto errout_with_usage; + } + + info->count = (uint16_t)count; + } + break; + + case 'i': + { + long delay = strtol(optarg, &endptr, 10); + if (delay < 1 || delay > INT16_MAX) + { + fprintf(stderr, "ERROR: out of range: %ld\n", delay); + goto errout_with_usage; + } + + info->delay = (int16_t)delay; + } + break; + + case 'h': + exitcode = EXIT_SUCCESS; + goto errout_with_usage; + + case ':': + fprintf(stderr, "ERROR: Missing required argument\n"); + goto errout_with_usage; + + case '?': + default: + fprintf(stderr, "ERROR: Unrecognized option\n"); + goto errout_with_usage; + } + } + + /* There should be one final parameters remaining on the command line */ + + if (optind >= argc) + { + printf("ERROR: Missing required argument\n"); + free(info); + show_usage(argv[0], EXIT_FAILURE); + } + + if (inet_pton(AF_INET, argv[optind], &info->dest) == 0) + { + fprintf(stderr, "ERROR: inet_aton(%s) failed\n", argv[optind]); + goto errout_with_info; + } + + info->sockfd = socket(AF_INET,SOCK_DGRAM,IPPROTO_ICMP); + if (info->sockfd < 0) + { + fprintf(stderr, "ERROR: socket() failed: %d\n", errno); + goto errout_with_info; + } + + start = clock_systimer(); + do_ping(info); + + /* Get the total elapsed time */ + + elapsed = (int32_t)TICK2MSEC(clock_systimer() - start); + + if (info->nrequests > 0) + { + unsigned int tmp; + + /* Calculate the percentage of lost packets */ + + tmp = (100 * (info->nrequests - info->nreplies) + (info->nrequests >> 1)) / + info->nrequests; + + printf("%u packets transmitted, %u received, %u%% packet loss, time %ld ms\n", + info->nrequests, info->nreplies, tmp, (long)elapsed); + } + + close(info->sockfd); + free(info); + return EXIT_SUCCESS; + +errout_with_usage: + free(info); + show_usage(argv[0], exitcode); + return exitcode; /* Not reachable */ + +errout_with_info: + free(info); + return EXIT_FAILURE; +}