1 /* sonar, Copyright (c) 1998-2012 Jamie Zawinski and Stephen Martin
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
11 * This implements the "ping" sensor for sonar.
14 #include "screenhackI.h"
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>
57 # include <sys/socket.h>
58 # include <netinet/in_systm.h>
59 # include <netinet/in.h>
60 # include <netinet/ip.h>
61 # include <netinet/ip_icmp.h>
62 # include <netinet/udp.h>
63 # include <arpa/inet.h>
66 # ifdef HAVE_GETIFADDRS
69 #endif /* HAVE_ICMP || HAVE_ICMPHDR */
71 #if defined(HAVE_ICMP)
74 # define ICMP_TYPE(p) (p)->icmp_type
75 # define ICMP_CODE(p) (p)->icmp_code
76 # define ICMP_CHECKSUM(p) (p)->icmp_cksum
77 # define ICMP_ID(p) (p)->icmp_id
78 # define ICMP_SEQ(p) (p)->icmp_seq
79 #elif defined(HAVE_ICMPHDR)
82 # define ICMP_TYPE(p) (p)->type
83 # define ICMP_CODE(p) (p)->code
84 # define ICMP_CHECKSUM(p) (p)->checksum
85 # define ICMP_ID(p) (p)->un.echo.id
86 # define ICMP_SEQ(p) (p)->un.echo.sequence
98 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
99 const char *subnet, int timeout,
100 Bool resolve_p, Bool times_p, Bool debug_p)
102 if (! (!subnet || !*subnet || !strcmp(subnet, "default")))
103 fprintf (stderr, "%s: not compiled with support for pinging hosts.\n",
108 #else /* HAVE_PING -- whole file */
111 #if defined(__DECC) || defined(_IP_VHL)
112 /* This is how you do it on DEC C, and possibly some BSD systems. */
113 # define IP_HDRLEN(ip) ((ip)->ip_vhl & 0x0F)
115 /* This is how you do it on everything else. */
116 # define IP_HDRLEN(ip) ((ip)->ip_hl)
119 /* yes, there is only one, even when multiple savers are running in the
120 same address space - since we can only open this socket before dropping
123 static int global_icmpsock = 0;
125 /* Set by a signal handler. */
126 static int timer_expired;
130 static u_short checksum(u_short *, int);
131 static long delta(struct timeval *, struct timeval *);
135 Display *dpy; /* Only used to get *useThreads. */
137 char *version; /* short version number of xscreensaver */
138 int icmpsock; /* socket for sending pings */
139 int pid; /* our process ID */
140 int seq; /* packet sequence number */
141 int timeout; /* packet timeout */
144 sonar_bogie *targets; /* the hosts we will ping;
145 those that pong end up on ssd->pending. */
146 sonar_bogie *last_pinged; /* pointer into 'targets' list */
147 double last_ping_time;
156 async_name_from_addr_t lookup_name;
157 async_addr_from_name_t lookup_addr;
158 async_netdb_sockaddr_storage_t address; /* ip address */
165 /* Packs an IP address quad into bigendian network order. */
167 pack_addr (unsigned int a, unsigned int b, unsigned int c, unsigned int d)
169 unsigned long i = (((a & 255) << 24) |
176 /* Unpacks an IP address quad from bigendian network order. */
178 unpack_addr (unsigned long addr,
179 unsigned int *a, unsigned int *b,
180 unsigned int *c, unsigned int *d)
183 *a = (addr >> 24) & 255;
184 *b = (addr >> 16) & 255;
185 *c = (addr >> 8) & 255;
192 /* Resolves the bogie's name (either a hostname or ip address string)
193 to a hostent. Returns 1 if successful, 0 if something went wrong.
196 resolve_bogie_hostname (ping_data *pd, sonar_bogie *sb, Bool resolve_p)
198 ping_bogie *pb = (ping_bogie *) sb->closure;
203 if (4 == sscanf (sb->name, " %u.%u.%u.%u %c",
204 &ip[0], &ip[1], &ip[2], &ip[3], &c))
206 /* It's an IP address.
208 struct sockaddr_in *iaddr = (struct sockaddr_in *) &(pb->address);
213 fprintf (stderr, "%s: ignoring bogus IP %s\n",
218 iaddr->sin_family = AF_INET;
219 iaddr->sin_addr.s_addr = pack_addr (ip[0], ip[1], ip[2], ip[3]);
220 pb->addrlen = sizeof(struct sockaddr_in);
225 async_name_from_addr_start (pd->dpy,
226 (const struct sockaddr *)&pb->address,
228 if (!pb->lookup_name)
230 fprintf (stderr, "%s: unable to start host resolution.\n",
237 /* It's a host name. */
239 /* don't waste time being confused by non-hostname tokens
240 in .ssh/known_hosts */
241 if (!strcmp (sb->name, "ssh-rsa") ||
242 !strcmp (sb->name, "ssh-dsa") ||
243 !strcmp (sb->name, "ssh-dss") ||
244 strlen (sb->name) >= 80)
247 /* .ssh/known_hosts sometimes contains weirdness like "[host]:port".
249 if (strchr (sb->name, '['))
252 fprintf (stderr, "%s: ignoring bogus address \"%s\"\n",
257 /* If the name contains a colon, it's probably IPv6. */
258 if (strchr (sb->name, ':'))
261 fprintf (stderr, "%s: ignoring ipv6 address \"%s\"\n",
266 pb->lookup_addr = async_addr_from_name_start(pd->dpy, sb->name);
267 if (!pb->lookup_addr)
270 /* Either address space exhaustion or RAM exhaustion. */
271 fprintf (stderr, "%s: unable to start host resolution.\n",
281 print_address (FILE *out, int width, const void *sockaddr, socklen_t addrlen)
283 #ifdef HAVE_GETADDRINFO
284 char buf[NI_MAXHOST];
289 const struct sockaddr *addr = (const struct sockaddr *)sockaddr;
290 const char *ips = buf;
292 if (!addr->sa_family)
293 ips = "<no address>";
296 #ifdef HAVE_GETADDRINFO
297 int gai_error = getnameinfo (sockaddr, addrlen, buf, sizeof(buf),
298 NULL, 0, NI_NUMERICHOST);
299 if (gai_error == EAI_SYSTEM)
300 ips = strerror(errno);
302 ips = gai_strerror(gai_error);
304 switch (addr->sa_family)
308 u_long ip = ((struct sockaddr_in *)sockaddr)->sin_addr.s_addr;
309 unsigned int a, b, c, d;
310 unpack_addr (ip, &a, &b, &c, &d); /* ip is in network order */
311 sprintf (buf, "%u.%u.%u.%u", a, b, c, d);
321 fprintf (out, "%-*s", width, ips);
326 print_host (FILE *out, const sonar_bogie *sb)
328 const ping_bogie *pb = (const ping_bogie *) sb->closure;
329 const char *name = sb->name;
330 if (!name || !*name) name = "<unknown>";
331 print_address (out, 16, &pb->address, pb->addrlen);
332 fprintf (out, " %s\n", name);
337 is_address_ok(Bool debug_p, const sonar_bogie *b)
339 const ping_bogie *pb = (const ping_bogie *) b->closure;
340 const struct sockaddr *addr = (const struct sockaddr *)&pb->address;
342 switch (addr->sa_family)
346 struct sockaddr_in *iaddr = (struct sockaddr_in *) addr;
348 /* Don't ever use loopback (127.0.0.x) hosts */
349 unsigned long ip = iaddr->sin_addr.s_addr;
350 if ((ntohl (ip) & 0xFFFFFF00L) == 0x7f000000L) /* 127.0.0.x */
353 fprintf (stderr, "%s: ignoring loopback host %s\n",
358 /* Don't ever use broadcast (255.x.x.x) hosts */
359 if ((ntohl (ip) & 0xFF000000L) == 0xFF000000L) /* 255.x.x.x */
362 fprintf (stderr, "%s: ignoring broadcast host %s\n",
375 /* Create a sonar_bogie from a host name or ip address string.
376 Returns NULL if the name could not be resolved.
379 bogie_for_host (sonar_sensor_data *ssd, const char *name, const char *fallback)
381 ping_data *pd = (ping_data *) ssd->closure;
382 sonar_bogie *b = (sonar_bogie *) calloc (1, sizeof(*b));
383 ping_bogie *pb = (ping_bogie *) calloc (1, sizeof(*pb));
384 Bool resolve_p = pd->resolve_p;
386 b->name = strdup (name);
389 if (! resolve_bogie_hostname (pd, b, resolve_p))
392 if (! pb->lookup_addr && ! is_address_ok (pd->debug_p, b))
397 fprintf (stderr, "%s: added ", progname);
398 print_host (stderr, b);
402 pb->fallback = strdup (fallback);
406 if (b) sonar_free_bogie (ssd, b);
409 return bogie_for_host (ssd, fallback, NULL);
417 /* Return a list of bogies read from a file.
418 The file can be like /etc/hosts or .ssh/known_hosts or probably
419 just about anything that has host names in it.
422 read_hosts_file (sonar_sensor_data *ssd, const char *filename)
424 ping_data *pd = (ping_data *) ssd->closure;
428 sonar_bogie *list = 0;
432 /* Kludge: on OSX, variables have not been expanded in the command
433 line arguments, so as a special case, allow the string to begin
434 with literal "$HOME/" or "~/".
436 This is so that the "Known Hosts" menu item in sonar.xml works.
438 if (!strncmp(filename, "~/", 2) || !strncmp(filename, "$HOME/", 6))
440 char *s = strchr (filename, '/');
441 strcpy (buf, getenv("HOME"));
446 fp = fopen(filename, "r");
450 sprintf(buf, "%s: %s", progname, filename);
452 if (pd->debug_p) /* on OSX don't syslog this */
459 fprintf (stderr, "%s: reading \"%s\"\n", progname, filename);
461 while ((p = fgets(buf, LINE_MAX, fp)))
463 while ((*p == ' ') || (*p == '\t')) /* skip whitespace */
465 if (*p == '#') /* skip comments */
468 /* Get the name and address */
470 if ((addr = strtok(buf, " ,;\t\n")))
471 name = strtok(0, " ,;\t\n");
475 /* Check to see if the addr looks like an addr. If not, assume
476 the addr is a name and there is no addr. This way, we can
477 handle files whose lines have "xx.xx.xx.xx hostname" as their
478 first two tokens, and also files that have a hostname as their
479 first token (like .ssh/known_hosts and .rhosts.)
483 if (4 != sscanf(addr, "%d.%d.%d.%d%c", &i, &i, &i, &i, &c))
490 /* If the name is all digits, it's not a name. */
494 for (s = name; *s; s++)
495 if (*s < '0' || *s > '9')
500 fprintf (stderr, "%s: skipping bogus name \"%s\" (%s)\n",
501 progname, name, addr);
506 /* Create a new target using first the name then the address */
514 new = bogie_for_host (ssd, name, addr);
526 #endif /* READ_FILES */
529 static sonar_bogie **
530 found_duplicate_host (const ping_data *pd, sonar_bogie **list,
535 fprintf (stderr, "%s: deleted duplicate: ", progname);
536 print_host (stderr, bogie);
543 static sonar_bogie **
544 find_duplicate_host (const ping_data *pd, sonar_bogie **list,
547 const ping_bogie *pb = (const ping_bogie *) bogie->closure;
548 const struct sockaddr *addr1 = (const struct sockaddr *) &(pb->address);
552 const ping_bogie *pb2 = (const ping_bogie *) (*list)->closure;
554 if (!pb2->lookup_addr)
556 const struct sockaddr *addr2 =
557 (const struct sockaddr *) &(pb2->address);
559 if (addr1->sa_family == addr2->sa_family)
561 switch (addr1->sa_family)
566 ((const struct sockaddr_in *)addr1)->sin_addr.s_addr;
567 const struct sockaddr_in *i2 =
568 (const struct sockaddr_in *)addr2;
569 unsigned long ip2 = i2->sin_addr.s_addr;
572 return found_duplicate_host (pd, list, bogie);
579 &((const struct sockaddr_in6 *)addr1)->sin6_addr,
580 &((const struct sockaddr_in6 *)addr2)->sin6_addr,
582 return found_duplicate_host (pd, list, bogie);
588 /* Fallback behavior: Just memcmp the two addresses.
590 For this to work, unused space in the sockaddr must be
591 set to zero. Which may actually be the case:
592 - async_addr_from_name_finish won't put garbage into
593 sockaddr_in.sin_zero or elsewhere unless getaddrinfo
595 - ping_bogie is allocated with calloc(). */
597 if (pb->addrlen == pb2->addrlen &&
598 ! memcmp(addr1, addr2, pb->addrlen))
599 return found_duplicate_host (pd, list, bogie);
606 list = &(*list)->next;
614 delete_duplicate_hosts (sonar_sensor_data *ssd, sonar_bogie *list)
616 ping_data *pd = (ping_data *) ssd->closure;
617 sonar_bogie *head = list;
618 sonar_bogie *sb = head;
622 ping_bogie *pb = (ping_bogie *) sb->closure;
624 if (!pb->lookup_addr)
626 sonar_bogie **sb2 = find_duplicate_host (pd, &sb->next, sb);
642 width_mask (int width)
646 for (i = 0; i < width; i++)
652 #ifdef HAVE_GETIFADDRS
654 mask_width (unsigned int mask)
657 for (i = 0; i < 32; i++)
665 /* Generate a list of bogies consisting of all of the entries on
666 the same subnet. 'base' ip is in network order; 0 means localhost.
669 subnet_hosts (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
670 unsigned long n_base, int subnet_width)
672 ping_data *pd = (ping_data *) ssd->closure;
673 unsigned long h_mask; /* host order */
674 unsigned long h_base; /* host order */
675 char address[BUFSIZ];
679 sonar_bogie *list = 0;
682 if (subnet_width < 24)
685 "Pinging %lu hosts is a bad\n"
686 "idea. Please use a subnet\n"
687 "mask of 24 bits or more.",
688 (unsigned long) (1L << (32 - subnet_width)) - 1);
689 *error_ret = strdup(buf);
692 else if (subnet_width > 30)
696 "doesn't make sense.\n"
697 "Try \"subnet/24\"\n"
698 "or \"subnet/29\".\n",
700 *error_ret = strdup(buf);
706 fprintf (stderr, "%s: adding %d-bit subnet\n", progname, subnet_width);
711 # ifdef HAVE_GETIFADDRS
713 /* To determine the local subnet, we need to know the local IP address.
714 Do this by looking at the IPs of every network interface.
716 struct in_addr in = { 0, };
717 struct ifaddrs *all = 0, *ifa;
720 fprintf (stderr, "%s: listing network interfaces\n", progname);
723 for (ifa = all; ifa; ifa = ifa->ifa_next)
727 if (ifa->ifa_addr->sa_family != AF_INET)
730 fprintf (stderr, "%s: if: %4s: %s\n", progname,
734 ifa->ifa_addr->sa_family == AF_UNIX ? "local" :
737 ifa->ifa_addr->sa_family == AF_LINK ? "link" :
740 ifa->ifa_addr->sa_family == AF_INET6 ? "ipv6" :
745 in2 = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
746 mask = ntohl (((struct sockaddr_in *) ifa->ifa_netmask)
749 fprintf (stderr, "%s: if: %4s: inet = %s /%d 0x%08lx\n",
755 if (in2.s_addr == 0x0100007f || /* 127.0.0.1 in network order */
759 /* At least on the AT&T 3G network, pinging either of the two
760 hosts on a /31 network doesn't work, so don't try.
762 if (mask_width (mask) == 31)
765 "Can't ping subnet:\n"
770 inet_ntoa (in2), mask_width (mask), ifa->ifa_name);
771 if (*error_ret) free (*error_ret);
772 *error_ret = strdup (buf);
777 subnet_width = mask_width (mask);
782 if (*error_ret) free (*error_ret);
784 n_base = in.s_addr; /* already in network order, I think? */
786 else if (!*error_ret)
787 *error_ret = strdup ("Unable to determine\nlocal IP address\n");
795 # else /* !HAVE_GETIFADDRS */
797 /* If we can't walk the list of network interfaces to figure out
798 our local IP address, try to do it by finding the local host
799 name, then resolving that.
801 char hostname[BUFSIZ];
802 struct hostent *hent = 0;
804 if (gethostname(hostname, BUFSIZ))
806 *error_ret = strdup ("Unable to determine\n"
811 /* Get our IP address and convert it to a string */
813 hent = gethostbyname(hostname);
816 strcat (hostname, ".local"); /* Necessary on iphone */
817 hent = gethostbyname(hostname);
823 "Unable to resolve\n"
824 "local host \"%.100s\"",
826 *error_ret = strdup(buf);
830 strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
831 n_base = pack_addr (hent->h_addr_list[0][0],
832 hent->h_addr_list[0][1],
833 hent->h_addr_list[0][2],
834 hent->h_addr_list[0][3]);
836 if (n_base == 0x0100007f) /* 127.0.0.1 in network order */
838 unsigned int a, b, c, d;
839 unpack_addr (n_base, &a, &b, &c, &d);
841 "Unable to determine\n"
842 "local subnet address:\n"
847 hostname, a, b, c, d);
848 *error_ret = strdup(buf);
852 # endif /* !HAVE_GETIFADDRS */
856 /* Construct targets for all addresses in this subnet */
858 h_mask = width_mask (subnet_width);
859 h_base = ntohl (n_base);
861 if (desc_ret && !*desc_ret) {
863 unsigned int a, b, c, d;
864 unsigned long bb = n_base & htonl(h_mask);
865 unpack_addr (bb, &a, &b, &c, &d);
866 if (subnet_width > 24)
867 sprintf (buf, "%u.%u.%u.%u/%d", a, b, c, d, subnet_width);
869 sprintf (buf, "%u.%u.%u/%d", a, b, c, subnet_width);
870 *desc_ret = strdup (buf);
873 for (i = 255; i >= 0; i--) {
874 unsigned int a, b, c, d;
875 int ip = (h_base & 0xFFFFFF00L) | i; /* host order */
877 if ((ip & h_mask) != (h_base & h_mask)) /* skip out-of-subnet host */
879 else if (subnet_width == 31) /* 1-bit bridge: 2 hosts */
881 else if ((ip & ~h_mask) == 0) /* skip network address */
883 else if ((ip & ~h_mask) == ~h_mask) /* skip broadcast address */
886 unpack_addr (htonl (ip), &a, &b, &c, &d);
887 sprintf (address, "%u.%u.%u.%u", a, b, c, d);
891 unsigned int aa, ab, ac, ad;
892 unsigned int ma, mb, mc, md;
893 unpack_addr (htonl (h_base & h_mask), &aa, &ab, &ac, &ad);
894 unpack_addr (htonl (h_mask), &ma, &mb, &mc, &md);
896 "%s: subnet: %s (%u.%u.%u.%u & %u.%u.%u.%u / %d)\n",
903 p = address + strlen(address) + 1;
906 new = bogie_for_host (ssd, address, NULL);
918 /* Send a ping packet.
921 send_ping (ping_data *pd, const sonar_bogie *b)
923 ping_bogie *pb = (ping_bogie *) b->closure;
926 const char *token = "org.jwz.xscreensaver.sonar";
929 int pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) +
930 sizeof(socklen_t) + pb->addrlen +
932 strlen(pd->version) + 1);
934 /* Create the ICMP packet */
936 if (! (packet = (u_char *) calloc(1, pcktsiz)))
937 return; /* Out of memory */
939 icmph = (struct ICMP *) packet;
940 ICMP_TYPE(icmph) = ICMP_ECHO;
941 ICMP_CODE(icmph) = 0;
942 ICMP_CHECKSUM(icmph) = 0;
943 ICMP_ID(icmph) = pd->pid;
944 ICMP_SEQ(icmph) = pd->seq++;
945 # ifdef GETTIMEOFDAY_TWO_ARGS
946 gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)],
947 (struct timezone *) 0);
949 gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]);
952 /* We store the sockaddr of the host we're pinging in the packet, and parse
953 that out of the return packet later (see get_ping() for why).
954 After that, we also include the name and version of this program,
955 just to give a clue to anyone sniffing and wondering what's up.
957 host_id = (char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)];
958 *(socklen_t *)host_id = pb->addrlen;
959 host_id += sizeof(socklen_t);
960 memcpy(host_id, &pb->address, pb->addrlen);
961 host_id += pb->addrlen;
962 sprintf (host_id, "%.20s %.20s", token, pd->version);
964 ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
968 if (sendto(pd->icmpsock, packet, pcktsiz, 0,
969 (struct sockaddr *)&pb->address, sizeof(pb->address))
974 sprintf(buf, "%s: pinging %.100s", progname, b->name);
988 /* Compute the checksum on a ping packet.
991 checksum (u_short *packet, int size)
993 register int nleft = size;
994 register u_short *w = packet;
995 register int sum = 0;
998 /* Using a 32 bit accumulator (sum), we add sequential 16 bit words
999 to it, and at the end, fold back all the carry bits from the
1000 top 16 bits into the lower 16 bits.
1008 /* mop up an odd byte, if necessary */
1012 *(u_char *)(&answer) = *(u_char *)w ;
1013 *(1 + (u_char *)(&answer)) = 0;
1017 /* add back carry outs from top 16 bits to low 16 bits */
1019 sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
1020 sum += (sum >> 16); /* add carry */
1021 answer = ~sum; /* truncate to 16 bits */
1027 /* Copies the sonar_bogie and the underlying ping_bogie.
1029 static sonar_bogie *
1030 copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
1032 sonar_bogie *b2 = sonar_copy_bogie (ssd, b);
1035 ping_bogie *pb = (ping_bogie *) b->closure;
1036 ping_bogie *pb2 = (ping_bogie *) calloc (1, sizeof(*pb));
1037 pb2->address = pb->address;
1044 /* Look for all outstanding ping replies.
1046 static sonar_bogie *
1047 get_ping (sonar_sensor_data *ssd)
1049 ping_data *pd = (ping_data *) ssd->closure;
1050 struct sockaddr from;
1051 unsigned int fromlen; /* Posix says socklen_t, but that's not portable */
1053 u_char packet[1024];
1055 struct timeval *then;
1059 sonar_bogie *bl = 0;
1060 sonar_bogie *new = 0;
1061 struct sigaction sa;
1062 struct itimerval it;
1066 /* Set up a signal to interrupt our wait for a packet */
1068 sigemptyset(&sa.sa_mask);
1070 sa.sa_handler = sigcatcher;
1071 if (sigaction(SIGALRM, &sa, 0) == -1)
1074 sprintf(msg, "%s: unable to trap SIGALRM", progname);
1079 /* Set up a timer to interupt us if we don't get a packet */
1081 it.it_interval.tv_sec = 0;
1082 it.it_interval.tv_usec = 0;
1083 it.it_value.tv_sec = 0;
1084 it.it_value.tv_usec = pd->timeout;
1086 setitimer(ITIMER_REAL, &it, 0);
1088 /* Wait for a result packet */
1090 fromlen = sizeof(from);
1091 while (! timer_expired)
1093 tv.tv_usec = pd->timeout;
1096 /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
1099 memset (&rfds, 0, sizeof(rfds));
1101 FD_SET(pd->icmpsock, &rfds);
1102 /* only wait a little while, in case we raced with the timer expiration.
1103 From Valentijn Sessink <valentyn@openoffice.nl> */
1104 if (select(pd->icmpsock + 1, &rfds, 0, 0, &tv) >0)
1106 result = recvfrom (pd->icmpsock, packet, sizeof(packet),
1107 0, &from, &fromlen);
1109 /* Check the packet */
1111 # ifdef GETTIMEOFDAY_TWO_ARGS
1112 gettimeofday(&now, (struct timezone *) 0);
1116 ip = (struct ip *) packet;
1117 iphdrlen = IP_HDRLEN(ip) << 2;
1118 icmph = (struct ICMP *) &packet[iphdrlen];
1119 then = (struct timeval *) &packet[iphdrlen + sizeof(struct ICMP)];
1122 /* Ignore anything but ICMP Replies */
1123 if (ICMP_TYPE(icmph) != ICMP_ECHOREPLY)
1126 /* Ignore packets not set from us */
1127 if (ICMP_ID(icmph) != pd->pid)
1130 /* Find the bogie in 'targets' that corresponds to this packet
1131 and copy it, so that this bogie stays in the same spot (th)
1132 on the screen, and so that we don't have to resolve it again.
1134 We could find the bogie by comparing ip->ip_src.s_addr to
1135 pb->address, but it is possible that, in certain weird router
1136 or NAT situations, that the reply will come back from a
1137 different address than the one we sent it to. So instead,
1138 we parse the sockaddr out of the reply packet payload.
1141 const socklen_t *host_id = (socklen_t *) &packet[
1142 iphdrlen + sizeof(struct ICMP) + sizeof(struct timeval)];
1146 /* Ensure that a maliciously-crafted return packet can't
1147 make us overflow in memcmp. */
1148 if (result > 0 && (const u_char *)(host_id + 1) <= packet + result)
1150 const u_char *host_end = (const u_char *)(host_id + 1) +
1153 if ((const u_char *)(host_id + 1) <= host_end &&
1154 host_end <= packet + result)
1156 for (b = pd->targets; b; b = b->next)
1158 ping_bogie *pb = (ping_bogie *)b->closure;
1159 if (*host_id == pb->addrlen &&
1160 !memcmp(&pb->address, host_id + 1, pb->addrlen) )
1162 /* Check to see if the name lookup is done. */
1163 if (pb->lookup_name &&
1164 async_name_from_addr_is_done (pb->lookup_name))
1168 async_name_from_addr_finish (pb->lookup_name,
1171 if (pd->debug_p > 1)
1172 fprintf (stderr, "%s: %s => %s\n",
1174 host ? host : "<unknown>");
1182 pb->lookup_name = NULL;
1185 new = copy_ping_bogie (ssd, b);
1193 if (! new) /* not in targets? */
1195 unsigned int a, b, c, d;
1196 unpack_addr (ip->ip_src.s_addr, &a, &b, &c, &d);
1198 "%s: UNEXPECTED PING REPLY! "
1199 "%4d bytes, icmp_seq=%-4d from %d.%d.%d.%d\n",
1200 progname, result, ICMP_SEQ(icmph), a, b, c, d);
1208 double msec = delta(then, &now) / 1000.0;
1212 if (new->desc) free (new->desc);
1213 new->desc = (char *) malloc (30);
1214 if (msec > 99) sprintf (new->desc, "%.0f ms", msec);
1215 else if (msec > 9) sprintf (new->desc, "%.1f ms", msec);
1216 else if (msec > 1) sprintf (new->desc, "%.2f ms", msec);
1217 else sprintf (new->desc, "%.3f ms", msec);
1220 if (pd->debug_p && pd->times_p) /* ping-like stdout log */
1222 char *s = strdup(new->name);
1226 s2 = s + strlen(s) - 28;
1227 strncpy (s2, "...", 3);
1230 "%3d bytes from %28s: icmp_seq=%-4d time=%s\n",
1231 result, s2, ICMP_SEQ(icmph), new->desc);
1236 /* The radius must be between 0.0 and 1.0.
1237 We want to display ping times on a logarithmic scale,
1238 with the three rings being 2.5, 70 and 2,000 milliseconds.
1240 if (msec <= 0) msec = 0.001;
1241 new->r = log (msec * 10) / log (20000);
1243 /* Don't put anyone *too* close to the center of the screen. */
1244 if (new->r < 0) new->r = 0;
1245 if (new->r < 0.1) new->r += 0.1;
1254 /* difference between the two times in microseconds.
1257 delta (struct timeval *then, struct timeval *now)
1259 return (((now->tv_sec - then->tv_sec) * 1000000) +
1260 (now->tv_usec - then->tv_usec));
1265 ping_free_data (sonar_sensor_data *ssd, void *closure)
1267 ping_data *pd = (ping_data *) closure;
1268 sonar_bogie *b = pd->targets;
1271 sonar_bogie *b2 = b->next;
1272 sonar_free_bogie (ssd, b);
1279 ping_free_bogie_data (sonar_sensor_data *sd, void *closure)
1281 ping_bogie *pb = (ping_bogie *) closure;
1283 if (pb->lookup_name)
1284 async_name_from_addr_cancel (pb->lookup_name);
1285 if (pb->lookup_addr)
1286 async_addr_from_name_cancel (pb->lookup_addr);
1287 free (pb->fallback);
1293 /* Returns the current time in seconds as a double.
1299 # ifdef GETTIMEOFDAY_TWO_ARGS
1300 struct timezone tzp;
1301 gettimeofday(&now, &tzp);
1306 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
1311 free_bogie_after_lookup(sonar_sensor_data *ssd, sonar_bogie **sbp,
1314 ping_bogie *pb = (ping_bogie *)(*sb)->closure;
1317 pb->lookup_addr = NULL; /* Prevent double-free in sonar_free_bogie. */
1318 sonar_free_bogie (ssd, *sb);
1323 /* Pings the next bogie, if it's time.
1324 Returns all outstanding ping replies.
1326 static sonar_bogie *
1327 ping_scan (sonar_sensor_data *ssd)
1329 ping_data *pd = (ping_data *) ssd->closure;
1330 double now = double_time();
1331 double ping_cycle = 10; /* re-ping a given host every 10 seconds */
1332 double ping_interval = ping_cycle / pd->target_count;
1334 if (now > pd->last_ping_time + ping_interval) /* time to ping someone */
1336 struct sonar_bogie **sbp;
1338 if (pd->last_pinged)
1340 sbp = &pd->last_pinged->next;
1348 /* Aaaaand we're out of bogies. */
1349 pd->last_pinged = NULL;
1352 sonar_bogie *sb = *sbp;
1353 ping_bogie *pb = (ping_bogie *)sb->closure;
1354 if (pb->lookup_addr &&
1355 async_addr_from_name_is_done (pb->lookup_addr))
1357 if (async_addr_from_name_finish (pb->lookup_addr, &pb->address,
1358 &pb->addrlen, NULL))
1360 char *fallback = pb->fallback;
1361 pb->fallback = NULL;
1364 fprintf (stderr, "%s: could not resolve host: %s\n",
1365 progname, sb->name);
1367 free_bogie_after_lookup (ssd, sbp, &sb);
1369 /* Insert the fallback bogie right where the old one was. */
1372 sonar_bogie *new_bogie = bogie_for_host (ssd, fallback,
1374 new_bogie->next = *sbp;
1376 if (! ((ping_bogie *)new_bogie->closure)->lookup_addr &&
1377 ! find_duplicate_host(pd, &pd->targets, new_bogie))
1380 sonar_free_bogie (ssd, new_bogie);
1387 if (pd->debug_p > 1)
1389 fprintf (stderr, "%s: %s => ", progname, sb->name);
1390 print_address (stderr, 0, &pb->address, pb->addrlen);
1394 if (! is_address_ok (pd->debug_p, sb))
1395 free_bogie_after_lookup (ssd, sbp, &sb);
1396 else if (find_duplicate_host (pd, &pd->targets, sb))
1397 /* Tricky: find_duplicate_host skips the current bogie when
1398 scanning the targets list because pb->lookup_addr hasn't
1401 Not that it matters much, but behavior here is to
1402 keep the existing address.
1404 free_bogie_after_lookup (ssd, sbp, &sb);
1408 pb->lookup_addr = NULL;
1411 if (sb && !pb->lookup_addr)
1413 assert (pb->addrlen);
1415 pd->last_pinged = sb;
1419 pd->last_ping_time = now;
1422 return get_ping (ssd);
1426 /* Returns a list of hosts to ping based on the "-ping" argument.
1428 static sonar_bogie *
1429 parse_mode (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
1430 const char *ping_arg, Bool ping_works_p)
1432 ping_data *pd = (ping_data *) ssd->closure;
1433 char *source, *token, *end, dummy;
1434 sonar_bogie *hostlist = 0;
1435 const char *fallback = "subnet";
1439 if (fallback && (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default")))
1440 source = strdup(fallback);
1442 source = strdup(ping_arg);
1447 end = source + strlen(source);
1451 sonar_bogie *new = 0;
1455 unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
1460 *next != ',' && *next != ' ' && *next != '\t' && *next != '\n';
1467 fprintf (stderr, "%s: parsing %s\n", progname, token);
1471 *error_ret = strdup ("Sonar must be setuid to ping!\n"
1472 "Running simulation instead.");
1476 if ((4 == sscanf (token, "%u.%u.%u/%u %c", &n0,&n1,&n2, &m,&d)) ||
1477 (5 == sscanf (token, "%u.%u.%u.%u/%u %c", &n0,&n1,&n2,&n3,&m,&d)))
1479 /* subnet: A.B.C.D/M
1482 unsigned long ip = pack_addr (n0, n1, n2, n3);
1483 new = subnet_hosts (ssd, error_ret, desc_ret, ip, m);
1485 else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d))
1489 new = bogie_for_host (ssd, token, NULL);
1491 else if (!strcmp (token, "subnet"))
1493 new = subnet_hosts (ssd, error_ret, desc_ret, 0, 24);
1495 else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
1497 new = subnet_hosts (ssd, error_ret, desc_ret, 0, m);
1499 else if (*token == '.' || *token == '/' ||
1500 *token == '$' || *token == '~')
1503 new = read_hosts_file (ssd, token);
1505 if (pd->debug_p) fprintf (stderr, "%s: skipping file\n", progname);
1509 else if (!stat (token, &st))
1511 new = read_hosts_file (ssd, token);
1513 # endif /* READ_FILES */
1516 /* not an existant file - must be a host name
1518 new = bogie_for_host (ssd, token, NULL);
1523 sonar_bogie *nn = new;
1526 nn->next = hostlist;
1531 while (token < end &&
1532 (*token == ',' || *token == ' ' ||
1533 *token == '\t' || *token == '\n'))
1539 /* If the arg was completely unparsable, fall back to the local subnet.
1540 This happens if the default is "/etc/hosts" but READ_FILES is off.
1541 Or if we're on a /31 network, in which case we try twice then fail.
1543 if (!hostlist && fallback)
1546 fprintf (stderr, "%s: no hosts parsed! Trying %s\n",
1547 progname, fallback);
1548 ping_arg = fallback;
1558 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
1559 const char *subnet, int timeout,
1560 Bool resolve_p, Bool times_p, Bool debug_p)
1562 /* Important! Do not return from this function without disavowing privileges
1563 with setuid(getuid()).
1565 sonar_sensor_data *ssd = (sonar_sensor_data *) calloc (1, sizeof(*ssd));
1566 ping_data *pd = (ping_data *) calloc (1, sizeof(*pd));
1570 Bool socket_initted_p = False;
1571 Bool socket_raw_p = False;
1575 pd->resolve_p = resolve_p;
1576 pd->times_p = times_p;
1577 pd->debug_p = debug_p;
1580 ssd->scan_cb = ping_scan;
1581 ssd->free_data_cb = ping_free_data;
1582 ssd->free_bogie_cb = ping_free_bogie_data;
1584 /* Get short version number. */
1585 s = strchr (screensaver_id, ' ');
1586 pd->version = strdup (s+1);
1587 s = strchr (pd->version, ' ');
1591 /* Create the ICMP socket. Do this before dropping privs.
1593 Raw sockets can only be opened by root (or setuid root), so we
1594 only try to do this when the effective uid is 0.
1596 We used to just always try, and notice the failure. But apparently
1597 that causes "SELinux" to log spurious warnings when running with the
1598 "strict" policy. So to avoid that, we just don't try unless we
1601 On MacOS X, we can avoid the whole problem by using a
1602 non-privileged datagram instead of a raw socket.
1604 if (global_icmpsock)
1606 pd->icmpsock = global_icmpsock;
1607 socket_initted_p = True;
1609 fprintf (stderr, "%s: re-using icmp socket\n", progname);
1612 else if ((pd->icmpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) >= 0)
1614 socket_initted_p = True;
1616 else if (geteuid() == 0 &&
1617 (pd->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) >= 0)
1619 socket_initted_p = True;
1620 socket_raw_p = True;
1623 if (socket_initted_p)
1625 global_icmpsock = pd->icmpsock;
1626 socket_initted_p = True;
1628 fprintf (stderr, "%s: opened %s icmp socket\n", progname,
1629 (socket_raw_p ? "raw" : "dgram"));
1632 fprintf (stderr, "%s: unable to open icmp socket\n", progname);
1637 pd->pid = getpid() & 0xFFFF;
1639 pd->timeout = timeout;
1641 /* Generate a list of targets */
1643 pd->targets = parse_mode (ssd, error_ret, desc_ret, subnet,
1645 pd->targets = delete_duplicate_hosts (ssd, pd->targets);
1649 fprintf (stderr, "%s: Target list:\n", progname);
1650 for (b = pd->targets; b; b = b->next)
1652 fprintf (stderr, "%s: ", progname);
1653 print_host (stderr, b);
1657 /* Make sure there is something to ping */
1659 pd->target_count = 0;
1660 for (b = pd->targets; b; b = b->next)
1663 if (pd->target_count == 0)
1666 *error_ret = strdup ("No hosts to ping!\n"
1667 "Simulating instead.");
1668 if (pd) ping_free_data (ssd, pd);
1669 if (ssd) free (ssd);
1673 /* Distribute them evenly around the display field, clockwise.
1674 Even on a /24, allocated IPs tend to cluster together, so
1675 don't put any two hosts closer together than N degrees to
1676 avoid unnecessary overlap when we have plenty of space due
1677 to addresses that probably won't respond. And don't spread
1678 them out too far apart, because that looks too symmetrical
1679 when there are a small number of hosts.
1682 double th = frand(M_PI);
1683 double sep = 360.0 / pd->target_count;
1684 if (sep < 23) sep = 23;
1685 if (sep > 43) sep = 43;
1687 for (b = pd->targets; b; b = b->next) {
1696 #endif /* HAVE_PING -- whole file */