-/* sonar, Copyright (c) 1998-2009 Jamie Zawinski and Stephen Martin
+/* sonar, Copyright (c) 1998-2016 Jamie Zawinski and Stephen Martin
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
#include "screenhackI.h"
#include "sonar.h"
#include "version.h"
+#include "async_netdb.h"
#undef usleep /* conflicts with unistd.h on OSX */
+#ifdef USE_IPHONE
+ /* Note: to get this to compile for iPhone, you need to fix Xcode!
+ The icmp headers exist for the simulator build environment, but
+ not for the real-device build environment. This appears to
+ just be an Apple bug, not intentional.
+
+ xc=/Applications/Xcode.app/Contents
+ for path in /Developer/Platforms/iPhone*?/Developer/SDKs/?* \
+ $xc/Developer/Platforms/iPhone*?/Developer/SDKs/?* ; do
+ for file in \
+ /usr/include/netinet/ip.h \
+ /usr/include/netinet/in_systm.h \
+ /usr/include/netinet/ip_icmp.h \
+ /usr/include/netinet/ip_var.h \
+ /usr/include/netinet/udp.h
+ do
+ ln -s "$file" "$path$file"
+ done
+ done
+ */
+#endif
+
+#ifndef HAVE_MOBILE
+# define READ_FILES
+#endif
+
#if defined(HAVE_ICMP) || defined(HAVE_ICMPHDR)
# include <unistd.h>
# include <sys/stat.h>
# include <sys/types.h>
# include <sys/time.h>
# include <sys/ipc.h>
-# include <sys/shm.h>
+# ifndef HAVE_ANDROID
+# include <sys/shm.h>
+# endif
# include <sys/socket.h>
# include <netinet/in_systm.h>
# include <netinet/in.h>
# include <netinet/udp.h>
# include <arpa/inet.h>
# include <netdb.h>
+# include <errno.h>
+# ifdef HAVE_GETIFADDRS
+# include <ifaddrs.h>
+# endif
#endif /* HAVE_ICMP || HAVE_ICMPHDR */
#if defined(HAVE_ICMP)
# undef HAVE_PING
#endif
+#ifndef HAVE_MOBILE
+# define LOAD_FILES
+#endif
+
#ifndef HAVE_PING
sonar_sensor_data *
-init_ping (Display *dpy, const char *subnet, int timeout,
- Bool resolve_p, Bool times_p, Bool debug_p)
+sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
+ const char *subnet, int timeout,
+ Bool resolve_p, Bool times_p, Bool debug_p)
{
if (! (!subnet || !*subnet || !strcmp(subnet, "default")))
fprintf (stderr, "%s: not compiled with support for pinging hosts.\n",
typedef struct {
+ Display *dpy; /* Only used to get *useThreads. */
+
char *version; /* short version number of xscreensaver */
int icmpsock; /* socket for sending pings */
int pid; /* our process ID */
} ping_data;
typedef struct {
- struct sockaddr address; /* ip address */
+ async_name_from_addr_t lookup_name;
+ async_addr_from_name_t lookup_addr;
+ async_netdb_sockaddr_storage_t address; /* ip address */
+ socklen_t addrlen;
+ char *fallback;
} ping_bogie;
-/* If resolves the bogie's name (either a hostname or ip address string)
- to a hostent. Returns 1 if successful, 0 if it failed to resolve.
+/* Resolves the bogie's name (either a hostname or ip address string)
+ to a hostent. Returns 1 if successful, 0 if something went wrong.
*/
static int
resolve_bogie_hostname (ping_data *pd, sonar_bogie *sb, Bool resolve_p)
{
ping_bogie *pb = (ping_bogie *) sb->closure;
- struct hostent *hent;
- struct sockaddr_in *iaddr;
unsigned int ip[4];
char c;
- iaddr = (struct sockaddr_in *) &(pb->address);
- iaddr->sin_family = AF_INET;
-
if (4 == sscanf (sb->name, " %u.%u.%u.%u %c",
&ip[0], &ip[1], &ip[2], &ip[3], &c))
{
/* It's an IP address.
*/
+ struct sockaddr_in *iaddr = (struct sockaddr_in *) &(pb->address);
+
if (ip[3] == 0)
{
if (pd->debug_p > 1)
return 0;
}
+ iaddr->sin_family = AF_INET;
iaddr->sin_addr.s_addr = pack_addr (ip[0], ip[1], ip[2], ip[3]);
- if (resolve_p)
- hent = gethostbyaddr ((const char *) &iaddr->sin_addr.s_addr,
- sizeof(iaddr->sin_addr.s_addr),
- AF_INET);
- else
- hent = 0;
+ pb->addrlen = sizeof(struct sockaddr_in);
- if (pd->debug_p > 1)
- fprintf (stderr, "%s: %s => %s\n",
- progname, sb->name,
- ((hent && hent->h_name && *hent->h_name)
- ? hent->h_name : "<unknown>"));
-
- if (hent && hent->h_name && *hent->h_name)
- sb->name = strdup (hent->h_name);
+ if (resolve_p)
+ {
+ pb->lookup_name =
+ async_name_from_addr_start (pd->dpy,
+ (const struct sockaddr *)&pb->address,
+ pb->addrlen);
+ if (!pb->lookup_name)
+ {
+ fprintf (stderr, "%s: unable to start host resolution.\n",
+ progname);
+ }
+ }
}
else
{
return 0;
}
- hent = gethostbyname (sb->name);
- if (!hent)
+ pb->lookup_addr = async_addr_from_name_start(pd->dpy, sb->name);
+ if (!pb->lookup_addr)
{
if (pd->debug_p)
- fprintf (stderr, "%s: could not resolve host: %s\n",
- progname, sb->name);
+ /* Either address space exhaustion or RAM exhaustion. */
+ fprintf (stderr, "%s: unable to start host resolution.\n",
+ progname);
return 0;
}
+ }
+ return 1;
+}
+
+
+static void
+print_address (FILE *out, int width, const void *sockaddr, socklen_t addrlen)
+{
+#ifdef HAVE_GETADDRINFO
+ char buf[NI_MAXHOST];
+#else
+ char buf[50];
+#endif
- memcpy (&iaddr->sin_addr, hent->h_addr_list[0],
- sizeof(iaddr->sin_addr));
+ const struct sockaddr *addr = (const struct sockaddr *)sockaddr;
+ const char *ips = buf;
- if (pd->debug_p > 1)
+ if (!addr->sa_family)
+ ips = "<no address>";
+ else
+ {
+#ifdef HAVE_GETADDRINFO
+ int gai_error = getnameinfo (sockaddr, addrlen, buf, sizeof(buf),
+ NULL, 0, NI_NUMERICHOST);
+ if (gai_error == EAI_SYSTEM)
+ ips = strerror(errno);
+ else if (gai_error)
+ ips = gai_strerror(gai_error);
+#else
+ switch (addr->sa_family)
{
- unsigned int a, b, c, d;
- unpack_addr (iaddr->sin_addr.s_addr, &a, &b, &c, &d);
- fprintf (stderr, "%s: %s => %d.%d.%d.%d\n",
- progname, sb->name, a, b, c, d);
+ case AF_INET:
+ {
+ u_long ip = ((struct sockaddr_in *)sockaddr)->sin_addr.s_addr;
+ unsigned int a, b, c, d;
+ unpack_addr (ip, &a, &b, &c, &d); /* ip is in network order */
+ sprintf (buf, "%u.%u.%u.%u", a, b, c, d);
+ }
+ break;
+ default:
+ ips = "<unknown>";
+ break;
}
+#endif
}
- return 1;
+
+ fprintf (out, "%-*s", width, ips);
}
static void
-print_host (FILE *out, unsigned long ip, const char *name)
+print_host (FILE *out, const sonar_bogie *sb)
{
- char ips[50];
- unsigned int a, b, c, d;
- unpack_addr (ip, &a, &b, &c, &d); /* ip is in network order */
- sprintf (ips, "%u.%u.%u.%u", a, b, c, d);
+ const ping_bogie *pb = (const ping_bogie *) sb->closure;
+ const char *name = sb->name;
if (!name || !*name) name = "<unknown>";
- fprintf (out, "%-16s %s\n", ips, name);
+ print_address (out, 16, &pb->address, pb->addrlen);
+ fprintf (out, " %s\n", name);
+}
+
+
+static Bool
+is_address_ok(Bool debug_p, const sonar_bogie *b)
+{
+ const ping_bogie *pb = (const ping_bogie *) b->closure;
+ const struct sockaddr *addr = (const struct sockaddr *)&pb->address;
+
+ switch (addr->sa_family)
+ {
+ case AF_INET:
+ {
+ struct sockaddr_in *iaddr = (struct sockaddr_in *) addr;
+
+ /* Don't ever use loopback (127.0.0.x) hosts */
+ unsigned long ip = iaddr->sin_addr.s_addr;
+ if ((ntohl (ip) & 0xFFFFFF00L) == 0x7f000000L) /* 127.0.0.x */
+ {
+ if (debug_p)
+ fprintf (stderr, "%s: ignoring loopback host %s\n",
+ progname, b->name);
+ return False;
+ }
+
+ /* Don't ever use broadcast (255.x.x.x) hosts */
+ if ((ntohl (ip) & 0xFF000000L) == 0xFF000000L) /* 255.x.x.x */
+ {
+ if (debug_p)
+ fprintf (stderr, "%s: ignoring broadcast host %s\n",
+ progname, b->name);
+ return False;
+ }
+ }
+
+ break;
+ }
+
+ return True;
}
-/* Create a sonar_bogie a host name or ip address string.
+/* Create a sonar_bogie from a host name or ip address string.
Returns NULL if the name could not be resolved.
*/
static sonar_bogie *
-bogie_for_host (sonar_sensor_data *ssd, const char *name, Bool resolve_p)
+bogie_for_host (sonar_sensor_data *ssd, const char *name, const char *fallback)
{
ping_data *pd = (ping_data *) ssd->closure;
sonar_bogie *b = (sonar_bogie *) calloc (1, sizeof(*b));
ping_bogie *pb = (ping_bogie *) calloc (1, sizeof(*pb));
- struct sockaddr_in *iaddr;
- unsigned long ip;
+ Bool resolve_p = pd->resolve_p;
b->name = strdup (name);
b->closure = pb;
if (! resolve_bogie_hostname (pd, b, resolve_p))
goto FAIL;
- iaddr = (struct sockaddr_in *) &(pb->address);
-
- /* Don't ever use loopback (127.0.0.x) hosts */
- ip = iaddr->sin_addr.s_addr;
- if ((ntohl (ip) & 0xFFFFFF00L) == 0x7f000000L) /* 127.0.0.x */
- {
- if (pd->debug_p)
- fprintf (stderr, "%s: ignoring loopback host %s\n",
- progname, b->name);
- goto FAIL;
- }
-
- /* Don't ever use broadcast (255.x.x.x) hosts */
- if ((ntohl (ip) & 0xFF000000L) == 0xFF000000L) /* 255.x.x.x */
- {
- if (pd->debug_p)
- fprintf (stderr, "%s: ignoring broadcast host %s\n",
- progname, b->name);
- goto FAIL;
- }
+ if (! pb->lookup_addr && ! is_address_ok (pd->debug_p, b))
+ goto FAIL;
if (pd->debug_p > 1)
{
fprintf (stderr, "%s: added ", progname);
- print_host (stderr, ip, b->name);
+ print_host (stderr, b);
}
+ if (fallback)
+ pb->fallback = strdup (fallback);
return b;
FAIL:
- if (b) free_bogie (ssd, b);
+ if (b) sonar_free_bogie (ssd, b);
+
+ if (fallback)
+ return bogie_for_host (ssd, fallback, NULL);
+
return 0;
}
+#ifdef READ_FILES
+
/* Return a list of bogies read from a file.
The file can be like /etc/hosts or .ssh/known_hosts or probably
just about anything that has host names in it.
{
char buf[1024];
sprintf(buf, "%s: %s", progname, filename);
-#ifdef HAVE_COCOA
+#ifdef HAVE_JWXYZ
if (pd->debug_p) /* on OSX don't syslog this */
#endif
perror (buf);
/* Create a new target using first the name then the address */
- new = 0;
- if (name)
- new = bogie_for_host (ssd, name, pd->resolve_p);
- if (!new && addr)
- new = bogie_for_host (ssd, addr, pd->resolve_p);
+ if (!name)
+ {
+ name = addr;
+ addr = NULL;
+ }
+
+ new = bogie_for_host (ssd, name, addr);
if (new)
{
fclose(fp);
return list;
}
+#endif /* READ_FILES */
+
+
+static sonar_bogie **
+found_duplicate_host (const ping_data *pd, sonar_bogie **list,
+ sonar_bogie *bogie)
+{
+ if (pd->debug_p)
+ {
+ fprintf (stderr, "%s: deleted duplicate: ", progname);
+ print_host (stderr, bogie);
+ }
+
+ return list;
+}
+
+
+static sonar_bogie **
+find_duplicate_host (const ping_data *pd, sonar_bogie **list,
+ sonar_bogie *bogie)
+{
+ const ping_bogie *pb = (const ping_bogie *) bogie->closure;
+ const struct sockaddr *addr1 = (const struct sockaddr *) &(pb->address);
+
+ while(*list)
+ {
+ const ping_bogie *pb2 = (const ping_bogie *) (*list)->closure;
+
+ if (!pb2->lookup_addr)
+ {
+ const struct sockaddr *addr2 =
+ (const struct sockaddr *) &(pb2->address);
+
+ if (addr1->sa_family == addr2->sa_family)
+ {
+ switch (addr1->sa_family)
+ {
+ case AF_INET:
+ {
+ unsigned long ip1 =
+ ((const struct sockaddr_in *)addr1)->sin_addr.s_addr;
+ const struct sockaddr_in *i2 =
+ (const struct sockaddr_in *)addr2;
+ unsigned long ip2 = i2->sin_addr.s_addr;
+
+ if (ip1 == ip2)
+ return found_duplicate_host (pd, list, bogie);
+ }
+ break;
+#ifdef AF_INET6
+ case AF_INET6:
+ {
+ if (! memcmp(
+ &((const struct sockaddr_in6 *)addr1)->sin6_addr,
+ &((const struct sockaddr_in6 *)addr2)->sin6_addr,
+ 16))
+ return found_duplicate_host (pd, list, bogie);
+ }
+ break;
+#endif
+ default:
+ {
+ /* Fallback behavior: Just memcmp the two addresses.
+
+ For this to work, unused space in the sockaddr must be
+ set to zero. Which may actually be the case:
+ - async_addr_from_name_finish won't put garbage into
+ sockaddr_in.sin_zero or elsewhere unless getaddrinfo
+ does.
+ - ping_bogie is allocated with calloc(). */
+
+ if (pb->addrlen == pb2->addrlen &&
+ ! memcmp(addr1, addr2, pb->addrlen))
+ return found_duplicate_host (pd, list, bogie);
+ }
+ break;
+ }
+ }
+ }
+
+ list = &(*list)->next;
+ }
+
+ return NULL;
+}
static sonar_bogie *
{
ping_data *pd = (ping_data *) ssd->closure;
sonar_bogie *head = list;
- sonar_bogie *sb;
+ sonar_bogie *sb = head;
- for (sb = head; sb; sb = sb->next)
+ while (sb)
{
ping_bogie *pb = (ping_bogie *) sb->closure;
- struct sockaddr_in *i1 = (struct sockaddr_in *) &(pb->address);
- unsigned long ip1 = i1->sin_addr.s_addr;
- sonar_bogie *sb2;
- for (sb2 = sb; sb2; sb2 = sb2->next)
+ if (!pb->lookup_addr)
{
- if (sb2 && sb2->next)
- {
- ping_bogie *pb2 = (ping_bogie *) sb2->next->closure;
- struct sockaddr_in *i2 = (struct sockaddr_in *) &(pb2->address);
- unsigned long ip2 = i2->sin_addr.s_addr;
-
- if (ip1 == ip2)
- {
- if (pd->debug_p)
- {
- fprintf (stderr, "%s: deleted duplicate: ", progname);
- print_host (stderr, ip2, sb2->next->name);
- }
- sb2->next = sb2->next->next;
- /* #### sb leaked */
- }
- }
+ sonar_bogie **sb2 = find_duplicate_host (pd, &sb->next, sb);
+ if (sb2)
+ *sb2 = (*sb2)->next;
+ /* #### sb leaked */
+ else
+ sb = sb->next;
}
+ else
+ sb = sb->next;
}
return head;
}
+static unsigned int
+width_mask (int width)
+{
+ unsigned int m = 0;
+ int i;
+ for (i = 0; i < width; i++)
+ m |= (1L << (31-i));
+ return m;
+}
+
+
+#ifdef HAVE_GETIFADDRS
+static int
+mask_width (unsigned int mask)
+{
+ int i;
+ for (i = 0; i < 32; i++)
+ if (mask & (1 << i))
+ break;
+ return 32-i;
+}
+#endif
+
+
/* Generate a list of bogies consisting of all of the entries on
the same subnet. 'base' ip is in network order; 0 means localhost.
*/
static sonar_bogie *
-subnet_hosts (sonar_sensor_data *ssd, char **error_ret,
+subnet_hosts (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
unsigned long n_base, int subnet_width)
{
ping_data *pd = (ping_data *) ssd->closure;
unsigned long h_mask; /* host order */
unsigned long h_base; /* host order */
-
- /* Local Variables */
-
- char hostname[BUFSIZ];
char address[BUFSIZ];
- struct hostent *hent;
char *p;
int i;
sonar_bogie *new;
if (pd->debug_p)
fprintf (stderr, "%s: adding %d-bit subnet\n", progname, subnet_width);
- /* Get our hostname */
- if (gethostname(hostname, BUFSIZ))
+ if (! n_base)
{
- *error_ret = strdup ("Unable to determine\n"
- "local host name!");
- return 0;
- }
+# ifdef HAVE_GETIFADDRS
- /* Get our IP address and convert it to a string */
+ /* To determine the local subnet, we need to know the local IP address.
+ Do this by looking at the IPs of every network interface.
+ */
+ struct in_addr in = { 0, };
+ struct ifaddrs *all = 0, *ifa;
- if (! (hent = gethostbyname(hostname)))
- {
- sprintf(buf,
- "Unable to resolve\n"
- "local host \"%s\"",
- hostname);
- *error_ret = strdup(buf);
- return 0;
+ if (pd->debug_p)
+ fprintf (stderr, "%s: listing network interfaces\n", progname);
+
+ getifaddrs (&all);
+ for (ifa = all; ifa; ifa = ifa->ifa_next)
+ {
+ struct in_addr in2;
+ unsigned long mask;
+ if (ifa->ifa_addr->sa_family != AF_INET)
+ {
+ if (pd->debug_p)
+ fprintf (stderr, "%s: if: %4s: %s\n", progname,
+ ifa->ifa_name,
+ (
+# ifdef AF_UNIX
+ ifa->ifa_addr->sa_family == AF_UNIX ? "local" :
+# endif
+# ifdef AF_LINK
+ ifa->ifa_addr->sa_family == AF_LINK ? "link" :
+# endif
+# ifdef AF_INET6
+ ifa->ifa_addr->sa_family == AF_INET6 ? "ipv6" :
+# endif
+ "other"));
+ continue;
+ }
+ in2 = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
+ mask = ntohl (((struct sockaddr_in *) ifa->ifa_netmask)
+ ->sin_addr.s_addr);
+ if (pd->debug_p)
+ fprintf (stderr, "%s: if: %4s: inet = %s /%d 0x%08lx\n",
+ progname,
+ ifa->ifa_name,
+ inet_ntoa (in2),
+ mask_width (mask),
+ mask);
+ if (in2.s_addr == 0x0100007f || /* 127.0.0.1 in network order */
+ mask == 0)
+ continue;
+
+ /* At least on the AT&T 3G network, pinging either of the two
+ hosts on a /31 network doesn't work, so don't try.
+ */
+ if (mask_width (mask) == 31)
+ {
+ sprintf (buf,
+ "Can't ping subnet:\n"
+ "local network is\n"
+ "%.100s/%d,\n"
+ "a p2p bridge\n"
+ "on if %.100s.",
+ inet_ntoa (in2), mask_width (mask), ifa->ifa_name);
+ if (*error_ret) free (*error_ret);
+ *error_ret = strdup (buf);
+ continue;
+ }
+
+ in = in2;
+ subnet_width = mask_width (mask);
+ }
+
+ if (in.s_addr)
+ {
+ if (*error_ret) free (*error_ret);
+ *error_ret = 0;
+ n_base = in.s_addr; /* already in network order, I think? */
+ }
+ else if (!*error_ret)
+ *error_ret = strdup ("Unable to determine\nlocal IP address\n");
+
+ if (all)
+ freeifaddrs (all);
+
+ if (*error_ret)
+ return 0;
+
+# else /* !HAVE_GETIFADDRS */
+
+ /* If we can't walk the list of network interfaces to figure out
+ our local IP address, try to do it by finding the local host
+ name, then resolving that.
+ */
+ char hostname[BUFSIZ];
+ struct hostent *hent = 0;
+
+ if (gethostname(hostname, BUFSIZ))
+ {
+ *error_ret = strdup ("Unable to determine\n"
+ "local host name!");
+ return 0;
+ }
+
+ /* Get our IP address and convert it to a string */
+
+ hent = gethostbyname(hostname);
+ if (! hent)
+ {
+ strcat (hostname, ".local"); /* Necessary on iphone */
+ hent = gethostbyname(hostname);
+ }
+
+ if (! hent)
+ {
+ sprintf(buf,
+ "Unable to resolve\n"
+ "local host \"%.100s\"",
+ hostname);
+ *error_ret = strdup(buf);
+ return 0;
+ }
+
+ strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
+ n_base = pack_addr (hent->h_addr_list[0][0],
+ hent->h_addr_list[0][1],
+ hent->h_addr_list[0][2],
+ hent->h_addr_list[0][3]);
+
+ if (n_base == 0x0100007f) /* 127.0.0.1 in network order */
+ {
+ unsigned int a, b, c, d;
+ unpack_addr (n_base, &a, &b, &c, &d);
+ sprintf (buf,
+ "Unable to determine\n"
+ "local subnet address:\n"
+ "\"%.100s\"\n"
+ "resolves to\n"
+ "loopback address\n"
+ "%u.%u.%u.%u.",
+ hostname, a, b, c, d);
+ *error_ret = strdup(buf);
+ return 0;
+ }
+
+# endif /* !HAVE_GETIFADDRS */
}
- strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
- /* Construct targets for all addresses in this subnet */
- h_mask = 0;
- for (i = 0; i < subnet_width; i++)
- h_mask |= (1L << (31-i));
+ /* Construct targets for all addresses in this subnet */
- /* If no base IP specified, assume localhost. */
- if (n_base == 0)
- n_base = pack_addr (hent->h_addr_list[0][0],
- hent->h_addr_list[0][1],
- hent->h_addr_list[0][2],
- hent->h_addr_list[0][3]);
+ h_mask = width_mask (subnet_width);
h_base = ntohl (n_base);
- if (h_base == 0x7F000001L) /* 127.0.0.1 in host order */
- {
- unsigned int a, b, c, d;
- unpack_addr (n_base, &a, &b, &c, &d);
- sprintf (buf,
- "Unable to determine\n"
- "local subnet address:\n"
- "\"%s\"\n"
- "resolves to\n"
- "loopback address\n"
- "%u.%u.%u.%u.",
- hostname, a, b, c, d);
- *error_ret = strdup(buf);
- return 0;
- }
+ if (desc_ret && !*desc_ret) {
+ char buf[255];
+ unsigned int a, b, c, d;
+ unsigned long bb = n_base & htonl(h_mask);
+ unpack_addr (bb, &a, &b, &c, &d);
+ if (subnet_width > 24)
+ sprintf (buf, "%u.%u.%u.%u/%d", a, b, c, d, subnet_width);
+ else
+ sprintf (buf, "%u.%u.%u/%d", a, b, c, subnet_width);
+ *desc_ret = strdup (buf);
+ }
for (i = 255; i >= 0; i--) {
unsigned int a, b, c, d;
int ip = (h_base & 0xFFFFFF00L) | i; /* host order */
- if ((ip & h_mask) != (h_base & h_mask)) /* not in mask range at all */
+ if ((ip & h_mask) != (h_base & h_mask)) /* skip out-of-subnet host */
continue;
- if ((ip & ~h_mask) == 0) /* broadcast address */
+ else if (subnet_width == 31) /* 1-bit bridge: 2 hosts */
+ ;
+ else if ((ip & ~h_mask) == 0) /* skip network address */
continue;
- if ((ip & ~h_mask) == ~h_mask) /* broadcast address */
+ else if ((ip & ~h_mask) == ~h_mask) /* skip broadcast address */
continue;
unpack_addr (htonl (ip), &a, &b, &c, &d);
p = address + strlen(address) + 1;
sprintf(p, "%d", i);
- new = bogie_for_host (ssd, address, pd->resolve_p);
+ new = bogie_for_host (ssd, address, NULL);
if (new)
{
new->next = list;
u_char *packet;
struct ICMP *icmph;
const char *token = "org.jwz.xscreensaver.sonar";
+ char *host_id;
int pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) +
- strlen(b->name) + 1 +
+ sizeof(socklen_t) + pb->addrlen +
strlen(token) + 1 +
strlen(pd->version) + 1);
gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]);
# endif
- /* We store the name of the host we're pinging in the packet, and parse
+ /* We store the sockaddr of the host we're pinging in the packet, and parse
that out of the return packet later (see get_ping() for why).
After that, we also include the name and version of this program,
just to give a clue to anyone sniffing and wondering what's up.
*/
- sprintf ((char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)],
- "%s%c%s %s",
- b->name, 0, token, pd->version);
+ host_id = (char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)];
+ *(socklen_t *)host_id = pb->addrlen;
+ host_id += sizeof(socklen_t);
+ memcpy(host_id, &pb->address, pb->addrlen);
+ host_id += pb->addrlen;
+ sprintf (host_id, "%.20s %.20s", token, pd->version);
ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
/* Send it */
if (sendto(pd->icmpsock, packet, pcktsiz, 0,
- &pb->address, sizeof(pb->address))
+ (struct sockaddr *)&pb->address, sizeof(pb->address))
!= pcktsiz)
{
#if 0
char buf[BUFSIZ];
- sprintf(buf, "%s: pinging %s", progname, b->name);
+ sprintf(buf, "%s: pinging %.100s", progname, b->name);
perror(buf);
#endif
}
static sonar_bogie *
copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
{
- sonar_bogie *b2 = copy_bogie (ssd, b);
+ sonar_bogie *b2 = sonar_copy_bogie (ssd, b);
if (b->closure)
{
ping_bogie *pb = (ping_bogie *) b->closure;
{
ping_data *pd = (ping_data *) ssd->closure;
struct sockaddr from;
- unsigned int fromlen; /* Posix says socklen_t, but that's not portable */
+ socklen_t fromlen;
int result;
u_char packet[1024];
struct timeval now;
pb->address, but it is possible that, in certain weird router
or NAT situations, that the reply will come back from a
different address than the one we sent it to. So instead,
- we parse the name out of the reply packet payload.
+ we parse the sockaddr out of the reply packet payload.
*/
{
- const char *name = (char *) &packet[iphdrlen +
- sizeof(struct ICMP) +
- sizeof(struct timeval)];
+ const socklen_t *host_id = (socklen_t *) &packet[
+ iphdrlen + sizeof(struct ICMP) + sizeof(struct timeval)];
+
sonar_bogie *b;
- for (b = pd->targets; b; b = b->next)
- if (!strcmp (name, b->name))
- {
- new = copy_ping_bogie (ssd, b);
- break;
- }
+
+ /* Ensure that a maliciously-crafted return packet can't
+ make us overflow in memcmp. */
+ if (result > 0 && (const u_char *)(host_id + 1) <= packet + result)
+ {
+ const u_char *host_end = (const u_char *)(host_id + 1) +
+ *host_id;
+
+ if ((const u_char *)(host_id + 1) <= host_end &&
+ host_end <= packet + result)
+ {
+ for (b = pd->targets; b; b = b->next)
+ {
+ ping_bogie *pb = (ping_bogie *)b->closure;
+ if (*host_id == pb->addrlen &&
+ !memcmp(&pb->address, host_id + 1, pb->addrlen) )
+ {
+ /* Check to see if the name lookup is done. */
+ if (pb->lookup_name &&
+ async_name_from_addr_is_done (pb->lookup_name))
+ {
+ char *host = NULL;
+
+ async_name_from_addr_finish (pb->lookup_name,
+ &host, NULL);
+
+ if (pd->debug_p > 1)
+ fprintf (stderr, "%s: %s => %s\n",
+ progname, b->name,
+ host ? host : "<unknown>");
+
+ if (host)
+ {
+ free(b->name);
+ b->name = host;
+ }
+
+ pb->lookup_name = NULL;
+ }
+
+ new = copy_ping_bogie (ssd, b);
+ break;
+ }
+ }
+ }
+ }
}
if (! new) /* not in targets? */
while (b)
{
sonar_bogie *b2 = b->next;
- free_bogie (ssd, b);
+ sonar_free_bogie (ssd, b);
b = b2;
}
free (pd);
static void
ping_free_bogie_data (sonar_sensor_data *sd, void *closure)
{
+ ping_bogie *pb = (ping_bogie *) closure;
+
+ if (pb->lookup_name)
+ async_name_from_addr_cancel (pb->lookup_name);
+ if (pb->lookup_addr)
+ async_addr_from_name_cancel (pb->lookup_addr);
+ free (pb->fallback);
+
free (closure);
}
}
-/* If a bogie is provided, pings it.
- Then, returns all outstanding ping replies.
+static void
+free_bogie_after_lookup(sonar_sensor_data *ssd, sonar_bogie **sbp,
+ sonar_bogie **sb)
+{
+ ping_bogie *pb = (ping_bogie *)(*sb)->closure;
+
+ *sbp = (*sb)->next;
+ pb->lookup_addr = NULL; /* Prevent double-free in sonar_free_bogie. */
+ sonar_free_bogie (ssd, *sb);
+ *sb = NULL;
+}
+
+
+/* Pings the next bogie, if it's time.
+ Returns all outstanding ping replies.
*/
static sonar_bogie *
ping_scan (sonar_sensor_data *ssd)
if (now > pd->last_ping_time + ping_interval) /* time to ping someone */
{
+ struct sonar_bogie **sbp;
+
if (pd->last_pinged)
- pd->last_pinged = pd->last_pinged->next;
- if (! pd->last_pinged)
- pd->last_pinged = pd->targets;
- send_ping (pd, pd->last_pinged);
+ {
+ sbp = &pd->last_pinged->next;
+ if (!*sbp)
+ sbp = &pd->targets;
+ }
+ else
+ sbp = &pd->targets;
+
+ if (!*sbp)
+ /* Aaaaand we're out of bogies. */
+ pd->last_pinged = NULL;
+ else
+ {
+ sonar_bogie *sb = *sbp;
+ ping_bogie *pb = (ping_bogie *)sb->closure;
+ if (pb->lookup_addr &&
+ async_addr_from_name_is_done (pb->lookup_addr))
+ {
+ if (async_addr_from_name_finish (pb->lookup_addr, &pb->address,
+ &pb->addrlen, NULL))
+ {
+ char *fallback = pb->fallback;
+ pb->fallback = NULL;
+
+ if (pd->debug_p)
+ fprintf (stderr, "%s: could not resolve host: %s\n",
+ progname, sb->name);
+
+ free_bogie_after_lookup (ssd, sbp, &sb);
+
+ /* Insert the fallback bogie right where the old one was. */
+ if (fallback)
+ {
+ sonar_bogie *new_bogie = bogie_for_host (ssd, fallback,
+ NULL);
+ new_bogie->next = *sbp;
+
+ if (! ((ping_bogie *)new_bogie->closure)->lookup_addr &&
+ ! find_duplicate_host(pd, &pd->targets, new_bogie))
+ *sbp = new_bogie;
+ else
+ sonar_free_bogie (ssd, new_bogie);
+
+ free (fallback);
+ }
+ }
+ else
+ {
+ if (pd->debug_p > 1)
+ {
+ fprintf (stderr, "%s: %s => ", progname, sb->name);
+ print_address (stderr, 0, &pb->address, pb->addrlen);
+ putc('\n', stderr);
+ }
+
+ if (! is_address_ok (pd->debug_p, sb))
+ free_bogie_after_lookup (ssd, sbp, &sb);
+ else if (find_duplicate_host (pd, &pd->targets, sb))
+ /* Tricky: find_duplicate_host skips the current bogie when
+ scanning the targets list because pb->lookup_addr hasn't
+ been NULL'd yet.
+
+ Not that it matters much, but behavior here is to
+ keep the existing address.
+ */
+ free_bogie_after_lookup (ssd, sbp, &sb);
+ }
+
+ if (sb)
+ pb->lookup_addr = NULL;
+ }
+
+ if (sb && !pb->lookup_addr)
+ {
+ if (!pb->addrlen) abort();
+ send_ping (pd, sb);
+ pd->last_pinged = sb;
+ }
+ }
+
pd->last_ping_time = now;
}
/* Returns a list of hosts to ping based on the "-ping" argument.
*/
static sonar_bogie *
-parse_mode (sonar_sensor_data *ssd, char **error_ret,
+parse_mode (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
const char *ping_arg, Bool ping_works_p)
{
ping_data *pd = (ping_data *) ssd->closure;
char *source, *token, *end, dummy;
sonar_bogie *hostlist = 0;
+ const char *fallback = "subnet";
- if (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default"))
- source = strdup("subnet/28");
- else
+ AGAIN:
+
+ if (fallback && (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default")))
+ source = strdup(fallback);
+ else if (ping_arg)
source = strdup(ping_arg);
+ else
+ return 0;
token = source;
end = source + strlen(source);
while (token < end)
{
char *next;
- sonar_bogie *new;
+ sonar_bogie *new = 0;
+# ifdef READ_FILES
struct stat st;
+# endif
unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
char d;
subnet: A.B.C/M
*/
unsigned long ip = pack_addr (n0, n1, n2, n3);
- new = subnet_hosts (ssd, error_ret, ip, m);
+ new = subnet_hosts (ssd, error_ret, desc_ret, ip, m);
}
else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d))
{
/* IP: A.B.C.D
*/
- new = bogie_for_host (ssd, token, pd->resolve_p);
+ new = bogie_for_host (ssd, token, NULL);
}
else if (!strcmp (token, "subnet"))
{
- new = subnet_hosts (ssd, error_ret, 0, 24);
+ new = subnet_hosts (ssd, error_ret, desc_ret, 0, 24);
}
else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
{
- new = subnet_hosts (ssd, error_ret, 0, m);
+ new = subnet_hosts (ssd, error_ret, desc_ret, 0, m);
}
else if (*token == '.' || *token == '/' ||
- *token == '$' || *token == '~' ||
- !stat (token, &st))
+ *token == '$' || *token == '~')
+ {
+# ifdef READ_FILES
+ new = read_hosts_file (ssd, token);
+# else
+ if (pd->debug_p) fprintf (stderr, "%s: skipping file\n", progname);
+# endif
+ }
+# ifdef READ_FILES
+ else if (!stat (token, &st))
{
- /* file name
- */
new = read_hosts_file (ssd, token);
}
+# endif /* READ_FILES */
else
{
/* not an existant file - must be a host name
*/
- new = bogie_for_host (ssd, token, pd->resolve_p);
+ new = bogie_for_host (ssd, token, NULL);
}
if (new)
}
free (source);
+
+ /* If the arg was completely unparsable, fall back to the local subnet.
+ This happens if the default is "/etc/hosts" but READ_FILES is off.
+ Or if we're on a /31 network, in which case we try twice then fail.
+ */
+ if (!hostlist && fallback)
+ {
+ if (pd->debug_p)
+ fprintf (stderr, "%s: no hosts parsed! Trying %s\n",
+ progname, fallback);
+ ping_arg = fallback;
+ fallback = 0;
+ goto AGAIN;
+ }
+
return hostlist;
}
sonar_sensor_data *
-init_ping (Display *dpy, char **error_ret,
- const char *subnet, int timeout,
- Bool resolve_p, Bool times_p, Bool debug_p)
+sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
+ const char *subnet, int timeout,
+ Bool resolve_p, Bool times_p, Bool debug_p)
{
+ /* Important! Do not return from this function without disavowing privileges
+ with setuid(getuid()).
+ */
sonar_sensor_data *ssd = (sonar_sensor_data *) calloc (1, sizeof(*ssd));
ping_data *pd = (ping_data *) calloc (1, sizeof(*pd));
sonar_bogie *b;
char *s;
- int i, div;
Bool socket_initted_p = False;
Bool socket_raw_p = False;
+ pd->dpy = dpy;
+
pd->resolve_p = resolve_p;
pd->times_p = times_p;
pd->debug_p = debug_p;
/* Generate a list of targets */
- pd->targets = parse_mode (ssd, error_ret, subnet, socket_initted_p);
+ pd->targets = parse_mode (ssd, error_ret, desc_ret, subnet,
+ socket_initted_p);
pd->targets = delete_duplicate_hosts (ssd, pd->targets);
if (debug_p)
fprintf (stderr, "%s: Target list:\n", progname);
for (b = pd->targets; b; b = b->next)
{
- ping_bogie *pb = (ping_bogie *) b->closure;
- struct sockaddr_in *iaddr = (struct sockaddr_in *) &(pb->address);
- unsigned long ip = iaddr->sin_addr.s_addr;
fprintf (stderr, "%s: ", progname);
- print_host (stderr, ip, b->name);
+ print_host (stderr, b);
}
}
return 0;
}
- /* Distribute them evenly around the display field.
+ /* Distribute them evenly around the display field, clockwise.
+ Even on a /24, allocated IPs tend to cluster together, so
+ don't put any two hosts closer together than N degrees to
+ avoid unnecessary overlap when we have plenty of space due
+ to addresses that probably won't respond. And don't spread
+ them out too far apart, because that looks too symmetrical
+ when there are a small number of hosts.
*/
- div = pd->target_count;
- if (div > 90) div = 90; /* no closer together than 4 degrees */
- for (i = 0, b = pd->targets; b; b = b->next, i++)
- b->th = M_PI * 2 * ((div - i) % div) / div;
+ {
+ double th = frand(M_PI);
+ double sep = 360.0 / pd->target_count;
+ if (sep < 23) sep = 23;
+ if (sep > 43) sep = 43;
+ sep /= 180/M_PI;
+ for (b = pd->targets; b; b = b->next) {
+ b->th = th;
+ th += sep;
+ }
+ }
return ssd;
}