]> git.tdb.fi Git - netmon.git/blob - main.c
Redesign the program to use network statistics
[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
23 unsigned long long read_number(const char *);
24 unsigned checksum(const char *, unsigned);
25 void send_ping(in_addr_t, uint16_t);
26 pid_t run_command(const char *);
27
28 int sock;
29 int pid;
30
31 int main(int argc, char **argv)
32 {
33         const char *ping_target_name = NULL;
34         in_addr_t ping_target = 0;
35         const char *interface = NULL;
36         struct pollfd pfd;
37         unsigned next_ping = 0;
38         uint16_t seq = 1;
39         uint16_t pending = 0;
40         unsigned ping_count = 0;
41         unsigned lost = 0;
42         unsigned next_stats = 0;
43         int o;
44         char *endp;
45         int no_daemon = 0;
46         int verbose = 0;
47         unsigned stats_interval = 1800;
48         unsigned trigger_delay = 60;
49         unsigned trigger_interval = 600;
50         const char *trigger_cmd = NULL;
51         unsigned next_trigger;
52         pid_t child_pid = 0;
53         unsigned long long rx_packets = 0;
54         unsigned long long tx_packets = 0;
55         unsigned last_receive = 0;
56         unsigned last_transmit = 0;
57         int connection_status = 1;
58         unsigned connection_down_time;
59
60         /* Parse options */
61         while((o = getopt(argc, argv, "fvs:t:c:i:p:"))!=-1)
62                 switch(o)
63                 {
64                 case 'f':
65                         no_daemon = 1;
66                         break;
67                 case 'v':
68                         verbose = 1;
69                         break;
70                 case 's':
71                         stats_interval = strtoul(optarg, NULL, 10);
72                         break;
73                 case 't':
74                         trigger_delay = strtoul(optarg, &endp, 10);
75                         if(*endp==',')
76                                 trigger_interval = strtoul(endp+1, NULL, 10);
77                         else
78                                 trigger_interval = trigger_delay;
79                         break;
80                 case 'c':
81                         trigger_cmd = optarg;
82                         break;
83                 case 'i':
84                         interface = optarg;
85                         break;
86                 case 'p':
87                         ping_target_name = optarg;
88                         break;
89                 }
90
91         if(ping_target_name)
92         {
93                 struct hostent *ping_target_host;
94
95                 ping_target_host = gethostbyname(ping_target_name);
96                 if(!ping_target_host)
97                 {
98                         herror("gethostbyname");
99                         return 1;
100                 }
101
102                 if(ping_target_host->h_addrtype!=AF_INET)
103                 {
104                         fprintf(stderr, "Got a hostent, but it doesn't have an IPv4 address");
105                         return 1;
106                 }
107                 ping_target = *(in_addr_t *)*ping_target_host->h_addr_list;
108
109                 if(verbose)
110                         printf("Ping target is %s\n", inet_ntoa(*(struct in_addr *)*ping_target_host->h_addr_list));
111         }
112
113         /* Miscellaneous initialization */
114         pid = getpid();
115         sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
116         if(sock==-1)
117         {
118                 perror("socket");
119                 return 1;
120         }
121
122         pfd.fd = sock;
123         pfd.events = POLLIN;
124
125         if(!no_daemon && daemon(1, 0)==-1)
126                 perror("daemon");
127
128         openlog("netmon", 0, LOG_LOCAL7);
129
130         next_trigger = trigger_delay;
131         while(1)
132         {
133                 struct timeval tv;
134                 int res;
135                 char fnbuf[256];
136                 unsigned long long count;
137
138                 gettimeofday(&tv, NULL);
139
140                 /* Read network statistics */
141                 snprintf(fnbuf, sizeof(fnbuf), "/sys/class/net/%s/statistics/rx_packets", interface);
142                 count = read_number(fnbuf);
143                 if(count>rx_packets)
144                         last_receive = tv.tv_sec;
145                 rx_packets = count;
146
147                 snprintf(fnbuf, sizeof(fnbuf), "/sys/class/net/%s/statistics/tx_packets", interface);
148                 count = read_number(fnbuf);
149                 if(count>tx_packets)
150                         last_transmit = tv.tv_sec;
151                 tx_packets = count;
152
153                 /* If packets have been transmitted more recently than received, there
154                 might be a problem */
155                 if(last_transmit>last_receive)
156                 {
157                         if(connection_status && tv.tv_sec>=last_receive+10)
158                         {
159                                 connection_status = 0;
160                                 connection_down_time = last_receive;
161                                 if(no_daemon)
162                                         printf("Connection is down\n");
163                                 else
164                                         syslog(LOG_INFO, "Connection is down");
165                         }
166
167                         if(trigger_cmd && tv.tv_sec>last_receive+next_trigger)
168                         {
169                                 if(no_daemon)
170                                         printf("Running %s\n", trigger_cmd);
171                                 else
172                                         syslog(LOG_INFO, "Running %s", trigger_cmd);
173                                 child_pid = run_command(trigger_cmd);
174                                 next_trigger += trigger_interval;
175                         }
176                 }
177                 else if(!connection_status)
178                 {
179                         unsigned duration = tv.tv_sec-connection_down_time;
180                         connection_status = 1;
181                         if(no_daemon)
182                                 printf("Connection is up (was down for %d seconds)\n", duration);
183                         else
184                                 syslog(LOG_INFO, "Connection is up (was down for %d seconds)", duration);
185
186                         next_trigger = trigger_delay;
187                 }
188
189                 if(ping_target)
190                 {
191                         /* Send ping packets to monitor packet loss */
192                         if(tv.tv_sec>=next_ping)
193                         {
194                                 if(pending)
195                                 {
196                                         ++lost;
197                                         if(verbose)
198                                                 printf("Lost ping\n");
199                                 }
200
201                                 send_ping(ping_target, seq);
202                                 pending = seq++;
203                                 if(!seq)
204                                         ++seq;
205                                 ++ping_count;
206                                 next_ping = tv.tv_sec+1;
207                         }
208
209                         if(tv.tv_sec>=next_stats)
210                         {
211                                 if(lost)
212                                 {
213                                         float loss_ratio = (float)lost/ping_count;
214                                         if(no_daemon)
215                                                 printf("Packet loss: %.2f%%\n", loss_ratio*100);
216                                         else
217                                                 syslog(LOG_INFO, "Packet loss: %.2f%%", loss_ratio*100);
218
219                                         ping_count = 0;
220                                         lost = 0;
221                                 }
222
223                                 next_stats = tv.tv_sec+stats_interval;
224                         }
225                 }
226
227                 /* Reap any finished child process */
228                 if(child_pid)
229                 {
230                         if(waitpid(child_pid, NULL, WNOHANG)==child_pid)
231                                 child_pid = 0;
232                 }
233
234                 res = poll(&pfd, 1, 1000);
235                 if(res>0)
236                 {
237                         struct sockaddr_in addr;
238                         socklen_t alen = sizeof(addr);
239                         char data[1500];
240                         int len;
241                         struct iphdr *ip;
242                         struct icmphdr *icmp;
243
244                         /* Receive a packet */
245                         len = recvfrom(sock, data, sizeof(data), 0, (struct sockaddr *)&addr, &alen);
246                         if(len==-1)
247                                 fprintf(stderr, "recvfrom error: %s\n", strerror(errno));
248                         else
249                         {
250                                 ip = (struct iphdr *)data;
251                                 if(ip->protocol==IPPROTO_ICMP)
252                                 {
253                                         icmp = (struct icmphdr *)(ip+1);
254                                         if(icmp->type==ICMP_ECHOREPLY && icmp->un.echo.id==pid)
255                                         {
256                                                 /* It's an ICMP echo reply and ours, process it */
257                                                 if(verbose)
258                                                         printf("Ping reply from %s\n", inet_ntoa(addr.sin_addr));
259                                                 if(icmp->un.echo.sequence==pending)
260                                                         pending = 0;
261                                                 else if(verbose)
262                                                         printf("Sequence %d, expected %d\n", icmp->un.echo.sequence, pending);
263                                         }
264                                 }
265                         }
266                 }
267         }
268
269         closelog();
270
271         return 0;
272 }
273
274 unsigned long long read_number(const char *fn)
275 {
276         char buf[64];
277         int fd;
278         int len;
279
280         fd = open(fn, O_RDONLY);
281         if(fd<0)
282                 return 0;
283
284         len = read(fd, buf, sizeof(buf));
285         close(fd);
286         if(len<0)
287                 return 0;
288
289         buf[len] = 0;
290
291         return strtoull(buf, NULL, 10);
292 }
293
294 unsigned checksum(const char *data, unsigned len)
295 {
296         unsigned        sum = 0;
297         unsigned i;
298
299         for(i=0; i<len; i+=2)
300                 sum += *(const unsigned short *)(data+i);
301         while(sum>0xFFFF)
302                 sum = (sum>>16)+(sum&0xFFFF);
303
304         return ~sum;
305 }
306
307 void send_ping(in_addr_t target, uint16_t seq)
308 {
309         char data[64];
310         unsigned i;
311         struct icmphdr *hdr;
312         struct sockaddr_in addr;
313
314         for(i=0; i<sizeof(data); ++i)
315                 data[i] = i;
316
317         hdr = (struct icmphdr *)data;
318         hdr->type = ICMP_ECHO;
319         hdr->code = 0;
320         hdr->checksum = 0;
321         hdr->un.echo.id = pid;
322         hdr->un.echo.sequence = seq;
323         hdr->checksum = checksum(data, sizeof(data));
324
325         addr.sin_family = AF_INET;
326         addr.sin_addr.s_addr = target;
327         addr.sin_port = 0;
328
329         if(sendto(sock, data, sizeof(data), 0, (struct sockaddr *)&addr, sizeof(addr))==-1)
330                 fprintf(stderr, "sendto error: %s\n", strerror(errno));
331 }
332
333 pid_t run_command(const char *cmd)
334 {
335         pid_t pid;
336
337         pid = fork();
338         if(pid==0)
339         {
340                 const char *argv[2];
341                 argv[0] = cmd;
342                 argv[1] = 0;
343                 execv(cmd, (char *const *)argv);
344                 exit(127);
345         }
346         else
347                 return pid;
348 }