diff --git a/webrtc/p2p/p2p.gyp b/webrtc/p2p/p2p.gyp index 4bce605dee..9812a0acb7 100644 --- a/webrtc/p2p/p2p.gyp +++ b/webrtc/p2p/p2p.gyp @@ -107,6 +107,8 @@ 'quic/quicconnectionhelper.h', 'quic/quicsession.cc', 'quic/quicsession.h', + 'quic/quictransportchannel.cc', + 'quic/quictransportchannel.h', 'quic/reliablequicstream.cc', 'quic/reliablequicstream.h', ], diff --git a/webrtc/p2p/p2p_tests.gypi b/webrtc/p2p/p2p_tests.gypi index 9354d33999..713d6e4d3e 100644 --- a/webrtc/p2p/p2p_tests.gypi +++ b/webrtc/p2p/p2p_tests.gypi @@ -41,6 +41,7 @@ 'sources': [ 'quic/quicconnectionhelper_unittest.cc', 'quic/quicsession_unittest.cc', + 'quic/quictransportchannel_unittest.cc', 'quic/reliablequicstream_unittest.cc', ], }], diff --git a/webrtc/p2p/quic/quictransportchannel.cc b/webrtc/p2p/quic/quictransportchannel.cc new file mode 100644 index 0000000000..eae936ba53 --- /dev/null +++ b/webrtc/p2p/quic/quictransportchannel.cc @@ -0,0 +1,552 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/quic/quictransportchannel.h" + +#include + +#include "net/quic/crypto/proof_source.h" +#include "net/quic/crypto/proof_verifier.h" +#include "net/quic/crypto/quic_crypto_client_config.h" +#include "net/quic/crypto/quic_crypto_server_config.h" +#include "net/quic/quic_connection.h" +#include "net/quic/quic_crypto_client_stream.h" +#include "net/quic/quic_crypto_server_stream.h" +#include "net/quic/quic_protocol.h" +#include "webrtc/base/checks.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/socket.h" +#include "webrtc/base/thread.h" +#include "webrtc/p2p/base/common.h" + +namespace { + +// QUIC public header constants for net::QuicConnection. These are arbitrary +// given that |channel_| only receives packets specific to this channel, +// in which case we already know the QUIC packets have the correct destination. +const net::QuicConnectionId kConnectionId = 0; +const net::IPAddressNumber kConnectionIpAddress(net::kIPv4AddressSize, 0); +const net::IPEndPoint kConnectionIpEndpoint(kConnectionIpAddress, 0); + +// Arbitrary server port number for net::QuicCryptoClientConfig. +const int kQuicServerPort = 0; + +// QUIC connection timeout. This is large so that |channel_| can +// be responsible for connection timeout. +const int kIdleConnectionStateLifetime = 1000; // seconds + +// Length of HKDF input keying material, equal to its number of bytes. +// https://tools.ietf.org/html/rfc5869#section-2.2. +// TODO(mikescarlett): Verify that input keying material length is correct. +const size_t kInputKeyingMaterialLength = 32; + +// We don't pull the RTP constants from rtputils.h, to avoid a layer violation. +const size_t kMinRtpPacketLen = 12; + +bool IsRtpPacket(const char* data, size_t len) { + const uint8_t* u = reinterpret_cast(data); + return (len >= kMinRtpPacketLen && (u[0] & 0xC0) == 0x80); +} + +// Function for detecting QUIC packets based off +// https://tools.ietf.org/html/draft-tsvwg-quic-protocol-02#section-6. +const size_t kMinQuicPacketLen = 2; + +bool IsQuicPacket(const char* data, size_t len) { + const uint8_t* u = reinterpret_cast(data); + return (len >= kMinQuicPacketLen && (u[0] & 0x80) == 0); +} + +// Used by QuicCryptoServerConfig to provide dummy proof credentials. +// TODO(mikescarlett): Remove when secure P2P QUIC handshake is possible. +class DummyProofSource : public net::ProofSource { + public: + DummyProofSource() {} + ~DummyProofSource() override {} + + // ProofSource override. + bool GetProof(const net::IPAddressNumber& server_ip, + const std::string& hostname, + const std::string& server_config, + bool ecdsa_ok, + const std::vector** out_certs, + std::string* out_signature, + std::string* out_leaf_cert_sct) override { + LOG(INFO) << "GetProof() providing dummy credentials for insecure QUIC"; + std::vector* certs = new std::vector(); + certs->push_back("Dummy cert"); + std::string signature("Dummy signature"); + + *out_certs = certs; + *out_signature = signature; + + return true; + } +}; + +// Used by QuicCryptoClientConfig to ignore the peer's credentials +// and establish an insecure QUIC connection. +// TODO(mikescarlett): Remove when secure P2P QUIC handshake is possible. +class InsecureProofVerifier : public net::ProofVerifier { + public: + InsecureProofVerifier() {} + ~InsecureProofVerifier() override {} + + // ProofVerifier override. + net::QuicAsyncStatus VerifyProof( + const std::string& hostname, + const std::string& server_config, + const std::vector& certs, + const std::string& cert_sct, + const std::string& signature, + const net::ProofVerifyContext* verify_context, + std::string* error_details, + scoped_ptr* verify_details, + net::ProofVerifierCallback* callback) override { + LOG(INFO) << "VerifyProof() ignoring credentials and returning success"; + return net::QUIC_SUCCESS; + } +}; + +} // namespace + +namespace cricket { + +QuicTransportChannel::QuicTransportChannel(TransportChannelImpl* channel) + : TransportChannelImpl(channel->transport_name(), channel->component()), + worker_thread_(rtc::Thread::Current()), + channel_(channel), + helper_(worker_thread_) { + channel_->SignalWritableState.connect(this, + &QuicTransportChannel::OnWritableState); + channel_->SignalReadPacket.connect(this, &QuicTransportChannel::OnReadPacket); + channel_->SignalSentPacket.connect(this, &QuicTransportChannel::OnSentPacket); + channel_->SignalReadyToSend.connect(this, + &QuicTransportChannel::OnReadyToSend); + channel_->SignalGatheringState.connect( + this, &QuicTransportChannel::OnGatheringState); + channel_->SignalCandidateGathered.connect( + this, &QuicTransportChannel::OnCandidateGathered); + channel_->SignalRoleConflict.connect(this, + &QuicTransportChannel::OnRoleConflict); + channel_->SignalRouteChange.connect(this, + &QuicTransportChannel::OnRouteChange); + channel_->SignalConnectionRemoved.connect( + this, &QuicTransportChannel::OnConnectionRemoved); + channel_->SignalReceivingState.connect( + this, &QuicTransportChannel::OnReceivingState); + + // Set the QUIC connection timeout. + config_.SetIdleConnectionStateLifetime( + net::QuicTime::Delta::FromSeconds(kIdleConnectionStateLifetime), + net::QuicTime::Delta::FromSeconds(kIdleConnectionStateLifetime)); + // Set the bytes reserved for the QUIC connection ID to zero. + config_.SetBytesForConnectionIdToSend(0); +} + +QuicTransportChannel::~QuicTransportChannel() {} + +bool QuicTransportChannel::SetLocalCertificate( + const rtc::scoped_refptr& certificate) { + if (!certificate) { + LOG_J(ERROR, this) << "No local certificate was supplied. Not doing QUIC."; + return false; + } + if (!local_certificate_) { + local_certificate_ = certificate; + return true; + } + if (certificate == local_certificate_) { + // This may happen during renegotiation. + LOG_J(INFO, this) << "Ignoring identical certificate"; + return true; + } + LOG_J(ERROR, this) << "Local certificate of the QUIC connection already set. " + "Can't change the local certificate once it's active."; + return false; +} + +rtc::scoped_refptr +QuicTransportChannel::GetLocalCertificate() const { + return local_certificate_; +} + +bool QuicTransportChannel::SetSslRole(rtc::SSLRole role) { + if (ssl_role_ && *ssl_role_ == role) { + LOG_J(WARNING, this) << "Ignoring SSL Role identical to current role."; + return true; + } + if (quic_state_ != QUIC_TRANSPORT_CONNECTED) { + ssl_role_ = rtc::Optional(role); + return true; + } + LOG_J(ERROR, this) + << "SSL Role can't be reversed after the session is setup."; + return false; +} + +bool QuicTransportChannel::GetSslRole(rtc::SSLRole* role) const { + if (!ssl_role_) { + return false; + } + *role = *ssl_role_; + return true; +} + +bool QuicTransportChannel::SetRemoteFingerprint(const std::string& digest_alg, + const uint8_t* digest, + size_t digest_len) { + if (digest_alg.empty()) { + RTC_DCHECK(!digest_len); + LOG_J(ERROR, this) << "Remote peer doesn't support digest algorithm."; + return false; + } + std::string remote_fingerprint_value(reinterpret_cast(digest), + digest_len); + // Once we have the local certificate, the same remote fingerprint can be set + // multiple times. This may happen during renegotiation. + if (remote_fingerprint_ && + remote_fingerprint_->value == remote_fingerprint_value && + remote_fingerprint_->algorithm == digest_alg) { + LOG_J(INFO, this) << "Ignoring identical remote fingerprint and algorithm"; + return true; + } + remote_fingerprint_ = rtc::Optional(RemoteFingerprint()); + remote_fingerprint_->value = remote_fingerprint_value; + remote_fingerprint_->algorithm = digest_alg; + return true; +} + +bool QuicTransportChannel::ExportKeyingMaterial(const std::string& label, + const uint8_t* context, + size_t context_len, + bool use_context, + uint8_t* result, + size_t result_len) { + std::string quic_context(reinterpret_cast(context), context_len); + std::string quic_result; + if (!quic_->ExportKeyingMaterial(label, quic_context, result_len, + &quic_result)) { + return false; + } + quic_result.copy(reinterpret_cast(result), result_len); + return true; +} + +bool QuicTransportChannel::GetSrtpCryptoSuite(int* cipher) { + *cipher = rtc::SRTP_AES128_CM_SHA1_80; + return true; +} + +// Called from upper layers to send a media packet. +int QuicTransportChannel::SendPacket(const char* data, + size_t size, + const rtc::PacketOptions& options, + int flags) { + if ((flags & PF_SRTP_BYPASS) && IsRtpPacket(data, size)) { + return channel_->SendPacket(data, size, options); + } + LOG(ERROR) << "Failed to send an invalid SRTP bypass packet using QUIC."; + return -1; +} + +// The state transition logic here is as follows: +// - Before the QUIC handshake is complete, the QUIC channel is unwritable. +// - When |channel_| goes writable we start the QUIC handshake. +// - Once the QUIC handshake completes, the state is that of the +// |channel_| again. +void QuicTransportChannel::OnWritableState(TransportChannel* channel) { + ASSERT(rtc::Thread::Current() == worker_thread_); + ASSERT(channel == channel_); + LOG_J(VERBOSE, this) + << "QuicTransportChannel: channel writable state changed to " + << channel_->writable(); + switch (quic_state_) { + case QUIC_TRANSPORT_NEW: + // Start the QUIC handshake when |channel_| is writable. + // This will fail if the SSL role or remote fingerprint are not set. + // Otherwise failure could result from network or QUIC errors. + MaybeStartQuic(); + break; + case QUIC_TRANSPORT_CONNECTED: + // Note: SignalWritableState fired by set_writable. + set_writable(channel_->writable()); + if (HasDataToWrite()) { + OnCanWrite(); + } + break; + case QUIC_TRANSPORT_CONNECTING: + // This channel is not writable until the QUIC handshake finishes. It + // might have been write blocked. + if (HasDataToWrite()) { + OnCanWrite(); + } + break; + case QUIC_TRANSPORT_CLOSED: + // TODO(mikescarlett): Allow the QUIC connection to be reset if it drops + // due to a non-failure. + break; + } +} + +void QuicTransportChannel::OnReceivingState(TransportChannel* channel) { + ASSERT(rtc::Thread::Current() == worker_thread_); + ASSERT(channel == channel_); + LOG_J(VERBOSE, this) + << "QuicTransportChannel: channel receiving state changed to " + << channel_->receiving(); + if (quic_state_ == QUIC_TRANSPORT_CONNECTED) { + // Note: SignalReceivingState fired by set_receiving. + set_receiving(channel_->receiving()); + } +} + +void QuicTransportChannel::OnReadPacket(TransportChannel* channel, + const char* data, + size_t size, + const rtc::PacketTime& packet_time, + int flags) { + ASSERT(rtc::Thread::Current() == worker_thread_); + ASSERT(channel == channel_); + ASSERT(flags == 0); + + switch (quic_state_) { + case QUIC_TRANSPORT_NEW: + // This would occur if other peer is ready to start QUIC but this peer + // hasn't started QUIC. + LOG_J(INFO, this) << "Dropping packet received before QUIC started."; + break; + case QUIC_TRANSPORT_CONNECTING: + case QUIC_TRANSPORT_CONNECTED: + // We should only get QUIC or SRTP packets; STUN's already been demuxed. + // Is this potentially a QUIC packet? + if (IsQuicPacket(data, size)) { + if (!HandleQuicPacket(data, size)) { + LOG_J(ERROR, this) << "Failed to handle QUIC packet."; + return; + } + } else { + // If this is an RTP packet, signal upwards as a bypass packet. + if (!IsRtpPacket(data, size)) { + LOG_J(ERROR, this) << "Received unexpected non-QUIC, non-RTP packet."; + return; + } + SignalReadPacket(this, data, size, packet_time, PF_SRTP_BYPASS); + } + break; + case QUIC_TRANSPORT_CLOSED: + // This shouldn't be happening. Drop the packet. + break; + } +} + +void QuicTransportChannel::OnSentPacket(TransportChannel* channel, + const rtc::SentPacket& sent_packet) { + ASSERT(rtc::Thread::Current() == worker_thread_); + SignalSentPacket(this, sent_packet); +} + +void QuicTransportChannel::OnReadyToSend(TransportChannel* channel) { + if (writable()) { + SignalReadyToSend(this); + } +} + +void QuicTransportChannel::OnGatheringState(TransportChannelImpl* channel) { + ASSERT(channel == channel_); + SignalGatheringState(this); +} + +void QuicTransportChannel::OnCandidateGathered(TransportChannelImpl* channel, + const Candidate& c) { + ASSERT(channel == channel_); + SignalCandidateGathered(this, c); +} + +void QuicTransportChannel::OnRoleConflict(TransportChannelImpl* channel) { + ASSERT(channel == channel_); + SignalRoleConflict(this); +} + +void QuicTransportChannel::OnRouteChange(TransportChannel* channel, + const Candidate& candidate) { + ASSERT(channel == channel_); + SignalRouteChange(this, candidate); +} + +void QuicTransportChannel::OnConnectionRemoved(TransportChannelImpl* channel) { + ASSERT(channel == channel_); + SignalConnectionRemoved(this); +} + +bool QuicTransportChannel::MaybeStartQuic() { + if (!channel_->writable()) { + LOG_J(ERROR, this) << "Couldn't start QUIC handshake."; + return false; + } + if (!CreateQuicSession() || !StartQuicHandshake()) { + LOG_J(WARNING, this) << "Underlying channel is writable but cannot start " + "the QUIC handshake."; + return false; + } + // Verify connection is not closed due to QUIC bug or network failure. + // A closed connection should not happen since |channel_| is writable. + if (!quic_->connection()->connected()) { + LOG_J(ERROR, this) << "QUIC connection should not be closed if underlying " + "channel is writable."; + return false; + } + // Indicate that |quic_| is ready to receive QUIC packets. + set_quic_state(QUIC_TRANSPORT_CONNECTING); + return true; +} + +bool QuicTransportChannel::CreateQuicSession() { + if (!ssl_role_ || !remote_fingerprint_) { + return false; + } + net::Perspective perspective = (*ssl_role_ == rtc::SSL_CLIENT) + ? net::Perspective::IS_CLIENT + : net::Perspective::IS_SERVER; + bool owns_writer = false; + scoped_ptr connection(new net::QuicConnection( + kConnectionId, kConnectionIpEndpoint, &helper_, this, owns_writer, + perspective, net::QuicSupportedVersions())); + quic_.reset(new QuicSession(std::move(connection), config_)); + quic_->SignalHandshakeComplete.connect( + this, &QuicTransportChannel::OnHandshakeComplete); + quic_->SignalConnectionClosed.connect( + this, &QuicTransportChannel::OnConnectionClosed); + return true; +} + +bool QuicTransportChannel::StartQuicHandshake() { + if (*ssl_role_ == rtc::SSL_CLIENT) { + // Unique identifier for remote peer. + net::QuicServerId server_id(remote_fingerprint_->value, kQuicServerPort); + // Perform authentication of remote peer; owned by QuicCryptoClientConfig. + // TODO(mikescarlett): Actually verify proof. + net::ProofVerifier* proof_verifier = new InsecureProofVerifier(); + quic_crypto_client_config_.reset( + new net::QuicCryptoClientConfig(proof_verifier)); + net::QuicCryptoClientStream* crypto_stream = + new net::QuicCryptoClientStream(server_id, quic_.get(), + new net::ProofVerifyContext(), + quic_crypto_client_config_.get(), this); + quic_->StartClientHandshake(crypto_stream); + LOG_J(INFO, this) << "QuicTransportChannel: Started client handshake."; + } else { + RTC_DCHECK_EQ(*ssl_role_, rtc::SSL_SERVER); + // Provide credentials to remote peer; owned by QuicCryptoServerConfig. + // TODO(mikescarlett): Actually provide credentials. + net::ProofSource* proof_source = new DummyProofSource(); + // Input keying material to HKDF, per http://tools.ietf.org/html/rfc5869. + // This is pseudorandom so that HKDF-Extract outputs a pseudorandom key, + // since QuicCryptoServerConfig does not use a salt value. + std::string source_address_token_secret; + if (!rtc::CreateRandomString(kInputKeyingMaterialLength, + &source_address_token_secret)) { + LOG_J(ERROR, this) << "Error generating input keying material for HKDF."; + return false; + } + quic_crypto_server_config_.reset(new net::QuicCryptoServerConfig( + source_address_token_secret, helper_.GetRandomGenerator(), + proof_source)); + // Provide server with serialized config string to prove ownership. + net::QuicCryptoServerConfig::ConfigOptions options; + quic_crypto_server_config_->AddDefaultConfig(helper_.GetRandomGenerator(), + helper_.GetClock(), options); + net::QuicCryptoServerStream* crypto_stream = + new net::QuicCryptoServerStream(quic_crypto_server_config_.get(), + quic_.get()); + quic_->StartServerHandshake(crypto_stream); + LOG_J(INFO, this) << "QuicTransportChannel: Started server handshake."; + } + return true; +} + +bool QuicTransportChannel::HandleQuicPacket(const char* data, size_t size) { + ASSERT(rtc::Thread::Current() == worker_thread_); + return quic_->OnReadPacket(data, size); +} + +net::WriteResult QuicTransportChannel::WritePacket( + const char* buffer, + size_t buf_len, + const net::IPAddressNumber& self_address, + const net::IPEndPoint& peer_address) { + // QUIC should never call this if IsWriteBlocked, but just in case... + if (IsWriteBlocked()) { + return net::WriteResult(net::WRITE_STATUS_BLOCKED, EWOULDBLOCK); + } + // TODO(mikescarlett): Figure out how to tell QUIC "I dropped your packet, but + // don't block" without the QUIC connection tearing itself down. + int sent = channel_->SendPacket(buffer, buf_len, rtc::PacketOptions()); + int bytes_written = sent > 0 ? sent : 0; + return net::WriteResult(net::WRITE_STATUS_OK, bytes_written); +} + +// TODO(mikescarlett): Implement check for whether |channel_| is currently +// write blocked so that |quic_| does not try to write packet. This is +// necessary because |channel_| can be writable yet write blocked and +// channel_->GetError() is not flushed when there is no error. +bool QuicTransportChannel::IsWriteBlocked() const { + return !channel_->writable(); +} + +void QuicTransportChannel::OnHandshakeComplete() { + set_quic_state(QUIC_TRANSPORT_CONNECTED); + set_writable(true); + // OnReceivingState might have been called before the QUIC channel was + // connected, in which case the QUIC channel is now receiving. + if (channel_->receiving()) { + set_receiving(true); + } +} + +void QuicTransportChannel::OnConnectionClosed(net::QuicErrorCode error, + bool from_peer) { + LOG_J(INFO, this) << "Connection closed by " << (from_peer ? "other" : "this") + << " peer " + << "with QUIC error " << error; + // TODO(mikescarlett): Allow the QUIC session to be reset when the connection + // does not close due to failure. + set_quic_state(QUIC_TRANSPORT_CLOSED); + set_writable(false); +} + +void QuicTransportChannel::OnProofValid( + const net::QuicCryptoClientConfig::CachedState& cached) { + LOG_J(INFO, this) << "Cached proof marked valid"; +} + +void QuicTransportChannel::OnProofVerifyDetailsAvailable( + const net::ProofVerifyDetails& verify_details) { + LOG_J(INFO, this) << "Proof verify details available from" + << " QuicCryptoClientStream"; +} + +bool QuicTransportChannel::HasDataToWrite() const { + return quic_ && quic_->HasDataToWrite(); +} + +void QuicTransportChannel::OnCanWrite() { + RTC_DCHECK(quic_ != nullptr); + quic_->connection()->OnCanWrite(); +} + +void QuicTransportChannel::set_quic_state(QuicTransportState state) { + LOG_J(VERBOSE, this) << "set_quic_state from:" << quic_state_ << " to " + << state; + quic_state_ = state; +} + +} // namespace cricket diff --git a/webrtc/p2p/quic/quictransportchannel.h b/webrtc/p2p/quic/quictransportchannel.h new file mode 100644 index 0000000000..f9001088e2 --- /dev/null +++ b/webrtc/p2p/quic/quictransportchannel.h @@ -0,0 +1,280 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_QUIC_QUICTRANSPORTCHANNEL_H_ +#define WEBRTC_P2P_QUIC_QUICTRANSPORTCHANNEL_H_ + +#include +#include + +#include "net/quic/quic_packet_writer.h" +#include "webrtc/base/optional.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/p2p/base/transportchannelimpl.h" +#include "webrtc/p2p/quic/quicconnectionhelper.h" +#include "webrtc/p2p/quic/quicsession.h" + +namespace cricket { + +enum QuicTransportState { + // Haven't started QUIC handshake. + QUIC_TRANSPORT_NEW = 0, + // Started QUIC handshake. + QUIC_TRANSPORT_CONNECTING, + // Negotiated, and has an encrypted connection. + QUIC_TRANSPORT_CONNECTED, + // QUIC connection closed due to handshake failure or explicit shutdown. + QUIC_TRANSPORT_CLOSED, +}; + +// QuicTransportChannel uses the QUIC protocol to establish encryption with +// another peer, wrapping an existing TransportChannelImpl instance +// (e.g a P2PTransportChannel) responsible for connecting peers. +// Once the wrapped transport channel is connected, QuicTransportChannel +// negotiates the crypto handshake and establishes SRTP keying material. +// +// How it works: +// +// QuicTransportChannel { +// QuicSession* quic_; +// TransportChannelImpl* channel_; +// } +// +// - Data written to SendPacket() is passed directly to |channel_| if it is +// an SRTP packet with the PF_SRTP_BYPASS flag. +// +// - |quic_| passes outgoing packets to WritePacket(), which transfers them +// to |channel_| to be sent across the network. +// +// - Data which comes into QuicTransportChannel::OnReadPacket is checked to +// see if it is QUIC, and if it is, passed to |quic_|. SRTP packets are +// signaled upwards as bypass packets. +// +// - When the QUIC handshake is completed, quic_state() returns +// QUIC_TRANSPORT_CONNECTED and SRTP keying material can be exported. +// +// TODO(mikescarlett): Implement secure QUIC handshake, 0-RTT handshakes, and +// QUIC data streams. +class QuicTransportChannel : public TransportChannelImpl, + public net::QuicPacketWriter, + public net::QuicCryptoClientStream::ProofHandler { + public: + // |channel| - the TransportChannelImpl we are wrapping. + explicit QuicTransportChannel(TransportChannelImpl* channel); + ~QuicTransportChannel() override; + + // TransportChannel overrides. + // TODO(mikescarlett): Implement certificate authentication. + bool SetLocalCertificate( + const rtc::scoped_refptr& certificate) override; + rtc::scoped_refptr GetLocalCertificate() const override; + // TODO(mikescarlett): Implement fingerprint authentication. + bool SetRemoteFingerprint(const std::string& digest_alg, + const uint8_t* digest, + size_t digest_len) override; + // TODO(mikescarlett): Remove this DTLS-specific method when TransportChannel + // does not require defining it. + bool IsDtlsActive() const override { return true; } + // Sends a RTP packet if the PF_SRTP_BYPASS flag is set. + int SendPacket(const char* data, + size_t size, + const rtc::PacketOptions& options, + int flags) override; + // Sets up the ciphers to use for SRTP. + // TODO(mikescarlett): Use SRTP ciphers for negotiation. + bool SetSrtpCryptoSuites(const std::vector& ciphers) override { + return true; + } + // Determines which SRTP cipher was negotiated. + // TODO(mikescarlett): Implement QUIC cipher negotiation. This currently + // returns SRTP_AES128_CM_SHA1_80. + bool GetSrtpCryptoSuite(int* cipher) override; + bool SetSslRole(rtc::SSLRole role) override; + bool GetSslRole(rtc::SSLRole* role) const override; + // Determines which SSL cipher was negotiated. + // TODO(mikescarlett): Implement QUIC cipher negotiation. + bool GetSslCipherSuite(int* cipher) override { return false; } + // Once QUIC is established (i.e., |quic_state_| is QUIC_TRANSPORT_CONNECTED), + // this extracts the keys negotiated during the QUIC handshake, for use + // in external encryption such as for extracting SRTP keys. + bool ExportKeyingMaterial(const std::string& label, + const uint8_t* context, + size_t context_len, + bool use_context, + uint8_t* result, + size_t result_len) override; + // TODO(mikescarlett): Remove this method once TransportChannel does not + // require defining it. + bool GetRemoteSSLCertificate(rtc::SSLCertificate** cert) const override { + return false; + } + + // TransportChannelImpl overrides that we forward to the wrapped transport. + void SetIceRole(IceRole role) override { channel_->SetIceRole(role); } + IceRole GetIceRole() const override { return channel_->GetIceRole(); } + int SetOption(rtc::Socket::Option opt, int value) override { + return channel_->SetOption(opt, value); + } + bool GetOption(rtc::Socket::Option opt, int* value) override { + return channel_->GetOption(opt, value); + } + int GetError() override { return channel_->GetError(); } + bool GetStats(ConnectionInfos* infos) override { + return channel_->GetStats(infos); + } + const std::string SessionId() const override { return channel_->SessionId(); } + TransportChannelState GetState() const override { + return channel_->GetState(); + } + void SetIceTiebreaker(uint64_t tiebreaker) override { + channel_->SetIceTiebreaker(tiebreaker); + } + void SetIceCredentials(const std::string& ice_ufrag, + const std::string& ice_pwd) override { + channel_->SetIceCredentials(ice_ufrag, ice_pwd); + } + void SetRemoteIceCredentials(const std::string& ice_ufrag, + const std::string& ice_pwd) override { + channel_->SetRemoteIceCredentials(ice_ufrag, ice_pwd); + } + void SetRemoteIceMode(IceMode mode) override { + channel_->SetRemoteIceMode(mode); + } + void MaybeStartGathering() override { channel_->MaybeStartGathering(); } + IceGatheringState gathering_state() const override { + return channel_->gathering_state(); + } + void AddRemoteCandidate(const Candidate& candidate) override { + channel_->AddRemoteCandidate(candidate); + } + void SetIceConfig(const IceConfig& config) override { + channel_->SetIceConfig(config); + } + void Connect() override { + channel_->Connect(); + } + + // QuicPacketWriter overrides. + // Called from net::QuicConnection when |quic_| has packets to write. + net::WriteResult WritePacket(const char* buffer, + size_t buf_len, + const net::IPAddressNumber& self_address, + const net::IPEndPoint& peer_address) override; + // Whether QuicTransportChannel buffers data when unable to write. If this is + // set to false, then net::QuicConnection buffers unsent packets. + bool IsWriteBlockedDataBuffered() const override { return false; } + // Whether QuicTransportChannel is write blocked. If this returns true, + // outgoing QUIC packets are queued by net::QuicConnection until + // QuicTransportChannel::OnCanWrite() is called. + bool IsWriteBlocked() const override; + // Maximum size of the QUIC packet which can be written. + net::QuicByteCount GetMaxPacketSize( + const net::IPEndPoint& peer_address) const override { + return net::kMaxPacketSize; + } + // This method is not used -- call set_writable(bool writable) instead. + // TODO(miekscarlett): Remove this method once QuicPacketWriter does not + // require defining it. + void SetWritable() override {} + + // QuicCryptoClientStream::ProofHandler overrides. + // Called by client crypto handshake when cached proof is marked valid. + void OnProofValid( + const net::QuicCryptoClientConfig::CachedState& cached) override; + // Called by the client crypto handshake when proof verification details + // become available, either because proof verification is complete, or when + // cached details are used. + void OnProofVerifyDetailsAvailable( + const net::ProofVerifyDetails& verify_details) override; + + // Returns true if |quic_| has queued data which wasn't written due + // to |channel_| being write blocked. + bool HasDataToWrite() const; + // Writes queued data for |quic_| when |channel_| is no longer write blocked. + void OnCanWrite(); + // Connectivity state of QuicTransportChannel. + QuicTransportState quic_state() const { return quic_state_; } + + private: + // Fingerprint of remote peer. + struct RemoteFingerprint { + std::string value; + std::string algorithm; + }; + + // Callbacks for |channel_|. + void OnReadableState(TransportChannel* channel); + void OnWritableState(TransportChannel* channel); + void OnReadPacket(TransportChannel* channel, + const char* data, + size_t size, + const rtc::PacketTime& packet_time, + int flags); + void OnSentPacket(TransportChannel* channel, + const rtc::SentPacket& sent_packet); + void OnReadyToSend(TransportChannel* channel); + void OnReceivingState(TransportChannel* channel); + void OnGatheringState(TransportChannelImpl* channel); + void OnCandidateGathered(TransportChannelImpl* channel, const Candidate& c); + void OnRoleConflict(TransportChannelImpl* channel); + void OnRouteChange(TransportChannel* channel, const Candidate& candidate); + void OnConnectionRemoved(TransportChannelImpl* channel); + + // Callbacks for |quic_|. + // Called when |quic_| has established the crypto handshake. + void OnHandshakeComplete(); + // Called when |quic_| has closed the connection. + void OnConnectionClosed(net::QuicErrorCode error, bool from_peer); + + // Called by OnReadPacket() when a QUIC packet is received. + bool HandleQuicPacket(const char* data, size_t size); + // Sets up the QUIC handshake. + bool MaybeStartQuic(); + // Creates the QUIC connection and |quic_|. + bool CreateQuicSession(); + // Creates the crypto stream and initializes the handshake. + bool StartQuicHandshake(); + // Sets the QuicTransportChannel connectivity state. + void set_quic_state(QuicTransportState state); + + // Everything should occur on this thread. + rtc::Thread* worker_thread_; + // Underlying channel which is responsible for connecting with the remote peer + // and sending/receiving packets across the network. + TransportChannelImpl* const channel_; + // Connectivity state of QuicTransportChannel. + QuicTransportState quic_state_ = QUIC_TRANSPORT_NEW; + // QUIC session which establishes the crypto handshake and converts data + // to/from QUIC packets. + rtc::scoped_ptr quic_; + // Non-crypto config for |quic_|. + net::QuicConfig config_; + // Helper for net::QuicConnection that provides timing and + // random number generation. + QuicConnectionHelper helper_; + // This peer's role in the QUIC crypto handshake. SSL_CLIENT implies this peer + // initiates the handshake, while SSL_SERVER implies the remote peer initiates + // the handshake. This must be set before we start QUIC. + rtc::Optional ssl_role_; + // Config for QUIC crypto client stream, used when |ssl_role_| is SSL_CLIENT. + rtc::scoped_ptr quic_crypto_client_config_; + // Config for QUIC crypto server stream, used when |ssl_role_| is SSL_SERVER. + rtc::scoped_ptr quic_crypto_server_config_; + // This peer's certificate. + rtc::scoped_refptr local_certificate_; + // Fingerprint of the remote peer. This must be set before we start QUIC. + rtc::Optional remote_fingerprint_; + + RTC_DISALLOW_COPY_AND_ASSIGN(QuicTransportChannel); +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_QUIC_QUICTRANSPORTCHANNEL_H_ diff --git a/webrtc/p2p/quic/quictransportchannel_unittest.cc b/webrtc/p2p/quic/quictransportchannel_unittest.cc new file mode 100644 index 0000000000..c5c4d2012e --- /dev/null +++ b/webrtc/p2p/quic/quictransportchannel_unittest.cc @@ -0,0 +1,488 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/quic/quictransportchannel.h" + +#include +#include +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sslidentity.h" +#include "webrtc/p2p/base/faketransportcontroller.h" + +using cricket::ConnectionRole; +using cricket::IceRole; +using cricket::QuicTransportChannel; +using cricket::TransportChannel; +using cricket::TransportDescription; + +// Timeout in milliseconds for asynchronous operations in unit tests. +static const int kTimeoutMs = 1000; + +// Export keying material parameters. +static const char kExporterLabel[] = "label"; +static const uint8_t kExporterContext[] = "context"; +static const size_t kExporterContextLength = sizeof(kExporterContext); +static const size_t kOutputKeyLength = 20; + +// Packet size for SRTP. +static const size_t kPacketSize = 100; + +// Indicates ICE channel has no write error. +static const int kNoWriteError = 0; + +// ICE parameters. +static const char kIceUfrag[] = "TESTICEUFRAG0001"; +static const char kIcePwd[] = "TESTICEPWD00000000000001"; + +// QUIC packet parameters. +static const net::IPAddressNumber kIpAddress(net::kIPv4AddressSize, 0); +static const net::IPEndPoint kIpEndpoint(kIpAddress, 0); + +// Detects incoming RTP packets. +static bool IsRtpLeadByte(uint8_t b) { + return (b & 0xC0) == 0x80; +} + +// Maps SSL role to ICE connection role. The peer with a client role is assumed +// to be the one who initiates the connection. +static ConnectionRole SslRoleToConnectionRole(rtc::SSLRole ssl_role) { + return (ssl_role == rtc::SSL_CLIENT) ? cricket::CONNECTIONROLE_ACTIVE + : cricket::CONNECTIONROLE_PASSIVE; +} + +// Allows cricket::FakeTransportChannel to simulate write blocked +// and write error states. +// TODO(mikescarlett): Add this functionality to cricket::FakeTransportChannel. +class FailableTransportChannel : public cricket::FakeTransportChannel { + public: + FailableTransportChannel(const std::string& name, int component) + : cricket::FakeTransportChannel(name, component), error_(kNoWriteError) {} + int GetError() override { return error_; } + void SetError(int error) { error_ = error; } + int SendPacket(const char* data, + size_t len, + const rtc::PacketOptions& options, + int flags) override { + if (error_ == kNoWriteError) { + return cricket::FakeTransportChannel::SendPacket(data, len, options, + flags); + } + return -1; + } + + private: + int error_; +}; + +// Peer who establishes a handshake using a QuicTransportChannel, which wraps +// a FailableTransportChannel to simulate network connectivity and ICE +// negotiation. +class QuicTestPeer : public sigslot::has_slots<> { + public: + explicit QuicTestPeer(const std::string& name) + : name_(name), + bytes_sent_(0), + ice_channel_(name_, 0), + quic_channel_(&ice_channel_) { + quic_channel_.SignalReadPacket.connect( + this, &QuicTestPeer::OnTransportChannelReadPacket); + ice_channel_.SetAsync(true); + rtc::scoped_refptr local_cert = + rtc::RTCCertificate::Create(rtc::scoped_ptr( + rtc::SSLIdentity::Generate(name_, rtc::KT_DEFAULT))); + quic_channel_.SetLocalCertificate(local_cert); + local_fingerprint_.reset(CreateFingerprint(local_cert.get())); + } + + // Connects |ice_channel_| to that of the other peer. + void Connect(QuicTestPeer* other_peer) { + ice_channel_.Connect(); + other_peer->ice_channel_.Connect(); + ice_channel_.SetDestination(&other_peer->ice_channel_); + } + + // Disconnects |ice_channel_|. + void Disconnect() { ice_channel_.SetDestination(nullptr); } + + // Generates ICE credentials and passes them to |quic_channel_|. + void SetIceParameters(IceRole local_ice_role, + ConnectionRole local_connection_role, + ConnectionRole remote_connection_role, + rtc::SSLFingerprint* remote_fingerprint) { + quic_channel_.SetIceRole(local_ice_role); + quic_channel_.SetIceTiebreaker( + (local_ice_role == cricket::ICEROLE_CONTROLLING) ? 1 : 2); + + TransportDescription local_desc( + std::vector(), kIceUfrag, kIcePwd, cricket::ICEMODE_FULL, + local_connection_role, local_fingerprint_.get()); + TransportDescription remote_desc( + std::vector(), kIceUfrag, kIcePwd, cricket::ICEMODE_FULL, + remote_connection_role, remote_fingerprint); + + quic_channel_.SetIceCredentials(local_desc.ice_ufrag, local_desc.ice_pwd); + quic_channel_.SetRemoteIceCredentials(remote_desc.ice_ufrag, + remote_desc.ice_pwd); + } + + // Creates fingerprint from certificate. + rtc::SSLFingerprint* CreateFingerprint(rtc::RTCCertificate* cert) { + std::string digest_algorithm; + bool get_digest_algorithm = + cert->ssl_certificate().GetSignatureDigestAlgorithm(&digest_algorithm); + if (!get_digest_algorithm || digest_algorithm.empty()) { + return nullptr; + } + scoped_ptr fingerprint( + rtc::SSLFingerprint::Create(digest_algorithm, cert->identity())); + if (digest_algorithm != rtc::DIGEST_SHA_256) { + return nullptr; + } + return fingerprint.release(); + } + + // Sends SRTP packet to the other peer via |quic_channel_|. + int SendSrtpPacket() { + char packet[kPacketSize]; + packet[0] = 0x80; // Make the packet header look like RTP. + int rv = quic_channel_.SendPacket( + &packet[0], kPacketSize, rtc::PacketOptions(), cricket::PF_SRTP_BYPASS); + bytes_sent_ += rv; + return rv; + } + + // Sends a non-SRTP packet with the PF_SRTP_BYPASS flag via |quic_channel_|. + int SendInvalidSrtpPacket() { + char packet[kPacketSize]; + // Fill the packet with 0 to form an invalid SRTP packet. + memset(packet, 0, kPacketSize); + return quic_channel_.SendPacket( + &packet[0], kPacketSize, rtc::PacketOptions(), cricket::PF_SRTP_BYPASS); + } + + // Sends an RTP packet to the other peer via |quic_channel_|, without the SRTP + // bypass flag. + int SendRtpPacket() { + char packet[kPacketSize]; + packet[0] = 0x80; // Make the packet header look like RTP. + return quic_channel_.SendPacket(&packet[0], kPacketSize, + rtc::PacketOptions(), 0); + } + + void ClearBytesSent() { bytes_sent_ = 0; } + + void ClearBytesReceived() { bytes_received_ = 0; } + + void SetWriteError(int error) { ice_channel_.SetError(error); } + + size_t bytes_received() const { return bytes_received_; } + + size_t bytes_sent() const { return bytes_sent_; } + + FailableTransportChannel* ice_channel() { return &ice_channel_; } + + QuicTransportChannel* quic_channel() { return &quic_channel_; } + + rtc::scoped_ptr& local_fingerprint() { + return local_fingerprint_; + } + + private: + // QUIC channel callback. + void OnTransportChannelReadPacket(TransportChannel* channel, + const char* data, + size_t size, + const rtc::PacketTime& packet_time, + int flags) { + bytes_received_ += size; + // Only SRTP packets should have the bypass flag set. + int expected_flags = IsRtpLeadByte(data[0]) ? cricket::PF_SRTP_BYPASS : 0; + ASSERT_EQ(expected_flags, flags); + } + + std::string name_; // Channel name. + size_t bytes_sent_; // Bytes sent by QUIC channel. + size_t bytes_received_; // Bytes received by QUIC channel. + FailableTransportChannel ice_channel_; // Simulates an ICE channel. + QuicTransportChannel quic_channel_; // QUIC channel to test. + rtc::scoped_ptr local_fingerprint_; +}; + +class QuicTransportChannelTest : public testing::Test { + public: + QuicTransportChannelTest() : peer1_("P1"), peer2_("P2") {} + + // Performs negotiation before QUIC handshake, then connects the fake + // transport channels of each peer. As a side effect, the QUIC channels + // start sending handshake messages. |peer1_| has a client role and |peer2_| + // has server role in the QUIC handshake. + void Connect() { + SetIceAndCryptoParameters(rtc::SSL_CLIENT, rtc::SSL_SERVER); + peer1_.Connect(&peer2_); + } + + // Disconnects the fake transport channels. + void Disconnect() { + peer1_.Disconnect(); + peer2_.Disconnect(); + } + + // Sets up ICE parameters and exchanges fingerprints before QUIC handshake. + void SetIceAndCryptoParameters(rtc::SSLRole peer1_ssl_role, + rtc::SSLRole peer2_ssl_role) { + peer1_.quic_channel()->SetSslRole(peer1_ssl_role); + peer2_.quic_channel()->SetSslRole(peer2_ssl_role); + + rtc::scoped_ptr& peer1_fingerprint = + peer1_.local_fingerprint(); + rtc::scoped_ptr& peer2_fingerprint = + peer2_.local_fingerprint(); + + peer1_.quic_channel()->SetRemoteFingerprint( + peer2_fingerprint->algorithm, + reinterpret_cast(peer2_fingerprint->digest.data()), + peer2_fingerprint->digest.size()); + peer2_.quic_channel()->SetRemoteFingerprint( + peer1_fingerprint->algorithm, + reinterpret_cast(peer1_fingerprint->digest.data()), + peer1_fingerprint->digest.size()); + + ConnectionRole peer1_connection_role = + SslRoleToConnectionRole(peer1_ssl_role); + ConnectionRole peer2_connection_role = + SslRoleToConnectionRole(peer2_ssl_role); + + peer1_.SetIceParameters(cricket::ICEROLE_CONTROLLED, peer1_connection_role, + peer2_connection_role, peer2_fingerprint.get()); + peer2_.SetIceParameters(cricket::ICEROLE_CONTROLLING, peer2_connection_role, + peer1_connection_role, peer1_fingerprint.get()); + } + + // Checks if QUIC handshake is done. + bool quic_connected() { + return peer1_.quic_channel()->quic_state() == + cricket::QUIC_TRANSPORT_CONNECTED && + peer2_.quic_channel()->quic_state() == + cricket::QUIC_TRANSPORT_CONNECTED; + } + + // Checks if QUIC channels are writable. + bool quic_writable() { + return peer1_.quic_channel()->writable() && + peer2_.quic_channel()->writable(); + } + + protected: + // QUIC peer with a client role, who initiates the QUIC handshake. + QuicTestPeer peer1_; + // QUIC peer with a server role, who responds to the client peer. + QuicTestPeer peer2_; +}; + +// Test that the QUIC channel passes ICE parameters to the underlying ICE +// channel. +TEST_F(QuicTransportChannelTest, ChannelSetupIce) { + SetIceAndCryptoParameters(rtc::SSL_CLIENT, rtc::SSL_SERVER); + FailableTransportChannel* channel1 = peer1_.ice_channel(); + FailableTransportChannel* channel2 = peer2_.ice_channel(); + EXPECT_EQ(cricket::ICEROLE_CONTROLLED, channel1->GetIceRole()); + EXPECT_EQ(2u, channel1->IceTiebreaker()); + EXPECT_EQ(kIceUfrag, channel1->ice_ufrag()); + EXPECT_EQ(kIcePwd, channel1->ice_pwd()); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel2->GetIceRole()); + EXPECT_EQ(1u, channel2->IceTiebreaker()); +} + +// Test that export keying material generates identical keys for both peers +// after the QUIC handshake. +TEST_F(QuicTransportChannelTest, ExportKeyingMaterial) { + Connect(); + ASSERT_TRUE_WAIT(quic_connected(), kTimeoutMs); + uint8_t key1[kOutputKeyLength]; + uint8_t key2[kOutputKeyLength]; + + bool from_success = peer1_.quic_channel()->ExportKeyingMaterial( + kExporterLabel, kExporterContext, kExporterContextLength, true, key1, + kOutputKeyLength); + ASSERT_TRUE(from_success); + bool to_success = peer2_.quic_channel()->ExportKeyingMaterial( + kExporterLabel, kExporterContext, kExporterContextLength, true, key2, + kOutputKeyLength); + ASSERT_TRUE(to_success); + + EXPECT_EQ(0, memcmp(key1, key2, sizeof(key1))); +} + +// Test that the QUIC channel is not writable before the QUIC handshake. +TEST_F(QuicTransportChannelTest, NotWritableBeforeHandshake) { + Connect(); + EXPECT_FALSE(quic_writable()); + Disconnect(); + EXPECT_FALSE(quic_writable()); + Connect(); + EXPECT_FALSE(quic_writable()); +} + +// Test that once handshake begins, QUIC is not writable until its completion. +TEST_F(QuicTransportChannelTest, QuicHandshake) { + Connect(); + EXPECT_FALSE(quic_writable()); + ASSERT_TRUE_WAIT(quic_connected(), kTimeoutMs); + EXPECT_TRUE(quic_writable()); +} + +// Test that Non-SRTP data is not sent using SendPacket(), regardless of QUIC +// channel state. +TEST_F(QuicTransportChannelTest, TransferNonSrtp) { + // Send data before ICE channel is connected. + peer1_.ClearBytesSent(); + peer2_.ClearBytesReceived(); + ASSERT_EQ(-1, peer1_.SendRtpPacket()); + EXPECT_EQ(0u, peer1_.bytes_sent()); + // Send data after ICE channel is connected, before QUIC handshake. + Connect(); + peer1_.ClearBytesSent(); + peer2_.ClearBytesReceived(); + ASSERT_EQ(-1, peer1_.SendRtpPacket()); + EXPECT_EQ(0u, peer1_.bytes_sent()); + // Send data after QUIC handshake. + ASSERT_TRUE_WAIT(quic_connected(), kTimeoutMs); + peer1_.ClearBytesSent(); + peer2_.ClearBytesReceived(); + ASSERT_EQ(-1, peer1_.SendRtpPacket()); + EXPECT_EQ(0u, peer1_.bytes_sent()); +} + +// Test that SRTP data is always be sent, regardless of QUIC channel state, when +// the ICE channel is connected. +TEST_F(QuicTransportChannelTest, TransferSrtp) { + // Send data after ICE channel is connected, before QUIC handshake. + Connect(); + peer1_.ClearBytesSent(); + peer2_.ClearBytesReceived(); + ASSERT_EQ(kPacketSize, static_cast(peer1_.SendSrtpPacket())); + EXPECT_EQ_WAIT(kPacketSize, peer2_.bytes_received(), kTimeoutMs); + EXPECT_EQ(kPacketSize, peer1_.bytes_sent()); + ASSERT_TRUE_WAIT(quic_connected(), kTimeoutMs); + // Send data after QUIC handshake. + peer1_.ClearBytesSent(); + peer2_.ClearBytesReceived(); + ASSERT_EQ(kPacketSize, static_cast(peer1_.SendSrtpPacket())); + EXPECT_EQ_WAIT(kPacketSize, peer2_.bytes_received(), kTimeoutMs); + EXPECT_EQ(kPacketSize, peer1_.bytes_sent()); +} + +// Test that invalid SRTP (non-SRTP data with +// PF_SRTP_BYPASS flag) fails to send with return value -1. +TEST_F(QuicTransportChannelTest, TransferInvalidSrtp) { + peer1_.ClearBytesSent(); + peer2_.ClearBytesReceived(); + EXPECT_EQ(-1, peer1_.SendInvalidSrtpPacket()); + EXPECT_EQ(0u, peer2_.bytes_received()); + Connect(); + peer1_.ClearBytesSent(); + peer2_.ClearBytesReceived(); + EXPECT_EQ(-1, peer1_.SendInvalidSrtpPacket()); + EXPECT_EQ(0u, peer2_.bytes_received()); +} + +// Test that QuicTransportChannel::WritePacket blocks when the ICE +// channel is not writable, and otherwise succeeds. +TEST_F(QuicTransportChannelTest, QuicWritePacket) { + peer1_.ice_channel()->Connect(); + peer2_.ice_channel()->Connect(); + peer1_.ice_channel()->SetDestination(peer2_.ice_channel()); + std::string packet = "FAKEQUICPACKET"; + + // QUIC should be write blocked when the ICE channel is not writable. + peer1_.ice_channel()->SetWritable(false); + EXPECT_TRUE(peer1_.quic_channel()->IsWriteBlocked()); + net::WriteResult write_blocked_result = peer1_.quic_channel()->WritePacket( + packet.data(), packet.size(), kIpAddress, kIpEndpoint); + EXPECT_EQ(net::WRITE_STATUS_BLOCKED, write_blocked_result.status); + EXPECT_EQ(EWOULDBLOCK, write_blocked_result.error_code); + + // QUIC should ignore errors when the ICE channel is writable. + peer1_.ice_channel()->SetWritable(true); + EXPECT_FALSE(peer1_.quic_channel()->IsWriteBlocked()); + peer1_.SetWriteError(EWOULDBLOCK); + net::WriteResult ignore_error_result = peer1_.quic_channel()->WritePacket( + packet.data(), packet.size(), kIpAddress, kIpEndpoint); + EXPECT_EQ(net::WRITE_STATUS_OK, ignore_error_result.status); + EXPECT_EQ(0, ignore_error_result.bytes_written); + + peer1_.SetWriteError(kNoWriteError); + net::WriteResult no_error_result = peer1_.quic_channel()->WritePacket( + packet.data(), packet.size(), kIpAddress, kIpEndpoint); + EXPECT_EQ(net::WRITE_STATUS_OK, no_error_result.status); + EXPECT_EQ(static_cast(packet.size()), no_error_result.bytes_written); +} + +// Test that SSL roles can be reversed before QUIC handshake. +TEST_F(QuicTransportChannelTest, QuicRoleReversalBeforeQuic) { + EXPECT_TRUE(peer1_.quic_channel()->SetSslRole(rtc::SSL_SERVER)); + EXPECT_TRUE(peer1_.quic_channel()->SetSslRole(rtc::SSL_CLIENT)); + EXPECT_TRUE(peer1_.quic_channel()->SetSslRole(rtc::SSL_SERVER)); +} + +// Test that SSL roles cannot be reversed after the QUIC handshake. SetSslRole +// returns true if the current SSL role equals the proposed SSL role. +TEST_F(QuicTransportChannelTest, QuicRoleReversalAfterQuic) { + Connect(); + ASSERT_TRUE_WAIT(quic_connected(), kTimeoutMs); + EXPECT_FALSE(peer1_.quic_channel()->SetSslRole(rtc::SSL_SERVER)); + EXPECT_TRUE(peer1_.quic_channel()->SetSslRole(rtc::SSL_CLIENT)); + EXPECT_FALSE(peer2_.quic_channel()->SetSslRole(rtc::SSL_CLIENT)); + EXPECT_TRUE(peer2_.quic_channel()->SetSslRole(rtc::SSL_SERVER)); +} + +// Set the SSL role, then test that GetSslRole returns the same value. +TEST_F(QuicTransportChannelTest, SetGetSslRole) { + ASSERT_TRUE(peer1_.quic_channel()->SetSslRole(rtc::SSL_SERVER)); + rtc::scoped_ptr role(new rtc::SSLRole()); + ASSERT_TRUE(peer1_.quic_channel()->GetSslRole(role.get())); + EXPECT_EQ(rtc::SSL_SERVER, *role); +} + +// Test that after the QUIC handshake is complete, the QUIC handshake remains +// confirmed even if the ICE channel reconnects. +TEST_F(QuicTransportChannelTest, HandshakeConfirmedAfterReconnect) { + Connect(); + ASSERT_TRUE_WAIT(quic_connected(), kTimeoutMs); + Disconnect(); + EXPECT_TRUE(quic_connected()); + Connect(); + EXPECT_TRUE(quic_connected()); +} + +// Test that if the ICE channel becomes receiving after the QUIC channel is +// connected, then the QUIC channel becomes receiving. +TEST_F(QuicTransportChannelTest, IceReceivingAfterConnected) { + Connect(); + ASSERT_TRUE_WAIT(quic_connected(), kTimeoutMs); + ASSERT_FALSE(peer1_.ice_channel()->receiving()); + EXPECT_FALSE(peer1_.quic_channel()->receiving()); + peer1_.ice_channel()->SetReceiving(true); + EXPECT_TRUE(peer1_.quic_channel()->receiving()); +} + +// Test that if the ICE channel becomes receiving before the QUIC channel is +// connected, then the QUIC channel becomes receiving. +TEST_F(QuicTransportChannelTest, IceReceivingBeforeConnected) { + Connect(); + peer1_.ice_channel()->SetReceiving(true); + ASSERT_TRUE(peer1_.ice_channel()->receiving()); + ASSERT_TRUE_WAIT(quic_connected(), kTimeoutMs); + EXPECT_TRUE(peer1_.quic_channel()->receiving()); +}