-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
network: Udp socket implementation. #5108
Changes from 8 commits
22c6319
70c6131
070ca04
43b7538
117f47f
a994a45
cf11020
dbb25c4
bb4f7a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,66 +16,94 @@ using testing::Return; | |
namespace Envoy { | ||
namespace Network { | ||
|
||
template <Network::Address::SocketType S> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: s/S/SocketType or something spelled out. |
||
class ListenSocketImplTest : public testing::TestWithParam<Address::IpVersion> { | ||
protected: | ||
ListenSocketImplTest() : version_(GetParam()) {} | ||
const Address::IpVersion version_; | ||
|
||
template <typename... Args> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, this is really interesting - new syntax to me. I'd been suggesting something more along the lines of That'd allow you to keep the various test code inline with the test cases since things like if (NetworkSocketTrait<S>::type == Address::SocketType::Stream) { While I think having test code inline with TEST_P is nice and it would reduce the diffs, I'm going to go ahead and say your current implementation solves my code duplication concerns, so if you want to leave as-is and not rewrite the tests a third time that's Ok :-) |
||
std::unique_ptr<ListenSocketImpl> createListenSocketPtr(Args&&... args) { | ||
using NetworkSocketTraitType = NetworkSocketTrait<S>; | ||
|
||
return std::make_unique<NetworkListenSocket<NetworkSocketTraitType>>( | ||
std::forward<Args>(args)...); | ||
} | ||
|
||
void testBindSpecificPort() { | ||
auto addr_fd = Network::Test::bindFreeLoopbackPort(version_, Address::SocketType::Stream); | ||
auto addr = addr_fd.first; | ||
EXPECT_LE(0, addr_fd.second); | ||
|
||
// Confirm that we got a reasonable address and port. | ||
ASSERT_EQ(Address::Type::Ip, addr->type()); | ||
ASSERT_EQ(version_, addr->ip()->version()); | ||
ASSERT_LT(0U, addr->ip()->port()); | ||
|
||
// Release the socket and re-bind it. | ||
// WARNING: This test has a small but real risk of flaky behavior if another thread or process | ||
// should bind to our assigned port during the interval between closing the fd and re-binding. | ||
// TODO(jamessynge): Consider adding a loop or other such approach to this test so that a | ||
// bind failure (in the TcpListenSocket ctor) once isn't considered an error. | ||
EXPECT_EQ(0, close(addr_fd.second)); | ||
|
||
auto option = std::make_unique<MockSocketOption>(); | ||
auto options = std::make_shared<std::vector<Network::Socket::OptionConstSharedPtr>>(); | ||
EXPECT_CALL(*option, setOption(_, envoy::api::v2::core::SocketOption::STATE_PREBIND)) | ||
.WillOnce(Return(true)); | ||
options->emplace_back(std::move(option)); | ||
auto socket1 = createListenSocketPtr(addr, options, true); | ||
// TODO (conqerAtapple): This is unfortunate. We should be able to templatize this | ||
// instead of if block. | ||
if (NetworkSocketTrait<S>::type == Address::SocketType::Stream) { | ||
EXPECT_EQ(0, listen(socket1->fd(), 0)); | ||
} | ||
|
||
EXPECT_EQ(addr->ip()->port(), socket1->localAddress()->ip()->port()); | ||
EXPECT_EQ(addr->ip()->addressAsString(), socket1->localAddress()->ip()->addressAsString()); | ||
|
||
auto option2 = std::make_unique<MockSocketOption>(); | ||
auto options2 = std::make_shared<std::vector<Network::Socket::OptionConstSharedPtr>>(); | ||
EXPECT_CALL(*option2, setOption(_, envoy::api::v2::core::SocketOption::STATE_PREBIND)) | ||
.WillOnce(Return(true)); | ||
options2->emplace_back(std::move(option2)); | ||
// The address and port are bound already, should throw exception. | ||
EXPECT_THROW(createListenSocketPtr(addr, options2, true), EnvoyException); | ||
|
||
// Test the case of a socket with fd and given address and port. | ||
auto socket3 = createListenSocketPtr(dup(socket1->fd()), addr, nullptr); | ||
EXPECT_EQ(addr->asString(), socket3->localAddress()->asString()); | ||
} | ||
|
||
void testBindPortZero() { | ||
auto loopback = Network::Test::getCanonicalLoopbackAddress(version_); | ||
auto socket = createListenSocketPtr(loopback, nullptr, true); | ||
EXPECT_EQ(Address::Type::Ip, socket->localAddress()->type()); | ||
EXPECT_EQ(version_, socket->localAddress()->ip()->version()); | ||
EXPECT_EQ(loopback->ip()->addressAsString(), socket->localAddress()->ip()->addressAsString()); | ||
EXPECT_GT(socket->localAddress()->ip()->port(), 0U); | ||
} | ||
}; | ||
|
||
INSTANTIATE_TEST_CASE_P(IpVersions, ListenSocketImplTest, | ||
using ListenSocketImplTestTcp = ListenSocketImplTest<Network::Address::SocketType::Stream>; | ||
using ListenSocketImplTestUdp = ListenSocketImplTest<Network::Address::SocketType::Datagram>; | ||
|
||
INSTANTIATE_TEST_CASE_P(IpVersions, ListenSocketImplTestTcp, | ||
testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), | ||
TestUtility::ipTestParamsToString); | ||
|
||
INSTANTIATE_TEST_CASE_P(IpVersions, ListenSocketImplTestUdp, | ||
testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), | ||
TestUtility::ipTestParamsToString); | ||
|
||
TEST_P(ListenSocketImplTest, BindSpecificPort) { | ||
// Pick a free port. | ||
auto addr_fd = Network::Test::bindFreeLoopbackPort(version_, Address::SocketType::Stream); | ||
auto addr = addr_fd.first; | ||
EXPECT_LE(0, addr_fd.second); | ||
|
||
// Confirm that we got a reasonable address and port. | ||
ASSERT_EQ(Address::Type::Ip, addr->type()); | ||
ASSERT_EQ(version_, addr->ip()->version()); | ||
ASSERT_LT(0U, addr->ip()->port()); | ||
|
||
// Release the socket and re-bind it. | ||
// WARNING: This test has a small but real risk of flaky behavior if another thread or process | ||
// should bind to our assigned port during the interval between closing the fd and re-binding. | ||
// TODO(jamessynge): Consider adding a loop or other such approach to this test so that a | ||
// bind failure (in the TcpListenSocket ctor) once isn't considered an error. | ||
EXPECT_EQ(0, close(addr_fd.second)); | ||
|
||
auto option = std::make_unique<MockSocketOption>(); | ||
auto options = std::make_shared<std::vector<Network::Socket::OptionConstSharedPtr>>(); | ||
EXPECT_CALL(*option, setOption(_, envoy::api::v2::core::SocketOption::STATE_PREBIND)) | ||
.WillOnce(Return(true)); | ||
options->emplace_back(std::move(option)); | ||
TcpListenSocket socket1(addr, options, true); | ||
EXPECT_EQ(0, listen(socket1.fd(), 0)); | ||
EXPECT_EQ(addr->ip()->port(), socket1.localAddress()->ip()->port()); | ||
EXPECT_EQ(addr->ip()->addressAsString(), socket1.localAddress()->ip()->addressAsString()); | ||
|
||
auto option2 = std::make_unique<MockSocketOption>(); | ||
auto options2 = std::make_shared<std::vector<Network::Socket::OptionConstSharedPtr>>(); | ||
EXPECT_CALL(*option2, setOption(_, envoy::api::v2::core::SocketOption::STATE_PREBIND)) | ||
.WillOnce(Return(true)); | ||
options2->emplace_back(std::move(option2)); | ||
// The address and port are bound already, should throw exception. | ||
EXPECT_THROW(Network::TcpListenSocket socket2(addr, options2, true), EnvoyException); | ||
|
||
// Test the case of a socket with fd and given address and port. | ||
TcpListenSocket socket3(dup(socket1.fd()), addr, nullptr); | ||
EXPECT_EQ(addr->asString(), socket3.localAddress()->asString()); | ||
} | ||
TEST_P(ListenSocketImplTestTcp, BindSpecificPort) { testBindSpecificPort(); } | ||
|
||
TEST_P(ListenSocketImplTestUdp, BindSpecificPort) { testBindSpecificPort(); } | ||
|
||
// Validate that we get port allocation when binding to port zero. | ||
TEST_P(ListenSocketImplTest, BindPortZero) { | ||
auto loopback = Network::Test::getCanonicalLoopbackAddress(version_); | ||
TcpListenSocket socket(loopback, nullptr, true); | ||
EXPECT_EQ(Address::Type::Ip, socket.localAddress()->type()); | ||
EXPECT_EQ(version_, socket.localAddress()->ip()->version()); | ||
EXPECT_EQ(loopback->ip()->addressAsString(), socket.localAddress()->ip()->addressAsString()); | ||
EXPECT_GT(socket.localAddress()->ip()->port(), 0U); | ||
} | ||
TEST_P(ListenSocketImplTestTcp, BindPortZero) { testBindPortZero(); } | ||
|
||
TEST_P(ListenSocketImplTestUdp, BindPortZero) { testBindPortZero(); } | ||
|
||
} // namespace Network | ||
} // namespace Envoy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we need to call
setProtocolSpecificSocketOptions()
in this variant also? Can we somehow unify the setup logic? It's a little confusing as with the constructor variants, protocol specific options, listener options, etc.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattklein123 Thanks for reviewing.
Looking at the original code(master), the two constructors differ in -
fd
does not have an option tobind
address
sets upSO_REUSEADDR
.So wanted to keep the logic same here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I suspect that's a bug and not a feature. Can you spend a little bit of time trying to figure out where the constructors are used? I would prefer we take this opportunity to unify the logic if possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the idea is that when an
fd
is present, then we don't create the listening socket and assume that the socket is already bound (to that listenfd
). So in that case, we should not be applying the optionSO_REUSEADDR
. All other options could still be applied . Although its confusing to have flagPRE_BIND
for all the options.So maybe we should rename
setProtocolSpecificSocketOptions
tosetPrebindSocketOptions
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense, +1 please rename.