folly: AsyncServerSocket::getAddress: prefer IPv6
authorSergey Doroshenko <sdoroshenko@fb.com>
Mon, 5 Jan 2015 19:00:41 +0000 (11:00 -0800)
committerViswanath Sivakumar <viswanath@fb.com>
Tue, 13 Jan 2015 19:01:03 +0000 (11:01 -0800)
Summary:
Can't connect from ipv6-only cluster to ipv4/ipv6 service which uses different ports.

Facebook:
A lot of tests start a service, get its port, and try to connect to 127.0.0.1:port, which breaks
after this diff since the port corresponds to IPv6 address, not IPv4. Fixing this by changing
127.0.0.1 to ::1 in places found by sandcastle.

```
fbconfig servicerouter/aggregator/tests servicerouter/client/cpp/test common/fb303/cpp/test thrift/lib/cpp2/test unicorn/hotswap/test common/client_mgmt thrift/test servicerouter/client/swig/tests unicorn/async/test servicerouter/selection/tests
```

Test Plan:
```
$ ./fastcopy_server --dir . &
$ netstat -atnlp | grep LISTEN | grep fastcopy
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

Subscribers: trunkagent, vkatich, fbcode-common-diffs@, chaoyc, search-fbcode-diffs@, davejwatson, andrewcox, mcduff, hitesh, unicorn-diffs@, alandau, mshneer, folly-diffs@, bmatheny, ps, soren

FB internal diff: D1760372

Tasks: 58688185886688

Signature: t1:1760372:1419992695:e7d254b2b8f730baefc169effa236b8daa9d846d

folly/io/async/AsyncServerSocket.cpp

index 7539ac67f5e42e31248187a71d068e8b6d4b8175..ef6b890231830c9899ed253a1bcb7317e699c33b 100644 (file)
@@ -364,13 +364,13 @@ void AsyncServerSocket::bind(uint16_t port) {
       freeaddrinfo(res0);
     });
 
-  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);
@@ -397,7 +397,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");
@@ -418,10 +437,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_);
 }