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