Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions source/common/network/address_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,32 @@
namespace Network {
namespace Address {

InstanceConstSharedPtr addressFromFd(int fd) {
sockaddr_storage ss;
socklen_t ss_len = sizeof ss;
const int rc = ::getsockname(fd, reinterpret_cast<sockaddr*>(&ss), &ss_len);
if (rc != 0) {
throw EnvoyException(fmt::format("getsockname failed for '{}': {}", fd, strerror(errno)));
}
switch (ss.ss_family) {
case AF_INET: {
ASSERT(ss_len == sizeof(sockaddr_in));
const struct sockaddr_in* sin = reinterpret_cast<const struct sockaddr_in*>(&ss);
ASSERT(AF_INET == sin->sin_family);
return InstanceConstSharedPtr(new Address::Ipv4Instance(sin));
}
case AF_INET6: {
ASSERT(ss_len == sizeof(sockaddr_in6));
const struct sockaddr_in6* sin6 = reinterpret_cast<const struct sockaddr_in6*>(&ss);
ASSERT(AF_INET6 == sin6->sin6_family);
return InstanceConstSharedPtr(new Address::Ipv6Instance(*sin6));
}
default:
throw EnvoyException(fmt::format("Unexpected family in getsockname result: {}", ss.ss_family));
}
NOT_REACHED;
}

int InstanceBase::flagsFromSocketType(SocketType type) const {
int flags = SOCK_NONBLOCK;
if (type == SocketType::Stream) {
Expand Down
7 changes: 7 additions & 0 deletions source/common/network/address_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
namespace Network {
namespace Address {

/**
* Obtain an address from a bound file descriptor. Raises an EnvoyException on failure.
* @param fd file descriptor.
* @return InstanceConstSharedPtr for bound address.
*/
InstanceConstSharedPtr addressFromFd(int fd);

/**
* Base class for all address types.
*/
Expand Down
5 changes: 5 additions & 0 deletions source/common/network/listen_socket_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ void ListenSocketImpl::doBind() {
throw EnvoyException(
fmt::format("cannot bind '{}': {}", local_address_->asString(), strerror(errno)));
}
if (local_address_->type() == Address::Type::Ip && local_address_->ip()->port() == 0) {
// If the port we bind is zero, then the OS will pick a free port for us (assuming there are
// any), and we need to find out the port number that the OS picked.
local_address_ = Address::addressFromFd(fd_);
}
}

// TODO(wattli): remove this once the admin port is updated with address.
Expand Down
9 changes: 9 additions & 0 deletions test/common/network/listen_socket_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,13 @@ TEST(ListenSocket, All) {
EXPECT_EQ("127.0.0.1:15004", socket6.localAddress()->asString());
}

// Validate we get port allocation when binding to port zero.
TEST(ListenSocket, BindPortZero) {
TcpListenSocket socket(Utility::resolveUrl("tcp://127.0.0.1:0"), true);
EXPECT_EQ(Address::Type::Ip, socket.localAddress()->type());
EXPECT_EQ("127.0.0.1", socket.localAddress()->ip()->addressAsString());
EXPECT_GT(socket.localAddress()->ip()->port(), 0U);
EXPECT_EQ(Address::IpVersion::v4, socket.localAddress()->ip()->version());
}

} // Network
33 changes: 3 additions & 30 deletions test/test_common/network_utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,37 +50,10 @@ Address::InstanceConstSharedPtr findOrCheckFreePort(Address::InstanceConstShared
<< "' with error: " << strerror(err) << " (" << err << ")";
return nullptr;
}
// If the port we bind is zero, then the OS will pick a free port for us (assuming there are
// any), and we need to find out the port number that the OS picked so we can return it.
if (addr_port->ip()->port() == 0) {
// If the port we bind is zero, then the OS will pick a free port for us (assuming there are
// any), and we need to find out the port number that the OS picked so we can return it.
sockaddr_storage ss;
socklen_t ss_len = sizeof ss;
rc = ::getsockname(fd, reinterpret_cast<sockaddr*>(&ss), &ss_len);
if (rc != 0) {
const int err = errno;
ADD_FAILURE() << "getsockname failed for '" << addr_port->asString()
<< "' with error: " << strerror(err) << " (" << err << ")";
return nullptr;
}
switch (ss.ss_family) {
case AF_INET: {
EXPECT_EQ(ss_len, sizeof(sockaddr_in));
const struct sockaddr_in* sin = reinterpret_cast<const struct sockaddr_in*>(&ss);
EXPECT_EQ(AF_INET, sin->sin_family);
addr_port.reset(new Address::Ipv4Instance(sin));
break;
}
case AF_INET6: {
EXPECT_EQ(ss_len, sizeof(sockaddr_in6));
const struct sockaddr_in6* sin6 = reinterpret_cast<const struct sockaddr_in6*>(&ss);
EXPECT_EQ(AF_INET6, sin6->sin6_family);
addr_port.reset(new Address::Ipv6Instance(*sin6));
break;
}
default:
ADD_FAILURE() << "Unexpected family in getsockname result: " << ss.ss_family;
return nullptr;
}
return Address::addressFromFd(fd);
}
return addr_port;
}
Expand Down