-
Notifications
You must be signed in to change notification settings - Fork 5.5k
connection: Add TCP_FASTOPEN listener option #2793
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
Changes from 3 commits
87bb932
cdfeb38
982a27b
7cc3768
8dcf11d
6f55c84
dea8e2c
cc211dd
d9ba97a
e56a8d7
f2db864
9f3c97e
d2d4a27
a03533a
89a3b99
8289daa
e7ff081
8f40b58
42ad7e4
f90b61f
1d5734a
9e57d17
5abe337
2b21dd5
8626895
521faad
cb298fb
4219d2e
2f47e3d
b9312fd
06d426e
f9f608a
2ca16c3
470f934
e0dbd9f
3dd9372
3f11ee6
ec0fa87
4494d2c
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 |
|---|---|---|
|
|
@@ -32,8 +32,25 @@ bool SocketOptionImpl::setOption(Socket& socket, Socket::SocketState state) cons | |
| return false; | ||
| } | ||
| } | ||
| } | ||
| } else if (state == Socket::SocketState::Listening) { | ||
|
Contributor
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. Please wrap the
Member
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. Good catch! |
||
| if (accept_tcp_fast_open_.has_value()) { | ||
| const int tfo_value = accept_tcp_fast_open_.value() ? ENVOY_TCP_FASTOPEN_BACKLOG : 0; | ||
| const SocketOptionName option_name = ENVOY_SOCKET_TCP_FASTOPEN; | ||
| int error = -1; | ||
| if (option_name) { | ||
| error = Api::OsSysCallsSingleton::get().setsockopt( | ||
| socket.fd(), IPPROTO_TCP, option_name.value(), | ||
| reinterpret_cast<const void*>(&tfo_value), sizeof(tfo_value)); | ||
| } else { | ||
| error = ENOTSUP; | ||
| } | ||
|
|
||
| if (error != 0) { | ||
| ENVOY_LOG(warn, "Setting IP_TCP_FASTOPEN on listener socket failed: {}", strerror(error)); | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,10 +41,28 @@ typedef absl::optional<int> SocketOptionName; | |
| #define ENVOY_SOCKET_IPV6_FREEBIND Network::SocketOptionName() | ||
| #endif | ||
|
|
||
| #ifdef TCP_FASTOPEN | ||
| #define ENVOY_SOCKET_TCP_FASTOPEN Network::SocketOptionName(TCP_FASTOPEN) | ||
| #else | ||
| #define ENVOY_SOCKET_TCP_FASTOPEN Network::SocketOptionName() | ||
|
Contributor
Author
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. @htuch Do you mean just enforce TCP_FASTOPEN on all listeners by default and then run the tests? |
||
| #endif | ||
|
|
||
| // On macOS the socket MUST be listening already for TCP_FASTOPEN to be set and backlog MUST be 1 | ||
| // (the actual value is set via the net.inet.tcp.fastopen_backlog kernel parameter. | ||
| // For Linux we default to 128, which libevent is using in | ||
| // https://github.com/libevent/libevent/blob/release-2.1.8-stable/listener.c#L176 | ||
| #if defined(__APPLE__) | ||
| #define ENVOY_TCP_FASTOPEN_BACKLOG 1 | ||
| #else | ||
| #define ENVOY_TCP_FASTOPEN_BACKLOG 128 | ||
| #endif | ||
|
|
||
| class SocketOptionImpl : public Socket::Option, Logger::Loggable<Logger::Id::connection> { | ||
| public: | ||
| SocketOptionImpl(absl::optional<bool> transparent, absl::optional<bool> freebind) | ||
| : transparent_(transparent), freebind_(freebind) {} | ||
| SocketOptionImpl(absl::optional<bool> transparent, absl::optional<bool> freebind, | ||
| absl::optional<bool> accept_tcp_fast_open) | ||
| : transparent_(transparent), freebind_(freebind), | ||
| accept_tcp_fast_open_(accept_tcp_fast_open) {} | ||
|
|
||
| // Socket::Option | ||
| bool setOption(Socket& socket, Socket::SocketState state) const override; | ||
|
|
@@ -72,6 +90,7 @@ class SocketOptionImpl : public Socket::Option, Logger::Loggable<Logger::Id::con | |
| private: | ||
| const absl::optional<bool> transparent_; | ||
| const absl::optional<bool> freebind_; | ||
| const absl::optional<bool> accept_tcp_fast_open_; | ||
| }; | ||
|
|
||
| } // namespace Network | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,24 @@ class SocketOptionImplTest : public testing::Test { | |
| NiceMock<MockListenSocket> socket_; | ||
| Api::MockOsSysCalls os_sys_calls_; | ||
| TestThreadsafeSingletonInjector<Api::OsSysCallsImpl> os_calls{&os_sys_calls_}; | ||
|
|
||
| void testSetSocketOptionSuccess(SocketOptionImpl& socket_option, int socket_level, | ||
| Network::SocketOptionName option_name, int option_val, | ||
| Socket::SocketState when) { | ||
| if (option_name.has_value()) { | ||
| Address::Ipv4Instance address("1.2.3.4", 5678); | ||
| const int fd = address.socket(Address::SocketType::Stream); | ||
| EXPECT_CALL(socket_, fd()).WillRepeatedly(Return(fd)); | ||
| EXPECT_CALL(os_sys_calls_, setsockopt_(_, socket_level, option_name.value(), _, sizeof(int))) | ||
| .WillOnce(Invoke([option_val](int, int, int, const void* optval, socklen_t) -> int { | ||
| EXPECT_EQ(option_val, *static_cast<const int*>(optval)); | ||
| return 0; | ||
| })); | ||
| EXPECT_TRUE(socket_option.setOption(socket_, when)); | ||
| } else { | ||
| EXPECT_FALSE(socket_option.setOption(socket_, when)); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| // We fail to set the option if the socket FD is bad. | ||
|
|
@@ -33,105 +51,63 @@ TEST_F(SocketOptionImplTest, BadFd) { | |
|
|
||
| // Nop when there are no socket options set. | ||
| TEST_F(SocketOptionImplTest, SetOptionEmptyNop) { | ||
| SocketOptionImpl socket_option{{}, {}}; | ||
| SocketOptionImpl socket_option{{}, {}, {}}; | ||
| EXPECT_TRUE(socket_option.setOption(socket_, Socket::SocketState::PreBind)); | ||
| EXPECT_TRUE(socket_option.setOption(socket_, Socket::SocketState::PostBind)); | ||
| EXPECT_TRUE(socket_option.setOption(socket_, Socket::SocketState::Listening)); | ||
| } | ||
|
|
||
| // We fail to set the option when the underlying setsockopt syscall fails. | ||
| TEST_F(SocketOptionImplTest, SetOptionTransparentFailure) { | ||
| SocketOptionImpl socket_option{true, {}}; | ||
| SocketOptionImpl socket_option{true, {}, {}}; | ||
| EXPECT_FALSE(socket_option.setOption(socket_, Socket::SocketState::PreBind)); | ||
| } | ||
|
|
||
| // We fail to set the option when the underlying setsockopt syscall fails. | ||
| TEST_F(SocketOptionImplTest, SetOptionFreebindFailure) { | ||
| SocketOptionImpl socket_option{{}, true}; | ||
| SocketOptionImpl socket_option{{}, true, {}}; | ||
| EXPECT_FALSE(socket_option.setOption(socket_, Socket::SocketState::PreBind)); | ||
| } | ||
|
|
||
| // We fail to set the tcp-fastopen option when the underlying setsockopt syscall fails. | ||
| TEST_F(SocketOptionImplTest, SetOptionTcpFastopenFailure) { | ||
| SocketOptionImpl socket_option{{}, {}, true}; | ||
| EXPECT_FALSE(socket_option.setOption(socket_, Socket::SocketState::Listening)); | ||
| } | ||
|
|
||
| // The happy path for setOption(); IP_TRANSPARENT is set to true. | ||
| TEST_F(SocketOptionImplTest, SetOptionTransparentSuccessTrue) { | ||
| SocketOptionImpl socket_option{true, {}}; | ||
| if (ENVOY_SOCKET_IP_TRANSPARENT.has_value()) { | ||
| Address::Ipv4Instance address("1.2.3.4", 5678); | ||
| const int fd = address.socket(Address::SocketType::Stream); | ||
| EXPECT_CALL(socket_, fd()).WillRepeatedly(Return(fd)); | ||
| EXPECT_CALL(os_sys_calls_, | ||
| setsockopt_(_, IPPROTO_IP, ENVOY_SOCKET_IP_TRANSPARENT.value(), _, sizeof(int))) | ||
| .WillOnce(Invoke([](int, int, int, const void* optval, socklen_t) -> int { | ||
| EXPECT_EQ(1, *static_cast<const int*>(optval)); | ||
| return 0; | ||
| })); | ||
| EXPECT_TRUE(socket_option.setOption(socket_, Socket::SocketState::PreBind)); | ||
| } else { | ||
| EXPECT_FALSE(socket_option.setOption(socket_, Socket::SocketState::PreBind)); | ||
| } | ||
| SocketOptionImpl socket_option{true, {}, {}}; | ||
| testSetSocketOptionSuccess(socket_option, IPPROTO_IP, ENVOY_SOCKET_IP_TRANSPARENT, 1, | ||
| Socket::SocketState::PreBind); | ||
| } | ||
|
|
||
| // The happy path for setOption(); IP_FREEBIND is set to true. | ||
| TEST_F(SocketOptionImplTest, SetOptionFreebindSuccessTrue) { | ||
| SocketOptionImpl socket_option{{}, true}; | ||
| if (ENVOY_SOCKET_IP_FREEBIND.has_value()) { | ||
| Address::Ipv4Instance address("1.2.3.4", 5678); | ||
| const int fd = address.socket(Address::SocketType::Stream); | ||
| EXPECT_CALL(socket_, fd()).WillRepeatedly(Return(fd)); | ||
| EXPECT_CALL(os_sys_calls_, | ||
| setsockopt_(_, IPPROTO_IP, ENVOY_SOCKET_IP_FREEBIND.value(), _, sizeof(int))) | ||
| .WillOnce(Invoke([](int, int, int, const void* optval, socklen_t) -> int { | ||
| EXPECT_EQ(1, *static_cast<const int*>(optval)); | ||
| return 0; | ||
| })); | ||
| EXPECT_TRUE(socket_option.setOption(socket_, Socket::SocketState::PreBind)); | ||
| } else { | ||
| EXPECT_FALSE(socket_option.setOption(socket_, Socket::SocketState::PreBind)); | ||
| } | ||
| SocketOptionImpl socket_option{{}, true, {}}; | ||
| testSetSocketOptionSuccess(socket_option, IPPROTO_IP, ENVOY_SOCKET_IP_FREEBIND, 1, | ||
| Socket::SocketState::PreBind); | ||
| } | ||
|
|
||
| // The happy path for setOpion(); IP_TRANSPARENT is set to false. | ||
| TEST_F(SocketOptionImplTest, SetOptionTransparentSuccessFalse) { | ||
| SocketOptionImpl socket_option{false, {}}; | ||
| if (ENVOY_SOCKET_IP_TRANSPARENT.has_value()) { | ||
| Address::Ipv4Instance address("1.2.3.4", 5678); | ||
| const int fd = address.socket(Address::SocketType::Stream); | ||
| EXPECT_CALL(socket_, fd()).WillRepeatedly(Return(fd)); | ||
| EXPECT_CALL(os_sys_calls_, | ||
| setsockopt_(_, IPPROTO_IP, ENVOY_SOCKET_IP_TRANSPARENT.value(), _, sizeof(int))) | ||
| .Times(2) | ||
| .WillRepeatedly(Invoke([](int, int, int, const void* optval, socklen_t) -> int { | ||
| EXPECT_EQ(0, *static_cast<const int*>(optval)); | ||
| return 0; | ||
| })); | ||
| EXPECT_TRUE(socket_option.setOption(socket_, Socket::SocketState::PreBind)); | ||
| EXPECT_TRUE(socket_option.setOption(socket_, Socket::SocketState::PostBind)); | ||
| } else { | ||
| EXPECT_FALSE(socket_option.setOption(socket_, Socket::SocketState::PreBind)); | ||
| EXPECT_FALSE(socket_option.setOption(socket_, Socket::SocketState::PostBind)); | ||
| } | ||
| SocketOptionImpl socket_option{false, {}, {}}; | ||
| testSetSocketOptionSuccess(socket_option, IPPROTO_IP, ENVOY_SOCKET_IP_TRANSPARENT, 0, | ||
| Socket::SocketState::PreBind); | ||
| testSetSocketOptionSuccess(socket_option, IPPROTO_IP, ENVOY_SOCKET_IP_TRANSPARENT, 0, | ||
| Socket::SocketState::PostBind); | ||
| } | ||
|
|
||
| // The happy path for setOpion(); IP_FREEBIND is set to false. | ||
| TEST_F(SocketOptionImplTest, SetOptionFreebindSuccessFalse) { | ||
| SocketOptionImpl socket_option{{}, false}; | ||
| if (ENVOY_SOCKET_IP_FREEBIND.has_value()) { | ||
| Address::Ipv4Instance address("1.2.3.4", 5678); | ||
| const int fd = address.socket(Address::SocketType::Stream); | ||
| EXPECT_CALL(socket_, fd()).WillRepeatedly(Return(fd)); | ||
| EXPECT_CALL(os_sys_calls_, | ||
| setsockopt_(_, IPPROTO_IP, ENVOY_SOCKET_IP_FREEBIND.value(), _, sizeof(int))) | ||
| .WillOnce(Invoke([](int, int, int, const void* optval, socklen_t) -> int { | ||
| EXPECT_EQ(0, *static_cast<const int*>(optval)); | ||
| return 0; | ||
| })); | ||
| EXPECT_TRUE(socket_option.setOption(socket_, Socket::SocketState::PreBind)); | ||
| } else { | ||
| EXPECT_FALSE(socket_option.setOption(socket_, Socket::SocketState::PreBind)); | ||
| } | ||
| SocketOptionImpl socket_option{{}, false, {}}; | ||
| testSetSocketOptionSuccess(socket_option, IPPROTO_IP, ENVOY_SOCKET_IP_FREEBIND, 0, | ||
|
Member
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. Nice cleanup! |
||
| Socket::SocketState::PreBind); | ||
| } | ||
|
|
||
| // Freebind settings have no effect on post-bind behavior. | ||
| TEST_F(SocketOptionImplTest, SetOptionFreebindPostBind) { | ||
| SocketOptionImpl socket_option{{}, true}; | ||
| SocketOptionImpl socket_option{{}, true, {}}; | ||
| EXPECT_TRUE(socket_option.setOption(socket_, Socket::SocketState::PostBind)); | ||
| } | ||
|
|
||
|
|
||
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 at this point we need clear comments on each of these to explain when they happen in the socket lifetime and relative to each other. Not sure why we need a
Listeningin addition toPostBindfor example.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.
Agreed.
I'll put this in the comments, but the reason for the new state is that on macOS, you cannot set this option until after listen() has been called. PostBind is run between bind() and listen().
Can you think of a better name for PostBind, now that this 3rd state is added? Or just comment it heavily and keep the name?
Uh oh!
There was an error while loading. Please reload this page.
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 it's fine to just comment. As long as there is a clear order in the comments wrt the socket creation -> accept life cycle it's all good.
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.
SocketState is documented