make AsyncServerSocket bind to same port on ipv4 and ipv6 with port=0
authorMark McDuff <mcduff@fb.com>
Thu, 22 Jan 2015 00:18:25 +0000 (16:18 -0800)
committerwoo <woo@fb.com>
Mon, 2 Feb 2015 21:12:34 +0000 (13:12 -0800)
Summary: I'm in unfamiliar territory, so shout if I'm doing something dumb.  Perhaps it's a bad assumption that if the ipv4 port is free that the ipv6 port is also free?

Test Plan: g-unittest

Reviewed By: davejwatson@fb.com

Subscribers: trunkagent, ps, bmatheny, folly-diffs@

FB internal diff: D1795120

Signature: t1:1795120:1422034693:bd315023ab6cd9e9bda12161d05dd781dc401546

folly/io/async/AsyncServerSocket.cpp
folly/io/async/test/AsyncSocketTest.cpp

index ef6b890231830c9899ed253a1bcb7317e699c33b..5b5ca6192e13bc428405ed7977d0f87e33c64903 100644 (file)
@@ -397,24 +397,53 @@ void AsyncServerSocket::bind(uint16_t port) {
         errno,
         "failed to bind to async server socket for port");
     }
+
+    if (port == 0) {
+      address.setFromLocalAddress(s);
+      snprintf(sport, sizeof(sport), "%u", address.getPort());
+      CHECK(!getaddrinfo(nullptr, sport, &hints, &res0));
+    }
+
   };
 
-  // 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 (int tries = 1; true; tries++) {
+    // 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);
+    try {
+      for (res = res0; res; res = res->ai_next) {
+        if (res->ai_family != AF_INET6) {
+            setupAddress(res);
+        }
+      }
+    } catch (const std::system_error& e) {
+      // if we can't bind to the same port on ipv4 as ipv6 when using port=0
+      // then we will try again another 2 times before giving up.  We do this
+      // by closing the sockets that were opened, then redoing the whole thing
+      if (port == 0 && !sockets_.empty() && tries != 3) {
+        for (const auto& socket : sockets_) {
+          if (socket.socket_ > 0) {
+            CHECK(::close(socket.socket_) == 0);
+          }
+        }
+        sockets_.clear();
+        snprintf(sport, sizeof(sport), "%u", port);
+        CHECK(!getaddrinfo(nullptr, sport, &hints, &res0));
+        continue;
+      }
+      throw;
     }
+
+    break;
   }
 
   if (sockets_.size() == 0) {
index 34971dc2efa35338cb08a009cb065fb142af224d..e8f3cf120fcf84d8440fe4bdb37e7cc747e36eef 100644 (file)
@@ -64,4 +64,16 @@ TEST(AsyncSocketTest, REUSEPORT) {
 
 }
 
+TEST(AsyncSocketTest, v4v6samePort) {
+  EventBase base;
+  auto serverSocket = AsyncServerSocket::newSocket(&base);
+  serverSocket->bind(0);
+  auto addrs = serverSocket->getAddresses();
+  ASSERT_GT(addrs.size(), 0);
+  uint16_t port = addrs[0].getPort();
+  for (const auto& addr : addrs) {
+    EXPECT_EQ(port, addr.getPort());
+  }
+}
+
 } // namespace