1 
2 //
3 // Copyright (c) 2015-2017, Denny Page
4 // All rights reserved.
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions
8 // are met:
9 //
10 // 1. Redistributions of source code must retain the above copyright
11 //    notice, this list of conditions and the following disclaimer.
12 //
13 // 2. Redistributions in binary form must reproduce the above copyright
14 //    notice, this list of conditions and the following disclaimer in the
15 //    documentation and/or other materials provided with the distribution.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
23 // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 
30 
31 // Silly that this is required for accept4 on Linux
32 #define _GNU_SOURCE
33 
34 
35 #include <stdio.h>
36 #include <errno.h>
37 #include <string.h>
38 #include <stdlib.h>
39 #include <stdarg.h>
40 #include <stdint.h>
41 #include <unistd.h>
42 #include <time.h>
43 #include <fcntl.h>
44 #include <signal.h>
45 
46 #include <netdb.h>
47 #include <sys/socket.h>
48 #include <net/if.h>
49 #include <sys/un.h>
50 #include <sys/stat.h>
51 #include <sys/file.h>
52 #include <netinet/in.h>
53 #include <netinet/ip.h>
54 #include <netinet/ip_icmp.h>
55 #include <netinet/icmp6.h>
56 #include <arpa/inet.h>
57 
58 #include <pthread.h>
59 #include <syslog.h>
60 
61 
62 // Who we are
63 static const char *             progname;
64 
65 // Process ID file
66 static unsigned int             foreground = 0;
67 static const char *             pidfile_name = NULL;
68 
69 // Flags
70 static unsigned int             flag_rewind = 0;
71 static unsigned int             flag_syslog = 0;
72 static unsigned int             flag_priority = 0;
73 
74 // String representation of target
75 #define ADDR_STR_MAX            (INET6_ADDRSTRLEN + IF_NAMESIZE + 1)
76 static char                     dest_str[ADDR_STR_MAX];
77 
78 // Time period over which we are averaging results in ms
79 static unsigned long            time_period_msec = 60000;
80 
81 // Interval between sends in ms
82 static unsigned long            send_interval_msec = 500;
83 
84 // Interval before a sequence is initially treated as lost
85 // Input from command line in ms and used in us
86 static unsigned long            loss_interval_msec = 0;
87 static unsigned long            loss_interval_usec = 0;
88 
89 // Interval between reports in ms
90 static unsigned long            report_interval_msec = 1000;
91 
92 // Interval between alert checks in ms
93 static unsigned long            alert_interval_msec = 1000;
94 
95 // Threshold for triggering alarms based on latency
96 // Input from command line in ms and used in us
97 static unsigned long            latency_alarm_threshold_msec = 0;
98 static unsigned long            latency_alarm_threshold_usec = 0;
99 
100 // Threshold for triggering alarms based on loss percentage
101 static unsigned long            loss_alarm_threshold_percent = 0;
102 
103 // Command to invoke for alerts
104 static char *                   alert_cmd = NULL;
105 static size_t                   alert_cmd_offset;
106 
107 // Number of periods to wait to declare an alarm as cleared
108 #define ALARM_DECAY_PERIODS     10
109 
110 // Report file
111 static const char *             report_name = NULL;
112 static int                      report_fd;
113 
114 // Unix socket
115 static const char *             usocket_name = NULL;
116 static int                      usocket_fd;
117 
118 static char                     identifier[64] = "\0";
119 
120 // Length of maximum output (dest_str alarm_flag average_latency_usec latency_deviation average_loss_percent)
121 #define OUTPUT_MAX              (sizeof(identifier) + sizeof(dest_str) + sizeof(" 1 999999999999 999999999999 100\0"))
122 
123 
124 // Main ping status array
125 typedef struct
126 {
127     enum
128     {
129         PACKET_STATUS_EMPTY     = 0,
130         PACKET_STATUS_SENT      = 1,
131         PACKET_STATUS_RECEIVED  = 2
132     } status;
133 
134     struct timespec             time_sent;
135     unsigned long               latency_usec;
136 } ping_entry_t;
137 
138 static ping_entry_t *           array;
139 static unsigned int             array_size;
140 static unsigned int             next_slot = 0;
141 
142 
143 // Sockets used to send and receive
144 static int                      send_sock;
145 static int                      recv_sock;
146 
147 // IPv4 / IPv6 parameters
148 static uint16_t                 af_family = AF_INET;                    // IPv6: AF_INET6
149 static uint8_t                  echo_request_type = ICMP_ECHO;          // IPv6: ICMP6_ECHO_REQUEST
150 static uint8_t                  echo_reply_type = ICMP_ECHOREPLY;       // IPv6: ICMP6_ECHO_REPLY
151 static int                      ip_proto = IPPROTO_ICMP;                // IPv6: IPPROTO_ICMPV6
152 
153 // Destination address
154 static struct sockaddr_storage  dest_addr;
155 static socklen_t                dest_addr_len;
156 
157 // Source (bind) address
158 static struct sockaddr_storage  bind_addr;
159 static socklen_t                bind_addr_len = 0;
160 
161 // ICMP echo request/reply header
162 //
163 // The physical layout of the ICMP is the same between IPv4 and IPv6 so we define our
164 // own type for convenience
165 typedef struct
166 {
167     uint8_t                     type;
168     uint8_t                     code;
169     uint16_t                    cksum;
170     uint16_t                    id;
171     uint16_t                    sequence;
172 } icmphdr_t;
173 
174 // Echo request/reply packet buffers
175 #define IPV4_ICMP_DATA_MAX      (IP_MAXPACKET - sizeof(struct ip) - sizeof(icmphdr_t))
176 #define IPV6_ICMP_DATA_MAX      (IP_MAXPACKET - sizeof(icmphdr_t))
177 #define PACKET_BUFLEN           (IP_MAXPACKET + 256)
178 
179 static unsigned long            echo_data_len = 0;
180 static unsigned int             echo_request_len = sizeof(icmphdr_t);
181 static unsigned int             echo_reply_len = IP_MAXPACKET;
182 static icmphdr_t *              echo_request;
183 static void *                   echo_reply;
184 
185 // Echo id and Sequence information
186 static uint16_t                 echo_id;
187 static uint16_t                 next_sequence = 0;
188 static uint16_t                 sequence_limit;
189 
190 // Receive thread ready
191 static unsigned int             recv_ready = 0;
192 
193 //
194 // Termination handler
195 //
196 __attribute__ ((noreturn))
197 static void
term_handler(void)198 term_handler(void)
199 {
200     // NB: This function may be simultaneously invoked by multiple threads
201     if (usocket_name)
202     {
203         (void) unlink(usocket_name);
204     }
205     if (pidfile_name)
206     {
207         (void) unlink(pidfile_name);
208     }
209     exit(0);
210 }
211 
212 
213 //
214 // Log for abnormal events
215 //
216 __attribute__ ((format (printf, 1, 2)))
217 static void
logger(const char * format,...)218 logger(
219     const char *                format,
220     ...)
221 {
222     va_list                     args;
223 
224     va_start(args, format);
225     if (flag_syslog)
226     {
227         vsyslog(LOG_WARNING, format, args);
228     }
229     else
230     {
231         vfprintf(stderr, format, args);
232     }
233     va_end(args);
234 }
235 
236 
237 //
238 // Compute checksum for ICMP
239 //
240 static uint16_t
cksum(const uint16_t * p,int len)241 cksum(
242     const uint16_t *            p,
243     int                         len)
244 {
245     uint32_t                    sum = 0;
246 
247     while (len > 1)
248     {
249         sum += *p++;
250         len -= sizeof(*p);
251     }
252 
253     if (len == 1)
254     {
255         sum += (uint16_t) *((const uint8_t *) p);
256     }
257 
258     sum = (sum >> 16) + (sum & 0xFFFF);
259     sum += (sum >> 16);
260 
261     return (uint16_t) ~sum;
262 }
263 
264 
265 //
266 // sqrt function for standard deviation
267 //
268 static unsigned long
llsqrt(unsigned long long x)269 llsqrt(
270     unsigned long long          x)
271 {
272     unsigned long long          prev;
273     unsigned long long          s;
274 
275     s = x;
276     if (s)
277     {
278         prev = ~((unsigned long long) 1 << 63);
279 
280         while (s < prev)
281         {
282             prev = s;
283             s = (s + (x / s)) / 2;
284         }
285     }
286 
287     return (unsigned long) s;
288 }
289 
290 
291 //
292 // Compute delta between old time and new time in microseconds
293 //
294 static unsigned long
ts_elapsed_usec(const struct timespec * old,const struct timespec * new)295 ts_elapsed_usec(
296     const struct timespec *     old,
297     const struct timespec *     new)
298 {
299     long                        r_usec;
300 
301     // Note that we are using monotonic clock and time cannot run backwards
302     if (new->tv_nsec >= old->tv_nsec)
303     {
304         r_usec = (new->tv_sec - old->tv_sec) * 1000000 + (new->tv_nsec - old->tv_nsec) / 1000;
305     }
306     else
307     {
308         r_usec = (new->tv_sec - old->tv_sec - 1) * 1000000 + (1000000000 + new->tv_nsec - old->tv_nsec) / 1000;
309     }
310 
311     return (unsigned long) r_usec;
312 }
313 
314 
315 //
316 // Send thread
317 //
318 __attribute__ ((noreturn))
319 static void *
send_thread(void * arg)320 send_thread(
321     __attribute__ ((unused))
322     void *                      arg)
323 {
324     struct timespec             sleeptime;
325     ssize_t                     len;
326     int                         r;
327 
328     // Set up our echo request packet
329     memset(echo_request, 0, echo_request_len);
330     echo_request->type = echo_request_type;
331     echo_request->code = 0;
332     echo_request->id = echo_id;
333 
334     // Give the recv thread a moment to initialize
335     sleeptime.tv_sec = 0;
336     sleeptime.tv_nsec = 10000; // 10us
337     do {
338         r = nanosleep(&sleeptime, NULL);
339         if (r == -1)
340         {
341             logger("nanosleep error in send thread waiting for recv thread: %d\n", errno);
342         }
343     } while (recv_ready == 0);
344 
345     // Set up the timespec for nanosleep
346     sleeptime.tv_sec = send_interval_msec / 1000;
347     sleeptime.tv_nsec = (send_interval_msec % 1000) * 1000000;
348 
349     while (1)
350     {
351         // Set sequence number and checksum
352         echo_request->sequence = htons(next_sequence);
353         echo_request->cksum = 0;
354         echo_request->cksum = cksum((uint16_t *) echo_request, sizeof(icmphdr_t));
355 
356         array[next_slot].status = PACKET_STATUS_EMPTY;
357         sched_yield();
358 
359         clock_gettime(CLOCK_MONOTONIC, &array[next_slot].time_sent);
360         array[next_slot].status = PACKET_STATUS_SENT;
361         len = sendto(send_sock, echo_request, echo_request_len, 0, (struct sockaddr *) &dest_addr, dest_addr_len);
362         if (len == -1)
363         {
364             logger("%s%s: sendto error: %d\n", identifier, dest_str, errno);
365         }
366 
367         next_slot = (next_slot + 1) % array_size;
368         next_sequence = (next_sequence + 1) % sequence_limit;
369 
370         r = nanosleep(&sleeptime, NULL);
371         if (r == -1)
372         {
373             logger("nanosleep error in send thread: %d\n", errno);
374         }
375     }
376 }
377 
378 
379 //
380 // Receive thread
381 //
382 __attribute__ ((noreturn))
383 static void *
recv_thread(void * arg)384 recv_thread(
385     __attribute__ ((unused))
386     void *                      arg)
387 {
388     struct sockaddr_storage     src_addr;
389     socklen_t                   src_addr_len;
390     ssize_t                     len;
391     icmphdr_t *                 icmp;
392     struct timespec             now;
393     unsigned int                array_slot;
394 
395     // Thread startup complete
396     recv_ready = 1;
397 
398     while (1)
399     {
400         src_addr_len = sizeof(src_addr);
401         len = recvfrom(recv_sock, echo_reply, echo_reply_len, 0, (struct sockaddr *) &src_addr, &src_addr_len);
402         if (len == -1)
403         {
404             logger("%s%s: recvfrom error: %d\n", identifier, dest_str, errno);
405             continue;
406         }
407         clock_gettime(CLOCK_MONOTONIC, &now);
408 
409         if (af_family == AF_INET)
410         {
411             struct ip *         ip;
412             size_t              ip_len;
413 
414             // With IPv4, we get the entire IP packet
415             if (len < (ssize_t) sizeof(struct ip))
416             {
417                 logger("%s%s: received packet too small for IP header\n", identifier, dest_str);
418                 continue;
419             }
420             ip = echo_reply;
421             ip_len = (size_t) ip->ip_hl << 2;
422 
423             icmp = (void *) ((char *) ip + ip_len);
424             len -= ip_len;
425         }
426         else
427         {
428             // With IPv6, we just get the ICMP payload
429             icmp = echo_reply;
430         }
431 
432         // This should never happen
433         if (len < (ssize_t) sizeof(icmphdr_t))
434         {
435             logger("%s%s: received packet too small for ICMP header\n", identifier, dest_str);
436             continue;
437         }
438 
439         // If it's not an echo reply for us, skip the packet
440         if (icmp->type != echo_reply_type || icmp->id != echo_id)
441         {
442             continue;
443         }
444 
445         array_slot = ntohs(icmp->sequence) % array_size;
446         if (array[array_slot].status == PACKET_STATUS_RECEIVED)
447         {
448             logger("%s%s: duplicate echo reply received\n", identifier, dest_str);
449             continue;
450         }
451 
452         array[array_slot].latency_usec = ts_elapsed_usec(&array[array_slot].time_sent, &now);
453         array[array_slot].status = PACKET_STATUS_RECEIVED;
454     }
455 }
456 
457 
458 //
459 // Generate a report
460 //
461 static void
report(unsigned long * average_latency_usec,unsigned long * latency_deviation,unsigned long * average_loss_percent)462 report(
463     unsigned long               *average_latency_usec,
464     unsigned long               *latency_deviation,
465     unsigned long               *average_loss_percent)
466 {
467     struct timespec             now;
468     unsigned long               packets_received = 0;
469     unsigned long               packets_lost = 0;
470     unsigned long               latency_usec = 0;
471     unsigned long               total_latency_usec = 0;
472     unsigned long long          total_latency_usec2 = 0;
473     unsigned int                slot;
474     unsigned int                i;
475 
476     clock_gettime(CLOCK_MONOTONIC, &now);
477 
478     slot = next_slot;
479     for (i = 0; i < array_size; i++)
480     {
481         if (array[slot].status == PACKET_STATUS_RECEIVED)
482         {
483             packets_received++;
484             latency_usec = array[slot].latency_usec;
485             total_latency_usec += latency_usec;
486             total_latency_usec2 += (unsigned long long) latency_usec * latency_usec;
487         }
488         else if (array[slot].status == PACKET_STATUS_SENT &&
489                  ts_elapsed_usec(&array[slot].time_sent, &now) > loss_interval_usec)
490         {
491             packets_lost++;
492         }
493 
494         slot = (slot + 1) % array_size;
495     }
496 
497     if (packets_received)
498     {
499         unsigned long           avg = total_latency_usec / packets_received;
500         unsigned long long      avg2 = total_latency_usec2 / packets_received;
501 
502         // stddev = sqrt((sum(rtt^2) / packets) - (sum(rtt) / packets)^2)
503         *average_latency_usec = avg;
504         *latency_deviation = llsqrt(avg2 - ((unsigned long long) avg * avg));
505     }
506     else
507     {
508         *average_latency_usec = 0;
509         *latency_deviation = 0;
510     }
511 
512     if (packets_lost)
513     {
514         *average_loss_percent = packets_lost * 100 / (packets_received + packets_lost);
515     }
516     else
517     {
518         *average_loss_percent = 0;
519     }
520 }
521 
522 
523 //
524 // Report thread
525 //
526 __attribute__ ((noreturn))
527 static void *
report_thread(void * arg)528 report_thread(
529     __attribute__ ((unused))
530     void *                      arg)
531 {
532     char                        buf[OUTPUT_MAX];
533     struct timespec             sleeptime;
534     unsigned long               average_latency_usec;
535     unsigned long               latency_deviation;
536     unsigned long               average_loss_percent;
537     ssize_t                     len;
538     ssize_t                     rs;
539     int                         r;
540 
541     // Set up the timespec for nanosleep
542     sleeptime.tv_sec = report_interval_msec / 1000;
543     sleeptime.tv_nsec = (report_interval_msec % 1000) * 1000000;
544 
545     while (1)
546     {
547         r = nanosleep(&sleeptime, NULL);
548         if (r == -1)
549         {
550             logger("nanosleep error in report thread: %d\n", errno);
551         }
552 
553         report(&average_latency_usec, &latency_deviation, &average_loss_percent);
554 
555         len = snprintf(buf, sizeof(buf), "%s%lu %lu %lu\n", identifier, average_latency_usec, latency_deviation, average_loss_percent);
556         if (len < 0 || (size_t) len > sizeof(buf))
557         {
558             logger("error formatting output in report thread\n");
559         }
560 
561         rs = write(report_fd, buf, (size_t) len);
562         if (rs == -1)
563         {
564             logger("write error in report thread: %d\n", errno);
565         }
566         else if (rs != len)
567         {
568             logger("short write in report thread: %zd/%zd\n", rs, len);
569         }
570 
571         if (flag_rewind)
572         {
573             (void) ftruncate(report_fd, len);
574             (void) lseek(report_fd, SEEK_SET, 0);
575         }
576     }
577 }
578 
579 
580 //
581 // Alert thread
582 //
583 __attribute__ ((noreturn))
584 static void *
alert_thread(void * arg)585 alert_thread(
586     __attribute__ ((unused))
587     void *                      arg)
588 {
589     struct timespec             sleeptime;
590     unsigned long               average_latency_usec;
591     unsigned long               latency_deviation;
592     unsigned long               average_loss_percent;
593     unsigned int                latency_alarm_decay = 0;
594     unsigned int                loss_alarm_decay = 0;
595     unsigned int                alert = 0;
596     unsigned int                alarm_on;
597     int                         r;
598 
599     // Set up the timespec for nanosleep
600     sleeptime.tv_sec = alert_interval_msec / 1000;
601     sleeptime.tv_nsec = (alert_interval_msec % 1000) * 1000000;
602 
603     while (1)
604     {
605         r = nanosleep(&sleeptime, NULL);
606         if (r == -1)
607         {
608             logger("nanosleep error in alert thread: %d\n", errno);
609         }
610 
611         report(&average_latency_usec, &latency_deviation, &average_loss_percent);
612 
613         if (latency_alarm_threshold_usec)
614         {
615             if (average_latency_usec > latency_alarm_threshold_usec)
616             {
617                 if (latency_alarm_decay == 0)
618                 {
619                     alert = 1;
620                 }
621 
622                 latency_alarm_decay = ALARM_DECAY_PERIODS;
623             }
624             else if (latency_alarm_decay)
625             {
626                 latency_alarm_decay--;
627                 if (latency_alarm_decay == 0)
628                 {
629                     alert = 1;
630                 }
631             }
632         }
633 
634         if (loss_alarm_threshold_percent)
635         {
636             if (average_loss_percent > loss_alarm_threshold_percent)
637             {
638                 if (loss_alarm_decay == 0)
639                 {
640                     alert = 1;
641                 }
642 
643                 loss_alarm_decay = ALARM_DECAY_PERIODS;
644             }
645             else if (loss_alarm_decay)
646             {
647                 loss_alarm_decay--;
648                 if (loss_alarm_decay == 0)
649                 {
650                     alert = 1;
651                 }
652             }
653         }
654 
655         if (alert)
656         {
657             alert = 0;
658 
659             alarm_on = latency_alarm_decay || loss_alarm_decay;
660             logger("%s%s: %s latency %luus stddev %luus loss %lu%%\n", identifier, dest_str, alarm_on ? "Alarm" : "Clear", average_latency_usec, latency_deviation, average_loss_percent);
661 
662             if (alert_cmd)
663             {
664                 r = snprintf(alert_cmd + alert_cmd_offset, OUTPUT_MAX, " %s%s %u %lu %lu %lu", identifier, dest_str, alarm_on, average_latency_usec, latency_deviation, average_loss_percent);
665                 if (r < 0 || (size_t) r >= OUTPUT_MAX)
666                 {
667                     logger("error formatting command in alert thread\n");
668                     continue;
669                 }
670 
671                 // Note that system waits for the alert command to finish before returning
672                 r = system(alert_cmd);
673                 if (r == -1)
674                 {
675                     logger("error executing command in alert thread\n");
676                 }
677             }
678         }
679     }
680 }
681 
682 //
683 // Unix socket thread
684 //
685 __attribute__ ((noreturn))
686 static void *
usocket_thread(void * arg)687 usocket_thread(
688     __attribute__ ((unused))
689     void *                      arg)
690 {
691     char                        buf[OUTPUT_MAX];
692     unsigned long               average_latency_usec;
693     unsigned long               latency_deviation;
694     unsigned long               average_loss_percent;
695     int                         sock_fd;
696     ssize_t                     len;
697     ssize_t                     rs;
698     int                         r;
699 
700     while (1)
701     {
702 #if defined(DISABLE_ACCEPT4)
703         // Legacy
704         sock_fd = accept(usocket_fd, NULL, NULL);
705         (void) fcntl(sock_fd, F_SETFL, FD_CLOEXEC);
706         (void) fcntl(sock_fd, F_SETFL, fcntl(sock_fd, F_GETFL, 0) | O_NONBLOCK);
707 #else
708         sock_fd = accept4(usocket_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
709 #endif
710 
711         report(&average_latency_usec, &latency_deviation, &average_loss_percent);
712 
713         len = snprintf(buf, sizeof(buf), "%s%lu %lu %lu\n", identifier, average_latency_usec, latency_deviation, average_loss_percent);
714         if (len < 0 || (size_t) len > sizeof(buf))
715         {
716             logger("error formatting output in usocket thread\n");
717         }
718 
719         rs = write(sock_fd, buf, (size_t) len);
720         if (rs == -1)
721         {
722             logger("write error in usocket thread: %d\n", errno);
723         }
724         else if (rs != len)
725         {
726             logger("short write in usocket thread: %zd/%zd\n", rs, len);
727         }
728 
729         r = close(sock_fd);
730         if (r == -1)
731         {
732             logger("close error in usocket thread: %d\n", errno);
733         }
734     }
735 }
736 
737 
738 
739 //
740 // Decode a time argument
741 //
742 static int
get_time_arg_msec(const char * arg,unsigned long * value)743 get_time_arg_msec(
744     const char *                arg,
745     unsigned long *             value)
746 {
747     long                        t;
748     char *                      suffix;
749 
750     t = strtol(arg, &suffix, 10);
751     if (*suffix == 'm')
752     {
753         // Milliseconds
754         suffix++;
755     }
756     else if (*suffix == 's')
757     {
758         // Seconds
759         t *= 1000;
760         suffix++;
761     }
762 
763     // Invalid specification?
764     if (t < 0 || *suffix != 0)
765     {
766         return 1;
767     }
768 
769     *value = (unsigned long) t;
770     return 0;
771 }
772 
773 
774 //
775 // Decode a percent argument
776 //
777 static int
get_percent_arg(const char * arg,unsigned long * value)778 get_percent_arg(
779     const char *                arg,
780     unsigned long *             value)
781 {
782     long                        t;
783     char *                      suffix;
784 
785     t = strtol(arg, &suffix, 10);
786     if (*suffix == '%')
787     {
788         suffix++;
789     }
790 
791     // Invalid specification?
792     if (t < 0 || t > 100 || *suffix != 0)
793     {
794         return 1;
795     }
796 
797     *value = (unsigned long) t;
798     return 0;
799 }
800 
801 
802 //
803 // Decode a byte length argument
804 //
805 static int
get_length_arg(const char * arg,unsigned long * value)806 get_length_arg(
807     const char *                arg,
808     unsigned long *             value)
809 {
810     long                        t;
811     char *                      suffix;
812 
813     t = strtol(arg, &suffix, 10);
814     if (*suffix == 'b')
815     {
816         // Bytes
817         suffix++;
818     }
819     else if (*suffix == 'k')
820     {
821         // Kilobytes
822         t *= 1024;
823         suffix++;
824     }
825 
826     // Invalid specification?
827     if (t < 0 || *suffix != 0)
828     {
829         return 1;
830     }
831 
832     *value = (unsigned long) t;
833     return 0;
834 }
835 
836 
837 //
838 // Output usage
839 //
840 static void
usage(void)841 usage(void)
842 {
843     fprintf(stderr, "Usage:\n");
844     fprintf(stderr, "  %s [-f] [-R] [-S] [-P] [-B bind_addr] [-s send_interval] [-l loss_interval] [-t time_period] [-r report_interval] [-d data_length] [-o output_file] [-A alert_interval] [-D latency_alarm] [-L loss_alarm] [-C alert_cmd] [-i identifier] [-u usocket] [-p pidfile] dest_addr\n\n", progname);
845     fprintf(stderr, "  options:\n");
846     fprintf(stderr, "    -f run in foreground\n");
847     fprintf(stderr, "    -R rewind output file between reports\n");
848     fprintf(stderr, "    -S log warnings via syslog\n");
849     fprintf(stderr, "    -P priority scheduling for receive thread (requires root)\n");
850     fprintf(stderr, "    -B bind (source) address\n");
851     fprintf(stderr, "    -s time interval between echo requests (default 500ms)\n");
852     fprintf(stderr, "    -l time interval before packets are treated as lost (default 4x send interval)\n");
853     fprintf(stderr, "    -t time period over which results are averaged (default 60s)\n");
854     fprintf(stderr, "    -r time interval between reports (default 1s)\n");
855     fprintf(stderr, "    -d data length (default 0)\n");
856     fprintf(stderr, "    -o output file for reports (default stdout)\n");
857     fprintf(stderr, "    -A time interval between alerts (default 1s)\n");
858     fprintf(stderr, "    -D time threshold for latency alarm (default none)\n");
859     fprintf(stderr, "    -L percent threshold for loss alarm (default none)\n");
860     fprintf(stderr, "    -C optional command to be invoked via system() for alerts\n");
861     fprintf(stderr, "    -i identifier text to include in output\n");
862     fprintf(stderr, "    -u unix socket name for polling\n");
863     fprintf(stderr, "    -p process id file name\n\n");
864     fprintf(stderr, "  notes:\n");
865     fprintf(stderr, "    IP addresses can be in either IPv4 or IPv6 format\n\n");
866     fprintf(stderr, "    time values can be expressed with a suffix of 'm' (milliseconds) or 's' (seconds)\n");
867     fprintf(stderr, "    if no suffix is specified, milliseconds is the default\n\n");
868     fprintf(stderr, "    the output format is \"latency_avg latency_stddev loss_pct\"\n");
869     fprintf(stderr, "    latency values are output in microseconds\n");
870     fprintf(stderr, "    loss percentage is reported in whole numbers of 0-100\n");
871     fprintf(stderr, "    resolution of loss calculation is: 100 * send_interval / (time_period - loss_interval)\n\n");
872     fprintf(stderr, "    the alert_cmd is invoked as \"alert_cmd dest_addr alarm_flag latency_avg loss_avg\"\n");
873     fprintf(stderr, "    alarm_flag is set to 1 if either latency or loss is in alarm state\n");
874     fprintf(stderr, "    alarm_flag will return to 0 when both have have cleared alarm state\n\n");
875 }
876 
877 
878 //
879 // Fatal error
880 //
881 __attribute__ ((noreturn, format (printf, 1, 2)))
882 static void
fatal(const char * format,...)883 fatal(
884     const char *                format,
885     ...)
886 {
887     va_list                 args;
888 
889     va_start(args, format);
890     vfprintf(stderr, format, args);
891     va_end(args);
892 
893     exit(EXIT_FAILURE);
894 }
895 
896 
897 //
898 // Parse command line arguments
899 //
900 static void
parse_args(int argc,char * const argv[])901 parse_args(
902     int                         argc,
903     char * const                argv[])
904 {
905     struct addrinfo             hint;
906     struct addrinfo *           addr_info;
907     const char *                dest_arg;
908     const char *                bind_arg = NULL;
909     size_t                      len;
910     int                         opt;
911     int                         r;
912 
913     progname = argv[0];
914 
915     while((opt = getopt(argc, argv, "fRSPB:s:l:t:r:d:o:A:D:L:C:i:u:p:")) != -1)
916     {
917         switch (opt)
918         {
919         case 'f':
920             foreground = 1;
921             break;
922 
923         case 'R':
924             flag_rewind = 1;
925             break;
926 
927         case 'S':
928             flag_syslog = 1;
929             break;
930 
931         case 'P':
932             flag_priority = 1;
933             break;
934 
935         case 'B':
936             bind_arg = optarg;
937             break;
938 
939         case 's':
940             r = get_time_arg_msec(optarg, &send_interval_msec);
941             if (r || send_interval_msec == 0)
942             {
943                 fatal("invalid send interval %s\n", optarg);
944             }
945             break;
946 
947         case 'l':
948             r = get_time_arg_msec(optarg, &loss_interval_msec);
949             if (r || loss_interval_msec == 0)
950             {
951                 fatal("invalid loss interval %s\n", optarg);
952             }
953             break;
954 
955         case 't':
956             r = get_time_arg_msec(optarg, &time_period_msec);
957             if (r || time_period_msec == 0)
958             {
959                 fatal("invalid averaging time period %s\n", optarg);
960             }
961             break;
962 
963         case 'r':
964             r = get_time_arg_msec(optarg, &report_interval_msec);
965             if (r)
966             {
967                 fatal("invalid report interval %s\n", optarg);
968             }
969             break;
970 
971         case 'd':
972             r = get_length_arg(optarg, &echo_data_len);
973             if (r)
974             {
975                 fatal("invalid data length %s\n", optarg);
976             }
977             break;
978 
979         case 'o':
980             report_name = optarg;
981             break;
982 
983         case 'A':
984             r = get_time_arg_msec(optarg, &alert_interval_msec);
985             if (r || alert_interval_msec == 0)
986             {
987                 fatal("invalid alert interval %s\n", optarg);
988             }
989             break;
990 
991         case 'D':
992             r = get_time_arg_msec(optarg, &latency_alarm_threshold_msec);
993             if (r)
994             {
995                 fatal("invalid latency alarm threshold %s\n", optarg);
996             }
997             latency_alarm_threshold_usec = latency_alarm_threshold_msec * 1000;
998             break;
999 
1000         case 'L':
1001             r = get_percent_arg(optarg, &loss_alarm_threshold_percent);
1002             if (r)
1003             {
1004                 fatal("invalid loss alarm threshold %s\n", optarg);
1005             }
1006             break;
1007 
1008         case 'C':
1009             alert_cmd_offset = strlen(optarg);
1010             alert_cmd = malloc(alert_cmd_offset + OUTPUT_MAX);
1011             if (alert_cmd == NULL)
1012             {
1013                 fatal("malloc of alert command buffer failed\n");
1014             }
1015             memcpy(alert_cmd, optarg, alert_cmd_offset);
1016             break;
1017 
1018         case 'i':
1019             len = strlen(optarg);
1020             if (len >= sizeof(identifier) - 1)
1021             {
1022                 fatal("identifier argument too large (max %u bytes)\n", (unsigned) sizeof(identifier) - 1);
1023             }
1024             // optarg with a space appended
1025             memcpy(identifier, optarg, len);
1026             identifier[len] = ' ';
1027             identifier[len + 1] = '\0';
1028             break;
1029 
1030         case 'u':
1031             usocket_name = optarg;
1032             break;
1033 
1034         case 'p':
1035             pidfile_name = optarg;
1036             break;
1037 
1038         default:
1039             usage();
1040             exit(EXIT_FAILURE);
1041         }
1042     }
1043 
1044     // Ensure we have the correct number of parameters
1045     if (argc != optind + 1)
1046     {
1047         usage();
1048         exit(EXIT_FAILURE);
1049     }
1050     dest_arg = argv[optind];
1051 
1052     // Ensure we have something to do: at least one of alarm, report, socket
1053     if (report_interval_msec == 0 && latency_alarm_threshold_msec == 0 && loss_alarm_threshold_percent == 0 && usocket_name == NULL)
1054     {
1055         fatal("no activity enabled\n");
1056     }
1057 
1058     // Ensure there is a minimum of one resolved slot at all times
1059     if (time_period_msec <= send_interval_msec * 2 + loss_interval_msec)
1060     {
1061         fatal("the time period must be greater than twice the send interval plus the loss interval\n");
1062     }
1063 
1064     // Ensure we don't have sequence space issues. This really should only be hit by
1065     // complete accident. Even a ratio of 16384:1 would be excessive.
1066     if (time_period_msec / send_interval_msec > 65536)
1067     {
1068         fatal("the ratio of time period to send interval cannot exceed 65536:1\n");
1069     }
1070 
1071     // Check destination address
1072     memset(&hint, 0, sizeof(struct addrinfo));
1073     hint.ai_flags = AI_NUMERICHOST;
1074     hint.ai_family = AF_UNSPEC;
1075     hint.ai_socktype = SOCK_RAW;
1076 
1077     r = getaddrinfo(dest_arg, NULL, &hint, &addr_info);
1078     if (r != 0)
1079     {
1080         fatal("invalid destination IP address %s\n", dest_arg);
1081     }
1082 
1083     if (addr_info->ai_family == AF_INET6)
1084     {
1085         af_family = AF_INET6;
1086         ip_proto = IPPROTO_ICMPV6;
1087         echo_request_type = ICMP6_ECHO_REQUEST;
1088         echo_reply_type = ICMP6_ECHO_REPLY;
1089     }
1090     else if (addr_info->ai_family != AF_INET)
1091     {
1092         fatal("invalid destination IP address %s\n", dest_arg);
1093     }
1094 
1095 
1096     dest_addr_len = addr_info->ai_addrlen;
1097     memcpy(&dest_addr, addr_info->ai_addr, dest_addr_len);
1098     freeaddrinfo(addr_info);
1099 
1100     // Check bind address
1101     if (bind_arg)
1102     {
1103         // Address family must match
1104         hint.ai_family = af_family;
1105 
1106         r = getaddrinfo(bind_arg, NULL, &hint, &addr_info);
1107         if (r != 0)
1108         {
1109            fatal("invalid bind IP address %s\n", bind_arg);
1110         }
1111 
1112         bind_addr_len = addr_info->ai_addrlen;
1113         memcpy(&bind_addr, addr_info->ai_addr, bind_addr_len);
1114         freeaddrinfo(addr_info);
1115     }
1116 
1117     // Check requested data length
1118     if (echo_data_len)
1119     {
1120         if (af_family == AF_INET)
1121         {
1122             if (echo_data_len > IPV4_ICMP_DATA_MAX)
1123             {
1124                 fatal("data length too large for IPv4 - maximum is %u bytes\n", (unsigned) IPV4_ICMP_DATA_MAX);
1125             }
1126         }
1127         else
1128         {
1129             if (echo_data_len > IPV6_ICMP_DATA_MAX)
1130             {
1131                 fatal("data length too large for IPv6 - maximum is %u bytes\n", (unsigned) IPV6_ICMP_DATA_MAX);
1132             }
1133         }
1134 
1135         echo_request_len += echo_data_len;
1136     }
1137 }
1138 
1139 
1140 //
1141 // Main
1142 //
1143 int
main(int argc,char * argv[])1144 main(
1145     int                         argc,
1146     char                        *argv[])
1147 {
1148     char                        bind_str[ADDR_STR_MAX] = "(none)";
1149     char                        pidbuf[64];
1150     int                         pidfile_fd = -1;
1151     pid_t                       pid;
1152     pthread_t                   thread;
1153     struct                      sigaction act;
1154     int                         buflen = PACKET_BUFLEN;
1155     ssize_t                     len;
1156     ssize_t                     rs;
1157     int                         r;
1158 
1159     // Handle command line args
1160     parse_args(argc, argv);
1161 
1162     // Set up our sockets
1163     send_sock = socket(af_family, SOCK_RAW, ip_proto);
1164     if (send_sock == -1)
1165     {
1166         perror("socket");
1167         fatal("cannot create send socket\n");
1168     }
1169     (void) fcntl(send_sock, F_SETFL, FD_CLOEXEC);
1170     (void) setsockopt(send_sock, SOL_SOCKET, SO_SNDBUF, &buflen, sizeof(buflen));
1171 
1172     recv_sock = socket(af_family, SOCK_RAW, ip_proto);
1173     if (recv_sock == -1)
1174     {
1175         perror("socket");
1176         fatal("cannot create recv socket\n");
1177     }
1178     (void) fcntl(recv_sock, F_SETFL, FD_CLOEXEC);
1179     (void) setsockopt(recv_sock, SOL_SOCKET, SO_RCVBUF, &buflen, sizeof(buflen));
1180 
1181     // Bind our sockets to an address if requested
1182     if (bind_addr_len)
1183     {
1184         r = bind(send_sock, (struct sockaddr *) &bind_addr, bind_addr_len);
1185         if (r == -1)
1186         {
1187             perror("bind");
1188             fatal("cannot bind send socket\n");
1189         }
1190         r = bind(recv_sock, (struct sockaddr *) &bind_addr, bind_addr_len);
1191         if (r == -1)
1192         {
1193             perror("bind");
1194             fatal("cannot bind recv socket\n");
1195         }
1196     }
1197 
1198     // Drop privileges
1199     (void) setgid(getgid());
1200     (void) setuid(getuid());
1201 
1202     // Create pid file
1203     if (pidfile_name)
1204     {
1205         pidfile_fd = open(pidfile_name, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
1206         if (pidfile_fd != -1)
1207         {
1208             // Lock the pid file
1209             r = flock(pidfile_fd, LOCK_EX | LOCK_NB);
1210             if (r == -1)
1211             {
1212                 perror("flock");
1213                 fatal("error locking pid file\n");
1214             }
1215         }
1216         else
1217         {
1218             // Pid file already exists?
1219             pidfile_fd = open(pidfile_name, O_RDWR | O_CREAT | O_CLOEXEC, 0644);
1220             if (pidfile_fd == -1)
1221             {
1222                 perror("open");
1223                 fatal("cannot create/open pid file %s\n", pidfile_name);
1224             }
1225 
1226             // Lock the pid file
1227             r = flock(pidfile_fd, LOCK_EX | LOCK_NB);
1228             if (r == -1)
1229             {
1230                 fatal("pid file %s is in use by another process\n", pidfile_name);
1231             }
1232 
1233             // Check for existing pid
1234             rs = read(pidfile_fd, pidbuf, sizeof(pidbuf) - 1);
1235             if (rs > 0)
1236             {
1237                 pidbuf[rs] = 0;
1238 
1239                 pid = (pid_t) strtol(pidbuf, NULL, 10);
1240                 if (pid > 0)
1241                 {
1242                     // Is the pid still alive?
1243                     r = kill(pid, 0);
1244                     if (r == 0)
1245                     {
1246                         fatal("pid file %s is in use by process %u\n", pidfile_name, (unsigned int) pid);
1247                     }
1248                 }
1249             }
1250 
1251             // Reset the pid file
1252             (void) lseek(pidfile_fd, 0, 0);
1253             r = ftruncate(pidfile_fd, 0);
1254             if (r == -1)
1255             {
1256                 perror("ftruncate");
1257                 fatal("cannot write pid file %s\n", pidfile_name);
1258             }
1259         }
1260     }
1261 
1262     // Create report file
1263     if (report_name)
1264     {
1265         report_fd = open(report_name, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
1266         if (report_fd == -1)
1267         {
1268             perror("open");
1269             fatal("cannot open/create report file %s\n", report_name);
1270         }
1271     }
1272     else
1273     {
1274         report_fd = fileno(stdout);
1275     }
1276 
1277     // Create unix socket
1278     if (usocket_name)
1279     {
1280         struct sockaddr_un      uaddr;
1281 
1282         if (strlen(usocket_name) >= sizeof(uaddr.sun_path))
1283         {
1284             fatal("socket name too large\n");
1285         }
1286 
1287         usocket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
1288         if (usocket_fd == -1)
1289         {
1290             perror("socket");
1291             fatal("cannot create unix domain socket\n");
1292         }
1293         (void) fcntl(usocket_fd, F_SETFL, FD_CLOEXEC);
1294         (void) unlink(usocket_name);
1295 
1296         memset(&uaddr, 0, sizeof(uaddr));
1297         uaddr.sun_family = AF_UNIX;
1298         strcpy(uaddr.sun_path, usocket_name);
1299         r = bind(usocket_fd, (struct sockaddr *) &uaddr, sizeof(uaddr));
1300         if (r == -1)
1301         {
1302              perror("bind");
1303              fatal("cannot bind unix domain socket\n");
1304         }
1305 
1306         r = chmod(usocket_name, 0666);
1307         if (r == -1)
1308         {
1309              perror("fchmod");
1310              fatal("cannot fchmod unix domain socket\n");
1311         }
1312 
1313         r = listen(usocket_fd, 5);
1314         if (r == -1)
1315         {
1316              perror("listen");
1317              fatal("cannot listen on unix domain socket\n");
1318         }
1319     }
1320 
1321     // End of general errors from command line options
1322 
1323     // Self background
1324     if (foreground == 0)
1325     {
1326         pid = fork();
1327 
1328         if (pid == -1)
1329         {
1330             perror("fork");
1331             fatal("cannot background\n");
1332         }
1333 
1334         if (pid)
1335         {
1336             _exit(EXIT_SUCCESS);
1337         }
1338 
1339         (void) setsid();
1340     }
1341 
1342     // Termination handler
1343     memset(&act, 0, sizeof(act));
1344     act.sa_handler = (void (*)(int)) term_handler;
1345     (void) sigaction(SIGTERM, &act, NULL);
1346     (void) sigaction(SIGINT, &act, NULL);
1347 
1348     // Write pid file
1349     if (pidfile_fd != -1)
1350     {
1351         len = snprintf(pidbuf, sizeof(pidbuf), "%u\n", (unsigned) getpid());
1352         if (len < 0 || (size_t) len > sizeof(pidbuf))
1353         {
1354             fatal("error formatting pidfile\n");
1355         }
1356 
1357         rs = write(pidfile_fd, pidbuf, (size_t) len);
1358         if (rs == -1)
1359         {
1360             perror("write");
1361             fatal("error writing pidfile\n");
1362         }
1363 
1364         r = close(pidfile_fd);
1365         if (r == -1)
1366         {
1367             perror("close");
1368             fatal("error writing pidfile\n");
1369         }
1370     }
1371 
1372     // Create the array
1373     array_size = (unsigned int) (time_period_msec / send_interval_msec);
1374     array = calloc(array_size, sizeof(*array));
1375     if (array == NULL)
1376     {
1377         fatal("calloc of packet array failed\n");
1378     }
1379 
1380     // Allocate the echo request/reply packet buffers
1381     echo_request = (icmphdr_t *) malloc(echo_request_len);
1382     echo_reply = malloc(echo_reply_len);
1383     if (echo_request == NULL || echo_reply == NULL)
1384     {
1385         fatal("malloc of packet buffers failed\n");
1386     }
1387 
1388     // Set the default loss interval
1389     if (loss_interval_msec == 0)
1390     {
1391         loss_interval_msec = send_interval_msec * 4;
1392     }
1393     loss_interval_usec = loss_interval_msec * 1000;
1394 
1395     // Log our general parameters
1396     r = getnameinfo((struct sockaddr *) &dest_addr, dest_addr_len, dest_str, sizeof(dest_str), NULL, 0, NI_NUMERICHOST);
1397     if (r != 0)
1398     {
1399         fatal("getnameinfo of destination address failed\n");
1400     }
1401 
1402     if (bind_addr_len)
1403     {
1404         r = getnameinfo((struct sockaddr *) &bind_addr, bind_addr_len, bind_str, sizeof(bind_str), NULL, 0, NI_NUMERICHOST);
1405         if (r != 0)
1406         {
1407             fatal("getnameinfo of bind address failed\n");
1408         }
1409     }
1410 
1411     logger("send_interval %lums  loss_interval %lums  time_period %lums  report_interval %lums  data_len %lu  alert_interval %lums  latency_alarm %lums  loss_alarm %lu%%  dest_addr %s  bind_addr %s  identifier \"%s\"\n",
1412            send_interval_msec, loss_interval_msec, time_period_msec, report_interval_msec, echo_data_len,
1413            alert_interval_msec, latency_alarm_threshold_msec, loss_alarm_threshold_percent,
1414            dest_str, bind_str, identifier);
1415 
1416     // Set my echo id
1417     echo_id = htons((uint16_t) getpid());
1418 
1419     // Set the limit for sequence number to ensure a multiple of array size
1420     sequence_limit = (uint16_t) array_size;
1421     while ((sequence_limit & 0x8000) == 0)
1422     {
1423         sequence_limit <<= 1;
1424     }
1425 
1426     // Create recv thread
1427     r = pthread_create(&thread, NULL, &recv_thread, NULL);
1428     if (r != 0)
1429     {
1430         perror("pthread_create");
1431         fatal("cannot create recv thread\n");
1432     }
1433 
1434     // Set priority on recv thread if requested
1435     if (flag_priority)
1436     {
1437         struct sched_param          thread_sched_param;
1438 
1439         r = sched_get_priority_min(SCHED_RR);
1440         if (r == -1)
1441         {
1442             perror("sched_get_priority_min");
1443             fatal("cannot determin minimum shceduling priority for SCHED_RR\n");
1444         }
1445         thread_sched_param.sched_priority = r;
1446 
1447         r = pthread_setschedparam(thread, SCHED_RR, &thread_sched_param);
1448         if (r != 0)
1449         {
1450             perror("pthread_setschedparam");
1451             fatal("cannot set receive thread priority\n");
1452         }
1453     }
1454 
1455     // Create send thread
1456     r = pthread_create(&thread, NULL, &send_thread, NULL);
1457     if (r != 0)
1458     {
1459         perror("pthread_create");
1460         fatal("cannot create send thread\n");
1461     }
1462 
1463     // Report thread
1464     if (report_interval_msec)
1465     {
1466         r = pthread_create(&thread, NULL, &report_thread, NULL);
1467         if (r != 0)
1468         {
1469             perror("pthread_create");
1470             fatal("cannot create report thread\n");
1471         }
1472     }
1473 
1474     // Create alert thread
1475     if (latency_alarm_threshold_msec || loss_alarm_threshold_percent)
1476     {
1477         r = pthread_create(&thread, NULL, &alert_thread, NULL);
1478         if (r != 0)
1479         {
1480             perror("pthread_create");
1481             fatal("cannot create alert thread\n");
1482         }
1483     }
1484 
1485     // Create usocket thread
1486     if (usocket_name)
1487     {
1488         r = pthread_create(&thread, NULL, &usocket_thread, NULL);
1489         if (r != 0)
1490         {
1491             perror("pthread_create");
1492             fatal("cannot create usocket thread\n");
1493         }
1494     }
1495 
1496     // Wait (forever) for last thread started
1497     pthread_join(thread, NULL);
1498 
1499     // notreached
1500     return 0;
1501 }
1502