root/src/modules/ping_icmp.c

Revision fb90c74b988a187602cf743f22813de38cb8dd94, 14.6 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 6 years ago)

change detection and keeping old status

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  */
5
6 #include "noit_defines.h"
7
8 #include <stdio.h>
9 #include <unistd.h>
10 #include <netdb.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <sys/ioctl.h>
14 #include <netinet/in.h>
15 #include <netinet/ip.h>
16 #include <netinet/ip_icmp.h>
17 #include <math.h>
18
19 #include "noit_module.h"
20 #include "noit_poller.h"
21 #include "utils/noit_log.h"
22
23 #define PING_INTERVAL 2000 /* 2000ms = 2s */
24 #define PING_COUNT    5
25
26 struct check_info {
27   int check_no;
28   int check_seq_no;
29   int seq;
30   int expected_count;
31   float *turnaround;
32   eventer_t timeout_event;
33 };
34 struct ping_payload {
35   uuid_t checkid;
36   struct timeval whence;
37   int    check_no;
38   int    check_pack_no;
39   int    check_pack_cnt;
40 };
41 struct ping_closure {
42   noit_module_t *self;
43   noit_check_t check;
44   void *payload;
45   int payload_len;
46 };
47 static noit_log_stream_t nlerr = NULL;
48 static noit_log_stream_t nldeb = NULL;
49 static int ping_icmp_recur_handler(eventer_t e, int mask, void *closure,
50                                    struct timeval *now);
51 static int in_cksum(u_short *addr, int len);
52
53 typedef struct  {
54   int ipv4_fd;
55   int ipv6_fd;
56 } ping_icmp_data_t;
57
58 static int ping_icmp_onload(noit_module_t *self) {
59   nlerr = noit_log_stream_find("error/ping_icmp");
60   nldeb = noit_log_stream_find("debug/ping_icmp");
61   if(!nlerr) nlerr = noit_stderr;
62   if(!nldeb) nldeb = noit_debug;
63   return 0;
64 }
65 static int ping_icmp_config(noit_module_t *self, noit_hash_table *options) {
66   return 0;
67 }
68 static int ping_icmp_is_complete(noit_module_t *self, noit_check_t check) {
69   int i;
70   struct check_info *data;
71   data = (struct check_info *)check->closure;
72   for(i=0; i<data->expected_count; i++)
73     if(data->turnaround[i] == 0.0) {
74       noit_log(nldeb, NULL, "ping_icmp: %s %d is still outstanding.\n",
75                check->target, i);
76       return 0;
77     }
78   return 1;
79 }
80 static void ping_icmp_log_results(noit_module_t *self, noit_check_t check) {
81   struct check_info *data;
82   float avail, min = MAXFLOAT, max = 0.0, avg = 0.0, cnt;
83   int i, points = 0;
84   char human_buffer[256];
85   stats_t current;
86   struct timeval duration;
87
88   data = (struct check_info *)check->closure;
89   for(i=0; i<data->expected_count; i++) {
90     if(data->turnaround[i] != 0) {
91       points++;
92       avg += data->turnaround[i];
93       if(data->turnaround[i] > max) max = data->turnaround[i];
94       if(data->turnaround[i] < min) min = data->turnaround[i];
95     }
96   }
97   if(points == 0) {
98     min = 0.0 / 0.0;
99     max = 0.0 / 0.0;
100   }
101   cnt = data->expected_count;
102   avail = (float)points /cnt;
103   avg /= (float)points;
104
105   snprintf(human_buffer, sizeof(human_buffer),
106            "cnt=%d,avail=%0.0f,min=%0.4f,max=%0.4f,avg=%0.4f",
107            (int)cnt, 100.0*avail, min, max, avg);
108   noit_log(nldeb, NULL, "ping_icmp(%s) [%s]\n", check->target, human_buffer);
109
110   gettimeofday(&current.whence, NULL);
111   sub_timeval(current.whence, check->last_fire_time, &duration);
112   current.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
113   current.available = (avail > 0.0) ? NP_AVAILABLE : NP_UNAVAILABLE;
114   current.state = (avail < 1.0) ? NP_BAD : NP_GOOD;
115   current.status = human_buffer;
116   noit_poller_set_state(check, &current);
117 }
118 static int ping_icmp_timeout(eventer_t e, int mask,
119                              void *closure, struct timeval *now) {
120   struct ping_closure *pcl = (struct ping_closure *)closure;
121   struct check_info *data;
122   ping_icmp_log_results(pcl->self, pcl->check);
123   data = (struct check_info *)pcl->check->closure;
124   data->timeout_event = NULL;
125   pcl->check->flags &= ~NP_RUNNING;
126   free(pcl);
127   return 0;
128 }
129 static int ping_icmp_handler(eventer_t e, int mask,
130                              void *closure, struct timeval *now) {
131   noit_module_t *self = (noit_module_t *)closure;
132   struct check_info *data;
133   char packet[1500];
134   int packet_len = sizeof(packet);
135   union {
136    struct sockaddr_in  in4;
137    struct sockaddr_in6 in6;
138   } from;
139   unsigned int from_len;
140   struct ip *ip = (struct ip *)packet;;
141   struct icmp *icp;
142   struct ping_payload *payload;
143
144   while(1) {
145     int inlen, iphlen;
146     noit_check_t check;
147     struct timeval tt;
148
149     from_len = sizeof(from);
150
151     inlen = recvfrom(e->fd, packet, packet_len, 0,
152                      (struct sockaddr *)&from, &from_len);
153     gettimeofday(now, NULL); /* set it, as we care about accuracy */
154
155     if(inlen < 0) {
156       if(errno == EAGAIN || errno == EINTR) break;
157       noit_log(nlerr, now, "ping_icmp recvfrom: %s\n", strerror(errno));
158       break;
159     }
160     iphlen = ip->ip_hl << 2;
161     if((inlen-iphlen) != (sizeof(struct icmp)+sizeof(struct ping_payload))) {
162       noit_log(nlerr, now,
163                "ping_icmp bad size: %d+%d\n", iphlen, inlen-iphlen);
164       continue;
165     }
166     icp = (struct icmp *)(packet + iphlen);
167     payload = (struct ping_payload *)(icp + 1);
168     if(icp->icmp_type != ICMP_ECHOREPLY) {
169       continue;
170     }
171     if(icp->icmp_id != (((vpsized_uint)self) & 0xffff)) {
172       noit_log(nlerr, now,
173                "ping_icmp not sent from this instance (%d:%d) vs. %d\n",
174                icp->icmp_id, ntohs(icp->icmp_seq),
175                (((vpsized_uint)self) & 0xffff));
176       continue;
177     }
178     check = noit_poller_lookup(payload->checkid);
179     if(!check) {
180       char uuid_str[37];
181       uuid_unparse_lower(payload->checkid, uuid_str);
182       noit_log(nlerr, now,
183                "ping_icmp response for unknown check '%s'\n", uuid_str);
184       continue;
185     }
186     data = (struct check_info *)check->closure;
187
188     /* If there is no timeout_event, the check must have completed.
189      * We have nothing to do. */
190     if(!data->timeout_event) continue;
191
192     /* Sanity check the payload */
193     if(payload->check_no != data->check_no) continue;
194     if(payload->check_pack_cnt != data->expected_count) continue;
195     if(payload->check_pack_no < 0 ||
196        payload->check_pack_no >= data->expected_count) continue;
197
198     sub_timeval(*now, payload->whence, &tt);
199     data->turnaround[payload->check_pack_no] =
200       (float)tt.tv_sec + (float)tt.tv_usec / 1000000.0;
201     if(ping_icmp_is_complete(self, check)) {
202       ping_icmp_log_results(self, check);
203       eventer_remove(data->timeout_event);
204       free(data->timeout_event->closure);
205       eventer_free(data->timeout_event);
206       data->timeout_event = NULL;
207       check->flags &= ~NP_RUNNING;
208     }
209   }
210   return EVENTER_READ;
211 }
212
213 static int ping_icmp_init(noit_module_t *self) {
214   socklen_t on;
215   struct protoent *proto;
216   ping_icmp_data_t *data;
217
218   data = malloc(sizeof(*data));
219   data->ipv4_fd = data->ipv6_fd = -1;
220
221   if ((proto = getprotobyname("icmp")) == NULL) {
222     noit_log(nlerr, NULL, "Couldn't find 'icmp' protocol\n");
223     return -1;
224   }
225
226   data->ipv4_fd = socket(AF_INET, SOCK_RAW, proto->p_proto);
227   if(data->ipv4_fd < 0) {
228     noit_log(nlerr, NULL, "ping_icmp: socket failed: %s\n",
229              strerror(errno));
230   }
231   else {
232     on = 1;
233     if(ioctl(data->ipv4_fd, FIONBIO, &on)) {
234       close(data->ipv4_fd);
235       data->ipv4_fd = -1;
236       noit_log(nlerr, NULL,
237                "ping_icmp: could not set socket non-blocking: %s\n",
238                strerror(errno));
239     }
240   }
241   if(data->ipv4_fd >= 0) {
242     eventer_t newe;
243     newe = eventer_alloc();
244     newe->fd = data->ipv4_fd;
245     newe->mask = EVENTER_READ;
246     newe->callback = ping_icmp_handler;
247     newe->closure = self;
248     eventer_add(newe);
249   }
250
251   data->ipv6_fd = socket(AF_INET6, SOCK_RAW, proto->p_proto);
252   if(data->ipv6_fd < 0) {
253     noit_log(nlerr, NULL, "ping_icmp: socket failed: %s\n",
254              strerror(errno));
255   }
256   else {
257     on = 1;
258     if(ioctl(data->ipv6_fd, FIONBIO, &on)) {
259       close(data->ipv6_fd);
260       data->ipv6_fd = -1;
261       noit_log(nlerr, NULL,
262                "ping_icmp: could not set socket non-blocking: %s\n",
263                strerror(errno));
264     }
265   }
266   if(data->ipv6_fd >= 0) {
267     eventer_t newe;
268     newe = eventer_alloc();
269     newe->fd = data->ipv6_fd;
270     newe->mask = EVENTER_READ;
271     newe->callback = ping_icmp_handler;
272     newe->closure = self;
273     eventer_add(newe);
274   }
275
276   noit_module_set_userdata(self, data);
277   return 0;
278 }
279
280 static int ping_icmp_real_send(eventer_t e, int mask,
281                                void *closure, struct timeval *now) {
282   struct ping_closure *pcl = (struct ping_closure *)closure;
283   struct icmp *icp;
284   struct ping_payload *payload;
285   ping_icmp_data_t *data;
286   int i;
287
288   noit_log(nldeb, NULL, "ping_icmp_real_send(%s)\n", pcl->check->target);
289   data = noit_module_get_userdata(pcl->self);
290   icp = (struct icmp *)pcl->payload;
291   payload = (struct ping_payload *)(icp + 1);
292   gettimeofday(&payload->whence, NULL); /* now isn't accurate enough */
293   icp->icmp_cksum = in_cksum(pcl->payload, pcl->payload_len);
294   if(pcl->check->target_family == AF_INET) {
295     struct sockaddr_in sin;
296     memset(&sin, 0, sizeof(sin));
297     sin.sin_family = AF_INET;
298     memcpy(&sin.sin_addr,
299            &pcl->check->target_addr.addr, sizeof(sin.sin_addr));
300     i = sendto(data->ipv4_fd,
301                pcl->payload, pcl->payload_len, 0,
302                (struct sockaddr *)&sin, sizeof(sin));
303   }
304   else {
305     struct sockaddr_in6 sin;
306     memset(&sin, 0, sizeof(sin));
307     sin.sin6_family = AF_INET6;
308     memcpy(&sin.sin6_addr,
309            &pcl->check->target_addr.addr6, sizeof(sin.sin6_addr));
310     i = sendto(data->ipv6_fd,
311                pcl->payload, pcl->payload_len, 0,
312                (struct sockaddr *)&sin, sizeof(sin));
313   }
314   if(i != pcl->payload_len) {
315     noit_log(nlerr, now, "Error sending ICMP packet to %s: %s\n",
316              pcl->check->target, strerror(errno));
317   }
318   free(pcl->payload);
319   free(pcl);
320   return 0;
321 }
322 static int ping_icmp_send(noit_module_t *self, noit_check_t check,
323                           int interval, int count) {
324   struct timeval when, p_int;
325   struct icmp *icp;
326   struct ping_payload *payload;
327   struct ping_closure *pcl;
328   struct check_info *ci = (struct check_info *)check->closure;
329   int packet_len, i;
330   eventer_t newe;
331
332   check->flags |= NP_RUNNING;
333   noit_log(nldeb, NULL, "ping_icmp_send(%p,%s,%d,%d)\n",
334            self, check->target, interval, count);
335
336   /* remove a timeout if we still have one -- we should unless someone
337    * has set a lower timeout than the period.
338    */
339   if(ci->timeout_event) {
340     eventer_remove(ci->timeout_event);
341     free(ci->timeout_event->closure);
342     eventer_free(ci->timeout_event);
343     ci->timeout_event = NULL;
344   }
345
346   gettimeofday(&when, NULL);
347   memcpy(&check->last_fire_time, &when, sizeof(when));
348
349   /* Setup some stuff used in the loop */
350   p_int.tv_sec = interval / 1000;
351   p_int.tv_usec = (interval % 1000) * 1000;
352   packet_len = sizeof(*icp) + sizeof(*payload);
353
354   /* Prep holding spots for return info */
355   ci->expected_count = count;
356   if(ci->turnaround) free(ci->turnaround);
357   ci->turnaround = calloc(count, sizeof(*ci->turnaround));
358
359   ++ci->check_no;
360   for(i=0; i<count; i++) {
361     newe = eventer_alloc();
362     newe->callback = ping_icmp_real_send;
363     newe->mask = EVENTER_TIMER;
364     memcpy(&newe->whence, &when, sizeof(when));
365     add_timeval(when, p_int, &when); /* Next one is a bit later */
366
367     icp = malloc(packet_len);
368     payload = (struct ping_payload *)(icp + 1);
369
370     icp->icmp_type = ICMP_ECHO;
371     icp->icmp_code = 0;
372     icp->icmp_cksum = 0;
373     icp->icmp_seq = htons(ci->seq++);
374     icp->icmp_id = (((vpsized_uint)self) & 0xffff);
375
376     uuid_copy(payload->checkid, check->checkid);
377     payload->check_no = ci->check_no;
378     payload->check_pack_no = i;
379     payload->check_pack_cnt = count;
380
381     pcl = calloc(1, sizeof(*pcl));
382     pcl->self = self;
383     pcl->check = check;
384     pcl->payload = icp;
385     pcl->payload_len = packet_len;
386
387     newe->closure = pcl;
388     eventer_add(newe);
389   }
390   newe = eventer_alloc();
391   newe->mask = EVENTER_TIMER;
392   gettimeofday(&when, NULL);
393   p_int.tv_sec = check->timeout / 1000;
394   p_int.tv_usec = (check->timeout % 1000) * 1000;
395   add_timeval(when, p_int, &newe->whence);
396   pcl = calloc(1, sizeof(*pcl));
397   pcl->self = self;
398   pcl->check = check;
399   newe->closure = pcl;
400   newe->callback = ping_icmp_timeout;
401   eventer_add(newe);
402   ci->timeout_event = newe;
403
404   return 0;
405 }
406 static int ping_icmp_schedule_next(noit_module_t *self,
407                                    eventer_t e, noit_check_t check,
408                                    struct timeval *now) {
409   eventer_t newe;
410   struct timeval last_check = { 0L, 0L };
411   struct timeval period, earliest;
412   struct ping_closure *pcl;
413
414   /* If we have an event, we know when we intended it to fire.  This means
415    * we should schedule that point + period.
416    */
417   if(now)
418     memcpy(&earliest, now, sizeof(earliest));
419   else
420     gettimeofday(&earliest, NULL);
421   if(e) memcpy(&last_check, &e->whence, sizeof(last_check));
422   period.tv_sec = check->period / 1000;
423   period.tv_usec = (check->period % 1000) * 1000;
424
425   newe = eventer_alloc();
426   memcpy(&newe->whence, &last_check, sizeof(last_check));
427   add_timeval(newe->whence, period, &newe->whence);
428   if(compare_timeval(newe->whence, earliest) < 0)
429     memcpy(&newe->whence, &earliest, sizeof(earliest));
430   newe->mask = EVENTER_TIMER;
431   newe->callback = ping_icmp_recur_handler;
432   pcl = calloc(1, sizeof(*pcl));
433   pcl->self = self;
434   pcl->check = check;
435   newe->closure = pcl;
436
437   eventer_add(newe);
438   check->fire_event = newe;
439   return 0;
440 }
441 static int ping_icmp_recur_handler(eventer_t e, int mask, void *closure,
442                                    struct timeval *now) {
443   struct ping_closure *cl = (struct ping_closure *)closure;
444   ping_icmp_schedule_next(cl->self, e, cl->check, now);
445   ping_icmp_send(cl->self, cl->check, PING_INTERVAL, PING_COUNT);
446   free(cl);
447   return 0;
448 }
449 static int ping_icmp_initiate_check(noit_module_t *self, noit_check_t check) {
450   check->closure = calloc(1, sizeof(struct check_info));
451   ping_icmp_schedule_next(self, NULL, check, NULL);
452   return 0;
453 }
454
455 /*
456  *      I N _ C K S U M
457  *          This is from Mike Muuss's Public Domain code.
458  * Checksum routine for Internet Protocol family headers (C Version)
459  *
460  */
461 static int in_cksum(u_short *addr, int len)
462 {
463   register int nleft = len;
464   register u_short *w = addr;
465   register u_short answer;
466   register int sum = 0;
467
468   /*
469    *  Our algorithm is simple, using a 32 bit accumulator (sum),
470    *  we add sequential 16 bit words to it, and at the end, fold
471    *  back all the carry bits from the top 16 bits into the lower
472    *  16 bits.
473    */
474   while( nleft > 1 )  {
475     sum += *w++;
476     nleft -= 2;
477   }
478
479   /* mop up an odd byte, if necessary */
480   if( nleft == 1 ) {
481     u_short  u = 0;
482
483     *(u_char *)(&u) = *(u_char *)w ;
484     sum += u;
485   }
486
487   /*
488    * add back carry outs from top 16 bits to low 16 bits
489    */
490   sum = (sum >> 16) + (sum & 0xffff);  /* add hi 16 to low 16 */
491   sum += (sum >> 16);      /* add carry */
492   answer = ~sum;        /* truncate to 16 bits */
493   return (answer);
494 }
495
496 noit_module_t ping_icmp = {
497   NOIT_MODULE_MAGIC,
498   NOIT_MODULE_ABI_VERSION,
499   "ping_icmp",
500   "ICMP based host availability detection",
501   ping_icmp_onload,
502   ping_icmp_config,
503   ping_icmp_init,
504   ping_icmp_initiate_check
505 };
506
Note: See TracBrowser for help on using the browser.