2 netmon - a simple network connectivity monitor
3 Copyright © 2008-2016 Mikko Rasa, Mikkosoft Productions
4 Distributed under the GPL
7 #define _DEFAULT_SOURCE
16 #include <sys/socket.h>
19 #include <net/ethernet.h>
20 #include <netinet/in.h>
21 #include <netinet/ip.h>
22 #include <netinet/ip_icmp.h>
23 #include <netinet/ip6.h>
25 #include <arpa/inet.h>
26 #include <pcap/pcap.h>
28 typedef unsigned long long Time;
32 struct sockaddr_storage address;
33 struct sockaddr_storage mask;
44 int connection_status;
45 Time connection_down_time;
60 const char *target_name;
61 struct sockaddr_in target_addr;
73 int monitor_init(Monitor *);
74 void monitor_check(Monitor *, Time);
75 void capture_handler(uint8_t *, const struct pcap_pkthdr *, const uint8_t *);
76 int trigger_init(Trigger *);
77 void trigger_check(Trigger *, Time);
78 int get_inet_scope(uint32_t, const Address *);
79 int get_inet6_scope(const struct in6_addr *, const Address *);
80 int pinger_init(Pinger *);
81 void pinger_check(Pinger *, Time);
82 unsigned checksum(const char *, unsigned);
83 void send_ping(Pinger *);
84 pid_t run_command(const char *);
89 int main(int argc, char **argv)
96 Time stats_interval = 1800000000ULL;
100 monitor.interface_name = NULL;
101 trigger.monitor = &monitor;
102 trigger.delay = 60000000ULL;
103 trigger.interval = 600000000ULL;
104 pinger.target_name = NULL;
107 while((o = getopt(argc, argv, "fvs:t:c:i:p:"))!=-1)
117 stats_interval = strtoul(optarg, NULL, 10)*1000000;
120 trigger.delay = strtoul(optarg, &endp, 10)*1000000;
122 trigger.interval = strtoul(endp+1, NULL, 10)*1000000;
124 trigger.interval = trigger.delay;
127 trigger.command = optarg;
130 monitor.interface_name = strdup(optarg);
133 pinger.target_name = strdup(optarg);
137 if(monitor.interface_name)
139 if(monitor_init(&monitor))
143 if(trigger_init(&trigger))
147 if(pinger.target_name)
148 if(pinger_init(&pinger))
151 if(!no_daemon && daemon(1, 0)==-1)
157 openlog("netmon", 0, LOG_LOCAL7);
159 Time next_stats = current_time()+stats_interval;
162 Time time = current_time();
164 if(monitor.interface_name)
166 monitor_check(&monitor, time);
169 trigger_check(&trigger, time);
172 if(pinger.target_name)
174 pinger_check(&pinger, time);
180 float loss_ratio = (float)pinger.lost/pinger.count;
182 printf("Packet loss: %.2f%%\n", loss_ratio*100);
184 syslog(LOG_INFO, "Packet loss: %.2f%%", loss_ratio*100);
189 next_stats += stats_interval;
202 gettimeofday(&tv, NULL);
203 return tv.tv_sec*1000000ULL+tv.tv_usec;
206 int monitor_init(Monitor *monitor)
208 char err[PCAP_ERRBUF_SIZE];
210 if(pcap_findalldevs(&devs, err)==-1)
212 fprintf(stderr, "pcap_findalldevs: %s\n", err);
216 monitor->pcap = pcap_open_live(monitor->interface_name, 64, 0, 10, err);
219 fprintf(stderr, "pcap_findalldevs: %s\n", err);
223 if(pcap_setnonblock(monitor->pcap, 1, err)==-1)
225 fprintf(stderr, "pcap_findalldevs: %s\n", err);
229 monitor->n_addresses = 0;
230 monitor->addresses = NULL;
231 for(pcap_if_t *d=devs; d; d=d->next)
232 for(pcap_addr_t *a=d->addresses; a; a=a->next)
233 if(a->addr->sa_family==AF_INET || a->addr->sa_family==AF_INET6)
235 monitor->addresses = (Address *)realloc(monitor->addresses, (monitor->n_addresses+1)*sizeof(Address));
236 unsigned size = (a->addr->sa_family==AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in));
237 memcpy(&monitor->addresses[monitor->n_addresses].address, a->addr, size);
238 memcpy(&monitor->addresses[monitor->n_addresses].mask, a->netmask, size);
239 ++monitor->n_addresses;
242 pcap_freealldevs(devs);
244 monitor->last_receive = 0;
245 monitor->last_transmit = 0;
246 monitor->connection_status = 1;
251 void monitor_check(Monitor *monitor, Time timestamp)
253 pcap_dispatch(monitor->pcap, -1, &capture_handler, (uint8_t *)monitor);
255 if(monitor->last_transmit && monitor->last_receive)
257 /* If packets have been transmitted more recently than received, there
258 might be a problem */
259 if(monitor->last_transmit>monitor->last_receive)
261 if(monitor->connection_status && timestamp>=monitor->last_receive+10000000)
263 monitor->connection_status = 0;
264 monitor->connection_down_time = monitor->last_receive;
266 printf("Connection is down\n");
268 syslog(LOG_INFO, "Connection is down");
271 else if(!monitor->connection_status)
273 Time duration = timestamp-monitor->connection_down_time;
274 monitor->connection_status = 1;
276 printf("Connection is up (was down for %lld seconds)\n", duration/1000000);
278 syslog(LOG_INFO, "Connection is up (was down for %lld seconds)", duration/1000000);
283 void capture_handler(uint8_t *user, const struct pcap_pkthdr *header, const uint8_t *data)
285 Monitor *monitor = (Monitor *)user;
286 const struct ether_header *eth = (const struct ether_header *)(data);
290 int proto = ntohs(eth->ether_type);
291 if(proto==ETHERTYPE_IP)
293 const struct ip *ip = (const struct ip *)(eth+1);
294 if(ntohl(ip->ip_dst.s_addr)>>28==14)
297 for(unsigned i=0; i<monitor->n_addresses; ++i)
298 if(monitor->addresses[i].address.ss_family==AF_INET)
300 int s = get_inet_scope(ip->ip_src.s_addr, &monitor->addresses[i]);
304 s = get_inet_scope(ip->ip_dst.s_addr, &monitor->addresses[i]);
309 else if(proto==ETHERTYPE_IPV6)
311 const struct ip6_hdr *ip6 = (const struct ip6_hdr *)(eth+1);
312 for(unsigned i=0; i<monitor->n_addresses; ++i)
313 if(monitor->addresses[i].address.ss_family==AF_INET6)
315 int s = get_inet6_scope(&ip6->ip6_src, &monitor->addresses[i]);
319 s = get_inet6_scope(&ip6->ip6_dst, &monitor->addresses[i]);
325 Time timestamp = header->ts.tv_sec*1000000ULL+header->ts.tv_usec;
326 if(src_scope==0 && dst_scope>0)
327 monitor->last_receive = timestamp;
328 else if(src_scope>0 && dst_scope==0)
329 monitor->last_transmit = timestamp;
332 int trigger_init(Trigger *trigger)
334 trigger->next = trigger->delay;
335 trigger->child_pid = 0;
340 void trigger_check(Trigger *trigger, Time time)
342 Monitor *monitor = trigger->monitor;
344 if(monitor->connection_status)
345 trigger->next = trigger->delay;
346 else if(time>=monitor->last_receive+trigger->next)
349 printf("Running %s\n", trigger->command);
351 syslog(LOG_INFO, "Running %s", trigger->command);
352 trigger->child_pid = run_command(trigger->command);
353 trigger->next += trigger->interval;
356 /* Reap any finished child process */
357 if(trigger->child_pid)
359 if(waitpid(trigger->child_pid, NULL, WNOHANG)==trigger->child_pid)
360 trigger->child_pid = 0;
364 int get_inet_scope(uint32_t addr, const Address *local_addr)
366 uint32_t diff = addr^((struct sockaddr_in *)&local_addr->address)->sin_addr.s_addr;
369 else if(!(diff&((struct sockaddr_in *)&local_addr->mask)->sin_addr.s_addr))
375 int get_inet6_scope(const struct in6_addr *addr, const Address *local_addr)
378 for(unsigned i=0; i<16; ++i)
380 uint8_t diff = addr->s6_addr[i]^((struct sockaddr_in6 *)&local_addr->address)->sin6_addr.s6_addr[i];
383 diff &= ((struct sockaddr_in6 *)&local_addr->mask)->sin6_addr.s6_addr[i];
394 int pinger_init(Pinger *pinger)
396 if(!pinger->target_name)
399 struct hostent *host;
401 host = gethostbyname(pinger->target_name);
404 herror("gethostbyname");
408 if(host->h_addrtype!=AF_INET)
410 fprintf(stderr, "Got a hostent, but it doesn't have an IPv4 address");
414 pinger->target_addr.sin_family = AF_INET;
415 pinger->target_addr.sin_addr = *(struct in_addr *)host->h_addr_list[0];
420 inet_ntop(AF_INET, &pinger->target_addr.sin_addr, buf, sizeof(buf));
421 printf("Ping target is %s\n", buf);
424 pinger->socket = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
425 if(pinger->socket==-1)
427 perror("socket(SOCK_RAW)");
432 pinger->id = getpid();
438 pinger->pfd.fd = pinger->socket;
439 pinger->pfd.events = POLLIN;
444 void pinger_check(Pinger *pinger, Time time)
446 if(poll(&pinger->pfd, 1, 10)>0)
448 struct sockaddr_in addr;
449 socklen_t alen = sizeof(addr);
451 /* Receive a packet */
453 int len = recvfrom(pinger->socket, data, sizeof(data), 0, (struct sockaddr *)&addr, &alen);
455 fprintf(stderr, "recvfrom error: %s\n", strerror(errno));
458 struct ip *ip = (struct ip *)data;
459 if(ip->ip_p==IPPROTO_ICMP)
461 struct icmp *icmp = (struct icmp *)(ip+1);
462 if(icmp->icmp_type==ICMP_ECHOREPLY && icmp->icmp_id==pinger->id)
464 /* It's an ICMP echo reply and ours, process it */
468 inet_ntop(AF_INET, &addr.sin_addr, buf, sizeof(buf));
469 printf("Ping reply from %s\n", buf);
472 if(icmp->icmp_seq==pinger->pending)
475 printf("Sequence %d, expected %d\n", icmp->icmp_seq, pinger->pending);
481 if(time>=pinger->next)
487 printf("Lost ping\n");
491 pinger->next = time+1000000;
495 unsigned checksum(const char *data, unsigned len)
498 for(unsigned i=0; i<len; i+=2)
499 sum += *(const unsigned short *)(data+i);
501 sum = (sum>>16)+(sum&0xFFFF);
506 void send_ping(Pinger *pinger)
509 for(unsigned i=0; i<sizeof(data); ++i)
512 struct icmp *hdr = (struct icmp *)data;
513 hdr->icmp_type = ICMP_ECHO;
516 hdr->icmp_id = pinger->id;
517 hdr->icmp_seq = pinger->seq;
518 hdr->icmp_cksum = checksum(data, sizeof(data));
520 if(sendto(pinger->socket, data, sizeof(data), 0, (struct sockaddr *)&pinger->target_addr, sizeof(struct sockaddr_in))==-1)
521 fprintf(stderr, "sendto error: %s\n", strerror(errno));
523 pinger->pending = pinger->seq++;
529 pid_t run_command(const char *cmd)
539 execv(cmd, (char *const *)argv);