root/src/modules/ping_icmp.c

Revision 1c535919af2adec9a696426d4642f6b27bef51ac, 17.2 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 8 years ago)

Eventhough I can't repeat the problem, this fixes the reported issue. refs #132

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