Define ETHERTYPE_IPV6 ourselves.
[darkstat] / dns.c
diff --git a/dns.c b/dns.c
index cb36d21..c89dc1b 100644 (file)
--- a/dns.c
+++ b/dns.c
@@ -1,5 +1,5 @@
 /* darkstat 3
- * copyright (c) 2001-2008 Emil Mikulic.
+ * copyright (c) 2001-2011 Emil Mikulic.
  *
  * dns.c: synchronous DNS in a child process.
  *
@@ -7,14 +7,16 @@
  * GNU General Public License version 2. (see COPYING.GPL)
  */
 
-#include "darkstat.h"
+#include "cdefs.h"
 #include "conv.h"
 #include "decode.h"
 #include "dns.h"
 #include "err.h"
 #include "hosts_db.h"
 #include "queue.h"
+#include "str.h"
 #include "tree.h"
+#include "bsd.h" /* for setproctitle, strlcpy */
 
 #include <sys/param.h>
 #include <sys/socket.h>
 #include <string.h>
 #include <unistd.h>
 
-static void dns_main(void); /* this is what the child process runs */
+#ifdef __NetBSD__
+# define gethostbyaddr(addr, len, type) \
+         gethostbyaddr((const char *)(addr), len, type)
+#endif
+
+static void dns_main(void) _noreturn_; /* the child process runs this */
 
 #define CHILD 0 /* child process uses this socket */
 #define PARENT 1
@@ -35,9 +42,9 @@ static int sock[2];
 static pid_t pid = -1;
 
 struct dns_reply {
-   in_addr_t ip;
-   int error; /* h_errno, or 0 if no error */
-   char name[MAXHOSTNAMELEN];
+   struct addr addr;
+   int error; /* for gai_strerror(), or 0 if no error */
+   char name[256]; /* http://tools.ietf.org/html/rfc1034#section-3.1 */
 };
 
 void
@@ -86,15 +93,22 @@ dns_stop(void)
 
 struct tree_rec {
    RB_ENTRY(tree_rec) ptree;
-   in_addr_t ip;
+   struct addr ip;
 };
 
 static int
 tree_cmp(struct tree_rec *a, struct tree_rec *b)
 {
-   if (a->ip < b->ip) return (-1); else
-   if (a->ip > b->ip) return (+1); else
-   return (0);
+   if (a->ip.family != b->ip.family)
+      /* Sort IPv4 to the left of IPv6.  */
+      return ((a->ip.family == IPv4) ? -1 : +1);
+
+   if (a->ip.family == IPv4)
+      return (memcmp(&a->ip.ip.v4, &b->ip.ip.v4, sizeof(a->ip.ip.v4)));
+   else {
+      assert(a->ip.family == IPv6);
+      return (memcmp(&a->ip.ip.v6, &b->ip.ip.v6, sizeof(a->ip.ip.v6)));
+   }
 }
 
 static RB_HEAD(tree_t, tree_rec) ip_tree = RB_INITIALIZER(&tree_rec);
@@ -106,7 +120,7 @@ static struct tree_rec * tree_t_RB_MINMAX(struct tree_t *head, int val)
 RB_GENERATE(tree_t, tree_rec, ptree, tree_cmp)
 
 void
-dns_queue(const in_addr_t ip)
+dns_queue(const struct addr *const ipaddr)
 {
    struct tree_rec *rec;
    ssize_t num_w;
@@ -114,38 +128,43 @@ dns_queue(const in_addr_t ip)
    if (pid == -1)
       return; /* no child was started - we're not doing any DNS */
 
+   if ((ipaddr->family != IPv4) && (ipaddr->family != IPv6)) {
+      verbosef("dns_queue() for unknown family %d", ipaddr->family);
+      return;
+   }
+
    rec = xmalloc(sizeof(*rec));
-   rec->ip = ip;
+   memcpy(&rec->ip, ipaddr, sizeof(rec->ip));
+
    if (RB_INSERT(tree_t, &ip_tree, rec) != NULL) {
       /* Already queued - this happens seldom enough that we don't care about
        * the performance hit of needlessly malloc()ing. */
-      verbosef("already queued %s", ip_to_str(ip));
+      verbosef("already queued %s", addr_to_str(ipaddr));
       free(rec);
       return;
    }
 
-   num_w = write(sock[PARENT], &ip, sizeof(ip)); /* won't block */
+   num_w = write(sock[PARENT], ipaddr, sizeof(*ipaddr)); /* won't block */
    if (num_w == 0)
       warnx("dns_queue: write: ignoring end of file");
    else if (num_w == -1)
       warn("dns_queue: ignoring write error");
-   else if (num_w != sizeof(ip))
-      err(1, "dns_queue: wrote %d instead of %d",
-         (int)num_w, (int)sizeof(ip));
+   else if (num_w != sizeof(*ipaddr))
+      err(1, "dns_queue: wrote %zu instead of %zu", num_w, sizeof(*ipaddr));
 }
 
 static void
