/* $Id$ */ /* Copyright (c) 2015-2016 Pierre Pronchery */ /* This file is part of DeforaOS Unix others */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef PROGNAME # define PROGNAME "ping" #endif /* ping */ /* private */ /* types */ typedef struct _Prefs { int family; unsigned int count; } Prefs; struct ping_msg { struct { uint8_t icmp_type; uint8_t icmp_code; uint16_t icmp_cksum; uint16_t icmp_id; uint16_t icmp_seq; } icmp; struct timespec ts; }; /* constants */ #ifndef ICMP_ECHO # define ICMP_ECHO 8 #endif /* prototypes */ static int _ping(Prefs * prefs, char const * hostname); static int _ping_error(char const * message, int ret); static int _ping_gaierror(char const * message, int error); static int _ping_usage(void); /* functions */ /* ping */ static int _ping_receive(int family, int fd, struct timeval * now); static int _ping(Prefs * prefs, char const * hostname) { struct addrinfo hints; struct addrinfo * ai; int res; struct addrinfo * to; struct ping_msg msg; int fd; unsigned int i; struct timeval tv; unsigned int cnt_sent = 0; unsigned int cnt_received = 0; unsigned int cnt_errors = 0; char buf[128]; struct sockaddr_in * sa; struct sockaddr_in6 * sa6; /* lookup hostname */ memset(&hints, 0, sizeof(hints)); hints.ai_family = prefs->family; hints.ai_socktype = SOCK_STREAM; if((res = getaddrinfo(hostname, NULL, &hints, &ai)) != 0) return _ping_gaierror(hostname, res); for(to = ai; to != NULL; to = to->ai_next) if(to->ai_family == AF_INET || to->ai_family == AF_INET6) break; if(to == NULL) { fprintf(stderr, "%s: %s: Host not found\n", PROGNAME, hostname); freeaddrinfo(ai); return -1; } /* create the socket */ if((fd = socket(to->ai_family, SOCK_RAW, IPPROTO_ICMP)) < 0) { freeaddrinfo(ai); return _ping_error("socket", 1); } /* initialize the packet */ memset(&msg, 0, sizeof(msg)); msg.icmp.icmp_type = ICMP_ECHO; printf("PING %s", hostname); switch(to->ai_family) { case AF_INET: sa = (struct sockaddr_in *)to->ai_addr; if(inet_ntop(to->ai_family, &sa->sin_addr, buf, sizeof(buf)) != NULL) printf(" (%s)", buf); break; case AF_INET6: sa6 = (struct sockaddr_in6 *)to->ai_addr; if(inet_ntop(to->ai_family, &sa6->sin6_addr, buf, sizeof(buf)) != NULL) printf(" (%s)", buf); break; } printf(": %lu data bytes\n", sizeof(msg)); for(i = 0; prefs->count == 0 || i < prefs->count; i++) { if(gettimeofday(&tv, NULL) != 0) { _ping_error("gettimeofday", 1); continue; } msg.icmp.icmp_seq = htons(i); msg.ts.tv_sec = htonl(tv.tv_sec); msg.ts.tv_nsec = htonl(tv.tv_usec); /* FIXME set the checksum */ if(sendto(fd, &msg, sizeof(msg), 0, to->ai_addr, to->ai_addrlen) != sizeof(msg)) cnt_errors += _ping_error(hostname, 1); else cnt_sent++; if(prefs->count == 0 || i < prefs->count - 1) if(_ping_receive(to->ai_family, fd, &tv) == 1) cnt_received++; } printf("%u packets transmitted, %u received, %u errors\n", cnt_sent, cnt_received, cnt_errors); freeaddrinfo(ai); if(close(fd) != 0) return _ping_error("close", 1); return 0; } static int _ping_receive(int family, int fd, struct timeval * now) { fd_set rfds; struct timeval tv; int res; struct ping_msg msg; struct sockaddr sa; struct sockaddr_in * sin = (struct sockaddr_in *)&sa; socklen_t salen; ssize_t size; FD_ZERO(&rfds); FD_SET(fd, &rfds); tv.tv_sec = 1; tv.tv_usec = 0; if((res = select(fd + 1, &rfds, NULL, NULL, &tv)) == -1) return -_ping_error("select", 1); else if(res == 0) return 0; if((size = recvfrom(fd, &msg, sizeof(msg), 0, &sa, &salen)) == -1) return -_ping_error("recvfrom", 1); /* FIXME really implement */ if(gettimeofday(&tv, NULL) != 0) return -_ping_error("gettimeofday", 1); printf("%lu bytes from %s", size, (family == AF_INET) ? inet_ntoa(sin->sin_addr) : "unknown"); if(size == sizeof(msg)) printf(", seq=%u, time=%ldms\n", ntohs(msg.icmp.icmp_seq), ((tv.tv_sec - now->tv_sec) * 1000) + ((tv.tv_usec >= now->tv_usec) ? (tv.tv_usec - now->tv_usec) : (now->tv_usec - tv.tv_usec)) / 1000); else printf(" (unknown answer)\n"); /* wait for the remaining time */ if(tv.tv_sec > now->tv_sec + 1) return 1; if(tv.tv_sec == now->tv_sec + 1 && tv.tv_usec >= now->tv_usec) return 1; tv.tv_sec = 0; tv.tv_usec = (tv.tv_usec >= now->tv_usec) ? (tv.tv_usec - now->tv_usec) : (now->tv_usec - tv.tv_usec); if(select(0, NULL, NULL, NULL, &tv) == -1) return -_ping_error("select", 1); return 1; } /* ping_error */ static int _ping_error(char const * message, int ret) { fputs(PROGNAME ": ", stderr); perror(message); return ret; } /* ping_gaierror */ static int _ping_gaierror(char const * message, int error) { fprintf(stderr, "%s%s%s%s\n", PROGNAME ": ", message, ": ", gai_strerror(error)); return -1; } /* ping_usage */ static int _ping_usage(void) { fputs("Usage: " PROGNAME " [-46][-c count] hostname\n", stderr); return 1; } /* public */ /* functions */ /* main */ int main(int argc, char * argv[]) { int o; Prefs prefs; char * p; memset(&prefs, 0, sizeof(prefs)); prefs.family = AF_UNSPEC; prefs.count = 4; while((o = getopt(argc, argv, "46c:")) != -1) switch(o) { case '4': prefs.family = AF_INET; break; case '6': prefs.family = AF_INET6; break; case 'c': prefs.count = strtoul(optarg, &p, 0); if(optarg[0] == '\0' || *p != '\0') return _ping_usage(); break; default: return _ping_usage(); } if(optind != argc - 1) return _ping_usage(); return (_ping(&prefs, argv[optind]) == 0) ? 0 : 2; }