1 /* sonar, Copyright (c) 1998-2018 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)
732 else if (ifa->ifa_addr->sa_family != AF_INET)
735 fprintf (stderr, "%s: if: %4s: %s\n", progname,
739 ifa->ifa_addr->sa_family == AF_UNIX ? "local" :
742 ifa->ifa_addr->sa_family == AF_LINK ? "link" :
745 ifa->ifa_addr->sa_family == AF_INET6 ? "ipv6" :
750 in2 = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
751 mask = ntohl (((struct sockaddr_in *) ifa->ifa_netmask)
754 fprintf (stderr, "%s: if: %4s: inet = %s /%d 0x%08lx\n",
760 if (in2.s_addr == 0x0100007f || /* 127.0.0.1 in network order */
764 /* At least on the AT&T 3G network, pinging either of the two
765 hosts on a /31 network doesn't work, so don't try.
767 if (mask_width (mask) == 31)
770 "Can't ping subnet:\n"
775 inet_ntoa (in2), mask_width (mask), ifa->ifa_name);
776 if (*error_ret) free (*error_ret);
777 *error_ret = strdup (buf);
782 subnet_width = mask_width (mask);
787 if (*error_ret) free (*error_ret);
789 n_base = in.s_addr; /* already in network order, I think? */
791 else if (!*error_ret)
792 *error_ret = strdup ("Unable to determine\nlocal IP address\n");
800 # else /* !HAVE_GETIFADDRS */
802 /* If we can't walk the list of network interfaces to figure out
803 our local IP address, try to do it by finding the local host
804 name, then resolving that.
806 char hostname[BUFSIZ];
807 struct hostent *hent = 0;
809 if (gethostname(hostname, BUFSIZ))
811 *error_ret = strdup ("Unable to determine\n"
816 /* Get our IP address and convert it to a string */
818 hent = gethostbyname(hostname);
821 strcat (hostname, ".local"); /* Necessary on iphone */
822 hent = gethostbyname(hostname);
828 "Unable to resolve\n"
829 "local host \"%.100s\"",
831 *error_ret = strdup(buf);
835 strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
836 n_base = pack_addr (hent->h_addr_list[0][0],
837 hent->h_addr_list[0][1],
838 hent->h_addr_list[0][2],
839 hent->h_addr_list[0][3]);
841 if (n_base == 0x0100007f) /* 127.0.0.1 in network order */
843 unsigned int a, b, c, d;
844 unpack_addr (n_base, &a, &b, &c, &d);
846 "Unable to determine\n"
847 "local subnet address:\n"
852 hostname, a, b, c, d);
853 *error_ret = strdup(buf);
857 # endif /* !HAVE_GETIFADDRS */
861 /* Construct targets for all addresses in this subnet */
863 h_mask = width_mask (subnet_width);
864 h_base = ntohl (n_base);
866 if (desc_ret && !*desc_ret) {
868 unsigned int a, b, c, d;
869 unsigned long bb = n_base & htonl(h_mask);
870 unpack_addr (bb, &a, &b, &c, &d);
871 if (subnet_width > 24)
872 sprintf (buf2, "%u.%u.%u.%u/%d", a, b, c, d, subnet_width);
874 sprintf (buf2, "%u.%u.%u/%d", a, b, c, subnet_width);
875 *desc_ret = strdup (buf2);
878 for (i = 255; i >= 0; i--) {
879 unsigned int a, b, c, d;
880 int ip = (h_base & 0xFFFFFF00L) | i; /* host order */
882 if ((ip & h_mask) != (h_base & h_mask)) /* skip out-of-subnet host */
884 else if (subnet_width == 31) /* 1-bit bridge: 2 hosts */
886 else if ((ip & ~h_mask) == 0) /* skip network address */
888 else if ((ip & ~h_mask) == ~h_mask) /* skip broadcast address */
891 unpack_addr (htonl (ip), &a, &b, &c, &d);
892 sprintf (address, "%u.%u.%u.%u", a, b, c, d);
896 unsigned int aa, ab, ac, ad;
897 unsigned int ma, mb, mc, md;
898 unpack_addr (htonl (h_base & h_mask), &aa, &ab, &ac, &ad);
899 unpack_addr (htonl (h_mask), &ma, &mb, &mc, &md);
901 "%s: subnet: %s (%u.%u.%u.%u & %u.%u.%u.%u / %d)\n",
908 p = address + strlen(address) + 1;
911 new = bogie_for_host (ssd, address, NULL);
923 /* Send a ping packet.
926 send_ping (ping_data *pd, const sonar_bogie *b)
928 ping_bogie *pb = (ping_bogie *) b->closure;
931 const char *token = "org.jwz.xscreensaver.sonar";
934 unsigned long pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) +
935 sizeof(socklen_t) + pb->addrlen +
937 strlen(pd->version) + 1);
939 /* Create the ICMP packet */
941 if (! (packet = (u_char *) calloc(1, pcktsiz)))
942 return; /* Out of memory */
944 icmph = (struct ICMP *) packet;
945 ICMP_TYPE(icmph) = ICMP_ECHO;
946 ICMP_CODE(icmph) = 0;
947 ICMP_CHECKSUM(icmph) = 0;
948 ICMP_ID(icmph) = pd->pid;
949 ICMP_SEQ(icmph) = pd->seq++;
950 # ifdef GETTIMEOFDAY_TWO_ARGS
951 gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)],
952 (struct timezone *) 0);
954 gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]);
957 /* We store the sockaddr of the host we're pinging in the packet, and parse
958 that out of the return packet later (see get_ping() for why).
959 After that, we also include the name and version of this program,
960 just to give a clue to anyone sniffing and wondering what's up.
962 host_id = (char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)];
963 *(socklen_t *)host_id = pb->addrlen;
964 host_id += sizeof(socklen_t);
965 memcpy(host_id, &pb->address, pb->addrlen);
966 host_id += pb->addrlen;
967 sprintf (host_id, "%.20s %.20s", token, pd->version);
969 ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
973 if (sendto(pd->icmpsock, packet, pcktsiz, 0,
974 (struct sockaddr *)&pb->address, sizeof(pb->address))
979 sprintf(buf, "%s: pinging %.100s", progname, b->name);
993 /* Compute the checksum on a ping packet.
996 checksum (u_short *packet, int size)
998 register int nleft = size;
999 register u_short *w = packet;
1000 register int sum = 0;
1003 /* Using a 32 bit accumulator (sum), we add sequential 16 bit words
1004 to it, and at the end, fold back all the carry bits from the
1005 top 16 bits into the lower 16 bits.
1013 /* mop up an odd byte, if necessary */
1017 *(u_char *)(&answer) = *(u_char *)w ;
1018 *(1 + (u_char *)(&answer)) = 0;
1022 /* add back carry outs from top 16 bits to low 16 bits */
1024 sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
1025 sum += (sum >> 16); /* add carry */
1026 answer = ~sum; /* truncate to 16 bits */
1032 /* Copies the sonar_bogie and the underlying ping_bogie.
1034 static sonar_bogie *
1035 copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
1037 sonar_bogie *b2 = sonar_copy_bogie (ssd, b);
1040 ping_bogie *pb = (ping_bogie *) b->closure;
1041 ping_bogie *pb2 = (ping_bogie *) calloc (1, sizeof(*pb));
1042 pb2->address = pb->address;
1049 /* Look for all outstanding ping replies.
1051 static sonar_bogie *
1052 get_ping (sonar_sensor_data *ssd)
1054 ping_data *pd = (ping_data *) ssd->closure;
1055 struct sockaddr from;
1058 u_char packet[1024];
1060 struct timeval *then;
1064 sonar_bogie *bl = 0;
1065 sonar_bogie *new = 0;
1066 struct sigaction sa;
1067 struct itimerval it;
1071 /* Set up a signal to interrupt our wait for a packet */
1073 sigemptyset(&sa.sa_mask);
1075 sa.sa_handler = sigcatcher;
1076 if (sigaction(SIGALRM, &sa, 0) == -1)
1079 sprintf(msg, "%s: unable to trap SIGALRM", progname);
1084 /* Set up a timer to interupt us if we don't get a packet */
1086 it.it_interval.tv_sec = 0;
1087 it.it_interval.tv_usec = 0;
1088 it.it_value.tv_sec = 0;
1089 it.it_value.tv_usec = pd->timeout;
1091 setitimer(ITIMER_REAL, &it, 0);
1093 /* Wait for a result packet */
1095 fromlen = sizeof(from);
1096 while (! timer_expired)
1098 tv.tv_usec = pd->timeout;
1101 /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
1104 memset (&rfds, 0, sizeof(rfds));
1106 FD_SET(pd->icmpsock, &rfds);
1107 /* only wait a little while, in case we raced with the timer expiration.
1108 From Valentijn Sessink <valentyn@openoffice.nl> */
1109 if (select(pd->icmpsock + 1, &rfds, 0, 0, &tv) >0)
1111 result = (int)recvfrom (pd->icmpsock, packet, sizeof(packet),
1112 0, &from, &fromlen);
1114 /* Check the packet */
1116 # ifdef GETTIMEOFDAY_TWO_ARGS
1117 gettimeofday(&now, (struct timezone *) 0);
1121 ip = (struct ip *) packet;
1122 iphdrlen = IP_HDRLEN(ip) << 2;
1123 icmph = (struct ICMP *) &packet[iphdrlen];
1124 then = (struct timeval *) &packet[iphdrlen + sizeof(struct ICMP)];
1127 /* Ignore anything but ICMP Replies */
1128 if (ICMP_TYPE(icmph) != ICMP_ECHOREPLY)
1131 /* Ignore packets not set from us */
1132 if (ICMP_ID(icmph) != pd->pid)
1135 /* Find the bogie in 'targets' that corresponds to this packet
1136 and copy it, so that this bogie stays in the same spot (th)
1137 on the screen, and so that we don't have to resolve it again.
1139 We could find the bogie by comparing ip->ip_src.s_addr to
1140 pb->address, but it is possible that, in certain weird router
1141 or NAT situations, that the reply will come back from a
1142 different address than the one we sent it to. So instead,
1143 we parse the sockaddr out of the reply packet payload.
1146 const socklen_t *host_id = (socklen_t *) &packet[
1147 iphdrlen + sizeof(struct ICMP) + sizeof(struct timeval)];
1151 /* Ensure that a maliciously-crafted return packet can't
1152 make us overflow in memcmp. */
1153 if (result > 0 && (const u_char *)(host_id + 1) <= packet + result)
1155 const u_char *host_end = (const u_char *)(host_id + 1) +
1158 if ((const u_char *)(host_id + 1) <= host_end &&
1159 host_end <= packet + result)
1161 for (b = pd->targets; b; b = b->next)
1163 ping_bogie *pb = (ping_bogie *)b->closure;
1164 if (*host_id == pb->addrlen &&
1165 !memcmp(&pb->address, host_id + 1, pb->addrlen) )
1167 /* Check to see if the name lookup is done. */
1168 if (pb->lookup_name &&
1169 async_name_from_addr_is_done (pb->lookup_name))
1173 async_name_from_addr_finish (pb->lookup_name,
1176 if (pd->debug_p > 1)
1177 fprintf (stderr, "%s: %s => %s\n",
1179 host ? host : "<unknown>");
1187 pb->lookup_name = NULL;
1190 new = copy_ping_bogie (ssd, b);
1198 if (! new) /* not in targets? */
1200 unsigned int a, b, c, d;
1201 unpack_addr (ip->ip_src.s_addr, &a, &b, &c, &d);
1203 "%s: UNEXPECTED PING REPLY! "
1204 "%4d bytes, icmp_seq=%-4d from %d.%d.%d.%d\n",
1205 progname, result, ICMP_SEQ(icmph), a, b, c, d);
1213 double msec = delta(then, &now) / 1000.0;
1217 if (new->desc) free (new->desc);
1218 new->desc = (char *) malloc (30);
1219 if (msec > 99) sprintf (new->desc, "%.0f ms", msec);
1220 else if (msec > 9) sprintf (new->desc, "%.1f ms", msec);
1221 else if (msec > 1) sprintf (new->desc, "%.2f ms", msec);
1222 else sprintf (new->desc, "%.3f ms", msec);
1225 if (pd->debug_p && pd->times_p) /* ping-like stdout log */
1227 char *s = strdup(new->name);
1231 s2 = s + strlen(s) - 28;
1232 strncpy (s2, "...", 3);
1235 "%3d bytes from %28s: icmp_seq=%-4d time=%s\n",
1236 result, s2, ICMP_SEQ(icmph), new->desc);
1241 /* The radius must be between 0.0 and 1.0.
1242 We want to display ping times on a logarithmic scale,
1243 with the three rings being 2.5, 70 and 2,000 milliseconds.
1245 if (msec <= 0) msec = 0.001;
1246 new->r = log (msec * 10) / log (20000);
1248 /* Don't put anyone *too* close to the center of the screen. */
1249 if (new->r < 0) new->r = 0;
1250 if (new->r < 0.1) new->r += 0.1;
1259 /* difference between the two times in microseconds.
1262 delta (struct timeval *then, struct timeval *now)
1264 return (((now->tv_sec - then->tv_sec) * 1000000) +
1265 (now->tv_usec - then->tv_usec));
1270 ping_free_data (sonar_sensor_data *ssd, void *closure)
1272 ping_data *pd = (ping_data *) closure;
1273 sonar_bogie *b = pd->targets;
1276 sonar_bogie *b2 = b->next;
1277 sonar_free_bogie (ssd, b);
1284 ping_free_bogie_data (sonar_sensor_data *sd, void *closure)
1286 ping_bogie *pb = (ping_bogie *) closure;
1288 if (pb->lookup_name)
1289 async_name_from_addr_cancel (pb->lookup_name);
1290 if (pb->lookup_addr)
1291 async_addr_from_name_cancel (pb->lookup_addr);
1292 free (pb->fallback);
1298 /* Returns the current time in seconds as a double.
1304 # ifdef GETTIMEOFDAY_TWO_ARGS
1305 struct timezone tzp;
1306 gettimeofday(&now, &tzp);
1311 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
1316 free_bogie_after_lookup(sonar_sensor_data *ssd, sonar_bogie **sbp,
1319 ping_bogie *pb = (ping_bogie *)(*sb)->closure;
1322 pb->lookup_addr = NULL; /* Prevent double-free in sonar_free_bogie. */
1323 sonar_free_bogie (ssd, *sb);
1328 /* Pings the next bogie, if it's time.
1329 Returns all outstanding ping replies.
1331 static sonar_bogie *
1332 ping_scan (sonar_sensor_data *ssd)
1334 ping_data *pd = (ping_data *) ssd->closure;
1335 double now = double_time();
1336 double ping_cycle = 10; /* re-ping a given host every 10 seconds */
1337 double ping_interval = ping_cycle / pd->target_count;
1339 if (now > pd->last_ping_time + ping_interval) /* time to ping someone */
1341 struct sonar_bogie **sbp;
1343 if (pd->last_pinged)
1345 sbp = &pd->last_pinged->next;
1353 /* Aaaaand we're out of bogies. */
1354 pd->last_pinged = NULL;
1357 sonar_bogie *sb = *sbp;
1358 ping_bogie *pb = (ping_bogie *)sb->closure;
1359 if (pb->lookup_addr &&
1360 async_addr_from_name_is_done (pb->lookup_addr))
1362 if (async_addr_from_name_finish (pb->lookup_addr, &pb->address,
1363 &pb->addrlen, NULL))
1365 char *fallback = pb->fallback;
1366 pb->fallback = NULL;
1369 fprintf (stderr, "%s: could not resolve host: %s\n",
1370 progname, sb->name);
1372 free_bogie_after_lookup (ssd, sbp, &sb);
1374 /* Insert the fallback bogie right where the old one was. */
1377 sonar_bogie *new_bogie = bogie_for_host (ssd, fallback,
1380 new_bogie->next = *sbp;
1382 if (! ((ping_bogie *)new_bogie->closure)->lookup_addr &&
1383 ! find_duplicate_host(pd, &pd->targets, new_bogie))
1386 sonar_free_bogie (ssd, new_bogie);
1394 if (pd->debug_p > 1)
1396 fprintf (stderr, "%s: %s => ", progname, sb->name);
1397 print_address (stderr, 0, &pb->address, pb->addrlen);
1401 if (! is_address_ok (pd->debug_p, sb))
1402 free_bogie_after_lookup (ssd, sbp, &sb);
1403 else if (find_duplicate_host (pd, &pd->targets, sb))
1404 /* Tricky: find_duplicate_host skips the current bogie when
1405 scanning the targets list because pb->lookup_addr hasn't
1408 Not that it matters much, but behavior here is to
1409 keep the existing address.
1411 free_bogie_after_lookup (ssd, sbp, &sb);
1415 pb->lookup_addr = NULL;
1418 if (sb && !pb->lookup_addr)
1420 if (!pb->addrlen) abort();
1422 pd->last_pinged = sb;
1426 pd->last_ping_time = now;
1429 return get_ping (ssd);
1433 /* Returns a list of hosts to ping based on the "-ping" argument.
1435 static sonar_bogie *
1436 parse_mode (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
1437 const char *ping_arg, Bool ping_works_p)
1439 ping_data *pd = (ping_data *) ssd->closure;
1440 char *source, *token, *end, dummy;
1441 sonar_bogie *hostlist = 0;
1442 const char *fallback = "subnet";
1446 if (fallback && (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default")))
1447 source = strdup(fallback);
1449 source = strdup(ping_arg);
1454 end = source + strlen(source);
1458 sonar_bogie *new = 0;
1462 unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
1467 *next != ',' && *next != ' ' && *next != '\t' && *next != '\n';
1474 fprintf (stderr, "%s: parsing %s\n", progname, token);
1478 *error_ret = strdup ("Sonar must be setuid to ping!\n"
1479 "Running simulation instead.");
1483 if ((4 == sscanf (token, "%u.%u.%u/%u %c", &n0,&n1,&n2, &m,&d)) ||
1484 (5 == sscanf (token, "%u.%u.%u.%u/%u %c", &n0,&n1,&n2,&n3,&m,&d)))
1486 /* subnet: A.B.C.D/M
1489 unsigned long ip = pack_addr (n0, n1, n2, n3);
1490 new = subnet_hosts (ssd, error_ret, desc_ret, ip, m);
1492 else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d))
1496 new = bogie_for_host (ssd, token, NULL);
1498 else if (!strcmp (token, "subnet"))
1500 new = subnet_hosts (ssd, error_ret, desc_ret, 0, 24);
1502 else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
1504 new = subnet_hosts (ssd, error_ret, desc_ret, 0, m);
1506 else if (*token == '.' || *token == '/' ||
1507 *token == '$' || *token == '~')
1510 new = read_hosts_file (ssd, token);
1512 if (pd->debug_p) fprintf (stderr, "%s: skipping file\n", progname);
1516 else if (!stat (token, &st))
1518 new = read_hosts_file (ssd, token);
1520 # endif /* READ_FILES */
1523 /* not an existant file - must be a host name
1525 new = bogie_for_host (ssd, token, NULL);
1530 sonar_bogie *nn = new;
1533 nn->next = hostlist;
1538 while (token < end &&
1539 (*token == ',' || *token == ' ' ||
1540 *token == '\t' || *token == '\n'))
1546 /* If the arg was completely unparsable, fall back to the local subnet.
1547 This happens if the default is "/etc/hosts" but READ_FILES is off.
1548 Or if we're on a /31 network, in which case we try twice then fail.
1550 if (!hostlist && fallback)
1553 fprintf (stderr, "%s: no hosts parsed! Trying %s\n",
1554 progname, fallback);
1555 ping_arg = fallback;
1565 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
1566 const char *subnet, int timeout,
1567 Bool resolve_p, Bool times_p, Bool debug_p)
1569 /* Important! Do not return from this function without disavowing privileges
1570 with setuid(getuid()).
1572 sonar_sensor_data *ssd = (sonar_sensor_data *) calloc (1, sizeof(*ssd));
1573 ping_data *pd = (ping_data *) calloc (1, sizeof(*pd));
1577 Bool socket_initted_p = False;
1578 Bool socket_raw_p = False;
1582 pd->resolve_p = resolve_p;
1583 pd->times_p = times_p;
1584 pd->debug_p = debug_p;
1587 ssd->scan_cb = ping_scan;
1588 ssd->free_data_cb = ping_free_data;
1589 ssd->free_bogie_cb = ping_free_bogie_data;
1591 /* Get short version number. */
1592 s = strchr (screensaver_id, ' ');
1593 pd->version = strdup (s+1);
1594 s = strchr (pd->version, ' ');
1598 /* Create the ICMP socket. Do this before dropping privs.
1600 Raw sockets can only be opened by root (or setuid root), so we
1601 only try to do this when the effective uid is 0.
1603 We used to just always try, and notice the failure. But apparently
1604 that causes "SELinux" to log spurious warnings when running with the
1605 "strict" policy. So to avoid that, we just don't try unless we
1608 On MacOS X, we can avoid the whole problem by using a
1609 non-privileged datagram instead of a raw socket.
1611 if (global_icmpsock)
1613 pd->icmpsock = global_icmpsock;
1614 socket_initted_p = True;
1616 fprintf (stderr, "%s: re-using icmp socket\n", progname);
1619 else if ((pd->icmpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) >= 0)
1621 socket_initted_p = True;
1623 else if (geteuid() == 0 &&
1624 (pd->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) >= 0)
1626 socket_initted_p = True;
1627 socket_raw_p = True;
1630 if (socket_initted_p)
1632 global_icmpsock = pd->icmpsock;
1633 socket_initted_p = True;
1635 fprintf (stderr, "%s: opened %s icmp socket\n", progname,
1636 (socket_raw_p ? "raw" : "dgram"));
1639 fprintf (stderr, "%s: unable to open icmp socket\n", progname);
1644 pd->pid = getpid() & 0xFFFF;
1646 pd->timeout = timeout;
1648 /* Generate a list of targets */
1650 pd->targets = parse_mode (ssd, error_ret, desc_ret, subnet,
1652 pd->targets = delete_duplicate_hosts (ssd, pd->targets);
1656 fprintf (stderr, "%s: Target list:\n", progname);
1657 for (b = pd->targets; b; b = b->next)
1659 fprintf (stderr, "%s: ", progname);
1660 print_host (stderr, b);
1664 /* Make sure there is something to ping */
1666 pd->target_count = 0;
1667 for (b = pd->targets; b; b = b->next)
1670 if (pd->target_count == 0)
1673 *error_ret = strdup ("No hosts to ping!\n"
1674 "Simulating instead.");
1675 if (pd) ping_free_data (ssd, pd);
1676 if (ssd) free (ssd);
1680 /* Distribute them evenly around the display field, clockwise.
1681 Even on a /24, allocated IPs tend to cluster together, so
1682 don't put any two hosts closer together than N degrees to
1683 avoid unnecessary overlap when we have plenty of space due
1684 to addresses that probably won't respond. And don't spread
1685 them out too far apart, because that looks too symmetrical
1686 when there are a small number of hosts.
1689 double th = frand(M_PI);
1690 double sep = 360.0 / pd->target_count;
1691 if (sep < 23) sep = 23;
1692 if (sep > 43) sep = 43;
1694 for (b = pd->targets; b; b = b->next) {
1703 #endif /* HAVE_PING -- whole file */