1
0
mirror of https://github.com/dennypage/dpinger.git synced 2024-05-19 06:50:01 +00:00

16 Commits
v1.2 ... v1.6

Author SHA1 Message Date
Denny Page
9c5bac8658 Add casts to eliminate some warnings from clang -Weverything 2016-02-01 13:21:46 -08:00
Denny Page
3a19391cee Update Copyright 2016-02-01 13:21:11 -08:00
Denny Page
570ade420a Add simple Makefile 2016-02-01 13:16:02 -08:00
Denny Page
3825066db9 Rename Makefile to Makefile.freebsd 2016-02-01 10:57:32 -08:00
Denny Page
f06b3c8f36 Fix compile warnings for FreeBSD ports 2016-02-01 09:27:55 -08:00
Denny Page
4a57f2584c Add -d option to allow setting data payload length 2016-01-30 21:18:44 -08:00
Denny Page
afbde05bcb Make the standard deviation calculation a little easier on the eyes 2016-01-30 20:44:10 -08:00
Denny Page
85d345f47f Fix defective parsing of time values with 's' suffix 2016-01-14 20:56:46 -08:00
dennypage
e0a0ae14f9 Merge pull request #19 from pfsense/ignore_files
Ignore dpinger.full, dpinger.debug and vim swap files
2016-01-04 10:09:13 -08:00
Renato Botelho
87cd4b6e3b Ignore dpinger.full, dpinger.debug and vim swap files 2016-01-04 10:16:50 -02:00
Denny Page
919fad77a2 Change default time period to 30s and relax default loss interval to 5x send interval to address nagging low level loss reports
Add usage note about loss percentage reporting and calculation
2015-12-30 12:06:54 -08:00
dennypage
2e0430edea Merge pull request #18 from pfsense/fix_build_9
Initialize pidfile_fd to silence FreeBSD 9 warning
2015-12-30 10:39:29 -08:00
Renato Botelho
31432284dc Initialize pidfile_fd to silence FreeBSD 9 warning 2015-12-30 09:15:39 -02:00
Denny Page
e1d00b4210 Set close on exec for all file descriptors 2015-12-29 14:20:06 -08:00
Denny Page
1ec615486b Temporary to allow building on old systems (FreeBSD 9.3, Linux 2.6.26) 2015-12-29 11:28:14 -08:00
Denny Page
6789e90a38 Set close on exec flag if report file is explicit 2015-12-29 10:57:28 -08:00
5 changed files with 174 additions and 59 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,5 @@
/dpinger
/dpinger.debug
/dpinger.full
/dpinger.o
/.*.swp

View File

@@ -1,4 +1,4 @@
Copyright (c) 2015, Denny Page
Copyright (c) 2015-2016, Denny Page
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@@ -1,9 +1,5 @@
PROG= dpinger
MAN=
WARNINGS=-Wall -Wextra -Wformat=2
BINDIR= ${PREFIX}/bin
WARNS= 6
CFLAGS=${WARNINGS} -pthread -g
LDADD= -lpthread
.include <bsd.prog.mk>
all: dpinger

9
Makefile.freebsd Normal file
View File

@@ -0,0 +1,9 @@
PROG= dpinger
MAN=
BINDIR= ${PREFIX}/bin
WARNS= 6
LDADD= -lpthread
.include <bsd.prog.mk>

209
dpinger.c
View File

