From: Mark Wooding Date: Wed, 6 Jun 2018 19:34:26 +0000 (+0100) Subject: noip.c: Support IPv6-mapped IPv4 addresses (nearly) correctly. X-Git-Tag: 1.2.0~18 X-Git-Url: https://git.distorted.org.uk/~mdw/preload-hacks/commitdiff_plain/a62e4eceee7363106e4b205e6446a1f476b69495 noip.c: Support IPv6-mapped IPv4 addresses (nearly) correctly. 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. --- diff --git a/noip.c b/noip.c index 1af5768..2d5e932 100644 --- 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); )