folly: AsyncServerSocket::getAddress: prefer IPv6
authorLucian Grijincu <lucian@fb.com>
Tue, 23 Dec 2014 10:19:34 +0000 (02:19 -0800)
committerDave Watson <davejwatson@fb.com>
Mon, 29 Dec 2014 18:40:11 +0000 (10:40 -0800)
Summary: Can't connect from ipv6-only cluster to ipv4/ipv6 service which uses different ports.

Test Plan:
```
./fastcopy_server --dir . &

# netstat -atnlp | grep LISTEN | grep fastc
tcp        0      0 0.0.0.0:65478               0.0.0.0:*                   LISTEN      9348/./fastcopy_ser
tcp        0      0 :::52793                    :::*                        LISTEN      9348/./fastcopy_ser
```

Reviewed By: philipp@fb.com, sdoroshenko@fb.com

Subscribers: ps, bmatheny, folly-diffs@

FB internal diff: D1752846

Tasks: 5868818

Signature: t1:1752846:1419043494:7cc0646882249f17258ade5ce7ae5619b13148a0

folly/io/async/AsyncServerSocket.cpp

index ea2a46d9025a282e723b74137c8f584d66711467..ba0b925d2f775a08dfbf979dd34e5d73803c311d 100644 (file)
@@ -365,13 +365,13 @@ void AsyncServerSocket::bind(uint16_t port) {
     });
   DCHECK(&guard);
 
-  for (res = res0; res; res = res->ai_next) {
+  auto setupAddress = [&] (struct addrinfo* res) {
     int s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
     // IPv6/IPv4 may not be supported by the kernel
     if (s < 0 && errno == EAFNOSUPPORT) {
-      continue;
+      return;
     }
-    CHECK(s);
+    CHECK_GE(s, 0);
 
     try {
       setupSocket(s);
@@ -398,7 +398,26 @@ void AsyncServerSocket::bind(uint16_t port) {
         errno,
         "failed to bind to async server socket for port");
     }
+  };
+
+  // Prefer AF_INET6 addresses. RFC 3484 mandates that getaddrinfo
+  // should return IPv6 first and then IPv4 addresses, but glibc's
+  // getaddrinfo(nullptr) with AI_PASSIVE returns:
+  // - 0.0.0.0 (IPv4-only)
+  // - :: (IPv6+IPv4) in this order
+  // See: https://sourceware.org/bugzilla/show_bug.cgi?id=9981
+  for (res = res0; res; res = res->ai_next) {
+    if (res->ai_family == AF_INET6) {
+      setupAddress(res);
+    }
   }
+
+  for (res = res0; res; res = res->ai_next) {
+    if (res->ai_family != AF_INET6) {
+      setupAddress(res);
+    }
+  }
+
   if (sockets_.size() == 0) {
     throw std::runtime_error(
         "did not bind any async server socket for port");
@@ -419,10 +438,10 @@ void AsyncServerSocket::listen(int backlog) {
 
 void AsyncServerSocket::getAddress(SocketAddress* addressReturn) const {
   CHECK(sockets_.size() >= 1);
-  if (sockets_.size() > 1) {
-    VLOG(2) << "Warning: getAddress can return multiple addresses, " <<
-      "but getAddress was called, so only returning the first";
-  }
+  VLOG_IF(2, sockets_.size() > 1)
+    << "Warning: getAddress() called and multiple addresses available ("
+    << sockets_.size() << "). Returning only the first one.";
+
   addressReturn->setFromLocalAddress(sockets_[0].socket_);
 }