Additional low-level socket API functionality Needed a bit more functionality to support the higher-level TCP socket API coming in a subsequent CL. PiperOrigin-RevId: 458307839
diff --git a/quiche/quic/core/io/socket.h b/quiche/quic/core/io/socket.h index 6750af7..748de4d 100644 --- a/quiche/quic/core/io/socket.h +++ b/quiche/quic/core/io/socket.h
@@ -9,6 +9,7 @@ #include "absl/status/statusor.h" #include "absl/strings/string_view.h" #include "absl/types/span.h" +#include "quiche/quic/core/quic_types.h" #include "quiche/quic/platform/api/quic_ip_address_family.h" #include "quiche/quic/platform/api/quic_socket_address.h" #include "quiche/common/platform/api/quiche_export.h" @@ -53,11 +54,23 @@ // `blocking` false). Must be a change from previous state. absl::Status SetSocketBlocking(SocketFd fd, bool blocking); +// Sets buffer sizes for socket `fd` to `size` bytes. +absl::Status SetReceiveBufferSize(SocketFd fd, QuicByteCount size); +absl::Status SetSendBufferSize(SocketFd fd, QuicByteCount size); + // Connects socket `fd` to `peer_address`. Returns a status with // `absl::StatusCode::kUnavailable` iff the socket is non-blocking and the -// connection could not be immediately completed. +// connection could not be immediately completed. The socket will then complete +// connecting asynchronously, and on becoming writable, the result can be +// checked using GetSocketError(). absl::Status Connect(SocketFd fd, const QuicSocketAddress& peer_address); +// Gets and clears socket error information for socket `fd`. Note that returned +// error could be either the found socket error, or unusually, an error from the +// attempt to retrieve error information. Typically used to determine connection +// result after asynchronous completion of a Connect() call. +absl::Status GetSocketError(SocketFd fd); + // Assign `address` to socket `fd`. absl::Status Bind(SocketFd fd, const QuicSocketAddress& address); @@ -83,8 +96,10 @@ // `buffer.size()` bytes. On success, returns a span pointing to the buffer // but resized to the actual number of bytes received. Returns a status with // `absl::StatusCode::kUnavailable` iff the socket is non-blocking and the -// receive operation could not be immediately completed. -absl::StatusOr<absl::Span<char>> Receive(SocketFd fd, absl::Span<char> buffer); +// receive operation could not be immediately completed. If `peek` is true, +// received data is not removed from the underlying socket data queue. +absl::StatusOr<absl::Span<char>> Receive(SocketFd fd, absl::Span<char> buffer, + bool peek = false); // Sends some or all of the data in `buffer` to socket `fd`. On success, // returns a string_view pointing to the unsent remainder of the buffer (or an
diff --git a/quiche/quic/core/io/socket_posix.cc b/quiche/quic/core/io/socket_posix.cc index 5a89c69..59f286b 100644 --- a/quiche/quic/core/io/socket_posix.cc +++ b/quiche/quic/core/io/socket_posix.cc
@@ -4,6 +4,8 @@ #include <fcntl.h> #include <sys/socket.h> +#include <climits> + #include "absl/base/attributes.h" #include "absl/container/flat_hash_set.h" #include "absl/status/status.h" @@ -212,6 +214,24 @@ } } +absl::Status SetSockOptInt(SocketFd fd, int option, int value) { + QUICHE_DCHECK_GE(fd, 0); + + int result; + do { + result = ::setsockopt(fd, SOL_SOCKET, option, &value, sizeof(value)); + } while (result < 0 && errno == EINTR); + + if (result >= 0) { + return absl::OkStatus(); + } else { + absl::Status status = ToStatus(errno, "::setsockopt()"); + QUICHE_DVLOG(1) << "Failed to set socket " << fd << " option " << option + << " to " << value << " with error: " << status; + return status; + } +} + } // namespace absl::StatusOr<SocketFd> CreateSocket(IpAddressFamily address_family, @@ -257,6 +277,20 @@ } } +absl::Status SetReceiveBufferSize(SocketFd fd, QuicByteCount size) { + QUICHE_DCHECK_GE(fd, 0); + QUICHE_DCHECK_LE(size, QuicByteCount{INT_MAX}); + + return SetSockOptInt(fd, SO_RCVBUF, static_cast<int>(size)); +} + +absl::Status SetSendBufferSize(SocketFd fd, QuicByteCount size) { + QUICHE_DCHECK_GE(fd, 0); + QUICHE_DCHECK_LE(size, QuicByteCount{INT_MAX}); + + return SetSockOptInt(fd, SO_SNDBUF, static_cast<int>(size)); +} + absl::Status Connect(SocketFd fd, const QuicSocketAddress& peer_address) { QUICHE_DCHECK_GE(fd, 0); QUICHE_DCHECK(peer_address.IsInitialized()); @@ -283,6 +317,32 @@ } } +absl::Status GetSocketError(SocketFd fd) { + QUICHE_DCHECK_GE(fd, 0); + + int socket_error = 0; + socklen_t len = sizeof(socket_error); + int sockopt_result; + do { + sockopt_result = + ::getsockopt(fd, SOL_SOCKET, SO_ERROR, &socket_error, &len); + } while (sockopt_result < 0 && errno == EINTR); + + if (sockopt_result >= 0) { + if (socket_error == 0) { + return absl::OkStatus(); + } else { + return ToStatus(socket_error, "SO_ERROR"); + } + } else { + absl::Status status = ToStatus(errno, "::getsockopt()"); + QUICHE_LOG_FIRST_N(ERROR, 100) + << "Failed to get socket error information from socket " << fd + << " with error: " << status; + return status; + } +} + absl::Status Bind(SocketFd fd, const QuicSocketAddress& address) { QUICHE_DCHECK_GE(fd, 0); QUICHE_DCHECK(address.IsInitialized()); @@ -380,13 +440,15 @@ return accept_result; } -absl::StatusOr<absl::Span<char>> Receive(SocketFd fd, absl::Span<char> buffer) { +absl::StatusOr<absl::Span<char>> Receive(SocketFd fd, absl::Span<char> buffer, + bool peek) { QUICHE_DCHECK_GE(fd, 0); QUICHE_DCHECK(!buffer.empty()); ssize_t num_read; do { - num_read = ::recv(fd, buffer.data(), buffer.size(), /*flags=*/0); + num_read = + ::recv(fd, buffer.data(), buffer.size(), /*flags=*/peek ? MSG_PEEK : 0); } while (num_read < 0 && errno == EINTR); if (num_read > 0 && static_cast<size_t>(num_read) > buffer.size()) {
diff --git a/quiche/quic/core/io/socket_test.cc b/quiche/quic/core/io/socket_test.cc index ecd611d..f738174 100644 --- a/quiche/quic/core/io/socket_test.cc +++ b/quiche/quic/core/io/socket_test.cc
@@ -56,6 +56,24 @@ EXPECT_TRUE(socket_api::Close(socket).ok()); } +TEST(SocketTest, SetReceiveBufferSize) { + SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp, + /*blocking=*/true); + + EXPECT_TRUE(socket_api::SetReceiveBufferSize(socket, /*size=*/100).ok()); + + EXPECT_TRUE(socket_api::Close(socket).ok()); +} + +TEST(SocketTest, SetSendBufferSize) { + SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp, + /*blocking=*/true); + + EXPECT_TRUE(socket_api::SetSendBufferSize(socket, /*size=*/100).ok()); + + EXPECT_TRUE(socket_api::Close(socket).ok()); +} + TEST(SocketTest, Connect) { SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp); @@ -67,6 +85,16 @@ EXPECT_TRUE(socket_api::Close(socket).ok()); } +TEST(SocketTest, GetSocketError) { + SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp, + /*blocking=*/true); + + absl::Status error = socket_api::GetSocketError(socket); + EXPECT_TRUE(error.ok()); + + EXPECT_TRUE(socket_api::Close(socket).ok()); +} + TEST(SocketTest, Bind) { SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp); @@ -134,6 +162,20 @@ EXPECT_TRUE(socket_api::Close(socket).ok()); } +TEST(SocketTest, Peek) { + // Non-blocking to avoid waiting when no data to receive. + SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp, + /*blocking=*/false); + + std::string buffer(100, 0); + absl::StatusOr<absl::Span<char>> result = + socket_api::Receive(socket, absl::MakeSpan(buffer), /*peek=*/true); + ASSERT_FALSE(result.ok()); + EXPECT_TRUE(absl::IsUnavailable(result.status())); + + EXPECT_TRUE(socket_api::Close(socket).ok()); +} + TEST(SocketTest, Send) { SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp); // UDP, so "connecting" should succeed without any listening sockets.