root/src/modules/ping_icmp.c

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

I think this should solve the issue, needs testing #288

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