]> git.tdb.fi Git - netmon.git/commitdiff
Rewrite the program again with libpcap
authorMikko Rasa <tdb@tdb.fi>
Thu, 7 Jan 2016 23:13:07 +0000 (01:13 +0200)
committerMikko Rasa <tdb@tdb.fi>
Thu, 7 Jan 2016 23:13:07 +0000 (01:13 +0200)
Network interface statistics were not good enough.  In particular, local
devices on the Internet side of the router (such as ADSL modem) may
generate packets as well and fool the logic into thinking there is
traffic coming from the Internet.  With packet capture it's possible to
distinguish local traffic from actual Internet traffic.

main.c

diff --git a/main.c b/main.c
index 472c83c82dc48d210139555bf7534d0e69745269..1b123858edba619a3b71e1f764260dfbd4535bf1 100644 (file)
--- a/main.c
+++ b/main.c
@@ -19,43 +19,74 @@ Distributed under the GPL
 #include <unistd.h>
 #include <sys/time.h>
 #include <string.h>
+#include <pcap/pcap.h>
+#include <netinet/ether.h>
+#include <netinet/ip6.h>
 
-unsigned long long read_number(const char *);
+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
+{
+       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 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 *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 next_stats = 0;
+       Monitor monitor;
+       Pinger pinger;
        int o;
        char *endp;
-       int no_daemon = 0;
-       int verbose = 0;
-       unsigned stats_interval = 1800;
-       unsigned trigger_delay = 60;
-       unsigned trigger_interval = 600;
+       Time stats_interval = 1800000000ULL;
+       Time trigger_delay = 60000000ULL;
+       Time trigger_interval = 600000000ULL;
        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;
+
+       no_daemon = 0;
+       verbose = 0;
+       monitor.interface_name = NULL;
+       pinger.target_name = NULL;
 
        /* Parse options */
        while((o = getopt(argc, argv, "fvs:t:c:i:p:"))!=-1)
@@ -68,12 +99,12 @@ int main(int argc, char **argv)
                        verbose = 1;
                        break;
                case 's':
-                       stats_interval = strtoul(optarg, NULL, 10);
+                       stats_interval = strtoul(optarg, NULL, 10)*1000000;
                        break;
                case 't':
-                       trigger_delay = strtoul(optarg, &endp, 10);
+                       trigger_delay = strtoul(optarg, &endp, 10)*1000000;
                        if(*endp==',')
-                               trigger_interval = strtoul(endp+1, NULL, 10);
+                               trigger_interval = strtoul(endp+1, NULL, 10)*1000000;
                        else
                                trigger_interval = trigger_delay;
                        break;
@@ -81,90 +112,38 @@ int main(int argc, char **argv)
                        trigger_cmd = optarg;
                        break;
                case 'i':
-                       interface = optarg;
+                       monitor.interface_name = strdup(optarg);
                        break;
                case 'p':
-                       ping_target_name = optarg;
+                       pinger.target_name = strdup(optarg);
                        break;
                }
 
-       if(ping_target_name)
-       {
-               struct hostent *ping_target_host;
-
-               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("Ping target is %s\n", inet_ntoa(*(struct in_addr *)*ping_target_host->h_addr_list));
-       }
-
-       /* Miscellaneous initialization */
-       pid = getpid();
-       sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
-       if(sock==-1)
-       {
-               perror("socket");
-               return 1;
-       }
+       if(monitor.interface_name)
+               monitor_init(&monitor);
 
-       pfd.fd = sock;
-       pfd.events = POLLIN;
+       if(pinger.target_name)
+               pinger_init(&pinger);
 
        if(!no_daemon && daemon(1, 0)==-1)
                perror("daemon");
 
        openlog("netmon", 0, LOG_LOCAL7);
 
