chiark / gitweb /
noip.c, noip.1: Multiple address family support.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 23 Apr 2014 09:49:30 +0000 (10:49 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 25 Apr 2014 09:11:46 +0000 (10:11 +0100)
Abstract out all of the address-family-specific hacking into a
collection of utility functions.  Now, with a little luck, adding
additional address families later will be straightforward.

The main casualty is the notional support for arbitrary netmasks, though
in fact they never worked correctly.  They've now been silently dropped:
the new parser simply refuses to try.

noip.1
noip.c

diff --git a/noip.1 b/noip.1
index c18110d99d3c4581e665474fbfd01cabb68eecbf..2d1beef16a8763ad80e4edf85a1119461d683f6d 100644 (file)
--- a/noip.1
+++ b/noip.1
@@ -193,7 +193,7 @@ is a comma-separated list of entries of the form:
 .RB [ \- \c
 .IR address | \c
 .BR / \c
-.IR mask ]| \c
+.IR prefix-length ]| \c
 .BR local | any
 .RB [ : \c
 .IR port [ \c
@@ -219,17 +219,17 @@ Matches all addresses.
 Matches the address of one of the machine's network interfaces.
 .TP
 .I address
-Matches just the given address
+Matches just the given address.  An
+.I address
+may be enclosed in square brackets.
 .TP
 .IB address \- address
 Matches any address which falls in the given range.  Addresses are
 compared lexicographically, with octets to the left given precedence
 over octets to the right.
 .TP
-.IB address / mask
-Matches an address in the given network.  The
-.I mask
-may be a netmask in dotted-quad form, or a one-bit-count.
+.IB address / prefix-length
+Matches an address in the given network.
 .PP
 The port portion may be omitted (which means `match any port'), or may
 be a single
diff --git a/noip.c b/noip.c
index aaf21b4d3d641b92daef4f330c779d1ed811bc1e..ec66340e4d57d7f9436aad511ebc9101214e120e 100644 (file)
--- a/noip.c
+++ b/noip.c
 
 /*----- Header files ------------------------------------------------------*/
 
+#include <assert.h>
 #include <ctype.h>
 #include <errno.h>
+#include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 
@@ -52,6 +54,7 @@
 #include <netinet/tcp.h>
 #include <netinet/udp.h>
 #include <ifaddrs.h>
+#include <netdb.h>
 
 /*----- Data structures ---------------------------------------------------*/
 
@@ -59,17 +62,37 @@ enum { UNUSED, STALE, USED };               /* Unix socket status values */
 enum { WANT_FRESH, WANT_EXISTING };    /* Socket address dispositions */
 enum { DENY, ALLOW };                  /* ACL verdicts */
 
+static int address_families[] = { AF_INET, -1 };
+
+#define ADDRBUFSZ 64
+
+/* Address representations. */
+typedef union ipaddr {
+  struct in_addr v4;
+} ipaddr;
+
+/* Convenient socket address hacking. */
+typedef union address {
+  struct sockaddr sa;
+  struct sockaddr_in sin;
+} address;
+
 /* Access control list nodes */
 typedef struct aclnode {
   struct aclnode *next;
   int act;
-  unsigned long minaddr, maxaddr;
+  int af;
+  ipaddr minaddr, maxaddr;
   unsigned short minport, maxport;
 } aclnode;
 
 /* Local address records */
+typedef struct full_ipaddr {
+  int af;
+  ipaddr addr;
+} full_ipaddr;
 #define MAX_LOCAL_IPADDRS 16
-static struct in_addr local_ipaddrs[MAX_LOCAL_IPADDRS];
+static full_ipaddr local_ipaddrs[MAX_LOCAL_IPADDRS];
 static int n_local_ipaddrs;
 
 /* General configuration */
@@ -159,6 +182,262 @@ static char *xstrdup(const char *p)
   memcpy(q, p, n);
   return (q);
 }
+
+/*----- Address-type hacking ----------------------------------------------*/
+
+/* If M is a simple mask, i.e., consists of a sequence of zero bits followed
+ * by a sequence of one bits, then return the length of the latter sequence
+ * (which may be zero); otherwise return -1.
+ */
+static int simple_mask_length(unsigned long m)
+{
+  int n = 0;
+
+  while (m & 1) { n++; m >>= 1; }
+  return (m ? -1 : n);
+}
+
+/* Answer whether AF is an interesting address family. */
+static int family_known_p(int af)
+{
+  switch (af) {
+    case AF_INET:
+      return (1);
+    default:
+      return (0);
+  }
+}
+
+/* Return the socket address length for address family AF. */
+static socklen_t family_socklen(int af)
+{
+  switch (af) {
+    case AF_INET: return (sizeof(struct sockaddr_in));
+    default: abort();
+  }
+}
+
+/* Return the width of addresses of kind AF. */
+static int address_width(int af)
+{
+  switch (af) {
+    case AF_INET: return 32;
+    default: abort();
+  }
+}
+
+/* If addresses A and B share a common prefix then return its length;
+ * otherwise return -1.
+ */
+static int common_prefix_length(int af, const ipaddr *a, const ipaddr *b)
+{
+  switch (af) {
+    case AF_INET: {
+      unsigned long aa = ntohl(a->v4.s_addr), bb = ntohl(b->v4.s_addr);
+      unsigned long m = aa^bb;
+      if ((aa&m) == 0 && (bb&m) == m) return (32 - simple_mask_length(m));
+      else return (-1);
+    } break;
+    default:
+      abort();
+  }
+}
+
+/* Extract the port number (in host byte-order) from SA. */
+static int port_from_sockaddr(const struct sockaddr *sa)
+{
+  switch (sa->sa_family) {
+    case AF_INET: return (ntohs(SIN(sa)->sin_port));
+    default: abort();
+  }
+}
+
+/* Store the port number PORT (in host byte-order) in SA. */
+static void port_to_sockaddr(struct sockaddr *sa, int port)
+{
+  switch (sa->sa_family) {
+    case AF_INET: SIN(sa)->sin_port = htons(port); break;
+    default: abort();
+  }
+}
+/* Extract the address part from SA and store it in A. */
+static void ipaddr_from_sockaddr(ipaddr *a, const struct sockaddr *sa)
+{
+  switch (sa->sa_family) {
+    case AF_INET: a->v4 = SIN(sa)->sin_addr; break;
+    default: abort();
+  }
+}
+
+/* Copy a whole socket address about. */
+static void copy_sockaddr(struct sockaddr *sa_dst,
+                         const struct sockaddr *sa_src)
+  { memcpy(sa_dst, sa_src, family_socklen(sa_src->sa_family)); }
+
+/* Answer whether two addresses are equal. */
+static int ipaddr_equal_p(int af, const ipaddr *a, const ipaddr *b)
+{
+  switch (af) {
+    case AF_INET: return (a->v4.s_addr == b->v4.s_addr);
+    default: abort();
+  }
+}
+
+/* Answer whether the address part of SA is between A and B (inclusive).  We
+ * assume that SA has the correct address family.
+ */
+static int sockaddr_in_range_p(const struct sockaddr *sa,
+                              const ipaddr *a, const ipaddr *b)
+{
+  switch (sa->sa_family) {
+    case AF_INET: {
+      unsigned long addr = ntohl(SIN(sa)->sin_addr.s_addr);
+      return (ntohl(a->v4.s_addr) <= addr &&
+             addr <= ntohl(b->v4.s_addr));
+    } break;
+    default:
+      abort();
+  }
+}
+
+/* Fill in SA with the appropriate wildcard address. */
+static void wildcard_address(int af, struct sockaddr *sa)
+{
+  switch (af) {
+    case AF_INET: {
+      struct sockaddr_in *sin = SIN(sa);
+      memset(sin, 0, sizeof(*sin));
+      sin->sin_family = AF_INET;
+      sin->sin_port = 0;
+      sin->sin_addr.s_addr = INADDR_ANY;
+    } break;
+    default:
+      abort();
+  }
+}
+
+/* Mask the address A, forcing all but the top PLEN bits to zero or one
+ * according to HIGHP.
+ */
+static void mask_address(int af, ipaddr *a, int plen, int highp)
+{
+  switch (af) {
+    case AF_INET: {
+      unsigned long addr = ntohl(a->v4.s_addr);
+      unsigned long mask = plen ? ~0ul << (32 - plen) : 0;
+      addr &= mask;
+      if (highp) addr |= ~mask;
+      a->v4.s_addr = htonl(addr & 0xffffffff);
+    } break;
+    default:
+      abort();
+  }
+}
+
+/* Write a presentation form of SA to BUF, a buffer of length SZ.  LEN is the
+ * address length; if it's zero, look it up based on the address family.
+ * Return a pointer to the string (which might, in an emergency, be a static
+ * string rather than your buffer).
+ */
+static char *present_sockaddr(const struct sockaddr *sa, socklen_t len,
+                             char *buf, size_t sz)
+{
+#define WANT(n_) do { if (sz < (n_)) goto nospace; } while (0)
+#define PUTC(c_) do { *buf++ = (c_); sz--; } while (0)
+
+  if (!sz) return "<no-space-in-buffer>";
+  if (!len) len = family_socklen(sa->sa_family);
+
+  switch (sa->sa_family) {
+    case AF_UNIX: {
+      struct sockaddr_un *sun = SUN(sa);
+      char *p = sun->sun_path;
+      size_t n = len - offsetof(struct sockaddr_un, sun_path);
+
+      assert(n);
+      if (*p == 0) {
+       WANT(1); PUTC('@');
+       p++; n--;
+       while (n) {
+         switch (*p) {
+           case 0: WANT(2); PUTC('\\'); PUTC('0'); break;
+           case '\a': WANT(2); PUTC('\\'); PUTC('a'); break;
+           case '\n': WANT(2); PUTC('\\'); PUTC('n'); break;
+           case '\r': WANT(2); PUTC('\\'); PUTC('r'); break;
+           case '\t': WANT(2); PUTC('\\'); PUTC('t'); break;
+           case '\v': WANT(2); PUTC('\\'); PUTC('v'); break;
+           case '\\': WANT(2); PUTC('\\'); PUTC('\\'); break;
+           default:
+             if (*p > ' ' && *p <= '~')
+               { WANT(1); PUTC(*p); }
+             else {
+               WANT(4); PUTC('\\'); PUTC('x');
+               PUTC((*p >> 4)&0xf); PUTC((*p >> 0)&0xf);
+             }
+             break;
+         }
+         p++; n--;
+       }
+      } else {
+       if (*p != '/') { WANT(2); PUTC('.'); PUTC('/'); }
+       while (n && *p) { WANT(1); PUTC(*p); p++; n--; }
+      }
+      WANT(1); PUTC(0);
+    } break;
+    case AF_INET: {
+      char addrbuf[NI_MAXHOST], portbuf[NI_MAXSERV];
+      int err = getnameinfo(sa, len,
+                           addrbuf, sizeof(addrbuf),
+                           portbuf, sizeof(portbuf),
+                           NI_NUMERICHOST | NI_NUMERICSERV);
+      assert(!err);
+      snprintf(buf, sz, strchr(addrbuf, ':') ? "[%s]:%s" : "%s:%s",
+              addrbuf, portbuf);
+    } break;
+    default:
+      snprintf(buf, sz, "<unknown-address-family %d>", sa->sa_family);
+      break;
+  }
+  return (buf);
+
+nospace:
+  buf[sz - 1] = 0;
+  return (buf);
+}
+
+/* Guess the family of a textual socket address. */
+static int guess_address_family(const char *p)
+  { return (AF_INET); }
+
+/* Parse a socket address P and write the result to SA. */
+static int parse_sockaddr(struct sockaddr *sa, const char *p)
+{
+  char buf[ADDRBUFSZ];
+  char *q;
+  struct addrinfo *ai, ai_hint = { 0 };
+
+  if (strlen(p) >= sizeof(buf) - 1) return (-1);
+  strcpy(buf, p); p = buf;
+  if (*p != '[') {
+    if ((q = strchr(p, ':')) == 0) return (-1);
+    *q++ = 0;
+  } else {
+    p++;
+    if ((q = strchr(p, ']')) == 0) return (-1);
+    *q++ = 0;
+    if (*q != ':') return (-1);
+    q++;
+  }
+
+  ai_hint.ai_family = AF_UNSPEC;
+  ai_hint.ai_socktype = SOCK_DGRAM;
+  ai_hint.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
+  if (getaddrinfo(p, q, &ai_hint, &ai)) return (-1);
+  memcpy(sa, ai->ai_addr, ai->ai_addrlen);
+  freeaddrinfo(ai);
+  return (0);
+}
+
 /*----- Access control lists ----------------------------------------------*/
 
 #ifdef DEBUG
@@ -166,22 +445,19 @@ static char *xstrdup(const char *p)
 /* Write to standard error a description of the ACL node A. */
 static void dump_aclnode(aclnode *a)
 {
-  char minbuf[16], maxbuf[16];
-  struct in_addr amin, amax;
+  char buf[ADDRBUFSZ];
+  const char *p;
+  int plen;
 
-  amin.s_addr = htonl(a->minaddr);
-  amax.s_addr = htonl(a->maxaddr);
   fprintf(stderr, "noip:   %c ", a->act ? '+' : '-');
-  if (a->minaddr == 0 && a->maxaddr == 0xffffffff)
-    fprintf(stderr, "any");
-  else {
-    fprintf(stderr, "%s",
-           inet_ntop(AF_INET, &amin, minbuf, sizeof(minbuf)));
-    if (a->maxaddr != a->minaddr) {
-      fprintf(stderr, "-%s",
-             inet_ntop(AF_INET, &amax, maxbuf, sizeof(maxbuf)));
-    }
-  }
+  plen = common_prefix_length(a->af, &a->minaddr, &a->maxaddr);
+  p = inet_ntop(a->af, &a->minaddr, buf, sizeof(buf));
+  fprintf(stderr, strchr(p, ':') ? "[%s]" : "%s", p);
+  if (plen < 0) {
+    p = inet_ntop(a->af, &a->maxaddr, buf, sizeof(buf));
+    fprintf(stderr, strchr(p, ':') ? "-[%s]" : "-%s", p);
+  } else if (plen < address_width(a->af))
+    fprintf(stderr, "/%d", plen);
   if (a->minport != 0 || a->maxport != 0xffff) {
     fprintf(stderr, ":%u", (unsigned)a->minport);
     if (a->minport != a->maxport)
@@ -204,20 +480,18 @@ static void dump_acl(aclnode *a)
 
 #endif
 
-/* Returns nonzero if the ACL A allows the IP socket SIN. */
-static int acl_allows_p(aclnode *a, const struct sockaddr_in *sin)
+/* Returns nonzero if the ACL A allows the socket address SA. */
+static int acl_allows_p(aclnode *a, const struct sockaddr *sa)
 {
-  unsigned long addr = ntohl(sin->sin_addr.s_addr);
-  unsigned short port = ntohs(sin->sin_port);
+  unsigned short port = port_from_sockaddr(sa);
   int act = ALLOW;
 
-  D( char buf[16];
-     fprintf(stderr, "noip: check %s:%u\n",
-            inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)),
-            ntohs((unsigned)sin->sin_port)); )
+  D({ char buf[ADDRBUFSZ];
+      fprintf(stderr, "noip: check %s\n",
+             present_sockaddr(sa, 0, buf, sizeof(buf))); })
   for (; a; a = a->next) {
     D( dump_aclnode(a); )
-    if (a->minaddr <= addr && addr <= a->maxaddr &&
+    if (sockaddr_in_range_p(sa, &a->minaddr, &a->maxaddr) &&
        a->minport <= port && port <= a->maxport) {
       D( fprintf(stderr, "noip: aha!  %s\n", a->act ? "ALLOW" : "DENY"); )
       return (a->act);
@@ -236,7 +510,8 @@ static unsigned randrange(unsigned min, unsigned max)
   unsigned mask, i;
 
   /* It's so nice not to have to care about the quality of the generator
-     much! */
+   * much!
+   */
   max -= min;
   for (mask = 1; mask < max; mask = (mask << 1) | 1)
     ;
@@ -281,48 +556,51 @@ done:
   return (rc);
 }
 
-/* Encode the Internet address SIN as a Unix-domain address SUN.  If WANT is
- * WANT_FRESH, and SIN->sin_port is zero, then we pick an arbitrary local
+/* Encode the Internet address SA as a Unix-domain address SUN.  If WANT is
+ * WANT_FRESH, and SA's port number is zero, then we pick an arbitrary local
  * port.  Otherwise we pick the port given.  There's an unpleasant hack to
- * find servers bound to INADDR_ANY.  Returns zero on success; -1 on failure.
+ * find servers bound to local wildcard addresses.  Returns zero on success;
+ * -1 on failure.
  */
 static int encode_inet_addr(struct sockaddr_un *sun,
-                           const struct sockaddr_in *sin,
+                           const struct sockaddr *sa,
                            int want)
 {
   int i;
   int desperatep = 0;
-  char buf[INET_ADDRSTRLEN];
+  address addr;
+  char buf[ADDRBUFSZ];
   int rc;
 
-  D( fprintf(stderr, "noip: encode %s:%u (%s)",
-            inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)),
-            (unsigned)ntohs(sin->sin_port),
+  D( fprintf(stderr, "noip: encode %s (%s)",
+            present_sockaddr(sa, 0, buf, sizeof(buf)),
             want == WANT_EXISTING ? "EXISTING" : "FRESH"); )
   sun->sun_family = AF_UNIX;
-  if (sin->sin_port || want == WANT_EXISTING) {
-    snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/%s:%u", sockdir,
-            inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)),
-            (unsigned)ntohs(sin->sin_port));
+  if (port_from_sockaddr(sa) || want == WANT_EXISTING) {
+    snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/%s", sockdir,
+            present_sockaddr(sa, 0, buf, sizeof(buf)));
     rc = unix_socket_status(sun, 0);
     if (rc == STALE) unlink(sun->sun_path);
     if (rc != USED && want == WANT_EXISTING) {
-      snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/0.0.0.0:%u",
-              sockdir, (unsigned)ntohs(sin->sin_port));
+      wildcard_address(sa->sa_family, &addr.sa);
+      port_to_sockaddr(&addr.sa, port_from_sockaddr(sa));
+      snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/%s", sockdir,
+              present_sockaddr(&addr.sa, 0, buf, sizeof(buf)));
       if (unix_socket_status(sun, 0) == STALE) unlink(sun->sun_path);
     }
   } else {
+    copy_sockaddr(&addr.sa, sa);
     for (i = 0; i < 10; i++) {
-      snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/%s:%u", sockdir,
-              inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)),
-              randrange(minautoport, maxautoport));
+      port_to_sockaddr(&addr.sa, randrange(minautoport, maxautoport));
+      snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/%s", sockdir,
+              present_sockaddr(&addr.sa, 0, buf, sizeof(buf)));
       if (unix_socket_status(sun, 1) == UNUSED) goto found;
     }
     for (desperatep = 0; desperatep < 2; desperatep++) {
       for (i = minautoport; i <= maxautoport; i++) {
-       snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/%s:%u", sockdir,
-                inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)),
-                (unsigned)i);
+       port_to_sockaddr(&addr.sa, i);
+       snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/%s", sockdir,
+                present_sockaddr(&addr.sa, 0, buf, sizeof(buf)));
        rc = unix_socket_status(sun, !desperatep);
        switch (rc) {
          case STALE: unlink(sun->sun_path);
@@ -339,34 +617,28 @@ static int encode_inet_addr(struct sockaddr_un *sun,
   return (0);
 }
 
-/* Decode the Unix address SUN to an Internet address SIN.  If
- * DECODE_UNBOUND_P is nonzero, an empty address (indicative of an unbound
- * Unix-domain socket) is translated to a wildcard Internet address.  Returns
- * zero on success; -1 on failure (e.g., it wasn't one of our addresses).
+/* Decode the Unix address SUN to an Internet address SIN.  If AF_HINT is
+ * nonzero, an empty address (indicative of an unbound Unix-domain socket) of
+ * the is translated to a wildcard Internet address of the appropriate
+ * family.  Returns zero on success; -1 on failure (e.g., it wasn't one of
+ * our addresses).
  */
-static int decode_inet_addr(struct sockaddr_in *sin,
+static int decode_inet_addr(struct sockaddr *sa, int af_hint,
                            const struct sockaddr_un *sun,
-                           socklen_t len,
-                           int decode_unbound_p)
+                           socklen_t len)
 {
-  char buf[INET_ADDRSTRLEN + 16];
-  char *p;
+  char buf[ADDRBUFSZ];
   size_t n = strlen(sockdir), nn;
-  struct sockaddr_in sin_mine;
-  unsigned long port;
+  address addr;
 
-  if (!sin)
-    sin = &sin_mine;
-  if (sun->sun_family != AF_UNIX)
-    return (-1);
+  if (!sa) sa = &addr.sa;
+  if (sun->sun_family != AF_UNIX) return (-1);
+  if (len > sizeof(*sun)) return (-1);
+  ((char *)sun)[len] = 0;
   nn = strlen(sun->sun_path);
-  if (len < sizeof(sun)) ((char *)sun)[len] = 0;
-  D( fprintf(stderr, "noip: decode (%d) `%s'",
-            *sun->sun_path, sun->sun_path); )
-  if (decode_unbound_p && !sun->sun_path[0]) {
-    sin->sin_family = AF_INET;
-    sin->sin_addr.s_addr = INADDR_ANY;
-    sin->sin_port = 0;
+  D( fprintf(stderr, "noip: decode `%s'", sun->sun_path); )
+  if (af_hint && !sun->sun_path[0]) {
+    wildcard_address(af_hint, sa);
     D( fprintf(stderr, " -- unbound socket\n"); )
     return (0);
   }
@@ -375,40 +647,23 @@ static int decode_inet_addr(struct sockaddr_in *sin,
     D( fprintf(stderr, " -- not one of ours\n"); )
     return (-1);
   }
-  memcpy(buf, sun->sun_path + n + 1, nn - n);
-  if ((p = strchr(buf, ':')) == 0) {
-    D( fprintf(stderr, " -- malformed (no port)\n"); )
-    return (-1);
-  }
-  *p++ = 0;
-  sin->sin_family = AF_INET;
-  if (inet_pton(AF_INET, buf, &sin->sin_addr) <= 0) {
-    D( fprintf(stderr, " -- malformed (bad address `%s')\n", buf); )
-    return (-1);
-  }
-  port = strtoul(p, &p, 10);
-  if (*p || port >= 65536) {
-    D( fprintf(stderr, " -- malformed (port out of range)"); )
-    return (-1);
-  }
-  sin->sin_port = htons(port);
-  D( fprintf(stderr, " -> %s:%u\n",
-            inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)),
-            (unsigned)port); )
+  if (parse_sockaddr(sa, sun->sun_path + n + 1)) return (-1);
+  D( fprintf(stderr, " -> %s\n",
+            present_sockaddr(sa, 0, buf, sizeof(buf))); )
   return (0);
 }
 
 /* SK is (or at least might be) a Unix-domain socket we created when an
  * Internet socket was asked for.  We've decided it should be an Internet
- * socket after all, so convert it.
+ * socket after all, with family AF_HINT, so convert it.
  */
