Rewrite WebRtcSession ICE integration tests as PeerConnection tests
Bug: webrtc:8222
Change-Id: I12d28b2016598f94602a273a82de70d6fc0e682f
Reviewed-on: https://webrtc-review.googlesource.com/7020
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Commit-Queue: Steve Anton <steveanton@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20318}
diff --git a/pc/peerconnection_integrationtest.cc b/pc/peerconnection_integrationtest.cc
index c162408..21701ce 100644
--- a/pc/peerconnection_integrationtest.cc
+++ b/pc/peerconnection_integrationtest.cc
@@ -31,6 +31,7 @@
#include "p2p/base/p2pconstants.h"
#include "p2p/base/portinterface.h"
#include "p2p/base/sessiondescription.h"
+#include "p2p/base/teststunserver.h"
#include "p2p/base/testturncustomizer.h"
#include "p2p/base/testturnserver.h"
#include "p2p/client/basicportallocator.h"
@@ -45,6 +46,7 @@
#include "pc/test/fakevideotrackrenderer.h"
#include "pc/test/mockpeerconnectionobservers.h"
#include "rtc_base/fakenetwork.h"
+#include "rtc_base/firewallsocketserver.h"
#include "rtc_base/gunit.h"
#include "rtc_base/virtualsocketserver.h"
#include "test/gmock.h"
@@ -55,6 +57,9 @@
using cricket::FakeWebRtcVideoEncoder;
using cricket::FakeWebRtcVideoEncoderFactory;
using cricket::MediaContentDescription;
+using rtc::SocketAddress;
+using ::testing::ElementsAre;
+using ::testing::Values;
using webrtc::DataBuffer;
using webrtc::DataChannelInterface;
using webrtc::DtmfSender;
@@ -94,6 +99,8 @@
static const int kDefaultSrtpCryptoSuite = rtc::SRTP_AES128_CM_SHA1_32;
static const int kDefaultSrtpCryptoSuiteGcm = rtc::SRTP_AEAD_AES_256_GCM;
+static const SocketAddress kDefaultLocalAddress("192.168.1.1", 0);
+
// Helper function for constructing offer/answer options to initiate an ICE
// restart.
PeerConnectionInterface::RTCOfferAnswerOptions IceRestartOfferAnswerOptions() {
@@ -277,10 +284,16 @@
generated_sdp_munger_ = munger;
}
- // Number of times the gathering state has transitioned to "gathering".
- // Useful for telling if an ICE restart occurred as expected.
- int transitions_to_gathering_state() const {
- return transitions_to_gathering_state_;
+ // Every ICE connection state in order that has been seen by the observer.
+ std::vector<PeerConnectionInterface::IceConnectionState>
+ ice_connection_state_history() const {
+ return ice_connection_state_history_;
+ }
+
+ // Every ICE gathering state in order that has been seen by the observer.
+ std::vector<PeerConnectionInterface::IceGatheringState>
+ ice_gathering_state_history() const {
+ return ice_gathering_state_history_;
}
// TODO(deadbeef): Switch the majority of these tests to use AddTrack instead
@@ -538,6 +551,11 @@
}
}
+ rtc::FakeNetworkManager* network() const {
+ return fake_network_manager_.get();
+ }
+ cricket::PortAllocator* port_allocator() const { return port_allocator_; }
+
private:
explicit PeerConnectionWrapper(const std::string& debug_name)
: debug_name_(debug_name) {}
@@ -554,10 +572,11 @@
RTC_DCHECK(!peer_connection_factory_);
fake_network_manager_.reset(new rtc::FakeNetworkManager());
- fake_network_manager_->AddInterface(rtc::SocketAddress("192.168.1.1", 0));
+ fake_network_manager_->AddInterface(kDefaultLocalAddress);
std::unique_ptr<cricket::PortAllocator> port_allocator(
new cricket::BasicPortAllocator(fake_network_manager_.get()));
+ port_allocator_ = port_allocator.get();
fake_audio_capture_module_ = FakeAudioCaptureModule::Create();
if (!fake_audio_capture_module_) {
return false;
@@ -612,6 +631,10 @@
void set_signaling_delay_ms(int delay_ms) { signaling_delay_ms_ = delay_ms; }
+ void set_signal_ice_candidates(bool signal) {
+ signal_ice_candidates_ = signal;
+ }
+
void EnableVideoDecoderFactory() {
video_decoder_factory_enabled_ = true;
fake_video_decoder_factory_->AddSupportedVideoCodecType(
@@ -818,20 +841,19 @@
void OnIceConnectionChange(
webrtc::PeerConnectionInterface::IceConnectionState new_state) override {
EXPECT_EQ(pc()->ice_connection_state(), new_state);
+ ice_connection_state_history_.push_back(new_state);
}
void OnIceGatheringChange(
webrtc::PeerConnectionInterface::IceGatheringState new_state) override {
- if (new_state == PeerConnectionInterface::kIceGatheringGathering) {
- ++transitions_to_gathering_state_;
- }
EXPECT_EQ(pc()->ice_gathering_state(), new_state);
+ ice_gathering_state_history_.push_back(new_state);
}
void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override {
LOG(LS_INFO) << debug_name_ << ": OnIceCandidate";
std::string ice_sdp;
EXPECT_TRUE(candidate->ToString(&ice_sdp));
- if (signaling_message_receiver_ == nullptr) {
+ if (signaling_message_receiver_ == nullptr || !signal_ice_candidates_) {
// Remote party may be deleted.
return;
}
@@ -886,6 +908,7 @@
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface>
peer_connection_factory_;
+ cricket::PortAllocator* port_allocator_;
// Needed to keep track of number of frames sent.
rtc::scoped_refptr<FakeAudioCaptureModule> fake_audio_capture_module_;
// Needed to keep track of number of frames received.
@@ -903,6 +926,7 @@
// For remote peer communication.
SignalingMessageReceiver* signaling_message_receiver_ = nullptr;
int signaling_delay_ms_ = 0;
+ bool signal_ice_candidates_ = true;
// Store references to the video capturers we've created, so that we can stop
// them, if required.
@@ -919,7 +943,10 @@
std::vector<std::unique_ptr<MockRtpReceiverObserver>> rtp_receiver_observers_;
- int transitions_to_gathering_state_ = 0;
+ std::vector<PeerConnectionInterface::IceConnectionState>
+ ice_connection_state_history_;
+ std::vector<PeerConnectionInterface::IceGatheringState>
+ ice_gathering_state_history_;
rtc::AsyncInvoker invoker_;
@@ -941,7 +968,8 @@
public:
PeerConnectionIntegrationTest()
: ss_(new rtc::VirtualSocketServer()),
- network_thread_(new rtc::Thread(ss_.get())),
+ fss_(new rtc::FirewallSocketServer(ss_.get())),
+ network_thread_(new rtc::Thread(fss_.get())),
worker_thread_(rtc::Thread::Create()) {
RTC_CHECK(network_thread_->Start());
RTC_CHECK(worker_thread_->Start());
@@ -1030,11 +1058,24 @@
callee_->set_signaling_message_receiver(caller_.get());
}
+ // Once called, SDP blobs will be automatically signaled between
+ // PeerConnections. Note that ICE candidates will not be signaled unless they
+ // are in the exchanged SDP blobs.
+ void ConnectFakeSignalingForSdpOnly() {
+ ConnectFakeSignaling();
+ SetSignalIceCandidates(false);
+ }
+
void SetSignalingDelayMs(int delay_ms) {
caller_->set_signaling_delay_ms(delay_ms);
callee_->set_signaling_delay_ms(delay_ms);
}
+ void SetSignalIceCandidates(bool signal) {
+ caller_->set_signal_ice_candidates(signal);
+ callee_->set_signal_ice_candidates(signal);
+ }
+
void EnableVideoDecoderFactory() {
caller_->EnableVideoDecoderFactory();
callee_->EnableVideoDecoderFactory();
@@ -1076,6 +1117,8 @@
return old;
}
+ rtc::FirewallSocketServer* firewall() const { return fss_.get(); }
+
// Expects the provided number of new frames to be received within |wait_ms|.
// "New frames" meaning that it waits for the current frame counts to
// *increase* by the provided values. For video, uses
@@ -1146,6 +1189,7 @@
private:
// |ss_| is used by |network_thread_| so it must be destroyed later.
std::unique_ptr<rtc::VirtualSocketServer> ss_;
+ std::unique_ptr<rtc::FirewallSocketServer> fss_;
// |network_thread_| and |worker_thread_| are used by both
// |caller_| and |callee_| so they must be destroyed
// later.
@@ -2720,6 +2764,207 @@
callee()->ice_connection_state(), kDefaultTimeout);
}
+// 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 PeerConnectionIntegrationTest,
+ public ::testing::WithParamInterface<std::tuple<std::string, uint32_t>> {
+ protected:
+ PeerConnectionIntegrationIceStatesTest() {
+ port_allocator_flags_ = std::get<1>(GetParam());
+ }
+
+ void StartStunServer(const SocketAddress& server_address) {
+ stun_server_.reset(
+ cricket::TestStunServer::Create(network_thread(), server_address));
+ }
+
+ bool TestIPv6() {
+ return (port_allocator_flags_ & cricket::PORTALLOCATOR_ENABLE_IPV6);
+ }
+
+ void SetPortAllocatorFlags() {
+ caller()->port_allocator()->set_flags(port_allocator_flags_);
+ callee()->port_allocator()->set_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()->RemoveInterface(kDefaultLocalAddress);
+ callee()->network()->RemoveInterface(kDefaultLocalAddress);
+
+ // Add network addresses for test.
+ for (const auto& caller_address : CallerAddresses()) {
+ caller()->network()->AddInterface(caller_address);
+ }
+ for (const auto& callee_address : CalleeAddresses()) {
+ callee()->network()->AddInterface(callee_address);
+ }
+ }
+
+ private:
+ uint32_t port_allocator_flags_;
+ std::unique_ptr<cricket::TestStunServer> stun_server_;
+};
+
+// 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(PeerConnectionIntegrationIceStatesTest, VerifyIceStates) {
+ rtc::ScopedFakeClock fake_clock;
+ // 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.
+ fake_clock.AdvanceTime(rtc::TimeDelta::FromSeconds(1));
+
+ 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()->AddAudioVideoMediaStream();
+ callee()->AddAudioVideoMediaStream();
+
+ // Initial state before anything happens.
+ ASSERT_EQ(PeerConnectionInterface::kIceGatheringNew,
+ caller()->ice_gathering_state());
+ ASSERT_EQ(PeerConnectionInterface::kIceConnectionNew,
+ caller()->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,
+ fake_clock);
+
+ // 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()->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);
+ }
+ LOG(LS_INFO) << "Firewall rules applied";
+ ASSERT_EQ_SIMULATED_WAIT(PeerConnectionInterface::kIceConnectionDisconnected,
+ caller()->ice_connection_state(), kDefaultTimeout,
+ fake_clock);
+
+ // Let ICE re-establish by removing the firewall rules.
+ firewall()->ClearRules();
+ LOG(LS_INFO) << "Firewall rules cleared";
+ ASSERT_EQ_SIMULATED_WAIT(PeerConnectionInterface::kIceConnectionCompleted,
+ caller()->ice_connection_state(), kDefaultTimeout,
+ fake_clock);
+
+ // 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 signalled 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);
+ }
+ LOG(LS_INFO) << "Firewall rules applied again";
+ ASSERT_EQ_SIMULATED_WAIT(PeerConnectionInterface::kIceConnectionFailed,
+ caller()->ice_connection_state(), kConsentTimeout,
+ fake_clock);
+}
+
+// Tests that the best connection is set to the appropriate IPv4/IPv6 connection
+// and that the statistics in the metric observers are updated correctly.
+TEST_P(PeerConnectionIntegrationIceStatesTest, VerifyBestConnection) {
+ ASSERT_TRUE(CreatePeerConnectionWrappers());
+ ConnectFakeSignaling();
+ SetPortAllocatorFlags();
+ SetUpNetworkInterfaces();
+ caller()->AddAudioVideoMediaStream();
+ callee()->AddAudioVideoMediaStream();
+
+ rtc::scoped_refptr<webrtc::FakeMetricsObserver> metrics_observer(
+ new rtc::RefCountedObject<webrtc::FakeMetricsObserver>());
+ caller()->pc()->RegisterUMAObserver(metrics_observer.get());
+
+ caller()->CreateAndSetAndSignalOffer();
+
+ ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+
+ const int num_best_ipv4 = metrics_observer->GetEnumCounter(
+ webrtc::kEnumCounterAddressFamily, webrtc::kBestConnections_IPv4);
+ const int num_best_ipv6 = metrics_observer->GetEnumCounter(
+ webrtc::kEnumCounterAddressFamily, webrtc::kBestConnections_IPv6);
+ if (TestIPv6()) {
+ // When IPv6 is enabled, we should prefer an IPv6 connection over an IPv4
+ // connection.
+ EXPECT_EQ(0u, num_best_ipv4);
+ EXPECT_EQ(1u, num_best_ipv6);
+ } else {
+ EXPECT_EQ(1u, num_best_ipv4);
+ EXPECT_EQ(0u, num_best_ipv6);
+ }
+
+ EXPECT_EQ(0u, metrics_observer->GetEnumCounter(
+ webrtc::kEnumCounterIceCandidatePairTypeUdp,
+ webrtc::kIceCandidatePairHostHost));
+ EXPECT_EQ(1u, metrics_observer->GetEnumCounter(
+ webrtc::kEnumCounterIceCandidatePairTypeUdp,
+ webrtc::kIceCandidatePairHostPublicHostPublic));
+}
+
+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_CASE_P(PeerConnectionIntegrationTest,
+ PeerConnectionIntegrationIceStatesTest,
+ Values(std::make_pair("IPv4 no STUN", kFlagsIPv4NoStun),
+ std::make_pair("IPv6 no STUN", kFlagsIPv6NoStun),
+ std::make_pair("IPv4 with STUN",
+ kFlagsIPv4Stun)));
+
// This test sets up a call between two parties with audio and video.
// During the call, the caller restarts ICE and the test verifies that
// new ICE candidates are generated and audio and video still can flow, and the
@@ -3232,6 +3477,38 @@
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
}
+// Test that if candidates are only signaled by applying full session
+// descriptions (instead of using AddIceCandidate), the peers can connect to
+// each other and exchange media.
+TEST_F(PeerConnectionIntegrationTest, MediaFlowsWhenCandidatesSetOnlyInSdp) {
+ ASSERT_TRUE(CreatePeerConnectionWrappers());
+ // Each side will signal the session descriptions but not candidates.
+ ConnectFakeSignalingForSdpOnly();
+
+ // Add audio video track and exchange the initial offer/answer with media
+ // information only. This will start ICE gathering on each side.
+ caller()->AddAudioVideoMediaStream();
+ callee()->AddAudioVideoMediaStream();
+ caller()->CreateAndSetAndSignalOffer();
+
+ // Wait for all candidates to be gathered on both the caller and callee.
+ ASSERT_EQ_WAIT(PeerConnectionInterface::kIceGatheringComplete,
+ caller()->ice_gathering_state(), kDefaultTimeout);
+ ASSERT_EQ_WAIT(PeerConnectionInterface::kIceGatheringComplete,
+ callee()->ice_gathering_state(), kDefaultTimeout);
+
+ // The candidates will now be included in the session description, so
+ // signaling them will start the ICE connection.
+ caller()->CreateAndSetAndSignalOffer();
+ ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+
+ // Ensure that media flows in both directions.
+ ExpectNewFramesReceivedWithWait(
+ kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+ kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+ kMaxWaitForFramesMs);
+}
+
} // namespace
#endif // if !defined(THREAD_SANITIZER)