blob: 53dfb1fbcdd043d7dc09b813b9e43a8c0dedc3eb [file] [log] [blame] [edit]
// 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/event_loop_connecting_client_socket.h"
#include <limits>
#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 "absl/types/variant.h"
#include "quiche/quic/core/io/quic_event_loop.h"
#include "quiche/quic/core/io/socket.h"
#include "quiche/quic/platform/api/quic_socket_address.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/platform/api/quiche_mem_slice.h"
namespace quic {
EventLoopConnectingClientSocket::EventLoopConnectingClientSocket(
socket_api::SocketProtocol protocol,
const quic::QuicSocketAddress& peer_address,
QuicByteCount receive_buffer_size, QuicByteCount send_buffer_size,
QuicEventLoop* event_loop, quiche::QuicheBufferAllocator* buffer_allocator,
AsyncVisitor* async_visitor)
: protocol_(protocol),
peer_address_(peer_address),
receive_buffer_size_(receive_buffer_size),
send_buffer_size_(send_buffer_size),
event_loop_(event_loop),
buffer_allocator_(buffer_allocator),
async_visitor_(async_visitor) {
QUICHE_DCHECK(event_loop_);
QUICHE_DCHECK(buffer_allocator_);
}
EventLoopConnectingClientSocket::~EventLoopConnectingClientSocket() {
// Connected socket must be closed via Disconnect() before destruction. Cannot
// safely recover if state indicates caller may be expecting async callbacks.
QUICHE_DCHECK(connect_status_ != ConnectStatus::kConnecting);
QUICHE_DCHECK(!receive_max_size_.has_value());
QUICHE_DCHECK(absl::holds_alternative<absl::monostate>(send_data_));
if (descriptor_ != kInvalidSocketFd) {
QUICHE_BUG(quic_event_loop_connecting_socket_invalid_destruction)
<< "Must call Disconnect() on connected socket before destruction.";
Close();
}
QUICHE_DCHECK(connect_status_ == ConnectStatus::kNotConnected);
QUICHE_DCHECK(send_remaining_.empty());
}
absl::Status EventLoopConnectingClientSocket::ConnectBlocking() {
QUICHE_DCHECK_EQ(descriptor_, kInvalidSocketFd);
QUICHE_DCHECK(connect_status_ == ConnectStatus::kNotConnected);
QUICHE_DCHECK(!receive_max_size_.has_value());
QUICHE_DCHECK(absl::holds_alternative<absl::monostate>(send_data_));
absl::Status status = Open();
if (!status.ok()) {
return status;
}
status = socket_api::SetSocketBlocking(descriptor_, /*blocking=*/true);
if (!status.ok()) {
QUICHE_LOG_FIRST_N(WARNING, 100)
<< "Failed to set socket to address: " << peer_address_.ToString()
<< " as blocking for connect with error: " << status;
Close();
return status;
}
status = DoInitialConnect();
if (absl::IsUnavailable(status)) {
QUICHE_LOG_FIRST_N(ERROR, 100)
<< "Non-blocking connect to should-be blocking socket to address:"
<< peer_address_.ToString() << ".";
Close();
connect_status_ = ConnectStatus::kNotConnected;
return status;
} else if (!status.ok()) {
// DoInitialConnect() closes the socket on failures.
QUICHE_DCHECK_EQ(descriptor_, kInvalidSocketFd);
QUICHE_DCHECK(connect_status_ == ConnectStatus::kNotConnected);
return status;
}
status = socket_api::SetSocketBlocking(descriptor_, /*blocking=*/false);
if (!status.ok()) {
QUICHE_LOG_FIRST_N(WARNING, 100)
<< "Failed to return socket to address: " << peer_address_.ToString()
<< " to non-blocking after connect with error: " << status;
Close();
connect_status_ = ConnectStatus::kNotConnected;
}
QUICHE_DCHECK(connect_status_ != ConnectStatus::kConnecting);
return status;
}
void EventLoopConnectingClientSocket::ConnectAsync() {
QUICHE_DCHECK(async_visitor_);
QUICHE_DCHECK_EQ(descriptor_, kInvalidSocketFd);
QUICHE_DCHECK(connect_status_ == ConnectStatus::kNotConnected);
QUICHE_DCHECK(!receive_max_size_.has_value());
QUICHE_DCHECK(absl::holds_alternative<absl::monostate>(send_data_));
absl::Status status = Open();
if (!status.ok()) {
async_visitor_->ConnectComplete(status);
return;
}
FinishOrRearmAsyncConnect(DoInitialConnect());
}
void EventLoopConnectingClientSocket::Disconnect() {
QUICHE_DCHECK_NE(descriptor_, kInvalidSocketFd);
QUICHE_DCHECK(connect_status_ != ConnectStatus::kNotConnected);
Close();
QUICHE_DCHECK_EQ(descriptor_, kInvalidSocketFd);
// Reset all state before invoking any callbacks.
bool require_connect_callback = connect_status_ == ConnectStatus::kConnecting;
connect_status_ = ConnectStatus::kNotConnected;
bool require_receive_callback = receive_max_size_.has_value();
receive_max_size_.reset();
bool require_send_callback =
!absl::holds_alternative<absl::monostate>(send_data_);
send_data_ = absl::monostate();
send_remaining_ = "";
if (require_connect_callback) {
QUICHE_DCHECK(async_visitor_);
async_visitor_->ConnectComplete(absl::CancelledError());
}
if (require_receive_callback) {
QUICHE_DCHECK(async_visitor_);
async_visitor_->ReceiveComplete(absl::CancelledError());
}
if (require_send_callback) {
QUICHE_DCHECK(async_visitor_);
async_visitor_->SendComplete(absl::CancelledError());
}
}
absl::StatusOr<QuicSocketAddress>
EventLoopConnectingClientSocket::GetLocalAddress() {
QUICHE_DCHECK_NE(descriptor_, kInvalidSocketFd);
QUICHE_DCHECK(connect_status_ == ConnectStatus::kConnected);
return socket_api::GetSocketAddress(descriptor_);
}
absl::StatusOr<quiche::QuicheMemSlice>
EventLoopConnectingClientSocket::ReceiveBlocking(QuicByteCount max_size) {
QUICHE_DCHECK_GT(max_size, 0u);
QUICHE_DCHECK_NE(descriptor_, kInvalidSocketFd);
QUICHE_DCHECK(connect_status_ == ConnectStatus::kConnected);
QUICHE_DCHECK(!receive_max_size_.has_value());
absl::Status status =
socket_api::SetSocketBlocking(descriptor_, /*blocking=*/true);
if (!status.ok()) {
QUICHE_LOG_FIRST_N(WARNING, 100)
<< "Failed to set socket to address: " << peer_address_.ToString()
<< " as blocking for receive with error: " << status;
return status;
}
receive_max_size_ = max_size;
absl::StatusOr<quiche::QuicheMemSlice> buffer = ReceiveInternal();
if (!buffer.ok() && absl::IsUnavailable(buffer.status())) {
QUICHE_LOG_FIRST_N(ERROR, 100)
<< "Non-blocking receive from should-be blocking socket to address:"
<< peer_address_.ToString() << ".";
receive_max_size_.reset();
} else {
QUICHE_DCHECK(!receive_max_size_.has_value());
}
absl::Status set_non_blocking_status =
socket_api::SetSocketBlocking(descriptor_, /*blocking=*/false);
if (!set_non_blocking_status.ok()) {
QUICHE_LOG_FIRST_N(WARNING, 100)
<< "Failed to return socket to address: " << peer_address_.ToString()
<< " to non-blocking after receive with error: "
<< set_non_blocking_status;
return set_non_blocking_status;
}
return buffer;
}
void EventLoopConnectingClientSocket::ReceiveAsync(QuicByteCount max_size) {
QUICHE_DCHECK(async_visitor_);
QUICHE_DCHECK_GT(max_size, 0u);
QUICHE_DCHECK_NE(descriptor_, kInvalidSocketFd);
QUICHE_DCHECK(connect_status_ == ConnectStatus::kConnected);
QUICHE_DCHECK(!receive_max_size_.has_value());
receive_max_size_ = max_size;
FinishOrRearmAsyncReceive(ReceiveInternal());
}
absl::Status EventLoopConnectingClientSocket::SendBlocking(std::string data) {
QUICHE_DCHECK(!data.empty());
QUICHE_DCHECK(absl::holds_alternative<absl::monostate>(send_data_));
send_data_ = std::move(data);
return SendBlockingInternal();
}
absl::Status EventLoopConnectingClientSocket::SendBlocking(
quiche::QuicheMemSlice data) {
QUICHE_DCHECK(!data.empty());
QUICHE_DCHECK(absl::holds_alternative<absl::monostate>(send_data_));
send_data_ = std::move(data);
return SendBlockingInternal();
}
void EventLoopConnectingClientSocket::SendAsync(std::string data) {
QUICHE_DCHECK(!data.empty());
QUICHE_DCHECK(absl::holds_alternative<absl::monostate>(send_data_));
send_data_ = std::move(data);
send_remaining_ = absl::get<std::string>(send_data_);
FinishOrRearmAsyncSend(SendInternal());
}
void EventLoopConnectingClientSocket::SendAsync(quiche::QuicheMemSlice data) {
QUICHE_DCHECK(!data.empty());
QUICHE_DCHECK(absl::holds_alternative<absl::monostate>(send_data_));
send_data_ = std::move(data);
send_remaining_ =
absl::get<quiche::QuicheMemSlice>(send_data_).AsStringView();
FinishOrRearmAsyncSend(SendInternal());
}
void EventLoopConnectingClientSocket::OnSocketEvent(
QuicEventLoop* event_loop, SocketFd fd, QuicSocketEventMask events) {
QUICHE_DCHECK_EQ(event_loop, event_loop_);
QUICHE_DCHECK_EQ(fd, descriptor_);
if (connect_status_ == ConnectStatus::kConnecting &&
(events & (kSocketEventWritable | kSocketEventError))) {
FinishOrRearmAsyncConnect(GetConnectResult());
return;
}
if (receive_max_size_.has_value() &&
(events & (kSocketEventReadable | kSocketEventError))) {
FinishOrRearmAsyncReceive(ReceiveInternal());
}
if (!send_remaining_.empty() &&
(events & (kSocketEventWritable | kSocketEventError))) {
FinishOrRearmAsyncSend(SendInternal());
}
}
absl::Status EventLoopConnectingClientSocket::Open() {
QUICHE_DCHECK_EQ(descriptor_, kInvalidSocketFd);
QUICHE_DCHECK(connect_status_ == ConnectStatus::kNotConnected);
QUICHE_DCHECK(!receive_max_size_.has_value());
QUICHE_DCHECK(absl::holds_alternative<absl::monostate>(send_data_));
QUICHE_DCHECK(send_remaining_.empty());
absl::StatusOr<SocketFd> descriptor =
socket_api::CreateSocket(peer_address_.host().address_family(), protocol_,
/*blocking=*/false);
if (!descriptor.ok()) {
QUICHE_DVLOG(1) << "Failed to open socket for connection to address: "
<< peer_address_.ToString()
<< " with error: " << descriptor.status();
return descriptor.status();
}
QUICHE_DCHECK_NE(*descriptor, kInvalidSocketFd);
descriptor_ = *descriptor;
if (async_visitor_) {
bool registered;
if (event_loop_->SupportsEdgeTriggered()) {
registered = event_loop_->RegisterSocket(
descriptor_,
kSocketEventReadable | kSocketEventWritable | kSocketEventError,
this);
} else {
// Just register the socket without any armed events for now. Will rearm
// with specific events as needed. Registering now before events are
// needed makes it easier to ensure the socket is registered only once
// and can always be unregistered on socket close.
registered = event_loop_->RegisterSocket(descriptor_, /*events=*/0, this);
}
QUICHE_DCHECK(registered);
}
if (receive_buffer_size_ != 0) {
absl::Status status =
socket_api::SetReceiveBufferSize(descriptor_, receive_buffer_size_);
if (!status.ok()) {
QUICHE_LOG_FIRST_N(WARNING, 100)
<< "Failed to set receive buffer size to: " << receive_buffer_size_
<< " for socket to address: " << peer_address_.ToString()
<< " with error: " << status;
Close();
return status;
}
}
if (send_buffer_size_ != 0) {
absl::Status status =
socket_api::SetSendBufferSize(descriptor_, send_buffer_size_);
if (!status.ok()) {
QUICHE_LOG_FIRST_N(WARNING, 100)
<< "Failed to set send buffer size to: " << send_buffer_size_
<< " for socket to address: " << peer_address_.ToString()
<< " with error: " << status;
Close();
return status;
}
}
return absl::OkStatus();
}
void EventLoopConnectingClientSocket::Close() {
QUICHE_DCHECK_NE(descriptor_, kInvalidSocketFd);
bool unregistered = event_loop_->UnregisterSocket(descriptor_);
QUICHE_DCHECK_EQ(unregistered, !!async_visitor_);
absl::Status status = socket_api::Close(descriptor_);
if (!status.ok()) {
QUICHE_LOG_FIRST_N(WARNING, 100)
<< "Could not close socket to address: " << peer_address_.ToString()
<< " with error: " << status;
}
descriptor_ = kInvalidSocketFd;
}
absl::Status EventLoopConnectingClientSocket::DoInitialConnect() {
QUICHE_DCHECK_NE(descriptor_, kInvalidSocketFd);
QUICHE_DCHECK(connect_status_ == ConnectStatus::kNotConnected);
QUICHE_DCHECK(!receive_max_size_.has_value());
QUICHE_DCHECK(absl::holds_alternative<absl::monostate>(send_data_));
absl::Status connect_result = socket_api::Connect(descriptor_, peer_address_);
if (connect_result.ok()) {
connect_status_ = ConnectStatus::kConnected;
} else if (absl::IsUnavailable(connect_result)) {
connect_status_ = ConnectStatus::kConnecting;
} else {
QUICHE_DVLOG(1) << "Synchronously failed to connect socket to address: "
<< peer_address_.ToString()
<< " with error: " << connect_result;
Close();
connect_status_ = ConnectStatus::kNotConnected;
}
return connect_result;
}
absl::Status EventLoopConnectingClientSocket::GetConnectResult() {
QUICHE_DCHECK_NE(descriptor_, kInvalidSocketFd);
QUICHE_DCHECK(connect_status_ == ConnectStatus::kConnecting);
QUICHE_DCHECK(!receive_max_size_.has_value());
QUICHE_DCHECK(absl::holds_alternative<absl::monostate>(send_data_));
absl::Status error = socket_api::GetSocketError(descriptor_);
if (!error.ok()) {
QUICHE_DVLOG(1) << "Asynchronously failed to connect socket to address: "
<< peer_address_.ToString() << " with error: " << error;
Close();
connect_status_ = ConnectStatus::kNotConnected;
return error;
}
// Peek at one byte to confirm the connection is actually alive. Motivation:
// 1) Plausibly could have a lot of cases where the connection operation
// itself technically succeeds but the socket then quickly fails. Don't
// want to claim connection success here if, by the time this code is
// running after event triggers and such, the socket has already failed.
// Lot of undefined room around whether or not such errors would be saved
// into SO_ERROR and returned by socket_api::GetSocketError().
// 2) With the various platforms and event systems involved, less than 100%
// trust that it's impossible to end up in this method before the async
// connect has completed/errored. Given that Connect() and GetSocketError()
// does not difinitevely differentiate between success and
// still-in-progress, and given that there's a very simple and performant
// way to positively confirm the socket is connected (peek), do that here.
// (Could consider making the not-connected case a QUIC_BUG if a way is
// found to differentiate it from (1).)
absl::StatusOr<bool> peek_data = OneBytePeek();
if (peek_data.ok() || absl::IsUnavailable(peek_data.status())) {
connect_status_ = ConnectStatus::kConnected;
} else {
error = peek_data.status();
QUICHE_LOG_FIRST_N(WARNING, 100)
<< "Socket to address: " << peer_address_.ToString()
<< " signalled writable after connect and no connect error found, "
"but socket does not appear connected with error: "
<< error;
Close();
connect_status_ = ConnectStatus::kNotConnected;
}
return error;
}
void EventLoopConnectingClientSocket::FinishOrRearmAsyncConnect(
absl::Status status) {
if (absl::IsUnavailable(status)) {
if (!event_loop_->SupportsEdgeTriggered()) {
bool result = event_loop_->RearmSocket(
descriptor_, kSocketEventWritable | kSocketEventError);
QUICHE_DCHECK(result);
}
QUICHE_DCHECK(connect_status_ == ConnectStatus::kConnecting);
} else {
QUICHE_DCHECK(connect_status_ != ConnectStatus::kConnecting);
async_visitor_->ConnectComplete(status);
}
}
absl::StatusOr<quiche::QuicheMemSlice>
EventLoopConnectingClientSocket::ReceiveInternal() {
QUICHE_DCHECK_NE(descriptor_, kInvalidSocketFd);
QUICHE_DCHECK(connect_status_ == ConnectStatus::kConnected);
QUICHE_CHECK(receive_max_size_.has_value());
QUICHE_DCHECK_GE(*receive_max_size_, 1u);
QUICHE_DCHECK_LE(*receive_max_size_, std::numeric_limits<size_t>::max());
// Before allocating a buffer, do a 1-byte peek to determine if needed.
if (*receive_max_size_ > 1) {
absl::StatusOr<bool> peek_data = OneBytePeek();
if (!peek_data.ok()) {
if (!absl::IsUnavailable(peek_data.status())) {
receive_max_size_.reset();
}
return peek_data.status();
} else if (!*peek_data) {
receive_max_size_.reset();
return quiche::QuicheMemSlice();
}
}
quiche::QuicheBuffer buffer(buffer_allocator_, *receive_max_size_);
absl::StatusOr<absl::Span<char>> received = socket_api::Receive(
descriptor_, absl::MakeSpan(buffer.data(), buffer.size()));
if (received.ok()) {
QUICHE_DCHECK_LE(received->size(), buffer.size());
QUICHE_DCHECK_EQ(received->data(), buffer.data());
receive_max_size_.reset();
return quiche::QuicheMemSlice(
quiche::QuicheBuffer(buffer.Release(), received->size()));
} else {
if (!absl::IsUnavailable(received.status())) {
QUICHE_DVLOG(1) << "Failed to receive from socket to address: "
<< peer_address_.ToString()
<< " with error: " << received.status();
receive_max_size_.reset();
}
return received.status();
}
}
void EventLoopConnectingClientSocket::FinishOrRearmAsyncReceive(
absl::StatusOr<quiche::QuicheMemSlice> buffer) {
QUICHE_DCHECK(async_visitor_);
QUICHE_DCHECK(connect_status_ == ConnectStatus::kConnected);
if (!buffer.ok() && absl::IsUnavailable(buffer.status())) {
if (!event_loop_->SupportsEdgeTriggered()) {
bool result = event_loop_->RearmSocket(
descriptor_, kSocketEventReadable | kSocketEventError);
QUICHE_DCHECK(result);
}
QUICHE_DCHECK(receive_max_size_.has_value());
} else {
QUICHE_DCHECK(!receive_max_size_.has_value());
async_visitor_->ReceiveComplete(std::move(buffer));
}
}
absl::StatusOr<bool> EventLoopConnectingClientSocket::OneBytePeek() {
QUICHE_DCHECK_NE(descriptor_, kInvalidSocketFd);
char peek_buffer;
absl::StatusOr<absl::Span<char>> peek_received = socket_api::Receive(
descriptor_, absl::MakeSpan(&peek_buffer, /*size=*/1), /*peek=*/true);
if (!peek_received.ok()) {
return peek_received.status();
} else {
return !peek_received->empty();
}
}
absl::Status EventLoopConnectingClientSocket::SendBlockingInternal() {
QUICHE_DCHECK_NE(descriptor_, kInvalidSocketFd);
QUICHE_DCHECK(connect_status_ == ConnectStatus::kConnected);
QUICHE_DCHECK(!absl::holds_alternative<absl::monostate>(send_data_));
QUICHE_DCHECK(send_remaining_.empty());
absl::Status status =
socket_api::SetSocketBlocking(descriptor_, /*blocking=*/true);
if (!status.ok()) {
QUICHE_LOG_FIRST_N(WARNING, 100)
<< "Failed to set socket to address: " << peer_address_.ToString()
<< " as blocking for send with error: " << status;
send_data_ = absl::monostate();
return status;
}
if (absl::holds_alternative<std::string>(send_data_)) {
send_remaining_ = absl::get<std::string>(send_data_);
} else {
send_remaining_ =
absl::get<quiche::QuicheMemSlice>(send_data_).AsStringView();
}
status = SendInternal();
if (absl::IsUnavailable(status)) {
QUICHE_LOG_FIRST_N(ERROR, 100)
<< "Non-blocking send for should-be blocking socket to address:"
<< peer_address_.ToString();
send_data_ = absl::monostate();
send_remaining_ = "";
} else {
QUICHE_DCHECK(absl::holds_alternative<absl::monostate>(send_data_));
QUICHE_DCHECK(send_remaining_.empty());
}
absl::Status set_non_blocking_status =
socket_api::SetSocketBlocking(descriptor_, /*blocking=*/false);
if (!set_non_blocking_status.ok()) {
QUICHE_LOG_FIRST_N(WARNING, 100)
<< "Failed to return socket to address: " << peer_address_.ToString()
<< " to non-blocking after send with error: "
<< set_non_blocking_status;
return set_non_blocking_status;
}
return status;
}
absl::Status EventLoopConnectingClientSocket::SendInternal() {
QUICHE_DCHECK_NE(descriptor_, kInvalidSocketFd);
QUICHE_DCHECK(connect_status_ == ConnectStatus::kConnected);
QUICHE_DCHECK(!absl::holds_alternative<absl::monostate>(send_data_));
QUICHE_DCHECK(!send_remaining_.empty());
// Repeat send until all data sent, unavailable, or error.
while (!send_remaining_.empty()) {
absl::StatusOr<absl::string_view> remainder =
socket_api::Send(descriptor_, send_remaining_);
if (remainder.ok()) {
QUICHE_DCHECK(remainder->empty() ||
(remainder->data() >= send_remaining_.data() &&
remainder->data() <
send_remaining_.data() + send_remaining_.size()));
QUICHE_DCHECK(remainder->empty() ||
(remainder->data() + remainder->size() ==
send_remaining_.data() + send_remaining_.size()));
send_remaining_ = *remainder;
} else {
if (!absl::IsUnavailable(remainder.status())) {
QUICHE_DVLOG(1) << "Failed to send to socket to address: "
<< peer_address_.ToString()
<< " with error: " << remainder.status();
send_data_ = absl::monostate();
send_remaining_ = "";
}
return remainder.status();
}
}
send_data_ = absl::monostate();
return absl::OkStatus();
}
void EventLoopConnectingClientSocket::FinishOrRearmAsyncSend(
absl::Status status) {
QUICHE_DCHECK(async_visitor_);
QUICHE_DCHECK(connect_status_ == ConnectStatus::kConnected);
if (absl::IsUnavailable(status)) {
if (!event_loop_->SupportsEdgeTriggered()) {
bool result = event_loop_->RearmSocket(
descriptor_, kSocketEventWritable | kSocketEventError);
QUICHE_DCHECK(result);
}
QUICHE_DCHECK(!absl::holds_alternative<absl::monostate>(send_data_));
QUICHE_DCHECK(!send_remaining_.empty());
} else {
QUICHE_DCHECK(absl::holds_alternative<absl::monostate>(send_data_));
QUICHE_DCHECK(send_remaining_.empty());
async_visitor_->SendComplete(status);
}
}
} // namespace quic