diff options
Diffstat (limited to 'webrtc/p2p')
46 files changed, 1863 insertions, 714 deletions
diff --git a/webrtc/p2p/OWNERS b/webrtc/p2p/OWNERS index 9a527df143..0f00d1aa48 100644 --- a/webrtc/p2p/OWNERS +++ b/webrtc/p2p/OWNERS @@ -9,4 +9,9 @@ pthatcher@webrtc.org sergeyu@chromium.org tommi@webrtc.org +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* + per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/p2p/base/candidate.h b/webrtc/p2p/base/candidate.h index 3f0ea43cde..ac7acabf05 100644 --- a/webrtc/p2p/base/candidate.h +++ b/webrtc/p2p/base/candidate.h @@ -105,6 +105,7 @@ class Candidate { std::min(prio_val, static_cast<uint64_t>(UINT_MAX))); } + // TODO(honghaiz): Change to usernameFragment or ufrag. const std::string & username() const { return username_; } void set_username(const std::string & username) { username_ = username; } diff --git a/webrtc/p2p/base/dtlstransport.h b/webrtc/p2p/base/dtlstransport.h index e9a1ae2ada..9f2903e1d7 100644 --- a/webrtc/p2p/base/dtlstransport.h +++ b/webrtc/p2p/base/dtlstransport.h @@ -35,7 +35,7 @@ class DtlsTransport : public Base { : Base(name, allocator), certificate_(certificate), secure_role_(rtc::SSL_CLIENT), - ssl_max_version_(rtc::SSL_PROTOCOL_DTLS_10) {} + ssl_max_version_(rtc::SSL_PROTOCOL_DTLS_12) {} ~DtlsTransport() { Base::DestroyAllChannels(); diff --git a/webrtc/p2p/base/dtlstransportchannel.cc b/webrtc/p2p/base/dtlstransportchannel.cc index 0c063e0323..d6b5bce723 100644 --- a/webrtc/p2p/base/dtlstransportchannel.cc +++ b/webrtc/p2p/base/dtlstransportchannel.cc @@ -8,6 +8,8 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include <utility> + #include "webrtc/p2p/base/dtlstransportchannel.h" #include "webrtc/p2p/base/common.h" @@ -95,7 +97,7 @@ DtlsTransportChannelWrapper::DtlsTransportChannelWrapper( channel_(channel), downward_(NULL), ssl_role_(rtc::SSL_CLIENT), - ssl_max_version_(rtc::SSL_PROTOCOL_DTLS_10) { + ssl_max_version_(rtc::SSL_PROTOCOL_DTLS_12) { channel_->SignalWritableState.connect(this, &DtlsTransportChannelWrapper::OnWritableState); channel_->SignalReadPacket.connect(this, @@ -199,6 +201,8 @@ bool DtlsTransportChannelWrapper::SetRemoteFingerprint( size_t digest_len) { rtc::Buffer remote_fingerprint_value(digest, digest_len); + // Once we have the local certificate, the same remote fingerprint can be set + // multiple times. if (dtls_active_ && remote_fingerprint_value_ == remote_fingerprint_value && !digest_alg.empty()) { // This may happen during renegotiation. @@ -206,28 +210,36 @@ bool DtlsTransportChannelWrapper::SetRemoteFingerprint( return true; } - // Allow SetRemoteFingerprint with a NULL digest even if SetLocalCertificate - // hasn't been called. - if (dtls_ || (!dtls_active_ && !digest_alg.empty())) { - LOG_J(LS_ERROR, this) << "Can't set DTLS remote settings in this state."; - return false; - } - + // If the other side doesn't support DTLS, turn off |dtls_active_|. if (digest_alg.empty()) { + RTC_DCHECK(!digest_len); LOG_J(LS_INFO, this) << "Other side didn't support DTLS."; dtls_active_ = false; return true; } + // Otherwise, we must have a local certificate before setting remote + // fingerprint. + if (!dtls_active_) { + LOG_J(LS_ERROR, this) << "Can't set DTLS remote settings in this state."; + return false; + } + // At this point we know we are doing DTLS - remote_fingerprint_value_ = remote_fingerprint_value.Pass(); + remote_fingerprint_value_ = std::move(remote_fingerprint_value); remote_fingerprint_algorithm_ = digest_alg; + bool reconnect = dtls_; + if (!SetupDtls()) { set_dtls_state(DTLS_TRANSPORT_FAILED); return false; } + if (reconnect) { + Reconnect(); + } + return true; } @@ -267,7 +279,7 @@ bool DtlsTransportChannelWrapper::SetupDtls() { // Set up DTLS-SRTP, if it's been enabled. if (!srtp_ciphers_.empty()) { - if (!dtls_->SetDtlsSrtpCiphers(srtp_ciphers_)) { + if (!dtls_->SetDtlsSrtpCryptoSuites(srtp_ciphers_)) { LOG_J(LS_ERROR, this) << "Couldn't set DTLS-SRTP ciphers."; return false; } @@ -279,11 +291,10 @@ bool DtlsTransportChannelWrapper::SetupDtls() { return true; } -bool DtlsTransportChannelWrapper::SetSrtpCiphers( - const std::vector<std::string>& ciphers) { - if (srtp_ciphers_ == ciphers) { +bool DtlsTransportChannelWrapper::SetSrtpCryptoSuites( + const std::vector<int>& ciphers) { + if (srtp_ciphers_ == ciphers) return true; - } if (dtls_state() == DTLS_TRANSPORT_CONNECTING) { LOG(LS_WARNING) << "Ignoring new SRTP ciphers while DTLS is negotiating"; @@ -294,18 +305,18 @@ bool DtlsTransportChannelWrapper::SetSrtpCiphers( // We don't support DTLS renegotiation currently. If new set of srtp ciphers // are different than what's being used currently, we will not use it. // So for now, let's be happy (or sad) with a warning message. - std::string current_srtp_cipher; - if (!dtls_->GetDtlsSrtpCipher(¤t_srtp_cipher)) { + int current_srtp_cipher; + if (!dtls_->GetDtlsSrtpCryptoSuite(¤t_srtp_cipher)) { LOG(LS_ERROR) << "Failed to get the current SRTP cipher for DTLS channel"; return false; } - const std::vector<std::string>::const_iterator iter = + const std::vector<int>::const_iterator iter = std::find(ciphers.begin(), ciphers.end(), current_srtp_cipher); if (iter == ciphers.end()) { std::string requested_str; for (size_t i = 0; i < ciphers.size(); ++i) { requested_str.append(" "); - requested_str.append(ciphers[i]); + requested_str.append(rtc::SrtpCryptoSuiteToName(ciphers[i])); requested_str.append(" "); } LOG(LS_WARNING) << "Ignoring new set of SRTP ciphers, as DTLS " @@ -324,12 +335,12 @@ bool DtlsTransportChannelWrapper::SetSrtpCiphers( return true; } -bool DtlsTransportChannelWrapper::GetSrtpCryptoSuite(std::string* cipher) { +bool DtlsTransportChannelWrapper::GetSrtpCryptoSuite(int* cipher) { if (dtls_state() != DTLS_TRANSPORT_CONNECTED) { return false; } - return dtls_->GetDtlsSrtpCipher(cipher); + return dtls_->GetDtlsSrtpCryptoSuite(cipher); } @@ -617,4 +628,12 @@ void DtlsTransportChannelWrapper::OnConnectionRemoved( SignalConnectionRemoved(this); } +void DtlsTransportChannelWrapper::Reconnect() { + set_dtls_state(DTLS_TRANSPORT_NEW); + set_writable(false); + if (channel_->writable()) { + OnWritableState(channel_); + } +} + } // namespace cricket diff --git a/webrtc/p2p/base/dtlstransportchannel.h b/webrtc/p2p/base/dtlstransportchannel.h index 41e081b7fe..955b963a36 100644 --- a/webrtc/p2p/base/dtlstransportchannel.h +++ b/webrtc/p2p/base/dtlstransportchannel.h @@ -126,10 +126,10 @@ class DtlsTransportChannelWrapper : public TransportChannelImpl { // Set up the ciphers to use for DTLS-SRTP. If this method is not called // before DTLS starts, or |ciphers| is empty, SRTP keys won't be negotiated. // This method should be called before SetupDtls. - bool SetSrtpCiphers(const std::vector<std::string>& ciphers) override; + bool SetSrtpCryptoSuites(const std::vector<int>& ciphers) override; // Find out which DTLS-SRTP cipher was negotiated - bool GetSrtpCryptoSuite(std::string* cipher) override; + bool GetSrtpCryptoSuite(int* cipher) override; bool GetSslRole(rtc::SSLRole* role) const override; bool SetSslRole(rtc::SSLRole role) override; @@ -216,6 +216,7 @@ class DtlsTransportChannelWrapper : public TransportChannelImpl { void OnRoleConflict(TransportChannelImpl* channel); void OnRouteChange(TransportChannel* channel, const Candidate& candidate); void OnConnectionRemoved(TransportChannelImpl* channel); + void Reconnect(); Transport* transport_; // The transport_ that created us. rtc::Thread* worker_thread_; // Everything should occur on this thread. @@ -223,7 +224,7 @@ class DtlsTransportChannelWrapper : public TransportChannelImpl { TransportChannelImpl* const channel_; rtc::scoped_ptr<rtc::SSLStreamAdapter> dtls_; // The DTLS stream StreamInterfaceChannel* downward_; // Wrapper for channel_, owned by dtls_. - std::vector<std::string> srtp_ciphers_; // SRTP ciphers to use with DTLS. + std::vector<int> srtp_ciphers_; // SRTP ciphers to use with DTLS. bool dtls_active_ = false; rtc::scoped_refptr<rtc::RTCCertificate> local_certificate_; rtc::SSLRole ssl_role_; diff --git a/webrtc/p2p/base/dtlstransportchannel_unittest.cc b/webrtc/p2p/base/dtlstransportchannel_unittest.cc index 07e3b87847..f5d42f3c6e 100644 --- a/webrtc/p2p/base/dtlstransportchannel_unittest.cc +++ b/webrtc/p2p/base/dtlstransportchannel_unittest.cc @@ -28,7 +28,6 @@ return; \ } -static const char AES_CM_128_HMAC_SHA1_80[] = "AES_CM_128_HMAC_SHA1_80"; static const char kIceUfrag1[] = "TESTICEUFRAG0001"; static const char kIcePwd1[] = "TESTICEPWD00000000000001"; static const size_t kPacketNumOffset = 8; @@ -49,14 +48,14 @@ class DtlsTestClient : public sigslot::has_slots<> { : name_(name), packet_size_(0), use_dtls_srtp_(false), - ssl_max_version_(rtc::SSL_PROTOCOL_DTLS_10), + ssl_max_version_(rtc::SSL_PROTOCOL_DTLS_12), negotiated_dtls_(false), received_dtls_client_hello_(false), received_dtls_server_hello_(false) {} void CreateCertificate(rtc::KeyType key_type) { - certificate_ = rtc::RTCCertificate::Create( - rtc::scoped_ptr<rtc::SSLIdentity>( - rtc::SSLIdentity::Generate(name_, key_type)).Pass()); + certificate_ = + rtc::RTCCertificate::Create(rtc::scoped_ptr<rtc::SSLIdentity>( + rtc::SSLIdentity::Generate(name_, key_type))); } const rtc::scoped_refptr<rtc::RTCCertificate>& certificate() { return certificate_; @@ -150,9 +149,9 @@ class DtlsTestClient : public sigslot::has_slots<> { // SRTP ciphers will be set only in the beginning. for (std::vector<cricket::DtlsTransportChannelWrapper*>::iterator it = channels_.begin(); it != channels_.end(); ++it) { - std::vector<std::string> ciphers; - ciphers.push_back(AES_CM_128_HMAC_SHA1_80); - ASSERT_TRUE((*it)->SetSrtpCiphers(ciphers)); + std::vector<int> ciphers; + ciphers.push_back(rtc::SRTP_AES128_CM_SHA1_80); + ASSERT_TRUE((*it)->SetSrtpCryptoSuites(ciphers)); } } @@ -215,16 +214,16 @@ class DtlsTestClient : public sigslot::has_slots<> { } } - void CheckSrtp(const std::string& expected_cipher) { + void CheckSrtp(int expected_crypto_suite) { for (std::vector<cricket::DtlsTransportChannelWrapper*>::iterator it = channels_.begin(); it != channels_.end(); ++it) { - std::string cipher; + int crypto_suite; - bool rv = (*it)->GetSrtpCryptoSuite(&cipher); - if (negotiated_dtls_ && !expected_cipher.empty()) { + bool rv = (*it)->GetSrtpCryptoSuite(&crypto_suite); + if (negotiated_dtls_ && expected_crypto_suite) { ASSERT_TRUE(rv); - ASSERT_EQ(cipher, expected_cipher); + ASSERT_EQ(crypto_suite, expected_crypto_suite); } else { ASSERT_FALSE(rv); } @@ -401,7 +400,7 @@ class DtlsTransportChannelTest : public testing::Test { channel_ct_(1), use_dtls_(false), use_dtls_srtp_(false), - ssl_expected_version_(rtc::SSL_PROTOCOL_DTLS_10) {} + ssl_expected_version_(rtc::SSL_PROTOCOL_DTLS_12) {} void SetChannelCount(size_t channel_ct) { channel_ct_ = static_cast<int>(channel_ct); @@ -469,11 +468,11 @@ class DtlsTransportChannelTest : public testing::Test { // Check that we negotiated the right ciphers. if (use_dtls_srtp_) { - client1_.CheckSrtp(AES_CM_128_HMAC_SHA1_80); - client2_.CheckSrtp(AES_CM_128_HMAC_SHA1_80); + client1_.CheckSrtp(rtc::SRTP_AES128_CM_SHA1_80); + client2_.CheckSrtp(rtc::SRTP_AES128_CM_SHA1_80); } else { - client1_.CheckSrtp(""); - client2_.CheckSrtp(""); + client1_.CheckSrtp(rtc::SRTP_INVALID_CRYPTO_SUITE); + client2_.CheckSrtp(rtc::SRTP_INVALID_CRYPTO_SUITE); } client1_.CheckSsl(rtc::SSLStreamAdapter::GetDefaultSslCipherForTest( ssl_expected_version_, rtc::KT_DEFAULT)); @@ -601,16 +600,30 @@ TEST_F(DtlsTransportChannelTest, TestTransferSrtpTwoChannels) { TestTransfer(1, 1000, 100, true); } +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestTransferDtls DISABLED_TestTransferDtls +#else +#define MAYBE_TestTransferDtls TestTransferDtls +#endif // Connect with DTLS, and transfer some data. -TEST_F(DtlsTransportChannelTest, TestTransferDtls) { +TEST_F(DtlsTransportChannelTest, MAYBE_TestTransferDtls) { MAYBE_SKIP_TEST(HaveDtls); PrepareDtls(true, true, rtc::KT_DEFAULT); ASSERT_TRUE(Connect()); TestTransfer(0, 1000, 100, false); } +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestTransferDtlsTwoChannels DISABLED_TestTransferDtlsTwoChannels +#else +#define MAYBE_TestTransferDtlsTwoChannels TestTransferDtlsTwoChannels +#endif // Create two channels with DTLS, and transfer some data. -TEST_F(DtlsTransportChannelTest, TestTransferDtlsTwoChannels) { +TEST_F(DtlsTransportChannelTest, MAYBE_TestTransferDtlsTwoChannels) { MAYBE_SKIP_TEST(HaveDtls); SetChannelCount(2); PrepareDtls(true, true, rtc::KT_DEFAULT); @@ -642,8 +655,15 @@ TEST_F(DtlsTransportChannelTest, TestDtls12None) { ASSERT_TRUE(Connect()); } +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestDtls12Both DISABLED_TestDtls12Both +#else +#define MAYBE_TestDtls12Both TestDtls12Both +#endif // Create two channels with DTLS 1.2 and check ciphers. -TEST_F(DtlsTransportChannelTest, TestDtls12Both) { +TEST_F(DtlsTransportChannelTest, MAYBE_TestDtls12Both) { MAYBE_SKIP_TEST(HaveDtls); SetChannelCount(2); PrepareDtls(true, true, rtc::KT_DEFAULT); @@ -669,8 +689,15 @@ TEST_F(DtlsTransportChannelTest, TestDtls12Client2) { ASSERT_TRUE(Connect()); } +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestTransferDtlsSrtp DISABLED_TestTransferDtlsSrtp +#else +#define MAYBE_TestTransferDtlsSrtp TestTransferDtlsSrtp +#endif // Connect with DTLS, negotiate DTLS-SRTP, and transfer SRTP using bypass. -TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtp) { +TEST_F(DtlsTransportChannelTest, MAYBE_TestTransferDtlsSrtp) { MAYBE_SKIP_TEST(HaveDtlsSrtp); PrepareDtls(true, true, rtc::KT_DEFAULT); PrepareDtlsSrtp(true, true); @@ -678,9 +705,18 @@ TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtp) { TestTransfer(0, 1000, 100, true); } +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestTransferDtlsInvalidSrtpPacket \ + DISABLED_TestTransferDtlsInvalidSrtpPacket +#else +#define MAYBE_TestTransferDtlsInvalidSrtpPacket \ + TestTransferDtlsInvalidSrtpPacket +#endif // Connect with DTLS-SRTP, transfer an invalid SRTP packet, and expects -1 // returned. -TEST_F(DtlsTransportChannelTest, TestTransferDtlsInvalidSrtpPacket) { +TEST_F(DtlsTransportChannelTest, MAYBE_TestTransferDtlsInvalidSrtpPacket) { MAYBE_SKIP_TEST(HaveDtls); PrepareDtls(true, true, rtc::KT_DEFAULT); PrepareDtlsSrtp(true, true); @@ -689,24 +725,47 @@ TEST_F(DtlsTransportChannelTest, TestTransferDtlsInvalidSrtpPacket) { ASSERT_EQ(-1, result); } +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestTransferDtlsSrtpRejected DISABLED_TestTransferDtlsSrtpRejected +#else +#define MAYBE_TestTransferDtlsSrtpRejected TestTransferDtlsSrtpRejected +#endif // Connect with DTLS. A does DTLS-SRTP but B does not. -TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpRejected) { +TEST_F(DtlsTransportChannelTest, MAYBE_TestTransferDtlsSrtpRejected) { MAYBE_SKIP_TEST(HaveDtlsSrtp); PrepareDtls(true, true, rtc::KT_DEFAULT); PrepareDtlsSrtp(true, false); ASSERT_TRUE(Connect()); } +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestTransferDtlsSrtpNotOffered \ + DISABLED_TestTransferDtlsSrtpNotOffered +#else +#define MAYBE_TestTransferDtlsSrtpNotOffered TestTransferDtlsSrtpNotOffered +#endif // Connect with DTLS. B does DTLS-SRTP but A does not. -TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpNotOffered) { +TEST_F(DtlsTransportChannelTest, MAYBE_TestTransferDtlsSrtpNotOffered) { MAYBE_SKIP_TEST(HaveDtlsSrtp); PrepareDtls(true, true, rtc::KT_DEFAULT); PrepareDtlsSrtp(false, true); ASSERT_TRUE(Connect()); } +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestTransferDtlsSrtpTwoChannels \ + DISABLED_TestTransferDtlsSrtpTwoChannels +#else +#define MAYBE_TestTransferDtlsSrtpTwoChannels TestTransferDtlsSrtpTwoChannels +#endif // Create two channels with DTLS, negotiate DTLS-SRTP, and transfer bypass SRTP. -TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpTwoChannels) { +TEST_F(DtlsTransportChannelTest, MAYBE_TestTransferDtlsSrtpTwoChannels) { MAYBE_SKIP_TEST(HaveDtlsSrtp); SetChannelCount(2); PrepareDtls(true, true, rtc::KT_DEFAULT); @@ -716,8 +775,15 @@ TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpTwoChannels) { TestTransfer(1, 1000, 100, true); } +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestTransferDtlsSrtpDemux DISABLED_TestTransferDtlsSrtpDemux +#else +#define MAYBE_TestTransferDtlsSrtpDemux TestTransferDtlsSrtpDemux +#endif // Create a single channel with DTLS, and send normal data and SRTP data on it. -TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpDemux) { +TEST_F(DtlsTransportChannelTest, MAYBE_TestTransferDtlsSrtpDemux) { MAYBE_SKIP_TEST(HaveDtlsSrtp); PrepareDtls(true, true, rtc::KT_DEFAULT); PrepareDtlsSrtp(true, true); @@ -726,8 +792,17 @@ TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpDemux) { TestTransfer(0, 1000, 100, true); } +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestTransferDtlsAnswererIsPassive \ + DISABLED_TestTransferDtlsAnswererIsPassive +#else +#define MAYBE_TestTransferDtlsAnswererIsPassive \ + TestTransferDtlsAnswererIsPassive +#endif // Testing when the remote is passive. -TEST_F(DtlsTransportChannelTest, TestTransferDtlsAnswererIsPassive) { +TEST_F(DtlsTransportChannelTest, MAYBE_TestTransferDtlsAnswererIsPassive) { MAYBE_SKIP_TEST(HaveDtlsSrtp); SetChannelCount(2); PrepareDtls(true, true, rtc::KT_DEFAULT); @@ -752,9 +827,16 @@ TEST_F(DtlsTransportChannelTest, TestDtlsSetupWithLegacyAsAnswerer) { EXPECT_EQ(rtc::SSL_CLIENT, channel2_role); } +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestDtlsReOfferFromOfferer DISABLED_TestDtlsReOfferFromOfferer +#else +#define MAYBE_TestDtlsReOfferFromOfferer TestDtlsReOfferFromOfferer +#endif // Testing re offer/answer after the session is estbalished. Roles will be // kept same as of the previous negotiation. -TEST_F(DtlsTransportChannelTest, TestDtlsReOfferFromOfferer) { +TEST_F(DtlsTransportChannelTest, MAYBE_TestDtlsReOfferFromOfferer) { MAYBE_SKIP_TEST(HaveDtlsSrtp); SetChannelCount(2); PrepareDtls(true, true, rtc::KT_DEFAULT); @@ -771,7 +853,14 @@ TEST_F(DtlsTransportChannelTest, TestDtlsReOfferFromOfferer) { TestTransfer(1, 1000, 100, true); } -TEST_F(DtlsTransportChannelTest, TestDtlsReOfferFromAnswerer) { +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestDtlsReOfferFromAnswerer DISABLED_TestDtlsReOfferFromAnswerer +#else +#define MAYBE_TestDtlsReOfferFromAnswerer TestDtlsReOfferFromAnswerer +#endif +TEST_F(DtlsTransportChannelTest, MAYBE_TestDtlsReOfferFromAnswerer) { MAYBE_SKIP_TEST(HaveDtlsSrtp); SetChannelCount(2); PrepareDtls(true, true, rtc::KT_DEFAULT); @@ -788,8 +877,15 @@ TEST_F(DtlsTransportChannelTest, TestDtlsReOfferFromAnswerer) { TestTransfer(1, 1000, 100, true); } +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestDtlsRoleReversal DISABLED_TestDtlsRoleReversal +#else +#define MAYBE_TestDtlsRoleReversal TestDtlsRoleReversal +#endif // Test that any change in role after the intial setup will result in failure. -TEST_F(DtlsTransportChannelTest, TestDtlsRoleReversal) { +TEST_F(DtlsTransportChannelTest, MAYBE_TestDtlsRoleReversal) { MAYBE_SKIP_TEST(HaveDtlsSrtp); SetChannelCount(2); PrepareDtls(true, true, rtc::KT_DEFAULT); @@ -803,9 +899,18 @@ TEST_F(DtlsTransportChannelTest, TestDtlsRoleReversal) { NF_REOFFER | NF_EXPECT_FAILURE); } +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestDtlsReOfferWithDifferentSetupAttr \ + DISABLED_TestDtlsReOfferWithDifferentSetupAttr +#else +#define MAYBE_TestDtlsReOfferWithDifferentSetupAttr \ + TestDtlsReOfferWithDifferentSetupAttr +#endif // Test that using different setup attributes which results in similar ssl // role as the initial negotiation will result in success. -TEST_F(DtlsTransportChannelTest, TestDtlsReOfferWithDifferentSetupAttr) { +TEST_F(DtlsTransportChannelTest, MAYBE_TestDtlsReOfferWithDifferentSetupAttr) { MAYBE_SKIP_TEST(HaveDtlsSrtp); SetChannelCount(2); PrepareDtls(true, true, rtc::KT_DEFAULT); @@ -865,8 +970,15 @@ TEST_F(DtlsTransportChannelTest, TestCertificatesBeforeConnect) { ASSERT_FALSE(remote_cert2 != NULL); } +#if defined(MEMORY_SANITIZER) +// Fails under MemorySanitizer: +// See https://code.google.com/p/webrtc/issues/detail?id=5381. +#define MAYBE_TestCertificatesAfterConnect DISABLED_TestCertificatesAfterConnect +#else +#define MAYBE_TestCertificatesAfterConnect TestCertificatesAfterConnect +#endif // Test Certificates state after connection. -TEST_F(DtlsTransportChannelTest, TestCertificatesAfterConnect) { +TEST_F(DtlsTransportChannelTest, MAYBE_TestCertificatesAfterConnect) { MAYBE_SKIP_TEST(HaveDtls); PrepareDtls(true, true, rtc::KT_DEFAULT); ASSERT_TRUE(Connect()); diff --git a/webrtc/p2p/base/faketransportcontroller.h b/webrtc/p2p/base/faketransportcontroller.h index 3e656fa4a3..65c59be98d 100644 --- a/webrtc/p2p/base/faketransportcontroller.h +++ b/webrtc/p2p/base/faketransportcontroller.h @@ -242,20 +242,20 @@ class FakeTransportChannel : public TransportChannelImpl, bool IsDtlsActive() const override { return do_dtls_; } - bool SetSrtpCiphers(const std::vector<std::string>& ciphers) override { + bool SetSrtpCryptoSuites(const std::vector<int>& ciphers) override { srtp_ciphers_ = ciphers; return true; } - bool GetSrtpCryptoSuite(std::string* cipher) override { - if (!chosen_srtp_cipher_.empty()) { - *cipher = chosen_srtp_cipher_; + bool GetSrtpCryptoSuite(int* crypto_suite) override { + if (chosen_crypto_suite_ != rtc::SRTP_INVALID_CRYPTO_SUITE) { + *crypto_suite = chosen_crypto_suite_; return true; } return false; } - bool GetSslCipherSuite(int* cipher) override { return false; } + bool GetSslCipherSuite(int* cipher_suite) override { return false; } rtc::scoped_refptr<rtc::RTCCertificate> GetLocalCertificate() const { return local_cert_; @@ -275,7 +275,7 @@ class FakeTransportChannel : public TransportChannelImpl, bool use_context, uint8_t* result, size_t result_len) override { - if (!chosen_srtp_cipher_.empty()) { + if (chosen_crypto_suite_ != rtc::SRTP_INVALID_CRYPTO_SUITE) { memset(result, 0xff, result_len); return true; } @@ -284,14 +284,13 @@ class FakeTransportChannel : public TransportChannelImpl, } void NegotiateSrtpCiphers() { - for (std::vector<std::string>::const_iterator it1 = srtp_ciphers_.begin(); + for (std::vector<int>::const_iterator it1 = srtp_ciphers_.begin(); it1 != srtp_ciphers_.end(); ++it1) { - for (std::vector<std::string>::const_iterator it2 = - dest_->srtp_ciphers_.begin(); + for (std::vector<int>::const_iterator it2 = dest_->srtp_ciphers_.begin(); it2 != dest_->srtp_ciphers_.end(); ++it2) { if (*it1 == *it2) { - chosen_srtp_cipher_ = *it1; - dest_->chosen_srtp_cipher_ = *it2; + chosen_crypto_suite_ = *it1; + dest_->chosen_crypto_suite_ = *it2; return; } } @@ -322,8 +321,8 @@ class FakeTransportChannel : public TransportChannelImpl, rtc::scoped_refptr<rtc::RTCCertificate> local_cert_; rtc::FakeSSLCertificate* remote_cert_ = nullptr; bool do_dtls_ = false; - std::vector<std::string> srtp_ciphers_; - std::string chosen_srtp_cipher_; + std::vector<int> srtp_ciphers_; + int chosen_crypto_suite_ = rtc::SRTP_INVALID_CRYPTO_SUITE; int receiving_timeout_ = -1; bool gather_continually_ = false; IceRole role_ = ICEROLE_UNKNOWN; @@ -333,7 +332,7 @@ class FakeTransportChannel : public TransportChannelImpl, std::string remote_ice_ufrag_; std::string remote_ice_pwd_; IceMode remote_ice_mode_ = ICEMODE_FULL; - rtc::SSLProtocolVersion ssl_max_version_ = rtc::SSL_PROTOCOL_DTLS_10; + rtc::SSLProtocolVersion ssl_max_version_ = rtc::SSL_PROTOCOL_DTLS_12; rtc::SSLFingerprint dtls_fingerprint_; rtc::SSLRole ssl_role_ = rtc::SSL_CLIENT; size_t connection_count_ = 0; @@ -454,7 +453,7 @@ class FakeTransport : public Transport { FakeTransport* dest_ = nullptr; bool async_ = false; rtc::scoped_refptr<rtc::RTCCertificate> certificate_; - rtc::SSLProtocolVersion ssl_max_version_ = rtc::SSL_PROTOCOL_DTLS_10; + rtc::SSLProtocolVersion ssl_max_version_ = rtc::SSL_PROTOCOL_DTLS_12; }; // Fake TransportController class, which can be passed into a BaseChannel object diff --git a/webrtc/p2p/base/p2ptransportchannel.cc b/webrtc/p2p/base/p2ptransportchannel.cc index 623085f9a8..952cfab747 100644 --- a/webrtc/p2p/base/p2ptransportchannel.cc +++ b/webrtc/p2p/base/p2ptransportchannel.cc @@ -187,6 +187,7 @@ namespace cricket { // well on a 28.8K modem, which is the slowest connection on which the voice // quality is reasonable at all. static const uint32_t PING_PACKET_SIZE = 60 * 8; +// TODO(honghaiz): Change the word DELAY to INTERVAL whenever appropriate. // STRONG_PING_DELAY (480ms) is applied when the best connection is both // writable and receiving. static const uint32_t STRONG_PING_DELAY = 1000 * PING_PACKET_SIZE / 1000; @@ -201,7 +202,6 @@ static const uint32_t MAX_CURRENT_STRONG_DELAY = 900; static const int MIN_CHECK_RECEIVING_DELAY = 50; // ms - P2PTransportChannel::P2PTransportChannel(const std::string& transport_name, int component, P2PTransport* transport, @@ -215,14 +215,13 @@ P2PTransportChannel::P2PTransportChannel(const std::string& transport_name, best_connection_(NULL), pending_best_connection_(NULL), sort_dirty_(false), - was_writable_(false), remote_ice_mode_(ICEMODE_FULL), ice_role_(ICEROLE_UNKNOWN), tiebreaker_(0), - remote_candidate_generation_(0), gathering_state_(kIceGatheringNew), check_receiving_delay_(MIN_CHECK_RECEIVING_DELAY * 5), - receiving_timeout_(MIN_CHECK_RECEIVING_DELAY * 50) { + receiving_timeout_(MIN_CHECK_RECEIVING_DELAY * 50), + backup_connection_ping_interval_(0) { uint32_t weak_ping_delay = ::strtoul( webrtc::field_trial::FindFullName("WebRTC-StunInterPacketDelay").c_str(), nullptr, 10); @@ -241,6 +240,8 @@ P2PTransportChannel::~P2PTransportChannel() { // Add the allocator session to our list so that we know which sessions // are still active. void P2PTransportChannel::AddAllocatorSession(PortAllocatorSession* session) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + session->set_generation(static_cast<uint32_t>(allocator_sessions_.size())); allocator_sessions_.push_back(session); @@ -295,9 +296,13 @@ void P2PTransportChannel::SetIceTiebreaker(uint64_t tiebreaker) { tiebreaker_ = tiebreaker; } +TransportChannelState P2PTransportChannel::GetState() const { + return state_; +} + // A channel is considered ICE completed once there is at most one active // connection per network and at least one active connection. -TransportChannelState P2PTransportChannel::GetState() const { +TransportChannelState P2PTransportChannel::ComputeState() const { if (!had_connection_) { return TransportChannelState::STATE_INIT; } @@ -341,25 +346,23 @@ void P2PTransportChannel::SetIceCredentials(const std::string& ice_ufrag, void P2PTransportChannel::SetRemoteIceCredentials(const std::string& ice_ufrag, const std::string& ice_pwd) { ASSERT(worker_thread_ == rtc::Thread::Current()); - bool ice_restart = false; - if (!remote_ice_ufrag_.empty() && !remote_ice_pwd_.empty()) { - ice_restart = (remote_ice_ufrag_ != ice_ufrag) || - (remote_ice_pwd_!= ice_pwd); + IceParameters* current_ice = remote_ice(); + IceParameters new_ice(ice_ufrag, ice_pwd); + if (!current_ice || *current_ice != new_ice) { + // Keep the ICE credentials so that newer connections + // are prioritized over the older ones. + remote_ice_parameters_.push_back(new_ice); + } + + // Update the pwd of remote candidate if needed. + for (RemoteCandidate& candidate : remote_candidates_) { + if (candidate.username() == ice_ufrag && candidate.password().empty()) { + candidate.set_password(ice_pwd); + } } - - remote_ice_ufrag_ = ice_ufrag; - remote_ice_pwd_ = ice_pwd; - // We need to update the credentials for any peer reflexive candidates. - std::vector<Connection*>::iterator it = connections_.begin(); - for (; it != connections_.end(); ++it) { - (*it)->MaybeSetRemoteIceCredentials(ice_ufrag, ice_pwd); - } - - if (ice_restart) { - // We need to keep track of the remote ice restart so newer - // connections are prioritized over the older. - ++remote_candidate_generation_; + for (Connection* conn : connections_) { + conn->MaybeSetRemoteIceCredentials(ice_ufrag, ice_pwd); } } @@ -371,18 +374,26 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) { gather_continually_ = config.gather_continually; LOG(LS_INFO) << "Set gather_continually to " << gather_continually_; - if (config.receiving_timeout_ms < 0) { - return; + if (config.backup_connection_ping_interval >= 0 && + backup_connection_ping_interval_ != + config.backup_connection_ping_interval) { + backup_connection_ping_interval_ = config.backup_connection_ping_interval; + LOG(LS_INFO) << "Set backup connection ping interval to " + << backup_connection_ping_interval_ << " milliseconds."; } - receiving_timeout_ = config.receiving_timeout_ms; - check_receiving_delay_ = - std::max(MIN_CHECK_RECEIVING_DELAY, receiving_timeout_ / 10); - for (Connection* connection : connections_) { - connection->set_receiving_timeout(receiving_timeout_); + if (config.receiving_timeout_ms >= 0 && + receiving_timeout_ != config.receiving_timeout_ms) { + receiving_timeout_ = config.receiving_timeout_ms; + check_receiving_delay_ = + std::max(MIN_CHECK_RECEIVING_DELAY, receiving_timeout_ / 10); + + for (Connection* connection : connections_) { + connection->set_receiving_timeout(receiving_timeout_); + } + LOG(LS_INFO) << "Set ICE receiving timeout to " << receiving_timeout_ + << " milliseconds"; } - LOG(LS_INFO) << "Set ICE receiving timeout to " << receiving_timeout_ - << " milliseconds"; } // Go into the state of processing candidates, and running in general @@ -519,11 +530,17 @@ void P2PTransportChannel::OnUnknownAddress( } } + uint32_t remote_generation = 0; // The STUN binding request may arrive after setRemoteDescription and before // adding remote candidate, so we need to set the password to the shared // password if the user name matches. - if (remote_password.empty() && remote_username == remote_ice_ufrag_) { - remote_password = remote_ice_pwd_; + if (remote_password.empty()) { + const IceParameters* ice_param = + FindRemoteIceFromUfrag(remote_username, &remote_generation); + // Note: if not found, the remote_generation will still be 0. + if (ice_param != nullptr) { + remote_password = ice_param->pwd; + } } Candidate remote_candidate; @@ -555,9 +572,9 @@ void P2PTransportChannel::OnUnknownAddress( // If the source transport address of the request does not match any // existing remote candidates, it represents a new peer reflexive remote // candidate. - remote_candidate = - Candidate(component(), ProtoToString(proto), address, 0, - remote_username, remote_password, PRFLX_PORT_TYPE, 0U, ""); + remote_candidate = Candidate(component(), ProtoToString(proto), address, 0, + remote_username, remote_password, + PRFLX_PORT_TYPE, remote_generation, ""); // From RFC 5245, section-7.2.1.3: // The foundation of the candidate is set to an arbitrary value, different @@ -604,14 +621,7 @@ void P2PTransportChannel::OnUnknownAddress( << (remote_candidate_is_new ? "peer reflexive" : "resurrected") << " candidate: " << remote_candidate.ToString(); AddConnection(connection); - connection->ReceivedPing(); - - bool received_use_candidate = - stun_msg->GetByteString(STUN_ATTR_USE_CANDIDATE) != nullptr; - if (received_use_candidate && ice_role_ == ICEROLE_CONTROLLED) { - connection->set_nominated(true); - OnNominated(connection); - } + connection->HandleBindingRequest(stun_msg); // Update the list of connections since we just added another. We do this // after sending the response since it could (in principle) delete the @@ -624,6 +634,21 @@ void P2PTransportChannel::OnRoleConflict(PortInterface* port) { // from Transport. } +const IceParameters* P2PTransportChannel::FindRemoteIceFromUfrag( + const std::string& ufrag, + uint32_t* generation) { + const auto& params = remote_ice_parameters_; + auto it = std::find_if( + params.rbegin(), params.rend(), + [ufrag](const IceParameters& param) { return param.ufrag == ufrag; }); + if (it == params.rend()) { + // Not found. + return nullptr; + } + *generation = params.rend() - it - 1; + return &(*it); +} + void P2PTransportChannel::OnNominated(Connection* conn) { ASSERT(worker_thread_ == rtc::Thread::Current()); ASSERT(ice_role_ == ICEROLE_CONTROLLED); @@ -648,19 +673,39 @@ void P2PTransportChannel::OnNominated(Connection* conn) { void P2PTransportChannel::AddRemoteCandidate(const Candidate& candidate) { ASSERT(worker_thread_ == rtc::Thread::Current()); - uint32_t generation = candidate.generation(); - // Network may not guarantee the order of the candidate delivery. If a - // remote candidate with an older generation arrives, drop it. - if (generation != 0 && generation < remote_candidate_generation_) { - LOG(LS_WARNING) << "Dropping a remote candidate because its generation " - << generation - << " is lower than the current remote generation " - << remote_candidate_generation_; + uint32_t generation = GetRemoteCandidateGeneration(candidate); + // If a remote candidate with a previous generation arrives, drop it. + if (generation < remote_ice_generation()) { + LOG(LS_WARNING) << "Dropping a remote candidate because its ufrag " + << candidate.username() + << " indicates it was for a previous generation."; return; } + Candidate new_remote_candidate(candidate); + new_remote_candidate.set_generation(generation); + // ICE candidates don't need to have username and password set, but + // the code below this (specifically, ConnectionRequest::Prepare in + // port.cc) uses the remote candidates's username. So, we set it + // here. + if (remote_ice()) { + if (candidate.username().empty()) { + new_remote_candidate.set_username(remote_ice()->ufrag); + } + if (new_remote_candidate.username() == remote_ice()->ufrag) { + if (candidate.password().empty()) { + new_remote_candidate.set_password(remote_ice()->pwd); + } + } else { + // The candidate belongs to the next generation. Its pwd will be set + // when the new remote ICE credentials arrive. + LOG(LS_WARNING) << "A remote candidate arrives with an unknown ufrag: " + << candidate.username(); + } + } + // Create connections to this remote candidate. - CreateConnections(candidate, NULL); + CreateConnections(new_remote_candidate, NULL); // Resort the connections list, which may have new elements. SortConnections(); @@ -673,20 +718,6 @@ bool P2PTransportChannel::CreateConnections(const Candidate& remote_candidate, PortInterface* origin_port) { ASSERT(worker_thread_ == rtc::Thread::Current()); - Candidate new_remote_candidate(remote_candidate); - new_remote_candidate.set_generation( - GetRemoteCandidateGeneration(remote_candidate)); - // ICE candidates don't need to have username and password set, but - // the code below this (specifically, ConnectionRequest::Prepare in - // port.cc) uses the remote candidates's username. So, we set it - // here. - if (remote_candidate.username().empty()) { - new_remote_candidate.set_username(remote_ice_ufrag_); - } - if (remote_candidate.password().empty()) { - new_remote_candidate.set_password(remote_ice_pwd_); - } - // If we've already seen the new remote candidate (in the current candidate // generation), then we shouldn't try creating connections for it. // We either already have a connection for it, or we previously created one @@ -695,7 +726,7 @@ bool P2PTransportChannel::CreateConnections(const Candidate& remote_candidate, // immediately be re-pruned, churning the network for no purpose. // This only applies to candidates received over signaling (i.e. origin_port // is NULL). - if (!origin_port && IsDuplicateRemoteCandidate(new_remote_candidate)) { + if (!origin_port && IsDuplicateRemoteCandidate(remote_candidate)) { // return true to indicate success, without creating any new connections. return true; } @@ -708,7 +739,7 @@ bool P2PTransportChannel::CreateConnections(const Candidate& remote_candidate, bool created = false; std::vector<PortInterface *>::reverse_iterator it; for (it = ports_.rbegin(); it != ports_.rend(); ++it) { - if (CreateConnection(*it, new_remote_candidate, origin_port)) { + if (CreateConnection(*it, remote_candidate, origin_port)) { if (*it == origin_port) created = true; } @@ -716,12 +747,12 @@ bool P2PTransportChannel::CreateConnections(const Candidate& remote_candidate, if ((origin_port != NULL) && std::find(ports_.begin(), ports_.end(), origin_port) == ports_.end()) { - if (CreateConnection(origin_port, new_remote_candidate, origin_port)) + if (CreateConnection(origin_port, remote_candidate, origin_port)) created = true; } // Remember this remote candidate so that we can add it to future ports. - RememberRemoteCandidate(new_remote_candidate, origin_port); + RememberRemoteCandidate(remote_candidate, origin_port); return created; } @@ -731,6 +762,9 @@ bool P2PTransportChannel::CreateConnections(const Candidate& remote_candidate, bool P2PTransportChannel::CreateConnection(PortInterface* port, const Candidate& remote_candidate, PortInterface* origin_port) { + if (!port->SupportsProtocol(remote_candidate.protocol())) { + return false; + } // Look for an existing connection with this remote address. If one is not // found, then we can create a new connection for this address. Connection* connection = port->GetConnection(remote_candidate.address()); @@ -777,11 +811,21 @@ bool P2PTransportChannel::FindConnection( uint32_t P2PTransportChannel::GetRemoteCandidateGeneration( const Candidate& candidate) { - // We need to keep track of the remote ice restart so newer - // connections are prioritized over the older. - ASSERT(candidate.generation() == 0 || - candidate.generation() == remote_candidate_generation_); - return remote_candidate_generation_; + // If the candidate has a ufrag, use it to find the generation. + if (!candidate.username().empty()) { + uint32_t generation = 0; + if (!FindRemoteIceFromUfrag(candidate.username(), &generation)) { + // If the ufrag is not found, assume the next/future generation. + generation = static_cast<uint32_t>(remote_ice_parameters_.size()); + } + return generation; + } + // If candidate generation is set, use that. + if (candidate.generation() > 0) { + return candidate.generation(); + } + // Otherwise, assume the generation from remote ice parameters. + return remote_ice_generation(); } // Check if remote candidate is already cached. @@ -990,17 +1034,13 @@ void P2PTransportChannel::SortConnections() { // Now update the writable state of the channel with the information we have // so far. - if (best_connection_ && best_connection_->writable()) { - HandleWritable(); - } else if (all_connections_timedout) { + if (all_connections_timedout) { HandleAllTimedOut(); - } else { - HandleNotWritable(); } // Update the state of this channel. This method is called whenever the // state of any connection changes, so this is a good place to do this. - UpdateChannelState(); + UpdateState(); } Connection* P2PTransportChannel::best_nominated_connection() const { @@ -1060,13 +1100,17 @@ void P2PTransportChannel::SwitchBestConnectionTo(Connection* conn) { } } -void P2PTransportChannel::UpdateChannelState() { - // The Handle* functions already set the writable state. We'll just double- - // check it here. +// Warning: UpdateState should eventually be called whenever a connection +// is added, deleted, or the write state of any connection changes so that the +// transport controller will get the up-to-date channel state. However it +// should not be called too often; in the case that multiple connection states +// change, it should be called after all the connection states have changed. For +// example, we call this at the end of SortConnections. +void P2PTransportChannel::UpdateState() { + state_ = ComputeState(); + bool writable = best_connection_ && best_connection_->writable(); - ASSERT(writable == this->writable()); - if (writable != this->writable()) - LOG(LS_ERROR) << "UpdateChannelState: writable state mismatch"; + set_writable(writable); bool receiving = false; for (const Connection* connection : connections_) { @@ -1078,11 +1122,8 @@ void P2PTransportChannel::UpdateChannelState() { set_receiving(receiving); } -// We checked the status of our connections and we had at least one that -// was writable, go into the writable state. -void P2PTransportChannel::HandleWritable() { - ASSERT(worker_thread_ == rtc::Thread::Current()); - if (writable()) { +void P2PTransportChannel::MaybeStopPortAllocatorSessions() { + if (!IsGettingPorts()) { return; } @@ -1098,18 +1139,6 @@ void P2PTransportChannel::HandleWritable() { } session->StopGettingPorts(); } - - was_writable_ = true; - set_writable(true); -} - -// Notify upper layer about channel not writable state, if it was before. -void P2PTransportChannel::HandleNotWritable() { - ASSERT(worker_thread_ == rtc::Thread::Current()); - if (was_writable_) { - was_writable_ = false; - set_writable(false); - } } // If all connections timed out, delete them all. @@ -1179,10 +1208,17 @@ void P2PTransportChannel::OnCheckAndPing() { thread()->PostDelayed(check_delay, this, MSG_CHECK_AND_PING); } +// A connection is considered a backup connection if the channel state +// is completed, the connection is not the best connection and it is active. +bool P2PTransportChannel::IsBackupConnection(Connection* conn) const { + return state_ == STATE_COMPLETED && conn != best_connection_ && + conn->active(); +} + // Is the connection in a state for us to even consider pinging the other side? // We consider a connection pingable even if it's not connected because that's // how a TCP connection is kicked into reconnecting on the active side. -bool P2PTransportChannel::IsPingable(Connection* conn) { +bool P2PTransportChannel::IsPingable(Connection* conn, uint32_t now) { const Candidate& remote = conn->remote_candidate(); // We should never get this far with an empty remote ufrag. ASSERT(!remote.username().empty()); @@ -1198,9 +1234,18 @@ bool P2PTransportChannel::IsPingable(Connection* conn) { return false; } - // If the channel is weak, ping all candidates. Otherwise, we only - // want to ping connections that have not timed out on writing. - return weak() || conn->write_state() != Connection::STATE_WRITE_TIMEOUT; + // If the channel is weakly connected, ping all connections. + if (weak()) { + return true; + } + + // Always ping active connections regardless whether the channel is completed + // or not, but backup connections are pinged at a slower rate. + if (IsBackupConnection(conn)) { + return (now >= conn->last_ping_response_received() + + backup_connection_ping_interval_); + } + return conn->active(); } // Returns the next pingable connection to ping. This will be the oldest @@ -1224,7 +1269,7 @@ Connection* P2PTransportChannel::FindNextPingableConnection() { Connection* oldest_needing_triggered_check = nullptr; Connection* oldest = nullptr; for (Connection* conn : connections_) { - if (!IsPingable(conn)) { + if (!IsPingable(conn, now)) { continue; } bool needs_triggered_check = @@ -1291,6 +1336,14 @@ void P2PTransportChannel::OnConnectionStateChange(Connection* connection) { } } + // May stop the allocator session when at least one connection becomes + // strongly connected after starting to get ports. It is not enough to check + // that the connection becomes weakly connected because the connection may be + // changing from (writable, receiving) to (writable, not receiving). + if (!connection->weak()) { + MaybeStopPortAllocatorSessions(); + } + // We have to unroll the stack before doing this because we may be changing // the state of connections while sorting. RequestSort(); @@ -1328,6 +1381,9 @@ void P2PTransportChannel::OnConnectionDestroyed(Connection* connection) { RequestSort(); } + UpdateState(); + // SignalConnectionRemoved should be called after the channel state is + // updated because the receiver of the event may access the channel state. SignalConnectionRemoved(this); } @@ -1368,8 +1424,7 @@ void P2PTransportChannel::OnReadPacket(Connection* connection, } } -void P2PTransportChannel::OnSentPacket(PortInterface* port, - const rtc::SentPacket& sent_packet) { +void P2PTransportChannel::OnSentPacket(const rtc::SentPacket& sent_packet) { ASSERT(worker_thread_ == rtc::Thread::Current()); SignalSentPacket(this, sent_packet); diff --git a/webrtc/p2p/base/p2ptransportchannel.h b/webrtc/p2p/base/p2ptransportchannel.h index 9efb96c42d..f2e9315343 100644 --- a/webrtc/p2p/base/p2ptransportchannel.h +++ b/webrtc/p2p/base/p2ptransportchannel.h @@ -36,6 +36,18 @@ namespace cricket { extern const uint32_t WEAK_PING_DELAY; +struct IceParameters { + std::string ufrag; + std::string pwd; + IceParameters(const std::string& ice_ufrag, const std::string& ice_pwd) + : ufrag(ice_ufrag), pwd(ice_pwd) {} + + bool operator==(const IceParameters& other) { + return ufrag == other.ufrag && pwd == other.pwd; + } + bool operator!=(const IceParameters& other) { return !(*this == other); } +}; + // Adds the port on which the candidate originated. class RemoteCandidate : public Candidate { public: @@ -108,12 +120,12 @@ class P2PTransportChannel : public TransportChannelImpl, bool SetSslRole(rtc::SSLRole role) override { return false; } // Set up the ciphers to use for DTLS-SRTP. - bool SetSrtpCiphers(const std::vector<std::string>& ciphers) override { + bool SetSrtpCryptoSuites(const std::vector<int>& ciphers) override { return false; } // Find out which DTLS-SRTP cipher was negotiated. - bool GetSrtpCryptoSuite(std::string* cipher) override { return false; } + bool GetSrtpCryptoSuite(int* cipher) override { return false; } // Find out which DTLS cipher was negotiated. bool GetSslCipherSuite(int* cipher) override { return false; } @@ -161,12 +173,20 @@ class P2PTransportChannel : public TransportChannelImpl, // Public for unit tests. const std::vector<Connection*>& connections() const { return connections_; } - private: - rtc::Thread* thread() { return worker_thread_; } + // Public for unit tests. PortAllocatorSession* allocator_session() { return allocator_sessions_.back(); } + // Public for unit tests. + const std::vector<RemoteCandidate>& remote_candidates() const { + return remote_candidates_; + } + + private: + rtc::Thread* thread() { return worker_thread_; } + bool IsGettingPorts() { return allocator_session()->IsGettingPorts(); } + // A transport channel is weak if the current best connection is either // not receiving or not writable, or if there is no best connection at all. bool weak() const; @@ -174,10 +194,10 @@ class P2PTransportChannel : public TransportChannelImpl, void RequestSort(); void SortConnections(); void SwitchBestConnectionTo(Connection* conn); - void UpdateChannelState(); - void HandleWritable(); - void HandleNotWritable(); + void UpdateState(); void HandleAllTimedOut(); + void MaybeStopPortAllocatorSessions(); + TransportChannelState ComputeState() const; Connection* GetBestConnectionOnNetwork(rtc::Network* network) const; bool CreateConnections(const Candidate& remote_candidate, @@ -191,7 +211,7 @@ class P2PTransportChannel : public TransportChannelImpl, bool IsDuplicateRemoteCandidate(const Candidate& candidate); void RememberRemoteCandidate(const Candidate& remote_candidate, PortInterface* origin_port); - bool IsPingable(Connection* conn); + bool IsPingable(Connection* conn, uint32_t now); void PingConnection(Connection* conn); void AddAllocatorSession(PortAllocatorSession* session); void AddConnection(Connection* connection); @@ -212,7 +232,7 @@ class P2PTransportChannel : public TransportChannelImpl, void OnConnectionStateChange(Connection* connection); void OnReadPacket(Connection *connection, const char *data, size_t len, const rtc::PacketTime& packet_time); - void OnSentPacket(PortInterface* port, const rtc::SentPacket& sent_packet); + void OnSentPacket(const rtc::SentPacket& sent_packet); void OnReadyToSend(Connection* connection); void OnConnectionDestroyed(Connection *connection); @@ -224,6 +244,25 @@ class P2PTransportChannel : public TransportChannelImpl, void PruneConnections(); Connection* best_nominated_connection() const; + bool IsBackupConnection(Connection* conn) const; + + // Returns the latest remote ICE parameters or nullptr if there are no remote + // ICE parameters yet. + IceParameters* remote_ice() { + return remote_ice_parameters_.empty() ? nullptr + : &remote_ice_parameters_.back(); + } + // Returns the remote IceParameters and generation that match |ufrag| + // if found, and returns nullptr otherwise. + const IceParameters* FindRemoteIceFromUfrag(const std::string& ufrag, + uint32_t* generation); + // Returns the index of the latest remote ICE parameters, or 0 if no remote + // ICE parameters have been received. + uint32_t remote_ice_generation() { + return remote_ice_parameters_.empty() + ? 0 + : static_cast<uint32_t>(remote_ice_parameters_.size() - 1); + } P2PTransport* transport_; PortAllocator* allocator_; @@ -239,25 +278,24 @@ class P2PTransportChannel : public TransportChannelImpl, Connection* pending_best_connection_; std::vector<RemoteCandidate> remote_candidates_; bool sort_dirty_; // indicates whether another sort is needed right now - bool was_writable_; bool had_connection_ = false; // if connections_ has ever been nonempty typedef std::map<rtc::Socket::Option, int> OptionMap; OptionMap options_; std::string ice_ufrag_; std::string ice_pwd_; - std::string remote_ice_ufrag_; - std::string remote_ice_pwd_; + std::vector<IceParameters> remote_ice_parameters_; IceMode remote_ice_mode_; IceRole ice_role_; uint64_t tiebreaker_; - uint32_t remote_candidate_generation_; IceGatheringState gathering_state_; int check_receiving_delay_; int receiving_timeout_; + int backup_connection_ping_interval_; uint32_t last_ping_sent_ms_ = 0; bool gather_continually_ = false; int weak_ping_delay_ = WEAK_PING_DELAY; + TransportChannelState state_ = TransportChannelState::STATE_INIT; RTC_DISALLOW_COPY_AND_ASSIGN(P2PTransportChannel); }; diff --git a/webrtc/p2p/base/p2ptransportchannel_unittest.cc b/webrtc/p2p/base/p2ptransportchannel_unittest.cc index 37cda7c661..90ddd43714 100644 --- a/webrtc/p2p/base/p2ptransportchannel_unittest.cc +++ b/webrtc/p2p/base/p2ptransportchannel_unittest.cc @@ -101,10 +101,12 @@ enum { }; static cricket::IceConfig CreateIceConfig(int receiving_timeout_ms, - bool gather_continually) { + bool gather_continually, + int backup_ping_interval = -1) { cricket::IceConfig config; config.receiving_timeout_ms = receiving_timeout_ms; config.gather_continually = gather_continually; + config.backup_connection_ping_interval = backup_ping_interval; return config; } @@ -650,6 +652,21 @@ class P2PTransportChannelTestBase : public testing::Test, GetEndpoint(endpoint)->save_candidates_ = true; } + // Tcp candidate verification has to be done when they are generated. + void VerifySavedTcpCandidates(int endpoint, const std::string& tcptype) { + for (auto& data : GetEndpoint(endpoint)->saved_candidates_) { + EXPECT_EQ(data->candidate.protocol(), cricket::TCP_PROTOCOL_NAME); + EXPECT_EQ(data->candidate.tcptype(), tcptype); + if (data->candidate.tcptype() == cricket::TCPTYPE_ACTIVE_STR) { + EXPECT_EQ(data->candidate.address().port(), cricket::DISCARD_PORT); + } else if (data->candidate.tcptype() == cricket::TCPTYPE_PASSIVE_STR) { + EXPECT_NE(data->candidate.address().port(), cricket::DISCARD_PORT); + } else { + FAIL() << "Unknown tcptype: " << data->candidate.tcptype(); + } + } + } + void ResumeCandidates(int endpoint) { Endpoint* ed = GetEndpoint(endpoint); std::vector<CandidateData*>::iterator it = ed->saved_candidates_.begin(); @@ -825,12 +842,12 @@ class P2PTransportChannelTest : public P2PTransportChannelTestBase { rtc::SocketAddress(), rtc::SocketAddress(), rtc::SocketAddress())); - cricket::RelayServerConfig relay_server(cricket::RELAY_TURN); - relay_server.credentials = kRelayCredentials; - relay_server.ports.push_back( + cricket::RelayServerConfig turn_server(cricket::RELAY_TURN); + turn_server.credentials = kRelayCredentials; + turn_server.ports.push_back( cricket::ProtocolAddress(kTurnUdpIntAddr, cricket::PROTO_UDP, false)); - GetEndpoint(0)->allocator_->AddRelay(relay_server); - GetEndpoint(1)->allocator_->AddRelay(relay_server); + GetEndpoint(0)->allocator_->AddTurnServer(turn_server); + GetEndpoint(1)->allocator_->AddTurnServer(turn_server); int delay = kMinimumStepDelay; ConfigureEndpoint(0, config1); @@ -1290,8 +1307,19 @@ TEST_F(P2PTransportChannelTest, TestTcpConnectionsFromActiveToPassive) { SetAllowTcpListen(0, true); // actpass. SetAllowTcpListen(1, false); // active. + // Pause candidate so we could verify the candidate properties. + PauseCandidates(0); + PauseCandidates(1); CreateChannels(1); + // Verify tcp candidates. + VerifySavedTcpCandidates(0, cricket::TCPTYPE_PASSIVE_STR); + VerifySavedTcpCandidates(1, cricket::TCPTYPE_ACTIVE_STR); + + // Resume candidates. + ResumeCandidates(0); + ResumeCandidates(1); + EXPECT_TRUE_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() && ep2_ch1()->receiving() && ep2_ch1()->writable(), 1000); @@ -1300,12 +1328,6 @@ TEST_F(P2PTransportChannelTest, TestTcpConnectionsFromActiveToPassive) { LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) && RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1])); - std::string kTcpProtocol = "tcp"; - EXPECT_EQ(kTcpProtocol, RemoteCandidate(ep1_ch1())->protocol()); - EXPECT_EQ(kTcpProtocol, LocalCandidate(ep1_ch1())->protocol()); - EXPECT_EQ(kTcpProtocol, RemoteCandidate(ep2_ch1())->protocol()); - EXPECT_EQ(kTcpProtocol, LocalCandidate(ep2_ch1())->protocol()); - TestSendRecv(1); DestroyChannels(); } @@ -1539,19 +1561,19 @@ TEST_F(P2PTransportChannelMultihomedTest, TestFailoverControlledSide) { // Create channels and let them go writable, as usual. CreateChannels(1); - // Make the receiving timeout shorter for testing. - cricket::IceConfig config = CreateIceConfig(1000, false); - ep1_ch1()->SetIceConfig(config); - ep2_ch1()->SetIceConfig(config); - - EXPECT_TRUE_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() && - ep2_ch1()->receiving() && ep2_ch1()->writable(), - 1000); + EXPECT_TRUE_WAIT_MARGIN(ep1_ch1()->receiving() && ep1_ch1()->writable() && + ep2_ch1()->receiving() && ep2_ch1()->writable(), + 1000, 1000); EXPECT_TRUE( ep1_ch1()->best_connection() && ep2_ch1()->best_connection() && LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) && RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1])); + // Make the receiving timeout shorter for testing. + cricket::IceConfig config = CreateIceConfig(1000, false); + ep1_ch1()->SetIceConfig(config); + ep2_ch1()->SetIceConfig(config); + // Blackhole any traffic to or from the public addrs. LOG(LS_INFO) << "Failing over..."; fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[1]); @@ -1591,18 +1613,19 @@ TEST_F(P2PTransportChannelMultihomedTest, TestFailoverControllingSide) { // Create channels and let them go writable, as usual. CreateChannels(1); - // Make the receiving timeout shorter for testing. - cricket::IceConfig config = CreateIceConfig(1000, false); - ep1_ch1()->SetIceConfig(config); - ep2_ch1()->SetIceConfig(config); - EXPECT_TRUE_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() && - ep2_ch1()->receiving() && ep2_ch1()->writable(), - 1000); + EXPECT_TRUE_WAIT_MARGIN(ep1_ch1()->receiving() && ep1_ch1()->writable() && + ep2_ch1()->receiving() && ep2_ch1()->writable(), + 1000, 1000); EXPECT_TRUE( ep1_ch1()->best_connection() && ep2_ch1()->best_connection() && LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) && RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1])); + // Make the receiving timeout shorter for testing. + cricket::IceConfig config = CreateIceConfig(1000, false); + ep1_ch1()->SetIceConfig(config); + ep2_ch1()->SetIceConfig(config); + // Blackhole any traffic to or from the public addrs. LOG(LS_INFO) << "Failing over..."; fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]); @@ -1627,6 +1650,43 @@ TEST_F(P2PTransportChannelMultihomedTest, TestFailoverControllingSide) { DestroyChannels(); } +// Test that the backup connection is pinged at a rate no faster than +// what was configured. +TEST_F(P2PTransportChannelMultihomedTest, TestPingBackupConnectionRate) { + AddAddress(0, kPublicAddrs[0]); + // Adding alternate address will make sure |kPublicAddrs| has the higher + // priority than others. This is due to FakeNetwork::AddInterface method. + AddAddress(1, kAlternateAddrs[1]); + AddAddress(1, kPublicAddrs[1]); + + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Create channels and let them go writable, as usual. + CreateChannels(1); + EXPECT_TRUE_WAIT_MARGIN(ep1_ch1()->receiving() && ep1_ch1()->writable() && + ep2_ch1()->receiving() && ep2_ch1()->writable(), + 1000, 1000); + int backup_ping_interval = 2000; + ep2_ch1()->SetIceConfig(CreateIceConfig(2000, false, backup_ping_interval)); + // After the state becomes COMPLETED, the backup connection will be pinged + // once every |backup_ping_interval| milliseconds. + ASSERT_TRUE_WAIT(ep2_ch1()->GetState() == cricket::STATE_COMPLETED, 1000); + const std::vector<cricket::Connection*>& connections = + ep2_ch1()->connections(); + ASSERT_EQ(2U, connections.size()); + cricket::Connection* backup_conn = connections[1]; + EXPECT_TRUE_WAIT(backup_conn->writable(), 3000); + uint32_t last_ping_response_ms = backup_conn->last_ping_response_received(); + EXPECT_TRUE_WAIT( + last_ping_response_ms < backup_conn->last_ping_response_received(), 5000); + int time_elapsed = + backup_conn->last_ping_response_received() - last_ping_response_ms; + LOG(LS_INFO) << "Time elapsed: " << time_elapsed; + EXPECT_GE(time_elapsed, backup_ping_interval); +} + TEST_F(P2PTransportChannelMultihomedTest, TestGetState) { AddAddress(0, kAlternateAddrs[0]); AddAddress(0, kPublicAddrs[0]); @@ -1707,12 +1767,14 @@ class P2PTransportChannelPingTest : public testing::Test, cricket::Candidate CreateCandidate(const std::string& ip, int port, - int priority) { + int priority, + const std::string& ufrag = "") { cricket::Candidate c; c.set_address(rtc::SocketAddress(ip, port)); c.set_component(1); c.set_protocol(cricket::UDP_PROTOCOL_NAME); c.set_priority(priority); + c.set_username(ufrag); return c; } @@ -1796,6 +1858,62 @@ TEST_F(P2PTransportChannelPingTest, TestNoTriggeredChecksWhenWritable) { EXPECT_EQ(conn2, ch.FindNextPingableConnection()); } +// Test adding remote candidates with different ufrags. If a remote candidate +// is added with an old ufrag, it will be discarded. If it is added with a +// ufrag that was not seen before, it will be used to create connections +// although the ICE pwd in the remote candidate will be set when the ICE +// credentials arrive. If a remote candidate is added with the current ICE +// ufrag, its pwd and generation will be set properly. +TEST_F(P2PTransportChannelPingTest, TestAddRemoteCandidateWithVariousUfrags) { + cricket::FakePortAllocator pa(rtc::Thread::Current(), nullptr); + cricket::P2PTransportChannel ch("add candidate", 1, nullptr, &pa); + PrepareChannel(&ch); + ch.Connect(); + ch.MaybeStartGathering(); + // Add a candidate with a future ufrag. + ch.AddRemoteCandidate(CreateCandidate("1.1.1.1", 1, 1, kIceUfrag[2])); + cricket::Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + const cricket::Candidate& candidate = conn1->remote_candidate(); + EXPECT_EQ(kIceUfrag[2], candidate.username()); + EXPECT_TRUE(candidate.password().empty()); + EXPECT_TRUE(ch.FindNextPingableConnection() == nullptr); + + // Set the remote credentials with the "future" ufrag. + // This should set the ICE pwd in the remote candidate of |conn1|, making + // it pingable. + ch.SetRemoteIceCredentials(kIceUfrag[2], kIcePwd[2]); + EXPECT_EQ(kIceUfrag[2], candidate.username()); + EXPECT_EQ(kIcePwd[2], candidate.password()); + EXPECT_EQ(conn1, ch.FindNextPingableConnection()); + + // Add a candidate with an old ufrag. No connection will be created. + ch.AddRemoteCandidate(CreateCandidate("2.2.2.2", 2, 2, kIceUfrag[1])); + rtc::Thread::Current()->ProcessMessages(500); + EXPECT_TRUE(GetConnectionTo(&ch, "2.2.2.2", 2) == nullptr); + + // Add a candidate with the current ufrag, its pwd and generation will be + // assigned, even if the generation is not set. + ch.AddRemoteCandidate(CreateCandidate("3.3.3.3", 3, 0, kIceUfrag[2])); + cricket::Connection* conn3 = nullptr; + ASSERT_TRUE_WAIT((conn3 = GetConnectionTo(&ch, "3.3.3.3", 3)) != nullptr, + 3000); + const cricket::Candidate& new_candidate = conn3->remote_candidate(); + EXPECT_EQ(kIcePwd[2], new_candidate.password()); + EXPECT_EQ(1U, new_candidate.generation()); + + // Check that the pwd of all remote candidates are properly assigned. + for (const cricket::RemoteCandidate& candidate : ch.remote_candidates()) { + EXPECT_TRUE(candidate.username() == kIceUfrag[1] || + candidate.username() == kIceUfrag[2]); + if (candidate.username() == kIceUfrag[1]) { + EXPECT_EQ(kIcePwd[1], candidate.password()); + } else if (candidate.username() == kIceUfrag[2]) { + EXPECT_EQ(kIcePwd[2], candidate.password()); + } + } +} + TEST_F(P2PTransportChannelPingTest, ConnectionResurrection) { cricket::FakePortAllocator pa(rtc::Thread::Current(), nullptr); cricket::P2PTransportChannel ch("connection resurrection", 1, nullptr, &pa); @@ -1868,7 +1986,7 @@ TEST_F(P2PTransportChannelPingTest, TestReceivingStateChange) { conn1->ReceivedPing(); conn1->OnReadPacket("ABC", 3, rtc::CreatePacketTime(0)); - EXPECT_TRUE_WAIT(ch.best_connection() != nullptr, 1000) + EXPECT_TRUE_WAIT(ch.best_connection() != nullptr, 1000); EXPECT_TRUE_WAIT(ch.receiving(), 1000); EXPECT_TRUE_WAIT(!ch.receiving(), 1000); } @@ -1932,7 +2050,8 @@ TEST_F(P2PTransportChannelPingTest, TestSelectConnectionBeforeNomination) { // The controlled side will select a connection as the "best connection" based // on requests from an unknown address before the controlling side nominates // a connection, and will nominate a connection from an unknown address if the -// request contains the use_candidate attribute. +// request contains the use_candidate attribute. Plus, it will also sends back +// a ping response and set the ICE pwd in the remote candidate appropriately. TEST_F(P2PTransportChannelPingTest, TestSelectConnectionFromUnknownAddress) { cricket::FakePortAllocator pa(rtc::Thread::Current(), nullptr); cricket::P2PTransportChannel ch("receiving state change", 1, nullptr, &pa); @@ -1948,14 +2067,16 @@ TEST_F(P2PTransportChannelPingTest, TestSelectConnectionFromUnknownAddress) { uint32_t prflx_priority = cricket::ICE_TYPE_PREFERENCE_PRFLX << 24; request.AddAttribute(new cricket::StunUInt32Attribute( cricket::STUN_ATTR_PRIORITY, prflx_priority)); - cricket::Port* port = GetPort(&ch); + cricket::TestUDPPort* port = static_cast<cricket::TestUDPPort*>(GetPort(&ch)); port->SignalUnknownAddress(port, rtc::SocketAddress("1.1.1.1", 1), cricket::PROTO_UDP, &request, kIceUfrag[1], false); cricket::Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); ASSERT_TRUE(conn1 != nullptr); + EXPECT_TRUE(port->sent_binding_response()); EXPECT_EQ(conn1, ch.best_connection()); conn1->ReceivedPingResponse(); EXPECT_EQ(conn1, ch.best_connection()); + port->set_sent_binding_response(false); // Another connection is nominated via use_candidate. ch.AddRemoteCandidate(CreateCandidate("2.2.2.2", 2, 1)); @@ -1977,8 +2098,10 @@ TEST_F(P2PTransportChannelPingTest, TestSelectConnectionFromUnknownAddress) { cricket::PROTO_UDP, &request, kIceUfrag[1], false); cricket::Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3); ASSERT_TRUE(conn3 != nullptr); + EXPECT_TRUE(port->sent_binding_response()); conn3->ReceivedPingResponse(); // Become writable. EXPECT_EQ(conn2, ch.best_connection()); + port->set_sent_binding_response(false); // However if the request contains use_candidate attribute, it will be // selected as the best connection. @@ -1988,10 +2111,23 @@ TEST_F(P2PTransportChannelPingTest, TestSelectConnectionFromUnknownAddress) { cricket::PROTO_UDP, &request, kIceUfrag[1], false); cricket::Connection* conn4 = WaitForConnectionTo(&ch, "4.4.4.4", 4); ASSERT_TRUE(conn4 != nullptr); + EXPECT_TRUE(port->sent_binding_response()); // conn4 is not the best connection yet because it is not writable. EXPECT_EQ(conn2, ch.best_connection()); conn4->ReceivedPingResponse(); // Become writable. EXPECT_EQ(conn4, ch.best_connection()); + + // Test that the request from an unknown address contains a ufrag from an old + // generation. + port->set_sent_binding_response(false); + ch.SetRemoteIceCredentials(kIceUfrag[2], kIcePwd[2]); + ch.SetRemoteIceCredentials(kIceUfrag[3], kIcePwd[3]); + port->SignalUnknownAddress(port, rtc::SocketAddress("5.5.5.5", 5), + cricket::PROTO_UDP, &request, kIceUfrag[2], false); + cricket::Connection* conn5 = WaitForConnectionTo(&ch, "5.5.5.5", 5); + ASSERT_TRUE(conn5 != nullptr); + EXPECT_TRUE(port->sent_binding_response()); + EXPECT_EQ(kIcePwd[2], conn5->remote_candidate().password()); } // The controlled side will select a connection as the "best connection" @@ -2114,7 +2250,9 @@ TEST_F(P2PTransportChannelPingTest, TestGetState) { EXPECT_TRUE_WAIT(conn2->pruned(), 1000); EXPECT_EQ(cricket::TransportChannelState::STATE_COMPLETED, ch.GetState()); conn1->Prune(); // All connections are pruned. - EXPECT_EQ(cricket::TransportChannelState::STATE_FAILED, ch.GetState()); + // Need to wait until the channel state is updated. + EXPECT_EQ_WAIT(cricket::TransportChannelState::STATE_FAILED, ch.GetState(), + 1000); } // Test that when a low-priority connection is pruned, it is not deleted @@ -2190,3 +2328,31 @@ TEST_F(P2PTransportChannelPingTest, TestDeleteConnectionsIfAllWriteTimedout) { conn3->Prune(); EXPECT_TRUE_WAIT(ch.connections().empty(), 1000); } + +// Test that after a port allocator session is started, it will be stopped +// when a new connection becomes writable and receiving. Also test that this +// holds even if the transport channel did not lose the writability. +TEST_F(P2PTransportChannelPingTest, TestStopPortAllocatorSessions) { + cricket::FakePortAllocator pa(rtc::Thread::Current(), nullptr); + cricket::P2PTransportChannel ch("test channel", 1, nullptr, &pa); + PrepareChannel(&ch); + ch.SetIceConfig(CreateIceConfig(2000, false)); + ch.Connect(); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateCandidate("1.1.1.1", 1, 100)); + cricket::Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1); + ASSERT_TRUE(conn1 != nullptr); + conn1->ReceivedPingResponse(); // Becomes writable and receiving + EXPECT_TRUE(!ch.allocator_session()->IsGettingPorts()); + + // Restart gathering even if the transport channel is still writable. + // It should stop getting ports after a new connection becomes strongly + // connected. + ch.SetIceCredentials(kIceUfrag[1], kIcePwd[1]); + ch.MaybeStartGathering(); + ch.AddRemoteCandidate(CreateCandidate("2.2.2.2", 2, 100)); + cricket::Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2); + ASSERT_TRUE(conn2 != nullptr); + conn2->ReceivedPingResponse(); // Becomes writable and receiving + EXPECT_TRUE(!ch.allocator_session()->IsGettingPorts()); +} diff --git a/webrtc/p2p/base/port.cc b/webrtc/p2p/base/port.cc index d34b05f8e9..9dd5c83fed 100644 --- a/webrtc/p2p/base/port.cc +++ b/webrtc/p2p/base/port.cc @@ -310,10 +310,6 @@ void Port::OnReadPacket( } } -void Port::OnSentPacket(const rtc::SentPacket& sent_packet) { - PortInterface::SignalSentPacket(this, sent_packet); -} - void Port::OnReadyToSend() { AddressMap::iterator iter = connections_.begin(); for (; iter != connections_.end(); ++iter) { @@ -567,10 +563,6 @@ void Port::SendBindingResponse(StunMessage* request, response.AddMessageIntegrity(password_); response.AddFingerprint(); - // The fact that we received a successful request means that this connection - // (if one exists) should now be receiving. - Connection* conn = GetConnection(addr); - // Send the response message. rtc::ByteBuffer buf; response.Write(&buf); @@ -585,6 +577,7 @@ void Port::SendBindingResponse(StunMessage* request, } else { // Log at LS_INFO if we send a stun ping response on an unwritable // connection. + Connection* conn = GetConnection(addr); rtc::LoggingSeverity sev = (conn && !conn->writable()) ? rtc::LS_INFO : rtc::LS_VERBOSE; LOG_JV(sev, this) @@ -592,10 +585,6 @@ void Port::SendBindingResponse(StunMessage* request, << ", to=" << addr.ToSensitiveString() << ", id=" << rtc::hex_encode(response.transaction_id()); } - - ASSERT(conn != NULL); - if (conn) - conn->ReceivedPing(); } void Port::SendBindingErrorResponse(StunMessage* request, @@ -924,29 +913,7 @@ void Connection::OnReadPacket( << ", id=" << rtc::hex_encode(msg->transaction_id()); if (remote_ufrag == remote_candidate_.username()) { - // Check for role conflicts. - if (!port_->MaybeIceRoleConflict(addr, msg.get(), remote_ufrag)) { - // Received conflicting role from the peer. - LOG(LS_INFO) << "Received conflicting role from the peer."; - return; - } - - // Incoming, validated stun request from remote peer. - // This call will also set the connection receiving. - port_->SendBindingResponse(msg.get(), addr); - - // If timed out sending writability checks, start up again - if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT)) - set_write_state(STATE_WRITE_INIT); - - if (port_->GetIceRole() == ICEROLE_CONTROLLED) { - const StunByteStringAttribute* use_candidate_attr = - msg->GetByteString(STUN_ATTR_USE_CANDIDATE); - if (use_candidate_attr) { - set_nominated(true); - SignalNominated(this); - } - } + HandleBindingRequest(msg.get()); } else { // The packet had the right local username, but the remote username // was not the right one for the remote address. @@ -986,6 +953,37 @@ void Connection::OnReadPacket( } } +void Connection::HandleBindingRequest(IceMessage* msg) { + // This connection should now be receiving. + ReceivedPing(); + + const rtc::SocketAddress& remote_addr = remote_candidate_.address(); + const std::string& remote_ufrag = remote_candidate_.username(); + // Check for role conflicts. + if (!port_->MaybeIceRoleConflict(remote_addr, msg, remote_ufrag)) { + // Received conflicting role from the peer. + LOG(LS_INFO) << "Received conflicting role from the peer."; + return; + } + + // This is a validated stun request from remote peer. + port_->SendBindingResponse(msg, remote_addr); + + // If it timed out on writing check, start up again + if (!pruned_ && write_state_ == STATE_WRITE_TIMEOUT) { + set_write_state(STATE_WRITE_INIT); + } + + if (port_->GetIceRole() == ICEROLE_CONTROLLED) { + const StunByteStringAttribute* use_candidate_attr = + msg->GetByteString(STUN_ATTR_USE_CANDIDATE); + if (use_candidate_attr) { + set_nominated(true); + SignalNominated(this); + } + } +} + void Connection::OnReadyToSend() { if (write_state_ == STATE_WRITABLE) { SignalReadyToSend(this); @@ -1006,6 +1004,11 @@ void Connection::Destroy() { port_->thread()->Post(this, MSG_DELETE); } +void Connection::FailAndDestroy() { + set_state(Connection::STATE_FAILED); + Destroy(); +} + void Connection::PrintPingsSinceLastResponse(std::string* s, size_t max) { std::ostringstream oss; oss << std::boolalpha; @@ -1117,25 +1120,28 @@ void Connection::ReceivedPingResponse() { } bool Connection::dead(uint32_t now) const { - if (now < (time_created_ms_ + MIN_CONNECTION_LIFETIME)) { - // A connection that hasn't passed its minimum lifetime is still alive. - // We do this to prevent connections from being pruned too quickly - // during a network change event when two networks would be up - // simultaneously but only for a brief period. - return false; + if (last_received() > 0) { + // If it has ever received anything, we keep it alive until it hasn't + // received anything for DEAD_CONNECTION_RECEIVE_TIMEOUT. This covers the + // normal case of a successfully used connection that stops working. This + // also allows a remote peer to continue pinging over a locally inactive + // (pruned) connection. + return (now > (last_received() + DEAD_CONNECTION_RECEIVE_TIMEOUT)); } - if (receiving_) { - // A connection that is receiving is alive. + if (active()) { + // If it has never received anything, keep it alive as long as it is + // actively pinging and not pruned. Otherwise, the connection might be + // deleted before it has a chance to ping. This is the normal case for a + // new connection that is pinging but hasn't received anything yet. return false; } - // A connection is alive until it is inactive. - return !active(); - - // TODO(honghaiz): Move from using the write state to using the receiving - // state with something like the following: - // return (now > (last_received() + DEAD_CONNECTION_RECEIVE_TIMEOUT)); + // If it has never received anything and is not actively pinging (pruned), we + // keep it around for at least MIN_CONNECTION_LIFETIME to prevent connections + // from being pruned too quickly during a network change event when two + // networks would be up simultaneously but only for a brief period. + return now > (time_created_ms_ + MIN_CONNECTION_LIFETIME); } std::string Connection::ToDebugId() const { @@ -1248,8 +1254,7 @@ void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request, // This is not a valid connection. LOG_J(LS_ERROR, this) << "Received STUN error response, code=" << error_code << "; killing connection"; - set_state(STATE_FAILED); - Destroy(); + FailAndDestroy(); } } @@ -1302,13 +1307,13 @@ void Connection::OnMessage(rtc::Message *pmsg) { delete this; } -uint32_t Connection::last_received() { +uint32_t Connection::last_received() const { return std::max(last_data_received_, std::max(last_ping_received_, last_ping_response_received_)); } size_t Connection::recv_bytes_second() { - return recv_rate_tracker_.ComputeRate(); + return round(recv_rate_tracker_.ComputeRate()); } size_t Connection::recv_total_bytes() { @@ -1316,7 +1321,7 @@ size_t Connection::recv_total_bytes() { } size_t Connection::sent_bytes_second() { - return send_rate_tracker_.ComputeRate(); + return round(send_rate_tracker_.ComputeRate()); } size_t Connection::sent_total_bytes() { @@ -1396,10 +1401,10 @@ void Connection::MaybeAddPrflxCandidate(ConnectionRequest* request, SignalStateChange(this); } -ProxyConnection::ProxyConnection(Port* port, size_t index, - const Candidate& candidate) - : Connection(port, index, candidate), error_(0) { -} +ProxyConnection::ProxyConnection(Port* port, + size_t index, + const Candidate& remote_candidate) + : Connection(port, index, remote_candidate) {} int ProxyConnection::Send(const void* data, size_t size, const rtc::PacketOptions& options) { diff --git a/webrtc/p2p/base/port.h b/webrtc/p2p/base/port.h index 01c45f26d8..436b1e7faa 100644 --- a/webrtc/p2p/base/port.h +++ b/webrtc/p2p/base/port.h @@ -54,6 +54,10 @@ extern const char TCPTYPE_SIMOPEN_STR[]; // it. const uint32_t MIN_CONNECTION_LIFETIME = 10 * 1000; // 10 seconds. +// A connection will be declared dead if it has not received anything for this +// long. +const uint32_t DEAD_CONNECTION_RECEIVE_TIMEOUT = 30 * 1000; // 30 seconds. + // The timeout duration when a connection does not receive anything. const uint32_t WEAK_CONNECTION_RECEIVE_TIMEOUT = 2500; // 2.5 seconds @@ -276,7 +280,11 @@ class Port : public PortInterface, public rtc::MessageHandler, const std::string& remote_ufrag); // Called when a packet has been sent to the socket. - void OnSentPacket(const rtc::SentPacket& sent_packet); + // This is made pure virtual to notify subclasses of Port that they MUST + // listen to AsyncPacketSocket::SignalSentPacket and then call + // PortInterface::OnSentPacket. + virtual void OnSentPacket(rtc::AsyncPacketSocket* socket, + const rtc::SentPacket& sent_packet) = 0; // Called when the socket is currently able to send. void OnReadyToSend(); @@ -442,7 +450,6 @@ class Connection : public rtc::MessageHandler, bool connected() const { return connected_; } bool weak() const { return !(writable() && receiving() && connected()); } bool active() const { - // TODO(honghaiz): Move from using |write_state_| to using |pruned_|. return write_state_ != STATE_WRITE_TIMEOUT; } // A connection is dead if it can be safely deleted. @@ -510,6 +517,9 @@ class Connection : public rtc::MessageHandler, // Makes the connection go away. void Destroy(); + // Makes the connection go away, in a failed state. + void FailAndDestroy(); + // Checks that the state of this connection is up-to-date. The argument is // the current time, which is compared against various timeouts. void UpdateState(uint32_t now); @@ -518,11 +528,16 @@ class Connection : public rtc::MessageHandler, uint32_t last_ping_sent() const { return last_ping_sent_; } void Ping(uint32_t now); void ReceivedPingResponse(); + uint32_t last_ping_response_received() const { + return last_ping_response_received_; + } // Called whenever a valid ping is received on this connection. This is // public because the connection intercepts the first ping for us. uint32_t last_ping_received() const { return last_ping_received_; } void ReceivedPing(); + // Handles the binding request; sends a response if this is a valid request. + void HandleBindingRequest(IceMessage* msg); // Debugging description of this connection std::string ToDebugId() const; @@ -557,7 +572,7 @@ class Connection : public rtc::MessageHandler, // Returns the last received time of any data, stun request, or stun // response in milliseconds - uint32_t last_received(); + uint32_t last_received() const; protected: enum { MSG_DELETE = 0, MSG_FIRST_AVAILABLE }; @@ -628,17 +643,18 @@ class Connection : public rtc::MessageHandler, friend class ConnectionRequest; }; -// ProxyConnection defers all the interesting work to the port +// ProxyConnection defers all the interesting work to the port. class ProxyConnection : public Connection { public: - ProxyConnection(Port* port, size_t index, const Candidate& candidate); + ProxyConnection(Port* port, size_t index, const Candidate& remote_candidate); - virtual int Send(const void* data, size_t size, - const rtc::PacketOptions& options); - virtual int GetError() { return error_; } + int Send(const void* data, + size_t size, + const rtc::PacketOptions& options) override; + int GetError() override { return error_; } private: - int error_; + int error_ = 0; }; } // namespace cricket diff --git a/webrtc/p2p/base/port_unittest.cc b/webrtc/p2p/base/port_unittest.cc index 4a4ed32456..449021ad9f 100644 --- a/webrtc/p2p/base/port_unittest.cc +++ b/webrtc/p2p/base/port_unittest.cc @@ -17,6 +17,7 @@ #include "webrtc/p2p/base/testturnserver.h" #include "webrtc/p2p/base/transport.h" #include "webrtc/p2p/base/turnport.h" +#include "webrtc/base/arraysize.h" #include "webrtc/base/crc32.h" #include "webrtc/base/gunit.h" #include "webrtc/base/helpers.h" @@ -140,6 +141,10 @@ class TestPort : public Port { ICE_TYPE_PREFERENCE_HOST, 0, true); } + virtual bool SupportsProtocol(const std::string& protocol) const { + return true; + } + // Exposed for testing candidate building. void AddCandidateAddress(const rtc::SocketAddress& addr) { AddAddress(addr, addr, rtc::SocketAddress(), "udp", "", "", Type(), @@ -199,9 +204,13 @@ class TestPort : public Port { } private: + void OnSentPacket(rtc::AsyncPacketSocket* socket, + const rtc::SentPacket& sent_packet) { + PortInterface::SignalSentPacket(sent_packet); + } rtc::scoped_ptr<ByteBuffer> last_stun_buf_; rtc::scoped_ptr<IceMessage> last_stun_msg_; - int type_preference_; + int type_preference_ = 0; }; class TestChannel : public sigslot::has_slots<> { @@ -456,9 +465,8 @@ class PortTest : public testing::Test, public sigslot::has_slots<> { } UDPPort* CreateUdpPort(const SocketAddress& addr, PacketSocketFactory* socket_factory) { - return UDPPort::Create(main_, socket_factory, &network_, - addr.ipaddr(), 0, 0, username_, password_, - std::string(), false); + return UDPPort::Create(main_, socket_factory, &network_, addr.ipaddr(), 0, + 0, username_, password_, std::string(), true); } TCPPort* CreateTcpPort(const SocketAddress& addr) { return CreateTcpPort(addr, &socket_factory_); @@ -1235,6 +1243,58 @@ TEST_F(PortTest, TestSslTcpToSslTcpRelay) { } */ +// Test that a connection will be dead and deleted if +// i) it has never received anything for MIN_CONNECTION_LIFETIME milliseconds +// since it was created, or +// ii) it has not received anything for DEAD_CONNECTION_RECEIVE_TIMEOUT +// milliseconds since last receiving. +TEST_F(PortTest, TestConnectionDead) { + UDPPort* port1 = CreateUdpPort(kLocalAddr1); + UDPPort* port2 = CreateUdpPort(kLocalAddr2); + TestChannel ch1(port1); + TestChannel ch2(port2); + // Acquire address. + ch1.Start(); + ch2.Start(); + ASSERT_EQ_WAIT(1, ch1.complete_count(), kTimeout); + ASSERT_EQ_WAIT(1, ch2.complete_count(), kTimeout); + + // Test case that the connection has never received anything. + uint32_t before_created = rtc::Time(); + ch1.CreateConnection(GetCandidate(port2)); + uint32_t after_created = rtc::Time(); + Connection* conn = ch1.conn(); + ASSERT(conn != nullptr); + // It is not dead if it is after MIN_CONNECTION_LIFETIME but not pruned. + conn->UpdateState(after_created + MIN_CONNECTION_LIFETIME + 1); + rtc::Thread::Current()->ProcessMessages(0); + EXPECT_TRUE(ch1.conn() != nullptr); + // It is not dead if it is before MIN_CONNECTION_LIFETIME and pruned. + conn->UpdateState(before_created + MIN_CONNECTION_LIFETIME - 1); + conn->Prune(); + rtc::Thread::Current()->ProcessMessages(0); + EXPECT_TRUE(ch1.conn() != nullptr); + // It will be dead after MIN_CONNECTION_LIFETIME and pruned. + conn->UpdateState(after_created + MIN_CONNECTION_LIFETIME + 1); + EXPECT_TRUE_WAIT(ch1.conn() == nullptr, kTimeout); + + // Test case that the connection has received something. + // Create a connection again and receive a ping. + ch1.CreateConnection(GetCandidate(port2)); + conn = ch1.conn(); + ASSERT(conn != nullptr); + uint32_t before_last_receiving = rtc::Time(); + conn->ReceivedPing(); + uint32_t after_last_receiving = rtc::Time(); + // The connection will be dead after DEAD_CONNECTION_RECEIVE_TIMEOUT + conn->UpdateState( + before_last_receiving + DEAD_CONNECTION_RECEIVE_TIMEOUT - 1); + rtc::Thread::Current()->ProcessMessages(100); + EXPECT_TRUE(ch1.conn() != nullptr); + conn->UpdateState(after_last_receiving + DEAD_CONNECTION_RECEIVE_TIMEOUT + 1); + EXPECT_TRUE_WAIT(ch1.conn() == nullptr, kTimeout); +} + // This test case verifies standard ICE features in STUN messages. Currently it // verifies Message Integrity attribute in STUN messages and username in STUN // binding request will have colon (":") between remote and local username. @@ -2224,7 +2284,7 @@ TEST_F(PortTest, TestWritableState) { // Data should be unsendable until the connection is accepted. char data[] = "abcd"; - int data_size = ARRAY_SIZE(data); + int data_size = arraysize(data); rtc::PacketOptions options; EXPECT_EQ(SOCKET_ERROR, ch1.conn()->Send(data, data_size, options)); @@ -2450,3 +2510,24 @@ TEST_F(PortTest, TestControlledToControllingNotDestroyed) { rtc::Thread::Current()->ProcessMessages(kTimeout); EXPECT_FALSE(destroyed()); } + +TEST_F(PortTest, TestSupportsProtocol) { + rtc::scoped_ptr<Port> udp_port(CreateUdpPort(kLocalAddr1)); + EXPECT_TRUE(udp_port->SupportsProtocol(UDP_PROTOCOL_NAME)); + EXPECT_FALSE(udp_port->SupportsProtocol(TCP_PROTOCOL_NAME)); + + rtc::scoped_ptr<Port> stun_port( + CreateStunPort(kLocalAddr1, nat_socket_factory1())); + EXPECT_TRUE(stun_port->SupportsProtocol(UDP_PROTOCOL_NAME)); + EXPECT_FALSE(stun_port->SupportsProtocol(TCP_PROTOCOL_NAME)); + + rtc::scoped_ptr<Port> tcp_port(CreateTcpPort(kLocalAddr1)); + EXPECT_TRUE(tcp_port->SupportsProtocol(TCP_PROTOCOL_NAME)); + EXPECT_TRUE(tcp_port->SupportsProtocol(SSLTCP_PROTOCOL_NAME)); + EXPECT_FALSE(tcp_port->SupportsProtocol(UDP_PROTOCOL_NAME)); + + rtc::scoped_ptr<Port> turn_port( + CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP)); + EXPECT_TRUE(turn_port->SupportsProtocol(UDP_PROTOCOL_NAME)); + EXPECT_FALSE(turn_port->SupportsProtocol(TCP_PROTOCOL_NAME)); +} diff --git a/webrtc/p2p/base/portallocator.h b/webrtc/p2p/base/portallocator.h index 4f8ec2fbe6..6fb79b065e 100644 --- a/webrtc/p2p/base/portallocator.h +++ b/webrtc/p2p/base/portallocator.h @@ -14,6 +14,7 @@ #include <string> #include <vector> +#include "webrtc/p2p/base/port.h" #include "webrtc/p2p/base/portinterface.h" #include "webrtc/base/helpers.h" #include "webrtc/base/proxyinfo.h" @@ -46,10 +47,14 @@ enum { PORTALLOCATOR_ENABLE_SHARED_UFRAG = 0x80, PORTALLOCATOR_ENABLE_SHARED_SOCKET = 0x100, PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE = 0x200, + // When specified, we'll only allocate the STUN candidate for the public + // interface as seen by regular http traffic and the HOST candidate associated + // with the default local interface. PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION = 0x400, - // When specified, a loopback candidate will be generated if - // PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION is specified. - PORTALLOCATOR_ENABLE_LOCALHOST_CANDIDATE = 0x800, + // When specified along with PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION, the + // default local candidate mentioned above will not be allocated. Only the + // STUN candidate will be. + PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE = 0x800, // Disallow use of UDP when connecting to a relay server. Since proxy servers // usually don't handle UDP, using UDP will leak the IP address. PORTALLOCATOR_DISABLE_UDP_RELAY = 0x1000, @@ -71,6 +76,38 @@ enum { CF_ALL = 0x7, }; +// TODO(deadbeef): Rename to TurnCredentials (and username to ufrag). +struct RelayCredentials { + RelayCredentials() {} + RelayCredentials(const std::string& username, const std::string& password) + : username(username), password(password) {} + + std::string username; + std::string password; +}; + +typedef std::vector<ProtocolAddress> PortList; +// TODO(deadbeef): Rename to TurnServerConfig. +struct RelayServerConfig { + RelayServerConfig(RelayType type) : type(type), priority(0) {} + + RelayServerConfig(const std::string& address, + int port, + const std::string& username, + const std::string& password, + ProtocolType proto, + bool secure) + : type(RELAY_TURN), credentials(username, password) { + ports.push_back( + ProtocolAddress(rtc::SocketAddress(address, port), proto, secure)); + } + + RelayType type; + PortList ports; + RelayCredentials credentials; + int priority; +}; + class PortAllocatorSession : public sigslot::has_slots<> { public: // Content name passed in mostly for logging and debugging. @@ -137,6 +174,18 @@ class PortAllocator : public sigslot::has_slots<> { } virtual ~PortAllocator() {} + // Set STUN and TURN servers to be used in future sessions. + virtual void SetIceServers( + const ServerAddresses& stun_servers, + const std::vector<RelayServerConfig>& turn_servers) = 0; + + // Sets the network types to ignore. + // Values are defined by the AdapterType enum. + // For instance, calling this with + // ADAPTER_TYPE_ETHERNET | ADAPTER_TYPE_LOOPBACK will ignore Ethernet and + // loopback interfaces. + virtual void SetNetworkIgnoreMask(int network_ignore_mask) = 0; + PortAllocatorSession* CreateSession( const std::string& sid, const std::string& content_name, diff --git a/webrtc/p2p/base/portinterface.h b/webrtc/p2p/base/portinterface.h index 0f77036ac1..e83879f3b7 100644 --- a/webrtc/p2p/base/portinterface.h +++ b/webrtc/p2p/base/portinterface.h @@ -53,6 +53,8 @@ class PortInterface { virtual bool SharedSocket() const = 0; + virtual bool SupportsProtocol(const std::string& protocol) const = 0; + // PrepareAddress will attempt to get an address for this port that other // clients can send to. It may take some time before the address is ready. // Once it is ready, we will send SignalAddressReady. If errors are @@ -114,7 +116,7 @@ class PortInterface { const rtc::SocketAddress&> SignalReadPacket; // Emitted each time a packet is sent on this port. - sigslot::signal2<PortInterface*, const rtc::SentPacket&> SignalSentPacket; + sigslot::signal1<const rtc::SentPacket&> SignalSentPacket; virtual std::string ToString() const = 0; diff --git a/webrtc/p2p/base/pseudotcp.cc b/webrtc/p2p/base/pseudotcp.cc index 5f035ca652..6281315dc1 100644 --- a/webrtc/p2p/base/pseudotcp.cc +++ b/webrtc/p2p/base/pseudotcp.cc @@ -16,6 +16,7 @@ #include <algorithm> #include <set> +#include "webrtc/base/arraysize.h" #include "webrtc/base/basictypes.h" #include "webrtc/base/bytebuffer.h" #include "webrtc/base/byteorder.h" @@ -187,7 +188,7 @@ void ReportStats() { char buffer[256]; size_t len = 0; for (int i = 0; i < S_NUM_STATS; ++i) { - len += rtc::sprintfn(buffer, ARRAY_SIZE(buffer), "%s%s:%d", + len += rtc::sprintfn(buffer, arraysize(buffer), "%s%s:%d", (i == 0) ? "" : ",", STAT_NAMES[i], g_stats[i]); g_stats[i] = 0; } diff --git a/webrtc/p2p/base/relayport.cc b/webrtc/p2p/base/relayport.cc index 88adcf2f88..19883a3121 100644 --- a/webrtc/p2p/base/relayport.cc +++ b/webrtc/p2p/base/relayport.cc @@ -754,7 +754,7 @@ void RelayEntry::OnReadPacket( void RelayEntry::OnSentPacket(rtc::AsyncPacketSocket* socket, const rtc::SentPacket& sent_packet) { - port_->OnSentPacket(sent_packet); + port_->OnSentPacket(socket, sent_packet); } void RelayEntry::OnReadyToSend(rtc::AsyncPacketSocket* socket) { diff --git a/webrtc/p2p/base/relayport.h b/webrtc/p2p/base/relayport.h index 8452b5b430..402736c34d 100644 --- a/webrtc/p2p/base/relayport.h +++ b/webrtc/p2p/base/relayport.h @@ -29,7 +29,7 @@ class RelayConnection; // is created. The RelayEntry will try to reach the remote destination // by connecting to all available server addresses in a pre defined // order with a small delay in between. When a connection is -// successful all other connection attemts are aborted. +// successful all other connection attempts are aborted. class RelayPort : public Port { public: typedef std::pair<rtc::Socket::Option, int> OptionValue; @@ -46,7 +46,7 @@ class RelayPort : public Port { return new RelayPort(thread, factory, network, ip, min_port, max_port, username, password); } - virtual ~RelayPort(); + ~RelayPort() override; void AddServerAddress(const ProtocolAddress& addr); void AddExternalAddress(const ProtocolAddress& addr); @@ -54,12 +54,16 @@ class RelayPort : public Port { const std::vector<OptionValue>& options() const { return options_; } bool HasMagicCookie(const char* data, size_t size); - virtual void PrepareAddress(); - virtual Connection* CreateConnection(const Candidate& address, - CandidateOrigin origin); - virtual int SetOption(rtc::Socket::Option opt, int value); - virtual int GetOption(rtc::Socket::Option opt, int* value); - virtual int GetError(); + void PrepareAddress() override; + Connection* CreateConnection(const Candidate& address, + CandidateOrigin origin) override; + int SetOption(rtc::Socket::Option opt, int value) override; + int GetOption(rtc::Socket::Option opt, int* value) override; + int GetError() override; + bool SupportsProtocol(const std::string& protocol) const override { + // Relay port may create both TCP and UDP connections. + return true; + } const ProtocolAddress * ServerAddress(size_t index) const; bool IsReady() { return ready_; } @@ -81,10 +85,11 @@ class RelayPort : public Port { void SetReady(); - virtual int SendTo(const void* data, size_t size, - const rtc::SocketAddress& addr, - const rtc::PacketOptions& options, - bool payload); + int SendTo(const void* data, + size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketOptions& options, + bool payload) override; // Dispatches the given packet to the port or connection as appropriate. void OnReadPacket(const char* data, size_t size, @@ -92,6 +97,11 @@ class RelayPort : public Port { ProtocolType proto, const rtc::PacketTime& packet_time); + // The OnSentPacket callback is left empty here since they are handled by + // RelayEntry. + void OnSentPacket(rtc::AsyncPacketSocket* socket, + const rtc::SentPacket& sent_packet) override {} + private: friend class RelayEntry; diff --git a/webrtc/p2p/base/stun_unittest.cc b/webrtc/p2p/base/stun_unittest.cc index cd4f7e1cbb..12492570c4 100644 --- a/webrtc/p2p/base/stun_unittest.cc +++ b/webrtc/p2p/base/stun_unittest.cc @@ -11,6 +11,7 @@ #include <string> #include "webrtc/p2p/base/stun.h" +#include "webrtc/base/arraysize.h" #include "webrtc/base/bytebuffer.h" #include "webrtc/base/gunit.h" #include "webrtc/base/logging.h" @@ -515,11 +516,11 @@ TEST_F(StunTest, MessageTypes) { STUN_BINDING_REQUEST, STUN_BINDING_INDICATION, STUN_BINDING_RESPONSE, STUN_BINDING_ERROR_RESPONSE }; - for (int i = 0; i < ARRAY_SIZE(types); ++i) { - EXPECT_EQ(i == 0, IsStunRequestType(types[i])); - EXPECT_EQ(i == 1, IsStunIndicationType(types[i])); - EXPECT_EQ(i == 2, IsStunSuccessResponseType(types[i])); - EXPECT_EQ(i == 3, IsStunErrorResponseType(types[i])); + for (size_t i = 0; i < arraysize(types); ++i) { + EXPECT_EQ(i == 0U, IsStunRequestType(types[i])); + EXPECT_EQ(i == 1U, IsStunIndicationType(types[i])); + EXPECT_EQ(i == 2U, IsStunSuccessResponseType(types[i])); + EXPECT_EQ(i == 3U, IsStunErrorResponseType(types[i])); EXPECT_EQ(1, types[i] & 0xFEEF); } } diff --git a/webrtc/p2p/base/stunport.cc b/webrtc/p2p/base/stunport.cc index 1598fe43ce..8f37dd5218 100644 --- a/webrtc/p2p/base/stunport.cc +++ b/webrtc/p2p/base/stunport.cc @@ -13,6 +13,7 @@ #include "webrtc/p2p/base/common.h" #include "webrtc/p2p/base/portallocator.h" #include "webrtc/p2p/base/stun.h" +#include "webrtc/base/checks.h" #include "webrtc/base/common.h" #include "webrtc/base/helpers.h" #include "webrtc/base/ipaddress.h" @@ -23,15 +24,19 @@ namespace cricket { // TODO: Move these to a common place (used in relayport too) const int KEEPALIVE_DELAY = 10 * 1000; // 10 seconds - sort timeouts -const int RETRY_DELAY = 50; // 50ms, from ICE spec const int RETRY_TIMEOUT = 50 * 1000; // ICE says 50 secs +// Stop sending STUN binding requests after this amount of time +// (in milliseconds) because the connection binding requests should keep +// the NAT binding alive. +const int KEEP_ALIVE_TIMEOUT = 2 * 60 * 1000; // 2 minutes // Handles a binding request sent to the STUN server. class StunBindingRequest : public StunRequest { public: - StunBindingRequest(UDPPort* port, bool keep_alive, - const rtc::SocketAddress& addr) - : port_(port), keep_alive_(keep_alive), server_addr_(addr) { + StunBindingRequest(UDPPort* port, + const rtc::SocketAddress& addr, + uint32_t deadline) + : port_(port), server_addr_(addr), deadline_(deadline) { start_time_ = rtc::Time(); } @@ -58,10 +63,10 @@ class StunBindingRequest : public StunRequest { } // We will do a keep-alive regardless of whether this request succeeds. - // This should have almost no impact on network usage. - if (keep_alive_) { + // It will be stopped after |deadline_| mostly to conserve the battery life. + if (rtc::Time() <= deadline_) { port_->requests_.SendDelayed( - new StunBindingRequest(port_, true, server_addr_), + new StunBindingRequest(port_, server_addr_, deadline_), port_->stun_keepalive_delay()); } } @@ -79,10 +84,10 @@ class StunBindingRequest : public StunRequest { port_->OnStunBindingOrResolveRequestFailed(server_addr_); - if (keep_alive_ - && (rtc::TimeSince(start_time_) <= RETRY_TIMEOUT)) { + uint32_t now = rtc::Time(); + if (now <= deadline_ && rtc::TimeDiff(now, start_time_) <= RETRY_TIMEOUT) { port_->requests_.SendDelayed( - new StunBindingRequest(port_, true, server_addr_), + new StunBindingRequest(port_, server_addr_, deadline_), port_->stun_keepalive_delay()); } } @@ -93,20 +98,13 @@ class StunBindingRequest : public StunRequest { << " (" << port_->Network()->name() << ")"; port_->OnStunBindingOrResolveRequestFailed(server_addr_); - - if (keep_alive_ - && (rtc::TimeSince(start_time_) <= RETRY_TIMEOUT)) { - port_->requests_.SendDelayed( - new StunBindingRequest(port_, true, server_addr_), - RETRY_DELAY); - } } private: UDPPort* port_; - bool keep_alive_; const rtc::SocketAddress server_addr_; uint32_t start_time_; + uint32_t deadline_; }; UDPPort::AddressResolver::AddressResolver( @@ -116,7 +114,10 @@ UDPPort::AddressResolver::AddressResolver( UDPPort::AddressResolver::~AddressResolver() { for (ResolverMap::iterator it = resolvers_.begin(); it != resolvers_.end(); ++it) { - it->second->Destroy(true); + // TODO(guoweis): Change to asynchronous DNS resolution to prevent the hang + // when passing true to the Destroy() which is a safer way to avoid the code + // unloaded before the thread exits. Please see webrtc bug 5139. + it->second->Destroy(false); } } @@ -166,15 +167,19 @@ UDPPort::UDPPort(rtc::Thread* thread, const std::string& username, const std::string& password, const std::string& origin, - bool emit_localhost_for_anyaddress) - : Port(thread, factory, network, socket->GetLocalAddress().ipaddr(), - username, password), + bool emit_local_for_anyaddress) + : Port(thread, + factory, + network, + socket->GetLocalAddress().ipaddr(), + username, + password), requests_(thread), socket_(socket), error_(0), ready_(false), stun_keepalive_delay_(KEEPALIVE_DELAY), - emit_localhost_for_anyaddress_(emit_localhost_for_anyaddress) { + emit_local_for_anyaddress_(emit_local_for_anyaddress) { requests_.set_origin(origin); } @@ -187,7 +192,7 @@ UDPPort::UDPPort(rtc::Thread* thread, const std::string& username, const std::string& password, const std::string& origin, - bool emit_localhost_for_anyaddress) + bool emit_local_for_anyaddress) : Port(thread, LOCAL_PORT_TYPE, factory, @@ -202,7 +207,7 @@ UDPPort::UDPPort(rtc::Thread* thread, error_(0), ready_(false), stun_keepalive_delay_(KEEPALIVE_DELAY), - emit_localhost_for_anyaddress_(emit_localhost_for_anyaddress) { + emit_local_for_anyaddress_(emit_local_for_anyaddress) { requests_.set_origin(origin); } @@ -248,9 +253,10 @@ void UDPPort::MaybePrepareStunCandidate() { } Connection* UDPPort::CreateConnection(const Candidate& address, - CandidateOrigin origin) { - if (address.protocol() != "udp") + CandidateOrigin origin) { + if (!SupportsProtocol(address.protocol())) { return NULL; + } if (!IsCompatibleAddress(address.address())) { return NULL; @@ -294,25 +300,27 @@ int UDPPort::GetError() { void UDPPort::OnLocalAddressReady(rtc::AsyncPacketSocket* socket, const rtc::SocketAddress& address) { // When adapter enumeration is disabled and binding to the any address, the - // loopback address will be issued as a candidate instead if - // |emit_localhost_for_anyaddress| is true. This is to allow connectivity on - // demo pages without STUN/TURN to work. + // default local address will be issued as a candidate instead if + // |emit_local_for_anyaddress| is true. This is to allow connectivity for + // applications which absolutely requires a HOST candidate. rtc::SocketAddress addr = address; - if (addr.IsAnyIP() && emit_localhost_for_anyaddress_) { - addr.SetIP(rtc::GetLoopbackIP(addr.family())); - } + + // If MaybeSetDefaultLocalAddress fails, we keep the "any" IP so that at + // least the port is listening. + MaybeSetDefaultLocalAddress(&addr); AddAddress(addr, addr, rtc::SocketAddress(), UDP_PROTOCOL_NAME, "", "", LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST, 0, false); MaybePrepareStunCandidate(); } -void UDPPort::OnReadPacket( - rtc::AsyncPacketSocket* socket, const char* data, size_t size, - const rtc::SocketAddress& remote_addr, - const rtc::PacketTime& packet_time) { +void UDPPort::OnReadPacket(rtc::AsyncPacketSocket* socket, + const char* data, + size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { ASSERT(socket == socket_); - ASSERT(!remote_addr.IsUnresolved()); + ASSERT(!remote_addr.IsUnresolvedIP()); // Look for a response from the STUN server. // Even if the response doesn't match one of our outstanding requests, we @@ -332,7 +340,7 @@ void UDPPort::OnReadPacket( void UDPPort::OnSentPacket(rtc::AsyncPacketSocket* socket, const rtc::SentPacket& sent_packet) { - Port::OnSentPacket(sent_packet); + PortInterface::SignalSentPacket(sent_packet); } void UDPPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) { @@ -341,7 +349,7 @@ void UDPPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) { void UDPPort::SendStunBindingRequests() { // We will keep pinging the stun server to make sure our NAT pin-hole stays - // open during the call. + // open until the deadline (specified in SendStunBindingRequest). ASSERT(requests_.empty()); for (ServerAddresses::const_iterator it = server_addresses_.begin(); @@ -356,6 +364,8 @@ void UDPPort::ResolveStunAddress(const rtc::SocketAddress& stun_addr) { resolver_->SignalDone.connect(this, &UDPPort::OnResolveResult); } + LOG_J(LS_INFO, this) << "Starting STUN host lookup for " + << stun_addr.ToSensitiveString(); resolver_->Resolve(stun_addr); } @@ -380,15 +390,15 @@ void UDPPort::OnResolveResult(const rtc::SocketAddress& input, } } -void UDPPort::SendStunBindingRequest( - const rtc::SocketAddress& stun_addr) { - if (stun_addr.IsUnresolved()) { +void UDPPort::SendStunBindingRequest(const rtc::SocketAddress& stun_addr) { + if (stun_addr.IsUnresolvedIP()) { ResolveStunAddress(stun_addr); } else if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND) { // Check if |server_addr_| is compatible with the port's ip. if (IsCompatibleAddress(stun_addr)) { - requests_.Send(new StunBindingRequest(this, true, stun_addr)); + requests_.Send(new StunBindingRequest(this, stun_addr, + rtc::Time() + KEEP_ALIVE_TIMEOUT)); } else { // Since we can't send stun messages to the server, we should mark this // port ready. @@ -398,6 +408,23 @@ void UDPPort::SendStunBindingRequest( } } +bool UDPPort::MaybeSetDefaultLocalAddress(rtc::SocketAddress* addr) const { + if (!addr->IsAnyIP() || !emit_local_for_anyaddress_ || + !Network()->default_local_address_provider()) { + return true; + } + rtc::IPAddress default_address; + bool result = + Network()->default_local_address_provider()->GetDefaultLocalAddress( + addr->family(), &default_address); + if (!result || default_address.IsNil()) { + return false; + } + + addr->SetIP(default_address); + return true; +} + void UDPPort::OnStunBindingRequestSucceeded( const rtc::SocketAddress& stun_server_addr, const rtc::SocketAddress& stun_reflected_addr) { @@ -415,7 +442,9 @@ void UDPPort::OnStunBindingRequestSucceeded( !HasCandidateWithAddress(stun_reflected_addr)) { rtc::SocketAddress related_address = socket_->GetLocalAddress(); - if (!(candidate_filter() & CF_HOST)) { + // If we can't stamp the related address correctly, empty it to avoid leak. + if (!MaybeSetDefaultLocalAddress(&related_address) || + !(candidate_filter() & CF_HOST)) { // If candidate filter doesn't have CF_HOST specified, empty raddr to // avoid local address leakage. related_address = rtc::EmptySocketAddressWithFamily( diff --git a/webrtc/p2p/base/stunport.h b/webrtc/p2p/base/stunport.h index 62b23cf074..ecf61a782d 100644 --- a/webrtc/p2p/base/stunport.h +++ b/webrtc/p2p/base/stunport.h @@ -35,10 +35,9 @@ class UDPPort : public Port { const std::string& username, const std::string& password, const std::string& origin, - bool emit_localhost_for_anyaddress) { - UDPPort* port = new UDPPort(thread, factory, network, socket, - username, password, origin, - emit_localhost_for_anyaddress); + bool emit_local_for_anyaddress) { + UDPPort* port = new UDPPort(thread, factory, network, socket, username, + password, origin, emit_local_for_anyaddress); if (!port->Init()) { delete port; port = NULL; @@ -55,11 +54,10 @@ class UDPPort : public Port { const std::string& username, const std::string& password, const std::string& origin, - bool emit_localhost_for_anyaddress) { - UDPPort* port = new UDPPort(thread, factory, network, - ip, min_port, max_port, - username, password, origin, - emit_localhost_for_anyaddress); + bool emit_local_for_anyaddress) { + UDPPort* port = + new UDPPort(thread, factory, network, ip, min_port, max_port, username, + password, origin, emit_local_for_anyaddress); if (!port->Init()) { delete port; port = NULL; @@ -97,6 +95,9 @@ class UDPPort : public Port { OnReadPacket(socket, data, size, remote_addr, packet_time); return true; } + virtual bool SupportsProtocol(const std::string& protocol) const { + return protocol == UDP_PROTOCOL_NAME; + } void set_stun_keepalive_delay(int delay) { stun_keepalive_delay_ = delay; @@ -115,7 +116,7 @@ class UDPPort : public Port { const std::string& username, const std::string& password, const std::string& origin, - bool emit_localhost_for_anyaddress); + bool emit_local_for_anyaddress); UDPPort(rtc::Thread* thread, rtc::PacketSocketFactory* factory, @@ -124,7 +125,7 @@ class UDPPort : public Port { const std::string& username, const std::string& password, const std::string& origin, - bool emit_localhost_for_anyaddress); + bool emit_local_for_anyaddress); bool Init(); @@ -150,6 +151,12 @@ class UDPPort : public Port { void SendStunBindingRequests(); + // Helper function which will set |addr|'s IP to the default local address if + // |addr| is the "any" address and |emit_local_for_anyaddress_| is true. When + // returning false, it indicates that the operation has failed and the + // address shouldn't be used by any candidate. + bool MaybeSetDefaultLocalAddress(rtc::SocketAddress* addr) const; + private: // A helper class which can be called repeatedly to resolve multiple // addresses, as opposed to rtc::AsyncResolverInterface, which can only @@ -211,8 +218,9 @@ class UDPPort : public Port { bool ready_; int stun_keepalive_delay_; - // This is true when PORTALLOCATOR_ENABLE_LOCALHOST_CANDIDATE is specified. - bool emit_localhost_for_anyaddress_; + // This is true by default and false when + // PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE is specified. + bool emit_local_for_anyaddress_; friend class StunBindingRequest; }; diff --git a/webrtc/p2p/base/stunrequest.cc b/webrtc/p2p/base/stunrequest.cc index df5614d3cc..ce0364e8db 100644 --- a/webrtc/p2p/base/stunrequest.cc +++ b/webrtc/p2p/base/stunrequest.cc @@ -53,6 +53,16 @@ void StunRequestManager::SendDelayed(StunRequest* request, int delay) { } } +void StunRequestManager::Flush(int msg_type) { + for (const auto kv : requests_) { + StunRequest* request = kv.second; + if (msg_type == kAllRequests || msg_type == request->type()) { + thread_->Clear(request, MSG_STUN_SEND); + thread_->Send(request, MSG_STUN_SEND, NULL); + } + } +} + void StunRequestManager::Remove(StunRequest* request) { ASSERT(request->manager() == this); RequestMap::iterator iter = requests_.find(request->id()); diff --git a/webrtc/p2p/base/stunrequest.h b/webrtc/p2p/base/stunrequest.h index 267b4a1959..44c1ebff56 100644 --- a/webrtc/p2p/base/stunrequest.h +++ b/webrtc/p2p/base/stunrequest.h @@ -21,6 +21,8 @@ namespace cricket { class StunRequest; +const int kAllRequests = 0; + // Manages a set of STUN requests, sending and resending until we receive a // response or determine that the request has timed out. class StunRequestManager { @@ -32,6 +34,11 @@ class StunRequestManager { void Send(StunRequest* request); void SendDelayed(StunRequest* request, int delay); + // If |msg_type| is kAllRequests, sends all pending requests right away. + // Otherwise, sends those that have a matching type right away. + // Only for testing. + void Flush(int msg_type); + // Removes a stun request that was added previously. This will happen // automatically when a request succeeds, fails, or times out. void Remove(StunRequest* request); diff --git a/webrtc/p2p/base/tcpport.cc b/webrtc/p2p/base/tcpport.cc index 2590d0aca8..cd3c9192e4 100644 --- a/webrtc/p2p/base/tcpport.cc +++ b/webrtc/p2p/base/tcpport.cc @@ -125,9 +125,7 @@ TCPPort::~TCPPort() { Connection* TCPPort::CreateConnection(const Candidate& address, CandidateOrigin origin) { - // We only support TCP protocols - if ((address.protocol() != TCP_PROTOCOL_NAME) && - (address.protocol() != SSLTCP_PROTOCOL_NAME)) { + if (!SupportsProtocol(address.protocol())) { return NULL; } @@ -184,10 +182,13 @@ void TCPPort::PrepareAddress() { } else { LOG_J(LS_INFO, this) << "Not listening due to firewall restrictions."; // Note: We still add the address, since otherwise the remote side won't - // recognize our incoming TCP connections. - AddAddress(rtc::SocketAddress(ip(), 0), rtc::SocketAddress(ip(), 0), - rtc::SocketAddress(), TCP_PROTOCOL_NAME, "", TCPTYPE_ACTIVE_STR, - LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST_TCP, 0, true); + // recognize our incoming TCP connections. According to + // https://tools.ietf.org/html/rfc6544#section-4.5, for active candidate, + // the port must be set to the discard port, i.e. 9. + AddAddress(rtc::SocketAddress(ip(), DISCARD_PORT), + rtc::SocketAddress(ip(), 0), rtc::SocketAddress(), + TCP_PROTOCOL_NAME, "", TCPTYPE_ACTIVE_STR, LOCAL_PORT_TYPE, + ICE_TYPE_PREFERENCE_HOST_TCP, 0, true); } } @@ -257,6 +258,7 @@ void TCPPort::OnNewConnection(rtc::AsyncPacketSocket* socket, incoming.socket = new_socket; incoming.socket->SignalReadPacket.connect(this, &TCPPort::OnReadPacket); incoming.socket->SignalReadyToSend.connect(this, &TCPPort::OnReadyToSend); + incoming.socket->SignalSentPacket.connect(this, &TCPPort::OnSentPacket); LOG_J(LS_VERBOSE, this) << "Accepted connection from " << incoming.addr.ToSensitiveString(); @@ -285,6 +287,11 @@ void TCPPort::OnReadPacket(rtc::AsyncPacketSocket* socket, Port::OnReadPacket(data, size, remote_addr, PROTO_TCP); } +void TCPPort::OnSentPacket(rtc::AsyncPacketSocket* socket, + const rtc::SentPacket& sent_packet) { + PortInterface::SignalSentPacket(sent_packet); +} + void TCPPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) { Port::OnReadyToSend(); } diff --git a/webrtc/p2p/base/tcpport.h b/webrtc/p2p/base/tcpport.h index a64c5eeab9..cfc6245601 100644 --- a/webrtc/p2p/base/tcpport.h +++ b/webrtc/p2p/base/tcpport.h @@ -45,16 +45,19 @@ class TCPPort : public Port { } return port; } - virtual ~TCPPort(); + ~TCPPort() override; - virtual Connection* CreateConnection(const Candidate& address, - CandidateOrigin origin); + Connection* CreateConnection(const Candidate& address, + CandidateOrigin origin) override; - virtual void PrepareAddress(); + void PrepareAddress() override; - virtual int GetOption(rtc::Socket::Option opt, int* value); - virtual int SetOption(rtc::Socket::Option opt, int value); - virtual int GetError(); + int GetOption(rtc::Socket::Option opt, int* value) override; + int SetOption(rtc::Socket::Option opt, int value) override; + int GetError() override; + bool SupportsProtocol(const std::string& protocol) const override { + return protocol == TCP_PROTOCOL_NAME || protocol == SSLTCP_PROTOCOL_NAME; + } protected: TCPPort(rtc::Thread* thread, @@ -69,10 +72,11 @@ class TCPPort : public Port { bool Init(); // Handles sending using the local TCP socket. - virtual int SendTo(const void* data, size_t size, - const rtc::SocketAddress& addr, - const rtc::PacketOptions& options, - bool payload); + int SendTo(const void* data, + size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketOptions& options, + bool payload) override; // Accepts incoming TCP connection. void OnNewConnection(rtc::AsyncPacketSocket* socket, @@ -93,6 +97,9 @@ class TCPPort : public Port { const rtc::SocketAddress& remote_addr, const rtc::PacketTime& packet_time); + void OnSentPacket(rtc::AsyncPacketSocket* socket, + const rtc::SentPacket& sent_packet) override; + void OnReadyToSend(rtc::AsyncPacketSocket* socket); void OnAddressReady(rtc::AsyncPacketSocket* socket, @@ -113,15 +120,16 @@ class TCPConnection : public Connection { // Connection is outgoing unless socket is specified TCPConnection(TCPPort* port, const Candidate& candidate, rtc::AsyncPacketSocket* socket = 0); - virtual ~TCPConnection(); + ~TCPConnection() override; - virtual int Send(const void* data, size_t size, - const rtc::PacketOptions& options); - virtual int GetError(); + int Send(const void* data, + size_t size, + const rtc::PacketOptions& options) override; + int GetError() override; rtc::AsyncPacketSocket* socket() { return socket_.get(); } - void OnMessage(rtc::Message* pmsg); + void OnMessage(rtc::Message* pmsg) override; // Allow test cases to overwrite the default timeout period. int reconnection_timeout() const { return reconnection_timeout_; } @@ -136,8 +144,8 @@ class TCPConnection : public Connection { // Set waiting_for_stun_binding_complete_ to false to allow data packets in // addition to what Port::OnConnectionRequestResponse does. - virtual void OnConnectionRequestResponse(ConnectionRequest* req, - StunMessage* response); + void OnConnectionRequestResponse(ConnectionRequest* req, + StunMessage* response) override; private: // Helper function to handle the case when Ping or Send fails with error diff --git a/webrtc/p2p/base/transport.cc b/webrtc/p2p/base/transport.cc index 2328e4587c..eff10aa0a9 100644 --- a/webrtc/p2p/base/transport.cc +++ b/webrtc/p2p/base/transport.cc @@ -305,8 +305,8 @@ bool Transport::GetStats(TransportStats* stats) { TransportChannelImpl* channel = kv.second; TransportChannelStats substats; substats.component = channel->component(); - channel->GetSrtpCryptoSuite(&substats.srtp_cipher); - channel->GetSslCipherSuite(&substats.ssl_cipher); + channel->GetSrtpCryptoSuite(&substats.srtp_crypto_suite); + channel->GetSslCipherSuite(&substats.ssl_cipher_suite); if (!channel->GetStats(&substats.connection_infos)) { return false; } diff --git a/webrtc/p2p/base/transport.h b/webrtc/p2p/base/transport.h index 955eb42098..6b4b37d4c5 100644 --- a/webrtc/p2p/base/transport.h +++ b/webrtc/p2p/base/transport.h @@ -123,8 +123,8 @@ typedef std::vector<ConnectionInfo> ConnectionInfos; struct TransportChannelStats { int component = 0; ConnectionInfos connection_infos; - std::string srtp_cipher; - int ssl_cipher = 0; + int srtp_crypto_suite = rtc::SRTP_INVALID_CRYPTO_SUITE; + int ssl_cipher_suite = rtc::TLS_NULL_WITH_NULL_NULL; }; // Information about all the channels of a transport. @@ -140,7 +140,11 @@ struct TransportStats { // Information about ICE configuration. struct IceConfig { // The ICE connection receiving timeout value. + // TODO(honghaiz): Remove suffix _ms to be consistent. int receiving_timeout_ms = -1; + // Time interval in milliseconds to ping a backup connection when the ICE + // channel is strongly connected. + int backup_connection_ping_interval = -1; // If true, the most recent port allocator session will keep on running. bool gather_continually = false; }; diff --git a/webrtc/p2p/base/transportchannel.cc b/webrtc/p2p/base/transportchannel.cc index 63d84494e5..6cbe2b7583 100644 --- a/webrtc/p2p/base/transportchannel.cc +++ b/webrtc/p2p/base/transportchannel.cc @@ -51,7 +51,20 @@ void TransportChannel::set_dtls_state(DtlsTransportState state) { LOG_J(LS_VERBOSE, this) << "set_dtls_state from:" << dtls_state_ << " to " << state; dtls_state_ = state; - SignalDtlsState(this); + SignalDtlsState(this, state); +} + +bool TransportChannel::SetSrtpCryptoSuites(const std::vector<int>& ciphers) { + return false; +} + +// TODO(guoweis): Remove this function once everything is moved away. +bool TransportChannel::SetSrtpCiphers(const std::vector<std::string>& ciphers) { + std::vector<int> crypto_suites; + for (const auto cipher : ciphers) { + crypto_suites.push_back(rtc::SrtpCryptoSuiteFromName(cipher)); + } + return SetSrtpCryptoSuites(crypto_suites); } } // namespace cricket diff --git a/webrtc/p2p/base/transportchannel.h b/webrtc/p2p/base/transportchannel.h index 767a5f68bf..b91af139b7 100644 --- a/webrtc/p2p/base/transportchannel.h +++ b/webrtc/p2p/base/transportchannel.h @@ -79,8 +79,9 @@ class TransportChannel : public sigslot::has_slots<> { // Emitted when the TransportChannel's ability to send has changed. sigslot::signal1<TransportChannel*> SignalReadyToSend; sigslot::signal1<TransportChannel*> SignalReceivingState; - // Emitted when the DtlsTransportState has changed. - sigslot::signal1<TransportChannel*> SignalDtlsState; + // Emitted whenever DTLS-SRTP is setup which will require setting up a new + // SRTP context. + sigslot::signal2<TransportChannel*, DtlsTransportState> SignalDtlsState; // Attempts to send the given packet. The return value is < 0 on failure. // TODO: Remove the default argument once channel code is updated. @@ -107,14 +108,17 @@ class TransportChannel : public sigslot::has_slots<> { // Default implementation. virtual bool GetSslRole(rtc::SSLRole* role) const = 0; - // Sets up the ciphers to use for DTLS-SRTP. - virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers) = 0; + // Sets up the ciphers to use for DTLS-SRTP. TODO(guoweis): Make this pure + // virtual once all dependencies have implementation. + virtual bool SetSrtpCryptoSuites(const std::vector<int>& ciphers); + + // Keep the original one for backward compatibility until all dependencies + // move away. TODO(guoweis): Remove this function. + virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers); // Finds out which DTLS-SRTP cipher was negotiated. // TODO(guoweis): Remove this once all dependencies implement this. - virtual bool GetSrtpCryptoSuite(std::string* cipher) { - return false; - } + virtual bool GetSrtpCryptoSuite(int* cipher) { return false; } // Finds out which DTLS cipher was negotiated. // TODO(guoweis): Remove this once all dependencies implement this. @@ -154,9 +158,6 @@ class TransportChannel : public sigslot::has_slots<> { std::string ToString() const; protected: - // TODO(honghaiz): Remove this once chromium's unit tests no longer call it. - void set_readable(bool readable) { set_receiving(readable); } - // Sets the writable state, signaling if necessary. void set_writable(bool writable); diff --git a/webrtc/p2p/base/transportcontroller.cc b/webrtc/p2p/base/transportcontroller.cc index 22b827a1a5..053388eeb8 100644 --- a/webrtc/p2p/base/transportcontroller.cc +++ b/webrtc/p2p/base/transportcontroller.cc @@ -66,9 +66,10 @@ void TransportController::SetIceRole(IceRole ice_role) { rtc::Bind(&TransportController::SetIceRole_w, this, ice_role)); } -bool TransportController::GetSslRole(rtc::SSLRole* role) { - return worker_thread_->Invoke<bool>( - rtc::Bind(&TransportController::GetSslRole_w, this, role)); +bool TransportController::GetSslRole(const std::string& transport_name, + rtc::SSLRole* role) { + return worker_thread_->Invoke<bool>(rtc::Bind( + &TransportController::GetSslRole_w, this, transport_name, role)); } bool TransportController::SetLocalCertificate( @@ -343,13 +344,16 @@ void TransportController::SetIceRole_w(IceRole ice_role) { } } -bool TransportController::GetSslRole_w(rtc::SSLRole* role) { +bool TransportController::GetSslRole_w(const std::string& transport_name, + rtc::SSLRole* role) { RTC_DCHECK(worker_thread()->IsCurrent()); - if (transports_.empty()) { + Transport* t = GetTransport_w(transport_name); + if (!t) { return false; } - return transports_.begin()->second->GetSslRole(role); + + return t->GetSslRole(role); } bool TransportController::SetLocalCertificate_w( diff --git a/webrtc/p2p/base/transportcontroller.h b/webrtc/p2p/base/transportcontroller.h index 8d57b460e8..450e6b391f 100644 --- a/webrtc/p2p/base/transportcontroller.h +++ b/webrtc/p2p/base/transportcontroller.h @@ -48,11 +48,7 @@ class TransportController : public sigslot::has_slots<>, void SetIceConfig(const IceConfig& config); void SetIceRole(IceRole ice_role); - // TODO(deadbeef) - Return role of each transport, as role may differ from - // one another. - // In current implementaion we just return the role of the first transport - // alphabetically. - bool GetSslRole(rtc::SSLRole* role); + bool GetSslRole(const std::string& transport_name, rtc::SSLRole* role); // Specifies the identity to use in this session. // Can only be called once. @@ -160,7 +156,7 @@ class TransportController : public sigslot::has_slots<>, bool SetSslMaxProtocolVersion_w(rtc::SSLProtocolVersion version); void SetIceConfig_w(const IceConfig& config); void SetIceRole_w(IceRole ice_role); - bool GetSslRole_w(rtc::SSLRole* role); + bool GetSslRole_w(const std::string& transport_name, rtc::SSLRole* role); bool SetLocalCertificate_w( const rtc::scoped_refptr<rtc::RTCCertificate>& certificate); bool GetLocalCertificate_w( @@ -202,7 +198,7 @@ class TransportController : public sigslot::has_slots<>, std::vector<RefCountedChannel> channels_; PortAllocator* const port_allocator_ = nullptr; - rtc::SSLProtocolVersion ssl_max_version_ = rtc::SSL_PROTOCOL_DTLS_10; + rtc::SSLProtocolVersion ssl_max_version_ = rtc::SSL_PROTOCOL_DTLS_12; // Aggregate state for TransportChannelImpls. IceConnectionState connection_state_ = kIceConnectionConnecting; diff --git a/webrtc/p2p/base/transportcontroller_unittest.cc b/webrtc/p2p/base/transportcontroller_unittest.cc index 23e4dc8067..6ff158e8fc 100644 --- a/webrtc/p2p/base/transportcontroller_unittest.cc +++ b/webrtc/p2p/base/transportcontroller_unittest.cc @@ -262,21 +262,18 @@ TEST_F(TransportControllerTest, TestGetSslRole) { ASSERT_NE(nullptr, channel); ASSERT_TRUE(channel->SetSslRole(rtc::SSL_CLIENT)); rtc::SSLRole role; - EXPECT_TRUE(transport_controller_->GetSslRole(&role)); + EXPECT_FALSE(transport_controller_->GetSslRole("video", &role)); + EXPECT_TRUE(transport_controller_->GetSslRole("audio", &role)); EXPECT_EQ(rtc::SSL_CLIENT, role); } TEST_F(TransportControllerTest, TestSetAndGetLocalCertificate) { rtc::scoped_refptr<rtc::RTCCertificate> certificate1 = - rtc::RTCCertificate::Create( - rtc::scoped_ptr<rtc::SSLIdentity>( - rtc::SSLIdentity::Generate("session1", rtc::KT_DEFAULT)) - .Pass()); + rtc::RTCCertificate::Create(rtc::scoped_ptr<rtc::SSLIdentity>( + rtc::SSLIdentity::Generate("session1", rtc::KT_DEFAULT))); rtc::scoped_refptr<rtc::RTCCertificate> certificate2 = - rtc::RTCCertificate::Create( - rtc::scoped_ptr<rtc::SSLIdentity>( - rtc::SSLIdentity::Generate("session2", rtc::KT_DEFAULT)) - .Pass()); + rtc::RTCCertificate::Create(rtc::scoped_ptr<rtc::SSLIdentity>( + rtc::SSLIdentity::Generate("session2", rtc::KT_DEFAULT))); rtc::scoped_refptr<rtc::RTCCertificate> returned_certificate; FakeTransportChannel* channel1 = CreateChannel("audio", 1); diff --git a/webrtc/p2p/base/transportdescription.cc b/webrtc/p2p/base/transportdescription.cc index 52033ec9c3..b8f14eaa98 100644 --- a/webrtc/p2p/base/transportdescription.cc +++ b/webrtc/p2p/base/transportdescription.cc @@ -10,7 +10,7 @@ #include "webrtc/p2p/base/transportdescription.h" -#include "webrtc/base/basicdefs.h" +#include "webrtc/base/arraysize.h" #include "webrtc/base/stringutils.h" #include "webrtc/p2p/base/constants.h" @@ -24,7 +24,7 @@ bool StringToConnectionRole(const std::string& role_str, ConnectionRole* role) { CONNECTIONROLE_HOLDCONN_STR }; - for (size_t i = 0; i < ARRAY_SIZE(roles); ++i) { + for (size_t i = 0; i < arraysize(roles); ++i) { if (_stricmp(roles[i], role_str.c_str()) == 0) { *role = static_cast<ConnectionRole>(CONNECTIONROLE_ACTIVE + i); return true; diff --git a/webrtc/p2p/base/transportdescriptionfactory_unittest.cc b/webrtc/p2p/base/transportdescriptionfactory_unittest.cc index e3992dfdd3..a52d9ed95a 100644 --- a/webrtc/p2p/base/transportdescriptionfactory_unittest.cc +++ b/webrtc/p2p/base/transportdescriptionfactory_unittest.cc @@ -26,11 +26,10 @@ using cricket::TransportOptions; class TransportDescriptionFactoryTest : public testing::Test { public: TransportDescriptionFactoryTest() - : cert1_(rtc::RTCCertificate::Create(scoped_ptr<rtc::SSLIdentity>( - new rtc::FakeSSLIdentity("User1")).Pass())), - cert2_(rtc::RTCCertificate::Create(scoped_ptr<rtc::SSLIdentity>( - new rtc::FakeSSLIdentity("User2")).Pass())) { - } + : cert1_(rtc::RTCCertificate::Create( + scoped_ptr<rtc::SSLIdentity>(new rtc::FakeSSLIdentity("User1")))), + cert2_(rtc::RTCCertificate::Create( + scoped_ptr<rtc::SSLIdentity>(new rtc::FakeSSLIdentity("User2")))) {} void CheckDesc(const TransportDescription* desc, const std::string& opt, const std::string& ice_ufrag, diff --git a/webrtc/p2p/base/turnport.cc b/webrtc/p2p/base/turnport.cc index 3fdcac5f31..5ed93dd1d8 100644 --- a/webrtc/p2p/base/turnport.cc +++ b/webrtc/p2p/base/turnport.cc @@ -38,6 +38,8 @@ static const size_t TURN_CHANNEL_HEADER_SIZE = 4U; // STUN_ERROR_ALLOCATION_MISMATCH error per rfc5766. static const size_t MAX_ALLOCATE_MISMATCH_RETRIES = 2; +static const int TURN_SUCCESS_RESULT_CODE = 0; + inline bool IsTurnChannelData(uint16_t msg_type) { return ((msg_type & 0xC000) == 0x4000); // MSB are 0b01 } @@ -137,11 +139,19 @@ class TurnEntry : public sigslot::has_slots<> { TurnPort* port() { return port_; } int channel_id() const { return channel_id_; } + // For testing only. + void set_channel_id(int channel_id) { channel_id_ = channel_id; } + const rtc::SocketAddress& address() const { return ext_addr_; } BindState state() const { return state_; } + uint32_t destruction_timestamp() { return destruction_timestamp_; } + void set_destruction_timestamp(uint32_t destruction_timestamp) { + destruction_timestamp_ = destruction_timestamp; + } + // Helper methods to send permission and channel bind requests. - void SendCreatePermissionRequest(); + void SendCreatePermissionRequest(int delay); void SendChannelBindRequest(int delay); // Sends a packet to the given destination address. // This will wrap the packet in STUN if necessary. @@ -150,8 +160,10 @@ class TurnEntry : public sigslot::has_slots<> { void OnCreatePermissionSuccess(); void OnCreatePermissionError(StunMessage* response, int code); + void OnCreatePermissionTimeout(); void OnChannelBindSuccess(); void OnChannelBindError(StunMessage* response, int code); + void OnChannelBindTimeout(); // Signal sent when TurnEntry is destroyed. sigslot::signal1<TurnEntry*> SignalDestroyed; @@ -160,6 +172,11 @@ class TurnEntry : public sigslot::has_slots<> { int channel_id_; rtc::SocketAddress ext_addr_; BindState state_; + // A non-zero value indicates that this entry is scheduled to be destroyed. + // It is also used as an ID of the event scheduling. When the destruction + // event actually fires, the TurnEntry will be destroyed only if the + // timestamp here matches the one in the firing event. + uint32_t destruction_timestamp_ = 0; }; TurnPort::TurnPort(rtc::Thread* thread, @@ -239,7 +256,7 @@ TurnPort::~TurnPort() { } while (!entries_.empty()) { - DestroyEntry(entries_.front()->address()); + DestroyEntry(entries_.front()); } if (resolver_) { resolver_->Destroy(false); @@ -267,7 +284,7 @@ void TurnPort::PrepareAddress() { server_address_.address.SetPort(TURN_DEFAULT_PORT); } - if (server_address_.address.IsUnresolved()) { + if (server_address_.address.IsUnresolvedIP()) { ResolveTurnAddress(server_address_.address); } else { // If protocol family of server address doesn't match with local, return. @@ -334,6 +351,8 @@ bool TurnPort::CreateTurnClientSocket() { socket_->SignalReadyToSend.connect(this, &TurnPort::OnReadyToSend); + socket_->SignalSentPacket.connect(this, &TurnPort::OnSentPacket); + // TCP port is ready to send stun requests after the socket is connected, // while UDP port is ready to do so once the socket is created. if (server_address_.proto == PROTO_TCP) { @@ -380,7 +399,7 @@ void TurnPort::OnSocketConnect(rtc::AsyncPacketSocket* socket) { } state_ = STATE_CONNECTED; // It is ready to send stun requests. - if (server_address_.address.IsUnresolved()) { + if (server_address_.address.IsUnresolvedIP()) { server_address_.address = socket_->GetRemoteAddress(); } @@ -392,11 +411,7 @@ void TurnPort::OnSocketConnect(rtc::AsyncPacketSocket* socket) { void TurnPort::OnSocketClose(rtc::AsyncPacketSocket* socket, int error) { LOG_J(LS_WARNING, this) << "Connection with server failed, error=" << error; ASSERT(socket == socket_); - if (!ready()) { - OnAllocateError(); - } - request_manager_.Clear(); - state_ = STATE_DISCONNECTED; + Close(); } void TurnPort::OnAllocateMismatch() { @@ -425,7 +440,7 @@ void TurnPort::OnAllocateMismatch() { Connection* TurnPort::CreateConnection(const Candidate& address, CandidateOrigin origin) { // TURN-UDP can only connect to UDP candidates. - if (address.protocol() != UDP_PROTOCOL_NAME) { + if (!SupportsProtocol(address.protocol())) { return NULL; } @@ -438,7 +453,7 @@ Connection* TurnPort::CreateConnection(const Candidate& address, } // Create an entry, if needed, so we can get our permissions set up correctly. - CreateEntry(address.address()); + CreateOrRefreshEntry(address.address()); // A TURN port will have two candiates, STUN and TURN. STUN may not // present in all cases. If present stun candidate will be added first @@ -454,6 +469,15 @@ Connection* TurnPort::CreateConnection(const Candidate& address, return NULL; } +bool TurnPort::DestroyConnection(const rtc::SocketAddress& address) { + Connection* conn = GetConnection(address); + if (conn != nullptr) { + conn->Destroy(); + return true; + } + return false; +} + int TurnPort::SetOption(rtc::Socket::Option opt, int value) { if (!socket_) { // If socket is not created yet, these options will be applied during socket @@ -560,6 +584,11 @@ void TurnPort::OnReadPacket( } } +void TurnPort::OnSentPacket(rtc::AsyncPacketSocket* socket, + const rtc::SentPacket& sent_packet) { + PortInterface::SignalSentPacket(sent_packet); +} + void TurnPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) { if (ready()) { Port::OnReadyToSend(); @@ -602,6 +631,8 @@ void TurnPort::ResolveTurnAddress(const rtc::SocketAddress& address) { if (resolver_) return; + LOG_J(LS_INFO, this) << "Starting TURN host lookup for " + << address.ToSensitiveString(); resolver_ = socket_factory()->CreateAsyncResolver(); resolver_->SignalDone.connect(this, &TurnPort::OnResolveResult); resolver_->Start(address); @@ -686,35 +717,59 @@ void TurnPort::OnAllocateError() { // We will send SignalPortError asynchronously as this can be sent during // port initialization. This way it will not be blocking other port // creation. - thread()->Post(this, MSG_ERROR); + thread()->Post(this, MSG_ALLOCATE_ERROR); } -void TurnPort::OnMessage(rtc::Message* message) { - if (message->message_id == MSG_ERROR) { - SignalPortError(this); - return; - } else if (message->message_id == MSG_ALLOCATE_MISMATCH) { - OnAllocateMismatch(); - return; - } else if (message->message_id == MSG_TRY_ALTERNATE_SERVER) { - if (server_address().proto == PROTO_UDP) { - // Send another allocate request to alternate server, with the received - // realm and nonce values. - SendRequest(new TurnAllocateRequest(this), 0); - } else { - // Since it's TCP, we have to delete the connected socket and reconnect - // with the alternate server. PrepareAddress will send stun binding once - // the new socket is connected. - ASSERT(server_address().proto == PROTO_TCP); - ASSERT(!SharedSocket()); - delete socket_; - socket_ = NULL; - PrepareAddress(); - } - return; +void TurnPort::OnTurnRefreshError() { + // Need to Close the port asynchronously because otherwise, the refresh + // request may be deleted twice: once at the end of the message processing + // and the other in Close(). + thread()->Post(this, MSG_REFRESH_ERROR); +} + +void TurnPort::Close() { + if (!ready()) { + OnAllocateError(); + } + request_manager_.Clear(); + // Stop the port from creating new connections. + state_ = STATE_DISCONNECTED; + // Delete all existing connections; stop sending data. + for (auto kv : connections()) { + kv.second->Destroy(); } +} - Port::OnMessage(message); +void TurnPort::OnMessage(rtc::Message* message) { + switch (message->message_id) { + case MSG_ALLOCATE_ERROR: + SignalPortError(this); + break; + case MSG_ALLOCATE_MISMATCH: + OnAllocateMismatch(); + break; + case MSG_REFRESH_ERROR: + Close(); + break; + case MSG_TRY_ALTERNATE_SERVER: + if (server_address().proto == PROTO_UDP) { + // Send another allocate request to alternate server, with the received + // realm and nonce values. + SendRequest(new TurnAllocateRequest(this), 0); + } else { + // Since it's TCP, we have to delete the connected socket and reconnect + // with the alternate server. PrepareAddress will send stun binding once + // the new socket is connected. + ASSERT(server_address().proto == PROTO_TCP); + ASSERT(!SharedSocket()); + delete socket_; + socket_ = NULL; + PrepareAddress(); + } + break; + default: + Port::OnMessage(message); + } } void TurnPort::OnAllocateRequestTimeout() { @@ -898,24 +953,73 @@ TurnEntry* TurnPort::FindEntry(int channel_id) const { return (it != entries_.end()) ? *it : NULL; } -TurnEntry* TurnPort::CreateEntry(const rtc::SocketAddress& addr) { - ASSERT(FindEntry(addr) == NULL); - TurnEntry* entry = new TurnEntry(this, next_channel_number_++, addr); - entries_.push_back(entry); - return entry; +bool TurnPort::EntryExists(TurnEntry* e) { + auto it = std::find(entries_.begin(), entries_.end(), e); + return it != entries_.end(); } -void TurnPort::DestroyEntry(const rtc::SocketAddress& addr) { +void TurnPort::CreateOrRefreshEntry(const rtc::SocketAddress& addr) { TurnEntry* entry = FindEntry(addr); + if (entry == nullptr) { + entry = new TurnEntry(this, next_channel_number_++, addr); + entries_.push_back(entry); + } else { + // The channel binding request for the entry will be refreshed automatically + // until the entry is destroyed. + CancelEntryDestruction(entry); + } +} + +void TurnPort::DestroyEntry(TurnEntry* entry) { ASSERT(entry != NULL); entry->SignalDestroyed(entry); entries_.remove(entry); delete entry; } +void TurnPort::DestroyEntryIfNotCancelled(TurnEntry* entry, + uint32_t timestamp) { + if (!EntryExists(entry)) { + return; + } + bool cancelled = timestamp != entry->destruction_timestamp(); + if (!cancelled) { + DestroyEntry(entry); + } +} + void TurnPort::OnConnectionDestroyed(Connection* conn) { - // Destroying TurnEntry for the connection, which is already destroyed. - DestroyEntry(conn->remote_candidate().address()); + // Schedule an event to destroy TurnEntry for the connection, which is + // already destroyed. + const rtc::SocketAddress& remote_address = conn->remote_candidate().address(); + TurnEntry* entry = FindEntry(remote_address); + ASSERT(entry != NULL); + ScheduleEntryDestruction(entry); +} + +void TurnPort::ScheduleEntryDestruction(TurnEntry* entry) { + ASSERT(entry->destruction_timestamp() == 0); + uint32_t timestamp = rtc::Time(); + entry->set_destruction_timestamp(timestamp); + invoker_.AsyncInvokeDelayed<void>( + thread(), + rtc::Bind(&TurnPort::DestroyEntryIfNotCancelled, this, entry, timestamp), + TURN_PERMISSION_TIMEOUT); +} + +void TurnPort::CancelEntryDestruction(TurnEntry* entry) { + ASSERT(entry->destruction_timestamp() != 0); + entry->set_destruction_timestamp(0); +} + +bool TurnPort::SetEntryChannelId(const rtc::SocketAddress& address, + int channel_id) { + TurnEntry* entry = FindEntry(address); + if (!entry) { + return false; + } + entry->set_channel_id(channel_id); + return true; } TurnAllocateRequest::TurnAllocateRequest(TurnPort* port) @@ -1131,16 +1235,12 @@ void TurnRefreshRequest::OnResponse(StunMessage* response) { // Schedule a refresh based on the returned lifetime value. port_->ScheduleRefresh(lifetime_attr->value()); + port_->SignalTurnRefreshResult(port_, TURN_SUCCESS_RESULT_CODE); } void TurnRefreshRequest::OnErrorResponse(StunMessage* response) { const StunErrorCodeAttribute* error_code = response->GetErrorCode(); - LOG_J(LS_INFO, port_) << "Received TURN refresh error response" - << ", id=" << rtc::hex_encode(id()) - << ", code=" << error_code->code() - << ", rtt=" << Elapsed(); - if (error_code->code() == STUN_ERROR_STALE_NONCE) { if (port_->UpdateNonce(response)) { // Send RefreshRequest immediately. @@ -1151,11 +1251,14 @@ void TurnRefreshRequest::OnErrorResponse(StunMessage* response) { << ", id=" << rtc::hex_encode(id()) << ", code=" << error_code->code() << ", rtt=" << Elapsed(); + port_->OnTurnRefreshError(); + port_->SignalTurnRefreshResult(port_, error_code->code()); } } void TurnRefreshRequest::OnTimeout() { LOG_J(LS_WARNING, port_) << "TURN refresh timeout " << rtc::hex_encode(id()); + port_->OnTurnRefreshError(); } TurnCreatePermissionRequest::TurnCreatePermissionRequest( @@ -1208,6 +1311,9 @@ void TurnCreatePermissionRequest::OnErrorResponse(StunMessage* response) { void TurnCreatePermissionRequest::OnTimeout() { LOG_J(LS_WARNING, port_) << "TURN create permission timeout " << rtc::hex_encode(id()); + if (entry_) { + entry_->OnCreatePermissionTimeout(); + } } void TurnCreatePermissionRequest::OnEntryDestroyed(TurnEntry* entry) { @@ -1275,6 +1381,9 @@ void TurnChannelBindRequest::OnErrorResponse(StunMessage* response) { void TurnChannelBindRequest::OnTimeout() { LOG_J(LS_WARNING, port_) << "TURN channel bind timeout " << rtc::hex_encode(id()); + if (entry_) { + entry_->OnChannelBindTimeout(); + } } void TurnChannelBindRequest::OnEntryDestroyed(TurnEntry* entry) { @@ -1289,12 +1398,12 @@ TurnEntry::TurnEntry(TurnPort* port, int channel_id, ext_addr_(ext_addr), state_(STATE_UNBOUND) { // Creating permission for |ext_addr_|. - SendCreatePermissionRequest(); + SendCreatePermissionRequest(0); } -void TurnEntry::SendCreatePermissionRequest() { - port_->SendRequest(new TurnCreatePermissionRequest( - port_, this, ext_addr_), 0); +void TurnEntry::SendCreatePermissionRequest(int delay) { + port_->SendRequest(new TurnCreatePermissionRequest(port_, this, ext_addr_), + delay); } void TurnEntry::SendChannelBindRequest(int delay) { @@ -1335,21 +1444,43 @@ void TurnEntry::OnCreatePermissionSuccess() { LOG_J(LS_INFO, port_) << "Create permission for " << ext_addr_.ToSensitiveString() << " succeeded"; - // For success result code will be 0. - port_->SignalCreatePermissionResult(port_, ext_addr_, 0); + port_->SignalCreatePermissionResult(port_, ext_addr_, + TURN_SUCCESS_RESULT_CODE); + + // If |state_| is STATE_BOUND, the permission will be refreshed + // by ChannelBindRequest. + if (state_ != STATE_BOUND) { + // Refresh the permission request about 1 minute before the permission + // times out. + int delay = TURN_PERMISSION_TIMEOUT - 60000; + SendCreatePermissionRequest(delay); + LOG_J(LS_INFO, port_) << "Scheduled create-permission-request in " + << delay << "ms."; + } } void TurnEntry::OnCreatePermissionError(StunMessage* response, int code) { if (code == STUN_ERROR_STALE_NONCE) { if (port_->UpdateNonce(response)) { - SendCreatePermissionRequest(); + SendCreatePermissionRequest(0); } } else { + port_->DestroyConnection(ext_addr_); // Send signal with error code. port_->SignalCreatePermissionResult(port_, ext_addr_, code); + Connection* c = port_->GetConnection(ext_addr_); + if (c) { + LOG_J(LS_ERROR, c) << "Received TURN CreatePermission error response, " + << "code=" << code << "; killing connection."; + c->FailAndDestroy(); + } } } +void TurnEntry::OnCreatePermissionTimeout() { + port_->DestroyConnection(ext_addr_); +} + void TurnEntry::OnChannelBindSuccess() { LOG_J(LS_INFO, port_) << "Channel bind for " << ext_addr_.ToSensitiveString() << " succeeded"; @@ -1358,14 +1489,21 @@ void TurnEntry::OnChannelBindSuccess() { } void TurnEntry::OnChannelBindError(StunMessage* response, int code) { - // TODO(mallinath) - Implement handling of error response for channel - // bind request as per http://tools.ietf.org/html/rfc5766#section-11.3 + // If the channel bind fails due to errors other than STATE_NONCE, + // we just destroy the connection and rely on ICE restart to re-establish + // the connection. if (code == STUN_ERROR_STALE_NONCE) { if (port_->UpdateNonce(response)) { // Send channel bind request with fresh nonce. SendChannelBindRequest(0); } + } else { + state_ = STATE_UNBOUND; + port_->DestroyConnection(ext_addr_); } } - +void TurnEntry::OnChannelBindTimeout() { + state_ = STATE_UNBOUND; + port_->DestroyConnection(ext_addr_); +} } // namespace cricket diff --git a/webrtc/p2p/base/turnport.h b/webrtc/p2p/base/turnport.h index 3bca727346..4d83806a37 100644 --- a/webrtc/p2p/base/turnport.h +++ b/webrtc/p2p/base/turnport.h @@ -16,9 +16,10 @@ #include <set> #include <string> +#include "webrtc/base/asyncinvoker.h" +#include "webrtc/base/asyncpacketsocket.h" #include "webrtc/p2p/base/port.h" #include "webrtc/p2p/client/basicportallocator.h" -#include "webrtc/base/asyncpacketsocket.h" namespace rtc { class AsyncResolver; @@ -105,7 +106,13 @@ class TurnPort : public Port { const rtc::SocketAddress& remote_addr, const rtc::PacketTime& packet_time); + virtual void OnSentPacket(rtc::AsyncPacketSocket* socket, + const rtc::SentPacket& sent_packet); virtual void OnReadyToSend(rtc::AsyncPacketSocket* socket); + virtual bool SupportsProtocol(const std::string& protocol) const { + // Turn port only connects to UDP candidates. + return protocol == UDP_PROTOCOL_NAME; + } void OnSocketConnect(rtc::AsyncPacketSocket* socket); void OnSocketClose(rtc::AsyncPacketSocket* socket, int error); @@ -122,6 +129,9 @@ class TurnPort : public Port { return socket_; } + // For testing only. + rtc::AsyncInvoker* invoker() { return &invoker_; } + // Signal with resolved server address. // Parameters are port, server address and resolved server address. // This signal will be sent only if server address is resolved successfully. @@ -129,9 +139,18 @@ class TurnPort : public Port { const rtc::SocketAddress&, const rtc::SocketAddress&> SignalResolvedServerAddress; - // This signal is only for testing purpose. + // All public methods/signals below are for testing only. + sigslot::signal2<TurnPort*, int> SignalTurnRefreshResult; sigslot::signal3<TurnPort*, const rtc::SocketAddress&, int> SignalCreatePermissionResult; + void FlushRequests(int msg_type) { request_manager_.Flush(msg_type); } + bool HasRequests() { return !request_manager_.empty(); } + void set_credentials(RelayCredentials& credentials) { + credentials_ = credentials; + } + // Finds the turn entry with |address| and sets its channel id. + // Returns true if the entry is found. + bool SetEntryChannelId(const rtc::SocketAddress& address, int channel_id); protected: TurnPort(rtc::Thread* thread, @@ -160,9 +179,10 @@ class TurnPort : public Port { private: enum { - MSG_ERROR = MSG_FIRST_AVAILABLE, + MSG_ALLOCATE_ERROR = MSG_FIRST_AVAILABLE, MSG_ALLOCATE_MISMATCH, - MSG_TRY_ALTERNATE_SERVER + MSG_TRY_ALTERNATE_SERVER, + MSG_REFRESH_ERROR }; typedef std::list<TurnEntry*> EntryList; @@ -181,6 +201,9 @@ class TurnPort : public Port { } } + // Shuts down the turn port, usually because of some fatal errors. + void Close(); + void OnTurnRefreshError(); bool SetAlternateServer(const rtc::SocketAddress& address); void ResolveTurnAddress(const rtc::SocketAddress& address); void OnResolveResult(rtc::AsyncResolverInterface* resolver); @@ -213,10 +236,20 @@ class TurnPort : public Port { bool HasPermission(const rtc::IPAddress& ipaddr) const; TurnEntry* FindEntry(const rtc::SocketAddress& address) const; TurnEntry* FindEntry(int channel_id) const; - TurnEntry* CreateEntry(const rtc::SocketAddress& address); - void DestroyEntry(const rtc::SocketAddress& address); + bool EntryExists(TurnEntry* e); + void CreateOrRefreshEntry(const rtc::SocketAddress& address); + void DestroyEntry(TurnEntry* entry); + // Destroys the entry only if |timestamp| matches the destruction timestamp + // in |entry|. + void DestroyEntryIfNotCancelled(TurnEntry* entry, uint32_t timestamp); + void ScheduleEntryDestruction(TurnEntry* entry); + void CancelEntryDestruction(TurnEntry* entry); void OnConnectionDestroyed(Connection* conn); + // Destroys the connection with remote address |address|. Returns true if + // a connection is found and destroyed. + bool DestroyConnection(const rtc::SocketAddress& address); + ProtocolAddress server_address_; RelayCredentials credentials_; AttemptedServerSet attempted_server_addresses_; @@ -242,6 +275,8 @@ class TurnPort : public Port { // The number of retries made due to allocate mismatch error. size_t allocate_mismatch_retries_; + rtc::AsyncInvoker invoker_; + friend class TurnEntry; friend class TurnAllocateRequest; friend class TurnRefreshRequest; diff --git a/webrtc/p2p/base/turnport_unittest.cc b/webrtc/p2p/base/turnport_unittest.cc index 724485ddde..916162575f 100644 --- a/webrtc/p2p/base/turnport_unittest.cc +++ b/webrtc/p2p/base/turnport_unittest.cc @@ -13,6 +13,7 @@ #include "webrtc/p2p/base/basicpacketsocketfactory.h" #include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/portallocator.h" #include "webrtc/p2p/base/tcpport.h" #include "webrtc/p2p/base/testturnserver.h" #include "webrtc/p2p/base/turnport.h" @@ -100,6 +101,24 @@ class TurnPortTestVirtualSocketServer : public rtc::VirtualSocketServer { using rtc::VirtualSocketServer::LookupBinding; }; +class TestConnectionWrapper : public sigslot::has_slots<> { + public: + TestConnectionWrapper(Connection* conn) : connection_(conn) { + conn->SignalDestroyed.connect( + this, &TestConnectionWrapper::OnConnectionDestroyed); + } + + Connection* connection() { return connection_; } + + private: + void OnConnectionDestroyed(Connection* conn) { + ASSERT_TRUE(conn == connection_); + connection_ = nullptr; + } + + Connection* connection_; +}; + class TurnPortTest : public testing::Test, public sigslot::has_slots<>, public rtc::MessageHandler { @@ -154,12 +173,15 @@ class TurnPortTest : public testing::Test, bool /*port_muxed*/) { turn_unknown_address_ = true; } - void OnTurnCreatePermissionResult(TurnPort* port, const SocketAddress& addr, - int code) { + void OnTurnCreatePermissionResult(TurnPort* port, + const SocketAddress& addr, + int code) { // Ignoring the address. - if (code == 0) { - turn_create_permission_success_ = true; - } + turn_create_permission_success_ = (code == 0); + } + + void OnTurnRefreshResult(TurnPort* port, int code) { + turn_refresh_success_ = (code == 0); } void OnTurnReadPacket(Connection* conn, const char* data, size_t size, const rtc::PacketTime& packet_time) { @@ -172,6 +194,7 @@ class TurnPortTest : public testing::Test, const rtc::PacketTime& packet_time) { udp_packets_.push_back(rtc::Buffer(data, size)); } + void OnConnectionDestroyed(Connection* conn) { connection_destroyed_ = true; } void OnSocketReadPacket(rtc::AsyncPacketSocket* socket, const char* data, size_t size, const rtc::SocketAddress& remote_addr, @@ -255,18 +278,42 @@ class TurnPortTest : public testing::Test, &TurnPortTest::OnTurnUnknownAddress); turn_port_->SignalCreatePermissionResult.connect(this, &TurnPortTest::OnTurnCreatePermissionResult); + turn_port_->SignalTurnRefreshResult.connect( + this, &TurnPortTest::OnTurnRefreshResult); } - void CreateUdpPort() { + void ConnectConnectionDestroyedSignal(Connection* conn) { + conn->SignalDestroyed.connect(this, &TurnPortTest::OnConnectionDestroyed); + } + + void CreateUdpPort() { CreateUdpPort(kLocalAddr2); } + + void CreateUdpPort(const SocketAddress& address) { udp_port_.reset(UDPPort::Create(main_, &socket_factory_, &network_, - kLocalAddr2.ipaddr(), 0, 0, - kIceUfrag2, kIcePwd2, - std::string(), false)); + address.ipaddr(), 0, 0, kIceUfrag2, + kIcePwd2, std::string(), false)); // UDP port will be controlled. udp_port_->SetIceRole(cricket::ICEROLE_CONTROLLED); udp_port_->SignalPortComplete.connect( this, &TurnPortTest::OnUdpPortComplete); } + void PrepareTurnAndUdpPorts() { + // turn_port_ should have been created. + ASSERT_TRUE(turn_port_ != nullptr); + turn_port_->PrepareAddress(); + ASSERT_TRUE_WAIT(turn_ready_, kTimeout); + + CreateUdpPort(); + udp_port_->PrepareAddress(); + ASSERT_TRUE_WAIT(udp_ready_, kTimeout); + } + + bool CheckConnectionDestroyed() { + turn_port_->FlushRequests(cricket::kAllRequests); + rtc::Thread::Current()->ProcessMessages(50); + return connection_destroyed_; + } + void TestTurnAlternateServer(cricket::ProtocolType protocol_type) { std::vector<rtc::SocketAddress> redirect_addresses; redirect_addresses.push_back(kTurnAlternateIntAddr); @@ -350,12 +397,7 @@ class TurnPortTest : public testing::Test, void TestTurnConnection() { // Create ports and prepare addresses. - ASSERT_TRUE(turn_port_ != NULL); - turn_port_->PrepareAddress(); - ASSERT_TRUE_WAIT(turn_ready_, kTimeout); - CreateUdpPort(); - udp_port_->PrepareAddress(); - ASSERT_TRUE_WAIT(udp_ready_, kTimeout); + PrepareTurnAndUdpPorts(); // Send ping from UDP to TURN. Connection* conn1 = udp_port_->CreateConnection( @@ -385,12 +427,46 @@ class TurnPortTest : public testing::Test, EXPECT_TRUE(conn2->receiving()); } + void TestDestroyTurnConnection() { + PrepareTurnAndUdpPorts(); + + // Create connections on both ends. + Connection* conn1 = udp_port_->CreateConnection(turn_port_->Candidates()[0], + Port::ORIGIN_MESSAGE); + Connection* conn2 = turn_port_->CreateConnection(udp_port_->Candidates()[0], + Port::ORIGIN_MESSAGE); + ASSERT_TRUE(conn2 != NULL); + ASSERT_TRUE_WAIT(turn_create_permission_success_, kTimeout); + // Make sure turn connection can receive. + conn1->Ping(0); + EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, conn1->write_state(), kTimeout); + EXPECT_FALSE(turn_unknown_address_); + + // Destroy the connection on the turn port. The TurnEntry is still + // there. So the turn port gets ping from unknown address if it is pinged. + conn2->Destroy(); + conn1->Ping(0); + EXPECT_TRUE_WAIT(turn_unknown_address_, kTimeout); + + // Flush all requests in the invoker to destroy the TurnEntry. + // Now the turn port cannot receive the ping. + turn_unknown_address_ = false; + turn_port_->invoker()->Flush(rtc::Thread::Current()); + conn1->Ping(0); + rtc::Thread::Current()->ProcessMessages(500); + EXPECT_FALSE(turn_unknown_address_); + + // If the connection is created again, it will start to receive pings. + conn2 = turn_port_->CreateConnection(udp_port_->Candidates()[0], + Port::ORIGIN_MESSAGE); + conn1->Ping(0); + EXPECT_TRUE_WAIT(conn2->receiving(), kTimeout); + EXPECT_FALSE(turn_unknown_address_); + } + void TestTurnSendData() { - turn_port_->PrepareAddress(); - EXPECT_TRUE_WAIT(turn_ready_, kTimeout); - CreateUdpPort(); - udp_port_->PrepareAddress(); - EXPECT_TRUE_WAIT(udp_ready_, kTimeout); + PrepareTurnAndUdpPorts(); + // Create connections and send pings. Connection* conn1 = turn_port_->CreateConnection( udp_port_->Candidates()[0], Port::ORIGIN_MESSAGE); @@ -446,6 +522,8 @@ class TurnPortTest : public testing::Test, bool turn_create_permission_success_; bool udp_ready_; bool test_finish_; + bool turn_refresh_success_ = false; + bool connection_destroyed_ = false; std::vector<rtc::Buffer> turn_packets_; std::vector<rtc::Buffer> udp_packets_; rtc::PacketOptions options; @@ -613,16 +691,33 @@ TEST_F(TurnPortTest, TestTurnTcpAllocateMismatch) { EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress()); } +TEST_F(TurnPortTest, TestRefreshRequestGetsErrorResponse) { + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + PrepareTurnAndUdpPorts(); + turn_port_->CreateConnection(udp_port_->Candidates()[0], + Port::ORIGIN_MESSAGE); + // Set bad credentials. + cricket::RelayCredentials bad_credentials("bad_user", "bad_pwd"); + turn_port_->set_credentials(bad_credentials); + turn_refresh_success_ = false; + // This sends out the first RefreshRequest with correct credentials. + // When this succeeds, it will schedule a new RefreshRequest with the bad + // credential. + turn_port_->FlushRequests(cricket::TURN_REFRESH_REQUEST); + EXPECT_TRUE_WAIT(turn_refresh_success_, kTimeout); + // Flush it again, it will receive a bad response. + turn_port_->FlushRequests(cricket::TURN_REFRESH_REQUEST); + EXPECT_TRUE_WAIT(!turn_refresh_success_, kTimeout); + EXPECT_TRUE_WAIT(!turn_port_->connected(), kTimeout); + EXPECT_TRUE_WAIT(turn_port_->connections().empty(), kTimeout); + EXPECT_FALSE(turn_port_->HasRequests()); +} + // Test that CreateConnection will return null if port becomes disconnected. TEST_F(TurnPortTest, TestCreateConnectionWhenSocketClosed) { turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr); - turn_port_->PrepareAddress(); - ASSERT_TRUE_WAIT(turn_ready_, kTimeout); - - CreateUdpPort(); - udp_port_->PrepareAddress(); - ASSERT_TRUE_WAIT(udp_ready_, kTimeout); + PrepareTurnAndUdpPorts(); // Create a connection. Connection* conn1 = turn_port_->CreateConnection(udp_port_->Candidates()[0], Port::ORIGIN_MESSAGE); @@ -694,6 +789,20 @@ TEST_F(TurnPortTest, TestTurnTcpConnection) { TestTurnConnection(); } +// Test that if a connection on a TURN port is destroyed, the TURN port can +// still receive ping on that connection as if it is from an unknown address. +// If the connection is created again, it will be used to receive ping. +TEST_F(TurnPortTest, TestDestroyTurnConnection) { + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + TestDestroyTurnConnection(); +} + +// Similar to above, except that this test will use the shared socket. +TEST_F(TurnPortTest, TestDestroyTurnConnectionUsingSharedSocket) { + CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + TestDestroyTurnConnection(); +} + // Test that we fail to create a connection when we want to use TLS over TCP. // This test should be removed once we have TLS support. TEST_F(TurnPortTest, TestTurnTlsTcpConnectionFails) { @@ -715,6 +824,54 @@ TEST_F(TurnPortTest, TestTurnConnectionUsingOTUNonce) { TestTurnConnection(); } +// Test that CreatePermissionRequest will be scheduled after the success +// of the first create permission request and the request will get an +// ErrorResponse if the ufrag and pwd are incorrect. +TEST_F(TurnPortTest, TestRefreshCreatePermissionRequest) { + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + PrepareTurnAndUdpPorts(); + + Connection* conn = turn_port_->CreateConnection(udp_port_->Candidates()[0], + Port::ORIGIN_MESSAGE); + ConnectConnectionDestroyedSignal(conn); + ASSERT_TRUE(conn != NULL); + ASSERT_TRUE_WAIT(turn_create_permission_success_, kTimeout); + turn_create_permission_success_ = false; + // A create-permission-request should be pending. + // After the next create-permission-response is received, it will schedule + // another request with bad_ufrag and bad_pwd. + cricket::RelayCredentials bad_credentials("bad_user", "bad_pwd"); + turn_port_->set_credentials(bad_credentials); + turn_port_->FlushRequests(cricket::kAllRequests); + ASSERT_TRUE_WAIT(turn_create_permission_success_, kTimeout); + // Flush the requests again; the create-permission-request will fail. + turn_port_->FlushRequests(cricket::kAllRequests); + EXPECT_TRUE_WAIT(!turn_create_permission_success_, kTimeout); + EXPECT_TRUE_WAIT(connection_destroyed_, kTimeout); +} + +TEST_F(TurnPortTest, TestChannelBindGetErrorResponse) { + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + PrepareTurnAndUdpPorts(); + Connection* conn1 = turn_port_->CreateConnection(udp_port_->Candidates()[0], + Port::ORIGIN_MESSAGE); + ASSERT_TRUE(conn1 != nullptr); + Connection* conn2 = udp_port_->CreateConnection(turn_port_->Candidates()[0], + Port::ORIGIN_MESSAGE); + ASSERT_TRUE(conn2 != nullptr); + ConnectConnectionDestroyedSignal(conn1); + conn1->Ping(0); + ASSERT_TRUE_WAIT(conn1->writable(), kTimeout); + + std::string data = "ABC"; + conn1->Send(data.data(), data.length(), options); + bool success = + turn_port_->SetEntryChannelId(udp_port_->Candidates()[0].address(), -1); + ASSERT_TRUE(success); + // Next time when the binding request is sent, it will get an ErrorResponse. + EXPECT_TRUE_WAIT(CheckConnectionDestroyed(), kTimeout); +} + // Do a TURN allocation, establish a UDP connection, and send some data. TEST_F(TurnPortTest, TestTurnSendDataTurnUdpToUdp) { // Create ports and prepare addresses. @@ -771,6 +928,29 @@ TEST_F(TurnPortTest, TestOriginHeader) { EXPECT_EQ(kTestOrigin, turn_server_.FindAllocation(local_address)->origin()); } +// Test that a CreatePermission failure will result in the connection being +// destroyed. +TEST_F(TurnPortTest, TestConnectionDestroyedOnCreatePermissionFailure) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); + turn_server_.server()->set_reject_private_addresses(true); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr); + turn_port_->PrepareAddress(); + ASSERT_TRUE_WAIT(turn_ready_, kTimeout); + + CreateUdpPort(SocketAddress("10.0.0.10", 0)); + udp_port_->PrepareAddress(); + ASSERT_TRUE_WAIT(udp_ready_, kTimeout); + // Create a connection. + TestConnectionWrapper conn(turn_port_->CreateConnection( + udp_port_->Candidates()[0], Port::ORIGIN_MESSAGE)); + ASSERT_TRUE(conn.connection() != nullptr); + + // Asynchronously, CreatePermission request should be sent and fail, closing + // the connection. + EXPECT_TRUE_WAIT(conn.connection() == nullptr, kTimeout); + EXPECT_FALSE(turn_create_permission_success_); +} + // Test that a TURN allocation is released when the port is closed. TEST_F(TurnPortTest, TestTurnReleaseAllocation) { CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); @@ -797,6 +977,10 @@ TEST_F(TurnPortTest, DISABLED_TestTurnTCPReleaseAllocation) { // This test verifies any FD's are not leaked after TurnPort is destroyed. // https://code.google.com/p/webrtc/issues/detail?id=2651 #if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +// 1 second is not always enough for getaddrinfo(). +// See: https://bugs.chromium.org/p/webrtc/issues/detail?id=5191 +static const unsigned int kResolverTimeout = 10000; + TEST_F(TurnPortTest, TestResolverShutdown) { turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, cricket::PROTO_UDP); int last_fd_count = GetFDCount(); @@ -805,7 +989,7 @@ TEST_F(TurnPortTest, TestResolverShutdown) { cricket::ProtocolAddress(rtc::SocketAddress( "www.google.invalid", 3478), cricket::PROTO_UDP)); turn_port_->PrepareAddress(); - ASSERT_TRUE_WAIT(turn_error_, kTimeout); + ASSERT_TRUE_WAIT(turn_error_, kResolverTimeout); EXPECT_TRUE(turn_port_->Candidates().empty()); turn_port_.reset(); rtc::Thread::Current()->Post(this, MSG_TESTFINISH); diff --git a/webrtc/p2p/base/turnserver.cc b/webrtc/p2p/base/turnserver.cc index 8d40a9030c..1502cdd52e 100644 --- a/webrtc/p2p/base/turnserver.cc +++ b/webrtc/p2p/base/turnserver.cc @@ -698,6 +698,12 @@ void TurnServerAllocation::HandleCreatePermissionRequest( return; } + if (server_->reject_private_addresses_ && + rtc::IPIsPrivate(peer_attr->GetAddress().ipaddr())) { + SendErrorResponse(msg, STUN_ERROR_FORBIDDEN, STUN_ERROR_REASON_FORBIDDEN); + return; + } + // Add this permission. AddPermission(peer_attr->GetAddress().ipaddr()); diff --git a/webrtc/p2p/base/turnserver.h b/webrtc/p2p/base/turnserver.h index d3bd77a866..113bd4c462 100644 --- a/webrtc/p2p/base/turnserver.h +++ b/webrtc/p2p/base/turnserver.h @@ -183,6 +183,11 @@ class TurnServer : public sigslot::has_slots<> { void set_enable_otu_nonce(bool enable) { enable_otu_nonce_ = enable; } + // If set to true, reject CreatePermission requests to RFC1918 addresses. + void set_reject_private_addresses(bool filter) { + reject_private_addresses_ = filter; + } + // Starts listening for packets from internal clients. void AddInternalSocket(rtc::AsyncPacketSocket* socket, ProtocolType proto); @@ -255,6 +260,7 @@ class TurnServer : public sigslot::has_slots<> { // otu - one-time-use. Server will respond with 438 if it's // sees the same nonce in next transaction. bool enable_otu_nonce_; + bool reject_private_addresses_ = false; InternalSocketMap server_sockets_; ServerSocketMap server_listen_sockets_; diff --git a/webrtc/p2p/client/basicportallocator.cc b/webrtc/p2p/client/basicportallocator.cc index 21c8921f40..e45d2c8f0f 100644 --- a/webrtc/p2p/client/basicportallocator.cc +++ b/webrtc/p2p/client/basicportallocator.cc @@ -10,6 +10,7 @@ #include "webrtc/p2p/client/basicportallocator.h" +#include <algorithm> #include <string> #include <vector> @@ -70,15 +71,16 @@ BasicPortAllocator::BasicPortAllocator( : network_manager_(network_manager), socket_factory_(socket_factory), stun_servers_() { - ASSERT(socket_factory_ != NULL); + ASSERT(network_manager_ != nullptr); + ASSERT(socket_factory_ != nullptr); Construct(); } -BasicPortAllocator::BasicPortAllocator( - rtc::NetworkManager* network_manager) +BasicPortAllocator::BasicPortAllocator(rtc::NetworkManager* network_manager) : network_manager_(network_manager), - socket_factory_(NULL), + socket_factory_(nullptr), stun_servers_() { + ASSERT(network_manager_ != nullptr); Construct(); } @@ -104,15 +106,19 @@ BasicPortAllocator::BasicPortAllocator( stun_servers_(stun_servers) { RelayServerConfig config(RELAY_GTURN); - if (!relay_address_udp.IsNil()) + if (!relay_address_udp.IsNil()) { config.ports.push_back(ProtocolAddress(relay_address_udp, PROTO_UDP)); - if (!relay_address_tcp.IsNil()) + } + if (!relay_address_tcp.IsNil()) { config.ports.push_back(ProtocolAddress(relay_address_tcp, PROTO_TCP)); - if (!relay_address_ssl.IsNil()) + } + if (!relay_address_ssl.IsNil()) { config.ports.push_back(ProtocolAddress(relay_address_ssl, PROTO_SSLTCP)); + } - if (!config.ports.empty()) - AddRelay(config); + if (!config.ports.empty()) { + AddTurnServer(config); + } Construct(); } @@ -241,8 +247,8 @@ void BasicPortAllocatorSession::GetPortConfigurations() { username(), password()); - for (size_t i = 0; i < allocator_->relays().size(); ++i) { - config->AddRelay(allocator_->relays()[i]); + for (const RelayServerConfig& turn_server : allocator_->turn_servers()) { + config->AddRelay(turn_server); } ConfigReady(config); } @@ -253,8 +259,9 @@ void BasicPortAllocatorSession::ConfigReady(PortConfiguration* config) { // Adds a configuration to the list. void BasicPortAllocatorSession::OnConfigReady(PortConfiguration* config) { - if (config) + if (config) { configs_.push_back(config); + } AllocatePorts(); } @@ -322,6 +329,12 @@ void BasicPortAllocatorSession::GetNetworks( } else { network_manager->GetNetworks(networks); } + networks->erase(std::remove_if(networks->begin(), networks->end(), + [this](rtc::Network* network) { + return allocator_->network_ignore_mask() & + network->type(); + }), + networks->end()); } // For each network, see if we have a sequence that covers it already. If not, @@ -436,7 +449,8 @@ void BasicPortAllocatorSession::AddAllocatedPort(Port* port, // When adapter enumeration is disabled, disable CF_HOST at port level so // local address is not leaked by stunport in the candidate's related address. - if (flags() & PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION) { + if ((flags() & PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION) && + (flags() & PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE)) { candidate_filter &= ~CF_HOST; } port->set_candidate_filter(candidate_filter); @@ -600,25 +614,6 @@ bool BasicPortAllocatorSession::CheckCandidateFilter(const Candidate& c) { return true; } - // If PORTALLOCATOR_ENABLE_LOCALHOST_CANDIDATE is specified and it's - // loopback address, we should allow it as it's for demo page connectivity - // when no TURN/STUN specified. - if (c.address().IsLoopbackIP() && - (flags() & PORTALLOCATOR_ENABLE_LOCALHOST_CANDIDATE) != 0) { - return true; - } - - // This is just to prevent the case when binding to any address (all 0s), if - // somehow the host candidate address is not all 0s. Either because local - // installed proxy changes the address or a packet has been sent for any - // reason before getsockname is called. - if (flags() & PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION) { - LOG(LS_WARNING) << "Received non-0 host address: " - << c.address().ToString() - << " when adapter enumeration is disabled"; - return false; - } - return ((filter & CF_HOST) != 0); } return false; @@ -882,19 +877,19 @@ void AllocationSequence::CreateUDPPorts() { // TODO(mallinath) - Remove UDPPort creating socket after shared socket // is enabled completely. UDPPort* port = NULL; - bool emit_localhost_for_anyaddress = - IsFlagSet(PORTALLOCATOR_ENABLE_LOCALHOST_CANDIDATE); + bool emit_local_candidate_for_anyaddress = + !IsFlagSet(PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE); if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET) && udp_socket_) { port = UDPPort::Create( session_->network_thread(), session_->socket_factory(), network_, udp_socket_.get(), session_->username(), session_->password(), - session_->allocator()->origin(), emit_localhost_for_anyaddress); + session_->allocator()->origin(), emit_local_candidate_for_anyaddress); } else { port = UDPPort::Create( session_->network_thread(), session_->socket_factory(), network_, ip_, session_->allocator()->min_port(), session_->allocator()->max_port(), session_->username(), session_->password(), - session_->allocator()->origin(), emit_localhost_for_anyaddress); + session_->allocator()->origin(), emit_local_candidate_for_anyaddress); } if (port) { diff --git a/webrtc/p2p/client/basicportallocator.h b/webrtc/p2p/client/basicportallocator.h index c8bcad21a9..ca1a23aaf2 100644 --- a/webrtc/p2p/client/basicportallocator.h +++ b/webrtc/p2p/client/basicportallocator.h @@ -14,7 +14,6 @@ #include <string> #include <vector> -#include "webrtc/p2p/base/port.h" #include "webrtc/p2p/base/portallocator.h" #include "webrtc/base/messagequeue.h" #include "webrtc/base/network.h" @@ -23,28 +22,6 @@ namespace cricket { -struct RelayCredentials { - RelayCredentials() {} - RelayCredentials(const std::string& username, - const std::string& password) - : username(username), - password(password) { - } - - std::string username; - std::string password; -}; - -typedef std::vector<ProtocolAddress> PortList; -struct RelayServerConfig { - RelayServerConfig(RelayType type) : type(type), priority(0) {} - - RelayType type; - PortList ports; - RelayCredentials credentials; - int priority; -}; - class BasicPortAllocator : public PortAllocator { public: BasicPortAllocator(rtc::NetworkManager* network_manager, @@ -60,6 +37,23 @@ class BasicPortAllocator : public PortAllocator { const rtc::SocketAddress& relay_server_ssl); virtual ~BasicPortAllocator(); + void SetIceServers( + const ServerAddresses& stun_servers, + const std::vector<RelayServerConfig>& turn_servers) override { + stun_servers_ = stun_servers; + turn_servers_ = turn_servers; + } + + // Set to kDefaultNetworkIgnoreMask by default. + void SetNetworkIgnoreMask(int network_ignore_mask) override { + // TODO(phoglund): implement support for other types than loopback. + // See https://code.google.com/p/webrtc/issues/detail?id=4288. + // Then remove set_network_ignore_list from NetworkManager. + network_ignore_mask_ = network_ignore_mask; + } + + int network_ignore_mask() const { return network_ignore_mask_; } + rtc::NetworkManager* network_manager() { return network_manager_; } // If socket_factory() is set to NULL each PortAllocatorSession @@ -70,27 +64,28 @@ class BasicPortAllocator : public PortAllocator { return stun_servers_; } - const std::vector<RelayServerConfig>& relays() const { - return relays_; + const std::vector<RelayServerConfig>& turn_servers() const { + return turn_servers_; } - virtual void AddRelay(const RelayServerConfig& relay) { - relays_.push_back(relay); + virtual void AddTurnServer(const RelayServerConfig& turn_server) { + turn_servers_.push_back(turn_server); } - virtual PortAllocatorSession* CreateSessionInternal( + PortAllocatorSession* CreateSessionInternal( const std::string& content_name, int component, const std::string& ice_ufrag, - const std::string& ice_pwd); + const std::string& ice_pwd) override; private: void Construct(); rtc::NetworkManager* network_manager_; rtc::PacketSocketFactory* socket_factory_; - const ServerAddresses stun_servers_; - std::vector<RelayServerConfig> relays_; + ServerAddresses stun_servers_; + std::vector<RelayServerConfig> turn_servers_; bool allow_tcp_listen_; + int network_ignore_mask_ = rtc::kDefaultNetworkIgnoreMask; }; struct PortConfiguration; @@ -110,10 +105,10 @@ class BasicPortAllocatorSession : public PortAllocatorSession, rtc::Thread* network_thread() { return network_thread_; } rtc::PacketSocketFactory* socket_factory() { return socket_factory_; } - virtual void StartGettingPorts(); - virtual void StopGettingPorts(); - virtual void ClearGettingPorts(); - virtual bool IsGettingPorts() { return running_; } + void StartGettingPorts() override; + void StopGettingPorts() override; + void ClearGettingPorts() override; + bool IsGettingPorts() override { return running_; } protected: // Starts the process of getting the port configurations. @@ -124,7 +119,7 @@ class BasicPortAllocatorSession : public PortAllocatorSession, virtual void ConfigReady(PortConfiguration* config); // MessageHandler. Can be overriden if message IDs do not conflict. - virtual void OnMessage(rtc::Message *message); + void OnMessage(rtc::Message* message) override; private: class PortData { @@ -204,6 +199,7 @@ class BasicPortAllocatorSession : public PortAllocatorSession, }; // Records configuration information useful in creating ports. +// TODO(deadbeef): Rename "relay" to "turn_server" in this struct. struct PortConfiguration : public rtc::MessageData { // TODO(jiayl): remove |stun_address| when Chrome is updated. rtc::SocketAddress stun_address; diff --git a/webrtc/p2p/client/fakeportallocator.h b/webrtc/p2p/client/fakeportallocator.h index dca86f633e..fb188261a2 100644 --- a/webrtc/p2p/client/fakeportallocator.h +++ b/webrtc/p2p/client/fakeportallocator.h @@ -24,6 +24,62 @@ class Thread; namespace cricket { +class TestUDPPort : public UDPPort { + public: + static TestUDPPort* Create(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + const rtc::IPAddress& ip, + uint16_t min_port, + uint16_t max_port, + const std::string& username, + const std::string& password, + const std::string& origin, + bool emit_localhost_for_anyaddress) { + TestUDPPort* port = new TestUDPPort(thread, factory, network, ip, min_port, + max_port, username, password, origin, + emit_localhost_for_anyaddress); + if (!port->Init()) { + delete port; + port = nullptr; + } + return port; + } + void SendBindingResponse(StunMessage* request, + const rtc::SocketAddress& addr) override { + UDPPort::SendBindingResponse(request, addr); + sent_binding_response_ = true; + } + bool sent_binding_response() { return sent_binding_response_; } + void set_sent_binding_response(bool response) { + sent_binding_response_ = response; + } + + protected: + TestUDPPort(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + const rtc::IPAddress& ip, + uint16_t min_port, + uint16_t max_port, + const std::string& username, + const std::string& password, + const std::string& origin, + bool emit_localhost_for_anyaddress) + : UDPPort(thread, + factory, + network, + ip, + min_port, + max_port, + username, + password, + origin, + emit_localhost_for_anyaddress) {} + + bool sent_binding_response_ = false; +}; + class FakePortAllocatorSession : public PortAllocatorSession { public: FakePortAllocatorSession(rtc::Thread* worker_thread, @@ -45,16 +101,9 @@ class FakePortAllocatorSession : public PortAllocatorSession { virtual void StartGettingPorts() { if (!port_) { - port_.reset(cricket::UDPPort::Create(worker_thread_, - factory_, - &network_, - network_.GetBestIP(), - 0, - 0, - username(), - password(), - std::string(), - false)); + port_.reset(TestUDPPort::Create(worker_thread_, factory_, &network_, + network_.GetBestIP(), 0, 0, username(), + password(), std::string(), false)); AddPort(port_.get()); } ++port_config_count_; @@ -101,11 +150,26 @@ class FakePortAllocator : public cricket::PortAllocator { } } + void SetIceServers( + const ServerAddresses& stun_servers, + const std::vector<RelayServerConfig>& turn_servers) override { + stun_servers_ = stun_servers; + turn_servers_ = turn_servers; + } + + void SetNetworkIgnoreMask(int network_ignore_mask) override {} + + const ServerAddresses& stun_servers() const { return stun_servers_; } + + const std::vector<RelayServerConfig>& turn_servers() const { + return turn_servers_; + } + virtual cricket::PortAllocatorSession* CreateSessionInternal( const std::string& content_name, int component, const std::string& ice_ufrag, - const std::string& ice_pwd) { + const std::string& ice_pwd) override { return new FakePortAllocatorSession( worker_thread_, factory_, content_name, component, ice_ufrag, ice_pwd); } @@ -114,6 +178,8 @@ class FakePortAllocator : public cricket::PortAllocator { rtc::Thread* worker_thread_; rtc::PacketSocketFactory* factory_; rtc::scoped_ptr<rtc::BasicPacketSocketFactory> owned_factory_; + ServerAddresses stun_servers_; + std::vector<RelayServerConfig> turn_servers_; }; } // namespace cricket diff --git a/webrtc/p2p/client/httpportallocator.cc b/webrtc/p2p/client/httpportallocator.cc index a2d5038f90..1342cf70e9 100644 --- a/webrtc/p2p/client/httpportallocator.cc +++ b/webrtc/p2p/client/httpportallocator.cc @@ -13,7 +13,6 @@ #include <algorithm> #include <map> -#include "webrtc/base/basicdefs.h" #include "webrtc/base/common.h" #include "webrtc/base/helpers.h" #include "webrtc/base/httpcommon.h" diff --git a/webrtc/p2p/client/portallocator_unittest.cc b/webrtc/p2p/client/portallocator_unittest.cc index 9617688302..5fce3b5762 100644 --- a/webrtc/p2p/client/portallocator_unittest.cc +++ b/webrtc/p2p/client/portallocator_unittest.cc @@ -32,6 +32,7 @@ #include "webrtc/base/virtualsocketserver.h" using cricket::ServerAddresses; +using rtc::IPAddress; using rtc::SocketAddress; using rtc::Thread; @@ -114,6 +115,17 @@ class PortAllocatorTest : public testing::Test, public sigslot::has_slots<> { void AddInterface(const SocketAddress& addr, const std::string& if_name) { network_manager_.AddInterface(addr, if_name); } + void AddInterface(const SocketAddress& addr, + const std::string& if_name, + rtc::AdapterType type) { + network_manager_.AddInterface(addr, if_name, type); + } + // The default route is the public address that STUN server will observe when + // the endpoint is sitting on the public internet and the local port is bound + // to the "any" address. This may be different from the default local address + // which the endpoint observes. This can occur if the route to the public + // endpoint like 8.8.8.8 (specified as the default local address) is + // different from the route to the STUN server (the default route). void AddInterfaceAsDefaultRoute(const SocketAddress& addr) { AddInterface(addr); // When a binding comes from the any address, the |addr| will be used as the @@ -148,19 +160,19 @@ class PortAllocatorTest : public testing::Test, public sigslot::has_slots<> { void AddTurnServers(const rtc::SocketAddress& udp_turn, const rtc::SocketAddress& tcp_turn) { - cricket::RelayServerConfig relay_server(cricket::RELAY_TURN); + cricket::RelayServerConfig turn_server(cricket::RELAY_TURN); cricket::RelayCredentials credentials(kTurnUsername, kTurnPassword); - relay_server.credentials = credentials; + turn_server.credentials = credentials; if (!udp_turn.IsNil()) { - relay_server.ports.push_back(cricket::ProtocolAddress( - kTurnUdpIntAddr, cricket::PROTO_UDP, false)); + turn_server.ports.push_back( + cricket::ProtocolAddress(kTurnUdpIntAddr, cricket::PROTO_UDP, false)); } if (!tcp_turn.IsNil()) { - relay_server.ports.push_back(cricket::ProtocolAddress( - kTurnTcpIntAddr, cricket::PROTO_TCP, false)); + turn_server.ports.push_back( + cricket::ProtocolAddress(kTurnTcpIntAddr, cricket::PROTO_TCP, false)); } - allocator_->AddRelay(relay_server); + allocator_->AddTurnServer(turn_server); } bool CreateSession(int component) { @@ -254,6 +266,8 @@ class PortAllocatorTest : public testing::Test, public sigslot::has_slots<> { const rtc::IPAddress& stun_candidate_addr, const rtc::IPAddress& relay_candidate_udp_transport_addr, const rtc::IPAddress& relay_candidate_tcp_transport_addr) { + network_manager_.set_default_local_addresses(kPrivateAddr.ipaddr(), + rtc::IPAddress()); if (!session_) { EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); } @@ -268,16 +282,20 @@ class PortAllocatorTest : public testing::Test, public sigslot::has_slots<> { if (!host_candidate_addr.IsNil()) { EXPECT_PRED5(CheckCandidate, candidates_[total_candidates], cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", - rtc::SocketAddress(host_candidate_addr, 0)); + rtc::SocketAddress(kPrivateAddr.ipaddr(), 0)); ++total_candidates; } if (!stun_candidate_addr.IsNil()) { EXPECT_PRED5(CheckCandidate, candidates_[total_candidates], cricket::ICE_CANDIDATE_COMPONENT_RTP, "stun", "udp", rtc::SocketAddress(stun_candidate_addr, 0)); - EXPECT_EQ(rtc::EmptySocketAddressWithFamily( - candidates_[total_candidates].address().family()), - candidates_[total_candidates].related_address()); + rtc::IPAddress related_address = host_candidate_addr; + if (host_candidate_addr.IsNil()) { + related_address = + rtc::GetAnyIP(candidates_[total_candidates].address().family()); + } + EXPECT_EQ(related_address, + candidates_[total_candidates].related_address().ipaddr()); ++total_candidates; } if (!relay_candidate_udp_transport_addr.IsNil()) { @@ -320,8 +338,8 @@ class PortAllocatorTest : public testing::Test, public sigslot::has_slots<> { } bool HasRelayAddress(const cricket::ProtocolAddress& proto_addr) { - for (size_t i = 0; i < allocator_->relays().size(); ++i) { - cricket::RelayServerConfig server_config = allocator_->relays()[i]; + for (size_t i = 0; i < allocator_->turn_servers().size(); ++i) { + cricket::RelayServerConfig server_config = allocator_->turn_servers()[i]; cricket::PortList::const_iterator relay_port; for (relay_port = server_config.ports.begin(); relay_port != server_config.ports.end(); ++relay_port) { @@ -374,11 +392,11 @@ class PortAllocatorTest : public testing::Test, public sigslot::has_slots<> { TEST_F(PortAllocatorTest, TestBasic) { EXPECT_EQ(&network_manager_, allocator().network_manager()); EXPECT_EQ(kStunAddr, *allocator().stun_servers().begin()); - ASSERT_EQ(1u, allocator().relays().size()); - EXPECT_EQ(cricket::RELAY_GTURN, allocator().relays()[0].type); + ASSERT_EQ(1u, allocator().turn_servers().size()); + EXPECT_EQ(cricket::RELAY_GTURN, allocator().turn_servers()[0].type); // Empty relay credentials are used for GTURN. - EXPECT_TRUE(allocator().relays()[0].credentials.username.empty()); - EXPECT_TRUE(allocator().relays()[0].credentials.password.empty()); + EXPECT_TRUE(allocator().turn_servers()[0].credentials.username.empty()); + EXPECT_TRUE(allocator().turn_servers()[0].credentials.password.empty()); EXPECT_TRUE(HasRelayAddress(cricket::ProtocolAddress( kRelayUdpIntAddr, cricket::PROTO_UDP))); EXPECT_TRUE(HasRelayAddress(cricket::ProtocolAddress( @@ -388,6 +406,50 @@ TEST_F(PortAllocatorTest, TestBasic) { EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); } +// Tests that our network filtering works properly. +TEST_F(PortAllocatorTest, TestIgnoreOnlyLoopbackNetworkByDefault) { + AddInterface(SocketAddress(IPAddress(0x12345600U), 0), "test_eth0", + rtc::ADAPTER_TYPE_ETHERNET); + AddInterface(SocketAddress(IPAddress(0x12345601U), 0), "test_wlan0", + rtc::ADAPTER_TYPE_WIFI); + AddInterface(SocketAddress(IPAddress(0x12345602U), 0), "test_cell0", + rtc::ADAPTER_TYPE_CELLULAR); + AddInterface(SocketAddress(IPAddress(0x12345603U), 0), "test_vpn0", + rtc::ADAPTER_TYPE_VPN); + AddInterface(SocketAddress(IPAddress(0x12345604U), 0), "test_lo", + rtc::ADAPTER_TYPE_LOOPBACK); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->set_flags(cricket::PORTALLOCATOR_DISABLE_STUN | + cricket::PORTALLOCATOR_DISABLE_RELAY | + cricket::PORTALLOCATOR_DISABLE_TCP); + session_->StartGettingPorts(); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + EXPECT_EQ(4U, candidates_.size()); + for (cricket::Candidate candidate : candidates_) { + EXPECT_LT(candidate.address().ip(), 0x12345604U); + } +} + +TEST_F(PortAllocatorTest, TestIgnoreNetworksAccordingToIgnoreMask) { + AddInterface(SocketAddress(IPAddress(0x12345600U), 0), "test_eth0", + rtc::ADAPTER_TYPE_ETHERNET); + AddInterface(SocketAddress(IPAddress(0x12345601U), 0), "test_wlan0", + rtc::ADAPTER_TYPE_WIFI); + AddInterface(SocketAddress(IPAddress(0x12345602U), 0), "test_cell0", + rtc::ADAPTER_TYPE_CELLULAR); + allocator_->SetNetworkIgnoreMask(rtc::ADAPTER_TYPE_ETHERNET | + rtc::ADAPTER_TYPE_LOOPBACK | + rtc::ADAPTER_TYPE_WIFI); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->set_flags(cricket::PORTALLOCATOR_DISABLE_STUN | + cricket::PORTALLOCATOR_DISABLE_RELAY | + cricket::PORTALLOCATOR_DISABLE_TCP); + session_->StartGettingPorts(); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + EXPECT_EQ(1U, candidates_.size()); + EXPECT_EQ(0x12345602U, candidates_[0].address().ip()); +} + // Tests that we allocator session not trying to allocate ports for every 250ms. TEST_F(PortAllocatorTest, TestNoNetworkInterface) { EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); @@ -403,7 +465,8 @@ TEST_F(PortAllocatorTest, TestNoNetworkInterface) { // Test that we could use loopback interface as host candidate. TEST_F(PortAllocatorTest, TestLoopbackNetworkInterface) { - AddInterface(kLoopbackAddr); + AddInterface(kLoopbackAddr, "test_loopback", rtc::ADAPTER_TYPE_LOOPBACK); + allocator_->SetNetworkIgnoreMask(0); EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); session_->set_flags(cricket::PORTALLOCATOR_DISABLE_STUN | cricket::PORTALLOCATOR_DISABLE_RELAY | @@ -589,7 +652,6 @@ TEST_F(PortAllocatorTest, TestGetAllPortsNoAdapters) { // candidate_filter() is set to CF_RELAY and no relay is specified. TEST_F(PortAllocatorTest, TestDisableAdapterEnumerationWithoutNatRelayTransportOnly) { - AddInterfaceAsDefaultRoute(kClientAddr); ResetWithStunServerNoNat(kStunAddr); allocator().set_candidate_filter(cricket::CF_RELAY); // Expect to see no ports and no candidates. @@ -597,86 +659,96 @@ TEST_F(PortAllocatorTest, rtc::IPAddress(), rtc::IPAddress()); } -// Test that we should only get STUN and TURN candidates when adapter -// enumeration is disabled. -TEST_F(PortAllocatorTest, TestDisableAdapterEnumerationBehindNat) { - AddInterface(kClientAddr); - // GTURN is not configured here. - ResetWithStunServerAndNat(kStunAddr); - AddTurnServers(kTurnUdpIntAddr, rtc::SocketAddress()); - // Expect to see 3 ports: STUN, TURN/UDP and TCP ports, and both STUN and - // TURN/UDP candidates. - CheckDisableAdapterEnumeration(3U, rtc::IPAddress(), kNatUdpAddr.ipaddr(), - kTurnUdpExtAddr.ipaddr(), rtc::IPAddress()); -} - -// Test that even with multiple interfaces, the result should still be one STUN -// and one TURN candidate since we bind to any address (i.e. all 0s). +// Test that even with multiple interfaces, the result should still be a single +// default private, one STUN and one TURN candidate since we bind to any address +// (i.e. all 0s). TEST_F(PortAllocatorTest, TestDisableAdapterEnumerationBehindNatMultipleInterfaces) { AddInterface(kPrivateAddr); AddInterface(kPrivateAddr2); ResetWithStunServerAndNat(kStunAddr); AddTurnServers(kTurnUdpIntAddr, rtc::SocketAddress()); - // Expect to see 3 ports: STUN, TURN/UDP and TCP ports, and both STUN and + + // Enable IPv6 here. Since the network_manager doesn't have IPv6 default + // address set and we have no IPv6 STUN server, there should be no IPv6 + // candidates. + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->set_flags(cricket::PORTALLOCATOR_ENABLE_IPV6); + + // Expect to see 3 ports for IPv4: HOST/STUN, TURN/UDP and TCP ports, 2 ports + // for IPv6: HOST, and TCP. Only IPv4 candidates: a default private, STUN and // TURN/UDP candidates. - CheckDisableAdapterEnumeration(3U, rtc::IPAddress(), kNatUdpAddr.ipaddr(), - kTurnUdpExtAddr.ipaddr(), rtc::IPAddress()); + CheckDisableAdapterEnumeration(5U, kPrivateAddr.ipaddr(), + kNatUdpAddr.ipaddr(), kTurnUdpExtAddr.ipaddr(), + rtc::IPAddress()); } -// Test that we should get STUN, TURN/UDP and TURN/TCP candidates when a -// TURN/TCP server is specified. +// Test that we should get a default private, STUN, TURN/UDP and TURN/TCP +// candidates when both TURN/UDP and TURN/TCP servers are specified. TEST_F(PortAllocatorTest, TestDisableAdapterEnumerationBehindNatWithTcp) { turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); - AddInterface(kClientAddr); - // GTURN is not configured here. + AddInterface(kPrivateAddr); ResetWithStunServerAndNat(kStunAddr); AddTurnServers(kTurnUdpIntAddr, kTurnTcpIntAddr); - // Expect to see 4 ports - STUN, TURN/UDP, TURN/TCP and TCP port. STUN, - // TURN/UDP, and TURN/TCP candidates. - CheckDisableAdapterEnumeration(4U, rtc::IPAddress(), kNatUdpAddr.ipaddr(), - kTurnUdpExtAddr.ipaddr(), + // Expect to see 4 ports - STUN, TURN/UDP, TURN/TCP and TCP port. A default + // private, STUN, TURN/UDP, and TURN/TCP candidates. + CheckDisableAdapterEnumeration(4U, kPrivateAddr.ipaddr(), + kNatUdpAddr.ipaddr(), kTurnUdpExtAddr.ipaddr(), kTurnUdpExtAddr.ipaddr()); } -// Test that we should only get STUN and TURN candidates when adapter -// enumeration is disabled. Since the endpoint is not behind NAT, the srflx -// address should be the public client interface. -TEST_F(PortAllocatorTest, TestDisableAdapterEnumerationWithoutNat) { - AddInterfaceAsDefaultRoute(kClientAddr); - ResetWithStunServerNoNat(kStunAddr); - AddTurnServers(kTurnUdpIntAddr, rtc::SocketAddress()); - // Expect to see 3 ports: STUN, TURN/UDP and TCP ports, but only both STUN and - // TURN candidates. The STUN candidate should have kClientAddr as srflx - // address, and TURN candidate with kClientAddr as the related address. - CheckDisableAdapterEnumeration(3U, rtc::IPAddress(), kClientAddr.ipaddr(), - kTurnUdpExtAddr.ipaddr(), rtc::IPAddress()); -} - // Test that when adapter enumeration is disabled, for endpoints without -// STUN/TURN specified, no candidate is generated. +// STUN/TURN specified, a default private candidate is still generated. TEST_F(PortAllocatorTest, TestDisableAdapterEnumerationWithoutNatOrServers) { - AddInterfaceAsDefaultRoute(kClientAddr); ResetWithNoServersOrNat(); - // Expect to see 2 ports: STUN and TCP ports, but no candidate. - CheckDisableAdapterEnumeration(2U, rtc::IPAddress(), rtc::IPAddress(), + // Expect to see 2 ports: STUN and TCP ports, one default private candidate. + CheckDisableAdapterEnumeration(2U, kPrivateAddr.ipaddr(), rtc::IPAddress(), rtc::IPAddress(), rtc::IPAddress()); } // Test that when adapter enumeration is disabled, with -// PORTALLOCATOR_ENABLE_LOCALHOST_CANDIDATE specified, for endpoints not behind -// a NAT, there are a localhost candidate in addition to a STUN candidate. +// PORTALLOCATOR_DISABLE_LOCALHOST_CANDIDATE specified, for endpoints not behind +// a NAT, there is no local candidate. TEST_F(PortAllocatorTest, - TestDisableAdapterEnumerationWithoutNatLocalhostCandidateRequested) { - AddInterfaceAsDefaultRoute(kClientAddr); + TestDisableAdapterEnumerationWithoutNatLocalhostCandidateDisabled) { ResetWithStunServerNoNat(kStunAddr); EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); - session_->set_flags(cricket::PORTALLOCATOR_ENABLE_LOCALHOST_CANDIDATE); + session_->set_flags(cricket::PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE); // Expect to see 2 ports: STUN and TCP ports, localhost candidate and STUN // candidate. - CheckDisableAdapterEnumeration(2U, rtc::GetLoopbackIP(AF_INET), - kClientAddr.ipaddr(), rtc::IPAddress(), - rtc::IPAddress()); + CheckDisableAdapterEnumeration(2U, rtc::IPAddress(), rtc::IPAddress(), + rtc::IPAddress(), rtc::IPAddress()); +} + +// Test that when adapter enumeration is disabled, with +// PORTALLOCATOR_DISABLE_LOCALHOST_CANDIDATE specified, for endpoints not behind +// a NAT, there is no local candidate. However, this specified default route +// (kClientAddr) which was discovered when sending STUN requests, will become +// the srflx addresses. +TEST_F( + PortAllocatorTest, + TestDisableAdapterEnumerationWithoutNatLocalhostCandidateDisabledWithDifferentDefaultRoute) { + ResetWithStunServerNoNat(kStunAddr); + AddInterfaceAsDefaultRoute(kClientAddr); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->set_flags(cricket::PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE); + // Expect to see 2 ports: STUN and TCP ports, localhost candidate and STUN + // candidate. + CheckDisableAdapterEnumeration(2U, rtc::IPAddress(), kClientAddr.ipaddr(), + rtc::IPAddress(), rtc::IPAddress()); +} + +// Test that when adapter enumeration is disabled, with +// PORTALLOCATOR_DISABLE_LOCALHOST_CANDIDATE specified, for endpoints behind a +// NAT, there is only one STUN candidate. +TEST_F(PortAllocatorTest, + TestDisableAdapterEnumerationWithNatLocalhostCandidateDisabled) { + ResetWithStunServerAndNat(kStunAddr); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->set_flags(cricket::PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE); + // Expect to see 2 ports: STUN and TCP ports, and single STUN candidate. + CheckDisableAdapterEnumeration(2U, rtc::IPAddress(), kNatUdpAddr.ipaddr(), + rtc::IPAddress(), rtc::IPAddress()); } // Test that we disable relay over UDP, and only TCP is used when connecting to @@ -1026,13 +1098,12 @@ TEST_F(PortAllocatorTest, TestSharedSocketWithServerAddressResolve) { cricket::PROTO_UDP); AddInterface(kClientAddr); allocator_.reset(new cricket::BasicPortAllocator(&network_manager_)); - cricket::RelayServerConfig relay_server(cricket::RELAY_TURN); + cricket::RelayServerConfig turn_server(cricket::RELAY_TURN); cricket::RelayCredentials credentials(kTurnUsername, kTurnPassword); - relay_server.credentials = credentials; - relay_server.ports.push_back(cricket::ProtocolAddress( - rtc::SocketAddress("localhost", 3478), - cricket::PROTO_UDP, false)); - allocator_->AddRelay(relay_server); + turn_server.credentials = credentials; + turn_server.ports.push_back(cricket::ProtocolAddress( + rtc::SocketAddress("localhost", 3478), cricket::PROTO_UDP, false)); + allocator_->AddTurnServer(turn_server); allocator_->set_step_delay(cricket::kMinimumStepDelay); allocator_->set_flags(allocator().flags() | @@ -1244,7 +1315,8 @@ TEST_F(PortAllocatorTest, TestSharedSocketNoUdpAllowed) { // adapters, the PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION is specified // automatically. TEST_F(PortAllocatorTest, TestNetworkPermissionBlocked) { - AddInterface(kClientAddr); + network_manager_.set_default_local_addresses(kPrivateAddr.ipaddr(), + rtc::IPAddress()); network_manager_.set_enumeration_permission( rtc::NetworkManager::ENUMERATION_BLOCKED); allocator().set_flags(allocator().flags() | @@ -1258,7 +1330,10 @@ TEST_F(PortAllocatorTest, TestNetworkPermissionBlocked) { cricket::PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION); session_->StartGettingPorts(); EXPECT_EQ_WAIT(1U, ports_.size(), kDefaultAllocationTimeout); - EXPECT_EQ(0U, candidates_.size()); + EXPECT_EQ(1U, candidates_.size()); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", + kPrivateAddr); EXPECT_TRUE((session_->flags() & cricket::PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION) != 0); } diff --git a/webrtc/p2p/stunprober/stunprober.cc b/webrtc/p2p/stunprober/stunprober.cc index ee9eb2258c..9316ea89bd 100644 --- a/webrtc/p2p/stunprober/stunprober.cc +++ b/webrtc/p2p/stunprober/stunprober.cc @@ -460,6 +460,7 @@ bool StunProber::GetStats(StunProber::Stats* prob_stats) const { continue; } + ++stats.raw_num_request_sent; IncrementCounterByAddress(&num_request_per_server, request->server_addr); if (!first_sent_time) { @@ -503,11 +504,6 @@ bool StunProber::GetStats(StunProber::Stats* prob_stats) const { num_sent += num_request_per_server[kv.first]; } - // Not receiving any response, the trial is inconclusive. - if (!num_received) { - return false; - } - // Shared mode is only true if we use the shared socket and there are more // than 1 responding servers. stats.shared_socket_mode = @@ -519,7 +515,8 @@ bool StunProber::GetStats(StunProber::Stats* prob_stats) const { // If we could find a local IP matching srflx, we're not behind a NAT. rtc::SocketAddress srflx_addr; - if (!srflx_addr.FromString(*(stats.srflx_addrs.begin()))) { + if (stats.srflx_addrs.size() && + !srflx_addr.FromString(*(stats.srflx_addrs.begin()))) { return false; } for (const auto& net : networks_) { @@ -544,9 +541,10 @@ bool StunProber::GetStats(StunProber::Stats* prob_stats) const { stats.success_percent = static_cast<int>(100 * num_received / num_sent); } - if (num_sent > 1) { + if (stats.raw_num_request_sent > 1) { stats.actual_request_interval_ns = - (1000 * (last_sent_time - first_sent_time)) / (num_sent - 1); + (1000 * (last_sent_time - first_sent_time)) / + (stats.raw_num_request_sent - 1); } if (num_received) { diff --git a/webrtc/p2p/stunprober/stunprober.h b/webrtc/p2p/stunprober/stunprober.h index 9d2ad222e5..b725cbef0a 100644 --- a/webrtc/p2p/stunprober/stunprober.h +++ b/webrtc/p2p/stunprober/stunprober.h @@ -71,7 +71,14 @@ class StunProber : public sigslot::has_slots<> { struct Stats { Stats() {} + // |raw_num_request_sent| is the total number of requests + // sent. |num_request_sent| is the count of requests against a server where + // we see at least one response. |num_request_sent| is designed to protect + // against DNS resolution failure or the STUN server is not responsive + // which could skew the result. + int raw_num_request_sent = 0; int num_request_sent = 0; + int num_response_received = 0; NatType nat_type = NATTYPE_INVALID; int average_rtt_ms = -1; |