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,8 +397,16 @@ void AsyncServerSocket::bind(uint16_t port) { ...@@ -397,8 +397,16 @@ 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));
}
}; };
for (int tries = 1; true; tries++) {
// Prefer AF_INET6 addresses. RFC 3484 mandates that getaddrinfo // Prefer AF_INET6 addresses. RFC 3484 mandates that getaddrinfo
// should return IPv6 first and then IPv4 addresses, but glibc's // should return IPv6 first and then IPv4 addresses, but glibc's
// getaddrinfo(nullptr) with AI_PASSIVE returns: // getaddrinfo(nullptr) with AI_PASSIVE returns:
...@@ -411,11 +419,32 @@ void AsyncServerSocket::bind(uint16_t port) { ...@@ -411,11 +419,32 @@ void AsyncServerSocket::bind(uint16_t port) {
} }
} }
try {
for (res = res0; res; res = res->ai_next) { for (res = res0; res; res = res->ai_next) {
if (res->ai_family != AF_INET6) { if (res->ai_family != AF_INET6) {
setupAddress(res); 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) {
throw std::runtime_error( throw std::runtime_error(
......
...@@ -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