Commit 9eeacede authored by Mark McDuff's avatar Mark McDuff Committed by woo

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
parent c8238d15
...@@ -397,24 +397,53 @@ void AsyncServerSocket::bind(uint16_t port) { ...@@ -397,24 +397,53 @@ void AsyncServerSocket::bind(uint16_t port) {
errno, errno,
"failed to bind to async server socket for port"); "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 for (int tries = 1; true; tries++) {
// should return IPv6 first and then IPv4 addresses, but glibc's // Prefer AF_INET6 addresses. RFC 3484 mandates that getaddrinfo
// getaddrinfo(nullptr) with AI_PASSIVE returns: // should return IPv6 first and then IPv4 addresses, but glibc's
// - 0.0.0.0 (IPv4-only) // getaddrinfo(nullptr) with AI_PASSIVE returns:
// - :: (IPv6+IPv4) in this order // - 0.0.0.0 (IPv4-only)
// See: https://sourceware.org/bugzilla/show_bug.cgi?id=9981 // - :: (IPv6+IPv4) in this order
for (res = res0; res; res = res->ai_next) { // See: https://sourceware.org/bugzilla/show_bug.cgi?id=9981
if (res->ai_family == AF_INET6) { for (res = res0; res; res = res->ai_next) {
setupAddress(res); if (res->ai_family == AF_INET6) {
setupAddress(res);
}
} }
}
for (res = res0; res; res = res->ai_next) { try {
if (res->ai_family != AF_INET6) { for (res = res0; res; res = res->ai_next) {
setupAddress(res); 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) { if (sockets_.size() == 0) {
......
...@@ -64,4 +64,16 @@ TEST(AsyncSocketTest, REUSEPORT) { ...@@ -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 } // namespace
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment