c93633c8ea7c6cfba58336f394a4d4c9b1c7019e
[liboping.git] / src / liboping.c
1 /**
2  * Object oriented C module to send ICMP and ICMPv6 `echo's.
3  * Copyright (C) 2006-2008  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; only version 2 of the License is
8  * applicable.
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 <stdint.h>
29 # include <inttypes.h>
30 # include <errno.h>
31 # include <assert.h>
32 #else
33 # error "You don't have the standard C99 header files installed"
34 #endif /* STDC_HEADERS */
35
36 #if HAVE_UNISTD_H
37 # include <unistd.h>
38 #endif
39
40 #if HAVE_FCNTL_H
41 # include <fcntl.h>
42 #endif
43 #if HAVE_SYS_TYPES_H
44 # include <sys/types.h>
45 #endif
46 #if HAVE_SYS_STAT_H
47 # include <sys/stat.h>
48 #endif
49
50 #if TIME_WITH_SYS_TIME
51 # include <sys/time.h>
52 # include <time.h>
53 #else
54 # if HAVE_SYS_TIME_H
55 #  include <sys/time.h>
56 # else
57 #  include <time.h>
58 # endif
59 #endif
60
61 #if HAVE_SYS_SOCKET_H
62 # include <sys/socket.h>
63 #endif
64 #if HAVE_NETDB_H
65 # include <netdb.h>
66 #endif
67
68 #if HAVE_NETINET_IN_SYSTM_H
69 # include <netinet/in_systm.h>
70 #endif
71 #if HAVE_NETINET_IN_H
72 # include <netinet/in.h>
73 #endif
74 #if HAVE_NETINET_IP_H
75 # include <netinet/ip.h>
76 #endif
77 #if HAVE_NETINET_IP_ICMP_H
78 # include <netinet/ip_icmp.h>
79 #endif
80 #ifdef HAVE_NETINET_IP_VAR_H
81 # include <netinet/ip_var.h>
82 #endif
83 #if HAVE_NETINET_IP6_H
84 # include <netinet/ip6.h>
85 #endif
86 #if HAVE_NETINET_ICMP6_H
87 # include <netinet/icmp6.h>
88 #endif
89
90 #include "oping.h"
91
92 #if WITH_DEBUG
93 # define dprintf(...) printf ("%s[%4i]: %-20s: ", __FILE__, __LINE__, __FUNCTION__); printf (__VA_ARGS__)
94 #else
95 # define dprintf(...) /**/
96 #endif
97
98 #define PING_ERRMSG_LEN 256
99
100 struct pinghost
101 {
102         /* username: name passed in by the user */
103         char                    *username;
104         /* hostname: name returned by the reverse lookup */
105         char                    *hostname;
106         struct sockaddr_storage *addr;
107         socklen_t                addrlen;
108         int                      addrfamily;
109         int                      fd;
110         int                      ident;
111         int                      sequence;
112         struct timeval          *timer;
113         double                   latency;
114         uint32_t                 dropped;
115         char                    *data;
116
117         void                    *context;
118
119         struct pinghost         *next;
120 };
121
122 struct pingobj
123 {
124         double                   timeout;
125         int                      ttl;
126         int                      addrfamily;
127         char                    *data;
128
129         struct sockaddr_storage *srcaddr;
130         socklen_t                srcaddrlen;
131
132         char                     errmsg[PING_ERRMSG_LEN];
133
134         pinghost_t              *head;
135 };
136
137 /*
138  * private (static) functions
139  */
140 static void ping_set_error (pingobj_t *obj, const char *function,
141                 const char *message)
142 {
143         snprintf (obj->errmsg, PING_ERRMSG_LEN, "%s: %s", function, message);
144         obj->errmsg[PING_ERRMSG_LEN - 1] = '\0';
145 }
146
147 static int ping_timeval_add (struct timeval *tv1, struct timeval *tv2,
148                 struct timeval *res)
149 {
150         res->tv_sec  = tv1->tv_sec  + tv2->tv_sec;
151         res->tv_usec = tv1->tv_usec + tv2->tv_usec;
152
153         while (res->tv_usec > 1000000)
154         {
155                 res->tv_usec -= 1000000;
156                 res->tv_sec++;
157         }
158
159         return (0);
160 }
161
162 static int ping_timeval_sub (struct timeval *tv1, struct timeval *tv2,
163                 struct timeval *res)
164 {
165
166         if ((tv1->tv_sec < tv2->tv_sec)
167                         || ((tv1->tv_sec == tv2->tv_sec)
168                                 && (tv1->tv_usec < tv2->tv_usec)))
169                 return (-1);
170
171         res->tv_sec  = tv1->tv_sec  - tv2->tv_sec;
172         res->tv_usec = tv1->tv_usec - tv2->tv_usec;
173
174         assert ((res->tv_sec > 0) || ((res->tv_sec == 0) && (res->tv_usec > 0)));
175
176         while (res->tv_usec < 0)
177         {
178                 res->tv_usec += 1000000;
179                 res->tv_sec--;
180         }
181
182         return (0);
183 }
184
185 static uint16_t ping_icmp4_checksum (char *buf, size_t len)
186 {
187         uint32_t sum = 0;
188         uint16_t ret = 0;
189
190         uint16_t *ptr;
191
192         for (ptr = (uint16_t *) buf; len > 1; ptr++, len -= 2)
193                 sum += *ptr;
194
195         if (len == 1)
196         {
197                 *(char *) &ret = *(char *) ptr;
198                 sum += ret;
199         }
200
201         /* Do this twice to get all possible carries.. */
202         sum = (sum >> 16) + (sum & 0xFFFF);
203         sum = (sum >> 16) + (sum & 0xFFFF);
204
205         ret = ~sum;
206
207         return (ret);
208 }
209
210 static pinghost_t *ping_receive_ipv4 (pinghost_t *ph, char *buffer, size_t buffer_len)
211 {
212         struct ip *ip_hdr;
213         struct icmp *icmp_hdr;
214
215         size_t ip_hdr_len;
216
217         uint16_t recv_checksum;
218         uint16_t calc_checksum;
219
220         uint16_t ident;
221         uint16_t seq;
222
223         pinghost_t *ptr;
224
225         if (buffer_len < sizeof (struct ip))
226                 return (NULL);
227
228         ip_hdr     = (struct ip *) buffer;
229         ip_hdr_len = ip_hdr->ip_hl << 2;
230
231         if (buffer_len < ip_hdr_len)
232                 return (NULL);
233
234         buffer     += ip_hdr_len;
235         buffer_len -= ip_hdr_len;
236
237         if (buffer_len < sizeof (struct icmp))
238                 return (NULL);
239
240         icmp_hdr = (struct icmp *) buffer;
241         buffer     += sizeof (struct icmp);
242         buffer_len -= sizeof (struct icmp);
243
244         if (icmp_hdr->icmp_type != ICMP_ECHOREPLY)
245         {
246                 dprintf ("Unexpected ICMP type: %i\n", icmp_hdr->icmp_type);
247                 return (NULL);
248         }
249
250         recv_checksum = icmp_hdr->icmp_cksum;
251         icmp_hdr->icmp_cksum = 0;
252         calc_checksum = ping_icmp4_checksum ((char *) icmp_hdr,
253                         sizeof (struct icmp) + buffer_len);
254
255         if (recv_checksum != calc_checksum)
256         {
257                 dprintf ("Checksum missmatch: Got 0x%04x, calculated 0x%04x\n",
258                                 recv_checksum, calc_checksum);
259                 return (NULL);
260         }
261
262         ident = ntohs (icmp_hdr->icmp_id);
263         seq   = ntohs (icmp_hdr->icmp_seq);
264
265         for (ptr = ph; ptr != NULL; ptr = ptr->next)
266         {
267                 dprintf ("hostname = %s, ident = 0x%04x, seq = %i\n",
268                                 ptr->hostname, ptr->ident, ((ptr->sequence - 1) & 0xFFFF));
269
270                 if (ptr->addrfamily != AF_INET)
271                         continue;
272
273                 if (!timerisset (ptr->timer))
274                         continue;
275
276                 if (ptr->ident != ident)
277                         continue;
278
279                 if (((ptr->sequence - 1) & 0xFFFF) != seq)
280                         continue;
281
282                 dprintf ("Match found: hostname = %s, ident = 0x%04x, seq = %i\n",
283                                 ptr->hostname, ident, seq);
284
285                 break;
286         }
287
288         if (ptr == NULL)
289         {
290                 dprintf ("No match found for ident = 0x%04x, seq = %i\n",
291                                 ident, seq);
292         }
293
294         return (ptr);
295 }
296
297 #ifndef ICMP6_ECHO_REQUEST
298 # ifdef ICMP6_ECHO /* AIX netinet/ip6_icmp.h */
299 #  define ICMP6_ECHO_REQUEST ICMP6_ECHO
300 # else
301 #  define ICMP6_ECHO_REQUEST 128
302 # endif
303 #endif
304
305 #ifndef ICMP6_ECHO_REPLY
306 # ifdef ICMP6_ECHOREPLY /* AIX netinet/ip6_icmp.h */
307 #  define ICMP6_ECHO_REPLY ICMP6_ECHOREPLY
308 # else
309 #  define ICMP6_ECHO_REPLY 129
310 # endif
311 #endif
312
313 static pinghost_t *ping_receive_ipv6 (pinghost_t *ph, char *buffer, size_t buffer_len)
314 {
315         struct icmp6_hdr *icmp_hdr;
316
317         uint16_t ident;
318         uint16_t seq;
319
320         pinghost_t *ptr;
321
322         if (buffer_len < sizeof (struct icmp6_hdr))
323                 return (NULL);
324
325         icmp_hdr = (struct icmp6_hdr *) buffer;
326         buffer     += sizeof (struct icmp);
327         buffer_len -= sizeof (struct icmp);
328
329         if (icmp_hdr->icmp6_type != ICMP6_ECHO_REPLY)
330         {
331                 dprintf ("Unexpected ICMP type: %02x\n", icmp_hdr->icmp6_type);
332                 return (NULL);
333         }
334
335         if (icmp_hdr->icmp6_code != 0)
336         {
337                 dprintf ("Unexpected ICMP code: %02x\n", icmp_hdr->icmp6_code);
338                 return (NULL);
339         }
340
341         ident = ntohs (icmp_hdr->icmp6_id);
342         seq   = ntohs (icmp_hdr->icmp6_seq);
343
344         for (ptr = ph; ptr != NULL; ptr = ptr->next)
345         {
346                 dprintf ("hostname = %s, ident = 0x%04x, seq = %i\n",
347                                 ptr->hostname, ptr->ident, ((ptr->sequence - 1) & 0xFFFF));
348
349                 if (ptr->addrfamily != AF_INET6)
350                         continue;
351
352                 if (!timerisset (ptr->timer))
353                         continue;
354
355                 if (ptr->ident != ident)
356                         continue;
357
358                 if (((ptr->sequence - 1) & 0xFFFF) != seq)
359                         continue;
360
361                 dprintf ("Match found: hostname = %s, ident = 0x%04x, seq = %i\n",
362                                 ptr->hostname, ident, seq);
363
364                 break;
365         }
366
367         if (ptr == NULL)
368         {
369                 dprintf ("No match found for ident = 0x%04x, seq = %i\n",
370                                 ident, seq);
371         }
372
373         return (ptr);
374 }
375
376 static int ping_receive_one (int fd, pinghost_t *ph, struct timeval *now)
377 {
378         char   buffer[4096];
379         size_t buffer_len;
380
381         struct timeval diff;
382
383         pinghost_t *host = NULL;
384
385         struct sockaddr_storage sa;
386         socklen_t               sa_len;
387
388         sa_len = sizeof (sa);
389
390         buffer_len = recvfrom (fd, buffer, sizeof (buffer), 0,
391                         (struct sockaddr *) &sa, &sa_len);
392         if (buffer_len == -1)
393         {
394                 dprintf ("recvfrom: %s\n", strerror (errno));
395                 return (-1);
396         }
397
398         dprintf ("Read %u bytes from fd = %i\n", (unsigned int) buffer_len, fd);
399
400         if (sa.ss_family == AF_INET)
401         {
402                 if ((host = ping_receive_ipv4 (ph, buffer, buffer_len)) == NULL)
403                         return (-1);
404         }
405         else if (sa.ss_family == AF_INET6)
406         {
407                 if ((host = ping_receive_ipv6 (ph, buffer, buffer_len)) == NULL)
408                         return (-1);
409         }
410
411         dprintf ("rcvd: %12i.%06i\n",
412                         (int) now->tv_sec,
413                         (int) now->tv_usec);
414         dprintf ("sent: %12i.%06i\n",
415                         (int) host->timer->tv_sec,
416                         (int) host->timer->tv_usec);
417
418         if (ping_timeval_sub (now, host->timer, &diff) < 0)
419         {
420                 timerclear (host->timer);
421                 return (-1);
422         }
423
424         dprintf ("diff: %12i.%06i\n",
425                         (int) diff.tv_sec,
426                         (int) diff.tv_usec);
427
428         host->latency  = ((double) diff.tv_usec) / 1000.0;
429         host->latency += ((double) diff.tv_sec)  * 1000.0;
430
431         timerclear (host->timer);
432
433         return (0);
434 }
435
436 static int ping_receive_all (pingobj_t *obj)
437 {
438         fd_set readfds;
439         int num_readfds;
440         int max_readfds;
441
442         pinghost_t *ph;
443         pinghost_t *ptr;
444
445         struct timeval endtime;
446         struct timeval nowtime;
447         struct timeval timeout;
448         int status;
449
450         int ret;
451
452         ph = obj->head;
453         ret = 0;
454
455         for (ptr = ph; ptr != NULL; ptr = ptr->next)
456                 ptr->latency = -1.0;
457
458         if (gettimeofday (&nowtime, NULL) == -1)
459         {
460                 ping_set_error (obj, "gettimeofday", strerror (errno));
461                 return (-1);
462         }
463
464         /* Set up timeout */
465         timeout.tv_sec = (time_t) obj->timeout;
466         timeout.tv_usec = (suseconds_t) (1000000 * (obj->timeout - ((double) timeout.tv_sec)));
467
468         dprintf ("Set timeout to %i.%06i seconds\n",
469                         (int) timeout.tv_sec,
470                         (int) timeout.tv_usec);
471
472         ping_timeval_add (&nowtime, &timeout, &endtime);
473
474         while (1)
475         {
476                 FD_ZERO (&readfds);
477                 num_readfds =  0;
478                 max_readfds = -1;
479
480                 for (ptr = ph; ptr != NULL; ptr = ptr->next)
481                 {
482                         if (!timerisset (ptr->timer))
483                                 continue;
484
485                         FD_SET (ptr->fd, &readfds);
486                         num_readfds++;
487
488                         if (max_readfds < ptr->fd)
489                                 max_readfds = ptr->fd;
490                 }
491
492                 if (num_readfds == 0)
493                         break;
494
495                 if (gettimeofday (&nowtime, NULL) == -1)
496                 {
497                         ping_set_error (obj, "gettimeofday", strerror (errno));
498                         return (-1);
499                 }
500
501                 if (ping_timeval_sub (&endtime, &nowtime, &timeout) == -1)
502                         break;
503
504                 dprintf ("Waiting on %i sockets for %i.%06i seconds\n", num_readfds,
505                                 (int) timeout.tv_sec,
506                                 (int) timeout.tv_usec);
507
508                 status = select (max_readfds + 1, &readfds, NULL, NULL, &timeout);
509
510                 if (gettimeofday (&nowtime, NULL) == -1)
511                 {
512                         ping_set_error (obj, "gettimeofday", strerror (errno));
513                         return (-1);
514                 }
515                 
516                 if ((status == -1) && (errno == EINTR))
517                 {
518                         dprintf ("select was interrupted by signal..\n");
519                         continue;
520                 }
521                 else if (status < 0)
522                 {
523                         dprintf ("select: %s\n", strerror (errno));
524                         break;
525                 }
526                 else if (status == 0)
527                 {
528                         dprintf ("select timed out\n");
529                         for (ptr = ph; ptr != NULL; ptr = ptr->next)
530                                 if (ptr->latency < 0.0)
531                                         ptr->dropped++;
532                         break;
533                 }
534
535                 for (ptr = ph; ptr != NULL; ptr = ptr->next)
536                 {
537                         if (FD_ISSET (ptr->fd, &readfds))
538                                 if (ping_receive_one (ptr->fd, ph, &nowtime) == 0)
539                                         ret++;
540                 }
541         } /* while (1) */
542         
543         return (ret);
544 }
545
546 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
547  * Sending functions:                                                        *
548  *                                                                           *
549  * ping_send_all                                                             *
550  * +-> ping_send_one_ipv4                                                    *
551  * `-> ping_send_one_ipv6                                                    *
552  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
553 static ssize_t ping_sendto (pingobj_t *obj, pinghost_t *ph,
554                 const void *buf, size_t buflen)
555 {
556         ssize_t ret;
557
558         if (gettimeofday (ph->timer, NULL) == -1)
559         {
560                 timerclear (ph->timer);
561                 return (-1);
562         }
563
564         ret = sendto (ph->fd, buf, buflen, 0,
565                         (struct sockaddr *) ph->addr, ph->addrlen);
566
567         if (ret < 0)
568         {
569 #if defined(EHOSTUNREACH)
570                 if (errno == EHOSTUNREACH)
571                         return (0);
572 #endif
573 #if defined(ENETUNREACH)
574                 if (errno == ENETUNREACH)
575                         return (0);
576 #endif
577                 ping_set_error (obj, "sendto", strerror (errno));
578         }
579
580         return (ret);
581 }
582
583 static int ping_send_one_ipv4 (pingobj_t *obj, pinghost_t *ph)
584 {
585         struct icmp *icmp4;
586         int status;
587
588         char buf[4096];
589         int  buflen;
590
591         char *data;
592         int   datalen;
593
594         dprintf ("ph->hostname = %s\n", ph->hostname);
595
596         memset (buf, '\0', sizeof (buf));
597         icmp4 = (struct icmp *) buf;
598         data  = (char *) (icmp4 + 1);
599
600         icmp4->icmp_type  = ICMP_ECHO;
601         icmp4->icmp_code  = 0;
602         icmp4->icmp_cksum = 0;
603         icmp4->icmp_id    = htons (ph->ident);
604         icmp4->icmp_seq   = htons (ph->sequence);
605
606         buflen = 4096 - sizeof (struct icmp);
607         strncpy (data, ph->data, buflen);
608         datalen = strlen (data);
609
610         buflen = datalen + sizeof (struct icmp);
611
612         icmp4->icmp_cksum = ping_icmp4_checksum (buf, buflen);
613
614         dprintf ("Sending ICMPv4 package with ID 0x%04x\n", ph->ident);
615
616         status = ping_sendto (obj, ph, buf, buflen);
617         if (status < 0)
618         {
619                 perror ("ping_sendto");
620                 return (-1);
621         }
622
623         dprintf ("sendto: status = %i\n", status);
624
625         return (0);
626 }
627
628 static int ping_send_one_ipv6 (pingobj_t *obj, pinghost_t *ph)
629 {
630         struct icmp6_hdr *icmp6;
631         int status;
632
633         char buf[4096];
634         int  buflen;
635
636         char *data;
637         int   datalen;
638
639         dprintf ("ph->hostname = %s\n", ph->hostname);
640
641         memset (buf, '\0', sizeof (buf));
642         icmp6 = (struct icmp6_hdr *) buf;
643         data  = (char *) (icmp6 + 1);
644
645         icmp6->icmp6_type  = ICMP6_ECHO_REQUEST;
646         icmp6->icmp6_code  = 0;
647         /* The checksum will be calculated by the TCP/IP stack.  */
648         /* FIXME */
649         icmp6->icmp6_cksum = 0;
650         icmp6->icmp6_id    = htons (ph->ident);
651         icmp6->icmp6_seq   = htons (ph->sequence);
652
653         buflen = 4096 - sizeof (struct icmp6_hdr);
654         strncpy (data, ph->data, buflen);
655         datalen = strlen (data);
656
657         buflen = datalen + sizeof (struct icmp6_hdr);
658
659         dprintf ("Sending ICMPv6 package with ID 0x%04x\n", ph->ident);
660
661         status = ping_sendto (obj, ph, buf, buflen);
662         if (status < 0)
663         {
664                 perror ("ping_sendto");
665                 return (-1);
666         }
667
668         dprintf ("sendto: status = %i\n", status);
669
670         return (0);
671 }
672
673 static int ping_send_all (pingobj_t *obj)
674 {
675         pinghost_t *ph;
676         pinghost_t *ptr;
677
678         int ret;
679
680         ret = 0;
681         ph = obj->head;
682
683         for (ptr = ph; ptr != NULL; ptr = ptr->next)
684         {
685                 /* start timer.. The GNU `ping6' starts the timer before
686                  * sending the packet, so I will do that too */
687                 if (gettimeofday (ptr->timer, NULL) == -1)
688                 {
689                         dprintf ("gettimeofday: %s\n", strerror (errno));
690                         timerclear (ptr->timer);
691                         ret--;
692                         continue;
693                 }
694                 else
695                 {
696                         dprintf ("timer set for hostname = %s\n", ptr->hostname);
697                 }
698
699                 if (ptr->addrfamily == AF_INET6)
700                 {       
701                         dprintf ("Sending ICMPv6 echo request to `%s'\n", ptr->hostname);
702                         if (ping_send_one_ipv6 (obj, ptr) != 0)
703                         {
704                                 timerclear (ptr->timer);
705                                 ret--;
706                                 continue;
707                         }
708                 }
709                 else if (ptr->addrfamily == AF_INET)
710                 {
711                         dprintf ("Sending ICMPv4 echo request to `%s'\n", ptr->hostname);
712                         if (ping_send_one_ipv4 (obj, ptr) != 0)
713                         {
714                                 timerclear (ptr->timer);
715                                 ret--;
716                                 continue;
717                         }
718                 }
719                 else /* this should not happen */
720                 {
721                         dprintf ("Unknown address family: %i\n", ptr->addrfamily);
722                         timerclear (ptr->timer);
723                         ret--;
724                         continue;
725                 }
726
727                 ptr->sequence++;
728         }
729
730         return (ret);
731 }
732
733 /*
734  * Set the TTL of a socket protocol independently.
735  */
736 static int ping_set_ttl (pinghost_t *ph, int ttl)
737 {
738         int ret = -2;
739
740         if (ph->addrfamily == AF_INET)
741         {
742                 ret = setsockopt (ph->fd, IPPROTO_IP, IP_TTL, &ttl, sizeof (ttl));
743         }
744         else if (ph->addrfamily == AF_INET6)
745         {
746                 ret = setsockopt (ph->fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof (ttl));
747         }
748
749         return (ret);
750 }
751
752 static int ping_get_ident (void)
753 {
754         int fd;
755         static int did_seed = 0;
756
757         int retval;
758
759         if (did_seed == 0)
760         {
761                 if ((fd = open ("/dev/urandom", O_RDONLY)) != -1)
762                 {
763                         unsigned int seed;
764
765                         if (read (fd, &seed, sizeof (seed)) != -1)
766                         {
767                                 did_seed = 1;
768                                 dprintf ("Random seed: %i\n", seed);
769                                 srandom (seed);
770                         }
771
772                         close (fd);
773                 }
774                 else
775                 {
776                         dprintf ("open (/dev/urandom): %s\n", strerror (errno));
777                 }
778         }
779
780         retval = (int) random ();
781
782         dprintf ("Random number: %i\n", retval);
783         
784         return (retval);
785 }
786
787 static pinghost_t *ping_alloc (void)
788 {
789         pinghost_t *ph;
790         size_t      ph_size;
791
792         ph_size = sizeof (pinghost_t)
793                 + sizeof (struct sockaddr_storage)
794                 + sizeof (struct timeval);
795
796         ph = (pinghost_t *) malloc (ph_size);
797         if (ph == NULL)
798                 return (NULL);
799
800         memset (ph, '\0', ph_size);
801
802         ph->timer   = (struct timeval *) (ph + 1);
803         ph->addr    = (struct sockaddr_storage *) (ph->timer + 1);
804
805         ph->addrlen = sizeof (struct sockaddr_storage);
806         ph->fd      = -1;
807         ph->latency = -1.0;
808         ph->dropped = 0;
809         ph->ident   = ping_get_ident () & 0xFFFF;
810
811         return (ph);
812 }
813
814 static void ping_free (pinghost_t *ph)
815 {
816         if (ph->fd >= 0)
817                 close (ph->fd);
818         
819         if (ph->username != NULL)
820                 free (ph->username);
821
822         if (ph->hostname != NULL)
823                 free (ph->hostname);
824
825         if (ph->data != NULL)
826                 free (ph->data);
827
828         free (ph);
829 }
830
831 /*
832  * public methods
833  */
834 const char *ping_get_error (pingobj_t *obj)
835 {
836         return (obj->errmsg);
837 }
838
839 pingobj_t *ping_construct (void)
840 {
841         pingobj_t *obj;
842
843         if ((obj = (pingobj_t *) malloc (sizeof (pingobj_t))) == NULL)
844                 return (NULL);
845         memset (obj, '\0', sizeof (pingobj_t));
846
847         obj->timeout    = PING_DEF_TIMEOUT;
848         obj->ttl        = PING_DEF_TTL;
849         obj->addrfamily = PING_DEF_AF;
850         obj->data       = strdup (PING_DEF_DATA);
851
852         return (obj);
853 }
854
855 void ping_destroy (pingobj_t *obj)
856 {
857         pinghost_t *current;
858         pinghost_t *next;
859
860         current = obj->head;
861         next = NULL;
862
863         while (current != NULL)
864         {
865                 next = current->next;
866                 ping_free (current);
867                 current = next;
868         }
869
870         if (obj->data != NULL)
871                 free (obj->data);
872
873         if (obj->srcaddr != NULL)
874                 free (obj->srcaddr);
875
876         free (obj);
877
878         return;
879 }
880
881 int ping_setopt (pingobj_t *obj, int option, void *value)
882 {
883         int ret = 0;
884
885         switch (option)
886         {
887                 case PING_OPT_TIMEOUT:
888                         obj->timeout = *((double *) value);
889                         if (obj->timeout < 0.0)
890                         {
891                                 obj->timeout = PING_DEF_TIMEOUT;
892                                 ret = -1;
893                         }
894                         break;
895
896                 case PING_OPT_TTL:
897                         obj->ttl = *((int *) value);
898                         if ((obj->ttl < 1) || (obj->ttl > 255))
899                         {
900                                 obj->ttl = PING_DEF_TTL;
901                                 ret = -1;
902                         }
903                         break;
904
905                 case PING_OPT_AF:
906                         obj->addrfamily = *((int *) value);
907                         if ((obj->addrfamily != AF_UNSPEC)
908                                         && (obj->addrfamily != AF_INET)
909                                         && (obj->addrfamily != AF_INET6))
910                         {
911                                 obj->addrfamily = PING_DEF_AF;
912                                 ret = -1;
913                         }
914                         if (obj->srcaddr != NULL)
915                         {
916                                 free (obj->srcaddr);
917                                 obj->srcaddr = NULL;
918                         }
919                         break;
920
921                 case PING_OPT_DATA:
922                         if (obj->data != NULL)
923                         {
924                                 free (obj->data);
925                                 obj->data = NULL;
926                         }
927                         obj->data = strdup ((const char *) value);
928                         break;
929
930                 case PING_OPT_SOURCE:
931                 {
932                         char            *hostname = (char *) value;
933                         struct addrinfo  ai_hints;
934                         struct addrinfo *ai_list;
935                         int              status;
936 #if WITH_DEBUG
937                         if (obj->addrfamily != AF_UNSPEC)
938                         {
939                                 dprintf ("Resetting obj->addrfamily to AF_UNSPEC.\n");
940                         }
941 #endif
942                         memset ((void *) &ai_hints, '\0', sizeof (ai_hints));
943                         ai_hints.ai_family = obj->addrfamily = AF_UNSPEC;
944 #if defined(AI_ADDRCONFIG)
945                         ai_hints.ai_flags = AI_ADDRCONFIG;
946 #endif
947                         status = getaddrinfo (hostname, NULL, &ai_hints, &ai_list);
948                         if (status != 0)
949                         {
950                                 ping_set_error (obj, "getaddrinfo",
951 #if defined(EAI_SYSTEM)
952                                                 (status == EAI_SYSTEM)
953                                                 ? strerror (errno) :
954 #endif
955                                                 gai_strerror (status));
956                                 ret = -1;
957                                 break;
958                         }
959 #if WITH_DEBUG
960                         if (ai_list->ai_next != NULL)
961                         {
962                                 dprintf ("hostname = `%s' is ambiguous.\n", hostname);
963                         }
964 #endif
965                         if (obj->srcaddr == NULL)
966                         {
967                                 obj->srcaddrlen = 0;
968                                 obj->srcaddr = (struct sockaddr_storage *) malloc (sizeof (struct sockaddr_storage));
969                                 if (obj->srcaddr == NULL)
970                                 {
971                                         ping_set_error (obj, "malloc",
972                                                         strerror (errno));
973                                         ret = -1;
974                                         freeaddrinfo (ai_list);
975                                         break;
976                                 }
977                         }
978                         memset ((void *) obj->srcaddr, '\0', sizeof (struct sockaddr_storage));
979                         assert (ai_list->ai_addrlen <= sizeof (struct sockaddr_storage));
980                         memcpy ((void *) obj->srcaddr, (const void *) ai_list->ai_addr,
981                                         ai_list->ai_addrlen);
982                         obj->srcaddrlen = ai_list->ai_addrlen;
983                         obj->addrfamily = ai_list->ai_family;
984
985                         freeaddrinfo (ai_list);
986                 } /* case PING_OPT_SOURCE */
987                 break;
988
989                 default:
990                         ret = -2;
991         } /* switch (option) */
992
993         return (ret);
994 } /* int ping_setopt */
995
996
997 int ping_send (pingobj_t *obj)
998 {
999         int ret;
1000
1001         if (ping_send_all (obj) < 0)
1002                 return (-1);
1003
1004         if ((ret = ping_receive_all (obj)) < 0)
1005                 return (-2);
1006
1007         return (ret);
1008 }
1009
1010 static pinghost_t *ping_host_search (pinghost_t *ph, const char *host)
1011 {
1012         while (ph != NULL)
1013         {
1014                 if (strcasecmp (ph->username, host) == 0)
1015                         break;
1016
1017                 ph = ph->next;
1018         }
1019
1020         return (ph);
1021 }
1022
1023 int ping_host_add (pingobj_t *obj, const char *host)
1024 {
1025         pinghost_t *ph;
1026
1027         struct sockaddr_storage sockaddr;
1028         socklen_t               sockaddr_len;
1029
1030         struct addrinfo  ai_hints;
1031         struct addrinfo *ai_list, *ai_ptr;
1032         int              ai_return;
1033
1034         dprintf ("host = %s\n", host);
1035
1036         if (ping_host_search (obj->head, host) != NULL)
1037                 return (0);
1038
1039         memset (&ai_hints, '\0', sizeof (ai_hints));
1040         ai_hints.ai_flags     = 0;
1041 #ifdef AI_ADDRCONFIG
1042         ai_hints.ai_flags    |= AI_ADDRCONFIG;
1043 #endif
1044 #ifdef AI_CANONNAME
1045         ai_hints.ai_flags    |= AI_CANONNAME;
1046 #endif
1047         ai_hints.ai_family    = obj->addrfamily;
1048         ai_hints.ai_socktype  = SOCK_RAW;
1049
1050         if ((ph = ping_alloc ()) == NULL)
1051         {
1052                 dprintf ("Out of memory!\n");
1053                 return (-1);
1054         }
1055
1056         if ((ph->username = strdup (host)) == NULL)
1057         {
1058                 dprintf ("Out of memory!\n");
1059                 ping_set_error (obj, "strdup", strerror (errno));
1060                 ping_free (ph);
1061                 return (-1);
1062         }
1063
1064         if ((ph->hostname = strdup (host)) == NULL)
1065         {
1066                 dprintf ("Out of memory!\n");
1067                 ping_set_error (obj, "strdup", strerror (errno));
1068                 ping_free (ph);
1069                 return (-1);
1070         }
1071
1072         /* obj->data is not garuanteed to be != NULL */
1073         if ((ph->data = strdup (obj->data == NULL ? PING_DEF_DATA : obj->data)) == NULL)
1074         {
1075                 dprintf ("Out of memory!\n");
1076                 ping_set_error (obj, "strdup", strerror (errno));
1077                 ping_free (ph);
1078                 return (-1);
1079         }
1080
1081         if ((ai_return = getaddrinfo (host, NULL, &ai_hints, &ai_list)) != 0)
1082         {
1083                 dprintf ("getaddrinfo failed\n");
1084                 ping_set_error (obj, "getaddrinfo",
1085 #if defined(EAI_SYSTEM)
1086                                                 (ai_return == EAI_SYSTEM)
1087                                                 ? strerror (errno) :
1088 #endif
1089                                 gai_strerror (ai_return));
1090                 ping_free (ph);
1091                 return (-1);
1092         }
1093
1094         if (ai_list == NULL)
1095                 ping_set_error (obj, "getaddrinfo", "No hosts returned");
1096
1097         for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
1098         {
1099                 ph->fd = -1;
1100
1101                 sockaddr_len = sizeof (sockaddr);
1102                 memset (&sockaddr, '\0', sockaddr_len);
1103
1104                 if (ai_ptr->ai_family == AF_INET)
1105                 {
1106                         struct sockaddr_in *si;
1107
1108                         si = (struct sockaddr_in *) &sockaddr;
1109                         si->sin_family = AF_INET;
1110                         si->sin_port   = htons (ph->ident);
1111                         si->sin_addr.s_addr = htonl (INADDR_ANY);
1112
1113                         ai_ptr->ai_socktype = SOCK_RAW;
1114                         ai_ptr->ai_protocol = IPPROTO_ICMP;
1115                 }
1116                 else if (ai_ptr->ai_family == AF_INET6)
1117                 {
1118                         struct sockaddr_in6 *si;
1119
1120                         si = (struct sockaddr_in6 *) &sockaddr;
1121                         si->sin6_family = AF_INET6;
1122                         si->sin6_port   = htons (ph->ident);
1123                         si->sin6_addr   = in6addr_any;
1124
1125                         ai_ptr->ai_socktype = SOCK_RAW;
1126                         ai_ptr->ai_protocol = IPPROTO_ICMPV6;
1127                 }
1128                 else
1129                 {
1130                         char errmsg[PING_ERRMSG_LEN];
1131
1132                         snprintf (errmsg, PING_ERRMSG_LEN, "Unknown `ai_family': %i", ai_ptr->ai_family);
1133                         errmsg[PING_ERRMSG_LEN - 1] = '\0';
1134
1135                         dprintf (errmsg);
1136                         ping_set_error (obj, "getaddrinfo", errmsg);
1137                         continue;
1138                 }
1139
1140                 /* TODO: Move this to a static function `ping_open_socket' and
1141                  * call it whenever the socket dies. */
1142                 ph->fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
1143                 if (ph->fd == -1)
1144                 {
1145                         dprintf ("socket: %s\n", strerror (errno));
1146                         ping_set_error (obj, "socket", strerror (errno));
1147                         continue;
1148                 }
1149
1150                 if (obj->srcaddr != NULL)
1151                 {
1152                         assert (obj->srcaddrlen > 0);
1153                         assert (obj->srcaddrlen <= sizeof (struct sockaddr_storage));
1154
1155                         if (bind (ph->fd, (struct sockaddr *) obj->srcaddr, obj->srcaddrlen) == -1)
1156                         {
1157                                 dprintf ("bind: %s\n", strerror (errno));
1158                                 ping_set_error (obj, "bind", strerror (errno));
1159                                 close (ph->fd);
1160                                 ph->fd = -1;
1161                                 continue;
1162                         }
1163                 }
1164
1165                 assert (sizeof (struct sockaddr_storage) >= ai_ptr->ai_addrlen);
1166                 memset (ph->addr, '\0', sizeof (struct sockaddr_storage));
1167                 memcpy (ph->addr, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
1168                 ph->addrlen = ai_ptr->ai_addrlen;
1169                 ph->addrfamily = ai_ptr->ai_family;
1170
1171 #ifdef AI_CANONNAME
1172                 if ((ai_ptr->ai_canonname != NULL)
1173                                 && (strcmp (ph->hostname, ai_ptr->ai_canonname) != 0))
1174                 {
1175                         char *old_hostname;
1176
1177                         dprintf ("ph->hostname = %s; ai_ptr->ai_canonname = %s;\n",
1178                                         ph->hostname, ai_ptr->ai_canonname);
1179
1180                         old_hostname = ph->hostname;
1181                         if ((ph->hostname = strdup (ai_ptr->ai_canonname)) == NULL)
1182                         {
1183                                 /* strdup failed, falling back to old hostname */
1184                                 ph->hostname = old_hostname;
1185                         }
1186                         else if (old_hostname != NULL)
1187                         {
1188                                 free (old_hostname);
1189                         }
1190                 }
1191 #endif /* AI_CANONNAME */
1192
1193                 break;
1194         }
1195
1196         freeaddrinfo (ai_list);
1197
1198         if (ph->fd < 0)
1199         {
1200                 ping_free (ph);
1201                 return (-1);
1202         }
1203
1204         /*
1205          * Adding in the front is much easier, but then the iterator will
1206          * return the host that was added last as first host. That's just not
1207          * nice. -octo
1208          */
1209         if (obj->head == NULL)
1210         {
1211                 obj->head = ph;
1212         }
1213         else
1214         {
1215                 pinghost_t *hptr;
1216
1217                 hptr = obj->head;
1218                 while (hptr->next != NULL)
1219                         hptr = hptr->next;
1220
1221                 assert ((hptr != NULL) && (hptr->next == NULL));
1222                 hptr->next = ph;
1223         }
1224
1225         ping_set_ttl (ph, obj->ttl);
1226
1227         return (0);
1228 }
1229
1230 int ping_host_remove (pingobj_t *obj, const char *host)
1231 {
1232         pinghost_t *pre, *cur;
1233
1234         pre = NULL;
1235         cur = obj->head;
1236
1237         while (cur != NULL)
1238         {
1239                 if (strcasecmp (host, cur->username) == 0)
1240                         break;
1241
1242                 pre = cur;
1243                 cur = cur->next;
1244         }
1245
1246         if (cur == NULL)
1247         {
1248                 ping_set_error (obj, "ping_host_remove", "Host not found");
1249                 return (-1);
1250         }
1251
1252         if (pre == NULL)
1253                 obj->head = cur->next;
1254         else
1255                 pre->next = cur->next;
1256         
1257         ping_free (cur);
1258
1259         return (0);
1260 }
1261
1262 pingobj_iter_t *ping_iterator_get (pingobj_t *obj)
1263 {
1264         return ((pingobj_iter_t *) obj->head);
1265 }
1266
1267 pingobj_iter_t *ping_iterator_next (pingobj_iter_t *iter)
1268 {
1269         return ((pingobj_iter_t *) iter->next);
1270 }
1271
1272 int ping_iterator_get_info (pingobj_iter_t *iter, int info,
1273                 void *buffer, size_t *buffer_len)
1274 {
1275         int ret = EINVAL;
1276
1277         size_t orig_buffer_len = *buffer_len;
1278
1279         switch (info)
1280         {
1281                 case PING_INFO_USERNAME:
1282                         ret = ENOMEM;
1283                         *buffer_len = strlen (iter->username) + 1;
1284                         if (orig_buffer_len <= *buffer_len)
1285                                 break;
1286                         /* Since (orig_buffer_len > *buffer_len) `strncpy'
1287                          * will copy `*buffer_len' and pad the rest of
1288                          * `buffer' with null-bytes */
1289                         strncpy (buffer, iter->username, orig_buffer_len);
1290                         ret = 0;
1291                         break;
1292
1293                 case PING_INFO_HOSTNAME:
1294                         ret = ENOMEM;
1295                         *buffer_len = strlen (iter->hostname) + 1;
1296                         if (orig_buffer_len <= *buffer_len)
1297                                 break;
1298                         /* Since (orig_buffer_len > *buffer_len) `strncpy'
1299                          * will copy `*buffer_len' and pad the rest of
1300                          * `buffer' with null-bytes */
1301                         strncpy (buffer, iter->hostname, orig_buffer_len);
1302                         ret = 0;
1303                         break;
1304
1305                 case PING_INFO_ADDRESS:
1306                         ret = getnameinfo ((struct sockaddr *) iter->addr,
1307                                         iter->addrlen,
1308                                         (char *) buffer,
1309                                         *buffer_len,
1310                                         NULL, 0,
1311                                         NI_NUMERICHOST);
1312                         if (ret != 0)
1313                         {
1314                                 if ((ret == EAI_MEMORY)
1315 #ifdef EAI_OVERFLOW
1316                                                 || (ret == EAI_OVERFLOW)
1317 #endif
1318                                    )
1319                                         ret = ENOMEM;
1320 #if defined(EAI_SYSTEM)
1321                                 else if (ret == EAI_SYSTEM)
1322                                         ret = errno;
1323 #endif
1324                                 else
1325                                         ret = EINVAL;
1326                         }
1327                         break;
1328
1329                 case PING_INFO_FAMILY:
1330                         ret = ENOMEM;
1331                         *buffer_len = sizeof (int);
1332                         if (orig_buffer_len < sizeof (int))
1333                                 break;
1334                         *((int *) buffer) = iter->addrfamily;
1335                         ret = 0;
1336                         break;
1337
1338                 case PING_INFO_LATENCY:
1339                         ret = ENOMEM;
1340                         *buffer_len = sizeof (double);
1341                         if (orig_buffer_len < sizeof (double))
1342                                 break;
1343                         *((double *) buffer) = iter->latency;
1344                         ret = 0;
1345                         break;
1346
1347                 case PING_INFO_DROPPED:
1348                         ret = ENOMEM;
1349                         *buffer_len = sizeof (uint32_t);
1350                         if (orig_buffer_len < sizeof (uint32_t))
1351                                 break;
1352                         *((uint32_t *) buffer) = iter->dropped;
1353                         ret = 0;
1354                         break;
1355
1356                 case PING_INFO_SEQUENCE:
1357                         ret = ENOMEM;
1358                         *buffer_len = sizeof (unsigned int);
1359                         if (orig_buffer_len < sizeof (unsigned int))
1360                                 break;
1361                         *((unsigned int *) buffer) = (unsigned int) iter->sequence;
1362                         ret = 0;
1363                         break;
1364
1365                 case PING_INFO_IDENT:
1366                         ret = ENOMEM;
1367                         *buffer_len = sizeof (uint16_t);
1368                         if (orig_buffer_len < sizeof (uint16_t))
1369                                 break;
1370                         *((uint16_t *) buffer) = (uint16_t) iter->ident;
1371                         ret = 0;
1372                         break;
1373
1374                 case PING_INFO_DATA:
1375                         ret = ENOMEM;
1376                         *buffer_len = strlen (iter->data);
1377                         if (orig_buffer_len < *buffer_len)
1378                                 break;
1379                         strncpy ((char *) buffer, iter->data, orig_buffer_len);
1380                         ret = 0;
1381                         break;
1382         }
1383
1384         return (ret);
1385 } /* ping_iterator_get_info */
1386
1387 void *ping_iterator_get_context (pingobj_iter_t *iter)
1388 {
1389         return (iter->context);
1390 }
1391
1392 void ping_iterator_set_context (pingobj_iter_t *iter, void *context)
1393 {
1394         iter->context = context;
1395 }