root/src/modules/ping_icmp.c

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

little bits of valgrind ecstasy

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