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