implement getaddrinfo's AI_ADDRCONFIG flag
authorRich Felker <dalias@aerifal.cx>
Sun, 15 Jul 2018 00:20:19 +0000 (20:20 -0400)
committerRich Felker <dalias@aerifal.cx>
Sun, 15 Jul 2018 00:57:24 +0000 (20:57 -0400)
this flag is notoriously under-/mis-specified, and in the past it was
implemented as a nop, essentially considering the absence of a
loopback interface with 127.0.0.1 and ::1 addresses an unsupported
configuration. however, common real-world container environments omit
IPv6 support (even for the network-namespaced loopback interface), and
some kernels omit IPv6 support entirely. future systems on the other
hand might omit IPv4 entirely.

treat these as supported configurations and suppress results of the
unconfigured/unsupported address families when AI_ADDRCONFIG is
requested. use routability of the loopback address to make the
determination; unlike other implementations, we do not exclude
loopback from the "an address is configured" condition, since there is
no basis in the specification for such exclusion. obtaining a result
with AI_ADDRCONFIG does not imply routability of the result, and
applications must still be able to cope with unroutable results even
if they pass AI_ADDRCONFIG.

src/network/getaddrinfo.c

index b9439f776fcebf41e00ed1e6fff674b7a42ea1be..ba26847a1c02e6be4578898d1b0d6a2dc55a2635 100644 (file)
@@ -3,6 +3,10 @@
 #include <netinet/in.h>
 #include <netdb.h>
 #include <string.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <endian.h>
+#include <errno.h>
 #include "lookup.h"
 
 int getaddrinfo(const char *restrict host, const char *restrict serv, const struct addrinfo *restrict hint, struct addrinfo **restrict res)
@@ -43,6 +47,41 @@ int getaddrinfo(const char *restrict host, const char *restrict serv, const stru
                }
        }
 
+       if (flags & AI_ADDRCONFIG) {
+               /* Define the "an address is configured" condition for address
+                * families via ability to create a socket for the family plus
+                * routability of the loopback address for the family. */
+               static const struct sockaddr_in lo4 = {
+                       .sin_family = AF_INET, .sin_port = 65535,
+                       .sin_addr.s_addr = __BYTE_ORDER == __BIG_ENDIAN
+                               ? 0x7f000001 : 0x0100007f
+               };
+               static const struct sockaddr_in6 lo6 = {
+                       .sin6_family = AF_INET6, .sin6_port = 65535,
+                       .sin6_addr = IN6ADDR_LOOPBACK_INIT
+               };
+               int tf[2] = { AF_INET, AF_INET6 };
+               const void *ta[2] = { &lo4, &lo6 };
+               socklen_t tl[2] = { sizeof lo4, sizeof lo6 };
+               for (i=0; i<2; i++) {
+                       if (family==tf[1-i]) continue;
+                       int s = socket(tf[i], SOCK_CLOEXEC|SOCK_DGRAM,
+                               IPPROTO_UDP);
+                       if (s>=0) {
+                               int cs;
+                               pthread_setcancelstate(
+                                       PTHREAD_CANCEL_DISABLE, &cs);
+                               int r = connect(s, ta[i], tl[i]);
+                               pthread_setcancelstate(cs, 0);
+                               close(s);
+                               if (!r) continue;
+                       }
+                       if (errno != EAFNOSUPPORT) return EAI_SYSTEM;
+                       if (family == tf[i]) return EAI_NONAME;
+                       family = tf[1-i];
+               }
+       }
+
        nservs = __lookup_serv(ports, serv, proto, socktype, flags);
        if (nservs < 0) return nservs;