1 /* sonar, Copyright (c) 1998-2012 Jamie Zawinski and Stephen Martin
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
11 * This implements the "ping" sensor for sonar.
14 #include "screenhackI.h"
18 #undef usleep /* conflicts with unistd.h on OSX */
21 /* Note: to get this to compile for iPhone, you need to fix Xcode!
22 The icmp headers exist for the simulator build environment, but
23 not for the real-device build environment. This appears to
24 just be an Apple bug, not intentional.
26 xc=/Applications/Xcode.app/Contents
27 for path in /Developer/Platforms/iPhone*?/Developer/SDKs/?* \
28 $xc/Developer/Platforms/iPhone*?/Developer/SDKs/?* ; do
30 /usr/include/netinet/ip.h \
31 /usr/include/netinet/in_systm.h \
32 /usr/include/netinet/ip_icmp.h \
33 /usr/include/netinet/ip_var.h \
34 /usr/include/netinet/udp.h
36 ln -s "$file" "$path$file"
46 #if defined(HAVE_ICMP) || defined(HAVE_ICMPHDR)
48 # include <sys/stat.h>
52 # include <sys/types.h>
53 # include <sys/time.h>
56 # include <sys/socket.h>
57 # include <netinet/in_systm.h>
58 # include <netinet/in.h>
59 # include <netinet/ip.h>
60 # include <netinet/ip_icmp.h>
61 # include <netinet/udp.h>
62 # include <arpa/inet.h>
64 # ifdef HAVE_GETIFADDRS
67 #endif /* HAVE_ICMP || HAVE_ICMPHDR */
69 #if defined(HAVE_ICMP)
72 # define ICMP_TYPE(p) (p)->icmp_type
73 # define ICMP_CODE(p) (p)->icmp_code
74 # define ICMP_CHECKSUM(p) (p)->icmp_cksum
75 # define ICMP_ID(p) (p)->icmp_id
76 # define ICMP_SEQ(p) (p)->icmp_seq
77 #elif defined(HAVE_ICMPHDR)
80 # define ICMP_TYPE(p) (p)->type
81 # define ICMP_CODE(p) (p)->code
82 # define ICMP_CHECKSUM(p) (p)->checksum
83 # define ICMP_ID(p) (p)->un.echo.id
84 # define ICMP_SEQ(p) (p)->un.echo.sequence
96 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
97 const char *subnet, int timeout,
98 Bool resolve_p, Bool times_p, Bool debug_p)
100 if (! (!subnet || !*subnet || !strcmp(subnet, "default")))
101 fprintf (stderr, "%s: not compiled with support for pinging hosts.\n",
106 #else /* HAVE_PING -- whole file */
109 #if defined(__DECC) || defined(_IP_VHL)
110 /* This is how you do it on DEC C, and possibly some BSD systems. */
111 # define IP_HDRLEN(ip) ((ip)->ip_vhl & 0x0F)
113 /* This is how you do it on everything else. */
114 # define IP_HDRLEN(ip) ((ip)->ip_hl)
117 /* yes, there is only one, even when multiple savers are running in the
118 same address space - since we can only open this socket before dropping
121 static int global_icmpsock = 0;
123 /* Set by a signal handler. */
124 static int timer_expired;
128 static u_short checksum(u_short *, int);
129 static long delta(struct timeval *, struct timeval *);
133 char *version; /* short version number of xscreensaver */
134 int icmpsock; /* socket for sending pings */
135 int pid; /* our process ID */
136 int seq; /* packet sequence number */
137 int timeout; /* packet timeout */
140 sonar_bogie *targets; /* the hosts we will ping;
141 those that pong end up on ssd->pending. */
142 sonar_bogie *last_pinged; /* pointer into 'targets' list */
143 double last_ping_time;
152 struct sockaddr address; /* ip address */
157 /* Packs an IP address quad into bigendian network order. */
159 pack_addr (unsigned int a, unsigned int b, unsigned int c, unsigned int d)
161 unsigned long i = (((a & 255) << 24) |
168 /* Unpacks an IP address quad from bigendian network order. */
170 unpack_addr (unsigned long addr,
171 unsigned int *a, unsigned int *b,
172 unsigned int *c, unsigned int *d)
175 *a = (addr >> 24) & 255;
176 *b = (addr >> 16) & 255;
177 *c = (addr >> 8) & 255;
184 /* Resolves the bogie's name (either a hostname or ip address string)
185 to a hostent. Returns 1 if successful, 0 if it failed to resolve.
188 resolve_bogie_hostname (ping_data *pd, sonar_bogie *sb, Bool resolve_p)
190 ping_bogie *pb = (ping_bogie *) sb->closure;
191 struct hostent *hent;
192 struct sockaddr_in *iaddr;
197 iaddr = (struct sockaddr_in *) &(pb->address);
198 iaddr->sin_family = AF_INET;
200 if (4 == sscanf (sb->name, " %u.%u.%u.%u %c",
201 &ip[0], &ip[1], &ip[2], &ip[3], &c))
203 /* It's an IP address.
208 fprintf (stderr, "%s: ignoring bogus IP %s\n",
213 iaddr->sin_addr.s_addr = pack_addr (ip[0], ip[1], ip[2], ip[3]);
215 hent = gethostbyaddr ((const char *) &iaddr->sin_addr.s_addr,
216 sizeof(iaddr->sin_addr.s_addr),
222 fprintf (stderr, "%s: %s => %s\n",
224 ((hent && hent->h_name && *hent->h_name)
225 ? hent->h_name : "<unknown>"));
227 if (hent && hent->h_name && *hent->h_name)
228 sb->name = strdup (hent->h_name);
232 /* It's a host name. */
234 /* don't waste time being confused by non-hostname tokens
235 in .ssh/known_hosts */
236 if (!strcmp (sb->name, "ssh-rsa") ||
237 !strcmp (sb->name, "ssh-dsa") ||
238 !strcmp (sb->name, "ssh-dss") ||
239 strlen (sb->name) >= 80)
242 /* .ssh/known_hosts sometimes contains weirdness like "[host]:port".
244 if (strchr (sb->name, '['))
247 fprintf (stderr, "%s: ignoring bogus address \"%s\"\n",
252 /* If the name contains a colon, it's probably IPv6. */
253 if (strchr (sb->name, ':'))
256 fprintf (stderr, "%s: ignoring ipv6 address \"%s\"\n",
261 hent = gethostbyname (sb->name);
265 fprintf (stderr, "%s: could not resolve host: %s\n",
270 memcpy (&iaddr->sin_addr, hent->h_addr_list[0],
271 sizeof(iaddr->sin_addr));
275 unsigned int a, b, c, d;
276 unpack_addr (iaddr->sin_addr.s_addr, &a, &b, &c, &d);
277 fprintf (stderr, "%s: %s => %d.%d.%d.%d\n",
278 progname, sb->name, a, b, c, d);
286 print_host (FILE *out, unsigned long ip, const char *name)
289 unsigned int a, b, c, d;
290 unpack_addr (ip, &a, &b, &c, &d); /* ip is in network order */
291 sprintf (ips, "%u.%u.%u.%u", a, b, c, d);
292 if (!name || !*name) name = "<unknown>";
293 fprintf (out, "%-16s %s\n", ips, name);
297 /* Create a sonar_bogie from a host name or ip address string.
298 Returns NULL if the name could not be resolved.
301 bogie_for_host (sonar_sensor_data *ssd, const char *name, Bool resolve_p)
303 ping_data *pd = (ping_data *) ssd->closure;
304 sonar_bogie *b = (sonar_bogie *) calloc (1, sizeof(*b));
305 ping_bogie *pb = (ping_bogie *) calloc (1, sizeof(*pb));
306 struct sockaddr_in *iaddr;
309 b->name = strdup (name);
312 if (! resolve_bogie_hostname (pd, b, resolve_p))
315 iaddr = (struct sockaddr_in *) &(pb->address);
317 /* Don't ever use loopback (127.0.0.x) hosts */
318 ip = iaddr->sin_addr.s_addr;
319 if ((ntohl (ip) & 0xFFFFFF00L) == 0x7f000000L) /* 127.0.0.x */
322 fprintf (stderr, "%s: ignoring loopback host %s\n",
327 /* Don't ever use broadcast (255.x.x.x) hosts */
328 if ((ntohl (ip) & 0xFF000000L) == 0xFF000000L) /* 255.x.x.x */
331 fprintf (stderr, "%s: ignoring broadcast host %s\n",
338 fprintf (stderr, "%s: added ", progname);
339 print_host (stderr, ip, b->name);
345 if (b) sonar_free_bogie (ssd, b);
352 /* Return a list of bogies read from a file.
353 The file can be like /etc/hosts or .ssh/known_hosts or probably
354 just about anything that has host names in it.
357 read_hosts_file (sonar_sensor_data *ssd, const char *filename)
359 ping_data *pd = (ping_data *) ssd->closure;
363 sonar_bogie *list = 0;
367 /* Kludge: on OSX, variables have not been expanded in the command
368 line arguments, so as a special case, allow the string to begin
369 with literal "$HOME/" or "~/".
371 This is so that the "Known Hosts" menu item in sonar.xml works.
373 if (!strncmp(filename, "~/", 2) || !strncmp(filename, "$HOME/", 6))
375 char *s = strchr (filename, '/');
376 strcpy (buf, getenv("HOME"));
381 fp = fopen(filename, "r");
385 sprintf(buf, "%s: %s", progname, filename);
387 if (pd->debug_p) /* on OSX don't syslog this */
394 fprintf (stderr, "%s: reading \"%s\"\n", progname, filename);
396 while ((p = fgets(buf, LINE_MAX, fp)))
398 while ((*p == ' ') || (*p == '\t')) /* skip whitespace */
400 if (*p == '#') /* skip comments */
403 /* Get the name and address */
405 if ((addr = strtok(buf, " ,;\t\n")))
406 name = strtok(0, " ,;\t\n");
410 /* Check to see if the addr looks like an addr. If not, assume
411 the addr is a name and there is no addr. This way, we can
412 handle files whose lines have "xx.xx.xx.xx hostname" as their
413 first two tokens, and also files that have a hostname as their
414 first token (like .ssh/known_hosts and .rhosts.)
418 if (4 != sscanf(addr, "%d.%d.%d.%d%c", &i, &i, &i, &i, &c))
425 /* If the name is all digits, it's not a name. */
429 for (s = name; *s; s++)
430 if (*s < '0' || *s > '9')
435 fprintf (stderr, "%s: skipping bogus name \"%s\" (%s)\n",
436 progname, name, addr);
441 /* Create a new target using first the name then the address */
445 new = bogie_for_host (ssd, name, pd->resolve_p);
447 new = bogie_for_host (ssd, addr, pd->resolve_p);
459 #endif /* READ_FILES */
463 delete_duplicate_hosts (sonar_sensor_data *ssd, sonar_bogie *list)
465 ping_data *pd = (ping_data *) ssd->closure;
466 sonar_bogie *head = list;
469 for (sb = head; sb; sb = sb->next)
471 ping_bogie *pb = (ping_bogie *) sb->closure;
472 struct sockaddr_in *i1 = (struct sockaddr_in *) &(pb->address);
473 unsigned long ip1 = i1->sin_addr.s_addr;
476 for (sb2 = sb; sb2; sb2 = sb2->next)
478 if (sb2 && sb2->next)
480 ping_bogie *pb2 = (ping_bogie *) sb2->next->closure;
481 struct sockaddr_in *i2 = (struct sockaddr_in *) &(pb2->address);
482 unsigned long ip2 = i2->sin_addr.s_addr;
488 fprintf (stderr, "%s: deleted duplicate: ", progname);
489 print_host (stderr, ip2, sb2->next->name);
491 sb2->next = sb2->next->next;
503 width_mask (int width)
507 for (i = 0; i < width; i++)
513 #ifdef HAVE_GETIFADDRS
515 mask_width (unsigned int mask)
518 for (i = 0; i < 32; i++)
526 /* Generate a list of bogies consisting of all of the entries on
527 the same subnet. 'base' ip is in network order; 0 means localhost.
530 subnet_hosts (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
531 unsigned long n_base, int subnet_width)
533 ping_data *pd = (ping_data *) ssd->closure;
534 unsigned long h_mask; /* host order */
535 unsigned long h_base; /* host order */
536 char address[BUFSIZ];
540 sonar_bogie *list = 0;
543 if (subnet_width < 24)
546 "Pinging %lu hosts is a bad\n"
547 "idea. Please use a subnet\n"
548 "mask of 24 bits or more.",
549 (unsigned long) (1L << (32 - subnet_width)) - 1);
550 *error_ret = strdup(buf);
553 else if (subnet_width > 30)
557 "doesn't make sense.\n"
558 "Try \"subnet/24\"\n"
559 "or \"subnet/29\".\n",
561 *error_ret = strdup(buf);
567 fprintf (stderr, "%s: adding %d-bit subnet\n", progname, subnet_width);
572 # ifdef HAVE_GETIFADDRS
574 /* To determine the local subnet, we need to know the local IP address.
575 Do this by looking at the IPs of every network interface.
577 struct in_addr in = { 0, };
578 struct ifaddrs *all = 0, *ifa;
581 fprintf (stderr, "%s: listing network interfaces\n", progname);
584 for (ifa = all; ifa; ifa = ifa->ifa_next)
588 if (ifa->ifa_addr->sa_family != AF_INET)
591 fprintf (stderr, "%s: if: %4s: %s\n", progname,
595 ifa->ifa_addr->sa_family == AF_UNIX ? "local" :
598 ifa->ifa_addr->sa_family == AF_LINK ? "link" :
601 ifa->ifa_addr->sa_family == AF_INET6 ? "ipv6" :
606 in2 = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
607 mask = ntohl (((struct sockaddr_in *) ifa->ifa_netmask)
610 fprintf (stderr, "%s: if: %4s: inet = %s /%d 0x%08lx\n",
616 if (in2.s_addr == 0x0100007f || /* 127.0.0.1 in network order */
620 /* At least on the AT&T 3G network, pinging either of the two
621 hosts on a /31 network doesn't work, so don't try.
623 if (mask_width (mask) == 31)
626 "Can't ping subnet:\n"
631 inet_ntoa (in2), mask_width (mask), ifa->ifa_name);
632 if (*error_ret) free (*error_ret);
633 *error_ret = strdup (buf);
638 subnet_width = mask_width (mask);
643 if (*error_ret) free (*error_ret);
645 n_base = in.s_addr; /* already in network order, I think? */
647 else if (!*error_ret)
648 *error_ret = strdup ("Unable to determine\nlocal IP address\n");
656 # else /* !HAVE_GETIFADDRS */
658 /* If we can't walk the list of network interfaces to figure out
659 our local IP address, try to do it by finding the local host
660 name, then resolving that.
662 char hostname[BUFSIZ];
663 struct hostent *hent = 0;
665 if (gethostname(hostname, BUFSIZ))
667 *error_ret = strdup ("Unable to determine\n"
672 /* Get our IP address and convert it to a string */
674 hent = gethostbyname(hostname);
677 strcat (hostname, ".local"); /* Necessary on iphone */
678 hent = gethostbyname(hostname);
684 "Unable to resolve\n"
685 "local host \"%.100s\"",
687 *error_ret = strdup(buf);
691 strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
692 n_base = pack_addr (hent->h_addr_list[0][0],
693 hent->h_addr_list[0][1],
694 hent->h_addr_list[0][2],
695 hent->h_addr_list[0][3]);
697 if (n_base == 0x0100007f) /* 127.0.0.1 in network order */
699 unsigned int a, b, c, d;
700 unpack_addr (n_base, &a, &b, &c, &d);
702 "Unable to determine\n"
703 "local subnet address:\n"
708 hostname, a, b, c, d);
709 *error_ret = strdup(buf);
713 # endif /* !HAVE_GETIFADDRS */
717 /* Construct targets for all addresses in this subnet */
719 h_mask = width_mask (subnet_width);
720 h_base = ntohl (n_base);
722 if (desc_ret && !*desc_ret) {
724 unsigned int a, b, c, d;
725 unsigned long bb = n_base & htonl(h_mask);
726 unpack_addr (bb, &a, &b, &c, &d);
727 if (subnet_width > 24)
728 sprintf (buf, "%u.%u.%u.%u/%d", a, b, c, d, subnet_width);
730 sprintf (buf, "%u.%u.%u/%d", a, b, c, subnet_width);
731 *desc_ret = strdup (buf);
734 for (i = 255; i >= 0; i--) {
735 unsigned int a, b, c, d;
736 int ip = (h_base & 0xFFFFFF00L) | i; /* host order */
738 if ((ip & h_mask) != (h_base & h_mask)) /* skip out-of-subnet host */
740 else if (subnet_width == 31) /* 1-bit bridge: 2 hosts */
742 else if ((ip & ~h_mask) == 0) /* skip network address */
744 else if ((ip & ~h_mask) == ~h_mask) /* skip broadcast address */
747 unpack_addr (htonl (ip), &a, &b, &c, &d);
748 sprintf (address, "%u.%u.%u.%u", a, b, c, d);
752 unsigned int aa, ab, ac, ad;
753 unsigned int ma, mb, mc, md;
754 unpack_addr (htonl (h_base & h_mask), &aa, &ab, &ac, &ad);
755 unpack_addr (htonl (h_mask), &ma, &mb, &mc, &md);
757 "%s: subnet: %s (%u.%u.%u.%u & %u.%u.%u.%u / %d)\n",
764 p = address + strlen(address) + 1;
767 new = bogie_for_host (ssd, address, pd->resolve_p);
779 /* Send a ping packet.
782 send_ping (ping_data *pd, const sonar_bogie *b)
784 ping_bogie *pb = (ping_bogie *) b->closure;
787 const char *token = "org.jwz.xscreensaver.sonar";
789 int pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) +
790 strlen(b->name) + 1 +
792 strlen(pd->version) + 1);
794 /* Create the ICMP packet */
796 if (! (packet = (u_char *) calloc(1, pcktsiz)))
797 return; /* Out of memory */
799 icmph = (struct ICMP *) packet;
800 ICMP_TYPE(icmph) = ICMP_ECHO;
801 ICMP_CODE(icmph) = 0;
802 ICMP_CHECKSUM(icmph) = 0;
803 ICMP_ID(icmph) = pd->pid;
804 ICMP_SEQ(icmph) = pd->seq++;
805 # ifdef GETTIMEOFDAY_TWO_ARGS
806 gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)],
807 (struct timezone *) 0);
809 gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]);
812 /* We store the name of the host we're pinging in the packet, and parse
813 that out of the return packet later (see get_ping() for why).
814 After that, we also include the name and version of this program,
815 just to give a clue to anyone sniffing and wondering what's up.
817 sprintf ((char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)],
818 "%.100s%c%.20s %.20s",
819 b->name, 0, token, pd->version);
821 ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
825 if (sendto(pd->icmpsock, packet, pcktsiz, 0,
826 &pb->address, sizeof(pb->address))
831 sprintf(buf, "%s: pinging %.100s", progname, b->name);
845 /* Compute the checksum on a ping packet.
848 checksum (u_short *packet, int size)
850 register int nleft = size;
851 register u_short *w = packet;
852 register int sum = 0;
855 /* Using a 32 bit accumulator (sum), we add sequential 16 bit words
856 to it, and at the end, fold back all the carry bits from the
857 top 16 bits into the lower 16 bits.
865 /* mop up an odd byte, if necessary */
869 *(u_char *)(&answer) = *(u_char *)w ;
870 *(1 + (u_char *)(&answer)) = 0;
874 /* add back carry outs from top 16 bits to low 16 bits */
876 sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
877 sum += (sum >> 16); /* add carry */
878 answer = ~sum; /* truncate to 16 bits */
884 /* Copies the sonar_bogie and the underlying ping_bogie.
887 copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
889 sonar_bogie *b2 = sonar_copy_bogie (ssd, b);
892 ping_bogie *pb = (ping_bogie *) b->closure;
893 ping_bogie *pb2 = (ping_bogie *) calloc (1, sizeof(*pb));
894 pb2->address = pb->address;
901 /* Look for all outstanding ping replies.
904 get_ping (sonar_sensor_data *ssd)
906 ping_data *pd = (ping_data *) ssd->closure;
907 struct sockaddr from;
908 unsigned int fromlen; /* Posix says socklen_t, but that's not portable */
912 struct timeval *then;
917 sonar_bogie *new = 0;
923 /* Set up a signal to interrupt our wait for a packet */
925 sigemptyset(&sa.sa_mask);
927 sa.sa_handler = sigcatcher;
928 if (sigaction(SIGALRM, &sa, 0) == -1)
931 sprintf(msg, "%s: unable to trap SIGALRM", progname);
936 /* Set up a timer to interupt us if we don't get a packet */
938 it.it_interval.tv_sec = 0;
939 it.it_interval.tv_usec = 0;
940 it.it_value.tv_sec = 0;
941 it.it_value.tv_usec = pd->timeout;
943 setitimer(ITIMER_REAL, &it, 0);
945 /* Wait for a result packet */
947 fromlen = sizeof(from);
948 while (! timer_expired)
950 tv.tv_usec = pd->timeout;
953 /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
956 memset (&rfds, 0, sizeof(rfds));
958 FD_SET(pd->icmpsock, &rfds);
959 /* only wait a little while, in case we raced with the timer expiration.
960 From Valentijn Sessink <valentyn@openoffice.nl> */
961 if (select(pd->icmpsock + 1, &rfds, 0, 0, &tv) >0)
963 result = recvfrom (pd->icmpsock, packet, sizeof(packet),
966 /* Check the packet */
968 # ifdef GETTIMEOFDAY_TWO_ARGS
969 gettimeofday(&now, (struct timezone *) 0);
973 ip = (struct ip *) packet;
974 iphdrlen = IP_HDRLEN(ip) << 2;
975 icmph = (struct ICMP *) &packet[iphdrlen];
976 then = (struct timeval *) &packet[iphdrlen + sizeof(struct ICMP)];
979 /* Ignore anything but ICMP Replies */
980 if (ICMP_TYPE(icmph) != ICMP_ECHOREPLY)
983 /* Ignore packets not set from us */
984 if (ICMP_ID(icmph) != pd->pid)
987 /* Find the bogie in 'targets' that corresponds to this packet
988 and copy it, so that this bogie stays in the same spot (th)
989 on the screen, and so that we don't have to resolve it again.
991 We could find the bogie by comparing ip->ip_src.s_addr to
992 pb->address, but it is possible that, in certain weird router
993 or NAT situations, that the reply will come back from a
994 different address than the one we sent it to. So instead,
995 we parse the name out of the reply packet payload.
998 const char *name = (char *) &packet[iphdrlen +
999 sizeof(struct ICMP) +
1000 sizeof(struct timeval)];
1003 /* Ensure that a maliciously-crafted return packet can't
1004 make us overflow in strcmp. */
1005 packet[sizeof(packet)-1] = 0;
1007 for (b = pd->targets; b; b = b->next)
1008 if (!strcmp (name, b->name))
1010 new = copy_ping_bogie (ssd, b);
1015 if (! new) /* not in targets? */
1017 unsigned int a, b, c, d;
1018 unpack_addr (ip->ip_src.s_addr, &a, &b, &c, &d);
1020 "%s: UNEXPECTED PING REPLY! "
1021 "%4d bytes, icmp_seq=%-4d from %d.%d.%d.%d\n",
1022 progname, result, ICMP_SEQ(icmph), a, b, c, d);
1030 double msec = delta(then, &now) / 1000.0;
1034 if (new->desc) free (new->desc);
1035 new->desc = (char *) malloc (30);
1036 if (msec > 99) sprintf (new->desc, "%.0f ms", msec);
1037 else if (msec > 9) sprintf (new->desc, "%.1f ms", msec);
1038 else if (msec > 1) sprintf (new->desc, "%.2f ms", msec);
1039 else sprintf (new->desc, "%.3f ms", msec);
1042 if (pd->debug_p && pd->times_p) /* ping-like stdout log */
1044 char *s = strdup(new->name);
1048 s2 = s + strlen(s) - 28;
1049 strncpy (s2, "...", 3);
1052 "%3d bytes from %28s: icmp_seq=%-4d time=%s\n",
1053 result, s2, ICMP_SEQ(icmph), new->desc);
1058 /* The radius must be between 0.0 and 1.0.
1059 We want to display ping times on a logarithmic scale,
1060 with the three rings being 2.5, 70 and 2,000 milliseconds.
1062 if (msec <= 0) msec = 0.001;
1063 new->r = log (msec * 10) / log (20000);
1065 /* Don't put anyone *too* close to the center of the screen. */
1066 if (new->r < 0) new->r = 0;
1067 if (new->r < 0.1) new->r += 0.1;
1076 /* difference between the two times in microseconds.
1079 delta (struct timeval *then, struct timeval *now)
1081 return (((now->tv_sec - then->tv_sec) * 1000000) +
1082 (now->tv_usec - then->tv_usec));
1087 ping_free_data (sonar_sensor_data *ssd, void *closure)
1089 ping_data *pd = (ping_data *) closure;
1090 sonar_bogie *b = pd->targets;
1093 sonar_bogie *b2 = b->next;
1094 sonar_free_bogie (ssd, b);
1101 ping_free_bogie_data (sonar_sensor_data *sd, void *closure)
1107 /* Returns the current time in seconds as a double.
1113 # ifdef GETTIMEOFDAY_TWO_ARGS
1114 struct timezone tzp;
1115 gettimeofday(&now, &tzp);
1120 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
1124 /* Pings the next bogie, if it's time.
1125 Returns all outstanding ping replies.
1127 static sonar_bogie *
1128 ping_scan (sonar_sensor_data *ssd)
1130 ping_data *pd = (ping_data *) ssd->closure;
1131 double now = double_time();
1132 double ping_cycle = 10; /* re-ping a given host every 10 seconds */
1133 double ping_interval = ping_cycle / pd->target_count;
1135 if (now > pd->last_ping_time + ping_interval) /* time to ping someone */
1137 if (pd->last_pinged)
1138 pd->last_pinged = pd->last_pinged->next;
1139 if (! pd->last_pinged)
1140 pd->last_pinged = pd->targets;
1141 send_ping (pd, pd->last_pinged);
1142 pd->last_ping_time = now;
1145 return get_ping (ssd);
1149 /* Returns a list of hosts to ping based on the "-ping" argument.
1151 static sonar_bogie *
1152 parse_mode (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
1153 const char *ping_arg, Bool ping_works_p)
1155 ping_data *pd = (ping_data *) ssd->closure;
1156 char *source, *token, *end, dummy;
1157 sonar_bogie *hostlist = 0;
1158 const char *fallback = "subnet";
1162 if (fallback && (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default")))
1163 source = strdup(fallback);
1165 source = strdup(ping_arg);
1170 end = source + strlen(source);
1174 sonar_bogie *new = 0;
1178 unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
1183 *next != ',' && *next != ' ' && *next != '\t' && *next != '\n';
1190 fprintf (stderr, "%s: parsing %s\n", progname, token);
1194 *error_ret = strdup ("Sonar must be setuid to ping!\n"
1195 "Running simulation instead.");
1199 if ((4 == sscanf (token, "%u.%u.%u/%u %c", &n0,&n1,&n2, &m,&d)) ||
1200 (5 == sscanf (token, "%u.%u.%u.%u/%u %c", &n0,&n1,&n2,&n3,&m,&d)))
1202 /* subnet: A.B.C.D/M
1205 unsigned long ip = pack_addr (n0, n1, n2, n3);
1206 new = subnet_hosts (ssd, error_ret, desc_ret, ip, m);
1208 else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d))
1212 new = bogie_for_host (ssd, token, pd->resolve_p);
1214 else if (!strcmp (token, "subnet"))
1216 new = subnet_hosts (ssd, error_ret, desc_ret, 0, 24);
1218 else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
1220 new = subnet_hosts (ssd, error_ret, desc_ret, 0, m);
1222 else if (*token == '.' || *token == '/' ||
1223 *token == '$' || *token == '~')
1226 new = read_hosts_file (ssd, token);
1228 if (pd->debug_p) fprintf (stderr, "%s: skipping file\n", progname);
1232 else if (!stat (token, &st))
1234 new = read_hosts_file (ssd, token);
1236 # endif /* READ_FILES */
1239 /* not an existant file - must be a host name
1241 new = bogie_for_host (ssd, token, pd->resolve_p);
1246 sonar_bogie *nn = new;
1249 nn->next = hostlist;
1254 while (token < end &&
1255 (*token == ',' || *token == ' ' ||
1256 *token == '\t' || *token == '\n'))
1262 /* If the arg was completely unparsable, fall back to the local subnet.
1263 This happens if the default is "/etc/hosts" but READ_FILES is off.
1264 Or if we're on a /31 network, in which case we try twice then fail.
1266 if (!hostlist && fallback)
1269 fprintf (stderr, "%s: no hosts parsed! Trying %s\n",
1270 progname, fallback);
1271 ping_arg = fallback;
1281 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
1282 const char *subnet, int timeout,
1283 Bool resolve_p, Bool times_p, Bool debug_p)
1285 sonar_sensor_data *ssd = (sonar_sensor_data *) calloc (1, sizeof(*ssd));
1286 ping_data *pd = (ping_data *) calloc (1, sizeof(*pd));
1290 Bool socket_initted_p = False;
1291 Bool socket_raw_p = False;
1293 pd->resolve_p = resolve_p;
1294 pd->times_p = times_p;
1295 pd->debug_p = debug_p;
1298 ssd->scan_cb = ping_scan;
1299 ssd->free_data_cb = ping_free_data;
1300 ssd->free_bogie_cb = ping_free_bogie_data;
1302 /* Get short version number. */
1303 s = strchr (screensaver_id, ' ');
1304 pd->version = strdup (s+1);
1305 s = strchr (pd->version, ' ');
1309 /* Create the ICMP socket. Do this before dropping privs.
1311 Raw sockets can only be opened by root (or setuid root), so we
1312 only try to do this when the effective uid is 0.
1314 We used to just always try, and notice the failure. But apparently
1315 that causes "SELinux" to log spurious warnings when running with the
1316 "strict" policy. So to avoid that, we just don't try unless we
1319 On MacOS X, we can avoid the whole problem by using a
1320 non-privileged datagram instead of a raw socket.
1322 if (global_icmpsock)
1324 pd->icmpsock = global_icmpsock;
1325 socket_initted_p = True;
1327 fprintf (stderr, "%s: re-using icmp socket\n", progname);
1330 else if ((pd->icmpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) >= 0)
1332 socket_initted_p = True;
1334 else if (geteuid() == 0 &&
1335 (pd->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) >= 0)
1337 socket_initted_p = True;
1338 socket_raw_p = True;
1341 if (socket_initted_p)
1343 global_icmpsock = pd->icmpsock;
1344 socket_initted_p = True;
1346 fprintf (stderr, "%s: opened %s icmp socket\n", progname,
1347 (socket_raw_p ? "raw" : "dgram"));
1350 fprintf (stderr, "%s: unable to open icmp socket\n", progname);
1355 pd->pid = getpid() & 0xFFFF;
1357 pd->timeout = timeout;
1359 /* Generate a list of targets */
1361 pd->targets = parse_mode (ssd, error_ret, desc_ret, subnet,
1363 pd->targets = delete_duplicate_hosts (ssd, pd->targets);
1367 fprintf (stderr, "%s: Target list:\n", progname);
1368 for (b = pd->targets; b; b = b->next)
1370 ping_bogie *pb = (ping_bogie *) b->closure;
1371 struct sockaddr_in *iaddr = (struct sockaddr_in *) &(pb->address);
1372 unsigned long ip = iaddr->sin_addr.s_addr;
1373 fprintf (stderr, "%s: ", progname);
1374 print_host (stderr, ip, b->name);
1378 /* Make sure there is something to ping */
1380 pd->target_count = 0;
1381 for (b = pd->targets; b; b = b->next)
1384 if (pd->target_count == 0)
1387 *error_ret = strdup ("No hosts to ping!\n"
1388 "Simulating instead.");
1389 if (pd) ping_free_data (ssd, pd);
1390 if (ssd) free (ssd);
1394 /* Distribute them evenly around the display field, clockwise.
1395 Even on a /24, allocated IPs tend to cluster together, so
1396 don't put any two hosts closer together than N degrees to
1397 avoid unnecessary overlap when we have plenty of space due
1398 to addresses that probably won't respond. And don't spread
1399 them out too far apart, because that looks too symmetrical
1400 when there are a small number of hosts.
1403 double th = frand(M_PI);
1404 double sep = 360.0 / pd->target_count;
1405 if (sep < 23) sep = 23;
1406 if (sep > 43) sep = 43;
1408 for (b = pd->targets; b; b = b->next) {
1417 #endif /* HAVE_PING -- whole file */