@@ -1,6 +1,6 @@
//
// Copyright (c) 2015, Denny Page
// Copyright (c) 2015-2016, Denny Page
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -52,6 +52,18 @@
#include <pthread.h>
#include <syslog.h>
// TODO:
//
// After December 31st, 2016, review use of fcntl() for setting non blocking
// and close on exec. It would be preferable to use accept4(), SOCK_CLOEXEC
// and SOCK_NONBLOCK. These are currently avoided to allow use on older
// systems such as FreeBSD 9.3, Linux 2.6.26.
// For Linux accept4() currently requires defining _GNU_SOURCE which we would
// like to avoid.
// For FreeBSD, these definitions were introduced with FreeBSD 10.0 and are
// not present in 9.3 which is supported through 2016.
// Who we are
static const char * progname;
@@ -68,7 +80,7 @@ static unsigned int flag_syslog = 0;
static char dest_str[ADDR_STR_MAX];
// Time period over which we are averaging results in ms
static unsigned long time_period_msec = 25000;
static unsigned long time_period_msec = 30000;
// Interval between sends in ms
static unsigned long send_interval_msec = 250;
@@ -138,8 +150,8 @@ static int recv_sock;
// IPv4 / IPv6 parameters
static uint16_t af_family = AF_INET; // IPv6: AF_INET6
static uint16_t echo_request_type = ICMP_ECHO; // IPv6: ICMP6_ECHO_REQUEST
static uint16_t echo_reply_type = ICMP_ECHOREPLY; // IPv6: ICMP6_ECHO_REPLY
static uint8_t echo_request_type = ICMP_ECHO; // IPv6: ICMP6_ECHO_REQUEST
static uint8_t echo_reply_type = ICMP_ECHOREPLY; // IPv6: ICMP6_ECHO_REPLY
static int ip_proto = IPPROTO_ICMP; // IPv6: IPPROTO_ICMPV6
// Destination address
@@ -163,8 +175,16 @@ typedef struct
uint16_t sequence;
} icmphdr_t;
// Echo request header for sendto
static icmphdr_t echo_request;
// Echo request/reply packet buffers
#define IPV4_ICMP_DATA_MAX (IP_MAXPACKET - sizeof(struct ip) - sizeof(icmphdr_t))
#define IPV6_ICMP_DATA_MAX (IP_MAXPACKET - sizeof(icmphdr_t))
#define PACKET_BUFLEN (IP_MAXPACKET + 256)
static unsigned long echo_data_len = 0;
static unsigned int echo_request_len = sizeof(icmphdr_t);
static unsigned int echo_reply_len = IP_MAXPACKET;
static icmphdr_t * echo_request;
static void * echo_reply;
// Echo id and Sequence information
static uint16_t echo_id;
@@ -242,7 +262,7 @@ cksum(
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
return ~sum;
return (uint16_t) ~sum;
}
@@ -307,9 +327,10 @@ send_thread(
int r;
// Set up our echo request packet
echo_request.type = echo_request_type;
echo_request.code = 0;
echo_request.id = echo_id;
memset(echo_request, 0, echo_request_len);
echo_request->type = echo_request_type;
echo_request->code = 0;
echo_request->id = echo_id;
// Set up the timespec for nanosleep
sleeptime.tv_sec = send_interval_msec / 1000;
@@ -324,20 +345,20 @@ send_thread(
}
// Set sequence number and checksum
echo_request.sequence = htons(next_sequence);
echo_request.cksum = 0;
echo_request.cksum = cksum((uint16_t *) &echo_request, sizeof(icmphdr_t));
echo_request->sequence = htons(next_sequence);
echo_request->cksum = 0;
echo_request->cksum = cksum((uint16_t *) echo_request, sizeof(icmphdr_t));
array[next_slot].status = PACKET_STATUS_EMPTY;
sched_yield();
clock_gettime(CLOCK_MONOTONIC, &array[next_slot].time_sent);
array[next_slot].status = PACKET_STATUS_SENT;
r = sendto(send_sock, &echo_request, sizeof(icmphdr_t), 0, (struct sockaddr *) &dest_addr, dest_addr_len);
r = sendto(send_sock, echo_request, echo_request_len, 0, (struct sockaddr *) &dest_addr, dest_addr_len);
if (r == -1)
{
logger("%s%s: sendto error: %d\n", identifier, dest_str, errno);
}
array[next_slot].status = PACKET_STATUS_SENT;
next_slot = (next_slot + 1) % array_size;
next_sequence = (next_sequence + 1) % sequence_limit;
@@ -355,10 +376,9 @@ static void *
recv_thread(
void * arg)
{
char packet[1024];
unsigned int packet_len;
struct sockaddr_storage src_addr;
socklen_t src_addr_len;
size_t len;
icmphdr_t * icmp;
struct timespec now;
unsigned int array_slot;
@@ -366,8 +386,8 @@ recv_thread(
while (1)
{
src_addr_len = sizeof(src_addr);
packet_len = recvfrom(recv_sock, &packet, sizeof(packet), 0, (struct sockaddr *) &src_addr, &src_addr_len);
if (packet_len == (unsigned int) -1)
len = recvfrom(recv_sock, echo_reply, echo_reply_len, 0, (struct sockaddr *) &src_addr, &src_addr_len);
if (len == (unsigned int) -1)
{
logger("%s%s: recvfrom error: %d\n", identifier, dest_str, errno);
continue;
@@ -380,25 +400,25 @@ recv_thread(
size_t ip_len;
// With IPv4, we get the entire IP packet
if (packet_len < sizeof(struct ip))
if (len < sizeof(struct ip))
{
logger("%s%s: received packet too small for IP header\n", identifier, dest_str);
continue;
}
ip = (void *) packet;
ip_len = ip->ip_hl << 2;
ip = echo_reply;
ip_len = (size_t) ip->ip_hl << 2;
icmp = (void *) (packet + ip_len);
packet_len -= ip_len;
icmp = (void *) ((char *) ip + ip_len);
len -= ip_len;
}
else
{
// With IPv6, we just get the ICMP payload
icmp = (void *) (packet);
icmp = echo_reply;
}
// This should never happen
if (packet_len < sizeof(icmphdr_t))
if (len < sizeof(icmphdr_t))
{
logger("%s%s: received packet too small for ICMP header\n", identifier, dest_str);
continue;
@@ -438,6 +458,7 @@ report(
struct timespec now;
unsigned long packets_received = 0;
unsigned long packets_lost = 0;
unsigned long latency_usec = 0;
unsigned long total_latency_usec = 0;
unsigned long long total_latency_usec2 = 0;
unsigned int slot;
@@ -451,8 +472,9 @@ report(
if (array[slot].status == PACKET_STATUS_RECEIVED)
{
packets_received++;
total_latency_usec += array[slot].latency_usec;
total_latency_usec2 += array[slot].latency_usec * array[slot].latency_usec;
latency_usec = array[slot].latency_usec;
total_latency_usec += latency_usec;
total_latency_usec2 += latency_usec * latency_usec;
}
else if (array[slot].status == PACKET_STATUS_SENT &&
ts_elapsed_usec(&array[slot].time_sent, &now) > loss_interval_usec)
@@ -465,10 +487,12 @@ report(
if (packets_received)
{
*average_latency_usec = total_latency_usec / packets_received;
unsigned long avg = total_latency_usec / packets_received;
unsigned long long avg2 = total_latency_usec2 / packets_received;
// sqrt( (sum(rtt^2) / packets) - (sum(rtt) / packets)^2)
*latency_deviation = llsqrt((total_latency_usec2 / packets_received) - (total_latency_usec / packets_received) * (total_latency_usec / packets_received));
// stddev = sqrt((sum(rtt^2) / packets) - (sum(rtt) / packets)^2)
*average_latency_usec = avg;
*latency_deviation = llsqrt(avg2 - (avg * avg));
}
else
{
@@ -665,6 +689,7 @@ usocket_thread(
while (1)
{
sock_fd = accept(usocket_fd, NULL, NULL);
(void) fcntl(sock_fd, F_SETFL, FD_CLOEXEC);
(void) fcntl(sock_fd, F_SETFL, fcntl(sock_fd, F_GETFL, 0) | O_NONBLOCK);
report(&average_latency_usec, &latency_deviation, &average_loss_percent);
@@ -718,7 +743,7 @@ get_time_arg_msec(
else if (*suffix == 's')
{
// Seconds
*value *= 1000;
t *= 1000;
suffix++;
}
@@ -761,6 +786,41 @@ get_percent_arg(
}
//
// Decode a byte length argument
//
static int
get_length_arg(
const char * arg,
unsigned long * value)
{
unsigned long t;
char * suffix;
t = strtoul(arg, &suffix, 10);
if (*suffix == 'b')
{
// Bytes
suffix++;
}
else if (*suffix == 'k')
{
// Kilobytes
t *= 1024;
suffix++;
}
// Garbage in the number?
if (*suffix != 0)
{
return 1;
}
*value = t;
return 0;
}
//
// Output usage
//
@@ -768,16 +828,17 @@ static void
usage(void)
{
fprintf(stderr, "Usage:\n");
fprintf(stderr, " %s [-f] [-R] [-S] [-B bind_addr] [-s send_interval] [-l loss_interval] [-t time_period] [-r report_interval] [-o output_file] [-A alert_interval] [-D latency_alarm] [-L loss_alarm] [-C alert_cmd] [-i identifier] [-u usocket] [-p pidfile] dest_addr\n\n", progname);
fprintf(stderr, " %s [-f] [-R] [-S] [-B bind_addr] [-s send_interval] [-l loss_interval] [-t time_period] [-r report_interval] [-d data_length] [-o output_file] [-A alert_interval] [-D latency_alarm] [-L loss_alarm] [-C alert_cmd] [-i identifier] [-u usocket] [-p pidfile] dest_addr\n\n", progname);
fprintf(stderr, " options:\n");
fprintf(stderr, " -f run in foreground\n");
fprintf(stderr, " -R rewind output file between reports\n");
fprintf(stderr, " -S log warnings via syslog\n");
fprintf(stderr, " -B bind (source) address\n");
fprintf(stderr, " -s time interval between echo requests (default 250ms)\n");
fprintf(stderr, " -l time interval before packets are treated as lost (default 2x send interval)\n");
fprintf(stderr, " -t time period over which results are averaged (default 25s)\n");
fprintf(stderr, " -l time interval before packets are treated as lost (default 5x send interval)\n");
fprintf(stderr, " -t time period over which results are averaged (default 30s)\n");
fprintf(stderr, " -r time interval between reports (default 1s)\n");
fprintf(stderr, " -d data length (default 0)\n");
fprintf(stderr, " -o output file for reports (default stdout)\n");
fprintf(stderr, " -A time interval between alerts (default 1s)\n");
fprintf(stderr, " -D time threshold for latency alarm (default none)\n");
@@ -787,11 +848,13 @@ usage(void)
fprintf(stderr, " -u unix socket name for polling\n");
fprintf(stderr, " -p process id file name\n\n");
fprintf(stderr, " notes:\n");
fprintf(stderr, " IP addresses can be in either IPv4 or IPv6 format\n\n");
fprintf(stderr, " time values can be expressed with a suffix of 'm' (milliseconds) or 's' (seconds)\n");
fprintf(stderr, " if no suffix is specified, milliseconds is the default\n\n");
fprintf(stderr, " IP addresses can be in either IPv4 or IPv6 format\n\n");
fprintf(stderr, " the output format is \"latency_avg latency_stddev loss_pct\"\n");
fprintf(stderr, " latency values are output in microseconds\n\n");
fprintf(stderr, " latency values are output in microseconds\n");
fprintf(stderr, " loss percentage is reported in whole numbers of 0-100\n");
fprintf(stderr, " resolution of loss calculation is: 100 * send_interval / (time_period - loss_interval)\n\n");
fprintf(stderr, " the alert_cmd is invoked as \"alert_cmd dest_addr alarm_flag latency_avg loss_avg\"\n");
fprintf(stderr, " alarm_flag is set to 1 if either latency or loss is in alarm state\n");
fprintf(stderr, " alarm_flag will return to 0 when both have have cleared alarm state\n\n");
@@ -837,7 +900,7 @@ parse_args(
progname = argv[0];
while((opt = getopt(argc, argv, "fRSB:s:l:t:r:o:A:D:L:C:i:u:p:")) != -1)
while((opt = getopt(argc, argv, "fRSB:s:l:t:r:d:o:A:D:L:C:i:u:p:")) != -1)
{
switch (opt)
{
@@ -889,6 +952,14 @@ parse_args(
}
break;
case 'd':
r = get_length_arg(optarg, &echo_data_len);
if (r)
{
fatal("invalid data length %s\n", optarg);
}
break;
case 'o':
report_name = optarg;
break;
@@ -920,7 +991,7 @@ parse_args(
case 'C':
alert_cmd_offset = strlen(optarg);
alert_cmd = malloc (alert_cmd_offset + OUTPUT_MAX);
alert_cmd = malloc(alert_cmd_offset + OUTPUT_MAX);
if (alert_cmd == NULL)
{
fatal("malloc of alert command buffer failed\n");
@@ -1026,6 +1097,27 @@ parse_args(
memcpy(&bind_addr, addr_info->ai_addr, bind_addr_len);
freeaddrinfo(addr_info);
}
// Check requested data length
if (echo_data_len)
{
if (af_family == AF_INET)
{
if (echo_data_len > IPV4_ICMP_DATA_MAX)
{
fatal("data length too large for IPv4 - maximum is %u bytes\n", IPV4_ICMP_DATA_MAX);
}
}
else
{
if (echo_data_len > IPV6_ICMP_DATA_MAX)
{
fatal("data length too large for IPv6 - maximum is %u bytes\n", IPV6_ICMP_DATA_MAX);
}
}
echo_request_len += echo_data_len;
}
}
@@ -1038,9 +1130,10 @@ main(
char *argv[])
{
char bind_str[ADDR_STR_MAX] = "(none)";
int pidfile_fd;
int pidfile_fd = -1;
pthread_t thread;
struct sigaction act;
int buflen = PACKET_BUFLEN;
int r;
// Handle command line args
@@ -1053,12 +1146,17 @@ main(
perror("socket");
fatal("cannot create send socket\n");
}
(void) fcntl(send_sock, F_SETFL, FD_CLOEXEC);
(void) setsockopt(send_sock, SOL_SOCKET, SO_SNDBUF, &buflen, sizeof(buflen));
recv_sock = socket(af_family, SOCK_RAW, ip_proto);
if (recv_sock == -1)
{
perror("socket");
fatal("cannot create recv socket\n");
}
(void) fcntl(recv_sock, F_SETFL, FD_CLOEXEC);
(void) setsockopt(recv_sock, SOL_SOCKET, SO_RCVBUF, &buflen, sizeof(buflen));
// Bind our sockets to an address if requested
if (bind_addr_len)
@@ -1084,7 +1182,7 @@ main(
// Create report file
if (report_name)
{
report_fd = open(report_name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
report_fd = open(report_name, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
if (report_fd == -1)
{
perror("open");
@@ -1106,12 +1204,13 @@ main(
fatal("socket name too large\n");
}
usocket_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
usocket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (usocket_fd == -1)
{
perror("socket");
fatal("cannot create unix domain socket\n");
}
(void) fcntl(usocket_fd, F_SETFL, FD_CLOEXEC);
(void) unlink(usocket_name);
@@ -1143,7 +1242,7 @@ main(
// Create pid file
if (pidfile_name)
{
pidfile_fd = open(pidfile_name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
pidfile_fd = open(pidfile_name, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
if (pidfile_fd == -1)
{
perror("open");
@@ -1179,7 +1278,7 @@ main(
sigaction(SIGINT, &act, NULL);
// Write pid file
if (pidfile_name)
if (pidfile_fd != -1)
{
char buf[64];
int len;
@@ -1197,7 +1296,7 @@ main(
fatal("error writing pidfile\n");
}
r= close(pidfile_fd);
r = close(pidfile_fd);
if (r == -1)
{
perror("close");
@@ -1206,17 +1305,25 @@ main(
}
// Create the array
array_size = time_period_msec / send_interval_msec;
array_size = (unsigned int) (time_period_msec / send_interval_msec);
array = calloc(array_size, sizeof(*array));
if (array == NULL)
{
fatal("calloc of packet array failed\n");
}
// Allocate the echo request/reply packet buffers
echo_request = (icmphdr_t *) malloc(echo_request_len);
echo_reply = malloc(echo_reply_len);
if (echo_request == NULL || echo_reply == NULL)
{
fatal("malloc of packet buffers failed\n");
}
// Set the default loss interval
if (loss_interval_msec == 0)
{
loss_interval_msec = send_interval_msec * 4;
loss_interval_msec = send_interval_msec * 5;
}
loss_interval_usec = loss_interval_msec * 1000;
@@ -1236,8 +1343,8 @@ main(
}
}
logger("send_interval %lums loss_interval %lums time_period %lums report_interval %lums alert_interval %lums latency_alarm %lums loss_alarm %lu%% dest_addr %s bind_addr %s identifier \"%s\"\n",
send_interval_msec, loss_interval_msec, time_period_msec, report_interval_msec,
logger("send_interval %lums loss_interval %lums time_period %lums report_interval %lums data_len %lu alert_interval %lums latency_alarm %lums loss_alarm %lu%% dest_addr %s bind_addr %s identifier \"%s\"\n",
send_interval_msec, loss_interval_msec, time_period_msec, report_interval_msec, echo_data_len,
alert_interval_msec, latency_alarm_threshold_msec, loss_alarm_threshold_percent,
dest_str, bind_str, identifier);
@@ -1245,10 +1352,10 @@ main(
echo_id = htons(getpid());
// Set the limit for sequence number to ensure a multiple of array size
sequence_limit = array_size;
sequence_limit = (uint16_t) array_size;
while ((sequence_limit & 0x8000) == 0)
{
sequence_limit = sequence_limit << 1;
sequence_limit <<= 1;
}
// Create recv thread