root/src/modules/ping_icmp.c

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

more work... framework for submitting arbitrary metrics

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