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 for TCP).

Converted QuicUdpSocketApi to be based off this new more general library in the couple places where trivial to do so.  But I'm calling it out of scope for now to make this new library replicate all the functionality of QuicUdpSocketApi to back all of it or to convert existing usage of QuicUdpSocketApi to the new library.

Simple tests to make sure each function is at least exercised, but did not aim for exhaustive testing.

In a subsequent CL (cl/443757825), I still plan to add a higher-level abstraction on top of this to add RAII and deal with asynchronousness and such for TCP sockets.

PiperOrigin-RevId: 458070348
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..ca65927
--- /dev/null
+++ b/quiche/quic/core/io/socket_posix.cc
@@ -0,0 +1,444 @@
+// 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__)
+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__)
+
+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 =
+      SetSocketNonBlocking(creation_result.value());
+  if (!set_non_blocking_result.ok()) {
+    QUICHE_LOG_FIRST_N(ERROR, 100) << "Failed to set socket " << socket.value()
+                                   << " as non-blocking on creation.";
+    Close(socket.value());
+    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 =
+      SetSocketNonBlocking(accept_result.value().fd);
+  if (!set_non_blocking_result.ok()) {
+    QUICHE_LOG_FIRST_N(ERROR, 100)
+        << "Failed to set socket " << fd << " as non-blocking on acceptance.";
+    Close(accept_result.value().fd);
+    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;