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;