]> git.tdb.fi Git - netmon.git/blob - main.c
Bail if an error occurs on startup
[netmon.git] / main.c
1 /*
2 netmon - a simple network connectivity monitor
3 Copyright © 2008-2016 Mikko Rasa, Mikkosoft Productions
4 Distributed under the GPL
5 */
6
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <unistd.h>
12 #include <errno.h>
13 #include <syslog.h>
14 #include <sys/poll.h>
15 #include <sys/socket.h>
16 #include <sys/time.h>
17 #include <sys/wait.h>
18 #include <net/ethernet.h>
19 #include <netinet/in.h>
20 #include <netinet/ip.h>
21 #include <netinet/ip_icmp.h>
22 #include <netinet/ip6.h>
23 #include <netdb.h>
24 #include <arpa/inet.h>
25 #include <pcap/pcap.h>
26
27 typedef unsigned long long Time;
28
29 typedef struct
30 {
31         struct sockaddr_storage address;
32         struct sockaddr_storage mask;
33 } Address;
34
35 typedef struct
36 {
37         char *interface_name;
38         pcap_t *pcap;
39         unsigned n_addresses;
40         Address *addresses;
41         Time last_receive;
42         Time last_transmit;
43         int connection_status;
44         Time connection_down_time;
45 } Monitor;
46
47 typedef struct
48 {
49         Monitor *monitor;
50         Time delay;
51         Time interval;
52         const char *command;
53         Time next;
54         pid_t child_pid;
55 } Trigger;
56
57 typedef struct
58 {
59         const char *target_name;
60         struct sockaddr_in target_addr;
61         int socket;
62         struct pollfd pfd;
63         Time next;
64         uint16_t id;
65         uint16_t seq;
66         uint16_t pending;
67         unsigned count;
68         unsigned lost;
69 } Pinger;
70
71 Time current_time();
72 int monitor_init(Monitor *);
73 void monitor_check(Monitor *, Time);
74 void capture_handler(uint8_t *, const struct pcap_pkthdr *, const uint8_t *);
75 int trigger_init(Trigger *);
76 void trigger_check(Trigger *, Time);
77 int get_inet_scope(uint32_t, const Address *);
78 int get_inet6_scope(const struct in6_addr *, const Address *);
79 int pinger_init(Pinger *);
80 void pinger_check(Pinger *, Time);
81 unsigned checksum(const char *, unsigned);
82 void send_ping(Pinger *);
83 pid_t run_command(const char *);
84
85 int no_daemon;
86 int verbose;
87
88 int main(int argc, char **argv)
89 {
90         Monitor monitor;
91         Trigger trigger;
92         Pinger pinger;
93         int o;
94         char *endp;
95         Time stats_interval = 1800000000ULL;
96
97         no_daemon = 0;
98         verbose = 0;
99         monitor.interface_name = NULL;
100         trigger.monitor = &monitor;
101         trigger.delay = 60000000ULL;
102         trigger.interval = 600000000ULL;
103         pinger.target_name = NULL;
104
105         /* Parse options */
106         while((o = getopt(argc, argv, "fvs:t:c:i:p:"))!=-1)
107                 switch(o)
108                 {
109                 case 'f':
110                         no_daemon = 1;
111                         break;
112                 case 'v':
113                         verbose = 1;
114                         break;
115                 case 's':
116                         stats_interval = strtoul(optarg, NULL, 10)*1000000;
117                         break;
118                 case 't':
119                         trigger.delay = strtoul(optarg, &endp, 10)*1000000;
120                         if(*endp==',')
121                                 trigger.interval = strtoul(endp+1, NULL, 10)*1000000;
122                         else
123                                 trigger.interval = trigger.delay;
124                         break;
125                 case 'c':
126                         trigger.command = optarg;
127                         break;
128                 case 'i':
129                         monitor.interface_name = strdup(optarg);
130                         break;
131                 case 'p':
132                         pinger.target_name = strdup(optarg);
133                         break;
134                 }
135
136         if(monitor.interface_name)
137         {
138                 if(monitor_init(&monitor))
139                         return 1;
140
141                 if(trigger.command)
142                         if(trigger_init(&trigger))
143                                 return 1;
144         }
145
146         if(pinger.target_name)
147                 if(pinger_init(&pinger))
148                         return 1;
149
150         if(!no_daemon && daemon(1, 0)==-1)
151         {
152                 perror("daemon");
153                 return 1;
154         }
155
156         openlog("netmon", 0, LOG_LOCAL7);
157
158         Time next_stats = current_time()+stats_interval;
159         while(1)
160         {
161                 Time time = current_time();
162
163                 if(monitor.interface_name)
164                 {
165                         monitor_check(&monitor, time);
166
167                         if(trigger.command)
168                                 trigger_check(&trigger, time);
169                 }
170
171                 if(pinger.target_name)
172                 {
173                         pinger_check(&pinger, time);
174
175                         if(time>=next_stats)
176                         {
177                                 if(pinger.count)
178                                 {
179                                         float loss_ratio = (float)pinger.lost/pinger.count;
180                                         if(no_daemon)
181                                                 printf("Packet loss: %.2f%%\n", loss_ratio*100);
182                                         else
183                                                 syslog(LOG_INFO, "Packet loss: %.2f%%", loss_ratio*100);
184                                 }
185
186                                 pinger.count = 0;
187                                 pinger.lost = 0;
188                                 next_stats += stats_interval;
189                         }
190                 }
191         }
192
193         closelog();
194
195         return 0;
196 }
197
198 Time current_time()
199 {
200         struct timeval tv;
201         gettimeofday(&tv, NULL);
202         return tv.tv_sec*1000000ULL+tv.tv_usec;
203 }
204
205 int monitor_init(Monitor *monitor)
206 {
207         char err[PCAP_ERRBUF_SIZE];
208         pcap_if_t *devs;
209         if(pcap_findalldevs(&devs, err)==-1)
210         {
211                 fprintf(stderr, "pcap_findalldevs: %s\n", err);
212                 return -1;
213         }
214
215         monitor->pcap = pcap_open_live(monitor->interface_name, 64, 0, 10, err);
216         if(!monitor->pcap)
217         {
218                 fprintf(stderr, "pcap_findalldevs: %s\n", err);
219                 return -1;
220         }
221
222         if(pcap_setnonblock(monitor->pcap, 1, err)==-1)
223         {
224                 fprintf(stderr, "pcap_findalldevs: %s\n", err);
225                 return -1;
226         }
227
228         monitor->n_addresses = 0;
229         monitor->addresses = NULL;
230         for(pcap_if_t *d=devs; d; d=d->next)
231                 for(pcap_addr_t *a=d->addresses; a; a=a->next)
232                         if(a->addr->sa_family==AF_INET || a->addr->sa_family==AF_INET6)
233                         {
234                                 monitor->addresses = (Address *)realloc(monitor->addresses, (monitor->n_addresses+1)*sizeof(Address));
235                                 unsigned size = (a->addr->sa_family==AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in));
236                                 memcpy(&monitor->addresses[monitor->n_addresses].address, a->addr, size);
237                                 memcpy(&monitor->addresses[monitor->n_addresses].mask, a->netmask, size);
238                                 ++monitor->n_addresses;
239                         }
240
241         pcap_freealldevs(devs);
242
243         monitor->last_receive = 0;
244         monitor->last_transmit = 0;
245         monitor->connection_status = 1;
246
247         return 0;
248 }
249
250 void monitor_check(Monitor *monitor, Time timestamp)
251 {
252         pcap_dispatch(monitor->pcap, -1, &capture_handler, (uint8_t *)monitor);
253
254         if(monitor->last_transmit && monitor->last_receive)
255         {
256                 /* If packets have been transmitted more recently than received, there
257                 might be a problem */
258                 if(monitor->last_transmit>monitor->last_receive)
259                 {
260                         if(monitor->connection_status && timestamp>=monitor->last_receive+10000000)
261                         {
262                                 monitor->connection_status = 0;
263                                 monitor->connection_down_time = monitor->last_receive;
264                                 if(no_daemon)
265                                         printf("Connection is down\n");
266                                 else
267                                         syslog(LOG_INFO, "Connection is down");
268                         }
269                 }
270                 else if(!monitor->connection_status)
271                 {
272                         Time duration = timestamp-monitor->connection_down_time;
273                         monitor->connection_status = 1;
274                         if(no_daemon)
275                                 printf("Connection is up (was down for %lld seconds)\n", duration/1000000);
276                         else
277                                 syslog(LOG_INFO, "Connection is up (was down for %lld seconds)", duration/1000000);
278                 }
279         }
280 }
281
282 void capture_handler(uint8_t *user, const struct pcap_pkthdr *header, const uint8_t *data)
283 {
284         Monitor *monitor = (Monitor *)user;
285         const struct ether_header *eth = (const struct ether_header *)(data);
286
287         int src_scope = 0;
288         int dst_scope = 0;
289         int proto = ntohs(eth->ether_type);
290         if(proto==ETHERTYPE_IP)
291         {
292                 const struct ip *ip = (const struct ip *)(eth+1);
293                 if(ntohl(ip->ip_dst.s_addr)>>28==14)
294                         dst_scope = 3;
295
296                 for(unsigned i=0; i<monitor->n_addresses; ++i)
297                         if(monitor->addresses[i].address.ss_family==AF_INET)
298                         {
299                                 int s = get_inet_scope(ip->ip_src.s_addr, &monitor->addresses[i]);
300                                 if(s>src_scope)
301                                         src_scope = s;
302
303                                 s = get_inet_scope(ip->ip_dst.s_addr, &monitor->addresses[i]);
304                                 if(s>dst_scope)
305                                         dst_scope = s;
306                         }
307         }
308         else if(proto==ETHERTYPE_IPV6)
309         {
310                 const struct ip6_hdr *ip6 = (const struct ip6_hdr *)(eth+1);
311                 for(unsigned i=0; i<monitor->n_addresses; ++i)
312                         if(monitor->addresses[i].address.ss_family==AF_INET6)
313                         {
314                                 int s = get_inet6_scope(&ip6->ip6_src, &monitor->addresses[i]);
315                                 if(s>src_scope)
316                                         src_scope = s;
317
318                                 s = get_inet6_scope(&ip6->ip6_dst, &monitor->addresses[i]);
319                                 if(s>dst_scope)
320                                         dst_scope = s;
321                         }
322         }
323
324         Time timestamp = header->ts.tv_sec*1000000ULL+header->ts.tv_usec;
325         if(src_scope==0 && dst_scope>0)
326                 monitor->last_receive = timestamp;
327         else if(src_scope>0 && dst_scope==0)
328                 monitor->last_transmit = timestamp;
329 }
330
331 int trigger_init(Trigger *trigger)
332 {
333         trigger->next = trigger->delay;
334         trigger->child_pid = 0;
335
336         return 0;
337 }
338
339 void trigger_check(Trigger *trigger, Time time)
340 {
341         Monitor *monitor = trigger->monitor;
342
343         if(monitor->connection_status)
344                 trigger->next = trigger->delay;
345         else if(time>=monitor->last_receive+trigger->next)
346         {
347                 if(no_daemon)
348                         printf("Running %s\n", trigger->command);
349                 else
350                         syslog(LOG_INFO, "Running %s", trigger->command);
351                 trigger->child_pid = run_command(trigger->command);
352                 trigger->next += trigger->interval;
353         }
354
355         /* Reap any finished child process */
356         if(trigger->child_pid)
357         {
358                 if(waitpid(trigger->child_pid, NULL, WNOHANG)==trigger->child_pid)
359                         trigger->child_pid = 0;
360         }
361 }
362
363 int get_inet_scope(uint32_t addr, const Address *local_addr)
364 {
365         uint32_t diff = addr^((struct sockaddr_in *)&local_addr->address)->sin_addr.s_addr;
366         if(!diff)
367                 return 2;
368         else if(!(diff&((struct sockaddr_in *)&local_addr->mask)->sin_addr.s_addr))
369                 return 1;
370         else
371                 return 0;
372 }
373
374 int get_inet6_scope(const struct in6_addr *addr, const Address *local_addr)
375 {
376         int result = 2;
377         for(unsigned i=0; i<16; ++i)
378         {
379                 uint8_t diff = addr->s6_addr[i]^((struct sockaddr_in6 *)&local_addr->address)->sin6_addr.s6_addr[i];
380                 if(diff)
381                 {
382                         diff &= ((struct sockaddr_in6 *)&local_addr->mask)->sin6_addr.s6_addr[i];
383                         if(diff)
384                                 return 0;
385                         else
386                                 result = 1;
387                 }
388         }
389
390         return result;
391 }
392
393 int pinger_init(Pinger *pinger)
394 {
395         if(!pinger->target_name)
396                 return -1;
397
398         struct hostent *host;
399
400         host = gethostbyname(pinger->target_name);
401         if(!host)
402         {
403                 herror("gethostbyname");
404                 return -1;
405         }
406
407         if(host->h_addrtype!=AF_INET)
408         {
409                 fprintf(stderr, "Got a hostent, but it doesn't have an IPv4 address");
410                 return -1;
411         }
412
413         pinger->target_addr.sin_family = AF_INET;
414         pinger->target_addr.sin_addr = *(struct in_addr *)host->h_addr_list[0];
415
416         if(verbose)
417         {
418                 char buf[64];
419                 inet_ntop(AF_INET, &pinger->target_addr.sin_addr, buf, sizeof(buf));
420                 printf("Ping target is %s\n", buf);
421         }
422
423         pinger->socket = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
424         if(pinger->socket==-1)
425         {
426                 perror("socket(SOCK_RAW)");
427                 return -1;
428         }
429
430         pinger->next = 0;
431         pinger->id = getpid();
432         pinger->seq = 1;
433         pinger->pending = 0;
434         pinger->count = 0;
435         pinger->lost = 0;
436
437         pinger->pfd.fd = pinger->socket;
438         pinger->pfd.events = POLLIN;
439
440         return 0;
441 }
442
443 void pinger_check(Pinger *pinger, Time time)
444 {
445         if(poll(&pinger->pfd, 1, 10)>0)
446         {
447                 struct sockaddr_in addr;
448                 socklen_t alen = sizeof(addr);
449
450                 /* Receive a packet */
451                 char data[1500];
452                 int len = recvfrom(pinger->socket, data, sizeof(data), 0, (struct sockaddr *)&addr, &alen);
453                 if(len==-1)
454                         fprintf(stderr, "recvfrom error: %s\n", strerror(errno));
455                 else
456                 {
457                         struct ip *ip = (struct ip *)data;
458                         if(ip->ip_p==IPPROTO_ICMP)
459                         {
460                                 struct icmp *icmp = (struct icmp *)(ip+1);
461                                 if(icmp->icmp_type==ICMP_ECHOREPLY && icmp->icmp_id==pinger->id)
462                                 {
463                                         /* It's an ICMP echo reply and ours, process it */
464                                         if(verbose)
465                                         {
466                                                 char buf[64];
467                                                 inet_ntop(AF_INET, &addr.sin_addr, buf, sizeof(buf));
468                                                 printf("Ping reply from %s\n", buf);
469                                         }
470
471                                         if(icmp->icmp_seq==pinger->pending)
472                                                 pinger->pending = 0;
473                                         else if(verbose)
474                                                 printf("Sequence %d, expected %d\n", icmp->icmp_seq, pinger->pending);
475                                 }
476                         }
477                 }
478         }
479
480         if(time>=pinger->next)
481         {
482                 if(pinger->pending)
483                 {
484                         ++pinger->lost;
485                         if(verbose)
486                                 printf("Lost ping\n");
487                 }
488
489                 send_ping(pinger);
490                 pinger->next = time+1000000;
491         }
492 }
493
494 unsigned checksum(const char *data, unsigned len)
495 {
496         unsigned        sum = 0;
497         for(unsigned i=0; i<len; i+=2)
498                 sum += *(const unsigned short *)(data+i);
499         while(sum>0xFFFF)
500                 sum = (sum>>16)+(sum&0xFFFF);
501
502         return ~sum;
503 }
504
505 void send_ping(Pinger *pinger)
506 {
507         char data[64];
508         for(unsigned i=0; i<sizeof(data); ++i)
509                 data[i] = i;
510
511         struct icmp *hdr = (struct icmp *)data;
512         hdr->icmp_type = ICMP_ECHO;
513         hdr->icmp_code = 0;
514         hdr->icmp_cksum = 0;
515         hdr->icmp_id = pinger->id;
516         hdr->icmp_seq = pinger->seq;
517         hdr->icmp_cksum = checksum(data, sizeof(data));
518
519         if(sendto(pinger->socket, data, sizeof(data), 0, (struct sockaddr *)&pinger->target_addr, sizeof(struct sockaddr_in))==-1)
520                 fprintf(stderr, "sendto error: %s\n", strerror(errno));
521
522         pinger->pending = pinger->seq++;
523         if(!pinger->seq)
524                 ++pinger->seq;
525         ++pinger->count;
526 }
527
528 pid_t run_command(const char *cmd)
529 {
530         pid_t pid;
531
532         pid = fork();
533         if(pid==0)
534         {
535                 const char *argv[2];
536                 argv[0] = cmd;
537                 argv[1] = 0;
538                 execv(cmd, (char *const *)argv);
539                 exit(127);
540         }
541         else
542                 return pid;
543 }