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