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