From: Mark McDuff Date: Thu, 22 Jan 2015 00:18:25 +0000 (-0800) Subject: make AsyncServerSocket bind to same port on ipv4 and ipv6 with port=0 X-Git-Tag: v0.23.0~26 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=9eeacede71b5094084492c2a817813007325bdb1;p=folly.git make AsyncServerSocket bind to same port on ipv4 and ipv6 with port=0 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 --- diff --git a/folly/io/async/AsyncServerSocket.cpp b/folly/io/async/AsyncServerSocket.cpp index ef6b8902..5b5ca619 100644 --- a/folly/io/async/AsyncServerSocket.cpp +++ b/folly/io/async/AsyncServerSocket.cpp @@ -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) { diff --git a/folly/io/async/test/AsyncSocketTest.cpp b/folly/io/async/test/AsyncSocketTest.cpp index 34971dc2..e8f3cf12 100644 --- a/folly/io/async/test/AsyncSocketTest.cpp +++ b/folly/io/async/test/AsyncSocketTest.cpp @@ -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