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 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 /* If 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 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,
593 (ifa->ifa_addr->sa_family == AF_UNIX ? "local" :
594 ifa->ifa_addr->sa_family == AF_LINK ? "link" :
595 ifa->ifa_addr->sa_family == AF_INET6 ? "ipv6" :
599 in2 = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
600 mask = ntohl (((struct sockaddr_in *) ifa->ifa_netmask)
603 fprintf (stderr, "%s: if: %4s: inet = %s /%d 0x%08lx\n",
609 if (in2.s_addr == 0x0100007f || /* 127.0.0.1 in network order */
613 /* At least on the AT&T 3G network, pinging either of the two
614 hosts on a /31 network doesn't work, so don't try.
616 if (mask_width (mask) == 31)
620 "Can't ping subnet:\n"
625 inet_ntoa (in2), mask_width (mask), ifa->ifa_name);
626 if (*error_ret) free (*error_ret);
627 *error_ret = strdup (buf);
632 subnet_width = mask_width (mask);
637 if (*error_ret) free (*error_ret);
639 n_base = in.s_addr; /* already in network order, I think? */
641 else if (!*error_ret)
642 *error_ret = strdup ("Unable to determine\nlocal IP address\n");
650 # else /* !HAVE_GETIFADDRS */
652 /* If we can't walk the list of network interfaces to figure out
653 our local IP address, try to do it by finding the local host
654 name, then resolving that.
656 char hostname[BUFSIZ];
657 struct hostent *hent = 0;
659 if (gethostname(hostname, BUFSIZ))
661 *error_ret = strdup ("Unable to determine\n"
666 /* Get our IP address and convert it to a string */
668 hent = gethostbyname(hostname);
671 strcat (hostname, ".local"); /* Necessary on iphone */
672 hent = gethostbyname(hostname);
678 "Unable to resolve\n"
681 *error_ret = strdup(buf);
685 strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
686 n_base = pack_addr (hent->h_addr_list[0][0],
687 hent->h_addr_list[0][1],
688 hent->h_addr_list[0][2],
689 hent->h_addr_list[0][3]);
691 if (n_base == 0x0100007f) /* 127.0.0.1 in network order */
693 unsigned int a, b, c, d;
694 unpack_addr (n_base, &a, &b, &c, &d);
696 "Unable to determine\n"
697 "local subnet address:\n"
702 hostname, a, b, c, d);
703 *error_ret = strdup(buf);
707 # endif /* !HAVE_GETIFADDRS */
711 /* Construct targets for all addresses in this subnet */
713 h_mask = width_mask (subnet_width);
714 h_base = ntohl (n_base);
716 if (desc_ret && !*desc_ret) {
717 unsigned int a, b, c, d;
719 unpack_addr (n_base, &a, &b, &c, &d);
720 sprintf (buf, "%u.%u.%u.%u/%d", a, b, c, d, subnet_width);
721 *desc_ret = strdup (buf);
724 for (i = 255; i >= 0; i--) {
725 unsigned int a, b, c, d;
726 int ip = (h_base & 0xFFFFFF00L) | i; /* host order */
728 if ((ip & h_mask) != (h_base & h_mask)) /* skip out-of-subnet host */
730 else if (subnet_width == 31) /* 1-bit bridge: 2 hosts */
732 else if ((ip & ~h_mask) == 0) /* skip network address */
734 else if ((ip & ~h_mask) == ~h_mask) /* skip broadcast address */
737 unpack_addr (htonl (ip), &a, &b, &c, &d);
738 sprintf (address, "%u.%u.%u.%u", a, b, c, d);
742 unsigned int aa, ab, ac, ad;
743 unsigned int ma, mb, mc, md;
744 unpack_addr (htonl (h_base & h_mask), &aa, &ab, &ac, &ad);
745 unpack_addr (htonl (h_mask), &ma, &mb, &mc, &md);
747 "%s: subnet: %s (%u.%u.%u.%u & %u.%u.%u.%u / %d)\n",
754 p = address + strlen(address) + 1;
757 new = bogie_for_host (ssd, address, pd->resolve_p);
769 /* Send a ping packet.
772 send_ping (ping_data *pd, const sonar_bogie *b)
774 ping_bogie *pb = (ping_bogie *) b->closure;
777 const char *token = "org.jwz.xscreensaver.sonar";
779 int pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) +
780 strlen(b->name) + 1 +
782 strlen(pd->version) + 1);
784 /* Create the ICMP packet */
786 if (! (packet = (u_char *) calloc(1, pcktsiz)))
787 return; /* Out of memory */
789 icmph = (struct ICMP *) packet;
790 ICMP_TYPE(icmph) = ICMP_ECHO;
791 ICMP_CODE(icmph) = 0;
792 ICMP_CHECKSUM(icmph) = 0;
793 ICMP_ID(icmph) = pd->pid;
794 ICMP_SEQ(icmph) = pd->seq++;
795 # ifdef GETTIMEOFDAY_TWO_ARGS
796 gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)],
797 (struct timezone *) 0);
799 gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]);
802 /* We store the name of the host we're pinging in the packet, and parse
803 that out of the return packet later (see get_ping() for why).
804 After that, we also include the name and version of this program,
805 just to give a clue to anyone sniffing and wondering what's up.
807 sprintf ((char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)],
809 b->name, 0, token, pd->version);
811 ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
815 if (sendto(pd->icmpsock, packet, pcktsiz, 0,
816 &pb->address, sizeof(pb->address))
821 sprintf(buf, "%s: pinging %s", progname, b->name);
835 /* Compute the checksum on a ping packet.
838 checksum (u_short *packet, int size)
840 register int nleft = size;
841 register u_short *w = packet;
842 register int sum = 0;
845 /* Using a 32 bit accumulator (sum), we add sequential 16 bit words
846 to it, and at the end, fold back all the carry bits from the
847 top 16 bits into the lower 16 bits.
855 /* mop up an odd byte, if necessary */
859 *(u_char *)(&answer) = *(u_char *)w ;
860 *(1 + (u_char *)(&answer)) = 0;
864 /* add back carry outs from top 16 bits to low 16 bits */
866 sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
867 sum += (sum >> 16); /* add carry */
868 answer = ~sum; /* truncate to 16 bits */
874 /* Copies the sonar_bogie and the underlying ping_bogie.
877 copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
879 sonar_bogie *b2 = sonar_copy_bogie (ssd, b);
882 ping_bogie *pb = (ping_bogie *) b->closure;
883 ping_bogie *pb2 = (ping_bogie *) calloc (1, sizeof(*pb));
884 pb2->address = pb->address;
891 /* Look for all outstanding ping replies.
894 get_ping (sonar_sensor_data *ssd)
896 ping_data *pd = (ping_data *) ssd->closure;
897 struct sockaddr from;
898 unsigned int fromlen; /* Posix says socklen_t, but that's not portable */
902 struct timeval *then;
907 sonar_bogie *new = 0;
913 /* Set up a signal to interrupt our wait for a packet */
915 sigemptyset(&sa.sa_mask);
917 sa.sa_handler = sigcatcher;
918 if (sigaction(SIGALRM, &sa, 0) == -1)
921 sprintf(msg, "%s: unable to trap SIGALRM", progname);
926 /* Set up a timer to interupt us if we don't get a packet */
928 it.it_interval.tv_sec = 0;
929 it.it_interval.tv_usec = 0;
930 it.it_value.tv_sec = 0;
931 it.it_value.tv_usec = pd->timeout;
933 setitimer(ITIMER_REAL, &it, 0);
935 /* Wait for a result packet */
937 fromlen = sizeof(from);
938 while (! timer_expired)
940 tv.tv_usec = pd->timeout;
943 /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
946 memset (&rfds, 0, sizeof(rfds));
948 FD_SET(pd->icmpsock, &rfds);
949 /* only wait a little while, in case we raced with the timer expiration.
950 From Valentijn Sessink <valentyn@openoffice.nl> */
951 if (select(pd->icmpsock + 1, &rfds, 0, 0, &tv) >0)
953 result = recvfrom (pd->icmpsock, packet, sizeof(packet),
956 /* Check the packet */
958 # ifdef GETTIMEOFDAY_TWO_ARGS
959 gettimeofday(&now, (struct timezone *) 0);
963 ip = (struct ip *) packet;
964 iphdrlen = IP_HDRLEN(ip) << 2;
965 icmph = (struct ICMP *) &packet[iphdrlen];
966 then = (struct timeval *) &packet[iphdrlen + sizeof(struct ICMP)];
969 /* Ignore anything but ICMP Replies */
970 if (ICMP_TYPE(icmph) != ICMP_ECHOREPLY)
973 /* Ignore packets not set from us */
974 if (ICMP_ID(icmph) != pd->pid)
977 /* Find the bogie in 'targets' that corresponds to this packet
978 and copy it, so that this bogie stays in the same spot (th)
979 on the screen, and so that we don't have to resolve it again.
981 We could find the bogie by comparing ip->ip_src.s_addr to
982 pb->address, but it is possible that, in certain weird router
983 or NAT situations, that the reply will come back from a
984 different address than the one we sent it to. So instead,
985 we parse the name out of the reply packet payload.
988 const char *name = (char *) &packet[iphdrlen +
989 sizeof(struct ICMP) +
990 sizeof(struct timeval)];
992 for (b = pd->targets; b; b = b->next)
993 if (!strcmp (name, b->name))
995 new = copy_ping_bogie (ssd, b);
1000 if (! new) /* not in targets? */
1002 unsigned int a, b, c, d;
1003 unpack_addr (ip->ip_src.s_addr, &a, &b, &c, &d);
1005 "%s: UNEXPECTED PING REPLY! "
1006 "%4d bytes, icmp_seq=%-4d from %d.%d.%d.%d\n",
1007 progname, result, ICMP_SEQ(icmph), a, b, c, d);
1015 double msec = delta(then, &now) / 1000.0;
1019 if (new->desc) free (new->desc);
1020 new->desc = (char *) malloc (30);
1021 if (msec > 99) sprintf (new->desc, "%.0f ms", msec);
1022 else if (msec > 9) sprintf (new->desc, "%.1f ms", msec);
1023 else if (msec > 1) sprintf (new->desc, "%.2f ms", msec);
1024 else sprintf (new->desc, "%.3f ms", msec);
1027 if (pd->debug_p && pd->times_p) /* ping-like stdout log */
1029 char *s = strdup(new->name);
1033 s2 = s + strlen(s) - 28;
1034 strncpy (s2, "...", 3);
1037 "%3d bytes from %28s: icmp_seq=%-4d time=%s\n",
1038 result, s2, ICMP_SEQ(icmph), new->desc);
1043 /* The radius must be between 0.0 and 1.0.
1044 We want to display ping times on a logarithmic scale,
1045 with the three rings being 2.5, 70 and 2,000 milliseconds.
1047 if (msec <= 0) msec = 0.001;
1048 new->r = log (msec * 10) / log (20000);
1050 /* Don't put anyone *too* close to the center of the screen. */
1051 if (new->r < 0) new->r = 0;
1052 if (new->r < 0.1) new->r += 0.1;
1061 /* difference between the two times in microseconds.
1064 delta (struct timeval *then, struct timeval *now)
1066 return (((now->tv_sec - then->tv_sec) * 1000000) +
1067 (now->tv_usec - then->tv_usec));
1072 ping_free_data (sonar_sensor_data *ssd, void *closure)
1074 ping_data *pd = (ping_data *) closure;
1075 sonar_bogie *b = pd->targets;
1078 sonar_bogie *b2 = b->next;
1079 sonar_free_bogie (ssd, b);
1086 ping_free_bogie_data (sonar_sensor_data *sd, void *closure)
1092 /* Returns the current time in seconds as a double.
1098 # ifdef GETTIMEOFDAY_TWO_ARGS
1099 struct timezone tzp;
1100 gettimeofday(&now, &tzp);
1105 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
1109 /* If a bogie is provided, pings it.
1110 Then, returns all outstanding ping replies.
1112 static sonar_bogie *
1113 ping_scan (sonar_sensor_data *ssd)
1115 ping_data *pd = (ping_data *) ssd->closure;
1116 double now = double_time();
1117 double ping_cycle = 10; /* re-ping a given host every 10 seconds */
1118 double ping_interval = ping_cycle / pd->target_count;
1120 if (now > pd->last_ping_time + ping_interval) /* time to ping someone */
1122 if (pd->last_pinged)
1123 pd->last_pinged = pd->last_pinged->next;
1124 if (! pd->last_pinged)
1125 pd->last_pinged = pd->targets;
1126 send_ping (pd, pd->last_pinged);
1127 pd->last_ping_time = now;
1130 return get_ping (ssd);
1134 /* Returns a list of hosts to ping based on the "-ping" argument.
1136 static sonar_bogie *
1137 parse_mode (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
1138 const char *ping_arg, Bool ping_works_p)
1140 ping_data *pd = (ping_data *) ssd->closure;
1141 char *source, *token, *end, dummy;
1142 sonar_bogie *hostlist = 0;
1143 const char *fallback = "subnet";
1147 if (fallback && (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default")))
1148 source = strdup(fallback);
1150 source = strdup(ping_arg);
1155 end = source + strlen(source);
1159 sonar_bogie *new = 0;
1163 unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
1168 *next != ',' && *next != ' ' && *next != '\t' && *next != '\n';
1175 fprintf (stderr, "%s: parsing %s\n", progname, token);
1179 *error_ret = strdup ("Sonar must be setuid to ping!\n"
1180 "Running simulation instead.");
1184 if ((4 == sscanf (token, "%u.%u.%u/%u %c", &n0,&n1,&n2, &m,&d)) ||
1185 (5 == sscanf (token, "%u.%u.%u.%u/%u %c", &n0,&n1,&n2,&n3,&m,&d)))
1187 /* subnet: A.B.C.D/M
1190 unsigned long ip = pack_addr (n0, n1, n2, n3);
1191 new = subnet_hosts (ssd, error_ret, desc_ret, ip, m);
1193 else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d))
1197 new = bogie_for_host (ssd, token, pd->resolve_p);
1199 else if (!strcmp (token, "subnet"))
1201 new = subnet_hosts (ssd, error_ret, desc_ret, 0, 24);
1203 else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
1205 new = subnet_hosts (ssd, error_ret, desc_ret, 0, m);
1207 else if (*token == '.' || *token == '/' ||
1208 *token == '$' || *token == '~')
1211 new = read_hosts_file (ssd, token);
1213 if (pd->debug_p) fprintf (stderr, "%s: skipping file\n", progname);
1217 else if (!stat (token, &st))
1219 new = read_hosts_file (ssd, token);
1221 # endif /* READ_FILES */
1224 /* not an existant file - must be a host name
1226 new = bogie_for_host (ssd, token, pd->resolve_p);
1231 sonar_bogie *nn = new;
1234 nn->next = hostlist;
1239 while (token < end &&
1240 (*token == ',' || *token == ' ' ||
1241 *token == '\t' || *token == '\n'))
1247 /* If the arg was completely unparsable, fall back to the local subnet.
1248 This happens if the default is "/etc/hosts" but READ_FILES is off.
1249 Or if we're on a /31 network, in which case we try twice then fail.
1251 if (!hostlist && fallback)
1254 fprintf (stderr, "%s: no hosts parsed! Trying %s\n",
1255 progname, fallback);
1256 ping_arg = fallback;
1266 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
1267 const char *subnet, int timeout,
1268 Bool resolve_p, Bool times_p, Bool debug_p)
1270 sonar_sensor_data *ssd = (sonar_sensor_data *) calloc (1, sizeof(*ssd));
1271 ping_data *pd = (ping_data *) calloc (1, sizeof(*pd));
1276 Bool socket_initted_p = False;
1277 Bool socket_raw_p = False;
1279 pd->resolve_p = resolve_p;
1280 pd->times_p = times_p;
1281 pd->debug_p = debug_p;
1284 ssd->scan_cb = ping_scan;
1285 ssd->free_data_cb = ping_free_data;
1286 ssd->free_bogie_cb = ping_free_bogie_data;
1288 /* Get short version number. */
1289 s = strchr (screensaver_id, ' ');
1290 pd->version = strdup (s+1);
1291 s = strchr (pd->version, ' ');
1295 /* Create the ICMP socket. Do this before dropping privs.
1297 Raw sockets can only be opened by root (or setuid root), so we
1298 only try to do this when the effective uid is 0.
1300 We used to just always try, and notice the failure. But apparently
1301 that causes "SELinux" to log spurious warnings when running with the
1302 "strict" policy. So to avoid that, we just don't try unless we
1305 On MacOS X, we can avoid the whole problem by using a
1306 non-privileged datagram instead of a raw socket.
1308 if (global_icmpsock)
1310 pd->icmpsock = global_icmpsock;
1311 socket_initted_p = True;
1313 fprintf (stderr, "%s: re-using icmp socket\n", progname);
1316 else if ((pd->icmpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) >= 0)
1318 socket_initted_p = True;
1320 else if (geteuid() == 0 &&
1321 (pd->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) >= 0)
1323 socket_initted_p = True;
1324 socket_raw_p = True;
1327 if (socket_initted_p)
1329 global_icmpsock = pd->icmpsock;
1330 socket_initted_p = True;
1332 fprintf (stderr, "%s: opened %s icmp socket\n", progname,
1333 (socket_raw_p ? "raw" : "dgram"));
1336 fprintf (stderr, "%s: unable to open icmp socket\n", progname);
1341 pd->pid = getpid() & 0xFFFF;
1343 pd->timeout = timeout;
1345 /* Generate a list of targets */
1347 pd->targets = parse_mode (ssd, error_ret, desc_ret, subnet,
1349 pd->targets = delete_duplicate_hosts (ssd, pd->targets);
1353 fprintf (stderr, "%s: Target list:\n", progname);
1354 for (b = pd->targets; b; b = b->next)
1356 ping_bogie *pb = (ping_bogie *) b->closure;
1357 struct sockaddr_in *iaddr = (struct sockaddr_in *) &(pb->address);
1358 unsigned long ip = iaddr->sin_addr.s_addr;
1359 fprintf (stderr, "%s: ", progname);
1360 print_host (stderr, ip, b->name);
1364 /* Make sure there is something to ping */
1366 pd->target_count = 0;
1367 for (b = pd->targets; b; b = b->next)
1370 if (pd->target_count == 0)
1373 *error_ret = strdup ("No hosts to ping!\n"
1374 "Simulating instead.");
1375 if (pd) ping_free_data (ssd, pd);
1376 if (ssd) free (ssd);
1380 /* Distribute them evenly around the display field, clockwise.
1381 Even on a /24, allocated IPs tend to cluster together, so
1382 don't put any two hosts closer together than N degrees to
1383 avoid unnecessary overlap when we have plenty of space due
1384 to addresses that probably won't respond.
1387 int min_separation = 23; /* degrees */
1388 int div = pd->target_count;
1389 if (div > 360 / min_separation)
1390 div = 360 / min_separation;
1391 for (i = 0, b = pd->targets; b; b = b->next, i++)
1393 M_PI * 2 * ((div - i) % div) / div);
1399 #endif /* HAVE_PING -- whole file */