/**************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #ifdef CONFIG_LIBC_NETDB # include #endif #include #include #include #include "netutils/icmp_ping.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define ICMP_IOBUFFER_SIZE(x) (sizeof(struct icmp_hdr_s) + (x)) /**************************************************************************** * 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 hint; FAR struct addrinfo *info; FAR struct sockaddr_in *addr; memset(&hint, 0, sizeof(hint)); hint.ai_family = AF_INET; if (getaddrinfo(hostname, NULL, &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; 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 *iobuffer; FAR uint8_t *ptr; long elapsed; clock_t kickoff; clock_t start; socklen_t addrlen; ssize_t nsent; ssize_t nrecvd; bool retry; int sockfd; 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 ping buffer */ iobuffer = (FAR uint8_t *)malloc(result.outsize); if (iobuffer == NULL) { icmp_callback(&result, ICMP_E_MEMORY, 0); return; } sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); if (sockfd < 0) { icmp_callback(&result, ICMP_E_SOCKET, errno); free(iobuffer); return; } kickoff = clock(); memset(&destaddr, 0, sizeof(struct sockaddr_in)); destaddr.sin_family = AF_INET; destaddr.sin_port = 0; destaddr.sin_addr.s_addr = result.dest.s_addr; memset(&outhdr, 0, sizeof(struct icmp_hdr_s)); outhdr.type = ICMP_ECHO_REQUEST; outhdr.id = htons(result.id); 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, &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; } } start = clock(); nsent = sendto(sockfd, iobuffer, result.outsize, 0, (FAR struct sockaddr *)&destaddr, sizeof(struct sockaddr_in)); if (nsent < 0) { icmp_callback(&result, ICMP_E_SENDTO, errno); goto done; } else if (nsent != result.outsize) { icmp_callback(&result, ICMP_E_SENDSMALL, nsent); goto done; } result.nrequests++; elapsed = 0; do { retry = false; recvfd.fd = sockfd; recvfd.events = POLLIN; recvfd.revents = 0; ret = poll(&recvfd, 1, info->timeout - elapsed / USEC_PER_MSEC); if (ret < 0) { icmp_callback(&result, ICMP_E_POLL, errno); goto done; } else if (ret == 0) { icmp_callback(&result, ICMP_W_TIMEOUT, info->timeout); continue; } /* Get the ICMP response (ignoring the sender) */ addrlen = sizeof(struct sockaddr_in); nrecvd = recvfrom(sockfd, iobuffer, result.outsize, 0, (FAR struct sockaddr *)&fromaddr, &addrlen); if (nrecvd < 0) { icmp_callback(&result, ICMP_E_RECVFROM, errno); goto done; } else if (nrecvd < sizeof(struct icmp_hdr_s)) { icmp_callback(&result, ICMP_E_RECVSMALL, nrecvd); goto done; } elapsed = TICK2USEC(clock() - 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)); retry = true; } else #endif if (ntohs(inhdr->seqno) > result.seqno) { icmp_callback(&result, ICMP_W_SEQNOBIG, ntohs(inhdr->seqno)); retry = true; } else if (ntohs(inhdr->seqno) < result.seqno) { icmp_callback(&result, ICMP_W_SEQNOSMALL, ntohs(inhdr->seqno)); retry = true; } else { bool verified = true; long pktdelay = elapsed; icmp_callback(&result, ICMP_I_ROUNDTRIP, pktdelay); /* Verify the payload data */ if (nrecvd != result.outsize) { icmp_callback(&result, ICMP_W_RECVBIG, 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 (retry && info->delay > elapsed / USEC_PER_MSEC && info->timeout > elapsed / USEC_PER_MSEC); /* Wait if necessary to preserved the requested ping rate */ elapsed = TICK2MSEC(clock() - 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; nanosleep(&rqt, NULL); } outhdr.seqno = htons(++result.seqno); } done: icmp_callback(&result, ICMP_I_FINISH, TICK2USEC(clock() - kickoff)); close(sockfd); free(iobuffer); }