diff --git a/pkg/tcpip/transport/tcp/endpoint.go b/pkg/tcpip/transport/tcp/endpoint.go index b05fdb484..7b0af1759 100644 --- a/pkg/tcpip/transport/tcp/endpoint.go +++ b/pkg/tcpip/transport/tcp/endpoint.go @@ -2059,11 +2059,16 @@ func (e *endpoint) GetSockOptInt(opt tcpip.SockOptInt) (int, tcpip.Error) { return v, nil case tcpip.MaxSegOption: - // This is just stubbed out. Linux never returns the user_mss - // value as it either returns the defaultMSS or returns the - // actual current MSS. Netstack just returns the defaultMSS - // always for now. + // Linux only returns user_mss value if user_mss is set and the socket is + // unconnected. Otherwise Linux returns the actual current MSS. Netstack + // mimics the user_mss behavior, but otherwise just returns the defaultMSS + // for now. v := header.TCPDefaultMSS + e.LockUser() + if state := e.EndpointState(); e.userMSS > 0 && (state.internal() || state == StateClose || state == StateListen) { + v = int(e.userMSS) + } + e.UnlockUser() return v, nil case tcpip.MTUDiscoverOption: diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc index 81b982089..2bce47300 100644 --- a/test/syscalls/linux/tcp_socket.cc +++ b/test/syscalls/linux/tcp_socket.cc @@ -28,6 +28,7 @@ #include #include +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/status/statusor.h" #include "absl/time/clock.h" @@ -38,6 +39,8 @@ #include "test/util/test_util.h" #include "test/util/thread_util.h" +using ::testing::AnyOf; + namespace gvisor { namespace testing { @@ -1857,17 +1860,19 @@ TEST_P(SimpleTcpSocketTest, SetMaxSeg) { sizeof(kTCPMaxSeg)), SyscallSucceedsWithValue(0)); - // Linux actually never returns the user_mss value. It will always return the - // default MSS value defined above for an unconnected socket and always return - // the actual current MSS for a connected one. int optval; socklen_t optlen = sizeof(optval); ASSERT_THAT(getsockopt(s.get(), IPPROTO_TCP, TCP_MAXSEG, &optval, &optlen), SyscallSucceedsWithValue(0)); ASSERT_EQ(optlen, sizeof(optval)); - EXPECT_EQ(kDefaultMSS, optval); - EXPECT_EQ(sizeof(optval), optlen); + // In older Linux versions, user_mss value was never actually returned. Linux + // would always return the default MSS value for an unconnected socket and + // always return the actual current MSS for a connected one. However, the + // behavior changed since 34dfde4ad87b ("tcp: Return user_mss for TCP_MAXSEG + // in CLOSE/LISTEN state if user_mss set"). With this change, user_mss is + // returned if set for unconnected sockets. So allow both. + EXPECT_THAT(optval, AnyOf(kDefaultMSS, kTCPMaxSeg)); } TEST_P(SimpleTcpSocketTest, SetMaxSegFailsForInvalidMSSValues) {