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) {
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) {
......
......@@ -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
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