-static int fixup_real_ip_socket(int sk)
+static int fixup_real_ip_socket(int sk, int af_hint)
 {
   int nsk;
   int type;
   int f, fd;
   struct sockaddr_un sun;
-  struct sockaddr_in sin;
+  address addr;
   socklen_t len;
 
 #define OPTS(_)                                                                \
@@ -429,11 +684,11 @@ static int fixup_real_ip_socket(int sk)
   len = sizeof(sun);
   if (real_getsockname(sk, SA(&sun), &len))
     return (-1);
-  if (decode_inet_addr(&sin, &sun, len, 1))
+  if (decode_inet_addr(&addr.sa, af_hint, &sun, len))
     return (0); /* Not one of ours */
   len = sizeof(type);
   if (real_getsockopt(sk, SOL_SOCKET, SO_TYPE, &type, &len) < 0 ||
-      (nsk = real_socket(PF_INET, type, 0)) < 0)
+      (nsk = real_socket(addr.sa.sa_family, type, 0)) < 0)
     return (-1);
 #define FIX(opt, ty) do {                                              \
   ty ov_;                                                              \
@@ -463,31 +718,27 @@ static int fixup_real_ip_socket(int sk)
 }
 
 /* The socket SK is about to be used to communicate with the remote address
- * SA.  Assign it a local address so that getpeername does something useful.
+ * SA.  Assign it a local address so that getpeername(2) does something
+ * useful.
  */
 static int do_implicit_bind(int sk, const struct sockaddr **sa,
                            socklen_t *len, struct sockaddr_un *sun)
 {
-  struct sockaddr_in sin;
+  address addr;
   socklen_t mylen = sizeof(*sun);
 
-  if (acl_allows_p(connect_real, SIN(*sa))) {
-    if (fixup_real_ip_socket(sk))
-      return (-1);
+  if (acl_allows_p(connect_real, *sa)) {
+    if (fixup_real_ip_socket(sk, (*sa)->sa_family)) return (-1);
   } else {
-    if (real_getsockname(sk, SA(sun), &mylen) < 0)
-      return (-1);
+    if (real_getsockname(sk, SA(sun), &mylen) < 0) return (-1);
     if (sun->sun_family == AF_UNIX) {
       if (mylen < sizeof(*sun)) ((char *)sun)[mylen] = 0;
       if (!sun->sun_path[0]) {
-       sin.sin_family = AF_INET;
-       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
-       sin.sin_port = 0;
-       encode_inet_addr(sun, &sin, WANT_FRESH);
-       if (real_bind(sk, SA(sun), SUN_LEN(sun)))
-         return (-1);
+       wildcard_address((*sa)->sa_family, &addr.sa);
+       encode_inet_addr(sun, &addr.sa, WANT_FRESH);
+       if (real_bind(sk, SA(sun), SUN_LEN(sun))) return (-1);
       }
-      encode_inet_addr(sun, SIN(*sa), WANT_EXISTING);
+      encode_inet_addr(sun, *sa, WANT_EXISTING);
       *sa = SA(sun);
       *len = SUN_LEN(sun);
     }
@@ -503,19 +754,17 @@ static int do_implicit_bind(int sk, const struct sockaddr **sa,
 static void return_fake_name(struct sockaddr *sa, socklen_t len,
                             struct sockaddr *fake, socklen_t *fakelen)
 {
-  struct sockaddr_in sin;
+  address addr;
   socklen_t alen;
 
   if (sa->sa_family == AF_UNIX &&
-      !decode_inet_addr(&sin, SUN(sa), len, 0)) {
-    sa = SA(&sin);
-    len = sizeof(sin);
+      !decode_inet_addr(&addr.sa, 0, SUN(sa), len)) {
+    sa = &addr.sa;
+    len = family_socklen(addr.sa.sa_family);
   }
   alen = len;
-  if (len > *fakelen)
-    len = *fakelen;
-  if (len > 0)
-    memcpy(fake, sa, len);
+  if (len > *fakelen) len = *fakelen;
+  if (len > 0) memcpy(fake, sa, len);
   *fakelen = alen;
 }
 
@@ -576,13 +825,25 @@ static char *user(void)
 
 /* Set Q to point to the next dotted-quad address, store the ending delimiter
  * in DEL, null-terminate it, and step P past it. */
-#define NEXTADDR(q, del) do {                                          \
-  SKIPSPC;                                                             \
-  q = p;                                                               \
-  while (*p && (*p == '.' || isdigit(UC(*p)))) p++;                    \
-  del = *p;                                                            \
-  if (*p) *p++ = 0;                                                    \
-} while (0)
+static void parse_nextaddr(char **pp, char **qq, int *del)
+{
+  char *p = *pp;
+
+  SKIPSPC;
+  if (*p == '[') {
+    p++; SKIPSPC;
+    *qq = p;
+    p += strcspn(p, "]");
+    if (*p) *p++ = 0;
+    *del = 0;
+  } else {
+    *qq = p;
+    while (*p && (*p == '.' || isdigit(UC(*p)))) p++;
+    *del = *p;
+    if (*p) *p++ = 0;
+  }
+  *pp = p;
+}
 
 /* Set Q to point to the next decimal number, store the ending delimiter in
  * DEL, null-terminate it, and step P past it. */
@@ -630,17 +891,19 @@ static void parse_ports(char **pp, unsigned short *min, unsigned short *max)
   *pp = p;
 }
 
-/* Make a new ACL node.  ACT is the verdict; MINADDR and MAXADDR are the
- * ranges on IP addresses; MINPORT and MAXPORT are the ranges on port
- * numbers; TAIL is the list tail to attach the new node to.
+/* Make a new ACL node.  ACT is the verdict; AF is the address family;
+ * MINADDR and MAXADDR are the ranges on IP addresses; MINPORT and MAXPORT
+ * are the ranges on port numbers; TAIL is the list tail to attach the new
+ * node to.
  */
 #define ACLNODE(tail_, act_,                                           \
-               minaddr_, maxaddr_, minport_, maxport_) do {            \
+               af_, minaddr_, maxaddr_, minport_, maxport_) do {       \
   aclnode *a_;                                                         \
   NEW(a_);                                                             \
-  a_->act = act_;                                                      \
-  a_->minaddr = minaddr_; a_->maxaddr = maxaddr_;                      \
-  a_->minport = minport_; a_->maxport = maxport_;                      \
+  a_->act = (act_);                                                    \
+  a_->af = (af_);                                                      \
+  a_->minaddr = (minaddr_); a_->maxaddr = (maxaddr_);                  \
+  a_->minport = (minport_); a_->maxport = (maxport_);                  \
   *tail_ = a_; tail_ = &a_->next;                                      \
 } while (0)
 
@@ -652,10 +915,9 @@ static void parse_ports(char **pp, unsigned short *min, unsigned short *max)
  */
 static void parse_acl_line(char **pp, aclnode ***tail)
 {
-  struct in_addr addr;
-  unsigned long minaddr, maxaddr, mask;
+  ipaddr minaddr, maxaddr;
   unsigned short minport, maxport;
-  int i, n;
+  int i, af, n;
   int act;
   int del;
   char *p = *pp;
@@ -670,52 +932,49 @@ static void parse_acl_line(char **pp, aclnode ***tail)
     p++;
     SKIPSPC;
     if (KWMATCHP("any")) {
-      minaddr = 0;
-      maxaddr = 0xffffffff;
-      goto justone;
+      parse_ports(&p, &minport, &maxport);
+      for (i = 0; address_families[i] >= 0; i++) {
+       af = address_families[i];
+       memset(&minaddr, 0, sizeof(minaddr));
+       maxaddr = minaddr; mask_address(af, &maxaddr, 0, 1);
+       ACLNODE(*tail, act, af, minaddr, maxaddr, minport, maxport);
+      }
     } else if (KWMATCHP("local")) {
       parse_ports(&p, &minport, &maxport);
-      ACLNODE(*tail, act, 0, 0, minport, maxport);
-      ACLNODE(*tail, act, 0xffffffff, 0xffffffff, minport, maxport);
+      for (i = 0; address_families[i] >= 0; i++) {
+       af = address_families[i];
+       memset(&minaddr, 0, sizeof(minaddr));
+       maxaddr = minaddr; mask_address(af, &maxaddr, 0, 1);
+       ACLNODE(*tail, act, af, minaddr, minaddr, minport, maxport);
+       ACLNODE(*tail, act, af, maxaddr, maxaddr, minport, maxport);
+      }
       for (i = 0; i < n_local_ipaddrs; i++) {
-       minaddr = ntohl(local_ipaddrs[i].s_addr);
-       ACLNODE(*tail, act, minaddr, minaddr, minport, maxport);
+       ACLNODE(*tail, act, local_ipaddrs[i].af,
+               local_ipaddrs[i].addr, local_ipaddrs[i].addr,
+               minport, maxport);
       }
     } else {
-      if (*p == ':') {
-       minaddr = 0;
-       maxaddr = 0xffffffff;
-      } else {
-       NEXTADDR(q, del);
-       if (inet_pton(AF_INET, q, &addr) <= 0) goto bad;
-       minaddr = ntohl(addr.s_addr);
+      parse_nextaddr(&p, &q, &del);
+      af = guess_address_family(q);
+      if (inet_pton(af, q, &minaddr) <= 0) goto bad;
+      RESCAN(del);
+      SKIPSPC;
+      if (*p == '-') {
+       p++;
+       parse_nextaddr(&p, &q, &del);
+       if (inet_pton(af, q, &maxaddr) <= 0) goto bad;
        RESCAN(del);
-       SKIPSPC;
-       if (*p == '-') {
-         p++;
-         NEXTADDR(q, del);
-         if (inet_pton(AF_INET, q, &addr) <= 0) goto bad;
-         RESCAN(del);
-         maxaddr = ntohl(addr.s_addr);
-       } else if (*p == '/') {
-         p++;
-         NEXTADDR(q, del);
-         if (strchr(q, '.')) {
-           if (inet_pton(AF_INET, q, &addr) <= 0) goto bad;
-           mask = ntohl(addr.s_addr);
-         } else {
-           n = strtoul(q, 0, 0);
-           mask = (~0ul << (32 - n)) & 0xffffffff;
-         }
-         RESCAN(del);
-         minaddr &= mask;
-         maxaddr = minaddr | (mask ^ 0xffffffff);
-       } else
-         maxaddr = minaddr;
-      }
-    justone:
+      } else if (*p == '/') {
+       p++;
+       NEXTNUMBER(q, del);
+       n = strtoul(q, 0, 0);
+       maxaddr = minaddr;
+       mask_address(af, &minaddr, n, 0);
+       mask_address(af, &maxaddr, n, 1);
+      } else
+       maxaddr = minaddr;
       parse_ports(&p, &minport, &maxport);
-      ACLNODE(*tail, act, minaddr, maxaddr, minport, maxport);
+      ACLNODE(*tail, act, af, minaddr, maxaddr, minport, maxport);
     }
     SKIPSPC;
     if (*p != ',') break;
@@ -837,17 +1096,18 @@ done:
 int socket(int pf, int ty, int proto)
 {
   switch (pf) {
-    case PF_INET:
+    default:
+      if (!family_known_p(pf)) {
+       errno = EAFNOSUPPORT;
+       return (-1);
+      }
       pf = PF_UNIX;
       proto = 0;
     case PF_UNIX:
 #ifdef PF_NETLINK
     case PF_NETLINK:
 #endif
-      return real_socket(pf, ty, proto);
-    default:
-      errno = EAFNOSUPPORT;
-      return -1;
+      return (real_socket(pf, ty, proto));
   }
 }
 
@@ -864,19 +1124,19 @@ int bind(int sk, const struct sockaddr *sa, socklen_t len)
 {
   struct sockaddr_un sun;
 
-  if (sa->sa_family == AF_INET) {
+  if (family_known_p(sa->sa_family)) {
     PRESERVING_ERRNO({
-      if (acl_allows_p(bind_real, SIN(sa))) {
-       if (fixup_real_ip_socket(sk))
+      if (acl_allows_p(bind_real, sa)) {
+       if (fixup_real_ip_socket(sk, sa->sa_family))
          return (-1);
       } else {
-       encode_inet_addr(&sun, SIN(sa), WANT_FRESH);
+       encode_inet_addr(&sun, sa, WANT_FRESH);
        sa = SA(&sun);
        len = SUN_LEN(&sun);
       }
     });
   }
-  return real_bind(sk, sa, len);
+  return (real_bind(sk, sa, len));
 }
 
 int connect(int sk, const struct sockaddr *sa, socklen_t len)
@@ -884,23 +1144,20 @@ int connect(int sk, const struct sockaddr *sa, socklen_t len)
   struct sockaddr_un sun;
   int rc;
 
-  switch (sa->sa_family) {
-    case AF_INET:
-      PRESERVING_ERRNO({
-       do_implicit_bind(sk, &sa, &len, &sun);
-      });
-      rc = real_connect(sk, sa, len);
-      if (rc < 0) {
-       switch (errno) {
-         case ENOENT:  errno = ECONNREFUSED;   break;
-       }
+  if (!family_known_p(sa->sa_family))
+    rc = real_connect(sk, sa, len);
+  else {
+    PRESERVING_ERRNO({
+      do_implicit_bind(sk, &sa, &len, &sun);
+    });
+    rc = real_connect(sk, sa, len);
+    if (rc < 0) {
+      switch (errno) {
+       case ENOENT: errno = ECONNREFUSED; break;
       }
-      break;
-    default:
-      rc = real_connect(sk, sa, len);
-      break;
+    }
   }
-  return rc;
+  return (rc);
 }
 
 ssize_t sendto(int sk, const void *buf, size_t len, int flags,
@@ -913,7 +1170,7 @@ ssize_t sendto(int sk, const void *buf, size_t len, int flags,
       do_implicit_bind(sk, &to, &tolen, &sun);
     });
   }
-  return real_sendto(sk, buf, len, flags, to, tolen);
+  return (real_sendto(sk, buf, len, flags, to, tolen));
 }
 
 ssize_t recvfrom(int sk, void *buf, size_t len, int flags,
@@ -949,7 +1206,7 @@ ssize_t sendmsg(int sk, const struct msghdr *msg, int flags)
       msg = &mymsg;
     });
   }
-  return real_sendmsg(sk, msg, flags);
+  return (real_sendmsg(sk, msg, flags));
 }
 
 ssize_t recvmsg(int sk, struct msghdr *msg, int flags)
