From cd0e4751b2fdfd646f12412543ed318d4f03bee5 Mon Sep 17 00:00:00 2001 From: mikescarlett Date: Mon, 8 Feb 2016 17:35:47 -0800 Subject: [PATCH] Create QuicSession This CL depends on the unofficial libquic (https://github.com/devsisters/libquic), with subtle modifications. BUG= Review URL: https://codereview.webrtc.org/1648763002 Cr-Commit-Position: refs/heads/master@{#11530} --- webrtc/build/common.gypi | 3 + webrtc/p2p/p2p.gyp | 16 + webrtc/p2p/p2p_tests.gypi | 9 + webrtc/p2p/quic/quicconnectionhelper.cc | 74 +++ webrtc/p2p/quic/quicconnectionhelper.h | 66 +++ .../p2p/quic/quicconnectionhelper_unittest.cc | 123 +++++ webrtc/p2p/quic/quicsession.cc | 103 ++++ webrtc/p2p/quic/quicsession.h | 94 ++++ webrtc/p2p/quic/quicsession_unittest.cc | 462 ++++++++++++++++++ webrtc/p2p/quic/reliablequicstream.cc | 51 ++ webrtc/p2p/quic/reliablequicstream.h | 50 ++ .../p2p/quic/reliablequicstream_unittest.cc | 255 ++++++++++ 12 files changed, 1306 insertions(+) create mode 100644 webrtc/p2p/quic/quicconnectionhelper.cc create mode 100644 webrtc/p2p/quic/quicconnectionhelper.h create mode 100644 webrtc/p2p/quic/quicconnectionhelper_unittest.cc create mode 100644 webrtc/p2p/quic/quicsession.cc create mode 100644 webrtc/p2p/quic/quicsession.h create mode 100644 webrtc/p2p/quic/quicsession_unittest.cc create mode 100644 webrtc/p2p/quic/reliablequicstream.cc create mode 100644 webrtc/p2p/quic/reliablequicstream.h create mode 100644 webrtc/p2p/quic/reliablequicstream_unittest.cc diff --git a/webrtc/build/common.gypi b/webrtc/build/common.gypi index 738333d660..2d8127128f 100644 --- a/webrtc/build/common.gypi +++ b/webrtc/build/common.gypi @@ -138,6 +138,9 @@ # Enabling this may break interop with Android clients that support H264. 'use_objc_h264%': 0, + # Determines whether QUIC code will be built. + 'use_quic%': 0, + 'conditions': [ # Enable this to build OpenH264 encoder/FFmpeg decoder. This is supported # on all platforms except Android and iOS. Because FFmpeg can be built diff --git a/webrtc/p2p/p2p.gyp b/webrtc/p2p/p2p.gyp index 490cfbf087..10a439b4bd 100644 --- a/webrtc/p2p/p2p.gyp +++ b/webrtc/p2p/p2p.gyp @@ -98,6 +98,22 @@ 'FEATURE_ENABLE_PSTN', ], }], + ['use_quic==1', { + 'dependencies': [ + '<(DEPTH)/third_party/libquic/libquic.gyp:libquic', + ], + 'sources': [ + 'quic/quicconnectionhelper.cc', + 'quic/quicconnectionhelper.h', + 'quic/quicsession.cc', + 'quic/quicsession.h', + 'quic/reliablequicstream.cc', + 'quic/reliablequicstream.h', + ], + 'export_dependent_settings': [ + '<(DEPTH)/third_party/libquic/libquic.gyp:libquic', + ], + }], ], }, { diff --git a/webrtc/p2p/p2p_tests.gypi b/webrtc/p2p/p2p_tests.gypi index ba7f553bba..9354d33999 100644 --- a/webrtc/p2p/p2p_tests.gypi +++ b/webrtc/p2p/p2p_tests.gypi @@ -36,6 +36,15 @@ 'client/portallocator_unittest.cc', 'stunprober/stunprober_unittest.cc', ], + 'conditions': [ + ['use_quic==1', { + 'sources': [ + 'quic/quicconnectionhelper_unittest.cc', + 'quic/quicsession_unittest.cc', + 'quic/reliablequicstream_unittest.cc', + ], + }], + ], }, }, ], diff --git a/webrtc/p2p/quic/quicconnectionhelper.cc b/webrtc/p2p/quic/quicconnectionhelper.cc new file mode 100644 index 0000000000..af5b4b213b --- /dev/null +++ b/webrtc/p2p/quic/quicconnectionhelper.cc @@ -0,0 +1,74 @@ +/* + * 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/quicconnectionhelper.h" + +namespace cricket { + +QuicAlarm* QuicConnectionHelper::CreateAlarm( + net::QuicAlarm::Delegate* delegate) { + return new QuicAlarm(GetClock(), thread_, delegate); +} + +QuicAlarm::QuicAlarm(const net::QuicClock* clock, + rtc::Thread* thread, + QuicAlarm::Delegate* delegate) + : net::QuicAlarm(delegate), clock_(clock), thread_(thread) {} + +QuicAlarm::~QuicAlarm() {} + +void QuicAlarm::OnMessage(rtc::Message* msg) { + // The alarm may have been cancelled. + if (!deadline().IsInitialized()) { + return; + } + + // The alarm may have been re-set to a later time. + if (clock_->Now() < deadline()) { + SetImpl(); + return; + } + + Fire(); +} + +int64 QuicAlarm::GetDelay() const { + return deadline().Subtract(clock_->Now()).ToMilliseconds(); +} + +void QuicAlarm::SetImpl() { + DCHECK(deadline().IsInitialized()); + CancelImpl(); // Unregister if already posted. + + int64 delay_ms = GetDelay(); + if (delay_ms < 0) { + delay_ms = 0; + } + thread_->PostDelayed(delay_ms, this); +} + +void QuicAlarm::CancelImpl() { + thread_->Clear(this); +} + +QuicConnectionHelper::QuicConnectionHelper(rtc::Thread* thread) + : thread_(thread) {} + +QuicConnectionHelper::~QuicConnectionHelper() {} + +const net::QuicClock* QuicConnectionHelper::GetClock() const { + return &clock_; +} + +net::QuicRandom* QuicConnectionHelper::GetRandomGenerator() { + return net::QuicRandom::GetInstance(); +} + +} // namespace cricket diff --git a/webrtc/p2p/quic/quicconnectionhelper.h b/webrtc/p2p/quic/quicconnectionhelper.h new file mode 100644 index 0000000000..02691727e5 --- /dev/null +++ b/webrtc/p2p/quic/quicconnectionhelper.h @@ -0,0 +1,66 @@ +/* + * 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_QUICCONNECTIONHELPER_H_ +#define WEBRTC_P2P_QUIC_QUICCONNECTIONHELPER_H_ + +#include "net/quic/crypto/quic_random.h" +#include "net/quic/quic_alarm.h" +#include "net/quic/quic_clock.h" +#include "net/quic/quic_connection.h" +#include "webrtc/base/thread.h" + +namespace cricket { + +// An alarm which will go off at a scheduled time, and execute the |OnAlarm| +// method of the delegate. +class QuicAlarm : public net::QuicAlarm, public rtc::MessageHandler { + public: + QuicAlarm(const net::QuicClock* clock, + rtc::Thread* thread, + QuicAlarm::Delegate* delegate); + + ~QuicAlarm() override; + + // rtc::MessageHandler override. + void OnMessage(rtc::Message* msg) override; + + // Helper method to get the delay in ms for posting task. + int64 GetDelay() const; + + protected: + // net::QuicAlarm overrides. + void SetImpl() override; + void CancelImpl() override; + + private: + const net::QuicClock* clock_; + rtc::Thread* thread_; +}; + +// Helper methods for QuicConnection timing and random number generation. +class QuicConnectionHelper : public net::QuicConnectionHelperInterface { + public: + explicit QuicConnectionHelper(rtc::Thread* thread); + ~QuicConnectionHelper() override; + + // QuicConnectionHelperInterface overrides. + const net::QuicClock* GetClock() const override; + net::QuicRandom* GetRandomGenerator() override; + QuicAlarm* CreateAlarm(net::QuicAlarm::Delegate* delegate) override; + + private: + net::QuicClock clock_; + rtc::Thread* thread_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_QUIC_QUICCONNECTIONHELPER_H_ diff --git a/webrtc/p2p/quic/quicconnectionhelper_unittest.cc b/webrtc/p2p/quic/quicconnectionhelper_unittest.cc new file mode 100644 index 0000000000..3d2dee5b2f --- /dev/null +++ b/webrtc/p2p/quic/quicconnectionhelper_unittest.cc @@ -0,0 +1,123 @@ +/* + * 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/quicconnectionhelper.h" + +#include "net/quic/quic_time.h" +#include "webrtc/base/gunit.h" + +using cricket::QuicAlarm; +using cricket::QuicConnectionHelper; + +using net::QuicClock; +using net::QuicTime; +using net::QuicWallTime; + +// Clock that can be set to arbitrary times. +class MockClock : public QuicClock { + public: + MockClock() : now_(QuicTime::Zero()) {} + + void AdvanceTime(QuicTime::Delta delta) { now_ = now_.Add(delta); } + + QuicTime Now() const override { return now_; } + + QuicTime ApproximateNow() const override { return now_; } + + QuicWallTime WallNow() const override { + return QuicWallTime::FromUNIXSeconds( + now_.Subtract(QuicTime::Zero()).ToSeconds()); + } + + base::TimeTicks NowInTicks() const { + base::TimeTicks ticks; + return ticks + base::TimeDelta::FromMicroseconds( + now_.Subtract(QuicTime::Zero()).ToMicroseconds()); + } + + private: + QuicTime now_; +}; + +// Implements OnAlarm() event which alarm triggers. +class MockAlarmDelegate : public QuicAlarm::Delegate { + public: + MockAlarmDelegate() : fired_(false) {} + + QuicTime OnAlarm() override { + fired_ = true; + return QuicTime::Zero(); + } + + bool fired() const { return fired_; } + void Clear() { fired_ = false; } + + private: + bool fired_; +}; + +class QuicAlarmTest : public ::testing::Test { + public: + QuicAlarmTest() + : delegate_(new MockAlarmDelegate()), + alarm_(new QuicAlarm(&clock_, rtc::Thread::Current(), delegate_)) {} + + // Make the alarm fire after the given microseconds (us). Negative values + // imply the alarm should fire immediately. + void SetTime(int us) { + QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(us); + alarm_->Set(clock_.Now().Add(delta)); + } + + // Make rtc::Thread::Current() process the next message. + void ProcessNextMessage() { rtc::Thread::Current()->ProcessMessages(0); } + + protected: + // Handles event that alarm fires. + MockAlarmDelegate* delegate_; + // Used for setting clock time relative to alarm. + MockClock clock_; + + scoped_ptr alarm_; +}; + +// Test that the alarm is fired. +TEST_F(QuicAlarmTest, FireAlarm) { + SetTime(-1); + ProcessNextMessage(); + ASSERT_TRUE(delegate_->fired()); + ASSERT_EQ(QuicTime::Zero(), alarm_->deadline()); +} + +// Test cancellation of alarm when it is set to fire. +TEST_F(QuicAlarmTest, CancelAlarmAfterSet) { + // TODO(mikescarlett): Test will fail in the future if + // rtc::Thread::PostDelayed calls the delegate synchronously for times <= 0. + // Rewrite this when rtc::Thread is able to use a mock clock. + SetTime(-1); + alarm_->Cancel(); + ProcessNextMessage(); + ASSERT_FALSE(delegate_->fired()); +} + +// Test cancellation of alarm when it is not set to fire. +TEST_F(QuicAlarmTest, CancelAlarmBeforeSet) { + alarm_->Cancel(); + ProcessNextMessage(); + ASSERT_FALSE(delegate_->fired()); +} + +// Test that timing for posting task is accurate. +TEST_F(QuicAlarmTest, AlarmGetDelay) { + SetTime(1000000); + EXPECT_EQ(1000, alarm_->GetDelay()); + clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(300000)); + EXPECT_EQ(700, alarm_->GetDelay()); +} diff --git a/webrtc/p2p/quic/quicsession.cc b/webrtc/p2p/quic/quicsession.cc new file mode 100644 index 0000000000..4197528703 --- /dev/null +++ b/webrtc/p2p/quic/quicsession.cc @@ -0,0 +1,103 @@ +/* + * 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/quicsession.h" + +#include +#include + +#include "webrtc/base/checks.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/messagequeue.h" + +namespace cricket { + +QuicSession::QuicSession(scoped_ptr connection, + const net::QuicConfig& config) + : net::QuicSession(connection.release(), config) {} + +QuicSession::~QuicSession() {} + +void QuicSession::StartClientHandshake( + net::QuicCryptoClientStream* crypto_stream) { + SetCryptoStream(crypto_stream); + net::QuicSession::Initialize(); + crypto_stream->CryptoConnect(); +} + +void QuicSession::StartServerHandshake( + net::QuicCryptoServerStream* crypto_stream) { + SetCryptoStream(crypto_stream); + net::QuicSession::Initialize(); +} + +void QuicSession::SetCryptoStream(net::QuicCryptoStream* crypto_stream) { + crypto_stream_.reset(crypto_stream); +} + +bool QuicSession::ExportKeyingMaterial(base::StringPiece label, + base::StringPiece context, + size_t result_len, + string* result) { + return crypto_stream_->ExportKeyingMaterial(label, context, result_len, + result); +} + +void QuicSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) { + net::QuicSession::OnCryptoHandshakeEvent(event); + if (event == HANDSHAKE_CONFIRMED) { + LOG(INFO) << "QuicSession handshake complete"; + RTC_DCHECK(IsEncryptionEstablished()); + RTC_DCHECK(IsCryptoHandshakeConfirmed()); + + SignalHandshakeComplete(); + } +} + +ReliableQuicStream* QuicSession::CreateIncomingDynamicStream( + net::QuicStreamId id) { + ReliableQuicStream* stream = CreateDataStream(id); + if (stream) { + SignalIncomingStream(stream); + } + return stream; +} + +ReliableQuicStream* QuicSession::CreateOutgoingDynamicStream( + net::SpdyPriority priority) { + ReliableQuicStream* stream = CreateDataStream(GetNextOutgoingStreamId()); + if (stream) { + ActivateStream(stream); + } + return stream; +} + +ReliableQuicStream* QuicSession::CreateDataStream(net::QuicStreamId id) { + if (crypto_stream_ == nullptr || !crypto_stream_->encryption_established()) { + // Encryption not active so no stream created + return nullptr; + } + return new ReliableQuicStream(id, this); +} + +void QuicSession::OnConnectionClosed(net::QuicErrorCode error, bool from_peer) { + net::QuicSession::OnConnectionClosed(error, from_peer); + SignalConnectionClosed(error, from_peer); +} + +bool QuicSession::OnReadPacket(const char* data, size_t data_len) { + net::QuicEncryptedPacket packet(data, data_len); + connection()->ProcessUdpPacket(connection()->self_address(), + connection()->peer_address(), packet); + return true; +} + +} // namespace cricket diff --git a/webrtc/p2p/quic/quicsession.h b/webrtc/p2p/quic/quicsession.h new file mode 100644 index 0000000000..689d43709e --- /dev/null +++ b/webrtc/p2p/quic/quicsession.h @@ -0,0 +1,94 @@ +/* + * 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_QUICSESSION_H_ +#define WEBRTC_P2P_QUIC_QUICSESSION_H_ + +#include + +#include "net/quic/quic_crypto_client_stream.h" +#include "net/quic/quic_crypto_server_stream.h" +#include "net/quic/quic_crypto_stream.h" +#include "net/quic/quic_session.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/sslidentity.h" +#include "webrtc/p2p/quic/reliablequicstream.h" + +namespace cricket { + +// This class provides a QUIC session over peer-to-peer transport that +// negotiates the crypto handshake (using QuicCryptoHandshake) and provides +// reading/writing of data using QUIC packets. +class QuicSession : public net::QuicSession, public sigslot::has_slots<> { + public: + QuicSession(scoped_ptr connection, + const net::QuicConfig& config); + ~QuicSession() override; + + // Initiates client crypto handshake by sending client hello. + void StartClientHandshake(net::QuicCryptoClientStream* crypto_stream); + + // Responds to a client who has inititated the crypto handshake. + void StartServerHandshake(net::QuicCryptoServerStream* crypto_stream); + + // QuicSession overrides. + net::QuicCryptoStream* GetCryptoStream() override { + return crypto_stream_.get(); + } + // TODO(mikescarlett): Verify whether outgoing streams should be owned by + // caller. It appears these are deleted in the net::QuicSession destructor, + // but Chromium's documentation says this should not happen. + ReliableQuicStream* CreateOutgoingDynamicStream( + net::SpdyPriority priority) override; + + // QuicSession optional overrides. + void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override; + + // QuicConnectionVisitorInterface overrides. + void OnConnectionClosed(net::QuicErrorCode error, bool from_peer) override; + + // Exports keying material for SRTP. + bool ExportKeyingMaterial(base::StringPiece label, + base::StringPiece context, + size_t result_len, + string* result); + + // Decrypts an incoming QUIC packet to a data stream. + bool OnReadPacket(const char* data, size_t data_len); + + // Called when peers have established forward-secure encryption + sigslot::signal0<> SignalHandshakeComplete; + // Called when connection closes locally, or remotely by peer. + sigslot::signal2 SignalConnectionClosed; + // Called when an incoming QUIC stream is created so we can process data + // from it by registering a listener to + // ReliableQuicStream::SignalDataReceived. + sigslot::signal1 SignalIncomingStream; + + protected: + // Sets the QUIC crypto stream and takes ownership of it. + void SetCryptoStream(net::QuicCryptoStream* crypto_stream); + + // QuicSession override. + ReliableQuicStream* CreateIncomingDynamicStream( + net::QuicStreamId id) override; + + virtual ReliableQuicStream* CreateDataStream(net::QuicStreamId id); + + private: + scoped_ptr crypto_stream_; + + RTC_DISALLOW_COPY_AND_ASSIGN(QuicSession); +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_QUIC_QUICSESSION_H_ diff --git a/webrtc/p2p/quic/quicsession_unittest.cc b/webrtc/p2p/quic/quicsession_unittest.cc new file mode 100644 index 0000000000..6733796127 --- /dev/null +++ b/webrtc/p2p/quic/quicsession_unittest.cc @@ -0,0 +1,462 @@ +/* + * 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/quicsession.h" + +#include +#include + +#include "net/base/ip_endpoint.h" +#include "net/quic/crypto/crypto_server_config_protobuf.h" +#include "net/quic/crypto/quic_random.h" +#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_crypto_client_stream.h" +#include "net/quic/quic_crypto_server_stream.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/p2p/base/faketransportcontroller.h" +#include "webrtc/p2p/quic/quicconnectionhelper.h" +#include "webrtc/p2p/quic/reliablequicstream.h" + +using net::IPAddressNumber; +using net::IPEndPoint; +using net::Perspective; +using net::ProofVerifyContext; +using net::ProofVerifyDetails; +using net::QuicByteCount; +using net::QuicClock; +using net::QuicConfig; +using net::QuicConnection; +using net::QuicCryptoClientConfig; +using net::QuicCryptoServerConfig; +using net::QuicCryptoClientStream; +using net::QuicCryptoServerStream; +using net::QuicCryptoStream; +using net::QuicErrorCode; +using net::QuicPacketWriter; +using net::QuicRandom; +using net::QuicServerConfigProtobuf; +using net::QuicServerId; +using net::QuicStreamId; +using net::WriteResult; +using net::WriteStatus; + +using cricket::FakeTransportChannel; +using cricket::QuicConnectionHelper; +using cricket::QuicSession; +using cricket::ReliableQuicStream; +using cricket::TransportChannel; + +using rtc::Thread; + +// Timeout for running asynchronous operations within unit tests. +const int kTimeoutMs = 1000; +// Testing SpdyPriority value for creating outgoing ReliableQuicStream. +const uint8 kDefaultPriority = 3; +// TExport keying material function +const char kExporterLabel[] = "label"; +const char kExporterContext[] = "context"; +const size_t kExporterContextLen = sizeof(kExporterContext); +// Identifies QUIC server session +const QuicServerId kServerId("www.google.com", 443); + +// Used by QuicCryptoServerConfig to provide server credentials, returning a +// canned response equal to |success|. +class FakeProofSource : public net::ProofSource { + public: + explicit FakeProofSource(bool success) : success_(success) {} + + // 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 { + if (success_) { + std::vector* certs = new std::vector(); + certs->push_back("Required to establish handshake"); + std::string signature("Signature"); + + *out_certs = certs; + *out_signature = signature; + } + return success_; + } + + private: + // Whether or not obtaining proof source succeeds. + bool success_; +}; + +// Used by QuicCryptoClientConfig to verify server credentials, returning a +// canned response of QUIC_SUCCESS if |success| is true. +class FakeProofVerifier : public net::ProofVerifier { + public: + explicit FakeProofVerifier(bool success) : success_(success) {} + + // 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 { + return success_ ? net::QUIC_SUCCESS : net::QUIC_FAILURE; + } + + private: + // Whether or not proof verification succeeds. + bool success_; +}; + +// Writes QUIC packets to a fake transport channel that simulates a network. +class FakeQuicPacketWriter : public QuicPacketWriter { + public: + explicit FakeQuicPacketWriter(FakeTransportChannel* fake_channel) + : fake_channel_(fake_channel) {} + + // Sends packets across the network. + WriteResult WritePacket(const char* buffer, + size_t buf_len, + const IPAddressNumber& self_address, + const IPEndPoint& peer_address) override { + rtc::PacketOptions packet_options; + int rv = fake_channel_->SendPacket(buffer, buf_len, packet_options, 0); + net::WriteStatus status; + if (rv > 0) { + status = net::WRITE_STATUS_OK; + } else if (fake_channel_->GetError() == EWOULDBLOCK) { + status = net::WRITE_STATUS_BLOCKED; + } else { + status = net::WRITE_STATUS_ERROR; + } + return net::WriteResult(status, rv); + } + + // Returns true if the writer buffers and subsequently rewrites data + // when an attempt to write results in the underlying socket becoming + // write blocked. + bool IsWriteBlockedDataBuffered() const override { return true; } + + // Returns true if the network socket is not writable. + bool IsWriteBlocked() const override { return !fake_channel_->writable(); } + + // Records that the socket has become writable, for example when an EPOLLOUT + // is received or an asynchronous write completes. + void SetWritable() override { fake_channel_->SetWritable(true); } + + // Returns the maximum size of the packet which can be written using this + // writer for the supplied peer address. This size may actually exceed the + // size of a valid QUIC packet. + QuicByteCount GetMaxPacketSize( + const IPEndPoint& peer_address) const override { + return net::kMaxPacketSize; + } + + private: + FakeTransportChannel* fake_channel_; +}; + +// Creates a FakePacketWriter for a given QuicConnection instance. +class FakePacketWriterFactory : public QuicConnection::PacketWriterFactory { + public: + explicit FakePacketWriterFactory(FakeTransportChannel* channel) + : channel_(channel) {} + + QuicPacketWriter* Create(QuicConnection* connection) const override { + return new FakeQuicPacketWriter(channel_); + } + + private: + FakeTransportChannel* channel_; +}; + +// Wrapper for QuicSession and transport channel that stores incoming data. +class QuicSessionForTest : public QuicSession { + public: + QuicSessionForTest(scoped_ptr connection, + const net::QuicConfig& config, + scoped_ptr channel) + : QuicSession(std::move(connection), config), + channel_(std::move(channel)) { + channel_->SignalReadPacket.connect( + this, &QuicSessionForTest::OnChannelReadPacket); + } + + // Called when channel has packets to read. + void OnChannelReadPacket(TransportChannel* channel, + const char* data, + size_t size, + const rtc::PacketTime& packet_time, + int flags) { + OnReadPacket(data, size); + } + + // Called when peer receives incoming stream from another peer. + void OnIncomingStream(ReliableQuicStream* stream) { + stream->SignalDataReceived.connect(this, + &QuicSessionForTest::OnDataReceived); + last_incoming_stream_ = stream; + } + + // Called when peer has data to read from incoming stream. + void OnDataReceived(net::QuicStreamId id, const char* data, size_t length) { + last_received_data_ = std::string(data, length); + } + + std::string data() { return last_received_data_; } + + bool has_data() { return data().size() > 0; } + + FakeTransportChannel* channel() { return channel_.get(); } + + ReliableQuicStream* incoming_stream() { return last_incoming_stream_; } + + private: + // Transports QUIC packets to/from peer. + scoped_ptr channel_; + // Stores data received by peer once it is sent from the other peer. + std::string last_received_data_; + // Handles incoming streams from sender. + ReliableQuicStream* last_incoming_stream_ = nullptr; +}; + +// Simulates data transfer between two peers using QUIC. +class QuicSessionTest : public ::testing::Test, + public QuicCryptoClientStream::ProofHandler { + public: + QuicSessionTest() : quic_helper_(rtc::Thread::Current()) {} + + // Instantiates |client_peer_| and |server_peer_|. + void CreateClientAndServerSessions(); + + scoped_ptr CreateSession( + scoped_ptr channel, + Perspective perspective); + + QuicCryptoClientStream* CreateCryptoClientStream(QuicSessionForTest* session, + bool handshake_success); + QuicCryptoServerStream* CreateCryptoServerStream(QuicSessionForTest* session, + bool handshake_success); + + scoped_ptr CreateConnection(FakeTransportChannel* channel, + Perspective perspective); + + void StartHandshake(bool client_handshake_success, + bool server_handshake_success); + + // Test handshake establishment and sending/receiving of data. + void TestStreamConnection(QuicSessionForTest* from_session, + QuicSessionForTest* to_session); + // Test that client and server are not connected after handshake failure. + void TestDisconnectAfterFailedHandshake(); + + // QuicCryptoClientStream::ProofHelper overrides. + void OnProofValid( + const QuicCryptoClientConfig::CachedState& cached) override {} + void OnProofVerifyDetailsAvailable( + const ProofVerifyDetails& verify_details) override {} + + protected: + QuicConnectionHelper quic_helper_; + QuicConfig config_; + QuicClock clock_; + + scoped_ptr client_peer_; + scoped_ptr server_peer_; +}; + +// Initializes "client peer" who begins crypto handshake and "server peer" who +// establishes encryption with client. +void QuicSessionTest::CreateClientAndServerSessions() { + scoped_ptr channel1( + new FakeTransportChannel(nullptr, "channel1", 0)); + scoped_ptr channel2( + new FakeTransportChannel(nullptr, "channel2", 0)); + + // Prevent channel1->OnReadPacket and channel2->OnReadPacket from calling + // themselves in a loop, which causes to future packets to be recursively + // consumed while the current thread blocks consumption of current ones. + channel2->SetAsync(true); + + // Configure peers to send packets to each other. + channel1->Connect(); + channel2->Connect(); + channel1->SetDestination(channel2.get()); + + client_peer_ = CreateSession(std::move(channel1), Perspective::IS_CLIENT); + server_peer_ = CreateSession(std::move(channel2), Perspective::IS_SERVER); +} + +scoped_ptr QuicSessionTest::CreateSession( + scoped_ptr channel, + Perspective perspective) { + scoped_ptr quic_connection = + CreateConnection(channel.get(), perspective); + return scoped_ptr(new QuicSessionForTest( + std::move(quic_connection), config_, std::move(channel))); +} + +QuicCryptoClientStream* QuicSessionTest::CreateCryptoClientStream( + QuicSessionForTest* session, + bool handshake_success) { + QuicCryptoClientConfig* client_config = + new QuicCryptoClientConfig(new FakeProofVerifier(handshake_success)); + return new QuicCryptoClientStream( + kServerId, session, new ProofVerifyContext(), client_config, this); +} + +QuicCryptoServerStream* QuicSessionTest::CreateCryptoServerStream( + QuicSessionForTest* session, + bool handshake_success) { + QuicCryptoServerConfig* server_config = + new QuicCryptoServerConfig("TESTING", QuicRandom::GetInstance(), + new FakeProofSource(handshake_success)); + // Provide server with serialized config string to prove ownership. + QuicCryptoServerConfig::ConfigOptions options; + QuicServerConfigProtobuf* primary_config = server_config->GenerateConfig( + QuicRandom::GetInstance(), &clock_, options); + server_config->AddConfig(primary_config, clock_.WallNow()); + return new QuicCryptoServerStream(server_config, session); +} + +scoped_ptr QuicSessionTest::CreateConnection( + FakeTransportChannel* channel, + Perspective perspective) { + FakePacketWriterFactory writer_factory(channel); + + IPAddressNumber ip(net::kIPv4AddressSize, 0); + bool owns_writer = true; + + return scoped_ptr(new QuicConnection( + 0, net::IPEndPoint(ip, 0), &quic_helper_, writer_factory, owns_writer, + perspective, net::QuicSupportedVersions())); +} + +void QuicSessionTest::StartHandshake(bool client_handshake_success, + bool server_handshake_success) { + server_peer_->StartServerHandshake( + CreateCryptoServerStream(server_peer_.get(), server_handshake_success)); + client_peer_->StartClientHandshake( + CreateCryptoClientStream(client_peer_.get(), client_handshake_success)); +} + +void QuicSessionTest::TestStreamConnection(QuicSessionForTest* from_session, + QuicSessionForTest* to_session) { + // Wait for crypto handshake to finish then check if encryption established. + ASSERT_TRUE_WAIT(from_session->IsCryptoHandshakeConfirmed() && + to_session->IsCryptoHandshakeConfirmed(), + kTimeoutMs); + + ASSERT_TRUE(from_session->IsEncryptionEstablished()); + ASSERT_TRUE(to_session->IsEncryptionEstablished()); + + string from_key; + string to_key; + + bool from_success = from_session->ExportKeyingMaterial( + kExporterLabel, kExporterContext, kExporterContextLen, &from_key); + ASSERT_TRUE(from_success); + bool to_success = to_session->ExportKeyingMaterial( + kExporterLabel, kExporterContext, kExporterContextLen, &to_key); + ASSERT_TRUE(to_success); + + EXPECT_EQ(from_key.size(), kExporterContextLen); + EXPECT_EQ(from_key, to_key); + + // Now we can establish encrypted outgoing stream. + ReliableQuicStream* outgoing_stream = + from_session->CreateOutgoingDynamicStream(kDefaultPriority); + ASSERT_NE(nullptr, outgoing_stream); + EXPECT_TRUE(from_session->HasOpenDynamicStreams()); + + outgoing_stream->SignalDataReceived.connect( + from_session, &QuicSessionForTest::OnDataReceived); + to_session->SignalIncomingStream.connect( + to_session, &QuicSessionForTest::OnIncomingStream); + + // Send a test message from peer 1 to peer 2. + const char kTestMessage[] = "Hello, World!"; + outgoing_stream->Write(kTestMessage, strlen(kTestMessage)); + + // Wait for peer 2 to receive messages. + ASSERT_TRUE_WAIT(to_session->has_data(), kTimeoutMs); + + ReliableQuicStream* incoming = to_session->incoming_stream(); + ASSERT_TRUE(incoming); + EXPECT_TRUE(to_session->HasOpenDynamicStreams()); + + EXPECT_EQ(to_session->data(), kTestMessage); + + // Send a test message from peer 2 to peer 1. + const char kTestResponse[] = "Response"; + incoming->Write(kTestResponse, strlen(kTestResponse)); + + // Wait for peer 1 to receive messages. + ASSERT_TRUE_WAIT(from_session->has_data(), kTimeoutMs); + + EXPECT_EQ(from_session->data(), kTestResponse); +} + +// Client and server should disconnect when proof verification fails. +void QuicSessionTest::TestDisconnectAfterFailedHandshake() { + EXPECT_TRUE_WAIT(!client_peer_->connection()->connected(), kTimeoutMs); + EXPECT_TRUE_WAIT(!server_peer_->connection()->connected(), kTimeoutMs); + + EXPECT_FALSE(client_peer_->IsEncryptionEstablished()); + EXPECT_FALSE(client_peer_->IsCryptoHandshakeConfirmed()); + + EXPECT_FALSE(server_peer_->IsEncryptionEstablished()); + EXPECT_FALSE(server_peer_->IsCryptoHandshakeConfirmed()); +} + +// Establish encryption then send message from client to server. +TEST_F(QuicSessionTest, ClientToServer) { + CreateClientAndServerSessions(); + StartHandshake(true, true); + TestStreamConnection(client_peer_.get(), server_peer_.get()); +} + +// Establish encryption then send message from server to client. +TEST_F(QuicSessionTest, ServerToClient) { + CreateClientAndServerSessions(); + StartHandshake(true, true); + TestStreamConnection(server_peer_.get(), client_peer_.get()); +} + +// Make client fail to verify proof from server. +TEST_F(QuicSessionTest, ClientRejection) { + CreateClientAndServerSessions(); + StartHandshake(false, true); + TestDisconnectAfterFailedHandshake(); +} + +// Make server fail to give proof to client. +TEST_F(QuicSessionTest, ServerRejection) { + CreateClientAndServerSessions(); + StartHandshake(true, false); + TestDisconnectAfterFailedHandshake(); +} + +// Test that data streams are not created before handshake. +TEST_F(QuicSessionTest, CannotCreateDataStreamBeforeHandshake) { + CreateClientAndServerSessions(); + EXPECT_EQ(nullptr, server_peer_->CreateOutgoingDynamicStream(5)); + EXPECT_EQ(nullptr, client_peer_->CreateOutgoingDynamicStream(5)); +} diff --git a/webrtc/p2p/quic/reliablequicstream.cc b/webrtc/p2p/quic/reliablequicstream.cc new file mode 100644 index 0000000000..ca2e3f0d3b --- /dev/null +++ b/webrtc/p2p/quic/reliablequicstream.cc @@ -0,0 +1,51 @@ +/* + * 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/reliablequicstream.h" + +#include + +#include "webrtc/base/checks.h" + +namespace cricket { + +ReliableQuicStream::ReliableQuicStream(net::QuicStreamId id, + net::QuicSession* session) + : net::ReliableQuicStream(id, session) { + RTC_DCHECK_NE(net::kCryptoStreamId, id); +} + +ReliableQuicStream::~ReliableQuicStream() {} + +void ReliableQuicStream::OnDataAvailable() { + struct iovec iov; + while (sequencer()->GetReadableRegions(&iov, 1) == 1) { + SignalDataReceived(id(), reinterpret_cast(iov.iov_base), + iov.iov_len); + sequencer()->MarkConsumed(iov.iov_len); + } +} + +void ReliableQuicStream::OnClose() { + net::ReliableQuicStream::OnClose(); + SignalClosed(id(), connection_error()); +} + +rtc::StreamResult ReliableQuicStream::Write(const char* data, size_t len) { + // Writes the data, or buffers it. + WriteOrBufferData(std::string(data, len), false, nullptr); + if (HasBufferedData()) { + return rtc::StreamResult(rtc::SR_BLOCK); + } + + return rtc::StreamResult(rtc::SR_SUCCESS); +} + +} // namespace cricket diff --git a/webrtc/p2p/quic/reliablequicstream.h b/webrtc/p2p/quic/reliablequicstream.h new file mode 100644 index 0000000000..33ed665b46 --- /dev/null +++ b/webrtc/p2p/quic/reliablequicstream.h @@ -0,0 +1,50 @@ +/* + * 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_RELIABLEQUICSTREAM_H_ +#define WEBRTC_P2P_QUIC_RELIABLEQUICSTREAM_H_ + +#include "net/quic/reliable_quic_stream.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/stream.h" + +namespace cricket { + +// Streams created by QuicSession. +class ReliableQuicStream : public net::ReliableQuicStream, + public sigslot::has_slots<> { + public: + ReliableQuicStream(net::QuicStreamId id, net::QuicSession* session); + + ~ReliableQuicStream() override; + + // ReliableQuicStream overrides. + void OnDataAvailable() override; + void OnClose() override; + net::SpdyPriority Priority() const override { return 0; } + + // Process decrypted data into encrypted QUIC packets, which get sent to the + // QuicPacketWriter. rtc::SR_BLOCK is returned if the operation blocks instead + // of writing, in which case the data is queued until OnCanWrite() is called. + rtc::StreamResult Write(const char* data, size_t len); + + // Called when decrypted data is ready to be read. + sigslot::signal3 SignalDataReceived; + // Called when stream closed. + sigslot::signal2 SignalClosed; + + private: + RTC_DISALLOW_COPY_AND_ASSIGN(ReliableQuicStream); +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_QUIC_RELIABLEQUICSTREAM_H_ diff --git a/webrtc/p2p/quic/reliablequicstream_unittest.cc b/webrtc/p2p/quic/reliablequicstream_unittest.cc new file mode 100644 index 0000000000..f9fb5cd7cd --- /dev/null +++ b/webrtc/p2p/quic/reliablequicstream_unittest.cc @@ -0,0 +1,255 @@ +/* + * 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/reliablequicstream.h" + +#include + +#include "net/base/ip_address_number.h" +#include "net/quic/quic_connection.h" +#include "net/quic/quic_protocol.h" +#include "net/quic/quic_session.h" +#include "webrtc/base/buffer.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/stream.h" +#include "webrtc/p2p/quic/quicconnectionhelper.h" + +using cricket::QuicConnectionHelper; +using cricket::ReliableQuicStream; + +using net::FecProtection; +using net::IPAddressNumber; +using net::IPEndPoint; +using net::Perspective; +using net::QuicAckListenerInterface; +using net::QuicConfig; +using net::QuicConnection; +using net::QuicConsumedData; +using net::QuicCryptoStream; +using net::QuicErrorCode; +using net::QuicIOVector; +using net::QuicPacketWriter; +using net::QuicRstStreamErrorCode; +using net::QuicSession; +using net::QuicStreamId; +using net::QuicStreamOffset; +using net::SpdyPriority; + +using rtc::SR_SUCCESS; +using rtc::SR_BLOCK; + +// QuicSession that does not create streams and writes data from +// ReliableQuicStream to a string. +class MockQuicSession : public QuicSession { + public: + MockQuicSession(QuicConnection* connection, + const QuicConfig& config, + std::string* write_buffer) + : QuicSession(connection, config), write_buffer_(write_buffer) {} + + // Writes outgoing data from ReliableQuicStream to a string. + QuicConsumedData WritevData( + QuicStreamId id, + QuicIOVector iovector, + QuicStreamOffset offset, + bool fin, + FecProtection fec_protection, + QuicAckListenerInterface* ack_notifier_delegate) override { + if (!writable_) { + return QuicConsumedData(0, false); + } + + const char* data = reinterpret_cast(iovector.iov->iov_base); + size_t len = iovector.total_length; + write_buffer_->append(data, len); + return QuicConsumedData(len, false); + } + + net::ReliableQuicStream* CreateIncomingDynamicStream( + QuicStreamId id) override { + return nullptr; + } + + net::ReliableQuicStream* CreateOutgoingDynamicStream( + SpdyPriority priority) override { + return nullptr; + } + + QuicCryptoStream* GetCryptoStream() override { return nullptr; } + + // Called by ReliableQuicStream when they want to close stream. + void SendRstStream(QuicStreamId id, + QuicRstStreamErrorCode error, + QuicStreamOffset bytes_written) override {} + + // Sets whether data is written to buffer, or else if this is write blocked. + void set_writable(bool writable) { writable_ = writable; } + + // Tracks whether the stream is write blocked and its priority. + void register_write_blocked_stream(QuicStreamId stream_id, + SpdyPriority priority) { + write_blocked_streams()->RegisterStream(stream_id, priority); + } + + private: + // Stores written data from ReliableQuicStream. + std::string* write_buffer_; + // Whether data is written to write_buffer_. + bool writable_ = true; +}; + +// Packet writer that does nothing. This is required for QuicConnection but +// isn't used for writing data. +class DummyPacketWriter : public QuicPacketWriter { + public: + DummyPacketWriter() {} + + // QuicPacketWriter overrides. + virtual net::WriteResult WritePacket(const char* buffer, + size_t buf_len, + const IPAddressNumber& self_address, + const IPEndPoint& peer_address) { + return net::WriteResult(net::WRITE_STATUS_ERROR, 0); + } + + bool IsWriteBlockedDataBuffered() const override { return false; } + + bool IsWriteBlocked() const override { return false; }; + + void SetWritable() override {} + + net::QuicByteCount GetMaxPacketSize( + const net::IPEndPoint& peer_address) const override { + return 0; + } +}; + +// QuicPacketWriter is not necessary, so this creates a packet writer that +// doesn't do anything. +class DummyPacketWriterFactory : public QuicConnection::PacketWriterFactory { + public: + DummyPacketWriterFactory() {} + + QuicPacketWriter* Create(QuicConnection* connection) const override { + return new DummyPacketWriter(); + } +}; + +class ReliableQuicStreamTest : public ::testing::Test, + public sigslot::has_slots<> { + public: + ReliableQuicStreamTest() {} + + void CreateReliableQuicStream() { + const net::QuicStreamId kStreamId = 5; + + // Arbitrary values for QuicConnection. + QuicConnectionHelper* quic_helper = + new QuicConnectionHelper(rtc::Thread::Current()); + Perspective perspective = Perspective::IS_SERVER; + net::IPAddressNumber ip(net::kIPv4AddressSize, 0); + + bool owns_writer = false; + + QuicConnection* connection = new QuicConnection( + 0, IPEndPoint(ip, 0), quic_helper, DummyPacketWriterFactory(), + owns_writer, perspective, net::QuicSupportedVersions()); + + session_.reset( + new MockQuicSession(connection, QuicConfig(), &write_buffer_)); + stream_.reset(new ReliableQuicStream(kStreamId, session_.get())); + stream_->SignalDataReceived.connect( + this, &ReliableQuicStreamTest::OnDataReceived); + stream_->SignalClosed.connect(this, &ReliableQuicStreamTest::OnClosed); + + session_->register_write_blocked_stream(stream_->id(), stream_->Priority()); + } + + void OnDataReceived(QuicStreamId id, const char* data, size_t length) { + ASSERT_EQ(id, stream_->id()); + read_buffer_.append(data, length); + } + + void OnClosed(QuicStreamId id, QuicErrorCode err) { closed_ = true; } + + protected: + scoped_ptr stream_; + scoped_ptr session_; + + // Data written by the ReliableQuicStream. + std::string write_buffer_; + // Data read by the ReliableQuicStream. + std::string read_buffer_; + // Whether the ReliableQuicStream is closed. + bool closed_ = false; +}; + +// Write an entire string. +TEST_F(ReliableQuicStreamTest, WriteDataWhole) { + CreateReliableQuicStream(); + EXPECT_EQ(SR_SUCCESS, stream_->Write("Foo bar", 7)); + + EXPECT_EQ("Foo bar", write_buffer_); +} + +// Write part of a string. +TEST_F(ReliableQuicStreamTest, WriteDataPartial) { + CreateReliableQuicStream(); + EXPECT_EQ(SR_SUCCESS, stream_->Write("Hello, World!", 8)); + EXPECT_EQ("Hello, W", write_buffer_); +} + +// Test that strings are buffered correctly. +TEST_F(ReliableQuicStreamTest, BufferData) { + CreateReliableQuicStream(); + + session_->set_writable(false); + EXPECT_EQ(SR_BLOCK, stream_->Write("Foo bar", 7)); + + EXPECT_EQ(0ul, write_buffer_.size()); + EXPECT_TRUE(stream_->HasBufferedData()); + + session_->set_writable(true); + stream_->OnCanWrite(); + + EXPECT_FALSE(stream_->HasBufferedData()); + EXPECT_EQ("Foo bar", write_buffer_); + + EXPECT_EQ(SR_SUCCESS, stream_->Write("xyzzy", 5)); + EXPECT_EQ("Foo barxyzzy", write_buffer_); +} + +// Read an entire string. +TEST_F(ReliableQuicStreamTest, ReadDataWhole) { + CreateReliableQuicStream(); + net::QuicStreamFrame frame(-1, false, 0, "Hello, World!"); + stream_->OnStreamFrame(frame); + + EXPECT_EQ("Hello, World!", read_buffer_); +} + +// Read part of a string. +TEST_F(ReliableQuicStreamTest, ReadDataPartial) { + CreateReliableQuicStream(); + net::QuicStreamFrame frame(-1, false, 0, "Hello, World!"); + frame.frame_length = 5; + stream_->OnStreamFrame(frame); + + EXPECT_EQ("Hello", read_buffer_); +} + +// Test that closing the stream results in a callback. +TEST_F(ReliableQuicStreamTest, CloseStream) { + CreateReliableQuicStream(); + EXPECT_FALSE(closed_); + stream_->OnClose(); + EXPECT_TRUE(closed_); +}