root/src/modules/ping_icmp.c

Revision c4546c715aee6db46888e7ab0bb87fd2ce5357c6, 15.3 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 7 years ago)

I sure hope this doesn't break other platforms too badly. Solaris support sans the eventer... untested, of course. refs #32.

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