| /**************************************************************************** |
| * 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> |
| |
| #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 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: 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, int 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; |
| int32_t 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; |
| |
| /* 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) |
| { |
| /* 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); |
| 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 = (unsigned int)TICK2MSEC(clock() - start); |
| inhdr = (FAR struct icmp_hdr_s *)iobuffer; |
| |
| if (inhdr->type == ICMP_ECHO_REPLY) |
| { |
| if (ntohs(inhdr->id) != result.id) |
| { |
| icmp_callback(&result, ICMP_W_IDDIFF, ntohs(inhdr->id)); |
| retry = true; |
| } |
| else if (ntohs(inhdr->seqno) > result.seqno) |
| { |
| icmp_callback(&result, ICMP_W_SEQNOBIG, |
| ntohs(inhdr->seqno)); |
| retry = true; |
| } |
| else |
| { |
| bool verified = true; |
| int32_t pktdelay = elapsed; |
| |
| if (ntohs(inhdr->seqno) < result.seqno) |
| { |
| icmp_callback(&result, ICMP_W_SEQNOSMALL, |
| ntohs(inhdr->seqno)); |
| pktdelay += info->delay; |
| retry = true; |
| } |
| |
| 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 && info->timeout > elapsed); |
| |
| /* Wait if necessary to preserved the requested ping rate */ |
| |
| elapsed = (unsigned int)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, TICK2MSEC(clock() - kickoff)); |
| close(sockfd); |
| free(iobuffer); |
| } |