root/src/modules/ping_icmp.c

Revision fd01712f1a1ee5b1313098087a6614adff90574d, 18.5 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 5 years ago)

fixes #190

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