diff options
author | Cronet Mainline Eng <cronet-mainline-eng+copybara@google.com> | 2023-03-20 09:24:50 -0800 |
---|---|---|
committer | Patrick Rohr <prohr@google.com> | 2023-03-20 10:25:51 -0700 |
commit | 14c9064f78517fd0e9366547030c0493aa075b47 (patch) | |
tree | 6e03046ec4055bb9881ff0341716266b5d53782b /net/socket/socket_bio_adapter.cc | |
parent | d1add53d6e90815f363c91d433735556ce79b0d2 (diff) | |
download | cronet-14c9064f78517fd0e9366547030c0493aa075b47.tar.gz |
Import Cronet version 108.0.5359.128
Project import generated by Copybara.
FolderOrigin-RevId: /tmp/copybara-origin/src
Test: none
Change-Id: I98ebcd5784650764c7cd70ab175dd4e1cc790dff
Diffstat (limited to 'net/socket/socket_bio_adapter.cc')
-rw-r--r-- | net/socket/socket_bio_adapter.cc | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/net/socket/socket_bio_adapter.cc b/net/socket/socket_bio_adapter.cc new file mode 100644 index 000000000..014be2d2d --- /dev/null +++ b/net/socket/socket_bio_adapter.cc @@ -0,0 +1,399 @@ +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/socket/socket_bio_adapter.h" + +#include <string.h> + +#include <algorithm> + +#include "base/bind.h" +#include "base/check_op.h" +#include "base/location.h" +#include "base/notreached.h" +#include "base/threading/thread_task_runner_handle.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/socket/socket.h" +#include "net/socket/stream_socket.h" +#include "net/ssl/openssl_ssl_util.h" +#include "net/traffic_annotation/network_traffic_annotation.h" +#include "third_party/boringssl/src/include/openssl/bio.h" + +namespace { + +const net::NetworkTrafficAnnotationTag kTrafficAnnotation = + net::DefineNetworkTrafficAnnotation("socket_bio_adapter", R"( + semantics { + sender: "Socket BIO Adapter" + description: + "SocketBIOAdapter is used only internal to //net code as an internal " + "detail to implement a TLS connection for a Socket class, and is not " + "being called directly outside of this abstraction." + trigger: + "Establishing a TLS connection to a remote endpoint. There are many " + "different ways in which a TLS connection may be triggered, such as " + "loading an HTTPS URL." + data: + "All data sent or received over a TLS connection. This traffic may " + "either be the handshake or application data. During the handshake, " + "the target host name, user's IP, data related to previous " + "handshake, client certificates, and channel ID, may be sent. When " + "the connection is used to load an HTTPS URL, the application data " + "includes cookies, request headers, and the response body." + destination: OTHER + destination_other: + "Any destination the implementing socket is connected to." + } + policy { + cookies_allowed: NO + setting: "This feature cannot be disabled." + policy_exception_justification: "Essential for navigation." + })"); + +} // namespace + +namespace net { + +SocketBIOAdapter::SocketBIOAdapter(StreamSocket* socket, + int read_buffer_capacity, + int write_buffer_capacity, + Delegate* delegate) + : socket_(socket), + read_buffer_capacity_(read_buffer_capacity), + write_buffer_capacity_(write_buffer_capacity), + delegate_(delegate) { + bio_.reset(BIO_new(&kBIOMethod)); + bio_->ptr = this; + bio_->init = 1; + + read_callback_ = base::BindRepeating(&SocketBIOAdapter::OnSocketReadComplete, + weak_factory_.GetWeakPtr()); + write_callback_ = base::BindRepeating( + &SocketBIOAdapter::OnSocketWriteComplete, weak_factory_.GetWeakPtr()); +} + +SocketBIOAdapter::~SocketBIOAdapter() { + // BIOs are reference-counted and may outlive the adapter. Clear the pointer + // so future operations fail. + bio_->ptr = nullptr; +} + +bool SocketBIOAdapter::HasPendingReadData() { + return read_result_ > 0; +} + +size_t SocketBIOAdapter::GetAllocationSize() const { + size_t buffer_size = 0; + if (read_buffer_) + buffer_size += read_buffer_capacity_; + + if (write_buffer_) + buffer_size += write_buffer_capacity_; + return buffer_size; +} + +int SocketBIOAdapter::BIORead(char* out, int len) { + if (len <= 0) + return len; + + // If there is no result available synchronously, report any Write() errors + // that were observed. Otherwise the application may have encountered a socket + // error while writing that would otherwise not be reported until the + // application attempted to write again - which it may never do. See + // https://crbug.com/249848. + if (write_error_ != OK && write_error_ != ERR_IO_PENDING && + (read_result_ == 0 || read_result_ == ERR_IO_PENDING)) { + OpenSSLPutNetError(FROM_HERE, write_error_); + return -1; + } + + if (read_result_ == 0) { + // Instantiate the read buffer and read from the socket. Although only |len| + // bytes were requested, intentionally read to the full buffer size. The SSL + // layer reads the record header and body in separate reads to avoid + // overreading, but issuing one is more efficient. SSL sockets are not + // reused after shutdown for non-SSL traffic, so overreading is fine. + DCHECK(!read_buffer_); + DCHECK_EQ(0, read_offset_); + read_buffer_ = base::MakeRefCounted<IOBuffer>(read_buffer_capacity_); + int result = socket_->ReadIfReady( + read_buffer_.get(), read_buffer_capacity_, + base::BindOnce(&SocketBIOAdapter::OnSocketReadIfReadyComplete, + weak_factory_.GetWeakPtr())); + if (result == ERR_IO_PENDING) + read_buffer_ = nullptr; + if (result == ERR_READ_IF_READY_NOT_IMPLEMENTED) { + result = socket_->Read(read_buffer_.get(), read_buffer_capacity_, + read_callback_); + } + if (result == ERR_IO_PENDING) { + read_result_ = ERR_IO_PENDING; + } else { + HandleSocketReadResult(result); + } + } + + // There is a pending Read(). Inform the caller to retry when it completes. + if (read_result_ == ERR_IO_PENDING) { + BIO_set_retry_read(bio()); + return -1; + } + + // If the last Read() failed, report the error. + if (read_result_ < 0) { + OpenSSLPutNetError(FROM_HERE, read_result_); + return -1; + } + + // Report the result of the last Read() if non-empty. + CHECK_LT(read_offset_, read_result_); + len = std::min(len, read_result_ - read_offset_); + memcpy(out, read_buffer_->data() + read_offset_, len); + read_offset_ += len; + + // Release the buffer when empty. + if (read_offset_ == read_result_) { + read_buffer_ = nullptr; + read_offset_ = 0; + read_result_ = 0; + } + + return len; +} + +void SocketBIOAdapter::HandleSocketReadResult(int result) { + DCHECK_NE(ERR_IO_PENDING, result); + + // If an EOF, canonicalize to ERR_CONNECTION_CLOSED here, so that higher + // levels don't report success. + if (result == 0) + result = ERR_CONNECTION_CLOSED; + + read_result_ = result; + + // The read buffer is no longer needed. + if (read_result_ <= 0) + read_buffer_ = nullptr; +} + +void SocketBIOAdapter::OnSocketReadComplete(int result) { + DCHECK_EQ(ERR_IO_PENDING, read_result_); + + HandleSocketReadResult(result); + delegate_->OnReadReady(); +} + +void SocketBIOAdapter::OnSocketReadIfReadyComplete(int result) { + DCHECK_EQ(ERR_IO_PENDING, read_result_); + DCHECK_GE(OK, result); + + // Do not use HandleSocketReadResult() because result == OK doesn't mean EOF. + read_result_ = result; + + delegate_->OnReadReady(); +} + +int SocketBIOAdapter::BIOWrite(const char* in, int len) { + if (len <= 0) + return len; + + // If the write buffer is not empty, there must be a pending Write() to flush + // it. + DCHECK(write_buffer_used_ == 0 || write_error_ == ERR_IO_PENDING); + + // If a previous Write() failed, report the error. + if (write_error_ != OK && write_error_ != ERR_IO_PENDING) { + OpenSSLPutNetError(FROM_HERE, write_error_); + return -1; + } + + // Instantiate the write buffer if needed. + if (!write_buffer_) { + DCHECK_EQ(0, write_buffer_used_); + write_buffer_ = base::MakeRefCounted<GrowableIOBuffer>(); + write_buffer_->SetCapacity(write_buffer_capacity_); + } + + // If the ring buffer is full, inform the caller to try again later. + if (write_buffer_used_ == write_buffer_->capacity()) { + BIO_set_retry_write(bio()); + return -1; + } + + int bytes_copied = 0; + + // If there is space after the offset, fill it. + if (write_buffer_used_ < write_buffer_->RemainingCapacity()) { + int chunk = + std::min(write_buffer_->RemainingCapacity() - write_buffer_used_, len); + memcpy(write_buffer_->data() + write_buffer_used_, in, chunk); + in += chunk; + len -= chunk; + bytes_copied += chunk; + write_buffer_used_ += chunk; + } + + // If there is still space for remaining data, try to wrap around. + if (len > 0 && write_buffer_used_ < write_buffer_->capacity()) { + // If there were any room after the offset, the previous branch would have + // filled it. + CHECK_LE(write_buffer_->RemainingCapacity(), write_buffer_used_); + int write_offset = write_buffer_used_ - write_buffer_->RemainingCapacity(); + int chunk = std::min(len, write_buffer_->capacity() - write_buffer_used_); + memcpy(write_buffer_->StartOfBuffer() + write_offset, in, chunk); + in += chunk; + len -= chunk; + bytes_copied += chunk; + write_buffer_used_ += chunk; + } + + // Either the buffer is now full or there is no more input. + DCHECK(len == 0 || write_buffer_used_ == write_buffer_->capacity()); + + // Schedule a socket Write() if necessary. (The ring buffer may previously + // have been empty.) + SocketWrite(); + + // If a read-interrupting write error was synchronously discovered, + // asynchronously notify OnReadReady. See https://crbug.com/249848. Avoid + // reentrancy by deferring it to a later event loop iteration. + if (write_error_ != OK && write_error_ != ERR_IO_PENDING && + read_result_ == ERR_IO_PENDING) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&SocketBIOAdapter::CallOnReadReady, + weak_factory_.GetWeakPtr())); + } + + return bytes_copied; +} + +void SocketBIOAdapter::SocketWrite() { + while (write_error_ == OK && write_buffer_used_ > 0) { + int write_size = + std::min(write_buffer_used_, write_buffer_->RemainingCapacity()); + int result = socket_->Write(write_buffer_.get(), write_size, + write_callback_, kTrafficAnnotation); + if (result == ERR_IO_PENDING) { + write_error_ = ERR_IO_PENDING; + return; + } + + HandleSocketWriteResult(result); + } +} + +void SocketBIOAdapter::HandleSocketWriteResult(int result) { + DCHECK_NE(ERR_IO_PENDING, result); + + if (result < 0) { + write_error_ = result; + + // The write buffer is no longer needed. + write_buffer_ = nullptr; + write_buffer_used_ = 0; + return; + } + + // Advance the ring buffer. + write_buffer_->set_offset(write_buffer_->offset() + result); + write_buffer_used_ -= result; + if (write_buffer_->RemainingCapacity() == 0) + write_buffer_->set_offset(0); + write_error_ = OK; + + // Release the write buffer if empty. + if (write_buffer_used_ == 0) + write_buffer_ = nullptr; +} + +void SocketBIOAdapter::OnSocketWriteComplete(int result) { + DCHECK_EQ(ERR_IO_PENDING, write_error_); + + bool was_full = write_buffer_used_ == write_buffer_->capacity(); + + HandleSocketWriteResult(result); + SocketWrite(); + + // If transitioning from being unable to accept data to being able to, signal + // OnWriteReady. + if (was_full) { + base::WeakPtr<SocketBIOAdapter> guard(weak_factory_.GetWeakPtr()); + delegate_->OnWriteReady(); + // OnWriteReady may delete the adapter. + if (!guard) + return; + } + + // Write errors are fed back into BIO_read once the read buffer is empty. If + // BIO_read is currently blocked, signal early that a read result is ready. + if (result < 0 && read_result_ == ERR_IO_PENDING) + delegate_->OnReadReady(); +} + +void SocketBIOAdapter::CallOnReadReady() { + if (read_result_ == ERR_IO_PENDING) + delegate_->OnReadReady(); +} + +SocketBIOAdapter* SocketBIOAdapter::GetAdapter(BIO* bio) { + DCHECK_EQ(&kBIOMethod, bio->method); + SocketBIOAdapter* adapter = reinterpret_cast<SocketBIOAdapter*>(bio->ptr); + if (adapter) + DCHECK_EQ(bio, adapter->bio()); + return adapter; +} + +int SocketBIOAdapter::BIOWriteWrapper(BIO* bio, const char* in, int len) { + BIO_clear_retry_flags(bio); + + SocketBIOAdapter* adapter = GetAdapter(bio); + if (!adapter) { + OpenSSLPutNetError(FROM_HERE, ERR_UNEXPECTED); + return -1; + } + + return adapter->BIOWrite(in, len); +} + +int SocketBIOAdapter::BIOReadWrapper(BIO* bio, char* out, int len) { + BIO_clear_retry_flags(bio); + + SocketBIOAdapter* adapter = GetAdapter(bio); + if (!adapter) { + OpenSSLPutNetError(FROM_HERE, ERR_UNEXPECTED); + return -1; + } + + return adapter->BIORead(out, len); +} + +long SocketBIOAdapter::BIOCtrlWrapper(BIO* bio, + int cmd, + long larg, + void* parg) { + switch (cmd) { + case BIO_CTRL_FLUSH: + // The SSL stack requires BIOs handle BIO_flush. + return 1; + } + + NOTIMPLEMENTED(); + return 0; +} + +const BIO_METHOD SocketBIOAdapter::kBIOMethod = { + 0, // type (unused) + nullptr, // name (unused) + SocketBIOAdapter::BIOWriteWrapper, + SocketBIOAdapter::BIOReadWrapper, + nullptr, // puts + nullptr, // gets + SocketBIOAdapter::BIOCtrlWrapper, + nullptr, // create + nullptr, // destroy + nullptr, // callback_ctrl +}; + +} // namespace net |