1 /* sonar, Copyright (c) 1998-2017 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"
17 #include "async_netdb.h"
19 #undef usleep /* conflicts with unistd.h on OSX */
22 /* Note: to get this to compile for iPhone, you need to fix Xcode!
23 The icmp headers exist for the simulator build environment, but
24 not for the real-device build environment. This appears to
25 just be an Apple bug, not intentional.
27 xc=/Applications/Xcode.app/Contents
28 for path in /Developer/Platforms/iPhone*?/Developer/SDKs/?* \
29 $xc/Developer/Platforms/iPhone*?/Developer/SDKs/?* ; do
31 /usr/include/netinet/ip.h \
32 /usr/include/netinet/in_systm.h \
33 /usr/include/netinet/ip_icmp.h \
34 /usr/include/netinet/ip_var.h \
35 /usr/include/netinet/udp.h
37 ln -s "$file" "$path$file"
47 #if defined(HAVE_ICMP) || defined(HAVE_ICMPHDR)
49 # include <sys/stat.h>
53 # include <sys/types.h>
54 # include <sys/time.h>
59 # include <sys/socket.h>
60 # include <netinet/in_systm.h>
61 # include <netinet/in.h>
62 # include <netinet/ip.h>
63 # include <netinet/ip_icmp.h>
64 # include <netinet/udp.h>
65 # include <arpa/inet.h>
68 # ifdef HAVE_GETIFADDRS
71 #endif /* HAVE_ICMP || HAVE_ICMPHDR */
73 #if defined(HAVE_ICMP)
76 # define ICMP_TYPE(p) (p)->icmp_type
77 # define ICMP_CODE(p) (p)->icmp_code
78 # define ICMP_CHECKSUM(p) (p)->icmp_cksum
79 # define ICMP_ID(p) (p)->icmp_id
80 # define ICMP_SEQ(p) (p)->icmp_seq
81 #elif defined(HAVE_ICMPHDR)
84 # define ICMP_TYPE(p) (p)->type
85 # define ICMP_CODE(p) (p)->code
86 # define ICMP_CHECKSUM(p) (p)->checksum
87 # define ICMP_ID(p) (p)->un.echo.id
88 # define ICMP_SEQ(p) (p)->un.echo.sequence
100 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
101 const char *subnet, int timeout,
102 Bool resolve_p, Bool times_p, Bool debug_p)
104 if (! (!subnet || !*subnet || !strcmp(subnet, "default")))
105 fprintf (stderr, "%s: not compiled with support for pinging hosts.\n",
110 #else /* HAVE_PING -- whole file */
113 #if defined(__DECC) || defined(_IP_VHL)
114 /* This is how you do it on DEC C, and possibly some BSD systems. */
115 # define IP_HDRLEN(ip) ((ip)->ip_vhl & 0x0F)
117 /* This is how you do it on everything else. */
118 # define IP_HDRLEN(ip) ((ip)->ip_hl)
121 /* yes, there is only one, even when multiple savers are running in the
122 same address space - since we can only open this socket before dropping
125 static int global_icmpsock = 0;
127 /* Set by a signal handler. */
128 static int timer_expired;
132 static u_short checksum(u_short *, int);
133 static long delta(struct timeval *, struct timeval *);
137 Display *dpy; /* Only used to get *useThreads. */
139 char *version; /* short version number of xscreensaver */
140 int icmpsock; /* socket for sending pings */
141 int pid; /* our process ID */
142 int seq; /* packet sequence number */
143 int timeout; /* packet timeout */
146 sonar_bogie *targets; /* the hosts we will ping;
147 those that pong end up on ssd->pending. */
148 sonar_bogie *last_pinged; /* pointer into 'targets' list */
149 double last_ping_time;
158 async_name_from_addr_t lookup_name;
159 async_addr_from_name_t lookup_addr;
160 async_netdb_sockaddr_storage_t address; /* ip address */
167 /* Packs an IP address quad into bigendian network order. */
169 pack_addr (unsigned int a, unsigned int b, unsigned int c, unsigned int d)
171 unsigned long i = (((a & 255) << 24) |
178 /* Unpacks an IP address quad from bigendian network order. */
180 unpack_addr (unsigned long addr,
181 unsigned int *a, unsigned int *b,
182 unsigned int *c, unsigned int *d)
185 *a = (addr >> 24) & 255;
186 *b = (addr >> 16) & 255;
187 *c = (addr >> 8) & 255;
194 /* Resolves the bogie's name (either a hostname or ip address string)
195 to a hostent. Returns 1 if successful, 0 if something went wrong.
198 resolve_bogie_hostname (ping_data *pd, sonar_bogie *sb, Bool resolve_p)
200 ping_bogie *pb = (ping_bogie *) sb->closure;
205 if (4 == sscanf (sb->name, " %u.%u.%u.%u %c",
206 &ip[0], &ip[1], &ip[2], &ip[3], &c))
208 /* It's an IP address.
210 struct sockaddr_in *iaddr = (struct sockaddr_in *) &(pb->address);
215 fprintf (stderr, "%s: ignoring bogus IP %s\n",
220 iaddr->sin_family = AF_INET;
221 iaddr->sin_addr.s_addr = pack_addr (ip[0], ip[1], ip[2], ip[3]);
222 pb->addrlen = sizeof(struct sockaddr_in);
227 async_name_from_addr_start (pd->dpy,
228 (const struct sockaddr *)&pb->address,
230 if (!pb->lookup_name)
232 fprintf (stderr, "%s: unable to start host resolution.\n",
239 /* It's a host name. */
241 /* don't waste time being confused by non-hostname tokens
242 in .ssh/known_hosts */
243 if (!strcmp (sb->name, "ssh-rsa") ||
244 !strcmp (sb->name, "ssh-dsa") ||
245 !strcmp (sb->name, "ssh-dss") ||
246 !strncmp (sb->name, "ecdsa-", 6) ||
247 strlen (sb->name) >= 80)
250 /* .ssh/known_hosts sometimes contains weirdness like "[host]:port".
252 if (strchr (sb->name, '['))
255 fprintf (stderr, "%s: ignoring bogus address \"%s\"\n",
260 /* If the name contains a colon, it's probably IPv6. */
261 if (strchr (sb->name, ':'))
264 fprintf (stderr, "%s: ignoring ipv6 address \"%s\"\n",
269 pb->lookup_addr = async_addr_from_name_start(pd->dpy, sb->name);
270 if (!pb->lookup_addr)
273 /* Either address space exhaustion or RAM exhaustion. */
274 fprintf (stderr, "%s: unable to start host resolution.\n",
284 print_address (FILE *out, int width, const void *sockaddr, socklen_t addrlen)
286 #ifdef HAVE_GETADDRINFO
287 char buf[NI_MAXHOST];
292 const struct sockaddr *addr = (const struct sockaddr *)sockaddr;
293 const char *ips = buf;
295 if (!addr->sa_family)
296 ips = "<no address>";
299 #ifdef HAVE_GETADDRINFO
300 int gai_error = getnameinfo (sockaddr, addrlen, buf, sizeof(buf),
301 NULL, 0, NI_NUMERICHOST);
302 if (gai_error == EAI_SYSTEM)
303 ips = strerror(errno);
305 ips = gai_strerror(gai_error);
307 switch (addr->sa_family)
311 u_long ip = ((struct sockaddr_in *)sockaddr)->sin_addr.s_addr;
312 unsigned int a, b, c, d;
313 unpack_addr (ip, &a, &b, &c, &d); /* ip is in network order */
314 sprintf (buf, "%u.%u.%u.%u", a, b, c, d);
324 fprintf (out, "%-*s", width, ips);
329 print_host (FILE *out, const sonar_bogie *sb)
331 const ping_bogie *pb = (const ping_bogie *) sb->closure;
332 const char *name = sb->name;
333 if (!name || !*name) name = "<unknown>";
334 print_address (out, 16, &pb->address, pb->addrlen);
335 fprintf (out, " %s\n", name);
340 is_address_ok(Bool debug_p, const sonar_bogie *b)
342 const ping_bogie *pb = (const ping_bogie *) b->closure;
343 const struct sockaddr *addr = (const struct sockaddr *)&pb->address;
345 switch (addr->sa_family)
349 struct sockaddr_in *iaddr = (struct sockaddr_in *) addr;
351 /* Don't ever use loopback (127.0.0.x) hosts */
352 unsigned long ip = iaddr->sin_addr.s_addr;
353 if ((ntohl (ip) & 0xFFFFFF00L) == 0x7f000000L) /* 127.0.0.x */
356 fprintf (stderr, "%s: ignoring loopback host %s\n",
361 /* Don't ever use broadcast (255.x.x.x) hosts */
362 if ((ntohl (ip) & 0xFF000000L) == 0xFF000000L) /* 255.x.x.x */
365 fprintf (stderr, "%s: ignoring broadcast host %s\n",
378 /* Create a sonar_bogie from a host name or ip address string.
379 Returns NULL if the name could not be resolved.
382 bogie_for_host (sonar_sensor_data *ssd, const char *name, const char *fallback)
384 ping_data *pd = (ping_data *) ssd->closure;
385 sonar_bogie *b = (sonar_bogie *) calloc (1, sizeof(*b));
386 ping_bogie *pb = (ping_bogie *) calloc (1, sizeof(*pb));
387 Bool resolve_p = pd->resolve_p;
389 b->name = strdup (name);
392 if (! resolve_bogie_hostname (pd, b, resolve_p))
395 if (! pb->lookup_addr && ! is_address_ok (pd->debug_p, b))
400 fprintf (stderr, "%s: added ", progname);
401 print_host (stderr, b);
405 pb->fallback = strdup (fallback);
409 if (b) sonar_free_bogie (ssd, b);
412 return bogie_for_host (ssd, fallback, NULL);
420 /* Return a list of bogies read from a file.
421 The file can be like /etc/hosts or .ssh/known_hosts or probably
422 just about anything that has host names in it.
425 read_hosts_file (sonar_sensor_data *ssd, const char *filename)
427 ping_data *pd = (ping_data *) ssd->closure;
431 sonar_bogie *list = 0;
435 /* Kludge: on OSX, variables have not been expanded in the command
436 line arguments, so as a special case, allow the string to begin
437 with literal "$HOME/" or "~/".
439 This is so that the "Known Hosts" menu item in sonar.xml works.
441 if (!strncmp(filename, "~/", 2) || !strncmp(filename, "$HOME/", 6))
443 char *s = strchr (filename, '/');
444 strcpy (buf, getenv("HOME"));
449 fp = fopen(filename, "r");
453 sprintf(buf2, "%s: %s", progname, filename);
455 if (pd->debug_p) /* on OSX don't syslog this */
462 fprintf (stderr, "%s: reading \"%s\"\n", progname, filename);
464 while ((p = fgets(buf, LINE_MAX, fp)))
466 while ((*p == ' ') || (*p == '\t')) /* skip whitespace */
468 if (*p == '#') /* skip comments */
471 /* Get the name and address */
473 if ((addr = strtok(buf, " ,;\t\n")))
474 name = strtok(0, " ,;\t\n");
478 /* Check to see if the addr looks like an addr. If not, assume
479 the addr is a name and there is no addr. This way, we can
480 handle files whose lines have "xx.xx.xx.xx hostname" as their
481 first two tokens, and also files that have a hostname as their
482 first token (like .ssh/known_hosts and .rhosts.)
486 if (4 != sscanf(addr, "%d.%d.%d.%d%c", &i, &i, &i, &i, &c))
493 /* If the name is all digits, it's not a name. */
497 for (s = name; *s; s++)
498 if (*s < '0' || *s > '9')
503 fprintf (stderr, "%s: skipping bogus name \"%s\" (%s)\n",
504 progname, name, addr);
509 /* Create a new target using first the name then the address */
517 new = bogie_for_host (ssd, name, addr);
529 #endif /* READ_FILES */
532 static sonar_bogie **
533 found_duplicate_host (const ping_data *pd, sonar_bogie **list,
538 fprintf (stderr, "%s: deleted duplicate: ", progname);
539 print_host (stderr, bogie);
546 static sonar_bogie **
547 find_duplicate_host (const ping_data *pd, sonar_bogie **list,
550 const ping_bogie *pb = (const ping_bogie *) bogie->closure;
551 const struct sockaddr *addr1 = (const struct sockaddr *) &(pb->address);
555 const ping_bogie *pb2 = (const ping_bogie *) (*list)->closure;
557 if (!pb2->lookup_addr)
559 const struct sockaddr *addr2 =
560 (const struct sockaddr *) &(pb2->address);
562 if (addr1->sa_family == addr2->sa_family)
564 switch (addr1->sa_family)
569 ((const struct sockaddr_in *)addr1)->sin_addr.s_addr;
570 const struct sockaddr_in *i2 =
571 (const struct sockaddr_in *)addr2;
572 unsigned long ip2 = i2->sin_addr.s_addr;
575 return found_duplicate_host (pd, list, bogie);
582 &((const struct sockaddr_in6 *)addr1)->sin6_addr,
583 &((const struct sockaddr_in6 *)addr2)->sin6_addr,
585 return found_duplicate_host (pd, list, bogie);
591 /* Fallback behavior: Just memcmp the two addresses.
593 For this to work, unused space in the sockaddr must be
594 set to zero. Which may actually be the case:
595 - async_addr_from_name_finish won't put garbage into
596 sockaddr_in.sin_zero or elsewhere unless getaddrinfo
598 - ping_bogie is allocated with calloc(). */
600 if (pb->addrlen == pb2->addrlen &&
601 ! memcmp(addr1, addr2, pb->addrlen))
602 return found_duplicate_host (pd, list, bogie);
609 list = &(*list)->next;
617 delete_duplicate_hosts (sonar_sensor_data *ssd, sonar_bogie *list)
619 ping_data *pd = (ping_data *) ssd->closure;
620 sonar_bogie *head = list;
621 sonar_bogie *sb = head;
625 ping_bogie *pb = (ping_bogie *) sb->closure;
627 if (!pb->lookup_addr)
629 sonar_bogie **sb2 = find_duplicate_host (pd, &sb->next, sb);
645 width_mask (unsigned long width)
649 for (i = 0; i < width; i++)
655 #ifdef HAVE_GETIFADDRS
657 mask_width (unsigned long mask)
660 for (i = 0; i < 32; i++)
668 /* Generate a list of bogies consisting of all of the entries on
669 the same subnet. 'base' ip is in network order; 0 means localhost.
672 subnet_hosts (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
673 unsigned long n_base, int subnet_width)
675 ping_data *pd = (ping_data *) ssd->closure;
676 unsigned long h_mask; /* host order */
677 unsigned long h_base; /* host order */
678 char address[BUFSIZ];
682 sonar_bogie *list = 0;
685 if (subnet_width < 24)
688 "Pinging %lu hosts is a bad\n"
689 "idea. Please use a subnet\n"
690 "mask of 24 bits or more.",
691 (unsigned long) (1L << (32 - subnet_width)) - 1);
692 *error_ret = strdup(buf);
695 else if (subnet_width > 30)
699 "doesn't make sense.\n"
700 "Try \"subnet/24\"\n"
701 "or \"subnet/29\".\n",
703 *error_ret = strdup(buf);
709 fprintf (stderr, "%s: adding %d-bit subnet\n", progname, subnet_width);
714 # ifdef HAVE_GETIFADDRS
716 /* To determine the local subnet, we need to know the local IP address.
717 Do this by looking at the IPs of every network interface.
719 struct in_addr in = { 0, };
720 struct ifaddrs *all = 0, *ifa;
723 fprintf (stderr, "%s: listing network interfaces\n", progname);
726 for (ifa = all; ifa; ifa = ifa->ifa_next)
730 if (ifa->ifa_addr->sa_family != AF_INET)
733 fprintf (stderr, "%s: if: %4s: %s\n", progname,
737 ifa->ifa_addr->sa_family == AF_UNIX ? "local" :
740 ifa->ifa_addr->sa_family == AF_LINK ? "link" :
743 ifa->ifa_addr->sa_family == AF_INET6 ? "ipv6" :
748 in2 = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
749 mask = ntohl (((struct sockaddr_in *) ifa->ifa_netmask)
752 fprintf (stderr, "%s: if: %4s: inet = %s /%d 0x%08lx\n",
758 if (in2.s_addr == 0x0100007f || /* 127.0.0.1 in network order */
762 /* At least on the AT&T 3G network, pinging either of the two
763 hosts on a /31 network doesn't work, so don't try.
765 if (mask_width (mask) == 31)
768 "Can't ping subnet:\n"
773 inet_ntoa (in2), mask_width (mask), ifa->ifa_name);
774 if (*error_ret) free (*error_ret);
775 *error_ret = strdup (buf);
780 subnet_width = mask_width (mask);
785 if (*error_ret) free (*error_ret);
787 n_base = in.s_addr; /* already in network order, I think? */
789 else if (!*error_ret)
790 *error_ret = strdup ("Unable to determine\nlocal IP address\n");
798 # else /* !HAVE_GETIFADDRS */
800 /* If we can't walk the list of network interfaces to figure out
801 our local IP address, try to do it by finding the local host
802 name, then resolving that.
804 char hostname[BUFSIZ];
805 struct hostent *hent = 0;
807 if (gethostname(hostname, BUFSIZ))
809 *error_ret = strdup ("Unable to determine\n"
814 /* Get our IP address and convert it to a string */
816 hent = gethostbyname(hostname);
819 strcat (hostname, ".local"); /* Necessary on iphone */
820 hent = gethostbyname(hostname);
826 "Unable to resolve\n"
827 "local host \"%.100s\"",
829 *error_ret = strdup(buf);
833 strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
834 n_base = pack_addr (hent->h_addr_list[0][0],
835 hent->h_addr_list[0][1],
836 hent->h_addr_list[0][2],
837 hent->h_addr_list[0][3]);
839 if (n_base == 0x0100007f) /* 127.0.0.1 in network order */
841 unsigned int a, b, c, d;
842 unpack_addr (n_base, &a, &b, &c, &d);
844 "Unable to determine\n"
845 "local subnet address:\n"
850 hostname, a, b, c, d);
851 *error_ret = strdup(buf);
855 # endif /* !HAVE_GETIFADDRS */
859 /* Construct targets for all addresses in this subnet */
861 h_mask = width_mask (subnet_width);
862 h_base = ntohl (n_base);
864 if (desc_ret && !*desc_ret) {
866 unsigned int a, b, c, d;
867 unsigned long bb = n_base & htonl(h_mask);
868 unpack_addr (bb, &a, &b, &c, &d);
869 if (subnet_width > 24)
870 sprintf (buf2, "%u.%u.%u.%u/%d", a, b, c, d, subnet_width);
872 sprintf (buf2, "%u.%u.%u/%d", a, b, c, subnet_width);
873 *desc_ret = strdup (buf2);
876 for (i = 255; i >= 0; i--) {
877 unsigned int a, b, c, d;
878 int ip = (h_base & 0xFFFFFF00L) | i; /* host order */
880 if ((ip & h_mask) != (h_base & h_mask)) /* skip out-of-subnet host */
882 else if (subnet_width == 31) /* 1-bit bridge: 2 hosts */
884 else if ((ip & ~h_mask) == 0) /* skip network address */
886 else if ((ip & ~h_mask) == ~h_mask) /* skip broadcast address */
889 unpack_addr (htonl (ip), &a, &b, &c, &d);
890 sprintf (address, "%u.%u.%u.%u", a, b, c, d);
894 unsigned int aa, ab, ac, ad;
895 unsigned int ma, mb, mc, md;
896 unpack_addr (htonl (h_base & h_mask), &aa, &ab, &ac, &ad);
897 unpack_addr (htonl (h_mask), &ma, &mb, &mc, &md);
899 "%s: subnet: %s (%u.%u.%u.%u & %u.%u.%u.%u / %d)\n",
906 p = address + strlen(address) + 1;
909 new = bogie_for_host (ssd, address, NULL);
921 /* Send a ping packet.
924 send_ping (ping_data *pd, const sonar_bogie *b)
926 ping_bogie *pb = (ping_bogie *) b->closure;
929 const char *token = "org.jwz.xscreensaver.sonar";
932 unsigned long pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) +
933 sizeof(socklen_t) + pb->addrlen +
935 strlen(pd->version) + 1);
937 /* Create the ICMP packet */
939 if (! (packet = (u_char *) calloc(1, pcktsiz)))
940 return; /* Out of memory */
942 icmph = (struct ICMP *) packet;
943 ICMP_TYPE(icmph) = ICMP_ECHO;
944 ICMP_CODE(icmph) = 0;
945 ICMP_CHECKSUM(icmph) = 0;
946 ICMP_ID(icmph) = pd->pid;
947 ICMP_SEQ(icmph) = pd->seq++;
948 # ifdef GETTIMEOFDAY_TWO_ARGS
949 gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)],
950 (struct timezone *) 0);
952 gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]);
955 /* We store the sockaddr of the host we're pinging in the packet, and parse
956 that out of the return packet later (see get_ping() for why).
957 After that, we also include the name and version of this program,
958 just to give a clue to anyone sniffing and wondering what's up.
960 host_id = (char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)];
961 *(socklen_t *)host_id = pb->addrlen;
962 host_id += sizeof(socklen_t);
963 memcpy(host_id, &pb->address, pb->addrlen);
964 host_id += pb->addrlen;
965 sprintf (host_id, "%.20s %.20s", token, pd->version);
967 ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
971 if (sendto(pd->icmpsock, packet, pcktsiz, 0,
972 (struct sockaddr *)&pb->address, sizeof(pb->address))
977 sprintf(buf, "%s: pinging %.100s", progname, b->name);
991 /* Compute the checksum on a ping packet.
994 checksum (u_short *packet, int size)
996 register int nleft = size;
997 register u_short *w = packet;
998 register int sum = 0;
1001 /* Using a 32 bit accumulator (sum), we add sequential 16 bit words
1002 to it, and at the end, fold back all the carry bits from the
1003 top 16 bits into the lower 16 bits.
1011 /* mop up an odd byte, if necessary */
1015 *(u_char *)(&answer) = *(u_char *)w ;
1016 *(1 + (u_char *)(&answer)) = 0;
1020 /* add back carry outs from top 16 bits to low 16 bits */
1022 sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
1023 sum += (sum >> 16); /* add carry */
1024 answer = ~sum; /* truncate to 16 bits */
1030 /* Copies the sonar_bogie and the underlying ping_bogie.
1032 static sonar_bogie *
1033 copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
1035 sonar_bogie *b2 = sonar_copy_bogie (ssd, b);
1038 ping_bogie *pb = (ping_bogie *) b->closure;
1039 ping_bogie *pb2 = (ping_bogie *) calloc (1, sizeof(*pb));
1040 pb2->address = pb->address;
1047 /* Look for all outstanding ping replies.
1049 static sonar_bogie *
1050 get_ping (sonar_sensor_data *ssd)
1052 ping_data *pd = (ping_data *) ssd->closure;
1053 struct sockaddr from;
1056 u_char packet[1024];
1058 struct timeval *then;
1062 sonar_bogie *bl = 0;
1063 sonar_bogie *new = 0;
1064 struct sigaction sa;
1065 struct itimerval it;
1069 /* Set up a signal to interrupt our wait for a packet */
1071 sigemptyset(&sa.sa_mask);
1073 sa.sa_handler = sigcatcher;
1074 if (sigaction(SIGALRM, &sa, 0) == -1)
1077 sprintf(msg, "%s: unable to trap SIGALRM", progname);
1082 /* Set up a timer to interupt us if we don't get a packet */
1084 it.it_interval.tv_sec = 0;
1085 it.it_interval.tv_usec = 0;
1086 it.it_value.tv_sec = 0;
1087 it.it_value.tv_usec = pd->timeout;
1089 setitimer(ITIMER_REAL, &it, 0);
1091 /* Wait for a result packet */
1093 fromlen = sizeof(from);
1094 while (! timer_expired)
1096 tv.tv_usec = pd->timeout;
1099 /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
1102 memset (&rfds, 0, sizeof(rfds));
1104 FD_SET(pd->icmpsock, &rfds);
1105 /* only wait a little while, in case we raced with the timer expiration.
1106 From Valentijn Sessink <valentyn@openoffice.nl> */
1107 if (select(pd->icmpsock + 1, &rfds, 0, 0, &tv) >0)
1109 result = (int)recvfrom (pd->icmpsock, packet, sizeof(packet),
1110 0, &from, &fromlen);
1112 /* Check the packet */
1114 # ifdef GETTIMEOFDAY_TWO_ARGS
1115 gettimeofday(&now, (struct timezone *) 0);
1119 ip = (struct ip *) packet;
1120 iphdrlen = IP_HDRLEN(ip) << 2;
1121 icmph = (struct ICMP *) &packet[iphdrlen];
1122 then = (struct timeval *) &packet[iphdrlen + sizeof(struct ICMP)];
1125 /* Ignore anything but ICMP Replies */
1126 if (ICMP_TYPE(icmph) != ICMP_ECHOREPLY)
1129 /* Ignore packets not set from us */
1130 if (ICMP_ID(icmph) != pd->pid)
1133 /* Find the bogie in 'targets' that corresponds to this packet
1134 and copy it, so that this bogie stays in the same spot (th)
1135 on the screen, and so that we don't have to resolve it again.
1137 We could find the bogie by comparing ip->ip_src.s_addr to
1138 pb->address, but it is possible that, in certain weird router
1139 or NAT situations, that the reply will come back from a
1140 different address than the one we sent it to. So instead,
1141 we parse the sockaddr out of the reply packet payload.
1144 const socklen_t *host_id = (socklen_t *) &packet[
1145 iphdrlen + sizeof(struct ICMP) + sizeof(struct timeval)];
1149 /* Ensure that a maliciously-crafted return packet can't
1150 make us overflow in memcmp. */
1151 if (result > 0 && (const u_char *)(host_id + 1) <= packet + result)
1153 const u_char *host_end = (const u_char *)(host_id + 1) +
1156 if ((const u_char *)(host_id + 1) <= host_end &&
1157 host_end <= packet + result)
1159 for (b = pd->targets; b; b = b->next)
1161 ping_bogie *pb = (ping_bogie *)b->closure;
1162 if (*host_id == pb->addrlen &&
1163 !memcmp(&pb->address, host_id + 1, pb->addrlen) )
1165 /* Check to see if the name lookup is done. */
1166 if (pb->lookup_name &&
1167 async_name_from_addr_is_done (pb->lookup_name))
1171 async_name_from_addr_finish (pb->lookup_name,
1174 if (pd->debug_p > 1)
1175 fprintf (stderr, "%s: %s => %s\n",
1177 host ? host : "<unknown>");
1185 pb->lookup_name = NULL;
1188 new = copy_ping_bogie (ssd, b);
1196 if (! new) /* not in targets? */
1198 unsigned int a, b, c, d;
1199 unpack_addr (ip->ip_src.s_addr, &a, &b, &c, &d);
1201 "%s: UNEXPECTED PING REPLY! "
1202 "%4d bytes, icmp_seq=%-4d from %d.%d.%d.%d\n",
1203 progname, result, ICMP_SEQ(icmph), a, b, c, d);
1211 double msec = delta(then, &now) / 1000.0;
1215 if (new->desc) free (new->desc);
1216 new->desc = (char *) malloc (30);
1217 if (msec > 99) sprintf (new->desc, "%.0f ms", msec);
1218 else if (msec > 9) sprintf (new->desc, "%.1f ms", msec);
1219 else if (msec > 1) sprintf (new->desc, "%.2f ms", msec);
1220 else sprintf (new->desc, "%.3f ms", msec);
1223 if (pd->debug_p && pd->times_p) /* ping-like stdout log */
1225 char *s = strdup(new->name);
1229 s2 = s + strlen(s) - 28;
1230 strncpy (s2, "...", 3);
1233 "%3d bytes from %28s: icmp_seq=%-4d time=%s\n",
1234 result, s2, ICMP_SEQ(icmph), new->desc);
1239 /* The radius must be between 0.0 and 1.0.
1240 We want to display ping times on a logarithmic scale,
1241 with the three rings being 2.5, 70 and 2,000 milliseconds.
1243 if (msec <= 0) msec = 0.001;
1244 new->r = log (msec * 10) / log (20000);
1246 /* Don't put anyone *too* close to the center of the screen. */
1247 if (new->r < 0) new->r = 0;
1248 if (new->r < 0.1) new->r += 0.1;
1257 /* difference between the two times in microseconds.
1260 delta (struct timeval *then, struct timeval *now)
1262 return (((now->tv_sec - then->tv_sec) * 1000000) +
1263 (now->tv_usec - then->tv_usec));
1268 ping_free_data (sonar_sensor_data *ssd, void *closure)
1270 ping_data *pd = (ping_data *) closure;
1271 sonar_bogie *b = pd->targets;
1274 sonar_bogie *b2 = b->next;
1275 sonar_free_bogie (ssd, b);
1282 ping_free_bogie_data (sonar_sensor_data *sd, void *closure)
1284 ping_bogie *pb = (ping_bogie *) closure;
1286 if (pb->lookup_name)
1287 async_name_from_addr_cancel (pb->lookup_name);
1288 if (pb->lookup_addr)
1289 async_addr_from_name_cancel (pb->lookup_addr);
1290 free (pb->fallback);
1296 /* Returns the current time in seconds as a double.
1302 # ifdef GETTIMEOFDAY_TWO_ARGS
1303 struct timezone tzp;
1304 gettimeofday(&now, &tzp);
1309 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
1314 free_bogie_after_lookup(sonar_sensor_data *ssd, sonar_bogie **sbp,
1317 ping_bogie *pb = (ping_bogie *)(*sb)->closure;
1320 pb->lookup_addr = NULL; /* Prevent double-free in sonar_free_bogie. */
1321 sonar_free_bogie (ssd, *sb);
1326 /* Pings the next bogie, if it's time.
1327 Returns all outstanding ping replies.
1329 static sonar_bogie *
1330 ping_scan (sonar_sensor_data *ssd)
1332 ping_data *pd = (ping_data *) ssd->closure;
1333 double now = double_time();
1334 double ping_cycle = 10; /* re-ping a given host every 10 seconds */
1335 double ping_interval = ping_cycle / pd->target_count;
1337 if (now > pd->last_ping_time + ping_interval) /* time to ping someone */
1339 struct sonar_bogie **sbp;
1341 if (pd->last_pinged)
1343 sbp = &pd->last_pinged->next;
1351 /* Aaaaand we're out of bogies. */
1352 pd->last_pinged = NULL;
1355 sonar_bogie *sb = *sbp;
1356 ping_bogie *pb = (ping_bogie *)sb->closure;
1357 if (pb->lookup_addr &&
1358 async_addr_from_name_is_done (pb->lookup_addr))
1360 if (async_addr_from_name_finish (pb->lookup_addr, &pb->address,
1361 &pb->addrlen, NULL))
1363 char *fallback = pb->fallback;
1364 pb->fallback = NULL;
1367 fprintf (stderr, "%s: could not resolve host: %s\n",
1368 progname, sb->name);
1370 free_bogie_after_lookup (ssd, sbp, &sb);
1372 /* Insert the fallback bogie right where the old one was. */
1375 sonar_bogie *new_bogie = bogie_for_host (ssd, fallback,
1378 new_bogie->next = *sbp;
1380 if (! ((ping_bogie *)new_bogie->closure)->lookup_addr &&
1381 ! find_duplicate_host(pd, &pd->targets, new_bogie))
1384 sonar_free_bogie (ssd, new_bogie);
1392 if (pd->debug_p > 1)
1394 fprintf (stderr, "%s: %s => ", progname, sb->name);
1395 print_address (stderr, 0, &pb->address, pb->addrlen);
1399 if (! is_address_ok (pd->debug_p, sb))
1400 free_bogie_after_lookup (ssd, sbp, &sb);
1401 else if (find_duplicate_host (pd, &pd->targets, sb))
1402 /* Tricky: find_duplicate_host skips the current bogie when
1403 scanning the targets list because pb->lookup_addr hasn't
1406 Not that it matters much, but behavior here is to
1407 keep the existing address.
1409 free_bogie_after_lookup (ssd, sbp, &sb);
1413 pb->lookup_addr = NULL;
1416 if (sb && !pb->lookup_addr)
1418 if (!pb->addrlen) abort();
1420 pd->last_pinged = sb;
1424 pd->last_ping_time = now;
1427 return get_ping (ssd);
1431 /* Returns a list of hosts to ping based on the "-ping" argument.
1433 static sonar_bogie *
1434 parse_mode (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
1435 const char *ping_arg, Bool ping_works_p)
1437 ping_data *pd = (ping_data *) ssd->closure;
1438 char *source, *token, *end, dummy;
1439 sonar_bogie *hostlist = 0;
1440 const char *fallback = "subnet";
1444 if (fallback && (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default")))
1445 source = strdup(fallback);
1447 source = strdup(ping_arg);
1452 end = source + strlen(source);
1456 sonar_bogie *new = 0;
1460 unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
1465 *next != ',' && *next != ' ' && *next != '\t' && *next != '\n';
1472 fprintf (stderr, "%s: parsing %s\n", progname, token);
1476 *error_ret = strdup ("Sonar must be setuid to ping!\n"
1477 "Running simulation instead.");
1481 if ((4 == sscanf (token, "%u.%u.%u/%u %c", &n0,&n1,&n2, &m,&d)) ||
1482 (5 == sscanf (token, "%u.%u.%u.%u/%u %c", &n0,&n1,&n2,&n3,&m,&d)))
1484 /* subnet: A.B.C.D/M
1487 unsigned long ip = pack_addr (n0, n1, n2, n3);
1488 new = subnet_hosts (ssd, error_ret, desc_ret, ip, m);
1490 else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d))
1494 new = bogie_for_host (ssd, token, NULL);
1496 else if (!strcmp (token, "subnet"))
1498 new = subnet_hosts (ssd, error_ret, desc_ret, 0, 24);
1500 else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
1502 new = subnet_hosts (ssd, error_ret, desc_ret, 0, m);
1504 else if (*token == '.' || *token == '/' ||
1505 *token == '$' || *token == '~')
1508 new = read_hosts_file (ssd, token);
1510 if (pd->debug_p) fprintf (stderr, "%s: skipping file\n", progname);
1514 else if (!stat (token, &st))
1516 new = read_hosts_file (ssd, token);
1518 # endif /* READ_FILES */
1521 /* not an existant file - must be a host name
1523 new = bogie_for_host (ssd, token, NULL);
1528 sonar_bogie *nn = new;
1531 nn->next = hostlist;
1536 while (token < end &&
1537 (*token == ',' || *token == ' ' ||
1538 *token == '\t' || *token == '\n'))
1544 /* If the arg was completely unparsable, fall back to the local subnet.
1545 This happens if the default is "/etc/hosts" but READ_FILES is off.
1546 Or if we're on a /31 network, in which case we try twice then fail.
1548 if (!hostlist && fallback)
1551 fprintf (stderr, "%s: no hosts parsed! Trying %s\n",
1552 progname, fallback);
1553 ping_arg = fallback;
1563 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
1564 const char *subnet, int timeout,
1565 Bool resolve_p, Bool times_p, Bool debug_p)
1567 /* Important! Do not return from this function without disavowing privileges
1568 with setuid(getuid()).
1570 sonar_sensor_data *ssd = (sonar_sensor_data *) calloc (1, sizeof(*ssd));
1571 ping_data *pd = (ping_data *) calloc (1, sizeof(*pd));
1575 Bool socket_initted_p = False;
1576 Bool socket_raw_p = False;
1580 pd->resolve_p = resolve_p;
1581 pd->times_p = times_p;
1582 pd->debug_p = debug_p;
1585 ssd->scan_cb = ping_scan;
1586 ssd->free_data_cb = ping_free_data;
1587 ssd->free_bogie_cb = ping_free_bogie_data;
1589 /* Get short version number. */
1590 s = strchr (screensaver_id, ' ');
1591 pd->version = strdup (s+1);
1592 s = strchr (pd->version, ' ');
1596 /* Create the ICMP socket. Do this before dropping privs.
1598 Raw sockets can only be opened by root (or setuid root), so we
1599 only try to do this when the effective uid is 0.
1601 We used to just always try, and notice the failure. But apparently
1602 that causes "SELinux" to log spurious warnings when running with the
1603 "strict" policy. So to avoid that, we just don't try unless we
1606 On MacOS X, we can avoid the whole problem by using a
1607 non-privileged datagram instead of a raw socket.
1609 if (global_icmpsock)
1611 pd->icmpsock = global_icmpsock;
1612 socket_initted_p = True;
1614 fprintf (stderr, "%s: re-using icmp socket\n", progname);
1617 else if ((pd->icmpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) >= 0)
1619 socket_initted_p = True;
1621 else if (geteuid() == 0 &&
1622 (pd->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) >= 0)
1624 socket_initted_p = True;
1625 socket_raw_p = True;
1628 if (socket_initted_p)
1630 global_icmpsock = pd->icmpsock;
1631 socket_initted_p = True;
1633 fprintf (stderr, "%s: opened %s icmp socket\n", progname,
1634 (socket_raw_p ? "raw" : "dgram"));
1637 fprintf (stderr, "%s: unable to open icmp socket\n", progname);
1642 pd->pid = getpid() & 0xFFFF;
1644 pd->timeout = timeout;
1646 /* Generate a list of targets */
1648 pd->targets = parse_mode (ssd, error_ret, desc_ret, subnet,
1650 pd->targets = delete_duplicate_hosts (ssd, pd->targets);
1654 fprintf (stderr, "%s: Target list:\n", progname);
1655 for (b = pd->targets; b; b = b->next)
1657 fprintf (stderr, "%s: ", progname);
1658 print_host (stderr, b);
1662 /* Make sure there is something to ping */
1664 pd->target_count = 0;
1665 for (b = pd->targets; b; b = b->next)
1668 if (pd->target_count == 0)
1671 *error_ret = strdup ("No hosts to ping!\n"
1672 "Simulating instead.");
1673 if (pd) ping_free_data (ssd, pd);
1674 if (ssd) free (ssd);
1678 /* Distribute them evenly around the display field, clockwise.
1679 Even on a /24, allocated IPs tend to cluster together, so
1680 don't put any two hosts closer together than N degrees to
1681 avoid unnecessary overlap when we have plenty of space due
1682 to addresses that probably won't respond. And don't spread
1683 them out too far apart, because that looks too symmetrical
1684 when there are a small number of hosts.
1687 double th = frand(M_PI);
1688 double sep = 360.0 / pd->target_count;
1689 if (sep < 23) sep = 23;
1690 if (sep > 43) sep = 43;
1692 for (b = pd->targets; b; b = b->next) {
1701 #endif /* HAVE_PING -- whole file */