255df7c01f
Signed-off-by: meijian <meijian@xiaomi.com>
436 lines
12 KiB
C
436 lines
12 KiB
C
/****************************************************************************
|
|
* apps/netutils/ping/icmp_ping.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <poll.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <stdbool.h>
|
|
|
|
#ifdef CONFIG_LIBC_NETDB
|
|
# include <netdb.h>
|
|
#endif
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <nuttx/clock.h>
|
|
#include <nuttx/net/icmp.h>
|
|
|
|
#include "netutils/icmp_ping.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define ICMP_IOBUFFER_SIZE(x) (sizeof(struct icmp_hdr_s) + (x))
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* Data needed for ping, reduce stack usage. */
|
|
|
|
struct ping_priv_s
|
|
{
|
|
struct sockaddr_in destaddr;
|
|
struct sockaddr_in fromaddr;
|
|
struct icmp_hdr_s outhdr;
|
|
struct pollfd recvfd;
|
|
socklen_t addrlen;
|
|
clock_t kickoff;
|
|
clock_t start;
|
|
ssize_t nsent;
|
|
ssize_t nrecvd;
|
|
long elapsed;
|
|
bool retry;
|
|
int sockfd;
|
|
};
|
|
|
|
/****************************************************************************
|
|
* 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;
|
|
static volatile bool g_exiting;
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: sigexit
|
|
****************************************************************************/
|
|
|
|
static void sigexit(int signo)
|
|
{
|
|
g_exiting = true;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ping_newid
|
|
****************************************************************************/
|
|
|
|
static inline uint16_t ping_newid(void)
|
|
{
|
|
/* Revisit: No thread safe */
|
|
|
|
return ++g_pingid;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ping_gethostip
|
|
*
|
|
* Description:
|
|
* Call getaddrinfo() to get the IP address associated with a hostname.
|
|
*
|
|
* Input Parameters
|
|
* hostname - The host name to use in the nslookup.
|
|
* destr - The location to return the IPv4 address.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) on success; ERROR on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int ping_gethostip(FAR const char *hostname, FAR struct in_addr *dest)
|
|
{
|
|
#ifdef CONFIG_LIBC_NETDB
|
|
/* Netdb DNS client support is enabled */
|
|
|
|
FAR struct addrinfo *info;
|
|
FAR struct sockaddr_in *addr;
|
|
static const struct addrinfo s_hint =
|
|
{
|
|
.ai_family = AF_INET
|
|
};
|
|
|
|
if (getaddrinfo(hostname, NULL, &s_hint, &info) != OK)
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
addr = (FAR struct sockaddr_in *)info->ai_addr;
|
|
memcpy(dest, &addr->sin_addr, sizeof(struct in_addr));
|
|
|
|
freeaddrinfo(info);
|
|
return OK;
|
|
|
|
#else /* CONFIG_LIBC_NETDB */
|
|
/* No host name support */
|
|
|
|
/* Convert strings to numeric IPv4 address */
|
|
|
|
int ret = inet_pton(AF_INET, hostname, dest);
|
|
|
|
/* 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 -1
|
|
* with errno set to EAFNOSUPPORT if the address family argument is
|
|
* unsupported.
|
|
*/
|
|
|
|
return (ret > 0) ? OK : ERROR;
|
|
|
|
#endif /* CONFIG_LIBC_NETDB */
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: icmp_callback
|
|
****************************************************************************/
|
|
|
|
static void icmp_callback(FAR struct ping_result_s *result,
|
|
int code, long extra)
|
|
{
|
|
result->code = code;
|
|
result->extra = extra;
|
|
result->info->callback(result);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: icmp_ping
|
|
****************************************************************************/
|
|
|
|
void icmp_ping(FAR const struct ping_info_s *info)
|
|
{
|
|
struct ping_result_s result;
|
|
FAR struct ping_priv_s *priv;
|
|
FAR struct icmp_hdr_s *inhdr;
|
|
FAR uint8_t *iobuffer;
|
|
FAR uint8_t *ptr;
|
|
int ret;
|
|
int ch;
|
|
int i;
|
|
|
|
g_exiting = false;
|
|
signal(SIGINT, sigexit);
|
|
|
|
/* Initialize result structure */
|
|
|
|
memset(&result, 0, sizeof(result));
|
|
result.info = info;
|
|
result.id = ping_newid();
|
|
result.outsize = ICMP_IOBUFFER_SIZE(info->datalen);
|
|
if (ping_gethostip(info->hostname, &result.dest) < 0)
|
|
{
|
|
icmp_callback(&result, ICMP_E_HOSTIP, 0);
|
|
return;
|
|
}
|
|
|
|
/* Allocate memory to hold private data and ping buffer */
|
|
|
|
priv = malloc(sizeof(*priv) + result.outsize);
|
|
if (priv == NULL)
|
|
{
|
|
icmp_callback(&result, ICMP_E_MEMORY, 0);
|
|
return;
|
|
}
|
|
|
|
iobuffer = (FAR uint8_t *)(priv + 1);
|
|
|
|
priv->sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
|
|
if (priv->sockfd < 0)
|
|
{
|
|
icmp_callback(&result, ICMP_E_SOCKET, errno);
|
|
free(priv);
|
|
return;
|
|
}
|
|
|
|
priv->kickoff = clock();
|
|
|
|
memset(&priv->destaddr, 0, sizeof(struct sockaddr_in));
|
|
priv->destaddr.sin_family = AF_INET;
|
|
priv->destaddr.sin_port = 0;
|
|
priv->destaddr.sin_addr.s_addr = result.dest.s_addr;
|
|
|
|
memset(&priv->outhdr, 0, sizeof(struct icmp_hdr_s));
|
|
priv->outhdr.type = ICMP_ECHO_REQUEST;
|
|
priv->outhdr.id = htons(result.id);
|
|
priv->outhdr.seqno = htons(result.seqno);
|
|
|
|
icmp_callback(&result, ICMP_I_BEGIN, 0);
|
|
|
|
while (result.nrequests < info->count)
|
|
{
|
|
if (g_exiting)
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* Copy the ICMP header into the I/O buffer */
|
|
|
|
memcpy(iobuffer, &priv->outhdr, sizeof(struct icmp_hdr_s));
|
|
|
|
/* Add some easily verifiable payload data */
|
|
|
|
ptr = &iobuffer[sizeof(struct icmp_hdr_s)];
|
|
ch = 0x20;
|
|
|
|
for (i = 0; i < info->datalen; i++)
|
|
{
|
|
*ptr++ = ch;
|
|
if (++ch > 0x7e)
|
|
{
|
|
ch = 0x20;
|
|
}
|
|
}
|
|
|
|
priv->start = clock();
|
|
result.nrequests++;
|
|
priv->nsent = sendto(priv->sockfd, iobuffer, result.outsize, 0,
|
|
(FAR struct sockaddr *)&priv->destaddr,
|
|
sizeof(struct sockaddr_in));
|
|
if (priv->nsent < 0)
|
|
{
|
|
icmp_callback(&result, ICMP_E_SENDTO, errno);
|
|
goto wait;
|
|
}
|
|
else if (priv->nsent != result.outsize)
|
|
{
|
|
icmp_callback(&result, ICMP_E_SENDSMALL, priv->nsent);
|
|
goto wait;
|
|
}
|
|
|
|
priv->elapsed = 0;
|
|
do
|
|
{
|
|
priv->retry = false;
|
|
priv->recvfd.fd = priv->sockfd;
|
|
priv->recvfd.events = POLLIN;
|
|
priv->recvfd.revents = 0;
|
|
|
|
ret = poll(&priv->recvfd, 1,
|
|
info->timeout - priv->elapsed / USEC_PER_MSEC);
|
|
if (ret < 0)
|
|
{
|
|
icmp_callback(&result, ICMP_E_POLL, errno);
|
|
goto wait;
|
|
}
|
|
else if (ret == 0)
|
|
{
|
|
icmp_callback(&result, ICMP_W_TIMEOUT, info->timeout);
|
|
goto wait;
|
|
}
|
|
|
|
if (priv->recvfd.revents & (POLLHUP | POLLERR))
|
|
{
|
|
icmp_callback(&result, ICMP_E_POLL, ENETDOWN);
|
|
goto wait;
|
|
}
|
|
|
|
/* Get the ICMP response (ignoring the sender) */
|
|
|
|
priv->addrlen = sizeof(struct sockaddr_in);
|
|
priv->nrecvd = recvfrom(priv->sockfd, iobuffer,
|
|
result.outsize, 0,
|
|
(FAR struct sockaddr *)&priv->fromaddr,
|
|
&priv->addrlen);
|
|
if (priv->nrecvd < 0)
|
|
{
|
|
icmp_callback(&result, ICMP_E_RECVFROM, errno);
|
|
goto wait;
|
|
}
|
|
else if (priv->nrecvd < sizeof(struct icmp_hdr_s))
|
|
{
|
|
icmp_callback(&result, ICMP_E_RECVSMALL, priv->nrecvd);
|
|
goto wait;
|
|
}
|
|
|
|
priv->elapsed = TICK2USEC(clock() - priv->start);
|
|
inhdr = (FAR struct icmp_hdr_s *)iobuffer;
|
|
|
|
if (inhdr->type == ICMP_ECHO_REPLY)
|
|
{
|
|
#ifndef CONFIG_SIM_NETUSRSOCK
|
|
if (ntohs(inhdr->id) != result.id)
|
|
{
|
|
icmp_callback(&result, ICMP_W_IDDIFF, ntohs(inhdr->id));
|
|
priv->retry = true;
|
|
}
|
|
else
|
|
#endif
|
|
if (ntohs(inhdr->seqno) > result.seqno)
|
|
{
|
|
icmp_callback(&result, ICMP_W_SEQNOBIG,
|
|
ntohs(inhdr->seqno));
|
|
priv->retry = true;
|
|
}
|
|
else if (ntohs(inhdr->seqno) < result.seqno)
|
|
{
|
|
icmp_callback(&result, ICMP_W_SEQNOSMALL,
|
|
ntohs(inhdr->seqno));
|
|
priv->retry = true;
|
|
}
|
|
else
|
|
{
|
|
bool verified = true;
|
|
|
|
icmp_callback(&result, ICMP_I_ROUNDTRIP, priv->elapsed);
|
|
|
|
/* Verify the payload data */
|
|
|
|
if (priv->nrecvd != result.outsize)
|
|
{
|
|
icmp_callback(&result, ICMP_W_RECVBIG, priv->nrecvd);
|
|
verified = false;
|
|
}
|
|
else
|
|
{
|
|
ptr = &iobuffer[sizeof(struct icmp_hdr_s)];
|
|
ch = 0x20;
|
|
|
|
for (i = 0; i < info->datalen; i++, ptr++)
|
|
{
|
|
if (*ptr != ch)
|
|
{
|
|
icmp_callback(&result, ICMP_W_DATADIFF, 0);
|
|
verified = false;
|
|
break;
|
|
}
|
|
|
|
if (++ch > 0x7e)
|
|
{
|
|
ch = 0x20;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Only count the number of good replies */
|
|
|
|
if (verified)
|
|
{
|
|
result.nreplies++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
icmp_callback(&result, ICMP_W_TYPE, inhdr->type);
|
|
}
|
|
}
|
|
while (priv->retry && info->delay > priv->elapsed / USEC_PER_MSEC &&
|
|
info->timeout > priv->elapsed / USEC_PER_MSEC);
|
|
|
|
/* Wait if necessary to preserved the requested ping rate */
|
|
|
|
wait:
|
|
priv->elapsed = TICK2MSEC(clock() - priv->start);
|
|
if (priv->elapsed < info->delay)
|
|
{
|
|
struct timespec rqt;
|
|
unsigned int remaining;
|
|
unsigned int sec;
|
|
unsigned int frac; /* In deciseconds */
|
|
|
|
remaining = info->delay - priv->elapsed;
|
|
sec = remaining / MSEC_PER_SEC;
|
|
frac = remaining - MSEC_PER_SEC * sec;
|
|
|
|
rqt.tv_sec = sec;
|
|
rqt.tv_nsec = frac * NSEC_PER_MSEC;
|
|
|
|
nanosleep(&rqt, NULL);
|
|
}
|
|
|
|
priv->outhdr.seqno = htons(++result.seqno);
|
|
}
|
|
|
|
icmp_callback(&result, ICMP_I_FINISH, TICK2USEC(clock() - priv->kickoff));
|
|
close(priv->sockfd);
|
|
free(priv);
|
|
}
|