Roll forward of changelist 458083483 with fixes
Original had some minor bugs in the sections #if'ed out on Linux. Fixed and confirmed those fixes by building and running the tests with the #if's commented up to run the non-Linux code on Linux.
(Note to self: When not developing in Chrome, there's a whole lot less presubmit support to make sure things still build on other platforms, so pay more attention to platform-specific code.)
*** Original change description ***
Create low-level socket library
Acts to hide platform-specific details. Fills a similar role (and is written with a similar interface) as QuicUdpSocketApi, except a little more general, less specific to UDP and QUIC's usage of UDP (and not named specifically "UDP" to avoid silliness like using QuicUdpSocketFd...
***
PiperOrigin-RevId: 458235267
diff --git a/build/source_list.bzl b/build/source_list.bzl
index e4570e6..0bb50d2 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -659,6 +659,7 @@
"quic/core/uber_quic_stream_id_manager.cc",
"quic/core/uber_received_packet_manager.cc",
"quic/platform/api/quic_ip_address.cc",
+ "quic/platform/api/quic_ip_address_family.cc",
"quic/platform/api/quic_socket_address.cc",
"spdy/core/array_output_buffer.cc",
"spdy/core/hpack/hpack_constants.cc",
@@ -939,6 +940,7 @@
"quic/core/io/quic_default_event_loop.h",
"quic/core/io/quic_event_loop.h",
"quic/core/io/quic_poll_event_loop.h",
+ "quic/core/io/socket.h",
"quic/core/quic_default_packet_writer.h",
"quic/core/quic_epoll_alarm_factory.h",
"quic/core/quic_epoll_clock.h",
@@ -971,6 +973,7 @@
"quic/core/batch_writer/quic_sendmmsg_batch_writer.cc",
"quic/core/io/quic_default_event_loop.cc",
"quic/core/io/quic_poll_event_loop.cc",
+ "quic/core/io/socket_posix.cc",
"quic/core/quic_default_packet_writer.cc",
"quic/core/quic_epoll_alarm_factory.cc",
"quic/core/quic_epoll_clock.cc",
@@ -1286,6 +1289,7 @@
"quic/core/http/quic_spdy_server_stream_base_test.cc",
"quic/core/io/quic_all_event_loops_test.cc",
"quic/core/io/quic_poll_event_loop_test.cc",
+ "quic/core/io/socket_test.cc",
"quic/core/quic_epoll_alarm_factory_test.cc",
"quic/core/quic_epoll_clock_test.cc",
"quic/core/quic_epoll_connection_helper_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 738fe2c..018f0fe 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -659,6 +659,7 @@
"src/quiche/quic/core/uber_quic_stream_id_manager.cc",
"src/quiche/quic/core/uber_received_packet_manager.cc",
"src/quiche/quic/platform/api/quic_ip_address.cc",
+ "src/quiche/quic/platform/api/quic_ip_address_family.cc",
"src/quiche/quic/platform/api/quic_socket_address.cc",
"src/quiche/spdy/core/array_output_buffer.cc",
"src/quiche/spdy/core/hpack/hpack_constants.cc",
@@ -939,6 +940,7 @@
"src/quiche/quic/core/io/quic_default_event_loop.h",
"src/quiche/quic/core/io/quic_event_loop.h",
"src/quiche/quic/core/io/quic_poll_event_loop.h",
+ "src/quiche/quic/core/io/socket.h",
"src/quiche/quic/core/quic_default_packet_writer.h",
"src/quiche/quic/core/quic_epoll_alarm_factory.h",
"src/quiche/quic/core/quic_epoll_clock.h",
@@ -971,6 +973,7 @@
"src/quiche/quic/core/batch_writer/quic_sendmmsg_batch_writer.cc",
"src/quiche/quic/core/io/quic_default_event_loop.cc",
"src/quiche/quic/core/io/quic_poll_event_loop.cc",
+ "src/quiche/quic/core/io/socket_posix.cc",
"src/quiche/quic/core/quic_default_packet_writer.cc",
"src/quiche/quic/core/quic_epoll_alarm_factory.cc",
"src/quiche/quic/core/quic_epoll_clock.cc",
@@ -1286,6 +1289,7 @@
"src/quiche/quic/core/http/quic_spdy_server_stream_base_test.cc",
"src/quiche/quic/core/io/quic_all_event_loops_test.cc",
"src/quiche/quic/core/io/quic_poll_event_loop_test.cc",
+ "src/quiche/quic/core/io/socket_test.cc",
"src/quiche/quic/core/quic_epoll_alarm_factory_test.cc",
"src/quiche/quic/core/quic_epoll_clock_test.cc",
"src/quiche/quic/core/quic_epoll_connection_helper_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index c8c1513..8566044 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -658,6 +658,7 @@
"quiche/quic/core/uber_quic_stream_id_manager.cc",
"quiche/quic/core/uber_received_packet_manager.cc",
"quiche/quic/platform/api/quic_ip_address.cc",
+ "quiche/quic/platform/api/quic_ip_address_family.cc",
"quiche/quic/platform/api/quic_socket_address.cc",
"quiche/spdy/core/array_output_buffer.cc",
"quiche/spdy/core/hpack/hpack_constants.cc",
@@ -938,6 +939,7 @@
"quiche/quic/core/io/quic_default_event_loop.h",
"quiche/quic/core/io/quic_event_loop.h",
"quiche/quic/core/io/quic_poll_event_loop.h",
+ "quiche/quic/core/io/socket.h",
"quiche/quic/core/quic_default_packet_writer.h",
"quiche/quic/core/quic_epoll_alarm_factory.h",
"quiche/quic/core/quic_epoll_clock.h",
@@ -970,6 +972,7 @@
"quiche/quic/core/batch_writer/quic_sendmmsg_batch_writer.cc",
"quiche/quic/core/io/quic_default_event_loop.cc",
"quiche/quic/core/io/quic_poll_event_loop.cc",
+ "quiche/quic/core/io/socket_posix.cc",
"quiche/quic/core/quic_default_packet_writer.cc",
"quiche/quic/core/quic_epoll_alarm_factory.cc",
"quiche/quic/core/quic_epoll_clock.cc",
@@ -1285,6 +1288,7 @@
"quiche/quic/core/http/quic_spdy_server_stream_base_test.cc",
"quiche/quic/core/io/quic_all_event_loops_test.cc",
"quiche/quic/core/io/quic_poll_event_loop_test.cc",
+ "quiche/quic/core/io/socket_test.cc",
"quiche/quic/core/quic_epoll_alarm_factory_test.cc",
"quiche/quic/core/quic_epoll_clock_test.cc",
"quiche/quic/core/quic_epoll_connection_helper_test.cc",
diff --git a/quiche/quic/core/io/socket.h b/quiche/quic/core/io/socket.h
new file mode 100644
index 0000000..6750af7
--- /dev/null
+++ b/quiche/quic/core/io/socket.h
@@ -0,0 +1,102 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_IO_SOCKET_H_
+#define QUICHE_QUIC_CORE_IO_SOCKET_H_
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.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"
+
+#if defined(_WIN32)
+#include <winsock2.h>
+#endif // defined(_WIN32)
+
+namespace quic {
+
+#if defined(_WIN32)
+using SocketFd = SOCKET;
+inline constexpr SocketFd kInvalidSocketFd = INVALID_SOCKET;
+#else
+using SocketFd = int;
+inline constexpr SocketFd kInvalidSocketFd = -1;
+#endif
+
+// Low-level platform-agnostic socket operations. Closely follows the behavior
+// of basic POSIX socket APIs, diverging mostly only to convert to/from cleaner
+// and platform-agnostic types.
+namespace socket_api {
+enum class SocketProtocol {
+ kUdp,
+ kTcp,
+};
+
+struct QUICHE_EXPORT_PRIVATE AcceptResult {
+ // Socket for interacting with the accepted connection.
+ SocketFd fd;
+
+ // Address of the connected peer.
+ QuicSocketAddress peer_address;
+};
+
+// Creates a socket with blocking or non-blocking behavior.
+absl::StatusOr<SocketFd> CreateSocket(IpAddressFamily address_family,
+ SocketProtocol protocol,
+ bool blocking = false);
+
+// Sets socket `fd` to blocking (if `blocking` true) or non-blocking (if
+// `blocking` false). Must be a change from previous state.
+absl::Status SetSocketBlocking(SocketFd fd, bool blocking);
+
+// 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.
+absl::Status Connect(SocketFd fd, const QuicSocketAddress& peer_address);
+
+// Assign `address` to socket `fd`.
+absl::Status Bind(SocketFd fd, const QuicSocketAddress& address);
+
+// Gets the address assigned to socket `fd`.
+absl::StatusOr<QuicSocketAddress> GetSocketAddress(SocketFd fd);
+
+// Marks socket `fd` as a passive socket listening for connection requests.
+// `backlog` is the maximum number of queued connection requests. Typically
+// expected to return a status with `absl::StatusCode::InvalidArgumentError`
+// if `fd` is not a TCP socket.
+absl::Status Listen(SocketFd fd, int backlog);
+
+// Accepts an incoming connection to the listening socket `fd`. The returned
+// connection socket will be set as non-blocking iff `blocking` is false.
+// Typically expected to return a status with
+// `absl::StatusCode::InvalidArgumentError` if `fd` is not a TCP socket or not
+// listening for connections. Returns a status with
+// `absl::StatusCode::kUnavailable` iff the socket is non-blocking and no
+// incoming connection could be immediately accepted.
+absl::StatusOr<AcceptResult> Accept(SocketFd fd, bool blocking = false);
+
+// Receives data from socket `fd`. Will fill `buffer.data()` with up to
+// `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);
+
+// 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
+// empty string_view if all of `buffer` was successfully sent). Returns a status
+// with `absl::StatusCode::kUnavailable` iff the socket is non-blocking and the
+// send operation could not be immediately completed.
+absl::StatusOr<absl::string_view> Send(SocketFd fd, absl::string_view buffer);
+
+// Closes socket `fd`.
+absl::Status Close(SocketFd fd);
+} // namespace socket_api
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_IO_SOCKET_H_
diff --git a/quiche/quic/core/io/socket_posix.cc b/quiche/quic/core/io/socket_posix.cc
new file mode 100644
index 0000000..5a89c69
--- /dev/null
+++ b/quiche/quic/core/io/socket_posix.cc
@@ -0,0 +1,452 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include <fcntl.h>
+#include <sys/socket.h>
+
+#include "absl/base/attributes.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/io/socket.h"
+#include "quiche/quic/platform/api/quic_ip_address_family.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+
+namespace quic::socket_api {
+
+namespace {
+
+int ToPlatformSocketType(SocketProtocol protocol) {
+ switch (protocol) {
+ case SocketProtocol::kUdp:
+ return SOCK_DGRAM;
+ case SocketProtocol::kTcp:
+ return SOCK_STREAM;
+ }
+
+ QUICHE_NOTREACHED();
+ return -1;
+}
+
+int ToPlatformProtocol(SocketProtocol protocol) {
+ switch (protocol) {
+ case SocketProtocol::kUdp:
+ return IPPROTO_UDP;
+ case SocketProtocol::kTcp:
+ return IPPROTO_TCP;
+ }
+
+ QUICHE_NOTREACHED();
+ return -1;
+}
+
+// Wrapper of absl::ErrnoToStatus that ensures the `unavailable_error_numbers`
+// and only those numbers result in `absl::StatusCode::kUnavailable`, converting
+// any other would-be-unavailable Statuses to `absl::StatusCode::kNotFound`.
+absl::Status ToStatus(int error_number, absl::string_view method_name,
+ absl::flat_hash_set<int> unavailable_error_numbers = {
+ EAGAIN, EWOULDBLOCK}) {
+ QUICHE_DCHECK_NE(error_number, 0);
+ QUICHE_DCHECK_NE(error_number, EINTR);
+
+ absl::Status status = absl::ErrnoToStatus(error_number, method_name);
+ QUICHE_DCHECK(!status.ok());
+
+ if (!absl::IsUnavailable(status) &&
+ unavailable_error_numbers.contains(error_number)) {
+ status = absl::UnavailableError(status.message());
+ } else if (absl::IsUnavailable(status) &&
+ !unavailable_error_numbers.contains(error_number)) {
+ status = absl::NotFoundError(status.message());
+ }
+
+ return status;
+}
+
+absl::Status SetSocketFlags(SocketFd fd, int to_add, int to_remove) {
+ QUICHE_DCHECK_GE(fd, 0);
+ QUICHE_DCHECK(to_add || to_remove);
+ QUICHE_DCHECK(!(to_add & to_remove));
+
+ int flags;
+ do {
+ flags = ::fcntl(fd, F_GETFL);
+ } while (flags < 0 && errno == EINTR);
+ if (flags < 0) {
+ absl::Status status = ToStatus(errno, "::fcntl()");
+ QUICHE_LOG_FIRST_N(ERROR, 100)
+ << "Could not get flags for socket " << fd << " with error: " << status;
+ return status;
+ }
+
+ QUICHE_DCHECK(!(flags & to_add) || (flags & to_remove));
+
+ int fcntl_result;
+ do {
+ fcntl_result = ::fcntl(fd, F_SETFL, (flags | to_add) & ~to_remove);
+ } while (fcntl_result < 0 && errno == EINTR);
+ if (fcntl_result < 0) {
+ absl::Status status = ToStatus(errno, "::fcntl()");
+ QUICHE_LOG_FIRST_N(ERROR, 100)
+ << "Could not set flags for socket " << fd << " with error: " << status;
+ return status;
+ }
+
+ return absl::OkStatus();
+}
+
+absl::StatusOr<QuicSocketAddress> ValidateAndConvertAddress(
+ const sockaddr_storage& addr, socklen_t addr_len) {
+ if (addr.ss_family != AF_INET && addr.ss_family != AF_INET6) {
+ QUICHE_DVLOG(1) << "Socket did not have recognized address family: "
+ << addr.ss_family;
+ return absl::UnimplementedError("Unrecognized address family.");
+ }
+
+ if ((addr.ss_family == AF_INET && addr_len != sizeof(sockaddr_in)) ||
+ (addr.ss_family == AF_INET6 && addr_len != sizeof(sockaddr_in6))) {
+ QUICHE_DVLOG(1) << "Socket did not have expected address size ("
+ << (addr.ss_family == AF_INET ? sizeof(sockaddr_in)
+ : sizeof(sockaddr_in6))
+ << "), had: " << addr_len;
+ return absl::UnimplementedError("Unhandled address size.");
+ }
+
+ return QuicSocketAddress(addr);
+}
+
+absl::StatusOr<SocketFd> CreateSocketWithFlags(IpAddressFamily address_family,
+ SocketProtocol protocol,
+ int flags) {
+ int address_family_int = ToPlatformAddressFamily(address_family);
+
+ int type_int = ToPlatformSocketType(protocol);
+ type_int |= flags;
+
+ int protocol_int = ToPlatformProtocol(protocol);
+
+ SocketFd fd;
+ do {
+ fd = ::socket(address_family_int, type_int, protocol_int);
+ } while (fd < 0 && errno == EINTR);
+
+ if (fd >= 0) {
+ return fd;
+ } else {
+ absl::Status status = ToStatus(errno, "::socket()");
+ QUICHE_LOG_FIRST_N(ERROR, 100)
+ << "Failed to create socket with error: " << status;
+ return status;
+ }
+}
+
+absl::StatusOr<AcceptResult> AcceptInternal(SocketFd fd) {
+ QUICHE_DCHECK_GE(fd, 0);
+
+ sockaddr_storage peer_addr;
+ socklen_t peer_addr_len = sizeof(peer_addr);
+ SocketFd connection_socket;
+ do {
+ connection_socket = ::accept(
+ fd, reinterpret_cast<struct sockaddr*>(&peer_addr), &peer_addr_len);
+ } while (connection_socket < 0 && errno == EINTR);
+
+ if (connection_socket < 0) {
+ absl::Status status = ToStatus(errno, "::accept()");
+ QUICHE_DVLOG(1) << "Failed to accept connection from socket " << fd
+ << " with error: " << status;
+ return status;
+ }
+
+ absl::StatusOr<QuicSocketAddress> peer_address =
+ ValidateAndConvertAddress(peer_addr, peer_addr_len);
+
+ if (peer_address.ok()) {
+ return AcceptResult{connection_socket, peer_address.value()};
+ } else {
+ return peer_address.status();
+ }
+}
+
+#if defined(__linux__) && defined(SOCK_NONBLOCK)
+absl::StatusOr<AcceptResult> AcceptWithFlags(SocketFd fd, int flags) {
+ QUICHE_DCHECK_GE(fd, 0);
+
+ sockaddr_storage peer_addr;
+ socklen_t peer_addr_len = sizeof(peer_addr);
+ SocketFd connection_socket;
+ do {
+ connection_socket =
+ ::accept4(fd, reinterpret_cast<struct sockaddr*>(&peer_addr),
+ &peer_addr_len, flags);
+ } while (connection_socket < 0 && errno == EINTR);
+
+ if (connection_socket < 0) {
+ absl::Status status = ToStatus(errno, "::accept4()");
+ QUICHE_DVLOG(1) << "Failed to accept connection from socket " << fd
+ << " with error: " << status;
+ return status;
+ }
+
+ absl::StatusOr<QuicSocketAddress> peer_address =
+ ValidateAndConvertAddress(peer_addr, peer_addr_len);
+
+ if (peer_address.ok()) {
+ return AcceptResult{connection_socket, peer_address.value()};
+ } else {
+ return peer_address.status();
+ }
+}
+#endif // defined(__linux__) && defined(SOCK_NONBLOCK)
+
+socklen_t GetAddrlen(IpAddressFamily family) {
+ switch (family) {
+ case IpAddressFamily::IP_V4:
+ return sizeof(sockaddr_in);
+ case IpAddressFamily::IP_V6:
+ return sizeof(sockaddr_in6);
+ default:
+ QUICHE_NOTREACHED();
+ return 0;
+ }
+}
+
+} // namespace
+
+absl::StatusOr<SocketFd> CreateSocket(IpAddressFamily address_family,
+ SocketProtocol protocol, bool blocking) {
+ int flags = 0;
+#if defined(__linux__) && defined(SOCK_NONBLOCK)
+ if (!blocking) {
+ flags = SOCK_NONBLOCK;
+ }
+#endif
+
+ absl::StatusOr<SocketFd> socket =
+ CreateSocketWithFlags(address_family, protocol, flags);
+ if (!socket.ok() || blocking) {
+ return socket;
+ }
+
+#if !defined(__linux__) || !defined(SOCK_NONBLOCK)
+ // If non-blocking could not be set directly on socket creation, need to do
+ // it now.
+ absl::Status set_non_blocking_result =
+ SetSocketBlocking(socket.value(), /*blocking=*/false);
+ if (!set_non_blocking_result.ok()) {
+ QUICHE_LOG_FIRST_N(ERROR, 100) << "Failed to set socket " << socket.value()
+ << " as non-blocking on creation.";
+ if (!Close(socket.value()).ok()) {
+ QUICHE_LOG_FIRST_N(ERROR, 100)
+ << "Failed to close socket " << socket.value()
+ << " after set-non-blocking error on creation.";
+ }
+ return set_non_blocking_result;
+ }
+#endif
+
+ return socket;
+}
+
+absl::Status SetSocketBlocking(SocketFd fd, bool blocking) {
+ if (blocking) {
+ return SetSocketFlags(fd, /*to_add=*/0, /*to_remove=*/O_NONBLOCK);
+ } else {
+ return SetSocketFlags(fd, /*to_add=*/O_NONBLOCK, /*to_remove=*/0);
+ }
+}
+
+absl::Status Connect(SocketFd fd, const QuicSocketAddress& peer_address) {
+ QUICHE_DCHECK_GE(fd, 0);
+ QUICHE_DCHECK(peer_address.IsInitialized());
+
+ sockaddr_storage addr = peer_address.generic_address();
+ socklen_t addrlen = GetAddrlen(peer_address.host().address_family());
+
+ int connect_result;
+ do {
+ connect_result = ::connect(fd, reinterpret_cast<sockaddr*>(&addr), addrlen);
+ } while (connect_result < 0 && errno == EINTR);
+
+ if (connect_result >= 0) {
+ return absl::OkStatus();
+ } else {
+ // For ::connect(), only `EINPROGRESS` indicates unavailable.
+ absl::Status status =
+ ToStatus(errno, "::connect()", /*unavailable_error_numbers=*/
+ {EINPROGRESS});
+ QUICHE_DVLOG(1) << "Failed to connect socket " << fd
+ << " to address: " << peer_address.ToString()
+ << " with error: " << status;
+ return status;
+ }
+}
+
+absl::Status Bind(SocketFd fd, const QuicSocketAddress& address) {
+ QUICHE_DCHECK_GE(fd, 0);
+ QUICHE_DCHECK(address.IsInitialized());
+
+ sockaddr_storage addr = address.generic_address();
+ socklen_t addr_len = GetAddrlen(address.host().address_family());
+
+ int result;
+ do {
+ result = ::bind(fd, reinterpret_cast<sockaddr*>(&addr), addr_len);
+ } while (result < 0 && errno == EINTR);
+
+ if (result >= 0) {
+ return absl::OkStatus();
+ } else {
+ absl::Status status = ToStatus(errno, "::bind()");
+ QUICHE_DVLOG(1) << "Failed to bind socket " << fd
+ << " to address: " << address.ToString()
+ << " with error: " << status;
+ return status;
+ }
+}
+
+absl::StatusOr<QuicSocketAddress> GetSocketAddress(SocketFd fd) {
+ QUICHE_DCHECK_GE(fd, 0);
+
+ sockaddr_storage addr;
+ socklen_t addr_len = sizeof(addr);
+
+ int result;
+ do {
+ result = ::getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &addr_len);
+ } while (result < 0 && errno == EINTR);
+
+ if (result >= 0) {
+ return ValidateAndConvertAddress(addr, addr_len);
+ } else {
+ absl::Status status = ToStatus(errno, "::getsockname()");
+ QUICHE_DVLOG(1) << "Failed to get socket " << fd
+ << " name with error: " << status;
+ return status;
+ }
+}
+
+absl::Status Listen(SocketFd fd, int backlog) {
+ QUICHE_DCHECK_GE(fd, 0);
+ QUICHE_DCHECK_GT(backlog, 0);
+
+ int result;
+ do {
+ result = ::listen(fd, backlog);
+ } while (result < 0 && errno == EINTR);
+
+ if (result >= 0) {
+ return absl::OkStatus();
+ } else {
+ absl::Status status = ToStatus(errno, "::listen()");
+ QUICHE_DVLOG(1) << "Failed to mark socket: " << fd
+ << " to listen with error :" << status;
+ return status;
+ }
+}
+
+absl::StatusOr<AcceptResult> Accept(SocketFd fd, bool blocking) {
+ QUICHE_DCHECK_GE(fd, 0);
+
+#if defined(__linux__) && defined(SOCK_NONBLOCK)
+ if (!blocking) {
+ return AcceptWithFlags(fd, SOCK_NONBLOCK);
+ }
+#endif
+
+ absl::StatusOr<AcceptResult> accept_result = AcceptInternal(fd);
+ if (!accept_result.ok() || blocking) {
+ return accept_result;
+ }
+
+#if !defined(__linux__) || !defined(SOCK_NONBLOCK)
+ // If non-blocking could not be set directly on socket acceptance, need to
+ // do it now.
+ absl::Status set_non_blocking_result =
+ SetSocketBlocking(accept_result.value().fd, /*blocking=*/false);
+ if (!set_non_blocking_result.ok()) {
+ QUICHE_LOG_FIRST_N(ERROR, 100)
+ << "Failed to set socket " << fd << " as non-blocking on acceptance.";
+ if (!Close(accept_result.value().fd).ok()) {
+ QUICHE_LOG_FIRST_N(ERROR, 100)
+ << "Failed to close socket " << accept_result.value().fd
+ << " after error setting non-blocking on acceptance.";
+ }
+ return set_non_blocking_result;
+ }
+#endif
+
+ return accept_result;
+}
+
+absl::StatusOr<absl::Span<char>> Receive(SocketFd fd, absl::Span<char> buffer) {
+ QUICHE_DCHECK_GE(fd, 0);
+ QUICHE_DCHECK(!buffer.empty());
+
+ ssize_t num_read;
+ do {
+ num_read = ::recv(fd, buffer.data(), buffer.size(), /*flags=*/0);
+ } while (num_read < 0 && errno == EINTR);
+
+ if (num_read > 0 && static_cast<size_t>(num_read) > buffer.size()) {
+ QUICHE_LOG_FIRST_N(WARNING, 100)
+ << "Received more bytes (" << num_read << ") from socket " << fd
+ << " than buffer size (" << buffer.size() << ").";
+ return absl::OutOfRangeError(
+ "::recv(): Received more bytes than buffer size.");
+ } else if (num_read >= 0) {
+ return buffer.subspan(0, num_read);
+ } else {
+ absl::Status status = ToStatus(errno, "::recv()");
+ QUICHE_DVLOG(1) << "Failed to receive from socket: " << fd
+ << " with error: " << status;
+ return status;
+ }
+}
+
+absl::StatusOr<absl::string_view> Send(SocketFd fd, absl::string_view buffer) {
+ QUICHE_DCHECK_GE(fd, 0);
+ QUICHE_DCHECK(!buffer.empty());
+
+ ssize_t num_sent;
+ do {
+ num_sent = ::send(fd, buffer.data(), buffer.size(), /*flags=*/0);
+ } while (num_sent < 0 && errno == EINTR);
+
+ if (num_sent > 0 && static_cast<size_t>(num_sent) > buffer.size()) {
+ QUICHE_LOG_FIRST_N(WARNING, 100)
+ << "Sent more bytes (" << num_sent << ") to socket " << fd
+ << " than buffer size (" << buffer.size() << ").";
+ return absl::OutOfRangeError("::send(): Sent more bytes than buffer size.");
+ } else if (num_sent >= 0) {
+ return buffer.substr(num_sent);
+ } else {
+ absl::Status status = ToStatus(errno, "::send()");
+ QUICHE_DVLOG(1) << "Failed to send to socket: " << fd
+ << " with error: " << status;
+ return status;
+ }
+}
+
+absl::Status Close(SocketFd fd) {
+ QUICHE_DCHECK_GE(fd, 0);
+
+ int close_result = ::close(fd);
+
+ if (close_result >= 0) {
+ return absl::OkStatus();
+ } else if (errno == EINTR) {
+ // Ignore EINTR on close because the socket is left in an undefined state
+ // and can't be acted on again.
+ QUICHE_DVLOG(1) << "Socket " << fd << " close unspecified due to EINTR.";
+ return absl::OkStatus();
+ } else {
+ absl::Status status = ToStatus(errno, "::close()");
+ QUICHE_DVLOG(1) << "Failed to close socket: " << fd
+ << " with error: " << status;
+ return status;
+ }
+}
+
+} // namespace quic::socket_api
diff --git a/quiche/quic/core/io/socket_test.cc b/quiche/quic/core/io/socket_test.cc
new file mode 100644
index 0000000..ecd611d
--- /dev/null
+++ b/quiche/quic/core/io/socket_test.cc
@@ -0,0 +1,154 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/core/io/socket.h"
+
+#include <string>
+#include <utility>
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.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_logging.h"
+#include "quiche/common/platform/api/quiche_test.h"
+#include "quiche/common/platform/api/quiche_test_loopback.h"
+
+namespace quic {
+namespace {
+
+using quiche::test::QuicheTest;
+using testing::Lt;
+using testing::SizeIs;
+
+SocketFd CreateTestSocket(socket_api::SocketProtocol protocol,
+ bool blocking = true) {
+ absl::StatusOr<SocketFd> socket = socket_api::CreateSocket(
+ quiche::TestLoopback().address_family(), protocol, blocking);
+
+ if (socket.ok()) {
+ return socket.value();
+ } else {
+ QUICHE_CHECK(false);
+ return kInvalidSocketFd;
+ }
+}
+
+TEST(SocketTest, CreateAndCloseSocket) {
+ QuicIpAddress localhost_address = quiche::TestLoopback();
+ absl::StatusOr<SocketFd> created_socket = socket_api::CreateSocket(
+ localhost_address.address_family(), socket_api::SocketProtocol::kUdp);
+
+ EXPECT_TRUE(created_socket.ok());
+
+ EXPECT_TRUE(socket_api::Close(created_socket.value()).ok());
+}
+
+TEST(SocketTest, SetSocketBlocking) {
+ SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp,
+ /*blocking=*/true);
+
+ EXPECT_TRUE(socket_api::SetSocketBlocking(socket, /*blocking=*/false).ok());
+
+ EXPECT_TRUE(socket_api::Close(socket).ok());
+}
+
+TEST(SocketTest, Connect) {
+ SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp);
+
+ // UDP, so "connecting" should succeed without any listening sockets.
+ EXPECT_TRUE(socket_api::Connect(
+ socket, QuicSocketAddress(quiche::TestLoopback(), /*port=*/0))
+ .ok());
+
+ EXPECT_TRUE(socket_api::Close(socket).ok());
+}
+
+TEST(SocketTest, Bind) {
+ SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp);
+
+ EXPECT_TRUE(socket_api::Bind(
+ socket, QuicSocketAddress(quiche::TestLoopback(), /*port=*/0))
+ .ok());
+
+ EXPECT_TRUE(socket_api::Close(socket).ok());
+}
+
+TEST(SocketTest, GetSocketAddress) {
+ SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp);
+ ASSERT_TRUE(socket_api::Bind(
+ socket, QuicSocketAddress(quiche::TestLoopback(), /*port=*/0))
+ .ok());
+
+ absl::StatusOr<QuicSocketAddress> address =
+ socket_api::GetSocketAddress(socket);
+ EXPECT_TRUE(address.ok());
+ EXPECT_TRUE(address.value().IsInitialized());
+ EXPECT_EQ(address.value().host(), quiche::TestLoopback());
+
+ EXPECT_TRUE(socket_api::Close(socket).ok());
+}
+
+TEST(SocketTest, Listen) {
+ SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kTcp);
+ ASSERT_TRUE(socket_api::Bind(
+ socket, QuicSocketAddress(quiche::TestLoopback(), /*port=*/0))
+ .ok());
+
+ EXPECT_TRUE(socket_api::Listen(socket, /*backlog=*/5).ok());
+
+ EXPECT_TRUE(socket_api::Close(socket).ok());
+}
+
+TEST(SocketTest, Accept) {
+ // Need a non-blocking socket to avoid waiting when no connection comes.
+ SocketFd socket =
+ CreateTestSocket(socket_api::SocketProtocol::kTcp, /*blocking=*/false);
+ ASSERT_TRUE(socket_api::Bind(
+ socket, QuicSocketAddress(quiche::TestLoopback(), /*port=*/0))
+ .ok());
+ ASSERT_TRUE(socket_api::Listen(socket, /*backlog=*/5).ok());
+
+ // Nothing set up to connect, so expect kUnavailable.
+ absl::StatusOr<socket_api::AcceptResult> result = socket_api::Accept(socket);
+ ASSERT_FALSE(result.ok());
+ EXPECT_TRUE(absl::IsUnavailable(result.status()));
+
+ EXPECT_TRUE(socket_api::Close(socket).ok());
+}
+
+TEST(SocketTest, Receive) {
+ // 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));
+ 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.
+ ASSERT_TRUE(socket_api::Connect(
+ socket, QuicSocketAddress(quiche::TestLoopback(), /*port=*/0))
+ .ok());
+
+ char buffer[] = {12, 34, 56, 78};
+ // Expect at least some data to be sent successfully.
+ absl::StatusOr<absl::string_view> result = socket_api::Send(socket, buffer);
+ ASSERT_TRUE(result.ok());
+ EXPECT_THAT(result.value(), SizeIs(Lt(4)));
+
+ EXPECT_TRUE(socket_api::Close(socket).ok());
+}
+
+} // namespace
+} // namespace quic
diff --git a/quiche/quic/core/quic_udp_socket.h b/quiche/quic/core/quic_udp_socket.h
index 039b07a..441ceef 100644
--- a/quiche/quic/core/quic_udp_socket.h
+++ b/quiche/quic/core/quic_udp_socket.h
@@ -9,6 +9,7 @@
#include <cstdint>
#include <type_traits>
+#include "quiche/quic/core/io/socket.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/core/quic_utils.h"
#include "quiche/quic/platform/api/quic_ip_address.h"
@@ -16,15 +17,10 @@
namespace quic {
-#if defined(_WIN32)
-using QuicUdpSocketFd = SOCKET;
-const QuicUdpSocketFd kQuicInvalidSocketFd = INVALID_SOCKET;
-#else
-using QuicUdpSocketFd = int;
-const QuicUdpSocketFd kQuicInvalidSocketFd = -1;
-#endif
+using QuicUdpSocketFd = SocketFd;
+inline constexpr QuicUdpSocketFd kQuicInvalidSocketFd = kInvalidSocketFd;
-const size_t kDefaultUdpPacketControlBufferSize = 512;
+inline constexpr size_t kDefaultUdpPacketControlBufferSize = 512;
enum class QuicUdpPacketInfoBit : uint8_t {
DROPPED_PACKETS = 0, // Read
diff --git a/quiche/quic/core/quic_udp_socket_posix.cc b/quiche/quic/core/quic_udp_socket_posix.cc
index c77d560..8a7fa77 100644
--- a/quiche/quic/core/quic_udp_socket_posix.cc
+++ b/quiche/quic/core/quic_udp_socket_posix.cc
@@ -10,8 +10,10 @@
#include <sys/types.h>
#include "absl/base/optimization.h"
+#include "quiche/quic/core/io/socket.h"
#include "quiche/quic/core/quic_udp_socket.h"
#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_ip_address_family.h"
#include "quiche/quic/platform/api/quic_udp_socket_platform_api.h"
#if defined(__APPLE__) && !defined(__APPLE_USE_RFC_3542)
@@ -58,49 +60,6 @@
+ kCmsgSpaceForRecvTimestamp + CMSG_SPACE(sizeof(int)) // TTL
+ kCmsgSpaceForGooglePacketHeader;
-QuicUdpSocketFd CreateNonblockingSocket(int address_family) {
-#if defined(__linux__) && defined(SOCK_NONBLOCK)
-
- // Create a nonblocking socket directly.
- int fd = socket(address_family, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP);
- if (fd < 0) {
- QUIC_LOG_FIRST_N(ERROR, 100)
- << "socket() failed with address_family=" << address_family << ": "
- << strerror(errno);
- return kQuicInvalidSocketFd;
- }
-#else
- // Create a socket and use fcntl to set it to nonblocking.
- // This implementation is used when building for iOS, OSX and old versions of
- // Linux (< 2.6.27) and old versions of Android (< API 21).
- int fd = socket(address_family, SOCK_DGRAM, IPPROTO_UDP);
- if (fd < 0) {
- QUIC_LOG_FIRST_N(ERROR, 100)
- << "socket() failed with address_family=" << address_family << ": "
- << strerror(errno);
- return kQuicInvalidSocketFd;
- }
- int current_flags = fcntl(fd, F_GETFL, 0);
- if (current_flags == -1) {
- QUIC_LOG_FIRST_N(ERROR, 100)
- << "failed to get current socket flags: " << strerror(errno);
- close(fd);
- return kQuicInvalidSocketFd;
- }
-
- int rc = fcntl(fd, F_SETFL, current_flags | O_NONBLOCK);
- if (rc == -1) {
- QUIC_LOG_FIRST_N(ERROR, 100)
- << "failed to set socket to non-blocking: " << strerror(errno);
- close(fd);
- return kQuicInvalidSocketFd;
- }
-#endif
-
- SetGoogleSocketOptions(fd);
- return fd;
-} // End CreateNonblockingSocket
-
void SetV4SelfIpInControlMessage(const QuicIpAddress& self_address,
cmsghdr* cmsg) {
QUICHE_DCHECK(self_address.IsIPv4());
@@ -237,19 +196,28 @@
// debug mode. This should have been a static_assert, however it can't be done
// on ios/osx because CMSG_SPACE isn't a constant expression there.
QUICHE_DCHECK_GE(kDefaultUdpPacketControlBufferSize, kMinCmsgSpaceForRead);
- QuicUdpSocketFd fd = CreateNonblockingSocket(address_family);
- if (fd == kQuicInvalidSocketFd) {
+ absl::StatusOr<SocketFd> socket =
+ socket_api::CreateSocket(FromPlatformAddressFamily(address_family),
+ socket_api::SocketProtocol::kUdp,
+ /*blocking=*/false);
+
+ if (!socket.ok()) {
+ QUIC_LOG_FIRST_N(ERROR, 100)
+ << "UDP non-blocking socket creation for address_family="
+ << address_family << " failed: " << socket.status();
return kQuicInvalidSocketFd;
}
- if (!SetupSocket(fd, address_family, receive_buffer_size, send_buffer_size,
- ipv6_only)) {
- Destroy(fd);
+ SetGoogleSocketOptions(socket.value());
+
+ if (!SetupSocket(socket.value(), address_family, receive_buffer_size,
+ send_buffer_size, ipv6_only)) {
+ Destroy(socket.value());
return kQuicInvalidSocketFd;
}
- return fd;
+ return socket.value();
}
bool QuicUdpSocketApi::SetupSocket(QuicUdpSocketFd fd, int address_family,
@@ -290,7 +258,11 @@
void QuicUdpSocketApi::Destroy(QuicUdpSocketFd fd) {
if (fd != kQuicInvalidSocketFd) {
- close(fd);
+ absl::Status result = socket_api::Close(fd);
+ if (!result.ok()) {
+ QUIC_LOG_FIRST_N(WARNING, 100)
+ << "Failed to close UDP socket with error " << result;
+ }
}
}
diff --git a/quiche/quic/platform/api/quic_ip_address.cc b/quiche/quic/platform/api/quic_ip_address.cc
index d8412c5..ba7f6af 100644
--- a/quiche/quic/platform/api/quic_ip_address.cc
+++ b/quiche/quic/platform/api/quic_ip_address.cc
@@ -10,24 +10,11 @@
#include <string>
#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_ip_address_family.h"
#include "quiche/quic/platform/api/quic_logging.h"
namespace quic {
-static int ToPlatformAddressFamily(IpAddressFamily family) {
- switch (family) {
- case IpAddressFamily::IP_V4:
- return AF_INET;
- case IpAddressFamily::IP_V6:
- return AF_INET6;
- case IpAddressFamily::IP_UNSPEC:
- return AF_UNSPEC;
- }
- QUIC_BUG(quic_bug_10126_1)
- << "Invalid IpAddressFamily " << static_cast<int32_t>(family);
- return AF_UNSPEC;
-}
-
QuicIpAddress QuicIpAddress::Loopback4() {
QuicIpAddress result;
result.family_ = IpAddressFamily::IP_V4;
diff --git a/quiche/quic/platform/api/quic_ip_address_family.cc b/quiche/quic/platform/api/quic_ip_address_family.cc
new file mode 100644
index 0000000..c78abf2
--- /dev/null
+++ b/quiche/quic/platform/api/quic_ip_address_family.cc
@@ -0,0 +1,47 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/platform/api/quic_ip_address_family.h"
+
+#include "quiche/quic/platform/api/quic_bug_tracker.h"
+
+#if defined(_WIN32)
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#endif // defined(_WIN32)
+
+namespace quic {
+
+int ToPlatformAddressFamily(IpAddressFamily family) {
+ switch (family) {
+ case IpAddressFamily::IP_V4:
+ return AF_INET;
+ case IpAddressFamily::IP_V6:
+ return AF_INET6;
+ case IpAddressFamily::IP_UNSPEC:
+ return AF_UNSPEC;
+ default:
+ QUIC_BUG(quic_bug_10126_1)
+ << "Invalid IpAddressFamily " << static_cast<int32_t>(family);
+ return AF_UNSPEC;
+ }
+}
+
+IpAddressFamily FromPlatformAddressFamily(int family) {
+ switch (family) {
+ case AF_INET:
+ return IpAddressFamily::IP_V4;
+ case AF_INET6:
+ return IpAddressFamily::IP_V6;
+ case AF_UNSPEC:
+ return IpAddressFamily::IP_UNSPEC;
+ default:
+ QUIC_BUG(quic_FromPlatformAddressFamily_unrecognized_family)
+ << "Invalid platform address family int " << family;
+ return IpAddressFamily::IP_UNSPEC;
+ }
+}
+
+} // namespace quic
diff --git a/quiche/quic/platform/api/quic_ip_address_family.h b/quiche/quic/platform/api/quic_ip_address_family.h
index dad2cb9..ad3963c 100644
--- a/quiche/quic/platform/api/quic_ip_address_family.h
+++ b/quiche/quic/platform/api/quic_ip_address_family.h
@@ -15,6 +15,9 @@
IP_UNSPEC,
};
+int ToPlatformAddressFamily(IpAddressFamily family);
+IpAddressFamily FromPlatformAddressFamily(int family);
+
} // namespace quic
#endif // QUICHE_QUIC_PLATFORM_API_QUIC_IP_ADDRESS_FAMILY_H_
diff --git a/quiche/quic/platform/api/quic_socket_address.h b/quiche/quic/platform/api/quic_socket_address.h
index 9ec9717..626a54d 100644
--- a/quiche/quic/platform/api/quic_socket_address.h
+++ b/quiche/quic/platform/api/quic_socket_address.h
@@ -30,7 +30,11 @@
bool IsInitialized() const;
std::string ToString() const;
+
+ // TODO(ericorth): Convert usage over to socket_api::GetSocketAddress() and
+ // remove.
int FromSocket(int fd);
+
QuicSocketAddress Normalized() const;
QuicIpAddress host() const;