others
/* $Id$ */
/* Copyright (c) 2015-2016 Pierre Pronchery <khorben@defora.org> */
/* 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 <http://www.gnu.org/licenses/>. */
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#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;
}