From 9eeacede71b5094084492c2a817813007325bdb1 Mon Sep 17 00:00:00 2001 From: Mark McDuff Date: Wed, 21 Jan 2015 16:18:25 -0800 Subject: [PATCH] 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 --- folly/io/async/AsyncServerSocket.cpp | 55 +++++++++++++++++++------ folly/io/async/test/AsyncSocketTest.cpp | 12 ++++++ 2 files changed, 54 insertions(+), 13 deletions(-) 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 -- 2.34.1