#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)
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;
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);
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;
}
}
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);
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)