blob: de2399ba51f33f1ca71d738f9a748e571860e86e [file] [log] [blame]
// Copyright 2018 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 "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.h"
#include "net/quic/quic_chromium_connection_helper.h"
#include "net/third_party/quic/core/crypto/quic_random.h"
#include "net/third_party/quic/core/quic_config.h"
#include "net/third_party/quic/core/tls_client_handshaker.h"
#include "net/third_party/quic/core/tls_server_handshaker.h"
#include "net/third_party/quic/tools/quic_simple_crypto_server_stream_helper.h"
namespace blink {
namespace {
static const char kClosingDetails[] = "Application closed connection.";
static const size_t kHostnameLength = 32;
// QUIC's default is 100. Setting this value to 10000 allows room for QUIC to
// not refuse new incoming streams in the case that an application wants to send
// a small chunk of data per stream (and immediately close) unreliably.
uint32_t kMaxIncomingDynamicStreams = 10000;
// The P2PQuicPacketWriter is a private helper class that implements the
// QuicPacketWriter using a P2PQuicPacketTransport. This allows us to
// connect our own packet transport for writing into the QuicConnection.
// The normal case is using an ICE transport (packet_transport) for writing.
class P2PQuicPacketWriter : public quic::QuicPacketWriter,
public P2PQuicPacketTransport::WriteObserver {
public:
P2PQuicPacketWriter(P2PQuicPacketTransport* packet_transport)
: packet_transport_(packet_transport) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(packet_transport_);
packet_transport_->SetWriteObserver(this);
}
// This way the packet transport knows it no longer has a write observer and
// can DCHECK this on destruction.
~P2PQuicPacketWriter() override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
packet_transport_->SetWriteObserver(nullptr);
}
// Sets the QuicConnection (which owns this packet writer). This allows us
// to get the packet numbers of QUIC packets we write. The QuicConnection
// is created with a quic::QuicPacketWriter, so we can't set the connection
// in the constructor.
void InitializeWithQuicConnection(quic::QuicConnection* connection) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(connection);
if (packet_transport_->Writable()) {
SetWritable();
}
connection_ = connection;
}
// quic::QuicPacketWriter overrides.
// Writes a QUIC packet to the network with the packet number as additional
// packet info.
quic::WriteResult WritePacket(const char* buffer,
size_t buf_len,
const quic::QuicIpAddress& self_address,
const quic::QuicSocketAddress& peer_address,
quic::PerPacketOptions* options) override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(connection_);
if (IsWriteBlocked()) {
return quic::WriteResult(quic::WRITE_STATUS_BLOCKED, EWOULDBLOCK);
}
P2PQuicPacketTransport::QuicPacket packet;
packet.packet_number = connection_->packet_generator().packet_number();
packet.buffer = buffer;
packet.buf_len = buf_len;
int bytes_written = packet_transport_->WritePacket(packet);
if (bytes_written <= 0) {
writable_ = false;
return quic::WriteResult(quic::WRITE_STATUS_BLOCKED, EWOULDBLOCK);
}
return quic::WriteResult(quic::WRITE_STATUS_OK, bytes_written);
}
bool IsWriteBlockedDataBuffered() const override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return false;
}
bool IsWriteBlocked() const override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return !writable_;
}
quic::QuicByteCount GetMaxPacketSize(
const quic::QuicSocketAddress& peer_address) const override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// This can be configured later.
return 1200;
}
void SetWritable() override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
writable_ = true;
}
bool SupportsReleaseTime() const override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return false;
}
bool IsBatchMode() const override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return false;
}
char* GetNextWriteLocation(
const quic::QuicIpAddress& self_address,
const quic::QuicSocketAddress& peer_address) override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return nullptr;
}
quic::WriteResult Flush() override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return quic::WriteResult(quic::WRITE_STATUS_OK, 0);
}
// P2PQuicPacketTransport::WriteDelegate override.
void OnCanWrite() override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
SetWritable();
connection_->OnCanWrite();
}
private:
// The packet transport is owned by the P2PQuicSession, not the
// BlinkPacketWriter.
P2PQuicPacketTransport* packet_transport_;
// The QuicConnection owns this packet writer and will outlive it.
quic::QuicConnection* connection_;
bool writable_ = false;
THREAD_CHECKER(thread_checker_);
};
// Creates the QuicConnection for the QuicSession. Currently this connection
// uses a dummy address and ID. The |packet_writer| is a basic implementation
// using the QuicTransportConfig::packet_transport for writing. The |helper|
// and |alarm_factory| should be chromium specific implementations.
std::unique_ptr<quic::QuicConnection> CreateQuicConnection(
quic::Perspective perspective,
quic::QuicConnectionHelperInterface* helper,
quic::QuicPacketWriter* packet_writer,
quic::QuicAlarmFactory* alarm_factory) {
quic::QuicIpAddress ip;
ip.FromString("0.0.0.0");
quic::QuicSocketAddress dummy_address(ip, 0 /* Port */);
return std::make_unique<quic::QuicConnection>(
quic::EmptyQuicConnectionId() /* dummy ID */, dummy_address, helper,
alarm_factory, packet_writer,
/* owns_writer */ true, perspective, quic::CurrentSupportedVersions());
}
// A dummy helper for a server crypto stream that accepts all client hellos
// and generates a random connection ID.
class DummyCryptoServerStreamHelper
: public quic::QuicCryptoServerStream::Helper {
public:
explicit DummyCryptoServerStreamHelper(quic::QuicRandom* random)
: random_(random) {}
~DummyCryptoServerStreamHelper() override {}
quic::QuicConnectionId GenerateConnectionIdForReject(
quic::QuicConnectionId connection_id) const override {
return quic::QuicConnectionIdFromUInt64(random_->RandUint64());
}
bool CanAcceptClientHello(const quic::CryptoHandshakeMessage& message,
const quic::QuicSocketAddress& client_address,
const quic::QuicSocketAddress& peer_address,
const quic::QuicSocketAddress& self_address,
quic::QuicString* error_details) const override {
return true;
}
private:
// Used to generate random connection IDs. Needs to outlive this.
quic::QuicRandom* random_;
};
} // namespace
std::unique_ptr<P2PQuicTransportImpl> P2PQuicTransportImpl::Create(
quic::QuicClock* clock,
quic::QuicAlarmFactory* alarm_factory,
quic::QuicRandom* quic_random,
P2PQuicTransport::Delegate* delegate,
P2PQuicPacketTransport* packet_transport,
const P2PQuicTransportConfig& config,
std::unique_ptr<P2PQuicCryptoConfigFactory> crypto_config_factory) {
DCHECK(delegate);
DCHECK(packet_transport);
DCHECK(crypto_config_factory);
// The P2PQuicSession owns these chromium specific objects required
// by the QuicConnection. These outlive the QuicConnection itself.
std::unique_ptr<net::QuicChromiumConnectionHelper> helper =
std::make_unique<net::QuicChromiumConnectionHelper>(clock, quic_random);
P2PQuicPacketWriter* packet_writer =
new P2PQuicPacketWriter(packet_transport);
std::unique_ptr<quic::QuicConnection> quic_connection = CreateQuicConnection(
config.perspective, helper.get(), packet_writer, alarm_factory);
// It's okay for the quic::QuicConnection to have a P2PQuicPacketWriter before
// the P2PQuicPacketWriter is initialized, because the P2QuicPacketWriter
// won't be writable until this occurs.
packet_writer->InitializeWithQuicConnection(quic_connection.get());
// QUIC configurations for the session are specified here.
// TODO(shampson): Consider setting larger initial flow control window sizes
// so that the default limit doesn't cause initial undersending.
quic::QuicConfig quic_config;
quic_config.SetMaxIncomingDynamicStreamsToSend(kMaxIncomingDynamicStreams);
// The handshake network timeouts are configured to large values to prevent
// the QUIC connection from being closed on a slow connection. This can occur
// if signaling is slow and one side begins the handshake early.
// See ICE related bug: bugs.webrtc.org/9869.
//
// This timeout is from time of creation of the quic::QuicConnection object to
// the completion of the handshake. It must be larger than the idle time.
quic_config.set_max_time_before_crypto_handshake(
quic::QuicTime::Delta::FromSeconds(50));
// This is the timeout for idle time in the handshake. This value allows
// time for slow signaling to complete.
quic_config.set_max_idle_time_before_crypto_handshake(
quic::QuicTime::Delta::FromSeconds(30));
return std::make_unique<P2PQuicTransportImpl>(
delegate, packet_transport, std::move(config), std::move(helper),
std::move(quic_connection), quic_config, std::move(crypto_config_factory),
clock);
}
P2PQuicTransportImpl::P2PQuicTransportImpl(
Delegate* delegate,
P2PQuicPacketTransport* packet_transport,
const P2PQuicTransportConfig& p2p_transport_config,
std::unique_ptr<net::QuicChromiumConnectionHelper> helper,
std::unique_ptr<quic::QuicConnection> connection,
const quic::QuicConfig& quic_config,
std::unique_ptr<P2PQuicCryptoConfigFactory> crypto_config_factory,
quic::QuicClock* clock)
: quic::QuicSession(connection.get(),
nullptr /* visitor */,
quic_config,
quic::CurrentSupportedVersions()),
helper_(std::move(helper)),
connection_(std::move(connection)),
crypto_config_factory_(std::move(crypto_config_factory)),
perspective_(p2p_transport_config.perspective),
packet_transport_(packet_transport),
delegate_(delegate),
clock_(clock),
stream_delegate_read_buffer_size_(
p2p_transport_config.stream_delegate_read_buffer_size),
stream_write_buffer_size_(p2p_transport_config.stream_write_buffer_size) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(delegate_);
DCHECK(crypto_config_factory_);
DCHECK(clock_);
DCHECK(packet_transport_);
DCHECK_GT(stream_delegate_read_buffer_size_, 0u);
DCHECK_GT(stream_write_buffer_size_, 0u);
if (!p2p_transport_config.certificates.empty()) {
// TODO(https://crbug.com/874296): The web API accepts multiple
// certificates, and we might want to pass these down to let QUIC decide on
// what to use.
certificate_ = p2p_transport_config.certificates[0];
}
switch (perspective_) {
case quic::Perspective::IS_CLIENT: {
crypto_client_config_ =
crypto_config_factory_->CreateClientCryptoConfig();
break;
}
case quic::Perspective::IS_SERVER: {
crypto_server_config_ =
crypto_config_factory_->CreateServerCryptoConfig();
break;
}
default:
NOTREACHED();
break;
}
}
P2PQuicTransportImpl::~P2PQuicTransportImpl() {
packet_transport_->SetReceiveDelegate(nullptr);
}
void P2PQuicTransportImpl::Stop() {
// This shouldn't be called before Start().
DCHECK(crypto_stream_);
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (IsClosed()) {
return;
}
// The error code used for the connection closing is
// quic::QUIC_CONNECTION_CANCELLED. This allows us to distinguish that the
// application closed the connection, as opposed to it closing from a
// failure/error.
connection_->CloseConnection(
quic::QuicErrorCode::QUIC_CONNECTION_CANCELLED, kClosingDetails,
quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
}
void P2PQuicTransportImpl::Start(StartConfig config) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Either the remote fingerprints are being verified or a pre shared key is
// set.
DCHECK((certificate_ && !config.remote_fingerprints.empty()) ||
!config.pre_shared_key.empty());
DCHECK(!crypto_stream_);
remote_fingerprints_ = std::move(config.remote_fingerprints);
switch (perspective_) {
case quic::Perspective::IS_CLIENT: {
crypto_client_config_->set_pre_shared_key(config.pre_shared_key);
break;
}
case quic::Perspective::IS_SERVER: {
crypto_server_config_->set_pre_shared_key(config.pre_shared_key);
break;
}
default:
NOTREACHED();
break;
}
InitializeCryptoStream();
if (perspective_ == quic::Perspective::IS_CLIENT) {
quic::QuicCryptoClientStream* client_crypto_stream =
static_cast<quic::QuicCryptoClientStream*>(crypto_stream_.get());
client_crypto_stream->CryptoConnect();
}
// Now that crypto streams are setup we are ready to receive QUIC packets.
packet_transport_->SetReceiveDelegate(this);
}
void P2PQuicTransportImpl::OnPacketDataReceived(const char* data,
size_t data_len) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Received data from the |packet_transport_|. Create a QUIC packet and send
// it to be processed by the QuicSession/Connection.
quic::QuicReceivedPacket packet(data, data_len, clock_->Now());
ProcessUdpPacket(connection()->self_address(), connection()->peer_address(),
packet);
}
quic::QuicCryptoStream* P2PQuicTransportImpl::GetMutableCryptoStream() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return crypto_stream_.get();
}
const quic::QuicCryptoStream* P2PQuicTransportImpl::GetCryptoStream() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return crypto_stream_.get();
}
P2PQuicStreamImpl* P2PQuicTransportImpl::CreateStream() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return CreateOutgoingBidirectionalStream();
}
P2PQuicTransportStats P2PQuicTransportImpl::GetStats() const {
return P2PQuicTransportStats(connection_->GetStats(),
num_outgoing_streams_created_,
num_incoming_streams_created_);
}
P2PQuicStreamImpl* P2PQuicTransportImpl::CreateOutgoingBidirectionalStream() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
num_outgoing_streams_created_++;
P2PQuicStreamImpl* stream =
CreateStreamInternal(GetNextOutgoingBidirectionalStreamId());
ActivateStream(std::unique_ptr<P2PQuicStreamImpl>(stream));
return stream;
}
P2PQuicStreamImpl* P2PQuicTransportImpl::CreateIncomingStream(
quic::QuicStreamId id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
num_incoming_streams_created_++;
P2PQuicStreamImpl* stream = CreateStreamInternal(id);
ActivateStream(std::unique_ptr<P2PQuicStreamImpl>(stream));
delegate_->OnStream(stream);
return stream;
}
P2PQuicStreamImpl* P2PQuicTransportImpl::CreateIncomingStream(
quic::PendingStream pending) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
num_incoming_streams_created_++;
P2PQuicStreamImpl* stream = CreateStreamInternal(std::move(pending));
ActivateStream(std::unique_ptr<P2PQuicStreamImpl>(stream));
delegate_->OnStream(stream);
return stream;
}
P2PQuicStreamImpl* P2PQuicTransportImpl::CreateStreamInternal(
quic::QuicStreamId id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(crypto_stream_);
DCHECK(IsEncryptionEstablished());
DCHECK(!IsClosed());
return new P2PQuicStreamImpl(id, this, stream_delegate_read_buffer_size_,
stream_write_buffer_size_);
}
P2PQuicStreamImpl* P2PQuicTransportImpl::CreateStreamInternal(
quic::PendingStream pending) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(crypto_stream_);
DCHECK(IsEncryptionEstablished());
DCHECK(!IsClosed());
return new P2PQuicStreamImpl(std::move(pending), this,
stream_delegate_read_buffer_size_,
stream_write_buffer_size_);
}
void P2PQuicTransportImpl::InitializeCryptoStream() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!crypto_stream_);
// TODO(shampson): If the P2PQuicTransportImpl is subclassed into a client
// and server class we can call this as a virtual function and not need this
// switch statement.
switch (perspective_) {
case quic::Perspective::IS_CLIENT: {
DCHECK(crypto_client_config_);
// The host must be unique for every endpoint the client communicates
// with.
char random_hostname[kHostnameLength];
helper_->GetRandomGenerator()->RandBytes(random_hostname,
kHostnameLength);
quic::QuicServerId server_id(
/*host=*/quic::QuicString(random_hostname, kHostnameLength),
/*port=*/0,
/*privacy_mode_enabled=*/false);
crypto_stream_ = std::make_unique<quic::QuicCryptoClientStream>(
server_id, /*QuicSession=*/this,
crypto_client_config_->proof_verifier()->CreateDefaultContext(),
crypto_client_config_.get(), /*ProofHandler=*/this);
QuicSession::Initialize();
break;
}
case quic::Perspective::IS_SERVER: {
DCHECK(crypto_server_config_);
// Provide server with serialized config string to prove ownership.
quic::QuicCryptoServerConfig::ConfigOptions options;
// The |message| is used to handle the return value of AddDefaultConfig
// which is raw pointer of the CryptoHandshakeMessage.
std::unique_ptr<quic::CryptoHandshakeMessage> message(
crypto_server_config_->AddDefaultConfig(
helper_->GetRandomGenerator(), helper_->GetClock(), options));
compressed_certs_cache_.reset(new quic::QuicCompressedCertsCache(
quic::QuicCompressedCertsCache::kQuicCompressedCertsCacheSize));
bool use_stateless_rejects_if_peer_supported = false;
server_stream_helper_ = std::make_unique<DummyCryptoServerStreamHelper>(
helper_->GetRandomGenerator());
crypto_stream_ = std::make_unique<quic::QuicCryptoServerStream>(
crypto_server_config_.get(), compressed_certs_cache_.get(),
use_stateless_rejects_if_peer_supported, this,
server_stream_helper_.get());
QuicSession::Initialize();
break;
}
default:
NOTREACHED();
break;
}
}
void P2PQuicTransportImpl::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
QuicSession::OnCryptoHandshakeEvent(event);
if (event == HANDSHAKE_CONFIRMED) {
DCHECK(IsEncryptionEstablished());
DCHECK(IsCryptoHandshakeConfirmed());
delegate_->OnConnected();
}
}
void P2PQuicTransportImpl::OnConnectionClosed(
quic::QuicErrorCode error,
const std::string& error_details,
quic::ConnectionCloseSource source) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
quic::QuicSession::OnConnectionClosed(error, error_details, source);
if (error != quic::QuicErrorCode::QUIC_CONNECTION_CANCELLED) {
delegate_->OnConnectionFailed(
error_details, source == quic::ConnectionCloseSource::FROM_PEER);
} else if (source == quic::ConnectionCloseSource::FROM_PEER) {
// This connection was closed by the application of the remote side.
delegate_->OnRemoteStopped();
}
}
bool P2PQuicTransportImpl::IsClosed() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return !connection_->connected();
}
void P2PQuicTransportImpl::set_crypto_client_config(
std::unique_ptr<quic::QuicCryptoClientConfig> crypto_client_config) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
crypto_client_config_ = std::move(crypto_client_config);
}
} // namespace blink