/*
netmon - a simple network connectivity monitor
-Copyright © 2008 Mikko Rasa, Mikkosoft Productions
+Copyright © 2008-2016 Mikko Rasa, Mikkosoft Productions
Distributed under the GPL
*/
#include <netdb.h>
#include <sys/wait.h>
#include <stdlib.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <string.h>
+unsigned long long read_number(const char *);
unsigned checksum(const char *, unsigned);
void send_ping(in_addr_t, uint16_t);
pid_t run_command(const char *);
int main(int argc, char **argv)
{
- const char *target_name = "google.com";
- struct hostent *target_host;
- in_addr_t target;
+ const char *ping_target_name = NULL;
+ in_addr_t ping_target = 0;
+ const char *interface = NULL;
struct pollfd pfd;
+ unsigned next_ping = 0;
uint16_t seq = 1;
uint16_t pending = 0;
+ unsigned ping_count = 0;
unsigned lost = 0;
- unsigned streak = 0;
- unsigned max_streak = 0;
unsigned next_stats = 0;
int o;
+ char *endp;
int no_daemon = 0;
int verbose = 0;
unsigned stats_interval = 1800;
- unsigned trigger_count = 900;
+ unsigned trigger_delay = 60;
+ unsigned trigger_interval = 600;
const char *trigger_cmd = NULL;
unsigned next_trigger;
pid_t child_pid = 0;
+ unsigned long long rx_packets = 0;
+ unsigned long long tx_packets = 0;
+ unsigned last_receive = 0;
+ unsigned last_transmit = 0;
+ int connection_status = 1;
+ unsigned connection_down_time;
/* Parse options */
- while((o = getopt(argc, argv, "fvi:t:c:"))!=-1)
+ while((o = getopt(argc, argv, "fvs:t:c:i:p:"))!=-1)
switch(o)
{
case 'f':
case 'v':
verbose = 1;
break;
- case 'i':
+ case 's':
stats_interval = strtoul(optarg, NULL, 10);
break;
case 't':
- trigger_count = strtoul(optarg, NULL, 10);
+ trigger_delay = strtoul(optarg, &endp, 10);
+ if(*endp==',')
+ trigger_interval = strtoul(endp+1, NULL, 10);
+ else
+ trigger_interval = trigger_delay;
break;
case 'c':
trigger_cmd = optarg;
break;
+ case 'i':
+ interface = optarg;
+ break;
+ case 'p':
+ ping_target_name = optarg;
+ break;
}
- /* Take target hostname from commandline if specified */
- if(optind<argc)
- target_name = argv[optind];
-
- target_host = gethostbyname(target_name);
- if(!target_host)
+ if(ping_target_name)
{
- herror("gethostbyname");
- return 1;
- }
+ struct hostent *ping_target_host;
- if(target_host->h_addrtype!=AF_INET)
- {
- fprintf(stderr, "Got a hostent, but it doesn't have an IPv4 address");
- return 1;
- }
- target = ntohl(*(in_addr_t *)*target_host->h_addr_list);
+ ping_target_host = gethostbyname(ping_target_name);
+ if(!ping_target_host)
+ {
+ herror("gethostbyname");
+ return 1;
+ }
+
+ if(ping_target_host->h_addrtype!=AF_INET)
+ {
+ fprintf(stderr, "Got a hostent, but it doesn't have an IPv4 address");
+ return 1;
+ }
+ ping_target = *(in_addr_t *)*ping_target_host->h_addr_list;
- if(verbose)
- printf("Target is %s\n", inet_ntoa(htonl(target)));
+ if(verbose)
+ printf("Ping target is %s\n", inet_ntoa(*(struct in_addr *)*ping_target_host->h_addr_list));
+ }
/* Miscellaneous initialization */
pid = getpid();
openlog("netmon", 0, LOG_LOCAL7);
- next_trigger = trigger_count;
+ next_trigger = trigger_delay;
while(1)
{
struct timeval tv;
int res;
+ char fnbuf[256];
+ unsigned long long count;
gettimeofday(&tv, NULL);
- /* Send stats to syslog every stats_interval seconds */
- if(tv.tv_sec>next_stats)
+ /* Read network statistics */
+ snprintf(fnbuf, sizeof(fnbuf), "/sys/class/net/%s/statistics/rx_packets", interface);
+ count = read_number(fnbuf);
+ if(count>rx_packets)
+ last_receive = tv.tv_sec;
+ rx_packets = count;
+
+ snprintf(fnbuf, sizeof(fnbuf), "/sys/class/net/%s/statistics/tx_packets", interface);
+ count = read_number(fnbuf);
+ if(count>tx_packets)
+ last_transmit = tv.tv_sec;
+ tx_packets = count;
+
+ /* If packets have been transmitted more recently than received, there
+ might be a problem */
+ if(last_transmit>last_receive)
{
- if(next_stats)
+ if(connection_status && tv.tv_sec>=last_receive+10)
{
+ connection_status = 0;
+ connection_down_time = last_receive;
if(no_daemon)
- printf("Lost %d, max streak %d\n", lost, max_streak);
+ printf("Connection is down\n");
else
- syslog(LOG_INFO, "Lost %d, max streak %d", lost, max_streak);
- lost = 0;
- max_streak = 0;
+ syslog(LOG_INFO, "Connection is down");
+ }
+
+ if(trigger_cmd && tv.tv_sec>last_receive+next_trigger)
+ {
+ if(no_daemon)
+ printf("Running %s\n", trigger_cmd);
+ else
+ syslog(LOG_INFO, "Running %s", trigger_cmd);
+ child_pid = run_command(trigger_cmd);
+ next_trigger += trigger_interval;
+ }
+ }
+ else if(!connection_status)
+ {
+ unsigned duration = tv.tv_sec-connection_down_time;
+ connection_status = 1;
+ if(no_daemon)
+ printf("Connection is up (was down for %d seconds)\n", duration);
+ else
+ syslog(LOG_INFO, "Connection is up (was down for %d seconds)", duration);
+
+ next_trigger = trigger_delay;
+ }
+
+ if(ping_target)
+ {
+ /* Send ping packets to monitor packet loss */
+ if(tv.tv_sec>=next_ping)
+ {
+ if(pending)
+ {
+ ++lost;
+ if(verbose)
+ printf("Lost ping\n");
+ }
+
+ send_ping(ping_target, seq);
+ pending = seq++;
+ if(!seq)
+ ++seq;
+ ++ping_count;
+ next_ping = tv.tv_sec+1;
+ }
+
+ if(tv.tv_sec>=next_stats)
+ {
+ if(lost)
+ {
+ float loss_ratio = (float)lost/ping_count;
+ if(no_daemon)
+ printf("Packet loss: %.2f%%\n", loss_ratio*100);
+ else
+ syslog(LOG_INFO, "Packet loss: %.2f%%", loss_ratio*100);
+
+ ping_count = 0;
+ lost = 0;
+ }
+
+ next_stats = tv.tv_sec+stats_interval;
}
- next_stats = tv.tv_sec+stats_interval;
}
+ /* Reap any finished child process */
if(child_pid)
{
if(waitpid(child_pid, NULL, WNOHANG)==child_pid)
{
/* It's an ICMP echo reply and ours, process it */
if(verbose)
- printf("Ping reply from %s\n", inet_ntoa(addr.sin_addr.s_addr));
+ printf("Ping reply from %s\n", inet_ntoa(addr.sin_addr));
if(icmp->un.echo.sequence==pending)
- {
pending = 0;
- streak = 0;
- next_trigger = trigger_count;
- }
else if(verbose)
printf("Sequence %d, expected %d\n", icmp->un.echo.sequence, pending);
}
}
}
}
- else
- {
- /* Poll timed out, check for lost ping and send more */
- if(pending)
- {
- if(verbose)
- printf("Lost ping\n");
- ++lost;
- ++streak;
- if(streak>max_streak)
- max_streak = streak;
- if(streak>=next_trigger && trigger_cmd && !child_pid)
- {
- if(no_daemon)
- printf("Running %s\n", trigger_cmd);
- else
- syslog(LOG_INFO, "Running %s\n", trigger_cmd);
- child_pid = run_command(trigger_cmd);
- next_trigger += trigger_count;
- }
- }
- send_ping(target, seq);
- pending = seq;
- if(!++seq)
- ++seq;
- }
}
closelog();
return 0;
}
+unsigned long long read_number(const char *fn)
+{
+ char buf[64];
+ int fd;
+ int len;
+
+ fd = open(fn, O_RDONLY);
+ if(fd<0)
+ return 0;
+
+ len = read(fd, buf, sizeof(buf));
+ close(fd);
+ if(len<0)
+ return 0;
+
+ buf[len] = 0;
+
+ return strtoull(buf, NULL, 10);
+}
+
unsigned checksum(const char *data, unsigned len)
{
unsigned sum = 0;
hdr->checksum = checksum(data, sizeof(data));
addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = htonl(target);
+ addr.sin_addr.s_addr = target;
addr.sin_port = 0;
if(sendto(sock, data, sizeof(data), 0, (struct sockaddr *)&addr, sizeof(addr))==-1)
const char *argv[2];
argv[0] = cmd;
argv[1] = 0;
- execv(cmd, argv);
+ execv(cmd, (char *const *)argv);
exit(127);
}
else