@@ -960,7 +1217,7 @@ ssize_t recvmsg(int sk, struct msghdr *msg, int flags)
   ssize_t n;
 
   if (!msg->msg_name)
-    return real_recvmsg(sk, msg, flags);
+    return (real_recvmsg(sk, msg, flags));
   PRESERVING_ERRNO({
     sa = SA(msg->msg_name);
     len = msg->msg_namelen;
@@ -1022,7 +1279,7 @@ int getsockopt(int sk, int lev, int opt, void *p, socklen_t *len)
        memset(p, 0, *len);
       return (0);
   }
-  return real_getsockopt(sk, lev, opt, p, len);
+  return (real_getsockopt(sk, lev, opt, p, len));
 }
 
 int setsockopt(int sk, int lev, int opt, const void *p, socklen_t len)
@@ -1039,7 +1296,7 @@ int setsockopt(int sk, int lev, int opt, const void *p, socklen_t len)
     case SO_DETACH_FILTER:
       return (0);
   }
-  return real_setsockopt(sk, lev, opt, p, len);
+  return (real_setsockopt(sk, lev, opt, p, len));
 }
 
 /*----- Initialization ----------------------------------------------------*/
@@ -1049,18 +1306,17 @@ static void cleanup_sockdir(void)
 {
   DIR *dir;
   struct dirent *d;
-  struct sockaddr_in sin;
+  address addr;
   struct sockaddr_un sun;
   struct stat st;
 
-  if ((dir = opendir(sockdir)) == 0)
-    return;
+  if ((dir = opendir(sockdir)) == 0) return;
   sun.sun_family = AF_UNIX;
   while ((d = readdir(dir)) != 0) {
     if (d->d_name[0] == '.') continue;
     snprintf(sun.sun_path, sizeof(sun.sun_path),
             "%s/%s", sockdir, d->d_name);
-    if (decode_inet_addr(&sin, &sun, SUN_LEN(&sun), 0) ||
+    if (decode_inet_addr(&addr.sa, 0, &sun, SUN_LEN(&sun)) ||
        stat(sun.sun_path, &st) ||
        !S_ISSOCK(st.st_mode)) {
       D( fprintf(stderr, "noip: ignoring unknown socketdir entry `%s'\n",
@@ -1082,26 +1338,30 @@ static void cleanup_sockdir(void)
 static void get_local_ipaddrs(void)
 {
   struct ifaddrs *ifa_head, *ifa;
-  const struct in_addr *a;
+  ipaddr a;
   int i;
 
   if (getifaddrs(&ifa_head)) { perror("getifaddrs"); return; }
   for (n_local_ipaddrs = 0, ifa = ifa_head;
        n_local_ipaddrs < MAX_LOCAL_IPADDRS && ifa;
        ifa = ifa->ifa_next) {
-    if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET)
+    if (!ifa->ifa_addr || !family_known_p(ifa->ifa_addr->sa_family))
       continue;
-    a = &SIN(ifa->ifa_addr)->sin_addr;
-    D( fprintf(stderr, "noip: local addr %s = %s",
-              ifa->ifa_name, inet_ntoa(*a)); )
+    ipaddr_from_sockaddr(&a, ifa->ifa_addr);
+    D({ char buf[ADDRBUFSZ];
+       fprintf(stderr, "noip: local addr %s = %s", ifa->ifa_name,
+               inet_ntop(ifa->ifa_addr->sa_family, &a,
+                         buf, sizeof(buf))); })
     for (i = 0; i < n_local_ipaddrs; i++) {
-      if (local_ipaddrs[i].s_addr == a->s_addr) {
+      if (ifa->ifa_addr->sa_family == local_ipaddrs[i].af &&
+         ipaddr_equal_p(local_ipaddrs[i].af, &a, &local_ipaddrs[i].addr)) {
        D( fprintf(stderr, " (duplicate)\n"); )
        goto skip;
       }
     }
     D( fprintf(stderr, "\n"); )
-    local_ipaddrs[n_local_ipaddrs] = *a;
+    local_ipaddrs[n_local_ipaddrs].af = ifa->ifa_addr->sa_family;
+    local_ipaddrs[n_local_ipaddrs].addr = a;
     n_local_ipaddrs++;
   skip:;
   }