webrtc_m130/webrtc/p2p/quic/quictransportchannel.cc
zhihuang f2c2f8f20c Refactoring on QUIC related classes.
Merge with the latest webrtc native code.
Remove deprecated function Connect() in QuicTransportChannel.
Fix the compiling issue and broken unit tests by adding the network thread to QUIC related classes.

Review-Url: https://codereview.webrtc.org/2089553002
Cr-Commit-Position: refs/heads/master@{#13472}
2016-07-13 21:13:56 +00:00

599 lines
23 KiB
C++

/*
* 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 <utility>
#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_packet_writer.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::IPAddress kConnectionIpAddress(0, 0, 0, 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<const uint8_t*>(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<const uint8_t*>(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::IPAddress& server_ip,
const std::string& hostname,
const std::string& server_config,
net::QuicVersion quic_version,
base::StringPiece chlo_hash,
bool ecdsa_ok,
scoped_refptr<net::ProofSource::Chain>* out_chain,
std::string* out_signature,
std::string* out_leaf_cert_sct) override {
LOG(LS_INFO) << "GetProof() providing dummy credentials for insecure QUIC";
std::vector<std::string> certs;
certs.push_back("Dummy cert");
*out_chain = new ProofSource::Chain(certs);
*out_signature = "Dummy signature";
*out_leaf_cert_sct = "Dummy timestamp";
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 uint16_t port,
const std::string& server_config,
net::QuicVersion quic_version,
base::StringPiece chlo_hash,
const std::vector<std::string>& certs,
const std::string& cert_sct,
const std::string& signature,
const net::ProofVerifyContext* context,
std::string* error_details,
std::unique_ptr<net::ProofVerifyDetails>* verify_details,
net::ProofVerifierCallback* callback) override {
LOG(LS_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_->SignalSelectedCandidatePairChanged.connect(
this, &QuicTransportChannel::OnSelectedCandidatePairChanged);
channel_->SignalStateChanged.connect(
this, &QuicTransportChannel::OnChannelStateChanged);
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<rtc::RTCCertificate>& certificate) {
if (!certificate) {
LOG_J(LS_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(LS_INFO, this) << "Ignoring identical certificate";
return true;
}
LOG_J(LS_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<rtc::RTCCertificate>
QuicTransportChannel::GetLocalCertificate() const {
return local_certificate_;
}
bool QuicTransportChannel::SetSslRole(rtc::SSLRole role) {
if (ssl_role_ && *ssl_role_ == role) {
LOG_J(LS_WARNING, this) << "Ignoring SSL Role identical to current role.";
return true;
}
if (quic_state_ != QUIC_TRANSPORT_CONNECTED) {
ssl_role_ = rtc::Optional<rtc::SSLRole>(role);
return true;
}
LOG_J(LS_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(LS_ERROR, this) << "Remote peer doesn't support digest algorithm.";
return false;
}
std::string remote_fingerprint_value(reinterpret_cast<const char*>(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(LS_INFO, this)
<< "Ignoring identical remote fingerprint and algorithm";
return true;
}
remote_fingerprint_ = rtc::Optional<RemoteFingerprint>(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<const char*>(context), context_len);
std::string quic_result;
if (!quic_->ExportKeyingMaterial(label, quic_context, result_len,
&quic_result)) {
return false;
}
quic_result.copy(reinterpret_cast<char*>(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(LS_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_.get());
LOG_J(LS_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_.get());
LOG_J(LS_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_.get());
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(LS_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(LS_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(LS_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_.get());
SignalGatheringState(this);
}
void QuicTransportChannel::OnCandidateGathered(TransportChannelImpl* channel,
const Candidate& c) {
ASSERT(channel == channel_.get());
SignalCandidateGathered(this, c);
}
void QuicTransportChannel::OnRoleConflict(TransportChannelImpl* channel) {
ASSERT(channel == channel_.get());
SignalRoleConflict(this);
}
void QuicTransportChannel::OnRouteChange(TransportChannel* channel,
const Candidate& candidate) {
ASSERT(channel == channel_.get());
SignalRouteChange(this, candidate);
}
void QuicTransportChannel::OnSelectedCandidatePairChanged(
TransportChannel* channel,
CandidatePairInterface* selected_candidate_pair,
int last_sent_packet_id,
bool ready_to_send) {
ASSERT(channel == channel_.get());
SignalSelectedCandidatePairChanged(this, selected_candidate_pair,
last_sent_packet_id, ready_to_send);
}
void QuicTransportChannel::OnChannelStateChanged(
TransportChannelImpl* channel) {
ASSERT(channel == channel_.get());
SignalStateChanged(this);
}
bool QuicTransportChannel::MaybeStartQuic() {
if (!channel_->writable()) {
LOG_J(LS_ERROR, this) << "Couldn't start QUIC handshake.";
return false;
}
if (!CreateQuicSession() || !StartQuicHandshake()) {
LOG_J(LS_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(LS_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;
std::unique_ptr<net::QuicConnection> 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);
quic_->SignalIncomingStream.connect(this,
&QuicTransportChannel::OnIncomingStream);
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(LS_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(LS_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);
quic_compressed_certs_cache_.reset(new net::QuicCompressedCertsCache(
net::QuicCompressedCertsCache::kQuicCompressedCertsCacheSize));
// TODO(mikescarlett): Add support for stateless rejects.
bool use_stateless_rejects_if_peer_supported = false;
net::QuicCryptoServerStream* crypto_stream =
new net::QuicCryptoServerStream(quic_crypto_server_config_.get(),
quic_compressed_certs_cache_.get(),
use_stateless_rejects_if_peer_supported,
quic_.get());
quic_->StartServerHandshake(crypto_stream);
LOG_J(LS_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::IPAddress& self_address,
const net::IPEndPoint& peer_address,
net::PerPacketOptions* options) {
// 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(LS_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);
SignalClosed();
}
void QuicTransportChannel::OnProofValid(
const net::QuicCryptoClientConfig::CachedState& cached) {
LOG_J(LS_INFO, this) << "Cached proof marked valid";
}
void QuicTransportChannel::OnProofVerifyDetailsAvailable(
const net::ProofVerifyDetails& verify_details) {
LOG_J(LS_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(LS_VERBOSE, this) << "set_quic_state from:" << quic_state_ << " to "
<< state;
quic_state_ = state;
}
ReliableQuicStream* QuicTransportChannel::CreateQuicStream() {
if (quic_) {
net::SpdyPriority priority = 0; // Priority of the QUIC stream
return quic_->CreateOutgoingDynamicStream(priority);
}
return nullptr;
}
void QuicTransportChannel::OnIncomingStream(ReliableQuicStream* stream) {
SignalIncomingStream(stream);
}
} // namespace cricket