1 /* sonar, Copyright (c) 1998-2016 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 strlen (sb->name) >= 80)
249 /* .ssh/known_hosts sometimes contains weirdness like "[host]:port".
251 if (strchr (sb->name, '['))
254 fprintf (stderr, "%s: ignoring bogus address \"%s\"\n",
259 /* If the name contains a colon, it's probably IPv6. */
260 if (strchr (sb->name, ':'))
263 fprintf (stderr, "%s: ignoring ipv6 address \"%s\"\n",
268 pb->lookup_addr = async_addr_from_name_start(pd->dpy, sb->name);
269 if (!pb->lookup_addr)
272 /* Either address space exhaustion or RAM exhaustion. */
273 fprintf (stderr, "%s: unable to start host resolution.\n",
283 print_address (FILE *out, int width, const void *sockaddr, socklen_t addrlen)
285 #ifdef HAVE_GETADDRINFO
286 char buf[NI_MAXHOST];
291 const struct sockaddr *addr = (const struct sockaddr *)sockaddr;
292 const char *ips = buf;
294 if (!addr->sa_family)
295 ips = "<no address>";
298 #ifdef HAVE_GETADDRINFO
299 int gai_error = getnameinfo (sockaddr, addrlen, buf, sizeof(buf),
300 NULL, 0, NI_NUMERICHOST);
301 if (gai_error == EAI_SYSTEM)
302 ips = strerror(errno);
304 ips = gai_strerror(gai_error);
306 switch (addr->sa_family)
310 u_long ip = ((struct sockaddr_in *)sockaddr)->sin_addr.s_addr;
311 unsigned int a, b, c, d;
312 unpack_addr (ip, &a, &b, &c, &d); /* ip is in network order */
313 sprintf (buf, "%u.%u.%u.%u", a, b, c, d);
323 fprintf (out, "%-*s", width, ips);
328 print_host (FILE *out, const sonar_bogie *sb)
330 const ping_bogie *pb = (const ping_bogie *) sb->closure;
331 const char *name = sb->name;
332 if (!name || !*name) name = "<unknown>";
333 print_address (out, 16, &pb->address, pb->addrlen);
334 fprintf (out, " %s\n", name);
339 is_address_ok(Bool debug_p, const sonar_bogie *b)
341 const ping_bogie *pb = (const ping_bogie *) b->closure;
342 const struct sockaddr *addr = (const struct sockaddr *)&pb->address;
344 switch (addr->sa_family)
348 struct sockaddr_in *iaddr = (struct sockaddr_in *) addr;
350 /* Don't ever use loopback (127.0.0.x) hosts */
351 unsigned long ip = iaddr->sin_addr.s_addr;
352 if ((ntohl (ip) & 0xFFFFFF00L) == 0x7f000000L) /* 127.0.0.x */
355 fprintf (stderr, "%s: ignoring loopback host %s\n",
360 /* Don't ever use broadcast (255.x.x.x) hosts */
361 if ((ntohl (ip) & 0xFF000000L) == 0xFF000000L) /* 255.x.x.x */
364 fprintf (stderr, "%s: ignoring broadcast host %s\n",
377 /* Create a sonar_bogie from a host name or ip address string.
378 Returns NULL if the name could not be resolved.
381 bogie_for_host (sonar_sensor_data *ssd, const char *name, const char *fallback)
383 ping_data *pd = (ping_data *) ssd->closure;
384 sonar_bogie *b = (sonar_bogie *) calloc (1, sizeof(*b));
385 ping_bogie *pb = (ping_bogie *) calloc (1, sizeof(*pb));
386 Bool resolve_p = pd->resolve_p;
388 b->name = strdup (name);
391 if (! resolve_bogie_hostname (pd, b, resolve_p))
394 if (! pb->lookup_addr && ! is_address_ok (pd->debug_p, b))
399 fprintf (stderr, "%s: added ", progname);
400 print_host (stderr, b);
404 pb->fallback = strdup (fallback);
408 if (b) sonar_free_bogie (ssd, b);
411 return bogie_for_host (ssd, fallback, NULL);
419 /* Return a list of bogies read from a file.
420 The file can be like /etc/hosts or .ssh/known_hosts or probably
421 just about anything that has host names in it.
424 read_hosts_file (sonar_sensor_data *ssd, const char *filename)
426 ping_data *pd = (ping_data *) ssd->closure;
430 sonar_bogie *list = 0;
434 /* Kludge: on OSX, variables have not been expanded in the command
435 line arguments, so as a special case, allow the string to begin
436 with literal "$HOME/" or "~/".
438 This is so that the "Known Hosts" menu item in sonar.xml works.
440 if (!strncmp(filename, "~/", 2) || !strncmp(filename, "$HOME/", 6))
442 char *s = strchr (filename, '/');
443 strcpy (buf, getenv("HOME"));
448 fp = fopen(filename, "r");
452 sprintf(buf, "%s: %s", progname, filename);
454 if (pd->debug_p) /* on OSX don't syslog this */
461 fprintf (stderr, "%s: reading \"%s\"\n", progname, filename);
463 while ((p = fgets(buf, LINE_MAX, fp)))
465 while ((*p == ' ') || (*p == '\t')) /* skip whitespace */
467 if (*p == '#') /* skip comments */
470 /* Get the name and address */
472 if ((addr = strtok(buf, " ,;\t\n")))
473 name = strtok(0, " ,;\t\n");
477 /* Check to see if the addr looks like an addr. If not, assume
478 the addr is a name and there is no addr. This way, we can
479 handle files whose lines have "xx.xx.xx.xx hostname" as their
480 first two tokens, and also files that have a hostname as their
481 first token (like .ssh/known_hosts and .rhosts.)
485 if (4 != sscanf(addr, "%d.%d.%d.%d%c", &i, &i, &i, &i, &c))
492 /* If the name is all digits, it's not a name. */
496 for (s = name; *s; s++)
497 if (*s < '0' || *s > '9')
502 fprintf (stderr, "%s: skipping bogus name \"%s\" (%s)\n",
503 progname, name, addr);
508 /* Create a new target using first the name then the address */
516 new = bogie_for_host (ssd, name, addr);
528 #endif /* READ_FILES */
531 static sonar_bogie **
532 found_duplicate_host (const ping_data *pd, sonar_bogie **list,
537 fprintf (stderr, "%s: deleted duplicate: ", progname);
538 print_host (stderr, bogie);
545 static sonar_bogie **
546 find_duplicate_host (const ping_data *pd, sonar_bogie **list,
549 const ping_bogie *pb = (const ping_bogie *) bogie->closure;
550 const struct sockaddr *addr1 = (const struct sockaddr *) &(pb->address);
554 const ping_bogie *pb2 = (const ping_bogie *) (*list)->closure;
556 if (!pb2->lookup_addr)
558 const struct sockaddr *addr2 =
559 (const struct sockaddr *) &(pb2->address);
561 if (addr1->sa_family == addr2->sa_family)
563 switch (addr1->sa_family)
568 ((const struct sockaddr_in *)addr1)->sin_addr.s_addr;
569 const struct sockaddr_in *i2 =
570 (const struct sockaddr_in *)addr2;
571 unsigned long ip2 = i2->sin_addr.s_addr;
574 return found_duplicate_host (pd, list, bogie);
581 &((const struct sockaddr_in6 *)addr1)->sin6_addr,
582 &((const struct sockaddr_in6 *)addr2)->sin6_addr,
584 return found_duplicate_host (pd, list, bogie);
590 /* Fallback behavior: Just memcmp the two addresses.
592 For this to work, unused space in the sockaddr must be
593 set to zero. Which may actually be the case:
594 - async_addr_from_name_finish won't put garbage into
595 sockaddr_in.sin_zero or elsewhere unless getaddrinfo
597 - ping_bogie is allocated with calloc(). */
599 if (pb->addrlen == pb2->addrlen &&
600 ! memcmp(addr1, addr2, pb->addrlen))
601 return found_duplicate_host (pd, list, bogie);
608 list = &(*list)->next;
616 delete_duplicate_hosts (sonar_sensor_data *ssd, sonar_bogie *list)
618 ping_data *pd = (ping_data *) ssd->closure;
619 sonar_bogie *head = list;
620 sonar_bogie *sb = head;
624 ping_bogie *pb = (ping_bogie *) sb->closure;
626 if (!pb->lookup_addr)
628 sonar_bogie **sb2 = find_duplicate_host (pd, &sb->next, sb);
644 width_mask (int width)
648 for (i = 0; i < width; i++)
654 #ifdef HAVE_GETIFADDRS
656 mask_width (unsigned int mask)
659 for (i = 0; i < 32; i++)
667 /* Generate a list of bogies consisting of all of the entries on
668 the same subnet. 'base' ip is in network order; 0 means localhost.
671 subnet_hosts (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
672 unsigned long n_base, int subnet_width)
674 ping_data *pd = (ping_data *) ssd->closure;
675 unsigned long h_mask; /* host order */
676 unsigned long h_base; /* host order */
677 char address[BUFSIZ];
681 sonar_bogie *list = 0;
684 if (subnet_width < 24)
687 "Pinging %lu hosts is a bad\n"
688 "idea. Please use a subnet\n"
689 "mask of 24 bits or more.",
690 (unsigned long) (1L << (32 - subnet_width)) - 1);
691 *error_ret = strdup(buf);
694 else if (subnet_width > 30)
698 "doesn't make sense.\n"
699 "Try \"subnet/24\"\n"
700 "or \"subnet/29\".\n",
702 *error_ret = strdup(buf);
708 fprintf (stderr, "%s: adding %d-bit subnet\n", progname, subnet_width);
713 # ifdef HAVE_GETIFADDRS
715 /* To determine the local subnet, we need to know the local IP address.
716 Do this by looking at the IPs of every network interface.
718 struct in_addr in = { 0, };
719 struct ifaddrs *all = 0, *ifa;
722 fprintf (stderr, "%s: listing network interfaces\n", progname);
725 for (ifa = all; ifa; ifa = ifa->ifa_next)
729 if (ifa->ifa_addr->sa_family != AF_INET)
732 fprintf (stderr, "%s: if: %4s: %s\n", progname,
736 ifa->ifa_addr->sa_family == AF_UNIX ? "local" :
739 ifa->ifa_addr->sa_family == AF_LINK ? "link" :
742 ifa->ifa_addr->sa_family == AF_INET6 ? "ipv6" :
747 in2 = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
748 mask = ntohl (((struct sockaddr_in *) ifa->ifa_netmask)
751 fprintf (stderr, "%s: if: %4s: inet = %s /%d 0x%08lx\n",
757 if (in2.s_addr == 0x0100007f || /* 127.0.0.1 in network order */
761 /* At least on the AT&T 3G network, pinging either of the two
762 hosts on a /31 network doesn't work, so don't try.
764 if (mask_width (mask) == 31)
767 "Can't ping subnet:\n"
772 inet_ntoa (in2), mask_width (mask), ifa->ifa_name);
773 if (*error_ret) free (*error_ret);
774 *error_ret = strdup (buf);
779 subnet_width = mask_width (mask);
784 if (*error_ret) free (*error_ret);
786 n_base = in.s_addr; /* already in network order, I think? */
788 else if (!*error_ret)
789 *error_ret = strdup ("Unable to determine\nlocal IP address\n");
797 # else /* !HAVE_GETIFADDRS */
799 /* If we can't walk the list of network interfaces to figure out
800 our local IP address, try to do it by finding the local host
801 name, then resolving that.
803 char hostname[BUFSIZ];
804 struct hostent *hent = 0;
806 if (gethostname(hostname, BUFSIZ))
808 *error_ret = strdup ("Unable to determine\n"
813 /* Get our IP address and convert it to a string */
815 hent = gethostbyname(hostname);
818 strcat (hostname, ".local"); /* Necessary on iphone */
819 hent = gethostbyname(hostname);
825 "Unable to resolve\n"
826 "local host \"%.100s\"",
828 *error_ret = strdup(buf);
832 strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
833 n_base = pack_addr (hent->h_addr_list[0][0],
834 hent->h_addr_list[0][1],
835 hent->h_addr_list[0][2],
836 hent->h_addr_list[0][3]);
838 if (n_base == 0x0100007f) /* 127.0.0.1 in network order */
840 unsigned int a, b, c, d;
841 unpack_addr (n_base, &a, &b, &c, &d);
843 "Unable to determine\n"
844 "local subnet address:\n"
849 hostname, a, b, c, d);
850 *error_ret = strdup(buf);
854 # endif /* !HAVE_GETIFADDRS */
858 /* Construct targets for all addresses in this subnet */
860 h_mask = width_mask (subnet_width);
861 h_base = ntohl (n_base);
863 if (desc_ret && !*desc_ret) {
865 unsigned int a, b, c, d;
866 unsigned long bb = n_base & htonl(h_mask);
867 unpack_addr (bb, &a, &b, &c, &d);
868 if (subnet_width > 24)
869 sprintf (buf, "%u.%u.%u.%u/%d", a, b, c, d, subnet_width);
871 sprintf (buf, "%u.%u.%u/%d", a, b, c, subnet_width);
872 *desc_ret = strdup (buf);
875 for (i = 255; i >= 0; i--) {
876 unsigned int a, b, c, d;
877 int ip = (h_base & 0xFFFFFF00L) | i; /* host order */
879 if ((ip & h_mask) != (h_base & h_mask)) /* skip out-of-subnet host */
881 else if (subnet_width == 31) /* 1-bit bridge: 2 hosts */
883 else if ((ip & ~h_mask) == 0) /* skip network address */
885 else if ((ip & ~h_mask) == ~h_mask) /* skip broadcast address */
888 unpack_addr (htonl (ip), &a, &b, &c, &d);
889 sprintf (address, "%u.%u.%u.%u", a, b, c, d);
893 unsigned int aa, ab, ac, ad;
894 unsigned int ma, mb, mc, md;
895 unpack_addr (htonl (h_base & h_mask), &aa, &ab, &ac, &ad);
896 unpack_addr (htonl (h_mask), &ma, &mb, &mc, &md);
898 "%s: subnet: %s (%u.%u.%u.%u & %u.%u.%u.%u / %d)\n",
905 p = address + strlen(address) + 1;
908 new = bogie_for_host (ssd, address, NULL);
920 /* Send a ping packet.
923 send_ping (ping_data *pd, const sonar_bogie *b)
925 ping_bogie *pb = (ping_bogie *) b->closure;
928 const char *token = "org.jwz.xscreensaver.sonar";
931 int pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) +
932 sizeof(socklen_t) + pb->addrlen +
934 strlen(pd->version) + 1);
936 /* Create the ICMP packet */
938 if (! (packet = (u_char *) calloc(1, pcktsiz)))
939 return; /* Out of memory */
941 icmph = (struct ICMP *) packet;
942 ICMP_TYPE(icmph) = ICMP_ECHO;
943 ICMP_CODE(icmph) = 0;
944 ICMP_CHECKSUM(icmph) = 0;
945 ICMP_ID(icmph) = pd->pid;
946 ICMP_SEQ(icmph) = pd->seq++;
947 # ifdef GETTIMEOFDAY_TWO_ARGS
948 gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)],
949 (struct timezone *) 0);
951 gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]);
954 /* We store the sockaddr of the host we're pinging in the packet, and parse
955 that out of the return packet later (see get_ping() for why).
956 After that, we also include the name and version of this program,
957 just to give a clue to anyone sniffing and wondering what's up.
959 host_id = (char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)];
960 *(socklen_t *)host_id = pb->addrlen;
961 host_id += sizeof(socklen_t);
962 memcpy(host_id, &pb->address, pb->addrlen);
963 host_id += pb->addrlen;
964 sprintf (host_id, "%.20s %.20s", token, pd->version);
966 ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
970 if (sendto(pd->icmpsock, packet, pcktsiz, 0,
971 (struct sockaddr *)&pb->address, sizeof(pb->address))
976 sprintf(buf, "%s: pinging %.100s", progname, b->name);
990 /* Compute the checksum on a ping packet.
993 checksum (u_short *packet, int size)
995 register int nleft = size;
996 register u_short *w = packet;
997 register int sum = 0;
1000 /* Using a 32 bit accumulator (sum), we add sequential 16 bit words
1001 to it, and at the end, fold back all the carry bits from the
1002 top 16 bits into the lower 16 bits.
1010 /* mop up an odd byte, if necessary */
1014 *(u_char *)(&answer) = *(u_char *)w ;
1015 *(1 + (u_char *)(&answer)) = 0;
1019 /* add back carry outs from top 16 bits to low 16 bits */
1021 sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
1022 sum += (sum >> 16); /* add carry */
1023 answer = ~sum; /* truncate to 16 bits */
1029 /* Copies the sonar_bogie and the underlying ping_bogie.
1031 static sonar_bogie *
1032 copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
1034 sonar_bogie *b2 = sonar_copy_bogie (ssd, b);
1037 ping_bogie *pb = (ping_bogie *) b->closure;
1038 ping_bogie *pb2 = (ping_bogie *) calloc (1, sizeof(*pb));
1039 pb2->address = pb->address;
1046 /* Look for all outstanding ping replies.
1048 static sonar_bogie *
1049 get_ping (sonar_sensor_data *ssd)
1051 ping_data *pd = (ping_data *) ssd->closure;
1052 struct sockaddr from;
1055 u_char packet[1024];
1057 struct timeval *then;
1061 sonar_bogie *bl = 0;
1062 sonar_bogie *new = 0;
1063 struct sigaction sa;
1064 struct itimerval it;
1068 /* Set up a signal to interrupt our wait for a packet */
1070 sigemptyset(&sa.sa_mask);
1072 sa.sa_handler = sigcatcher;
1073 if (sigaction(SIGALRM, &sa, 0) == -1)
1076 sprintf(msg, "%s: unable to trap SIGALRM", progname);
1081 /* Set up a timer to interupt us if we don't get a packet */
1083 it.it_interval.tv_sec = 0;
1084 it.it_interval.tv_usec = 0;
1085 it.it_value.tv_sec = 0;
1086 it.it_value.tv_usec = pd->timeout;
1088 setitimer(ITIMER_REAL, &it, 0);
1090 /* Wait for a result packet */
1092 fromlen = sizeof(from);
1093 while (! timer_expired)
1095 tv.tv_usec = pd->timeout;
1098 /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
1101 memset (&rfds, 0, sizeof(rfds));
1103 FD_SET(pd->icmpsock, &rfds);
1104 /* only wait a little while, in case we raced with the timer expiration.
1105 From Valentijn Sessink <valentyn@openoffice.nl> */
1106 if (select(pd->icmpsock + 1, &rfds, 0, 0, &tv) >0)
1108 result = recvfrom (pd->icmpsock, packet, sizeof(packet),
1109 0, &from, &fromlen);
1111 /* Check the packet */
1113 # ifdef GETTIMEOFDAY_TWO_ARGS
1114 gettimeofday(&now, (struct timezone *) 0);
1118 ip = (struct ip *) packet;
1119 iphdrlen = IP_HDRLEN(ip) << 2;
1120 icmph = (struct ICMP *) &packet[iphdrlen];
1121 then = (struct timeval *) &packet[iphdrlen + sizeof(struct ICMP)];
1124 /* Ignore anything but ICMP Replies */
1125 if (ICMP_TYPE(icmph) != ICMP_ECHOREPLY)
1128 /* Ignore packets not set from us */
1129 if (ICMP_ID(icmph) != pd->pid)
1132 /* Find the bogie in 'targets' that corresponds to this packet
1133 and copy it, so that this bogie stays in the same spot (th)
1134 on the screen, and so that we don't have to resolve it again.
1136 We could find the bogie by comparing ip->ip_src.s_addr to
1137 pb->address, but it is possible that, in certain weird router
1138 or NAT situations, that the reply will come back from a
1139 different address than the one we sent it to. So instead,
1140 we parse the sockaddr out of the reply packet payload.
1143 const socklen_t *host_id = (socklen_t *) &packet[
1144 iphdrlen + sizeof(struct ICMP) + sizeof(struct timeval)];
1148 /* Ensure that a maliciously-crafted return packet can't
1149 make us overflow in memcmp. */
1150 if (result > 0 && (const u_char *)(host_id + 1) <= packet + result)
1152 const u_char *host_end = (const u_char *)(host_id + 1) +
1155 if ((const u_char *)(host_id + 1) <= host_end &&
1156 host_end <= packet + result)
1158 for (b = pd->targets; b; b = b->next)
1160 ping_bogie *pb = (ping_bogie *)b->closure;
1161 if (*host_id == pb->addrlen &&
1162 !memcmp(&pb->address, host_id + 1, pb->addrlen) )
1164 /* Check to see if the name lookup is done. */
1165 if (pb->lookup_name &&
1166 async_name_from_addr_is_done (pb->lookup_name))
1170 async_name_from_addr_finish (pb->lookup_name,
1173 if (pd->debug_p > 1)
1174 fprintf (stderr, "%s: %s => %s\n",
1176 host ? host : "<unknown>");
1184 pb->lookup_name = NULL;
1187 new = copy_ping_bogie (ssd, b);
1195 if (! new) /* not in targets? */
1197 unsigned int a, b, c, d;
1198 unpack_addr (ip->ip_src.s_addr, &a, &b, &c, &d);
1200 "%s: UNEXPECTED PING REPLY! "
1201 "%4d bytes, icmp_seq=%-4d from %d.%d.%d.%d\n",
1202 progname, result, ICMP_SEQ(icmph), a, b, c, d);
1210 double msec = delta(then, &now) / 1000.0;
1214 if (new->desc) free (new->desc);
1215 new->desc = (char *) malloc (30);
1216 if (msec > 99) sprintf (new->desc, "%.0f ms", msec);
1217 else if (msec > 9) sprintf (new->desc, "%.1f ms", msec);
1218 else if (msec > 1) sprintf (new->desc, "%.2f ms", msec);
1219 else sprintf (new->desc, "%.3f ms", msec);
1222 if (pd->debug_p && pd->times_p) /* ping-like stdout log */
1224 char *s = strdup(new->name);
1228 s2 = s + strlen(s) - 28;
1229 strncpy (s2, "...", 3);
1232 "%3d bytes from %28s: icmp_seq=%-4d time=%s\n",
1233 result, s2, ICMP_SEQ(icmph), new->desc);
1238 /* The radius must be between 0.0 and 1.0.
1239 We want to display ping times on a logarithmic scale,
1240 with the three rings being 2.5, 70 and 2,000 milliseconds.
1242 if (msec <= 0) msec = 0.001;
1243 new->r = log (msec * 10) / log (20000);
1245 /* Don't put anyone *too* close to the center of the screen. */
1246 if (new->r < 0) new->r = 0;
1247 if (new->r < 0.1) new->r += 0.1;
1256 /* difference between the two times in microseconds.
1259 delta (struct timeval *then, struct timeval *now)
1261 return (((now->tv_sec - then->tv_sec) * 1000000) +
1262 (now->tv_usec - then->tv_usec));
1267 ping_free_data (sonar_sensor_data *ssd, void *closure)
1269 ping_data *pd = (ping_data *) closure;
1270 sonar_bogie *b = pd->targets;
1273 sonar_bogie *b2 = b->next;
1274 sonar_free_bogie (ssd, b);
1281 ping_free_bogie_data (sonar_sensor_data *sd, void *closure)
1283 ping_bogie *pb = (ping_bogie *) closure;
1285 if (pb->lookup_name)
1286 async_name_from_addr_cancel (pb->lookup_name);
1287 if (pb->lookup_addr)
1288 async_addr_from_name_cancel (pb->lookup_addr);
1289 free (pb->fallback);
1295 /* Returns the current time in seconds as a double.
1301 # ifdef GETTIMEOFDAY_TWO_ARGS
1302 struct timezone tzp;
1303 gettimeofday(&now, &tzp);
1308 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
1313 free_bogie_after_lookup(sonar_sensor_data *ssd, sonar_bogie **sbp,
1316 ping_bogie *pb = (ping_bogie *)(*sb)->closure;
1319 pb->lookup_addr = NULL; /* Prevent double-free in sonar_free_bogie. */
1320 sonar_free_bogie (ssd, *sb);
1325 /* Pings the next bogie, if it's time.
1326 Returns all outstanding ping replies.
1328 static sonar_bogie *
1329 ping_scan (sonar_sensor_data *ssd)
1331 ping_data *pd = (ping_data *) ssd->closure;
1332 double now = double_time();
1333 double ping_cycle = 10; /* re-ping a given host every 10 seconds */
1334 double ping_interval = ping_cycle / pd->target_count;
1336 if (now > pd->last_ping_time + ping_interval) /* time to ping someone */
1338 struct sonar_bogie **sbp;
1340 if (pd->last_pinged)
1342 sbp = &pd->last_pinged->next;
1350 /* Aaaaand we're out of bogies. */
1351 pd->last_pinged = NULL;
1354 sonar_bogie *sb = *sbp;
1355 ping_bogie *pb = (ping_bogie *)sb->closure;
1356 if (pb->lookup_addr &&
1357 async_addr_from_name_is_done (pb->lookup_addr))
1359 if (async_addr_from_name_finish (pb->lookup_addr, &pb->address,
1360 &pb->addrlen, NULL))
1362 char *fallback = pb->fallback;
1363 pb->fallback = NULL;
1366 fprintf (stderr, "%s: could not resolve host: %s\n",
1367 progname, sb->name);
1369 free_bogie_after_lookup (ssd, sbp, &sb);
1371 /* Insert the fallback bogie right where the old one was. */
1374 sonar_bogie *new_bogie = bogie_for_host (ssd, fallback,
1376 new_bogie->next = *sbp;
1378 if (! ((ping_bogie *)new_bogie->closure)->lookup_addr &&
1379 ! find_duplicate_host(pd, &pd->targets, new_bogie))
1382 sonar_free_bogie (ssd, new_bogie);
1389 if (pd->debug_p > 1)
1391 fprintf (stderr, "%s: %s => ", progname, sb->name);
1392 print_address (stderr, 0, &pb->address, pb->addrlen);
1396 if (! is_address_ok (pd->debug_p, sb))
1397 free_bogie_after_lookup (ssd, sbp, &sb);
1398 else if (find_duplicate_host (pd, &pd->targets, sb))
1399 /* Tricky: find_duplicate_host skips the current bogie when
1400 scanning the targets list because pb->lookup_addr hasn't
1403 Not that it matters much, but behavior here is to
1404 keep the existing address.
1406 free_bogie_after_lookup (ssd, sbp, &sb);
1410 pb->lookup_addr = NULL;
1413 if (sb && !pb->lookup_addr)
1415 if (!pb->addrlen) abort();
1417 pd->last_pinged = sb;
1421 pd->last_ping_time = now;
1424 return get_ping (ssd);
1428 /* Returns a list of hosts to ping based on the "-ping" argument.
1430 static sonar_bogie *
1431 parse_mode (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
1432 const char *ping_arg, Bool ping_works_p)
1434 ping_data *pd = (ping_data *) ssd->closure;
1435 char *source, *token, *end, dummy;
1436 sonar_bogie *hostlist = 0;
1437 const char *fallback = "subnet";
1441 if (fallback && (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default")))
1442 source = strdup(fallback);
1444 source = strdup(ping_arg);
1449 end = source + strlen(source);
1453 sonar_bogie *new = 0;
1457 unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
1462 *next != ',' && *next != ' ' && *next != '\t' && *next != '\n';
1469 fprintf (stderr, "%s: parsing %s\n", progname, token);
1473 *error_ret = strdup ("Sonar must be setuid to ping!\n"
1474 "Running simulation instead.");
1478 if ((4 == sscanf (token, "%u.%u.%u/%u %c", &n0,&n1,&n2, &m,&d)) ||
1479 (5 == sscanf (token, "%u.%u.%u.%u/%u %c", &n0,&n1,&n2,&n3,&m,&d)))
1481 /* subnet: A.B.C.D/M
1484 unsigned long ip = pack_addr (n0, n1, n2, n3);
1485 new = subnet_hosts (ssd, error_ret, desc_ret, ip, m);
1487 else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d))
1491 new = bogie_for_host (ssd, token, NULL);
1493 else if (!strcmp (token, "subnet"))
1495 new = subnet_hosts (ssd, error_ret, desc_ret, 0, 24);
1497 else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
1499 new = subnet_hosts (ssd, error_ret, desc_ret, 0, m);
1501 else if (*token == '.' || *token == '/' ||
1502 *token == '$' || *token == '~')
1505 new = read_hosts_file (ssd, token);
1507 if (pd->debug_p) fprintf (stderr, "%s: skipping file\n", progname);
1511 else if (!stat (token, &st))
1513 new = read_hosts_file (ssd, token);
1515 # endif /* READ_FILES */
1518 /* not an existant file - must be a host name
1520 new = bogie_for_host (ssd, token, NULL);
1525 sonar_bogie *nn = new;
1528 nn->next = hostlist;
1533 while (token < end &&
1534 (*token == ',' || *token == ' ' ||
1535 *token == '\t' || *token == '\n'))
1541 /* If the arg was completely unparsable, fall back to the local subnet.
1542 This happens if the default is "/etc/hosts" but READ_FILES is off.
1543 Or if we're on a /31 network, in which case we try twice then fail.
1545 if (!hostlist && fallback)
1548 fprintf (stderr, "%s: no hosts parsed! Trying %s\n",
1549 progname, fallback);
1550 ping_arg = fallback;
1560 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
1561 const char *subnet, int timeout,
1562 Bool resolve_p, Bool times_p, Bool debug_p)
1564 /* Important! Do not return from this function without disavowing privileges
1565 with setuid(getuid()).
1567 sonar_sensor_data *ssd = (sonar_sensor_data *) calloc (1, sizeof(*ssd));
1568 ping_data *pd = (ping_data *) calloc (1, sizeof(*pd));
1572 Bool socket_initted_p = False;
1573 Bool socket_raw_p = False;
1577 pd->resolve_p = resolve_p;
1578 pd->times_p = times_p;
1579 pd->debug_p = debug_p;
1582 ssd->scan_cb = ping_scan;
1583 ssd->free_data_cb = ping_free_data;
1584 ssd->free_bogie_cb = ping_free_bogie_data;
1586 /* Get short version number. */
1587 s = strchr (screensaver_id, ' ');
1588 pd->version = strdup (s+1);
1589 s = strchr (pd->version, ' ');
1593 /* Create the ICMP socket. Do this before dropping privs.
1595 Raw sockets can only be opened by root (or setuid root), so we
1596 only try to do this when the effective uid is 0.
1598 We used to just always try, and notice the failure. But apparently
1599 that causes "SELinux" to log spurious warnings when running with the
1600 "strict" policy. So to avoid that, we just don't try unless we
1603 On MacOS X, we can avoid the whole problem by using a
1604 non-privileged datagram instead of a raw socket.
1606 if (global_icmpsock)
1608 pd->icmpsock = global_icmpsock;
1609 socket_initted_p = True;
1611 fprintf (stderr, "%s: re-using icmp socket\n", progname);
1614 else if ((pd->icmpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) >= 0)
1616 socket_initted_p = True;
1618 else if (geteuid() == 0 &&
1619 (pd->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) >= 0)
1621 socket_initted_p = True;
1622 socket_raw_p = True;
1625 if (socket_initted_p)
1627 global_icmpsock = pd->icmpsock;
1628 socket_initted_p = True;
1630 fprintf (stderr, "%s: opened %s icmp socket\n", progname,
1631 (socket_raw_p ? "raw" : "dgram"));
1634 fprintf (stderr, "%s: unable to open icmp socket\n", progname);
1639 pd->pid = getpid() & 0xFFFF;
1641 pd->timeout = timeout;
1643 /* Generate a list of targets */
1645 pd->targets = parse_mode (ssd, error_ret, desc_ret, subnet,
1647 pd->targets = delete_duplicate_hosts (ssd, pd->targets);
1651 fprintf (stderr, "%s: Target list:\n", progname);
1652 for (b = pd->targets; b; b = b->next)
1654 fprintf (stderr, "%s: ", progname);
1655 print_host (stderr, b);
1659 /* Make sure there is something to ping */
1661 pd->target_count = 0;
1662 for (b = pd->targets; b; b = b->next)
1665 if (pd->target_count == 0)
1668 *error_ret = strdup ("No hosts to ping!\n"
1669 "Simulating instead.");
1670 if (pd) ping_free_data (ssd, pd);
1671 if (ssd) free (ssd);
1675 /* Distribute them evenly around the display field, clockwise.
1676 Even on a /24, allocated IPs tend to cluster together, so
1677 don't put any two hosts closer together than N degrees to
1678 avoid unnecessary overlap when we have plenty of space due
1679 to addresses that probably won't respond. And don't spread
1680 them out too far apart, because that looks too symmetrical
1681 when there are a small number of hosts.
1684 double th = frand(M_PI);
1685 double sep = 360.0 / pd->target_count;
1686 if (sep < 23) sep = 23;
1687 if (sep > 43) sep = 43;
1689 for (b = pd->targets; b; b = b->next) {
1698 #endif /* HAVE_PING -- whole file */