diff --git a/src/nebula_core/nebula_core_hw_interfaces/include/nebula_core_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp b/src/nebula_core/nebula_core_hw_interfaces/include/nebula_core_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp index e3d7f3b53..55c2178c8 100644 --- a/src/nebula_core/nebula_core_hw_interfaces/include/nebula_core_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp +++ b/src/nebula_core/nebula_core_hw_interfaces/include/nebula_core_hw_interfaces/nebula_hw_interfaces_common/connections/udp.hpp @@ -185,6 +185,26 @@ class UdpSocket auto buf_size = static_cast(bytes); sock_fd_.setsockopt(SOL_SOCKET, SO_RCVBUF, buf_size).value_or_throw(); + + // The kernel silently clamps SO_RCVBUF to net.core.rmem_max (default ~212 KB). + // Read back the actual value to detect this. + int actual = 0; + socklen_t len = sizeof(actual); + if (::getsockopt(sock_fd_.get(), SOL_SOCKET, SO_RCVBUF, &actual, &len) == 0) { + // The kernel internally doubles SO_RCVBUF, so `actual` is normally ~2x requested. + // When the requested value is near INT32_MAX, the doubled value may overflow or + // the kernel may round down slightly, so only flag significant differences (>4KB). + // The real clamping case (e.g. 5.4MB requested, 212KB rmem_max -> actual=424KB) + // has a difference of millions of bytes, well above this threshold. + if (actual < buf_size && (buf_size - actual) > 4096) { + throw SocketError( + "SO_RCVBUF was clamped by the kernel: requested " + std::to_string(buf_size) + + " bytes, got " + std::to_string(actual) + + " bytes. Increase net.core.rmem_max: sudo sysctl -w net.core.rmem_max=" + + std::to_string(buf_size)); + } + } + return std::move(*this); } diff --git a/src/nebula_core/nebula_core_hw_interfaces/test/common/test_udp.cpp b/src/nebula_core/nebula_core_hw_interfaces/test/common/test_udp.cpp index c8ffc0fec..02af34519 100644 --- a/src/nebula_core/nebula_core_hw_interfaces/test/common/test_udp.cpp +++ b/src/nebula_core/nebula_core_hw_interfaces/test/common/test_udp.cpp @@ -97,6 +97,33 @@ TEST(TestUdp, TestBufferResize) UsageError); } +TEST(TestUdp, TestBufferClampingDetected) +{ + auto rmem_max_maybe = read_sys_param("net.core.rmem_max"); + if (!rmem_max_maybe.has_value()) GTEST_SKIP() << rmem_max_maybe.error(); + size_t rmem_max = rmem_max_maybe.value(); + + // Nebula's configured buffer size for Pandar128E4X + constexpr size_t nebula_buf_size = 5400000; + + if (rmem_max >= nebula_buf_size) { + // On a properly configured system, requesting 5.4 MB should succeed because + // the kernel doubles SO_RCVBUF internally, so actual (10.8 MB) >= requested (5.4 MB). + ASSERT_NO_THROW( + UdpSocket::Builder(g_localhost_ip, g_host_port) + .set_socket_buffer_size(nebula_buf_size) + .bind()); + } else { + // On a system with default rmem_max (~212 KB), requesting 5.4 MB should throw + // SocketError because getsockopt will return ~2*rmem_max < 5.4 MB. + ASSERT_THROW( + UdpSocket::Builder(g_localhost_ip, g_host_port) + .set_socket_buffer_size(nebula_buf_size) + .bind(), + SocketError); + } +} + TEST(TestUdp, TestCorrectUsageIsEnforced) { // The following functions can be called in any order, any number of times