diff --git a/api/test/network_emulation_manager.h b/api/test/network_emulation_manager.h index 58ee3bfd1a..8619f36307 100644 --- a/api/test/network_emulation_manager.h +++ b/api/test/network_emulation_manager.h @@ -13,6 +13,7 @@ #include #include +#include #include #include "api/array_view.h" @@ -66,6 +67,37 @@ struct EmulatedEndpointConfig { StatsGatheringMode stats_gathering_mode = StatsGatheringMode::kDefault; }; +struct EmulatedTURNServerConfig { + EmulatedEndpointConfig client_config; + EmulatedEndpointConfig peer_config; +}; + +// EmulatedTURNServer is an abstraction for a TURN server. +class EmulatedTURNServerInterface { + public: + struct IceServerConfig { + std::string username; + std::string password; + std::string url; + }; + + virtual ~EmulatedTURNServerInterface() {} + + // Get an IceServer configuration suitable to add to a PeerConnection. + virtual IceServerConfig GetIceServerConfig() const = 0; + + // Get non-null client endpoint, an endpoint that accepts TURN allocations. + // This shall typically be connected to one or more webrtc endpoint. + virtual EmulatedEndpoint* GetClientEndpoint() const = 0; + + // Returns socket address, which client should use to connect to TURN server + // and do TURN allocation. + virtual rtc::SocketAddress GetClientEndpointAddress() const = 0; + + // Get non-null peer endpoint, that is "connected to the internet". + // This shall typically be connected to another TURN server. + virtual EmulatedEndpoint* GetPeerEndpoint() const = 0; +}; // Provide interface to obtain all required objects to inject network emulation // layer into PeerConnection. Also contains information about network interfaces @@ -210,6 +242,13 @@ class NetworkEmulationManager { rtc::ArrayView endpoints, std::function)> stats_callback) = 0; + + // Create a EmulatedTURNServer. + // The TURN server has 2 endpoints that need to be connected with routes, + // - GetClientEndpoint() - the endpoint that accepts TURN allocations. + // - GetPeerEndpoint() - the endpoint that is "connected to the internet". + virtual EmulatedTURNServerInterface* CreateTURNServer( + EmulatedTURNServerConfig config) = 0; }; } // namespace webrtc diff --git a/test/DEPS b/test/DEPS index 2cbb1d2dc3..0e51f003ab 100644 --- a/test/DEPS +++ b/test/DEPS @@ -76,5 +76,8 @@ specific_include_rules = { ], "benchmark_main\.cc": [ "+benchmark", + ], + "emulated_turn_server\.h": [ + "+p2p/base/turn_server.h", ] } diff --git a/test/network/BUILD.gn b/test/network/BUILD.gn index 081064f34c..23501a544e 100644 --- a/test/network/BUILD.gn +++ b/test/network/BUILD.gn @@ -25,6 +25,8 @@ rtc_library("emulated_network") { "cross_traffic.h", "emulated_network_manager.cc", "emulated_network_manager.h", + "emulated_turn_server.cc", + "emulated_turn_server.h", "fake_network_socket_server.cc", "fake_network_socket_server.h", "network_emulation.cc", @@ -37,14 +39,17 @@ rtc_library("emulated_network") { deps = [ "../../api:array_view", "../../api:network_emulation_manager_api", + "../../api:packet_socket_factory", "../../api:simulated_network_api", "../../api:time_controller", "../../api/numerics", + "../../api/transport:stun_types", "../../api/units:data_rate", "../../api/units:data_size", "../../api/units:time_delta", "../../api/units:timestamp", "../../call:simulated_network", + "../../p2p:p2p_server_utils", "../../rtc_base", "../../rtc_base:rtc_base_tests_utils", "../../rtc_base:rtc_task_queue", diff --git a/test/network/emulated_turn_server.cc b/test/network/emulated_turn_server.cc new file mode 100644 index 0000000000..06a8bf9a94 --- /dev/null +++ b/test/network/emulated_turn_server.cc @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "test/network/emulated_turn_server.h" + +#include +#include + +#include "api/packet_socket_factory.h" + +namespace { + +static const char kTestRealm[] = "example.org"; +static const char kTestSoftware[] = "TestTurnServer"; + +// A wrapper class for copying data between an AsyncPacketSocket and a +// EmulatedEndpoint. This is used by the cricket::TurnServer when +// sending data back into the emulated network. +class AsyncPacketSocketWrapper : public rtc::AsyncPacketSocket { + public: + AsyncPacketSocketWrapper(webrtc::test::EmulatedTURNServer* turn_server, + webrtc::EmulatedEndpoint* endpoint, + uint16_t port) + : turn_server_(turn_server), + endpoint_(endpoint), + local_address_( + rtc::SocketAddress(endpoint_->GetPeerLocalAddress(), port)) {} + ~AsyncPacketSocketWrapper() { turn_server_->Unbind(local_address_); } + + rtc::SocketAddress GetLocalAddress() const override { return local_address_; } + rtc::SocketAddress GetRemoteAddress() const override { + return rtc::SocketAddress(); + } + int Send(const void* pv, + size_t cb, + const rtc::PacketOptions& options) override { + RTC_CHECK(false) << "TCP not implemented"; + return -1; + } + int SendTo(const void* pv, + size_t cb, + const rtc::SocketAddress& addr, + const rtc::PacketOptions& options) override { + // Copy from rtc::AsyncPacketSocket to EmulatedEndpoint. + rtc::CopyOnWriteBuffer buf(reinterpret_cast(pv), cb); + endpoint_->SendPacket(local_address_, addr, buf); + return cb; + } + int Close() override { return 0; } + + rtc::AsyncPacketSocket::State GetState() const override { + return rtc::AsyncPacketSocket::STATE_BOUND; + } + int GetOption(rtc::Socket::Option opt, int* value) override { return 0; } + int SetOption(rtc::Socket::Option opt, int value) override { return 0; } + int GetError() const override { return 0; } + void SetError(int error) override {} + + private: + webrtc::test::EmulatedTURNServer* const turn_server_; + webrtc::EmulatedEndpoint* const endpoint_; + const rtc::SocketAddress local_address_; +}; + +// A wrapper class for cricket::TurnServer to allocate sockets. +class PacketSocketFactoryWrapper : public rtc::PacketSocketFactory { + public: + explicit PacketSocketFactoryWrapper( + webrtc::test::EmulatedTURNServer* turn_server) + : turn_server_(turn_server) {} + ~PacketSocketFactoryWrapper() override {} + + // This method is called from TurnServer when making a TURN ALLOCATION. + // It will create a socket on the |peer_| endpoint. + rtc::AsyncPacketSocket* CreateUdpSocket(const rtc::SocketAddress& address, + uint16_t min_port, + uint16_t max_port) override { + return turn_server_->CreatePeerSocket(); + } + + rtc::AsyncPacketSocket* CreateServerTcpSocket( + const rtc::SocketAddress& local_address, + uint16_t min_port, + uint16_t max_port, + int opts) override { + return nullptr; + } + rtc::AsyncPacketSocket* CreateClientTcpSocket( + const rtc::SocketAddress& local_address, + const rtc::SocketAddress& remote_address, + const rtc::ProxyInfo& proxy_info, + const std::string& user_agent, + const rtc::PacketSocketTcpOptions& tcp_options) override { + return nullptr; + } + rtc::AsyncResolverInterface* CreateAsyncResolver() override { + return nullptr; + } + + private: + webrtc::test::EmulatedTURNServer* turn_server_; +}; + +} // namespace + +namespace webrtc { +namespace test { + +EmulatedTURNServer::EmulatedTURNServer(std::unique_ptr thread, + EmulatedEndpoint* client, + EmulatedEndpoint* peer) + : thread_(std::move(thread)), client_(client), peer_(peer) { + ice_config_.username = "keso"; + ice_config_.password = "keso"; + thread_->Invoke(RTC_FROM_HERE, [=]() { + RTC_DCHECK_RUN_ON(thread_.get()); + turn_server_ = std::make_unique(thread_.get()); + turn_server_->set_realm(kTestRealm); + turn_server_->set_realm(kTestSoftware); + turn_server_->set_auth_hook(this); + + auto client_socket = Wrap(client_); + turn_server_->AddInternalSocket(client_socket, cricket::PROTO_UDP); + turn_server_->SetExternalSocketFactory(new PacketSocketFactoryWrapper(this), + rtc::SocketAddress()); + client_address_ = client_socket->GetLocalAddress(); + char buf[256]; + rtc::SimpleStringBuilder str(buf); + str.AppendFormat("turn:%s?transport=udp", + client_address_.ToString().c_str()); + ice_config_.url = str.str(); + }); +} + +void EmulatedTURNServer::Stop() { + thread_->Invoke(RTC_FROM_HERE, [=]() { + RTC_DCHECK_RUN_ON(thread_.get()); + sockets_.clear(); + }); +} + +EmulatedTURNServer::~EmulatedTURNServer() { + thread_->Invoke(RTC_FROM_HERE, [=]() { + RTC_DCHECK_RUN_ON(thread_.get()); + turn_server_.reset(nullptr); + }); +} + +rtc::AsyncPacketSocket* EmulatedTURNServer::Wrap(EmulatedEndpoint* endpoint) { + RTC_DCHECK_RUN_ON(thread_.get()); + auto port = endpoint->BindReceiver(0, this).value(); + auto socket = new AsyncPacketSocketWrapper(this, endpoint, port); + sockets_[rtc::SocketAddress(endpoint->GetPeerLocalAddress(), port)] = socket; + return socket; +} + +void EmulatedTURNServer::OnPacketReceived(webrtc::EmulatedIpPacket packet) { + // Copy from EmulatedEndpoint to rtc::AsyncPacketSocket. + thread_->PostTask(RTC_FROM_HERE, [this, packet(std::move(packet))]() { + RTC_DCHECK_RUN_ON(thread_.get()); + auto it = sockets_.find(packet.to); + if (it != sockets_.end()) { + it->second->SignalReadPacket( + it->second, reinterpret_cast(packet.cdata()), + packet.size(), packet.from, packet.arrival_time.ms()); + } + }); +} + +void EmulatedTURNServer::Unbind(rtc::SocketAddress address) { + RTC_DCHECK_RUN_ON(thread_.get()); + if (GetClientEndpoint()->GetPeerLocalAddress() == address.ipaddr()) { + GetClientEndpoint()->UnbindReceiver(address.port()); + } else { + GetPeerEndpoint()->UnbindReceiver(address.port()); + } + sockets_.erase(address); +} + +} // namespace test +} // namespace webrtc diff --git a/test/network/emulated_turn_server.h b/test/network/emulated_turn_server.h new file mode 100644 index 0000000000..f91124d4d6 --- /dev/null +++ b/test/network/emulated_turn_server.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef TEST_NETWORK_EMULATED_TURN_SERVER_H_ +#define TEST_NETWORK_EMULATED_TURN_SERVER_H_ + +#include +#include +#include + +#include "api/test/network_emulation_manager.h" +#include "api/transport/stun.h" +#include "p2p/base/turn_server.h" +#include "rtc_base/async_packet_socket.h" + +namespace webrtc { +namespace test { + +// EmulatedTURNServer wraps cricket::TurnServer to be used inside +// a emulated network. +// +// Packets from EmulatedEndpoint (client or peer) are received in +// EmulatedTURNServer::OnPacketReceived which performs a map lookup +// and delivers them into cricket::TurnServer using +// AsyncPacketSocket::SignalReadPacket +// +// Packets from cricket::TurnServer to EmulatedEndpoint are sent into +// using a wrapper around AsyncPacketSocket (no lookup required as the +// wrapper around AsyncPacketSocket keep a pointer to the EmulatedEndpoint). +class EmulatedTURNServer : public EmulatedTURNServerInterface, + public cricket::TurnAuthInterface, + public webrtc::EmulatedNetworkReceiverInterface { + public: + // Create an EmulatedTURNServer. + // |thread| is a thread that will be used to run cricket::TurnServer + // that expects all calls to be made from a single thread. + EmulatedTURNServer(std::unique_ptr thread, + EmulatedEndpoint* client, + EmulatedEndpoint* peer); + ~EmulatedTURNServer() override; + + IceServerConfig GetIceServerConfig() const override { return ice_config_; } + + EmulatedEndpoint* GetClientEndpoint() const override { return client_; } + + rtc::SocketAddress GetClientEndpointAddress() const override { + return client_address_; + } + + EmulatedEndpoint* GetPeerEndpoint() const override { return peer_; } + + // cricket::TurnAuthInterface + bool GetKey(const std::string& username, + const std::string& realm, + std::string* key) override { + return cricket::ComputeStunCredentialHash(username, realm, username, key); + } + + rtc::AsyncPacketSocket* CreatePeerSocket() { return Wrap(peer_); } + + // This method is called by network emulation when a packet + // comes from an emulated link. + void OnPacketReceived(webrtc::EmulatedIpPacket packet) override; + + // This is called when the TURN server deletes a socket. + void Unbind(rtc::SocketAddress address); + + // Unbind all sockets. + void Stop(); + + private: + std::unique_ptr thread_; + rtc::SocketAddress client_address_; + IceServerConfig ice_config_; + EmulatedEndpoint* const client_; + EmulatedEndpoint* const peer_; + std::unique_ptr turn_server_ RTC_GUARDED_BY(&thread_); + std::map sockets_ + RTC_GUARDED_BY(&thread_); + + // Wraps a EmulatedEndpoint in a AsyncPacketSocket to bridge interaction + // with TurnServer. cricket::TurnServer gets ownership of the socket. + rtc::AsyncPacketSocket* Wrap(EmulatedEndpoint* endpoint); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_NETWORK_EMULATED_TURN_SERVER_H_ diff --git a/test/network/network_emulation_manager.cc b/test/network/network_emulation_manager.cc index 4a2e31e0f9..e9656fa8e0 100644 --- a/test/network/network_emulation_manager.cc +++ b/test/network/network_emulation_manager.cc @@ -17,6 +17,7 @@ #include "api/units/timestamp.h" #include "call/simulated_network.h" #include "rtc_base/fake_network.h" +#include "test/network/emulated_turn_server.h" #include "test/time_controller/real_time_controller.h" #include "test/time_controller/simulated_time_controller.h" @@ -55,7 +56,11 @@ NetworkEmulationManagerImpl::NetworkEmulationManagerImpl(TimeMode mode) // TODO(srte): Ensure that any pending task that must be run for consistency // (such as stats collection tasks) are not cancelled when the task queue is // destroyed. -NetworkEmulationManagerImpl::~NetworkEmulationManagerImpl() = default; +NetworkEmulationManagerImpl::~NetworkEmulationManagerImpl() { + for (auto& turn_server : turn_servers_) { + turn_server->Stop(); + } +} EmulatedNetworkNode* NetworkEmulationManagerImpl::CreateEmulatedNode( BuiltInNetworkBehaviorConfig config) { @@ -332,5 +337,20 @@ Timestamp NetworkEmulationManagerImpl::Now() const { return clock_->CurrentTime(); } +EmulatedTURNServerInterface* NetworkEmulationManagerImpl::CreateTURNServer( + EmulatedTURNServerConfig config) { + auto* client = CreateEndpoint(config.client_config); + auto* peer = CreateEndpoint(config.client_config); + char buf[128]; + rtc::SimpleStringBuilder str(buf); + str.AppendFormat("turn_server_%u", + static_cast(turn_servers_.size())); + auto turn = std::make_unique( + time_controller_->CreateThread(str.str()), client, peer); + auto out = turn.get(); + turn_servers_.push_back(std::move(turn)); + return out; +} + } // namespace test } // namespace webrtc diff --git a/test/network/network_emulation_manager.h b/test/network/network_emulation_manager.h index 7532b0a88f..7b954e7759 100644 --- a/test/network/network_emulation_manager.h +++ b/test/network/network_emulation_manager.h @@ -31,6 +31,7 @@ #include "system_wrappers/include/clock.h" #include "test/network/cross_traffic.h" #include "test/network/emulated_network_manager.h" +#include "test/network/emulated_turn_server.h" #include "test/network/fake_network_socket_server.h" #include "test/network/network_emulation.h" #include "test/network/traffic_route.h" @@ -92,6 +93,9 @@ class NetworkEmulationManagerImpl : public NetworkEmulationManager { Timestamp Now() const; + EmulatedTURNServerInterface* CreateTURNServer( + EmulatedTURNServerConfig config) override; + private: absl::optional GetNextIPv4Address(); const std::unique_ptr time_controller_; @@ -114,6 +118,7 @@ class NetworkEmulationManagerImpl : public NetworkEmulationManager { std::list> tcp_message_routes_; std::vector> endpoints_containers_; std::vector> network_managers_; + std::vector> turn_servers_; std::map endpoint_to_network_manager_; diff --git a/test/network/network_emulation_pc_unittest.cc b/test/network/network_emulation_pc_unittest.cc index 3d0140f5a2..6420e36275 100644 --- a/test/network/network_emulation_pc_unittest.cc +++ b/test/network/network_emulation_pc_unittest.cc @@ -78,7 +78,8 @@ rtc::scoped_refptr CreatePeerConnectionFactory( rtc::scoped_refptr CreatePeerConnection( const rtc::scoped_refptr& pcf, PeerConnectionObserver* observer, - rtc::NetworkManager* network_manager) { + rtc::NetworkManager* network_manager, + EmulatedTURNServerInterface* turn_server = nullptr) { PeerConnectionDependencies pc_deps(observer); auto port_allocator = std::make_unique(network_manager); @@ -90,6 +91,13 @@ rtc::scoped_refptr CreatePeerConnection( pc_deps.allocator = std::move(port_allocator); PeerConnectionInterface::RTCConfiguration rtc_configuration; rtc_configuration.sdp_semantics = SdpSemantics::kUnifiedPlan; + if (turn_server != nullptr) { + webrtc::PeerConnectionInterface::IceServer server; + server.username = turn_server->GetIceServerConfig().username; + server.password = turn_server->GetIceServerConfig().username; + server.urls.push_back(turn_server->GetIceServerConfig().url); + rtc_configuration.servers.push_back(server); + } return pcf->CreatePeerConnection(rtc_configuration, std::move(pc_deps)); } @@ -185,5 +193,114 @@ TEST(NetworkEmulationManagerPCTest, Run) { }); } +TEST(NetworkEmulationManagerPCTest, RunTURN) { + std::unique_ptr signaling_thread = rtc::Thread::Create(); + signaling_thread->SetName(kSignalThreadName, nullptr); + signaling_thread->Start(); + + // Setup emulated network + NetworkEmulationManagerImpl emulation(TimeMode::kRealTime); + + EmulatedNetworkNode* alice_node = emulation.CreateEmulatedNode( + std::make_unique(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* bob_node = emulation.CreateEmulatedNode( + std::make_unique(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* turn_node = emulation.CreateEmulatedNode( + std::make_unique(BuiltInNetworkBehaviorConfig())); + EmulatedEndpoint* alice_endpoint = + emulation.CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + emulation.CreateEndpoint(EmulatedEndpointConfig()); + EmulatedTURNServerInterface* alice_turn = + emulation.CreateTURNServer(EmulatedTURNServerConfig()); + EmulatedTURNServerInterface* bob_turn = + emulation.CreateTURNServer(EmulatedTURNServerConfig()); + + emulation.CreateRoute(alice_endpoint, {alice_node}, + alice_turn->GetClientEndpoint()); + emulation.CreateRoute(alice_turn->GetClientEndpoint(), {alice_node}, + alice_endpoint); + + emulation.CreateRoute(bob_endpoint, {bob_node}, + bob_turn->GetClientEndpoint()); + emulation.CreateRoute(bob_turn->GetClientEndpoint(), {bob_node}, + bob_endpoint); + + emulation.CreateRoute(alice_turn->GetPeerEndpoint(), {turn_node}, + bob_turn->GetPeerEndpoint()); + emulation.CreateRoute(bob_turn->GetPeerEndpoint(), {turn_node}, + alice_turn->GetPeerEndpoint()); + + EmulatedNetworkManagerInterface* alice_network = + emulation.CreateEmulatedNetworkManagerInterface({alice_endpoint}); + EmulatedNetworkManagerInterface* bob_network = + emulation.CreateEmulatedNetworkManagerInterface({bob_endpoint}); + + // Setup peer connections. + rtc::scoped_refptr alice_pcf; + rtc::scoped_refptr alice_pc; + std::unique_ptr alice_observer = + std::make_unique(); + + rtc::scoped_refptr bob_pcf; + rtc::scoped_refptr bob_pc; + std::unique_ptr bob_observer = + std::make_unique(); + + signaling_thread->Invoke(RTC_FROM_HERE, [&]() { + alice_pcf = CreatePeerConnectionFactory(signaling_thread.get(), + alice_network->network_thread()); + alice_pc = + CreatePeerConnection(alice_pcf, alice_observer.get(), + alice_network->network_manager(), alice_turn); + + bob_pcf = CreatePeerConnectionFactory(signaling_thread.get(), + bob_network->network_thread()); + bob_pc = CreatePeerConnection(bob_pcf, bob_observer.get(), + bob_network->network_manager(), bob_turn); + }); + + std::unique_ptr alice = + std::make_unique(alice_pcf, alice_pc, + std::move(alice_observer)); + std::unique_ptr bob = + std::make_unique(bob_pcf, bob_pc, + std::move(bob_observer)); + + signaling_thread->Invoke(RTC_FROM_HERE, [&]() { + rtc::scoped_refptr source = + alice_pcf->CreateAudioSource(cricket::AudioOptions()); + rtc::scoped_refptr track = + alice_pcf->CreateAudioTrack("audio", source); + alice->AddTransceiver(track); + + // Connect peers. + ASSERT_TRUE(alice->ExchangeOfferAnswerWith(bob.get())); + // Do the SDP negotiation, and also exchange ice candidates. + ASSERT_TRUE_WAIT( + alice->signaling_state() == PeerConnectionInterface::kStable, + kDefaultTimeoutMs); + ASSERT_TRUE_WAIT(alice->IsIceGatheringDone(), kDefaultTimeoutMs); + ASSERT_TRUE_WAIT(bob->IsIceGatheringDone(), kDefaultTimeoutMs); + + // Connect an ICE candidate pairs. + ASSERT_TRUE( + AddIceCandidates(bob.get(), alice->observer()->GetAllCandidates())); + ASSERT_TRUE( + AddIceCandidates(alice.get(), bob->observer()->GetAllCandidates())); + // This means that ICE and DTLS are connected. + ASSERT_TRUE_WAIT(bob->IsIceConnected(), kDefaultTimeoutMs); + ASSERT_TRUE_WAIT(alice->IsIceConnected(), kDefaultTimeoutMs); + + // Close peer connections + alice->pc()->Close(); + bob->pc()->Close(); + + // Delete peers. + alice.reset(); + bob.reset(); + }); +} + } // namespace test } // namespace webrtc diff --git a/test/network/network_emulation_unittest.cc b/test/network/network_emulation_unittest.cc index 476906fc87..c92b344872 100644 --- a/test/network/network_emulation_unittest.cc +++ b/test/network/network_emulation_unittest.cc @@ -568,5 +568,42 @@ TEST(NetworkEmulationManagerTest, EndpointLoopback) { network_manager.time_controller()->AdvanceTime(TimeDelta::Seconds(1)); } +TEST(NetworkEmulationManagerTURNTest, GetIceServerConfig) { + NetworkEmulationManagerImpl network_manager(TimeMode::kRealTime); + auto turn = network_manager.CreateTURNServer(EmulatedTURNServerConfig()); + + EXPECT_GT(turn->GetIceServerConfig().username.size(), 0u); + EXPECT_GT(turn->GetIceServerConfig().password.size(), 0u); + EXPECT_NE(turn->GetIceServerConfig().url.find( + turn->GetClientEndpoint()->GetPeerLocalAddress().ToString()), + std::string::npos); +} + +TEST(NetworkEmulationManagerTURNTest, ClientTraffic) { + NetworkEmulationManagerImpl emulation(TimeMode::kSimulated); + auto* ep = emulation.CreateEndpoint(EmulatedEndpointConfig()); + auto* turn = emulation.CreateTURNServer(EmulatedTURNServerConfig()); + auto* node = CreateEmulatedNodeWithDefaultBuiltInConfig(&emulation); + emulation.CreateRoute(ep, {node}, turn->GetClientEndpoint()); + emulation.CreateRoute(turn->GetClientEndpoint(), {node}, ep); + + MockReceiver recv; + int port = ep->BindReceiver(0, &recv).value(); + + // Construct a STUN BINDING. + cricket::StunMessage ping; + ping.SetType(cricket::STUN_BINDING_REQUEST); + rtc::ByteBufferWriter buf; + ping.Write(&buf); + rtc::CopyOnWriteBuffer packet(buf.Data(), buf.Length()); + + // We expect to get a ping reply. + EXPECT_CALL(recv, OnPacketReceived(::testing::_)).Times(1); + + ep->SendPacket(rtc::SocketAddress(ep->GetPeerLocalAddress(), port), + turn->GetClientEndpointAddress(), packet); + emulation.time_controller()->AdvanceTime(TimeDelta::Seconds(1)); +} + } // namespace test } // namespace webrtc