-dns_unqueue(const in_addr_t ip)
+dns_unqueue(const struct addr *const ipaddr)
 {
    struct tree_rec tmp, *rec;
 
-   tmp.ip = ip;
+   memcpy(&tmp.ip, ipaddr, sizeof(tmp.ip));
    if ((rec = RB_FIND(tree_t, &ip_tree, &tmp)) != NULL) {
       RB_REMOVE(tree_t, &ip_tree, rec);
       free(rec);
    }
    else
-      verbosef("couldn't unqueue %s - not in queue!", ip_to_str(ip));
+      verbosef("couldn't unqueue %s - not in queue!", addr_to_str(ipaddr));
 }
 
 /*
@@ -153,7 +172,7 @@ dns_unqueue(const in_addr_t ip)
  * (name buffer is allocated by dns_poll)
  */
 static int
-dns_get_result(in_addr_t *ip, char **name)
+dns_get_result(struct addr *ipaddr, char **name)
 {
    struct dns_reply reply;
    ssize_t numread;
@@ -168,16 +187,33 @@ dns_get_result(in_addr_t *ip, char **name)
    if (numread == 0)
       goto error; /* EOF */
    if (numread != sizeof(reply))
-      errx(1, "dns_get_result read got %d, expected %d",
-         (int)numread, (int)sizeof(reply));
+      errx(1, "dns_get_result read got %zu, expected %zu",
+         numread, sizeof(reply));
 
    /* Return successful reply. */
-   *ip = reply.ip;
-   if (reply.error != 0)
-      xasprintf(name, "(%s)", hstrerror(reply.error));
-   else
+   memcpy(ipaddr, &reply.addr, sizeof(*ipaddr));
+   if (reply.error != 0) {
+      /* Identify common special cases.  */
+      const char *type = "none";
+
+      if (reply.addr.family == IPv6) {
+         if (IN6_IS_ADDR_LINKLOCAL(&reply.addr.ip.v6))
+            type = "link-local";
+         else if (IN6_IS_ADDR_SITELOCAL(&reply.addr.ip.v6))
+            type = "site-local";
+         else if (IN6_IS_ADDR_MULTICAST(&reply.addr.ip.v6))
+            type = "multicast";
+      } else {
+         assert(reply.addr.family == IPv4);
+         if (IN_MULTICAST(htonl(reply.addr.ip.v4)))
+            type = "multicast";
+      }
+      xasprintf(name, "(%s)", type);
+   }
+   else  /* Correctly resolved name.  */
       *name = xstrdup(reply.name);
-   dns_unqueue(reply.ip);
+
+   dns_unqueue(&reply.addr);
    return (1);
 
 error:
@@ -189,7 +225,7 @@ error:
 void
 dns_poll(void)
 {
-   in_addr_t ip;
+   struct addr ip;
    char *name;
 
    if (pid == -1)
@@ -197,15 +233,16 @@ dns_poll(void)
 
    while (dns_get_result(&ip, &name)) {
       /* push into hosts_db */
-      struct bucket *b = host_find(ip);
+      struct bucket *b = host_find(&ip);
+
       if (b == NULL) {
          verbosef("resolved %s to %s but it's not in the DB!",
-            ip_to_str(ip), name);
+            addr_to_str(&ip), name);
          return;
       }
       if (b->u.host.dns != NULL) {
          verbosef("resolved %s to %s but it's already in the DB!",
-            ip_to_str(ip), name);
+            addr_to_str(&ip), name);
          return;
       }
       b->u.host.dns = name;
@@ -216,25 +253,25 @@ dns_poll(void)
 
 struct qitem {
    STAILQ_ENTRY(qitem) entries;
-   in_addr_t ip;
+   struct addr ip;
 };
 
 STAILQ_HEAD(qhead, qitem) queue = STAILQ_HEAD_INITIALIZER(queue);
 
 static void
-enqueue(const in_addr_t ip)
+enqueue(const struct addr *const ip)
 {
    struct qitem *i;
 
    i = xmalloc(sizeof(*i));
-   i->ip = ip;
+   memcpy(&i->ip, ip, sizeof(i->ip));
    STAILQ_INSERT_TAIL(&queue, i, entries);
-   verbosef("DNS: enqueued %s", ip_to_str(ip));
+   verbosef("DNS: enqueued %s", addr_to_str(ip));
 }
 
 /* Return non-zero and populate <ip> pointer if queue isn't empty. */
 static int
-dequeue(in_addr_t *ip)
+dequeue(struct addr *ip)
 {
    struct qitem *i;
 
@@ -242,8 +279,9 @@ dequeue(in_addr_t *ip)
    if (i == NULL)
       return (0);
    STAILQ_REMOVE_HEAD(&queue, entries);
-   *ip = i->ip;
+   memcpy(ip, &i->ip, sizeof(*ip));
    free(i);
+   verbosef("DNS: dequeued %s", addr_to_str(ip));
    return 1;
 }
 
@@ -261,11 +299,9 @@ xwrite(const int d, const void *buf, const size_t nbytes)
 static void
 dns_main(void)
 {
-   in_addr_t ip;
+   struct addr ip;
 
-#ifdef HAVE_SETPROCTITLE
    setproctitle("DNS child");
-#endif
    fd_set_nonblock(sock[CHILD]);
    verbosef("DNS child entering main DNS loop");
    for (;;) {
@@ -292,9 +328,9 @@ dns_main(void)
             err(1, "DNS: read failed");
          }
          if (numread != sizeof(ip))
-            err(1, "DNS: read got %d bytes, expecting %d",
-               (int)numread, (int)sizeof(ip));
-         enqueue(ip);
+            err(1, "DNS: read got %zu bytes, expecting %zu",
+               numread, sizeof(ip));
+         enqueue(&ip);
          if (blocking) {
             /* After one blocking read, become non-blocking so that when we
              * run out of input we fall through to queue processing.
@@ -307,26 +343,59 @@ dns_main(void)
       /* Process queue. */
       if (dequeue(&ip)) {
          struct dns_reply reply;
+         struct sockaddr_in sin;
+         struct sockaddr_in6 sin6;
          struct hostent *he;
+         char host[NI_MAXHOST];
+         int ret, flags;
+
+         reply.addr = ip;
+         flags = NI_NAMEREQD;
+#  ifdef NI_IDN
+         flags |= NI_IDN;
+#  endif
+         switch (ip.family) {
+            case IPv4:
+               sin.sin_family = AF_INET;
+               sin.sin_addr.s_addr = ip.ip.v4;
+               ret = getnameinfo((struct sockaddr *) &sin, sizeof(sin),
+                                 host, sizeof(host), NULL, 0, flags);
+               if (ret == EAI_FAMILY) {
+                  verbosef("getnameinfo error %s, trying gethostbyname",
+                     gai_strerror(ret));
+                  he = gethostbyaddr(&sin.sin_addr.s_addr,
+                     sizeof(sin.sin_addr.s_addr), sin.sin_family);
+                  if (he == NULL) {
+                     ret = EAI_FAIL;
+                     verbosef("gethostbyname error %s", hstrerror(h_errno));
+                  } else {
+                     ret = 0;
+                     strlcpy(host, he->h_name, sizeof(host));
+                  }
+               }
+               break;
+            case IPv6:
+               sin6.sin6_family = AF_INET6;
+               memcpy(&sin6.sin6_addr, &ip.ip.v6, sizeof(sin6.sin6_addr));
+               ret = getnameinfo((struct sockaddr *) &sin6, sizeof(sin6),
+                                 host, sizeof(host), NULL, 0, flags);
+               break;
+            default:
+               ret = EAI_FAMILY;
+         }
 
-         reply.ip = ip;
-         he = gethostbyaddr((char *)&ip, sizeof(ip), AF_INET);
-
-         /* On some platforms (for example Linux with GLIBC 2.3.3), h_errno
-          * will be non-zero here even though the lookup succeeded.
-          */
-         if (he == NULL) {
+         if (ret != 0) {
             reply.name[0] = '\0';
-            reply.error = h_errno;
+            reply.error = ret;
          } else {
             assert(sizeof(reply.name) > sizeof(char *)); /* not just a ptr */
-            strlcpy(reply.name, he->h_name, sizeof(reply.name));
+            strlcpy(reply.name, host, sizeof(reply.name));
             reply.error = 0;
          }
          fd_set_block(sock[CHILD]);
          xwrite(sock[CHILD], &reply, sizeof(reply));
-         verbosef("DNS: %s is %s", ip_to_str(ip),
-            (h_errno == 0)?reply.name:hstrerror(h_errno));
+         verbosef("DNS: %s is \"%s\".", addr_to_str(&reply.addr),
+            (ret == 0) ? reply.name : gai_strerror(ret));
       }
    }
 }