225dfa760c39894a05edea00eb76347d2fa6a347
[collectd.git] / src / liboping / liboping.c
1 /**
2  * Object oriented C module to send ICMP and ICMPv6 `echo's.
3  * Copyright (C) 2006  Florian octo Forster <octo at verplant.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  */
19
20 #if HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #if STDC_HEADERS
25 # include <stdlib.h>
26 # include <stdio.h>
27 # include <string.h>
28 # include <errno.h>
29 # include <assert.h>
30 #else
31 # error "You don't have the standard C99 header files installed"
32 #endif /* STDC_HEADERS */
33
34 #if HAVE_UNISTD_H
35 # include <unistd.h>
36 #endif
37
38 #if HAVE_FCNTL_H
39 # include <fcntl.h>
40 #endif
41 #if HAVE_SYS_TYPES_H
42 # include <sys/types.h>
43 #endif
44 #if HAVE_SYS_STAT_H
45 # include <sys/stat.h>
46 #endif
47
48 #if TIME_WITH_SYS_TIME
49 # include <sys/time.h>
50 # include <time.h>
51 #endif
52
53 #if HAVE_SYS_SOCKET_H
54 # include <sys/socket.h>
55 #endif
56 #if HAVE_NETDB_H
57 # include <netdb.h>
58 #endif
59
60 #if HAVE_NETINET_IN_SYSTM_H
61 # include <netinet/in_systm.h>
62 #endif
63 #if HAVE_NETINET_IN_H
64 # include <netinet/in.h>
65 #endif
66 #if HAVE_NETINET_IP_H
67 # include <netinet/ip.h>
68 #endif
69 #if HAVE_NETINET_IP_ICMP_H
70 # include <netinet/ip_icmp.h>
71 #endif
72 #ifdef HAVE_NETINET_IP_VAR_H
73 # include <netinet/ip_var.h>
74 #endif
75 #if HAVE_NETINET_IP6_H
76 # include <netinet/ip6.h>
77 #endif
78 #if HAVE_NETINET_ICMP6_H
79 # include <netinet/icmp6.h>
80 #endif
81
82 #include "liboping.h"
83
84 #if DEBUG
85 # define dprintf(...) printf ("%s[%4i]: %-20s: ", __FILE__, __LINE__, __FUNCTION__); printf (__VA_ARGS__)
86 #else
87 # define dprintf(...) /**/
88 #endif
89
90 #define PING_ERRMSG_LEN 256
91
92 #define PING_DATA "Florian Forster <octo@verplant.org> http://verplant.org/"
93
94 struct pinghost
95 {
96         char                    *hostname;
97         struct sockaddr_storage *addr;
98         socklen_t                addrlen;
99         int                      addrfamily;
100         int                      fd;
101         int                      ident;
102         int                      sequence;
103         struct timeval          *timer;
104         double                   latency;
105
106         struct pinghost         *next;
107 };
108
109 struct pingobj
110 {
111         double      timeout;
112         int         ttl;
113         int         addrfamily;
114
115         char        errmsg[PING_ERRMSG_LEN];
116
117         pinghost_t *head;
118 };
119
120 /*
121  * private (static) functions
122  */
123 static void ping_set_error (pingobj_t *obj, const char *function,
124                 const char *message)
125 {
126         snprintf (obj->errmsg, PING_ERRMSG_LEN, "%s: %s", function, message);
127         obj->errmsg[PING_ERRMSG_LEN - 1] = '\0';
128 }
129
130 static int ping_timeval_add (struct timeval *tv1, struct timeval *tv2,
131                 struct timeval *res)
132 {
133         res->tv_sec  = tv1->tv_sec  + tv2->tv_sec;
134         res->tv_usec = tv1->tv_usec + tv2->tv_usec;
135
136         while (res->tv_usec > 1000000)
137         {
138                 res->tv_usec -= 1000000;
139                 res->tv_sec++;
140         }
141
142         return (0);
143 }
144
145 static int ping_timeval_sub (struct timeval *tv1, struct timeval *tv2,
146                 struct timeval *res)
147 {
148
149         if ((tv1->tv_sec < tv2->tv_sec)
150                         || ((tv1->tv_sec == tv2->tv_sec)
151                                 && (tv1->tv_usec < tv2->tv_usec)))
152                 return (-1);
153
154         res->tv_sec  = tv1->tv_sec  - tv2->tv_sec;
155         res->tv_usec = tv1->tv_usec - tv2->tv_usec;
156
157         assert ((res->tv_sec > 0) || ((res->tv_sec == 0) && (res->tv_usec > 0)));
158
159         while (res->tv_usec < 0)
160         {
161                 res->tv_usec += 1000000;
162                 res->tv_sec--;
163         }
164
165         return (0);
166 }
167
168 static uint16_t ping_icmp4_checksum (char *buf, size_t len)
169 {
170         uint32_t sum = 0;
171         uint16_t ret = 0;
172
173         uint16_t *ptr;
174
175         for (ptr = (uint16_t *) buf; len > 1; ptr++, len -= 2)
176                 sum += *ptr;
177
178         if (len == 1)
179         {
180                 *(char *) &ret = *(char *) ptr;
181                 sum += ret;
182         }
183
184         /* Do this twice to get all possible carries.. */
185         sum = (sum >> 16) + (sum & 0xFFFF);
186         sum = (sum >> 16) + (sum & 0xFFFF);
187
188         ret = ~sum;
189
190         return (ret);
191 }
192
193 static pinghost_t *ping_receive_ipv4 (pinghost_t *ph, char *buffer, size_t buffer_len)
194 {
195         struct ip *ip_hdr;
196         struct icmp *icmp_hdr;
197
198         size_t ip_hdr_len;
199
200         uint16_t recv_checksum;
201         uint16_t calc_checksum;
202
203         uint16_t ident;
204         uint16_t seq;
205
206         pinghost_t *ptr;
207
208         if (buffer_len < sizeof (struct ip))
209                 return (NULL);
210
211         ip_hdr     = (struct ip *) buffer;
212         ip_hdr_len = ip_hdr->ip_hl << 2;
213
214         if (buffer_len < ip_hdr_len)
215                 return (NULL);
216
217         buffer     += ip_hdr_len;
218         buffer_len -= ip_hdr_len;
219
220         if (buffer_len < sizeof (struct icmp))
221                 return (NULL);
222
223         icmp_hdr = (struct icmp *) buffer;
224         buffer     += sizeof (struct icmp);
225         buffer_len -= sizeof (struct icmp);
226
227         if (icmp_hdr->icmp_type != ICMP_ECHOREPLY)
228         {
229                 dprintf ("Unexpected ICMP type: %i\n", icmp_hdr->icmp_type);
230                 return (NULL);
231         }
232
233         recv_checksum = icmp_hdr->icmp_cksum;
234         icmp_hdr->icmp_cksum = 0;
235         calc_checksum = ping_icmp4_checksum ((char *) icmp_hdr,
236                         sizeof (struct icmp) + buffer_len);
237
238         if (recv_checksum != calc_checksum)
239         {
240                 dprintf ("Checksum missmatch: Got 0x%04x, calculated 0x%04x\n",
241                                 recv_checksum, calc_checksum);
242                 return (NULL);
243         }
244
245         ident = ntohs (icmp_hdr->icmp_id);
246         seq   = ntohs (icmp_hdr->icmp_seq);
247
248         for (ptr = ph; ptr != NULL; ptr = ptr->next)
249         {
250                 dprintf ("hostname = %s, ident = 0x%04x, seq = %i\n",
251                                 ptr->hostname, ptr->ident, ptr->sequence - 1);
252
253                 if (ptr->addrfamily != AF_INET)
254                         continue;
255
256                 if (!timerisset (ptr->timer))
257                         continue;
258
259                 if (ptr->ident != ident)
260                         continue;
261
262                 if ((ptr->sequence - 1) != seq)
263                         continue;
264
265                 dprintf ("Match found: hostname = %s, ident = 0x%04x, seq = %i\n",
266                                 ptr->hostname, ident, seq);
267
268                 break;
269         }
270
271         if (ptr == NULL)
272         {
273                 dprintf ("No match found for ident = 0x%04x, seq = %i\n",
274                                 ident, seq);
275         }
276
277         return (ptr);
278 }
279
280 static pinghost_t *ping_receive_ipv6 (pinghost_t *ph, char *buffer, size_t buffer_len)
281 {
282         struct icmp6_hdr *icmp_hdr;
283
284         uint16_t ident;
285         uint16_t seq;
286
287         pinghost_t *ptr;
288
289         if (buffer_len < sizeof (struct icmp6_hdr))
290                 return (NULL);
291
292         icmp_hdr = (struct icmp6_hdr *) buffer;
293         buffer     += sizeof (struct icmp);
294         buffer_len -= sizeof (struct icmp);
295
296         if (icmp_hdr->icmp6_type != ICMP6_ECHO_REPLY)
297         {
298                 dprintf ("Unexpected ICMP type: %02x\n", icmp_hdr->icmp6_type);
299                 return (NULL);
300         }
301
302         if (icmp_hdr->icmp6_code != 0)
303         {
304                 dprintf ("Unexpected ICMP code: %02x\n", icmp_hdr->icmp6_code);
305                 return (NULL);
306         }
307
308         ident = ntohs (icmp_hdr->icmp6_id);
309         seq   = ntohs (icmp_hdr->icmp6_seq);
310
311         for (ptr = ph; ptr != NULL; ptr = ptr->next)
312         {
313                 dprintf ("hostname = %s, ident = 0x%04x, seq = %i\n",
314                                 ptr->hostname, ptr->ident, ptr->sequence - 1);
315
316                 if (ptr->addrfamily != AF_INET6)
317                         continue;
318
319                 if (!timerisset (ptr->timer))
320                         continue;
321
322                 if (ptr->ident != ident)
323                         continue;
324
325                 if ((ptr->sequence - 1) != seq)
326                         continue;
327
328                 dprintf ("Match found: hostname = %s, ident = 0x%04x, seq = %i\n",
329                                 ptr->hostname, ident, seq);
330
331                 break;
332         }
333
334         if (ptr == NULL)
335         {
336                 dprintf ("No match found for ident = 0x%04x, seq = %i\n",
337                                 ident, seq);
338         }
339
340         return (ptr);
341 }
342
343 static int ping_receive_one (int fd, pinghost_t *ph, struct timeval *now)
344 {
345         char   buffer[4096];
346         size_t buffer_len;
347
348         struct timeval diff;
349
350         pinghost_t *host = NULL;
351
352         struct sockaddr_storage sa;
353         socklen_t               sa_len;
354
355         sa_len = sizeof (sa);
356
357         buffer_len = recvfrom (fd, buffer, sizeof (buffer), 0,
358                         (struct sockaddr *) &sa, &sa_len);
359         if (buffer_len == -1)
360         {
361                 dprintf ("recvfrom: %s\n", strerror (errno));
362                 return (-1);
363         }
364
365         dprintf ("Read %i bytes from fd = %i\n", buffer_len, fd);
366
367         if (sa.ss_family == AF_INET)
368         {
369                 if ((host = ping_receive_ipv4 (ph, buffer, buffer_len)) == NULL)
370                         return (-1);
371         }
372         else if (sa.ss_family == AF_INET6)
373         {
374                 if ((host = ping_receive_ipv6 (ph, buffer, buffer_len)) == NULL)
375                         return (-1);
376         }
377
378         dprintf ("rcvd: %12i.%06i\n",
379                         (int) now->tv_sec,
380                         (int) now->tv_usec);
381         dprintf ("sent: %12i.%06i\n",
382                         (int) host->timer->tv_sec,
383                         (int) host->timer->tv_usec);
384
385         if (ping_timeval_sub (now, host->timer, &diff) < 0)
386         {
387                 timerclear (host->timer);
388                 return (-1);
389         }
390
391         dprintf ("diff: %12i.%06i\n",
392                         (int) diff.tv_sec,
393                         (int) diff.tv_usec);
394
395         host->latency  = ((double) diff.tv_usec) / 1000.0;
396         host->latency += ((double) diff.tv_sec)  * 1000.0;
397
398         timerclear (host->timer);
399
400         return (0);
401 }
402
403 static int ping_receive_all (pingobj_t *obj)
404 {
405         fd_set readfds;
406         int num_readfds;
407         int max_readfds;
408
409         pinghost_t *ph;
410         pinghost_t *ptr;
411
412         struct timeval endtime;
413         struct timeval nowtime;
414         struct timeval timeout;
415         int status;
416
417         int ret;
418
419         ph = obj->head;
420         ret = 0;
421
422         for (ptr = ph; ptr != NULL; ptr = ptr->next)
423                 ptr->latency = -1.0;
424
425         if (gettimeofday (&nowtime, NULL) == -1)
426         {
427                 ping_set_error (obj, "gettimeofday", strerror (errno));
428                 return (-1);
429         }
430
431         /* Set up timeout */
432         timeout.tv_sec = (time_t) obj->timeout;
433         timeout.tv_usec = (suseconds_t) (1000000 * (obj->timeout - ((double) timeout.tv_sec)));
434
435         dprintf ("Set timeout to %i.%06i seconds\n",
436                         (int) timeout.tv_sec,
437                         (int) timeout.tv_usec);
438
439         ping_timeval_add (&nowtime, &timeout, &endtime);
440
441         while (1)
442         {
443                 FD_ZERO (&readfds);
444                 num_readfds =  0;
445                 max_readfds = -1;
446
447                 for (ptr = ph; ptr != NULL; ptr = ptr->next)
448                 {
449                         if (!timerisset (ptr->timer))
450                                 continue;
451
452                         FD_SET (ptr->fd, &readfds);
453                         num_readfds++;
454
455                         if (max_readfds < ptr->fd)
456                                 max_readfds = ptr->fd;
457                 }
458
459                 if (num_readfds == 0)
460                         break;
461
462                 if (gettimeofday (&nowtime, NULL) == -1)
463                 {
464                         ping_set_error (obj, "gettimeofday", strerror (errno));
465                         return (-1);
466                 }
467
468                 if (ping_timeval_sub (&endtime, &nowtime, &timeout) == -1)
469                         break;
470
471                 dprintf ("Waiting on %i sockets for %i.%06i seconds\n", num_readfds,
472                                 (int) timeout.tv_sec,
473                                 (int) timeout.tv_usec);
474
475                 status = select (max_readfds + 1, &readfds, NULL, NULL, &timeout);
476
477                 if (gettimeofday (&nowtime, NULL) == -1)
478                 {
479                         ping_set_error (obj, "gettimeofday", strerror (errno));
480                         return (-1);
481                 }
482                 
483                 if ((status == -1) && (errno == EINTR))
484                 {
485                         dprintf ("select was interrupted by signal..\n");
486                         continue;
487                 }
488                 else if (status < 0)
489                 {
490                         dprintf ("select: %s\n", strerror (errno));
491                         break;
492                 }
493                 else if (status == 0)
494                 {
495                         dprintf ("select timed out\n");
496                         break;
497                 }
498
499                 for (ptr = ph; ptr != NULL; ptr = ptr->next)
500                 {
501                         if (FD_ISSET (ptr->fd, &readfds))
502                                 if (ping_receive_one (ptr->fd, ph, &nowtime) == 0)
503                                         ret++;
504                 }
505         } /* while (1) */
506         
507         return (ret);
508 }
509
510 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
511  * Sending functions:                                                        *
512  *                                                                           *
513  * ping_send_all                                                             *
514  * +-> ping_send_one_ipv4                                                    *
515  * `-> ping_send_one_ipv6                                                    *
516  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
517 static ssize_t ping_sendto (pinghost_t *ph, const void *buf, size_t buflen)
518 {
519         ssize_t ret;
520
521         if (gettimeofday (ph->timer, NULL) == -1)
522         {
523                 timerclear (ph->timer);
524                 return (-1);
525         }
526
527         ret = sendto (ph->fd, buf, buflen, 0,
528                         (struct sockaddr *) ph->addr, ph->addrlen);
529
530         return (ret);
531 }
532
533 static int ping_send_one_ipv4 (pinghost_t *ph)
534 {
535         struct icmp *icmp4;
536         int status;
537
538         char buf[4096];
539         int  buflen;
540
541         char *data;
542         int   datalen;
543
544         dprintf ("ph->hostname = %s\n", ph->hostname);
545
546         memset (buf, '\0', sizeof (buf));
547         icmp4 = (struct icmp *) buf;
548         data  = (char *) (icmp4 + 1);
549
550         icmp4->icmp_type  = ICMP_ECHO;
551         icmp4->icmp_code  = 0;
552         icmp4->icmp_cksum = 0;
553         icmp4->icmp_id    = htons (ph->ident);
554         icmp4->icmp_seq   = htons (ph->sequence);
555
556         strcpy (data, PING_DATA);
557         datalen = strlen (data);
558
559         buflen = datalen + sizeof (struct icmp);
560
561         icmp4->icmp_cksum = ping_icmp4_checksum (buf, buflen);
562
563         dprintf ("Sending ICMPv4 package with ID 0x%04x\n", ph->ident);
564
565         status = ping_sendto (ph, buf, buflen);
566         if (status < 0)
567         {
568                 perror ("ping_sendto");
569                 return (-1);
570         }
571
572         dprintf ("sendto: status = %i\n", status);
573
574         return (0);
575 }
576
577 static int ping_send_one_ipv6 (pinghost_t *ph)
578 {
579         struct icmp6_hdr *icmp6;
580         int status;
581
582         char buf[4096];
583         int  buflen;
584
585         char *data;
586         int   datalen;
587
588         dprintf ("ph->hostname = %s\n", ph->hostname);
589
590         memset (buf, '\0', sizeof (buf));
591         icmp6 = (struct icmp6_hdr *) buf;
592         data  = (char *) (icmp6 + 1);
593
594         icmp6->icmp6_type  = ICMP6_ECHO_REQUEST;
595         icmp6->icmp6_code  = 0;
596         /* The checksum will be calculated by the TCP/IP stack.  */
597         icmp6->icmp6_cksum = 0;
598         icmp6->icmp6_id    = htons (ph->ident);
599         icmp6->icmp6_seq   = htons (ph->sequence);
600
601         strcpy (data, PING_DATA);
602         datalen = strlen (data);
603
604         buflen = datalen + sizeof (struct icmp6_hdr);
605
606         dprintf ("Sending ICMPv6 package with ID 0x%04x\n", ph->ident);
607
608         status = ping_sendto (ph, buf, buflen);
609         if (status < 0)
610         {
611                 perror ("ping_sendto");
612                 return (-1);
613         }
614
615         dprintf ("sendto: status = %i\n", status);
616
617         return (0);
618 }
619
620 static int ping_send_all (pinghost_t *ph)
621 {
622         pinghost_t *ptr;
623
624         for (ptr = ph; ptr != NULL; ptr = ptr->next)
625         {
626                 /* start timer.. The GNU `ping6' starts the timer before
627                  * sending the packet, so I will do that too */
628                 if (gettimeofday (ptr->timer, NULL) == -1)
629                 {
630                         dprintf ("gettimeofday: %s\n", strerror (errno));
631                         timerclear (ptr->timer);
632                         continue;
633                 }
634                 else
635                 {
636                         dprintf ("timer set for hostname = %s\n", ptr->hostname);
637                 }
638
639                 if (ptr->addrfamily == AF_INET6)
640                 {       
641                         dprintf ("Sending ICMPv6 echo request to `%s'\n", ptr->hostname);
642                         if (ping_send_one_ipv6 (ptr) != 0)
643                         {
644                                 timerclear (ptr->timer);
645                                 continue;
646                         }
647                 }
648                 else if (ptr->addrfamily == AF_INET)
649                 {
650                         dprintf ("Sending ICMPv4 echo request to `%s'\n", ptr->hostname);
651                         if (ping_send_one_ipv4 (ptr) != 0)
652                         {
653                                 timerclear (ptr->timer);
654                                 continue;
655                         }
656                 }
657                 else /* this should not happen */
658                 {
659                         dprintf ("Unknown address family: %i\n", ptr->addrfamily);
660                         timerclear (ptr->timer);
661                         continue;
662                 }
663
664                 ptr->sequence++;
665         }
666
667         /* FIXME */
668         return (0);
669 }
670
671 /*
672  * Set the TTL of a socket protocol independently.
673  */
674 static int ping_set_ttl (pinghost_t *ph, int ttl)
675 {
676         int ret = -2;
677
678         if (ph->addrfamily == AF_INET)
679         {
680                 ret = setsockopt (ph->fd, IPPROTO_IP, IP_TTL, &ttl, sizeof (ttl));
681         }
682         else if (ph->addrfamily == AF_INET6)
683         {
684                 ret = setsockopt (ph->fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof (ttl));
685         }
686
687         return (ret);
688 }
689
690 static int ping_get_ident (void)
691 {
692         int fd;
693         static int did_seed = 0;
694
695         int retval;
696
697         if (did_seed == 0)
698         {
699                 if ((fd = open ("/dev/urandom", O_RDONLY)) != -1)
700                 {
701                         unsigned int seed;
702
703                         if (read (fd, &seed, sizeof (seed)) != -1)
704                         {
705                                 did_seed = 1;
706                                 dprintf ("Random seed: %i\n", seed);
707                                 srandom (seed);
708                         }
709
710                         close (fd);
711                 }
712                 else
713                 {
714                         dprintf ("open (/dev/urandom): %s\n", strerror (errno));
715                 }
716         }
717
718         retval = (int) random ();
719
720         dprintf ("Random number: %i\n", retval);
721         
722         return (retval);
723 }
724
725 static pinghost_t *ping_alloc (void)
726 {
727         pinghost_t *ph;
728         size_t      ph_size;
729
730         ph_size = sizeof (pinghost_t)
731                 + sizeof (struct sockaddr_storage)
732                 + sizeof (struct timeval);
733
734         ph = (pinghost_t *) malloc (ph_size);
735         if (ph == NULL)
736                 return (NULL);
737
738         memset (ph, '\0', ph_size);
739
740         ph->timer   = (struct timeval *) (ph + 1);
741         ph->addr    = (struct sockaddr_storage *) (ph->timer + 1);
742
743         ph->addrlen = sizeof (struct sockaddr_storage);
744         ph->latency = -1.0;
745         ph->ident   = ping_get_ident () & 0xFFFF;
746
747         return (ph);
748 }
749
750 static void ping_free (pinghost_t *ph)
751 {
752         if (ph->hostname != NULL)
753                 free (ph->hostname);
754
755         free (ph);
756 }
757
758 /*
759  * public methods
760  */
761 const char *ping_get_error (pingobj_t *obj)
762 {
763         return (obj->errmsg);
764 }
765
766 pingobj_t *ping_construct (void)
767 {
768         pingobj_t *obj;
769
770         if ((obj = (pingobj_t *) malloc (sizeof (pingobj_t))) == NULL)
771                 return (NULL);
772         memset (obj, '\0', sizeof (pingobj_t));
773
774         return (obj);
775 }
776
777 void ping_destroy (pingobj_t *obj)
778 {
779         pinghost_t *current;
780         pinghost_t *next;
781
782         current = obj->head;
783         next = NULL;
784
785         while (current != NULL)
786         {
787                 next = current->next;
788                 ping_free (current);
789                 current = next;
790         }
791
792         free (obj);
793
794         return;
795 }
796
797 int ping_setopt (pingobj_t *obj, int option, void *value)
798 {
799         int ret = 0;
800
801         switch (option)
802         {
803                 case PING_OPT_TIMEOUT:
804                         obj->timeout = *((double *) value);
805                         if (obj->timeout < 0.0)
806                         {
807                                 obj->timeout = PING_DEF_TIMEOUT;
808                                 ret = -1;
809                         }
810                         break;
811
812                 case PING_OPT_TTL:
813                         obj->ttl = *((int *) value);
814                         if ((obj->ttl < 1) || (obj->ttl > 255))
815                         {
816                                 obj->ttl = PING_DEF_TTL;
817                                 ret = -1;
818                         }
819                         break;
820
821                 case PING_OPT_AF:
822                         obj->addrfamily = *((int *) value);
823                         if ((obj->addrfamily != AF_UNSPEC)
824                                         && (obj->addrfamily != AF_INET)
825                                         && (obj->addrfamily != AF_INET6))
826                         {
827                                 obj->addrfamily = PING_DEF_AF;
828                                 ret = -1;
829                         }
830                         break;
831
832                 default:
833                         ret = -2;
834         } /* switch (option) */
835
836         return (ret);
837 } /* int ping_setopt */
838
839
840 int ping_send (pingobj_t *obj)
841 {
842         int ret;
843
844         if (ping_send_all (obj->head) < 0)
845                 return (-1);
846
847         if ((ret = ping_receive_all (obj)) < 0)
848                 return (-2);
849
850         return (ret);
851 }
852
853 static pinghost_t *ping_host_search (pinghost_t *ph, const char *host)
854 {
855         while (ph != NULL)
856         {
857                 if (strcasecmp (ph->hostname, host) == 0)
858                         break;
859
860                 ph = ph->next;
861         }
862
863         return (ph);
864 }
865
866 int ping_host_add (pingobj_t *obj, const char *host)
867 {
868         pinghost_t *ph;
869
870         struct sockaddr_storage sockaddr;
871         socklen_t               sockaddr_len;
872
873         struct addrinfo  ai_hints;
874         struct addrinfo *ai_list, *ai_ptr;
875         int              ai_return;
876
877         dprintf ("host = %s\n", host);
878
879         if (ping_host_search (obj->head, host) != NULL)
880                 return (0);
881
882         memset (&ai_hints, '\0', sizeof (ai_hints));
883         ai_hints.ai_flags     = 0;
884 #ifdef AI_ADDRCONFIG
885         ai_hints.ai_flags    |= AI_ADDRCONFIG;
886 #endif
887         ai_hints.ai_family    = obj->addrfamily;
888         ai_hints.ai_socktype  = SOCK_RAW;
889
890         if ((ph = ping_alloc ()) == NULL)
891         {
892                 dprintf ("Out of memory!\n");
893                 return (-1);
894         }
895
896         if ((ph->hostname = strdup (host)) == NULL)
897         {
898                 dprintf ("Out of memory!\n");
899                 ping_set_error (obj, "strdup", strerror (errno));
900                 ping_free (ph);
901                 return (-1);
902         }
903
904         if ((ai_return = getaddrinfo (host, NULL, &ai_hints, &ai_list)) != 0)
905         {
906                 dprintf ("getaddrinfo failed\n");
907                 ping_set_error (obj, "getaddrinfo",
908                                 (ai_return == EAI_SYSTEM)
909                                 ? strerror (errno)
910                                 : gai_strerror (ai_return));
911                 ping_free (ph);
912                 return (-1);
913         }
914
915         for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
916         {
917                 ph->fd = -1;
918
919                 sockaddr_len = sizeof (sockaddr);
920                 memset (&sockaddr, '\0', sockaddr_len);
921
922                 if (ai_ptr->ai_family == AF_INET)
923                 {
924                         struct sockaddr_in *si;
925
926                         si = (struct sockaddr_in *) &sockaddr;
927                         si->sin_family = AF_INET;
928                         si->sin_port   = htons (ph->ident);
929                         si->sin_addr.s_addr = htonl (INADDR_ANY);
930
931                         ai_ptr->ai_protocol = IPPROTO_ICMP;
932                 }
933                 else if (ai_ptr->ai_family == AF_INET6)
934                 {
935                         struct sockaddr_in6 *si;
936
937                         si = (struct sockaddr_in6 *) &sockaddr;
938                         si->sin6_family = AF_INET6;
939                         si->sin6_port   = htons (ph->ident);
940                         si->sin6_addr   = in6addr_any;
941
942                         ai_ptr->ai_protocol = IPPROTO_ICMPV6;
943                 }
944                 else
945                 {
946                         dprintf ("Unknown `ai_family': %i\n", ai_ptr->ai_family);
947                         continue;
948                 }
949
950                 ph->fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
951                 if (ph->fd == -1)
952                 {
953                         dprintf ("socket: %s\n", strerror (errno));
954                         continue;
955                 }
956
957                 if (bind (ph->fd, (struct sockaddr *) &sockaddr, sockaddr_len) == -1)
958                 {
959                         dprintf ("bind: %s\n", strerror (errno));
960                         close (ph->fd);
961                         ph->fd = -1;
962                         continue;
963                 }
964
965                 assert (sizeof (struct sockaddr_storage) >= ai_ptr->ai_addrlen);
966                 memset (ph->addr, '\0', sizeof (struct sockaddr_storage));
967                 memcpy (ph->addr, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
968                 ph->addrlen = ai_ptr->ai_addrlen;
969                 ph->addrfamily = ai_ptr->ai_family;
970
971                 break;
972         }
973
974         freeaddrinfo (ai_list);
975
976         if (ph->fd < 0)
977         {
978                 free (ph->hostname);
979                 free (ph);
980                 ping_set_error (obj, "ping_host_add", "Unable to open socket");
981                 return (-1);
982         }
983
984         ph->next  = obj->head;
985         obj->head = ph;
986
987         ping_set_ttl (ph, obj->ttl);
988
989         return (0);
990 }
991
992 int ping_host_remove (pingobj_t *obj, const char *host)
993 {
994         pinghost_t *pre, *cur;
995
996         pre = NULL;
997         cur = obj->head;
998
999         while (cur != NULL)
1000         {
1001                 if (strcasecmp (host, cur->hostname))
1002                         break;
1003
1004                 pre = cur;
1005                 cur = cur->next;
1006         }
1007
1008         if (cur == NULL)
1009         {
1010                 ping_set_error (obj, "ping_host_remove", "Host not found");
1011                 return (-1);
1012         }
1013
1014         if (pre == NULL)
1015                 obj->head = cur->next;
1016         else
1017                 pre->next = cur->next;
1018         
1019         if (cur->fd >= 0)
1020                 close (cur->fd);
1021
1022         ping_free (cur);
1023
1024         return (0);
1025 }
1026
1027 pingobj_iter_t *ping_iterator_get (pingobj_t *obj)
1028 {
1029         return ((pingobj_iter_t *) obj->head);
1030 }
1031
1032 pingobj_iter_t *ping_iterator_next (pingobj_iter_t *iter)
1033 {
1034         return ((pingobj_iter_t *) iter->next);
1035 }
1036
1037 const char *ping_iterator_get_host (pingobj_iter_t *iter)
1038 {
1039         return (iter->hostname);
1040 }
1041
1042 double ping_iterator_get_latency (pingobj_iter_t *iter)
1043 {
1044         return (iter->latency);
1045 }