-       next_trigger = trigger_delay;
+       Time next_stats = current_time()+stats_interval;
+       Time next_trigger = trigger_delay;
+       pid_t child_pid = 0;
        while(1)
        {
-               struct timeval tv;
-               int res;
-               char fnbuf[256];
-               unsigned long long count;
-
-               gettimeofday(&tv, NULL);
-
-               /* 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;
+               Time time = current_time();
 
-               /* If packets have been transmitted more recently than received, there
-               might be a problem */
-               if(last_transmit>last_receive)
+               if(monitor.interface_name)
                {
-                       if(connection_status && tv.tv_sec>=last_receive+10)
-                       {
-                               connection_status = 0;
-                               connection_down_time = last_receive;
-                               if(no_daemon)
-                                       printf("Connection is down\n");
-                               else
-                                       syslog(LOG_INFO, "Connection is down");
-                       }
+                       monitor_check(&monitor, time);
 
-                       if(trigger_cmd && tv.tv_sec>last_receive+next_trigger)
+                       if(monitor.connection_status)
+                               next_trigger = trigger_delay;
+                       else if(time>=monitor.last_receive+next_trigger)
                        {
                                if(no_daemon)
                                        printf("Running %s\n", trigger_cmd);
@@ -174,53 +153,25 @@ int main(int argc, char **argv)
                                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)
+               if(pinger.target_name)
                {
-                       /* 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;
-                       }
+                       pinger_check(&pinger, time);
 
-                       if(tv.tv_sec>=next_stats)
+                       if(time>=next_stats)
                        {
-                               if(lost)
+                               if(pinger.count)
                                {
-                                       float loss_ratio = (float)lost/ping_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);
-
-                                       ping_count = 0;
-                                       lost = 0;
                                }
 
-                               next_stats = tv.tv_sec+stats_interval;
+                               pinger.count = 0;
+                               pinger.lost = 0;
+                               next_stats += stats_interval;
                        }
                }
 
@@ -230,73 +181,281 @@ int main(int argc, char **argv)
                        if(waitpid(child_pid, NULL, WNOHANG)==child_pid)
                                child_pid = 0;
                }
+       }
+
+       closelog();
+
+       return 0;
+}
+
+Time current_time()
+{
+       struct timeval tv;
+       gettimeofday(&tv, NULL);
+       return tv.tv_sec*1000000ULL+tv.tv_usec;
+}
+
+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;
+       }
 
-               res = poll(&pfd, 1, 1000);
-               if(res>0)
+       if(pcap_setnonblock(monitor->pcap, 1, err)==-1)
+       {
+               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);
+
+       monitor->last_receive = 0;
+       monitor->last_transmit = 0;
+       monitor->connection_status = 1;
+
+       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(monitor->connection_status && timestamp>=monitor->last_receive+10000000)
+                       {
+                               monitor->connection_status = 0;
+                               monitor->connection_down_time = monitor->last_receive;
+                               if(no_daemon)
+                                       printf("Connection is down\n");
+                               else
+                                       syslog(LOG_INFO, "Connection is down");
+                       }
+               }
+               else if(!monitor->connection_status)
                {
-                       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));
+                       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);
+               }
+       }
+}
+
+void capture_handler(uint8_t *user, const struct pcap_pkthdr *header, const uint8_t *data)
+{
+       Monitor *monitor = (Monitor *)user;
+       const struct ethhdr *eth = (const struct ethhdr *)(data);
+
+       int src_scope = 0;
+       int dst_scope = 0;
+       int proto = ntohs(eth->h_proto);
+       if(proto==ETH_P_IP)
+       {
+               const struct iphdr *ip = (const struct iphdr *)(eth+1);
+               if(ntohl(ip->daddr)>>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));
-                                               if(icmp->un.echo.sequence==pending)
-                                                       pending = 0;
-                                               else if(verbose)
-                                                       printf("Sequence %d, expected %d\n", icmp->un.echo.sequence, pending);
-                                       }
-                               }
+                               int s = get_inet_scope(ip->saddr, &monitor->addresses[i]);
+                               if(s>src_scope)
+                                       src_scope = s;
+
+                               s = get_inet_scope(ip->daddr, &monitor->addresses[i]);
+                               if(s>dst_scope)
+                                       dst_scope = s;
+                       }
+       }
+       else if(proto==ETH_P_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 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;
                }
        }
 
-       closelog();
+       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");
+               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;
 }
 
-unsigned long long read_number(const char *fn)
+void pinger_check(Pinger *pinger, Time time)
 {
-       char buf[64];
-       int fd;
-       int len;
-
-       fd = open(fn, O_RDONLY);
-       if(fd<0)
-               return 0;
+       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
+               {
+                       struct iphdr *ip = (struct iphdr *)data;
+                       if(ip->protocol==IPPROTO_ICMP)
+                       {
+                               struct icmphdr *icmp = (struct icmphdr *)(ip+1);
+                               if(icmp->type==ICMP_ECHOREPLY && icmp->un.echo.id==pinger->id)
+                               {
+                                       /* 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);
+                                       }
 
-       len = read(fd, buf, sizeof(buf));
-       close(fd);
-       if(len<0)
-               return 0;
+                                       if(icmp->un.echo.sequence==pinger->pending)
+                                               pinger->pending = 0;
+                                       else if(verbose)
+                                               printf("Sequence %d, expected %d\n", icmp->un.echo.sequence, pinger->pending);
+                               }
+                       }
+               }
+       }
 
-       buf[len] = 0;
+       if(time>=pinger->next)
+       {
+               if(pinger->pending)
+               {
+                       ++pinger->lost;
+                       if(verbose)
+                               printf("Lost ping\n");
+               }
 
-       return strtoull(buf, NULL, 10);
+               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);
@@ -304,30 +463,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;
+       struct icmphdr *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->un.echo.id = pinger->id;
+       hdr->un.echo.sequence = pinger->seq;
        hdr->checksum = checksum(data, sizeof(data));
 
-       addr.sin_family = AF_INET;
-       addr.sin_addr.s_addr = 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)