]> git.tdb.fi Git - netmon.git/blobdiff - main.c
Request default POSIX features from system headers
[netmon.git] / main.c
diff --git a/main.c b/main.c
index fd2e8789148735853ac4c954fda0466d5a78687d..e22dbbe2f3c1670da6095938ba4a254e32f682fc 100644 (file)
--- a/main.c
+++ b/main.c
 /*
 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':
@@ -53,160 +113,389 @@ int main(int argc, char **argv)
                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);
@@ -214,30 +503,27 @@ unsigned checksum(const char *data, unsigned len)
        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)
@@ -250,7 +536,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