noip.c: Support IPv6-mapped IPv4 addresses (nearly) correctly.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 6 Jun 2018 19:34:26 +0000 (20:34 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 6 Jun 2018 19:34:26 +0000 (20:34 +0100)
There's a longstanding bug here.  If a program makes an `AF_INET6'
socket, and then tries to connect to a v6-mapped v4 address, then `noip'
/ought/ to match that against an existing binding of the corresponding
real IPv4 address, and /vice versa/.

Also, to support non-`IPV6_V6ONLY' server sockets, when trying to
resolve an IPv4 connection, consider the IPv6 wildcard address as a
suitable match.

There's unfortunately quite a lot here, but it all really needs to be
done in one go to work properly.

  * Add `map_ipv4_sockaddr' and `unmap_ipv4_sockaddr' functions to
    do the grunt-work of the address handling.

  * In `encode_unused_inet_addr', check that a proposed v4 socket
    address doesn't conflict with any v6-mapped and/or wildcard sockets.

  * In `encode_inet_addr', try to match v4 remote addresses against
    local v6-mapped and/or wildcard sockets.

  * Complicate `return_fake_name' to optionally v6-map an IPv4 address.

  * Introduce and deploy a new `return_fake_peer' function, which
    inspects a socket's local address to discover what its `native'
    address family is, so that it can map the remote address if
    necessary.

  * Complicate `do_implicit_bind' so that it will v6-map the local
    address it's decided on, if necessary.  This is essential now as a
    means of recording the socket's `native' address family, as used by
    `return_fake_peer'.

  * Complicate `fixup_client_socket' to unmap remote v6-mapped v4
    addresses before trying to encode them as Unix-domain addresses.

noip.c

diff --git a/noip.c b/noip.c
index 1af5768..2d5e932 100644 (file)
--- a/noip.c
+++ b/noip.c
@@ -339,6 +339,43 @@ static void copy_sockaddr(struct sockaddr *sa_dst,
                          const struct sockaddr *sa_src)
   { memcpy(sa_dst, sa_src, family_socklen(sa_src->sa_family)); }
 
+/* Convert an AF_INET socket address into the equivalent IPv4-mapped AF_INET6
+ * address.
+ */
+static void map_ipv4_sockaddr(struct sockaddr_in6 *a6,
+                             const struct sockaddr_in *a4)
+{
+  size_t i;
+  in_addr_t a = ntohl(a4->sin_addr.s_addr);
+
+  a6->sin6_family = AF_INET6;
+  a6->sin6_port = a4->sin_port;
+  a6->sin6_scope_id = 0;
+  a6->sin6_flowinfo = 0;
+  for (i = 0; i < 10; i++) a6->sin6_addr.s6_addr[i] = 0;
+  for (i = 10; i < 12; i++) a6->sin6_addr.s6_addr[i] = 0xff;
+  for (i = 0; i < 4; i++) a6->sin6_addr.s6_addr[15 - i] = (a >> 8*i)&0xff;
+}
+
+/* Convert an AF_INET6 socket address containing an IPv4-mapped IPv6 address
+ * into the equivalent AF_INET4 address.  Return zero on success, or -1 if
+ * the address has the wrong form.
+ */
+static int unmap_ipv4_sockaddr(struct sockaddr_in *a4,
+                              const struct sockaddr_in6 *a6)
+{
+  size_t i;
+  in_addr_t a;
+
+  for (i = 0; i < 10; i++) if (a6->sin6_addr.s6_addr[i] != 0) return (-1);
+  for (i = 10; i < 12; i++) if (a6->sin6_addr.s6_addr[i] != 0xff) return (-1);
+  for (i = 0, a = 0; i < 4; i++) a |= a6->sin6_addr.s6_addr[15 - i] << 8*i;
+  a4->sin_family = AF_INET;
+  a4->sin_port = a6->sin6_port;
+  a4->sin_addr.s_addr = htonl(a);
+  return (0);
+}
+
 /* Answer whether two addresses are equal. */
 static int ipaddr_equal_p(int af, const ipaddr *a, const ipaddr *b)
 {
@@ -704,7 +741,7 @@ static int encode_unused_inet_addr(struct sockaddr *sa,
                                   struct sockaddr_un *sun,
                                   int desperatep)
 {
-  address waddr;
+  address waddr, maddr;
   struct sockaddr_un wsun;
   int port = port_from_sockaddr(sa);
 
@@ -723,6 +760,25 @@ static int encode_unused_inet_addr(struct sockaddr *sa,
   if (encode_single_inet_addr(&waddr.sa, &wsun, !desperatep) == USED)
     return (-1);
 
+  /* We're not done yet.  If this is an IPv4 address, then /also/ check (a)
+   * the v6-mapped version, (b) the v6-mapped v4 wildcard, /and/ (c) the v6
+   * wildcard.  Ugh!
+   */
+  if (sa->sa_family == AF_INET) {
+    map_ipv4_sockaddr(&maddr.sin6, SIN(&sa));
+    if (encode_single_inet_addr(&maddr.sa, &wsun, !desperatep) == USED)
+      return (-1);
+
+    map_ipv4_sockaddr(&maddr.sin6, &waddr.sin);
+    if (encode_single_inet_addr(&maddr.sa, &wsun, !desperatep) == USED)
+      return (-1);
+
+    wildcard_address(AF_INET6, &waddr.sa);
+    port_to_sockaddr(&waddr.sa, port);
+    if (encode_single_inet_addr(&waddr.sa, &wsun, !desperatep) == USED)
+      return (-1);
+  }
+
   /* All is well. */
   return (0);
 }
@@ -741,6 +797,7 @@ static int encode_inet_addr(struct sockaddr_un *sun,
   int i;
   int desperatep = 0;
   address addr;
+  struct sockaddr_in6 sin6;
   int port = port_from_sockaddr(sa);
   char buf[ADDRBUFSZ];
 
@@ -759,12 +816,45 @@ static int encode_inet_addr(struct sockaddr_un *sun,
     if (encode_single_inet_addr(sa, sun, 0) == USED || (f&ENCF_FRESH))
       goto found;
 
-    /* We're looking for a socket which already exists.  Try the
-     * corresponding wildcard address.
+    /* We're looking for a socket which already exists.  This is
+     * unfortunately difficult, because we must deal both with wildcards and
+     * v6-mapped IPv4 addresses.
+     *
+     *   * We've just tried searching for a socket whose name is an exact
+     *    match for our remote address.  If the remote address is IPv4, then
+     *    we should try again with the v6-mapped equivalent.
+     *
+     *   * Failing that, we try again with the wildcard address for the
+     *    appropriate address family.
+     *
+     *   * Failing /that/, if the remote address is IPv4, then we try
+     *    /again/, increasingly desperately, first with the v6-mapped IPv4
+     *    wildcard address, and then with the IPv6 wildcard address.  This
+     *    will cause magic v6-mapping to occur when the connection is
+     *    accepted, which we hope won't cause too much trouble.
      */
+
+    if (sa->sa_family == AF_INET) {
+      map_ipv4_sockaddr(&addr.sin6, SIN(sa));
+      if (encode_single_inet_addr(&addr.sa, sun, 0) == USED) goto found;
+    }
+
     wildcard_address(sa->sa_family, &addr.sa);
     port_to_sockaddr(&addr.sa, port);
-    encode_single_inet_addr(&addr.sa, sun, 0);
+    if (encode_single_inet_addr(&addr.sa, sun, 0) == USED) goto found;
+
+    if (sa->sa_family == AF_INET) {
+      map_ipv4_sockaddr(&sin6, &addr.sin);
+      if (encode_single_inet_addr(SA(&sin6), sun, 0) == USED) goto found;
+      wildcard_address(AF_INET6, &addr.sa);
+      port_to_sockaddr(&addr.sa, port);
+      if (encode_single_inet_addr(&addr.sa, sun, 0) == USED) goto found;
+    }
+
+    /* Well, this isn't going to work (unless a miraculous race is lost), but
+     * we might as well try.
+     */
+    encode_single_inet_addr(sa, sun, 1);
 
   } else {
     /* We want a fresh new socket. */
@@ -912,16 +1002,25 @@ static int fixup_real_ip_socket(int sk, int af_hint, int *tmp)
  * deception.  Whatever happens, put the result at FAKE and store its length
  * at FAKELEN.
  */
+#define FNF_V6MAPPED 1u
 static void return_fake_name(struct sockaddr *sa, socklen_t len,
-                            struct sockaddr *fake, socklen_t *fakelen)
+                            struct sockaddr *fake, socklen_t *fakelen,
+                            unsigned f)
 {
   address addr;
+  struct sockaddr_in6 sin6;
   socklen_t alen;
 
   if (sa->sa_family == AF_UNIX &&
       !decode_inet_addr(&addr.sa, 0, SUN(sa), len)) {
-    sa = &addr.sa;
-    len = family_socklen(addr.sa.sa_family);
+    if (addr.sa.sa_family != AF_INET || !(f&FNF_V6MAPPED)) {
+      sa = &addr.sa;
+      len = family_socklen(addr.sa.sa_family);
+    } else {
+      map_ipv4_sockaddr(&sin6, &addr.sin);
+      sa = SA(&sin6);
+      len = family_socklen(AF_INET6);
+    }
   }
   alen = len;
   if (len > *fakelen) len = *fakelen;
@@ -929,6 +1028,30 @@ static void return_fake_name(struct sockaddr *sa, socklen_t len,
   *fakelen = alen;
 }
 
+/* Variant of `return_fake_name' above, specifically handling the weirdness
+ * of remote v6-mapped IPv4 addresses.  If SK's fake local address is IPv6,
+ * and the remote address is IPv4, then return a v6-mapped version of the
+ * remote address.
+ */
+static void return_fake_peer(int sk, struct sockaddr *sa, socklen_t len,
+                            struct sockaddr *fake, socklen_t *fakelen)
+{
+  char sabuf[1024];
+  socklen_t mylen = sizeof(sabuf);
+  unsigned fnf = 0;
+  address addr;
+  int rc;
+
+  PRESERVING_ERRNO({
+    rc = real_getsockname(sk, SA(sabuf), &mylen);
+    if (!rc && sa->sa_family == AF_UNIX &&
+       !decode_inet_addr(&addr.sa, 0, SUN(sabuf), mylen) &&
+       addr.sa.sa_family == AF_INET6)
+      fnf |= FNF_V6MAPPED;
+  });
+  return_fake_name(sa, len, fake, fakelen, fnf);
+}
+
 /*----- Implicit binding --------------------------------------------------*/
 
 #ifdef DEBUG
@@ -962,10 +1085,19 @@ static void dump_impbind_list(void)
 /* The socket SK is about to be used to communicate with the remote address
  * SA.  Assign it a local address so that getpeername(2) does something
  * useful.
+ *
+ * If the flag `IBF_V6MAPPED' is set then, then SA must be an `AF_INET'
+ * address; after deciding on the appropriate local address, convert it to be
+ * an IPv4-mapped IPv6 address before final conversion to a Unix-domain
+ * socket address and actually binding.  Note that this could well mean that
+ * the socket ends up bound to the v6-mapped v4 wildcard address
+ * ::ffff:0.0.0.0, which looks very strange but is meaningful.
  */
-static int do_implicit_bind(int sk, const struct sockaddr *sa)
+#define IBF_V6MAPPED 1u
+static int do_implicit_bind(int sk, const struct sockaddr *sa, unsigned f)
 {
   address addr;
+  struct sockaddr_in6 sin6;
   struct sockaddr_un sun;
   const impbind *i;
   Dpid;
@@ -984,7 +1116,9 @@ static int do_implicit_bind(int sk, const struct sockaddr *sa)
   D( fprintf(stderr, "noip(%d): no match; using wildcard\n", pid); )
   wildcard_address(sa->sa_family, &addr.sa);
 found:
-  encode_inet_addr(&sun, &addr.sa, ENCF_FRESH);
+  if (addr.sa.sa_family != AF_INET || !(f&IBF_V6MAPPED)) sa = &addr.sa;
+  else { map_ipv4_sockaddr(&sin6, &addr.sin); sa = SA(&sin6); }
+  encode_inet_addr(&sun, sa, ENCF_FRESH);
   D( fprintf(stderr, "noip(%d): implicitly binding to %s\n",
             pid, sun.sun_path); )
   if (real_bind(sk, SA(&sun), SUN_LEN(&sun))) return (-1);
@@ -1001,14 +1135,25 @@ found:
 static int fixup_client_socket(int sk, const struct sockaddr **sa_r,
                               socklen_t *len_r, struct sockaddr_un *sun)
 {
+  struct sockaddr_in sin;
   socklen_t mylen = sizeof(*sun);
   const struct sockaddr *sa = *sa_r;
+  unsigned ibf = 0;
 
   /* If this isn't a Unix-domain socket then there's nothing to do. */
   if (real_getsockname(sk, SA(sun), &mylen) < 0) return (-1);
   if (sun->sun_family != AF_UNIX) return (0);
   if (mylen < sizeof(*sun)) ((char *)sun)[mylen] = 0;
 
+  /* If the remote address is v6-mapped IPv4, then unmap it so as to search
+   * for IPv4 servers.  Also remember to v6-map the local address when we
+   * autobind.
+   */
+  if (sa->sa_family == AF_INET6 && !(unmap_ipv4_sockaddr(&sin, SIN6(sa)))) {
+    sa = SA(&sin);
+    ibf |= IBF_V6MAPPED;
+  }
+
   /* If we're allowed to talk to a real remote endpoint, then fix things up
    * as necessary and proceed.
    */
@@ -1020,7 +1165,7 @@ static int fixup_client_socket(int sk, const struct sockaddr **sa_r,
   /* Speaking of which, if we don't have a local address, then we should
    * arrange one now.
    */
-  if (!sun->sun_path[0] && do_implicit_bind(sk, sa)) return (-1);
+  if (!sun->sun_path[0] && do_implicit_bind(sk, sa, ibf)) return (-1);
 
   /* And then come up with a remote address. */
   encode_inet_addr(sun, sa, 0);
@@ -1671,7 +1816,7 @@ ssize_t recvfrom(int sk, void *buf, size_t len, int flags,
       n = real_recvfrom(sk, buf, len, flags, SA(sabuf), &mylen);
       if (n >= 0) {
        D( fprintf(stderr, " -> converting...\n"); )
-       return_fake_name(SA(sabuf), mylen, from, fromlen);
+       return_fake_peer(sk, SA(sabuf), mylen, from, fromlen);
        D( fprintf(stderr, "noip(%d): ... RECVFROM", pid); )
       }
     });
@@ -1735,7 +1880,7 @@ ssize_t recvmsg(int sk, struct msghdr *msg, int flags)
       n = real_recvmsg(sk, msg, flags);
       if (n >= 0) {
        D( fprintf(stderr, " -> converting...\n"); )
-       return_fake_name(SA(sabuf), msg->msg_namelen, sa, &len);
+       return_fake_peer(sk, SA(sabuf), msg->msg_namelen, sa, &len);
        D( fprintf(stderr, "noip(%d): ... RECVMSG", pid); )
       }
       msg->msg_name = sa;
@@ -1760,7 +1905,7 @@ int accept(int sk, struct sockaddr *sa, socklen_t *len)
   else if (!sa) D( fprintf(stderr, " -> address not wanted"); )
   else {
     D( fprintf(stderr, " -> converting...\n"); )
-    return_fake_name(SA(sabuf), mylen, sa, len);
+    return_fake_peer(sk, SA(sabuf), mylen, sa, len);
     D( fprintf(stderr, "noip(%d): ... ACCEPT", pid); )
   }
   D( dump_addrresult(nsk, sa, len ? *len : 0); )
@@ -1778,7 +1923,7 @@ int getsockname(int sk, struct sockaddr *sa, socklen_t *len)
   rc = real_getsockname(sk, SA(sabuf), &mylen);
   if (rc >= 0) {
     D( fprintf(stderr, " -> converting...\n"); )
-    return_fake_name(SA(sabuf), mylen, sa, len);
+    return_fake_name(SA(sabuf), mylen, sa, len, 0);
     D( fprintf(stderr, "noip(%d): ... GETSOCKNAME", pid); )
   }
   D( dump_addrresult(rc, sa, *len); )
@@ -1796,7 +1941,7 @@ int getpeername(int sk, struct sockaddr *sa, socklen_t *len)
   rc = real_getpeername(sk, SA(sabuf), &mylen);
   if (rc >= 0) {
     D( fprintf(stderr, " -> converting...\n"); )
-    return_fake_name(SA(sabuf), mylen, sa, len);
+    return_fake_peer(sk, SA(sabuf), mylen, sa, len);
     D( fprintf(stderr, "noip(%d): ... GETPEERNAME", pid); )
   }
   D( dump_addrresult(rc, sa, *len); )