| /* |
| * Copyright 2022 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| // This file is intended for PeerConnection integration tests that are |
| // slow to execute (currently defined as more than 5 seconds per test). |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/algorithm/container.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/optional.h" |
| #include "api/dtmf_sender_interface.h" |
| #include "api/peer_connection_interface.h" |
| #include "api/rtp_receiver_interface.h" |
| #include "api/scoped_refptr.h" |
| #include "api/units/time_delta.h" |
| #include "p2p/base/port_allocator.h" |
| #include "p2p/base/port_interface.h" |
| #include "p2p/base/stun_server.h" |
| #include "p2p/base/test_stun_server.h" |
| #include "pc/test/integration_test_helpers.h" |
| #include "pc/test/mock_peer_connection_observers.h" |
| #include "rtc_base/fake_clock.h" |
| #include "rtc_base/fake_network.h" |
| #include "rtc_base/firewall_socket_server.h" |
| #include "rtc_base/gunit.h" |
| #include "rtc_base/logging.h" |
| #include "rtc_base/socket_address.h" |
| #include "rtc_base/ssl_certificate.h" |
| #include "rtc_base/test_certificate_verifier.h" |
| #include "test/gmock.h" |
| #include "test/gtest.h" |
| |
| namespace webrtc { |
| |
| namespace { |
| |
| class PeerConnectionIntegrationTest |
| : public PeerConnectionIntegrationBaseTest, |
| public ::testing::WithParamInterface<SdpSemantics> { |
| protected: |
| PeerConnectionIntegrationTest() |
| : PeerConnectionIntegrationBaseTest(GetParam()) {} |
| }; |
| |
| // Fake clock must be set before threads are started to prevent race on |
| // Set/GetClockForTesting(). |
| // To achieve that, multiple inheritance is used as a mixin pattern |
| // where order of construction is finely controlled. |
| // This also ensures peerconnection is closed before switching back to non-fake |
| // clock, avoiding other races and DCHECK failures such as in rtp_sender.cc. |
| class FakeClockForTest : public rtc::ScopedFakeClock { |
| protected: |
| FakeClockForTest() { |
| // Some things use a time of "0" as a special value, so we need to start out |
| // the fake clock at a nonzero time. |
| // TODO(deadbeef): Fix this. |
| AdvanceTime(TimeDelta::Seconds(1000)); |
| } |
| |
| // Explicit handle. |
| ScopedFakeClock& FakeClock() { return *this; } |
| }; |
| |
| // Ensure FakeClockForTest is constructed first (see class for rationale). |
| class PeerConnectionIntegrationTestWithFakeClock |
| : public FakeClockForTest, |
| public PeerConnectionIntegrationTest {}; |
| |
| class PeerConnectionIntegrationTestPlanB |
| : public PeerConnectionIntegrationBaseTest { |
| protected: |
| PeerConnectionIntegrationTestPlanB() |
| : PeerConnectionIntegrationBaseTest(SdpSemantics::kPlanB_DEPRECATED) {} |
| }; |
| |
| class PeerConnectionIntegrationTestUnifiedPlan |
| : public PeerConnectionIntegrationBaseTest { |
| protected: |
| PeerConnectionIntegrationTestUnifiedPlan() |
| : PeerConnectionIntegrationBaseTest(SdpSemantics::kUnifiedPlan) {} |
| }; |
| |
| // Test the OnFirstPacketReceived callback from audio/video RtpReceivers. This |
| // includes testing that the callback is invoked if an observer is connected |
| // after the first packet has already been received. |
| TEST_P(PeerConnectionIntegrationTest, |
| RtpReceiverObserverOnFirstPacketReceived) { |
| ASSERT_TRUE(CreatePeerConnectionWrappers()); |
| ConnectFakeSignaling(); |
| caller()->AddAudioVideoTracks(); |
| callee()->AddAudioVideoTracks(); |
| // Start offer/answer exchange and wait for it to complete. |
| caller()->CreateAndSetAndSignalOffer(); |
| ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); |
| // Should be one receiver each for audio/video. |
| EXPECT_EQ(2U, caller()->rtp_receiver_observers().size()); |
| EXPECT_EQ(2U, callee()->rtp_receiver_observers().size()); |
| // Wait for all "first packet received" callbacks to be fired. |
| EXPECT_TRUE_WAIT( |
| absl::c_all_of(caller()->rtp_receiver_observers(), |
| [](const std::unique_ptr<MockRtpReceiverObserver>& o) { |
| return o->first_packet_received(); |
| }), |
| kMaxWaitForFramesMs); |
| EXPECT_TRUE_WAIT( |
| absl::c_all_of(callee()->rtp_receiver_observers(), |
| [](const std::unique_ptr<MockRtpReceiverObserver>& o) { |
| return o->first_packet_received(); |
| }), |
| kMaxWaitForFramesMs); |
| // If new observers are set after the first packet was already received, the |
| // callback should still be invoked. |
| caller()->ResetRtpReceiverObservers(); |
| callee()->ResetRtpReceiverObservers(); |
| EXPECT_EQ(2U, caller()->rtp_receiver_observers().size()); |
| EXPECT_EQ(2U, callee()->rtp_receiver_observers().size()); |
| EXPECT_TRUE( |
| absl::c_all_of(caller()->rtp_receiver_observers(), |
| [](const std::unique_ptr<MockRtpReceiverObserver>& o) { |
| return o->first_packet_received(); |
| })); |
| EXPECT_TRUE( |
| absl::c_all_of(callee()->rtp_receiver_observers(), |
| [](const std::unique_ptr<MockRtpReceiverObserver>& o) { |
| return o->first_packet_received(); |
| })); |
| } |
| |
| class DummyDtmfObserver : public DtmfSenderObserverInterface { |
| public: |
| DummyDtmfObserver() : completed_(false) {} |
| |
| // Implements DtmfSenderObserverInterface. |
| void OnToneChange(const std::string& tone) override { |
| tones_.push_back(tone); |
| if (tone.empty()) { |
| completed_ = true; |
| } |
| } |
| |
| const std::vector<std::string>& tones() const { return tones_; } |
| bool completed() const { return completed_; } |
| |
| private: |
| bool completed_; |
| std::vector<std::string> tones_; |
| }; |
| |
| TEST_P(PeerConnectionIntegrationTest, |
| SSLCertificateVerifierFailureUsedForTurnConnectionsFailsConnection) { |
| static const rtc::SocketAddress turn_server_internal_address{"88.88.88.0", |
| 3478}; |
| static const rtc::SocketAddress turn_server_external_address{"88.88.88.1", 0}; |
| |
| // Enable TCP-TLS for the fake turn server. We need to pass in 88.88.88.0 so |
| // that host name verification passes on the fake certificate. |
| CreateTurnServer(turn_server_internal_address, turn_server_external_address, |
| cricket::PROTO_TLS, "88.88.88.0"); |
| |
| PeerConnectionInterface::IceServer ice_server; |
| ice_server.urls.push_back("turns:88.88.88.0:3478?transport=tcp"); |
| ice_server.username = "test"; |
| ice_server.password = "test"; |
| |
| PeerConnectionInterface::RTCConfiguration client_1_config; |
| client_1_config.servers.push_back(ice_server); |
| client_1_config.type = PeerConnectionInterface::kRelay; |
| |
| PeerConnectionInterface::RTCConfiguration client_2_config; |
| client_2_config.servers.push_back(ice_server); |
| // Setting the type to kRelay forces the connection to go through a TURN |
| // server. |
| client_2_config.type = PeerConnectionInterface::kRelay; |
| |
| // Get a copy to the pointer so we can verify calls later. |
| rtc::TestCertificateVerifier* client_1_cert_verifier = |
| new rtc::TestCertificateVerifier(); |
| client_1_cert_verifier->verify_certificate_ = false; |
| rtc::TestCertificateVerifier* client_2_cert_verifier = |
| new rtc::TestCertificateVerifier(); |
| client_2_cert_verifier->verify_certificate_ = false; |
| |
| // Create the dependencies with the test certificate verifier. |
| PeerConnectionDependencies client_1_deps(nullptr); |
| client_1_deps.tls_cert_verifier = |
| std::unique_ptr<rtc::TestCertificateVerifier>(client_1_cert_verifier); |
| PeerConnectionDependencies client_2_deps(nullptr); |
| client_2_deps.tls_cert_verifier = |
| std::unique_ptr<rtc::TestCertificateVerifier>(client_2_cert_verifier); |
| |
| ASSERT_TRUE(CreatePeerConnectionWrappersWithConfigAndDeps( |
| client_1_config, std::move(client_1_deps), client_2_config, |
| std::move(client_2_deps))); |
| ConnectFakeSignaling(); |
| |
| // Set "offer to receive audio/video" without adding any tracks, so we just |
| // set up ICE/DTLS with no media. |
| PeerConnectionInterface::RTCOfferAnswerOptions options; |
| options.offer_to_receive_audio = 1; |
| options.offer_to_receive_video = 1; |
| caller()->SetOfferAnswerOptions(options); |
| caller()->CreateAndSetAndSignalOffer(); |
| bool wait_res = true; |
| // TODO(bugs.webrtc.org/9219): When IceConnectionState is implemented |
| // properly, should be able to just wait for a state of "failed" instead of |
| // waiting a fixed 10 seconds. |
| WAIT_(DtlsConnected(), kDefaultTimeout, wait_res); |
| ASSERT_FALSE(wait_res); |
| |
| EXPECT_GT(client_1_cert_verifier->call_count_, 0u); |
| EXPECT_GT(client_2_cert_verifier->call_count_, 0u); |
| } |
| |
| // Test that we can get capture start ntp time. |
| TEST_P(PeerConnectionIntegrationTest, GetCaptureStartNtpTimeWithOldStatsApi) { |
| ASSERT_TRUE(CreatePeerConnectionWrappers()); |
| ConnectFakeSignaling(); |
| caller()->AddAudioTrack(); |
| |
| callee()->AddAudioTrack(); |
| |
| // Do offer/answer, wait for the callee to receive some frames. |
| caller()->CreateAndSetAndSignalOffer(); |
| ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); |
| |
| // Get the remote audio track created on the receiver, so they can be used as |
| // GetStats filters. |
| auto receivers = callee()->pc()->GetReceivers(); |
| ASSERT_EQ(1u, receivers.size()); |
| auto remote_audio_track = receivers[0]->track(); |
| |
| // Get the audio output level stats. Note that the level is not available |
| // until an RTCP packet has been received. |
| EXPECT_TRUE_WAIT(callee()->OldGetStatsForTrack(remote_audio_track.get()) |
| ->CaptureStartNtpTime() > 0, |
| 2 * kMaxWaitForFramesMs); |
| } |
| |
| // Test that firewalling the ICE connection causes the clients to identify the |
| // disconnected state and then removing the firewall causes them to reconnect. |
| class PeerConnectionIntegrationIceStatesTest |
| : public PeerConnectionIntegrationBaseTest, |
| public ::testing::WithParamInterface< |
| std::tuple<SdpSemantics, std::tuple<std::string, uint32_t>>> { |
| protected: |
| PeerConnectionIntegrationIceStatesTest() |
| : PeerConnectionIntegrationBaseTest(std::get<0>(GetParam())) { |
| port_allocator_flags_ = std::get<1>(std::get<1>(GetParam())); |
| } |
| |
| void StartStunServer(const SocketAddress& server_address) { |
| stun_server_.reset( |
| cricket::TestStunServer::Create(firewall(), server_address)); |
| } |
| |
| bool TestIPv6() { |
| return (port_allocator_flags_ & cricket::PORTALLOCATOR_ENABLE_IPV6); |
| } |
| |
| void SetPortAllocatorFlags() { |
| PeerConnectionIntegrationBaseTest::SetPortAllocatorFlags( |
| port_allocator_flags_, port_allocator_flags_); |
| } |
| |
| std::vector<SocketAddress> CallerAddresses() { |
| std::vector<SocketAddress> addresses; |
| addresses.push_back(SocketAddress("1.1.1.1", 0)); |
| if (TestIPv6()) { |
| addresses.push_back(SocketAddress("1111:0:a:b:c:d:e:f", 0)); |
| } |
| return addresses; |
| } |
| |
| std::vector<SocketAddress> CalleeAddresses() { |
| std::vector<SocketAddress> addresses; |
| addresses.push_back(SocketAddress("2.2.2.2", 0)); |
| if (TestIPv6()) { |
| addresses.push_back(SocketAddress("2222:0:a:b:c:d:e:f", 0)); |
| } |
| return addresses; |
| } |
| |
| void SetUpNetworkInterfaces() { |
| // Remove the default interfaces added by the test infrastructure. |
| caller()->network_manager()->RemoveInterface(kDefaultLocalAddress); |
| callee()->network_manager()->RemoveInterface(kDefaultLocalAddress); |
| |
| // Add network addresses for test. |
| for (const auto& caller_address : CallerAddresses()) { |
| caller()->network_manager()->AddInterface(caller_address); |
| } |
| for (const auto& callee_address : CalleeAddresses()) { |
| callee()->network_manager()->AddInterface(callee_address); |
| } |
| } |
| |
| private: |
| uint32_t port_allocator_flags_; |
| std::unique_ptr<cricket::TestStunServer> stun_server_; |
| }; |
| |
| // Ensure FakeClockForTest is constructed first (see class for rationale). |
| class PeerConnectionIntegrationIceStatesTestWithFakeClock |
| : public FakeClockForTest, |
| public PeerConnectionIntegrationIceStatesTest {}; |
| |
| #if !defined(THREAD_SANITIZER) |
| // This test provokes TSAN errors. bugs.webrtc.org/11282 |
| |
| // Tests that the PeerConnection goes through all the ICE gathering/connection |
| // states over the duration of the call. This includes Disconnected and Failed |
| // states, induced by putting a firewall between the peers and waiting for them |
| // to time out. |
| TEST_P(PeerConnectionIntegrationIceStatesTestWithFakeClock, VerifyIceStates) { |
| const SocketAddress kStunServerAddress = |
| SocketAddress("99.99.99.1", cricket::STUN_SERVER_PORT); |
| StartStunServer(kStunServerAddress); |
| |
| PeerConnectionInterface::RTCConfiguration config; |
| PeerConnectionInterface::IceServer ice_stun_server; |
| ice_stun_server.urls.push_back( |
| "stun:" + kStunServerAddress.HostAsURIString() + ":" + |
| kStunServerAddress.PortAsString()); |
| config.servers.push_back(ice_stun_server); |
| |
| ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config)); |
| ConnectFakeSignaling(); |
| SetPortAllocatorFlags(); |
| SetUpNetworkInterfaces(); |
| caller()->AddAudioVideoTracks(); |
| callee()->AddAudioVideoTracks(); |
| |
| // Initial state before anything happens. |
| ASSERT_EQ(PeerConnectionInterface::kIceGatheringNew, |
| caller()->ice_gathering_state()); |
| ASSERT_EQ(PeerConnectionInterface::kIceConnectionNew, |
| caller()->ice_connection_state()); |
| ASSERT_EQ(PeerConnectionInterface::kIceConnectionNew, |
| caller()->standardized_ice_connection_state()); |
| |
| // Start the call by creating the offer, setting it as the local description, |
| // then sending it to the peer who will respond with an answer. This happens |
| // asynchronously so that we can watch the states as it runs in the |
| // background. |
| caller()->CreateAndSetAndSignalOffer(); |
| |
| ASSERT_EQ_SIMULATED_WAIT(PeerConnectionInterface::kIceConnectionCompleted, |
| caller()->ice_connection_state(), kDefaultTimeout, |
| FakeClock()); |
| ASSERT_EQ_SIMULATED_WAIT(PeerConnectionInterface::kIceConnectionCompleted, |
| caller()->standardized_ice_connection_state(), |
| kDefaultTimeout, FakeClock()); |
| |
| // Verify that the observer was notified of the intermediate transitions. |
| EXPECT_THAT(caller()->ice_connection_state_history(), |
| ElementsAre(PeerConnectionInterface::kIceConnectionChecking, |
| PeerConnectionInterface::kIceConnectionConnected, |
| PeerConnectionInterface::kIceConnectionCompleted)); |
| EXPECT_THAT(caller()->standardized_ice_connection_state_history(), |
| ElementsAre(PeerConnectionInterface::kIceConnectionChecking, |
| PeerConnectionInterface::kIceConnectionConnected, |
| PeerConnectionInterface::kIceConnectionCompleted)); |
| EXPECT_THAT( |
| caller()->peer_connection_state_history(), |
| ElementsAre(PeerConnectionInterface::PeerConnectionState::kConnecting, |
| PeerConnectionInterface::PeerConnectionState::kConnected)); |
| EXPECT_THAT(caller()->ice_gathering_state_history(), |
| ElementsAre(PeerConnectionInterface::kIceGatheringGathering, |
| PeerConnectionInterface::kIceGatheringComplete)); |
| |
| // Block connections to/from the caller and wait for ICE to become |
| // disconnected. |
| for (const auto& caller_address : CallerAddresses()) { |
| firewall()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, caller_address); |
| } |
| RTC_LOG(LS_INFO) << "Firewall rules applied"; |
| ASSERT_EQ_SIMULATED_WAIT(PeerConnectionInterface::kIceConnectionDisconnected, |
| caller()->ice_connection_state(), kDefaultTimeout, |
| FakeClock()); |
| ASSERT_EQ_SIMULATED_WAIT(PeerConnectionInterface::kIceConnectionDisconnected, |
| caller()->standardized_ice_connection_state(), |
| kDefaultTimeout, FakeClock()); |
| |
| // Let ICE re-establish by removing the firewall rules. |
| firewall()->ClearRules(); |
| RTC_LOG(LS_INFO) << "Firewall rules cleared"; |
| ASSERT_EQ_SIMULATED_WAIT(PeerConnectionInterface::kIceConnectionCompleted, |
| caller()->ice_connection_state(), kDefaultTimeout, |
| FakeClock()); |
| ASSERT_EQ_SIMULATED_WAIT(PeerConnectionInterface::kIceConnectionCompleted, |
| caller()->standardized_ice_connection_state(), |
| kDefaultTimeout, FakeClock()); |
| |
| // According to RFC7675, if there is no response within 30 seconds then the |
| // peer should consider the other side to have rejected the connection. This |
| // is signaled by the state transitioning to "failed". |
| constexpr int kConsentTimeout = 30000; |
| for (const auto& caller_address : CallerAddresses()) { |
| firewall()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, caller_address); |
| } |
| RTC_LOG(LS_INFO) << "Firewall rules applied again"; |
| ASSERT_EQ_SIMULATED_WAIT(PeerConnectionInterface::kIceConnectionFailed, |
| caller()->ice_connection_state(), kConsentTimeout, |
| FakeClock()); |
| ASSERT_EQ_SIMULATED_WAIT(PeerConnectionInterface::kIceConnectionFailed, |
| caller()->standardized_ice_connection_state(), |
| kConsentTimeout, FakeClock()); |
| } |
| #endif |
| |
| // This test sets up a call that's transferred to a new caller with a different |
| // DTLS fingerprint. |
| TEST_P(PeerConnectionIntegrationTest, CallTransferredForCallee) { |
| ASSERT_TRUE(CreatePeerConnectionWrappers()); |
| ConnectFakeSignaling(); |
| caller()->AddAudioVideoTracks(); |
| callee()->AddAudioVideoTracks(); |
| caller()->CreateAndSetAndSignalOffer(); |
| ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); |
| |
| // Keep the original peer around which will still send packets to the |
| // receiving client. These SRTP packets will be dropped. |
| std::unique_ptr<PeerConnectionIntegrationWrapper> original_peer( |
| SetCallerPcWrapperAndReturnCurrent( |
| CreatePeerConnectionWrapperWithAlternateKey().release())); |
| // TODO(deadbeef): Why do we call Close here? That goes against the comment |
| // directly above. |
| original_peer->pc()->Close(); |
| |
| ConnectFakeSignaling(); |
| caller()->AddAudioVideoTracks(); |
| caller()->CreateAndSetAndSignalOffer(); |
| ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); |
| // Wait for some additional frames to be transmitted end-to-end. |
| MediaExpectations media_expectations; |
| media_expectations.ExpectBidirectionalAudioAndVideo(); |
| ASSERT_TRUE(ExpectNewFrames(media_expectations)); |
| } |
| |
| // This test sets up a call that's transferred to a new callee with a different |
| // DTLS fingerprint. |
| TEST_P(PeerConnectionIntegrationTest, CallTransferredForCaller) { |
| ASSERT_TRUE(CreatePeerConnectionWrappers()); |
| ConnectFakeSignaling(); |
| caller()->AddAudioVideoTracks(); |
| callee()->AddAudioVideoTracks(); |
| caller()->CreateAndSetAndSignalOffer(); |
| ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); |
| |
| // Keep the original peer around which will still send packets to the |
| // receiving client. These SRTP packets will be dropped. |
| std::unique_ptr<PeerConnectionIntegrationWrapper> original_peer( |
| SetCalleePcWrapperAndReturnCurrent( |
| CreatePeerConnectionWrapperWithAlternateKey().release())); |
| // TODO(deadbeef): Why do we call Close here? That goes against the comment |
| // directly above. |
| original_peer->pc()->Close(); |
| |
| ConnectFakeSignaling(); |
| callee()->AddAudioVideoTracks(); |
| caller()->SetOfferAnswerOptions(IceRestartOfferAnswerOptions()); |
| caller()->CreateAndSetAndSignalOffer(); |
| ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); |
| // Wait for some additional frames to be transmitted end-to-end. |
| MediaExpectations media_expectations; |
| media_expectations.ExpectBidirectionalAudioAndVideo(); |
| ASSERT_TRUE(ExpectNewFrames(media_expectations)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(PeerConnectionIntegrationTest, |
| PeerConnectionIntegrationTest, |
| Values(SdpSemantics::kPlanB_DEPRECATED, |
| SdpSemantics::kUnifiedPlan)); |
| |
| constexpr uint32_t kFlagsIPv4NoStun = cricket::PORTALLOCATOR_DISABLE_TCP | |
| cricket::PORTALLOCATOR_DISABLE_STUN | |
| cricket::PORTALLOCATOR_DISABLE_RELAY; |
| constexpr uint32_t kFlagsIPv6NoStun = |
| cricket::PORTALLOCATOR_DISABLE_TCP | cricket::PORTALLOCATOR_DISABLE_STUN | |
| cricket::PORTALLOCATOR_ENABLE_IPV6 | cricket::PORTALLOCATOR_DISABLE_RELAY; |
| constexpr uint32_t kFlagsIPv4Stun = |
| cricket::PORTALLOCATOR_DISABLE_TCP | cricket::PORTALLOCATOR_DISABLE_RELAY; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| PeerConnectionIntegrationTest, |
| PeerConnectionIntegrationIceStatesTestWithFakeClock, |
| Combine(Values(SdpSemantics::kPlanB_DEPRECATED, SdpSemantics::kUnifiedPlan), |
| Values(std::make_pair("IPv4 no STUN", kFlagsIPv4NoStun), |
| std::make_pair("IPv6 no STUN", kFlagsIPv6NoStun), |
| std::make_pair("IPv4 with STUN", kFlagsIPv4Stun)))); |
| |
| } // namespace |
| } // namespace webrtc |