From bf1a831e4606ffb150e5ad961360b74dab54dfdb Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Thu, 7 Jan 2016 21:49:53 +0200 Subject: [PATCH] Redesign the program to use network statistics This method can leverage any traffic that passes through the network interface. Ping is still used as well but only for reporting packet loss. --- main.c | 228 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 159 insertions(+), 69 deletions(-) diff --git a/main.c b/main.c index fd2e878..472c83c 100644 --- a/main.c +++ b/main.c @@ -1,6 +1,6 @@ /* netmon - a simple network connectivity monitor -Copyright © 2008 Mikko Rasa, Mikkosoft Productions +Copyright © 2008-2016 Mikko Rasa, Mikkosoft Productions Distributed under the GPL */ @@ -14,7 +14,13 @@ Distributed under the GPL #include #include #include +#include +#include +#include +#include +#include +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 *); @@ -24,27 +30,35 @@ int pid; 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': @@ -53,37 +67,48 @@ int main(int argc, char **argv) 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(optindh_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(); @@ -102,29 +127,104 @@ int main(int argc, char **argv) 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) @@ -155,45 +255,15 @@ int main(int argc, char **argv) { /* 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(); @@ -201,6 +271,26 @@ int main(int argc, char **argv) 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; @@ -233,7 +323,7 @@ void send_ping(in_addr_t target, uint16_t seq) 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) @@ -250,7 +340,7 @@ pid_t run_command(const char *cmd) const char *argv[2]; argv[0] = cmd; argv[1] = 0; - execv(cmd, argv); + execv(cmd, (char *const *)argv); exit(127); } else -- 2.43.0