/*
netmon - a simple network connectivity monitor
-Copyright © 2008 Mikko Rasa, Mikkosoft Productions
+Copyright © 2008-2016 Mikko Rasa, Mikkosoft Productions
Distributed under the GPL
*/
+#define _DEFAULT_SOURCE
#include <stdint.h>
#include <stdio.h>
-#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
#include <errno.h>
#include <syslog.h>
#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <net/ethernet.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
+#include <netinet/ip6.h>
#include <netdb.h>
-#include <sys/wait.h>
-#include <stdlib.h>
+#include <arpa/inet.h>
+#include <pcap/pcap.h>
+
+typedef unsigned long long Time;
+
+typedef struct
+{
+ struct sockaddr_storage address;
+ struct sockaddr_storage mask;
+} Address;
+
+typedef struct
+{
+ char *interface_name;
+ pcap_t *pcap;
+ unsigned n_addresses;
+ Address *addresses;
+ Time last_receive;
+ Time last_transmit;
+ int connection_status;
+ Time connection_down_time;
+} Monitor;
+
+typedef struct
+{
+ Monitor *monitor;
+ Time delay;
+ Time interval;
+ const char *command;
+ Time next;
+ pid_t child_pid;
+} Trigger;
+typedef struct
+{
+ const char *target_name;
+ struct sockaddr_in target_addr;
+ int socket;
+ struct pollfd pfd;
+ Time next;
+ uint16_t id;
+ uint16_t seq;
+ uint16_t pending;
+ unsigned count;
+ unsigned lost;
+} Pinger;
+
+Time current_time();
+int monitor_init(Monitor *);
+void monitor_check(Monitor *, Time);
+void capture_handler(uint8_t *, const struct pcap_pkthdr *, const uint8_t *);
+int trigger_init(Trigger *);
+void trigger_check(Trigger *, Time);
+int get_inet_scope(uint32_t, const Address *);
+int get_inet6_scope(const struct in6_addr *, const Address *);
+int pinger_init(Pinger *);
+void pinger_check(Pinger *, Time);
unsigned checksum(const char *, unsigned);
-void send_ping(in_addr_t, uint16_t);
+void send_ping(Pinger *);
pid_t run_command(const char *);
-int sock;
-int pid;
+int no_daemon;
+int verbose;
int main(int argc, char **argv)
{
- const char *target_name = "google.com";
- struct hostent *target_host;
- in_addr_t target;
- struct pollfd pfd;
- uint16_t seq = 1;
- uint16_t pending = 0;
- unsigned lost = 0;
- unsigned streak = 0;
- unsigned max_streak = 0;
- unsigned next_stats = 0;
+ Monitor monitor;
+ Trigger trigger;
+ Pinger pinger;
int o;
- int no_daemon = 0;
- int verbose = 0;
- unsigned stats_interval = 1800;
- unsigned trigger_count = 900;
- const char *trigger_cmd = NULL;
- unsigned next_trigger;
- pid_t child_pid = 0;
+ char *endp;
+ Time stats_interval = 1800000000ULL;
+
+ no_daemon = 0;
+ verbose = 0;
+ monitor.interface_name = NULL;
+ trigger.monitor = &monitor;
+ trigger.delay = 60000000ULL;
+ trigger.interval = 600000000ULL;
+ pinger.target_name = NULL;
/* 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':
- stats_interval = strtoul(optarg, NULL, 10);
+ case 's':
+ stats_interval = strtoul(optarg, NULL, 10)*1000000;
break;
case 't':
- trigger_count = strtoul(optarg, NULL, 10);
+ trigger.delay = strtoul(optarg, &endp, 10)*1000000;
+ if(*endp==',')
+ trigger.interval = strtoul(endp+1, NULL, 10)*1000000;
+ else
+ trigger.interval = trigger.delay;
break;
case 'c':
- trigger_cmd = optarg;
+ trigger.command = optarg;
+ break;
+ case 'i':
+ monitor.interface_name = strdup(optarg);
+ break;
+ case 'p':
+ pinger.target_name = strdup(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(monitor.interface_name)
{
- herror("gethostbyname");
- return 1;
+ if(monitor_init(&monitor))
+ return 1;
+
+ if(trigger.command)
+ if(trigger_init(&trigger))
+ return 1;
}
- if(target_host->h_addrtype!=AF_INET)
+ if(pinger.target_name)
+ if(pinger_init(&pinger))
+ return 1;
+
+ if(!no_daemon && daemon(1, 0)==-1)
{
- fprintf(stderr, "Got a hostent, but it doesn't have an IPv4 address");
+ perror("daemon");
return 1;
}
- target = ntohl(*(in_addr_t *)*target_host->h_addr_list);
- if(verbose)
- printf("Target is %s\n", inet_ntoa(htonl(target)));
+ openlog("netmon", 0, LOG_LOCAL7);
- /* Miscellaneous initialization */
- pid = getpid();
- sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
- if(sock==-1)
+ Time next_stats = current_time()+stats_interval;
+ while(1)
{
- perror("socket");
- return 1;
+ Time time = current_time();
+
+ if(monitor.interface_name)
+ {
+ monitor_check(&monitor, time);
+
+ if(trigger.command)
+ trigger_check(&trigger, time);
+ }
+
+ if(pinger.target_name)
+ {
+ pinger_check(&pinger, time);
+
+ if(time>=next_stats)
+ {
+ if(pinger.count)
+ {
+ float loss_ratio = (float)pinger.lost/pinger.count;
+ if(no_daemon)
+ printf("Packet loss: %.2f%%\n", loss_ratio*100);
+ else
+ syslog(LOG_INFO, "Packet loss: %.2f%%", loss_ratio*100);
+ }
+
+ pinger.count = 0;
+ pinger.lost = 0;
+ next_stats += stats_interval;
+ }
+ }
}
- pfd.fd = sock;
- pfd.events = POLLIN;
+ closelog();
- if(!no_daemon && daemon(1, 0)==-1)
- perror("daemon");
+ return 0;
+}
- openlog("netmon", 0, LOG_LOCAL7);
+Time current_time()
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ return tv.tv_sec*1000000ULL+tv.tv_usec;
+}
- next_trigger = trigger_count;
- while(1)
+int monitor_init(Monitor *monitor)
+{
+ char err[PCAP_ERRBUF_SIZE];
+ pcap_if_t *devs;
+ if(pcap_findalldevs(&devs, err)==-1)
+ {
+ fprintf(stderr, "pcap_findalldevs: %s\n", err);
+ return -1;
+ }
+
+ monitor->pcap = pcap_open_live(monitor->interface_name, 64, 0, 10, err);
+ if(!monitor->pcap)
+ {
+ fprintf(stderr, "pcap_findalldevs: %s\n", err);
+ return -1;
+ }
+
+ if(pcap_setnonblock(monitor->pcap, 1, err)==-1)
{
- struct timeval tv;
- int res;
+ fprintf(stderr, "pcap_findalldevs: %s\n", err);
+ return -1;
+ }
+
+ monitor->n_addresses = 0;
+ monitor->addresses = NULL;
+ for(pcap_if_t *d=devs; d; d=d->next)
+ for(pcap_addr_t *a=d->addresses; a; a=a->next)
+ if(a->addr->sa_family==AF_INET || a->addr->sa_family==AF_INET6)
+ {
+ monitor->addresses = (Address *)realloc(monitor->addresses, (monitor->n_addresses+1)*sizeof(Address));
+ unsigned size = (a->addr->sa_family==AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in));
+ memcpy(&monitor->addresses[monitor->n_addresses].address, a->addr, size);
+ memcpy(&monitor->addresses[monitor->n_addresses].mask, a->netmask, size);
+ ++monitor->n_addresses;
+ }
+
+ pcap_freealldevs(devs);
- gettimeofday(&tv, NULL);
+ monitor->last_receive = 0;
+ monitor->last_transmit = 0;
+ monitor->connection_status = 1;
- /* Send stats to syslog every stats_interval seconds */
- if(tv.tv_sec>next_stats)
+ return 0;
+}
+
+void monitor_check(Monitor *monitor, Time timestamp)
+{
+ pcap_dispatch(monitor->pcap, -1, &capture_handler, (uint8_t *)monitor);
+
+ if(monitor->last_transmit && monitor->last_receive)
+ {
+ /* If packets have been transmitted more recently than received, there
+ might be a problem */
+ if(monitor->last_transmit>monitor->last_receive)
{
- if(next_stats)
+ if(monitor->connection_status && timestamp>=monitor->last_receive+10000000)
{
+ monitor->connection_status = 0;
+ monitor->connection_down_time = monitor->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");
}
- next_stats = tv.tv_sec+stats_interval;
}
-
- if(child_pid)
+ else if(!monitor->connection_status)
{
- if(waitpid(child_pid, NULL, WNOHANG)==child_pid)
- child_pid = 0;
+ Time duration = timestamp-monitor->connection_down_time;
+ monitor->connection_status = 1;
+ if(no_daemon)
+ printf("Connection is up (was down for %lld seconds)\n", duration/1000000);
+ else
+ syslog(LOG_INFO, "Connection is up (was down for %lld seconds)", duration/1000000);
}
+ }
+}
- res = poll(&pfd, 1, 1000);
- if(res>0)
- {
- struct sockaddr_in addr;
- socklen_t alen = sizeof(addr);
- char data[1500];
- int len;
- struct iphdr *ip;
- struct icmphdr *icmp;
-
- /* Receive a packet */
- len = recvfrom(sock, data, sizeof(data), 0, (struct sockaddr *)&addr, &alen);
- if(len==-1)
- fprintf(stderr, "recvfrom error: %s\n", strerror(errno));
- else
+void capture_handler(uint8_t *user, const struct pcap_pkthdr *header, const uint8_t *data)
+{
+ Monitor *monitor = (Monitor *)user;
+ const struct ether_header *eth = (const struct ether_header *)(data);
+
+ int src_scope = 0;
+ int dst_scope = 0;
+ int proto = ntohs(eth->ether_type);
+ if(proto==ETHERTYPE_IP)
+ {
+ const struct ip *ip = (const struct ip *)(eth+1);
+ if(ntohl(ip->ip_dst.s_addr)>>28==14)
+ dst_scope = 3;
+
+ for(unsigned i=0; i<monitor->n_addresses; ++i)
+ if(monitor->addresses[i].address.ss_family==AF_INET)
{
- ip = (struct iphdr *)data;
- if(ip->protocol==IPPROTO_ICMP)
- {
- icmp = (struct icmphdr *)(ip+1);
- if(icmp->type==ICMP_ECHOREPLY && icmp->un.echo.id==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));
- 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);
- }
- }
+ int s = get_inet_scope(ip->ip_src.s_addr, &monitor->addresses[i]);
+ if(s>src_scope)
+ src_scope = s;
+
+ s = get_inet_scope(ip->ip_dst.s_addr, &monitor->addresses[i]);
+ if(s>dst_scope)
+ dst_scope = s;
}
+ }
+ else if(proto==ETHERTYPE_IPV6)
+ {
+ const struct ip6_hdr *ip6 = (const struct ip6_hdr *)(eth+1);
+ for(unsigned i=0; i<monitor->n_addresses; ++i)
+ if(monitor->addresses[i].address.ss_family==AF_INET6)
+ {
+ int s = get_inet6_scope(&ip6->ip6_src, &monitor->addresses[i]);
+ if(s>src_scope)
+ src_scope = s;
+
+ s = get_inet6_scope(&ip6->ip6_dst, &monitor->addresses[i]);
+ if(s>dst_scope)
+ dst_scope = s;
+ }
+ }
+
+ Time timestamp = header->ts.tv_sec*1000000ULL+header->ts.tv_usec;
+ if(src_scope==0 && dst_scope>0)
+ monitor->last_receive = timestamp;
+ else if(src_scope>0 && dst_scope==0)
+ monitor->last_transmit = timestamp;
+}
+
+int trigger_init(Trigger *trigger)
+{
+ trigger->next = trigger->delay;
+ trigger->child_pid = 0;
+
+ return 0;
+}
+
+void trigger_check(Trigger *trigger, Time time)
+{
+ Monitor *monitor = trigger->monitor;
+
+ if(monitor->connection_status)
+ trigger->next = trigger->delay;
+ else if(time>=monitor->last_receive+trigger->next)
+ {
+ if(no_daemon)
+ printf("Running %s\n", trigger->command);
+ else
+ syslog(LOG_INFO, "Running %s", trigger->command);
+ trigger->child_pid = run_command(trigger->command);
+ trigger->next += trigger->interval;
+ }
+
+ /* Reap any finished child process */
+ if(trigger->child_pid)
+ {
+ if(waitpid(trigger->child_pid, NULL, WNOHANG)==trigger->child_pid)
+ trigger->child_pid = 0;
+ }
+}
+
+int get_inet_scope(uint32_t addr, const Address *local_addr)
+{
+ uint32_t diff = addr^((struct sockaddr_in *)&local_addr->address)->sin_addr.s_addr;
+ if(!diff)
+ return 2;
+ else if(!(diff&((struct sockaddr_in *)&local_addr->mask)->sin_addr.s_addr))
+ return 1;
+ else
+ return 0;
+}
+
+int get_inet6_scope(const struct in6_addr *addr, const Address *local_addr)
+{
+ int result = 2;
+ for(unsigned i=0; i<16; ++i)
+ {
+ uint8_t diff = addr->s6_addr[i]^((struct sockaddr_in6 *)&local_addr->address)->sin6_addr.s6_addr[i];
+ if(diff)
+ {
+ diff &= ((struct sockaddr_in6 *)&local_addr->mask)->sin6_addr.s6_addr[i];
+ if(diff)
+ return 0;
+ else
+ result = 1;
}
+ }
+
+ return result;
+}
+
+int pinger_init(Pinger *pinger)
+{
+ if(!pinger->target_name)
+ return -1;
+
+ struct hostent *host;
+
+ host = gethostbyname(pinger->target_name);
+ if(!host)
+ {
+ herror("gethostbyname");
+ return -1;
+ }
+
+ if(host->h_addrtype!=AF_INET)
+ {
+ fprintf(stderr, "Got a hostent, but it doesn't have an IPv4 address");
+ return -1;
+ }
+
+ pinger->target_addr.sin_family = AF_INET;
+ pinger->target_addr.sin_addr = *(struct in_addr *)host->h_addr_list[0];
+
+ if(verbose)
+ {
+ char buf[64];
+ inet_ntop(AF_INET, &pinger->target_addr.sin_addr, buf, sizeof(buf));
+ printf("Ping target is %s\n", buf);
+ }
+
+ pinger->socket = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
+ if(pinger->socket==-1)
+ {
+ perror("socket(SOCK_RAW)");
+ return -1;
+ }
+
+ pinger->next = 0;
+ pinger->id = getpid();
+ pinger->seq = 1;
+ pinger->pending = 0;
+ pinger->count = 0;
+ pinger->lost = 0;
+
+ pinger->pfd.fd = pinger->socket;
+ pinger->pfd.events = POLLIN;
+
+ return 0;
+}
+
+void pinger_check(Pinger *pinger, Time time)
+{
+ if(poll(&pinger->pfd, 1, 10)>0)
+ {
+ struct sockaddr_in addr;
+ socklen_t alen = sizeof(addr);
+
+ /* Receive a packet */
+ char data[1500];
+ int len = recvfrom(pinger->socket, data, sizeof(data), 0, (struct sockaddr *)&addr, &alen);
+ if(len==-1)
+ fprintf(stderr, "recvfrom error: %s\n", strerror(errno));
else
{
- /* Poll timed out, check for lost ping and send more */
- if(pending)
+ struct ip *ip = (struct ip *)data;
+ if(ip->ip_p==IPPROTO_ICMP)
{
- if(verbose)
- printf("Lost ping\n");
- ++lost;
- ++streak;
- if(streak>max_streak)
- max_streak = streak;
- if(streak>=next_trigger && trigger_cmd && !child_pid)
+ struct icmp *icmp = (struct icmp *)(ip+1);
+ if(icmp->icmp_type==ICMP_ECHOREPLY && icmp->icmp_id==pinger->id)
{
- 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;
+ /* It's an ICMP echo reply and ours, process it */
+ if(verbose)
+ {
+ char buf[64];
+ inet_ntop(AF_INET, &addr.sin_addr, buf, sizeof(buf));
+ printf("Ping reply from %s\n", buf);
+ }
+
+ if(icmp->icmp_seq==pinger->pending)
+ pinger->pending = 0;
+ else if(verbose)
+ printf("Sequence %d, expected %d\n", icmp->icmp_seq, pinger->pending);
}
}
- send_ping(target, seq);
- pending = seq;
- if(!++seq)
- ++seq;
}
}
- closelog();
+ if(time>=pinger->next)
+ {
+ if(pinger->pending)
+ {
+ ++pinger->lost;
+ if(verbose)
+ printf("Lost ping\n");
+ }
- return 0;
+ send_ping(pinger);
+ pinger->next = time+1000000;
+ }
}
unsigned checksum(const char *data, unsigned len)
{
unsigned sum = 0;
- unsigned i;
-
- for(i=0; i<len; i+=2)
+ for(unsigned i=0; i<len; i+=2)
sum += *(const unsigned short *)(data+i);
while(sum>0xFFFF)
sum = (sum>>16)+(sum&0xFFFF);
return ~sum;
}
-void send_ping(in_addr_t target, uint16_t seq)
+void send_ping(Pinger *pinger)
{
char data[64];
- unsigned i;
- struct icmphdr *hdr;
- struct sockaddr_in addr;
-
- for(i=0; i<sizeof(data); ++i)
+ for(unsigned i=0; i<sizeof(data); ++i)
data[i] = i;
- hdr = (struct icmphdr *)data;
- hdr->type = ICMP_ECHO;
- hdr->code = 0;
- hdr->checksum = 0;
- hdr->un.echo.id = pid;
- hdr->un.echo.sequence = seq;
- hdr->checksum = checksum(data, sizeof(data));
+ struct icmp *hdr = (struct icmp *)data;
+ hdr->icmp_type = ICMP_ECHO;
+ hdr->icmp_code = 0;
+ hdr->icmp_cksum = 0;
+ hdr->icmp_id = pinger->id;
+ hdr->icmp_seq = pinger->seq;
+ hdr->icmp_cksum = checksum(data, sizeof(data));
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = htonl(target);
- addr.sin_port = 0;
-
- if(sendto(sock, data, sizeof(data), 0, (struct sockaddr *)&addr, sizeof(addr))==-1)
+ if(sendto(pinger->socket, data, sizeof(data), 0, (struct sockaddr *)&pinger->target_addr, sizeof(struct sockaddr_in))==-1)
fprintf(stderr, "sendto error: %s\n", strerror(errno));
+
+ pinger->pending = pinger->seq++;
+ if(!pinger->seq)
+ ++pinger->seq;
+ ++pinger->count;
}
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