root/src/modules/ping_icmp.c

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

I can ping things...whahahahahaha

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