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;