root/src/modules/ping_icmp.c

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

support 'no check' on console to delete checks as well as 'reload' on console (currently only reloads checks)

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