From ff393129580bbd38ee59c0f48c977e97dd23155d Mon Sep 17 00:00:00 2001 From: Artem Titov Date: Fri, 5 Apr 2019 11:19:52 +0200 Subject: [PATCH] Add ability to have multiple connected remote endpoints Bug: webrtc:10138 Change-Id: Ic305c2f247588d75b6ced17052ba12d937d1a056 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/128864 Commit-Queue: Artem Titov Reviewed-by: Karl Wiberg Reviewed-by: Sebastian Jansson Cr-Commit-Position: refs/heads/master@{#27460} --- api/test/network_emulation_manager.h | 27 ++-- test/scenario/network/BUILD.gn | 9 ++ .../network/cross_traffic_unittest.cc | 5 +- test/scenario/network/network_emulation.cc | 24 ++- test/scenario/network/network_emulation.h | 19 +-- .../network/network_emulation_manager.cc | 12 +- .../network/network_emulation_unittest.cc | 140 ++++++++++++++++++ 7 files changed, 187 insertions(+), 49 deletions(-) diff --git a/api/test/network_emulation_manager.h b/api/test/network_emulation_manager.h index 0049eb6a83..b409bce6d5 100644 --- a/api/test/network_emulation_manager.h +++ b/api/test/network_emulation_manager.h @@ -86,18 +86,23 @@ class NetworkEmulationManager { // Creates a route between endpoints going through specified network nodes. // This route is single direction only and describe how traffic that was // sent by network interface |from| have to be delivered to the network - // interface |to|. Return object can be used to remove created route. + // interface |to|. Return object can be used to remove created route. The + // route must contains at least one network node inside it. // - // Assume there are endpoints E1, E2 and E3 and network nodes A, B, C and D. - // Also assume, that there is a route constructed via A, B and C like this: - // E1 -> A -> B -> C -> E2. In such case: - // * Caller mustn't use A, B and C in any route, that is leading to E2. - // * If caller will then create a new route E1 -> D -> E3, then first - // route will be corrupted, so if caller want to do this, first route - // should be deleted by ClearRoute(...) and then a new one should be - // created. - // * Caller can use A, B or C for any other routes. - // * Caller can create other routes leading to E2. + // Assume that E{0-9} are endpoints and N{0-9} are network nodes, then + // creation of the route have to follow these rules: + // 1. A route consists of a source endpoint, an ordered list of one or + // more network nodes, and a destination endpoint. + // 2. If (E1, ..., E2) is a route, then E1 != E2. + // In other words, the source and the destination may not be the same. + // 3. Given two simultaneously existing routes (E1, ..., E2) and + // (E3, ..., E4), either E1 != E3 or E2 != E4. + // In other words, there may be at most one route from any given source + // endpoint to any given destination endpoint. + // 4. Given two simultaneously existing routes (E1, ..., N1, ..., E2) + // and (E3, ..., N2, ..., E4), either N1 != N2 or E2 != E4. + // In other words, a network node may not belong to two routes that lead + // to the same destination endpoint. virtual EmulatedRoute* CreateRoute( EmulatedEndpoint* from, const std::vector& via_nodes, diff --git a/test/scenario/network/BUILD.gn b/test/scenario/network/BUILD.gn index d3ca6ab95c..0bcd65d999 100644 --- a/test/scenario/network/BUILD.gn +++ b/test/scenario/network/BUILD.gn @@ -9,6 +9,13 @@ import("../../../webrtc.gni") rtc_source_set("emulated_network") { + visibility = [ + "../../../api:create_network_emulation_manager", + ":*", + ] + if (rtc_include_tests) { + visibility += [ "../:scenario" ] + } testonly = true sources = [ "cross_traffic.cc", @@ -57,8 +64,10 @@ rtc_source_set("network_emulation_unittest") { ":emulated_network", "../../../api:simulated_network_api", "../../../call:simulated_network", + "../../../rtc_base:gunit_helpers", "../../../rtc_base:logging", "../../../rtc_base:rtc_event", + "../../../system_wrappers:system_wrappers", "../../../test:test_support", "//third_party/abseil-cpp/absl/memory", ] diff --git a/test/scenario/network/cross_traffic_unittest.cc b/test/scenario/network/cross_traffic_unittest.cc index 450d5f85c2..4bff27277a 100644 --- a/test/scenario/network/cross_traffic_unittest.cc +++ b/test/scenario/network/cross_traffic_unittest.cc @@ -39,8 +39,9 @@ class CountingReceiver : public EmulatedNetworkReceiverInterface { struct TrafficCounterFixture { SimulatedClock clock{0}; CountingReceiver counter; - EmulatedEndpoint endpoint{1 /*id */, rtc::IPAddress(), true /*is_enabled*/, - &clock}; + TaskQueueForTest task_queue_; + EmulatedEndpoint endpoint{/*id=*/1, rtc::IPAddress(), /*is_enabled=*/true, + &task_queue_, &clock}; }; } // namespace diff --git a/test/scenario/network/network_emulation.cc b/test/scenario/network/network_emulation.cc index fe977e56a3..72964393c5 100644 --- a/test/scenario/network/network_emulation.cc +++ b/test/scenario/network/network_emulation.cc @@ -161,12 +161,14 @@ EmulatedNetworkNode::~EmulatedNetworkNode() = default; EmulatedEndpoint::EmulatedEndpoint(uint64_t id, const rtc::IPAddress& ip, bool is_enabled, + rtc::TaskQueue* task_queue, Clock* clock) : id_(id), peer_local_addr_(ip), is_enabled_(is_enabled), - send_node_(nullptr), clock_(clock), + task_queue_(task_queue), + router_(task_queue_), next_port_(kFirstEphemeralPort) { constexpr int kIPv4NetworkPrefixLength = 24; constexpr int kIPv6NetworkPrefixLength = 64; @@ -191,18 +193,18 @@ uint64_t EmulatedEndpoint::GetId() const { return id_; } -void EmulatedEndpoint::SetSendNode(EmulatedNetworkNode* send_node) { - send_node_ = send_node; -} - void EmulatedEndpoint::SendPacket(const rtc::SocketAddress& from, const rtc::SocketAddress& to, rtc::CopyOnWriteBuffer packet) { RTC_CHECK(from.ipaddr() == peer_local_addr_); - RTC_CHECK(send_node_); - send_node_->OnPacketReceived( - EmulatedIpPacket(from, to, std::move(packet), - Timestamp::us(clock_->TimeInMicroseconds()))); + struct Closure { + void operator()() { endpoint->router_.OnPacketReceived(std::move(packet)); } + EmulatedEndpoint* endpoint; + EmulatedIpPacket packet; + }; + task_queue_->PostTask(Closure{ + this, EmulatedIpPacket(from, to, std::move(packet), + Timestamp::us(clock_->TimeInMicroseconds()))}); } absl::optional EmulatedEndpoint::BindReceiver( @@ -293,10 +295,6 @@ bool EmulatedEndpoint::Enabled() const { return is_enabled_; } -EmulatedNetworkNode* EmulatedEndpoint::GetSendNode() const { - return send_node_; -} - EndpointsContainer::EndpointsContainer( const std::vector& endpoints) : endpoints_(endpoints) {} diff --git a/test/scenario/network/network_emulation.h b/test/scenario/network/network_emulation.h index 7505cbdccc..84fd7ae6e3 100644 --- a/test/scenario/network/network_emulation.h +++ b/test/scenario/network/network_emulation.h @@ -30,13 +30,6 @@ #include "system_wrappers/include/clock.h" namespace webrtc { -namespace test { - -// Forward declare NetworkEmulationManagerImpl for friend access from -// EmulatedEndpoint. -class NetworkEmulationManagerImpl; - -} // namespace test struct EmulatedIpPacket { public: @@ -159,13 +152,13 @@ class EmulatedEndpoint : public EmulatedNetworkReceiverInterface { EmulatedEndpoint(uint64_t id, const rtc::IPAddress& ip, bool is_enabled, + rtc::TaskQueue* task_queue, Clock* clock); ~EmulatedEndpoint() override; uint64_t GetId() const; - // Set network node, that will be used to send packets to the network. - void SetSendNode(EmulatedNetworkNode* send_node); + NetworkRouterNode* router() { return &router_; } // Send packet into network. // |from| will be used to set source address for the packet in destination // socket. @@ -200,11 +193,6 @@ class EmulatedEndpoint : public EmulatedNetworkReceiverInterface { const rtc::Network& network() const { return *network_.get(); } - protected: - friend class test::NetworkEmulationManagerImpl; - - EmulatedNetworkNode* GetSendNode() const; - private: static constexpr uint16_t kFirstEphemeralPort = 49152; uint16_t NextPort() RTC_EXCLUSIVE_LOCKS_REQUIRED(receiver_lock_); @@ -216,9 +204,10 @@ class EmulatedEndpoint : public EmulatedNetworkReceiverInterface { // Peer's local IP address for this endpoint network interface. const rtc::IPAddress peer_local_addr_; bool is_enabled_ RTC_GUARDED_BY(enabled_state_checker_); - EmulatedNetworkNode* send_node_; Clock* const clock_; + rtc::TaskQueue* const task_queue_; std::unique_ptr network_; + NetworkRouterNode router_; uint16_t next_port_ RTC_GUARDED_BY(receiver_lock_); std::map port_to_receiver_ diff --git a/test/scenario/network/network_emulation_manager.cc b/test/scenario/network/network_emulation_manager.cc index 190a0a856d..4f689c0af9 100644 --- a/test/scenario/network/network_emulation_manager.cc +++ b/test/scenario/network/network_emulation_manager.cc @@ -81,7 +81,7 @@ EmulatedEndpoint* NetworkEmulationManagerImpl::CreateEndpoint( bool res = used_ip_addresses_.insert(*ip).second; RTC_CHECK(res) << "IP=" << ip->ToString() << " already in use"; auto node = absl::make_unique( - next_node_id_++, *ip, config.start_as_enabled, clock_); + next_node_id_++, *ip, config.start_as_enabled, &task_queue_, clock_); EmulatedEndpoint* out = node.get(); endpoints_.push_back(std::move(node)); return out; @@ -109,7 +109,7 @@ EmulatedRoute* NetworkEmulationManagerImpl::CreateRoute( // provided here. RTC_CHECK(!via_nodes.empty()); - from->SetSendNode(via_nodes[0]); + from->router()->SetReceiver(to->GetPeerLocalAddress(), via_nodes[0]); EmulatedNetworkNode* cur_node = via_nodes[0]; for (size_t i = 1; i < via_nodes.size(); ++i) { cur_node->router()->SetReceiver(to->GetPeerLocalAddress(), via_nodes[i]); @@ -131,12 +131,8 @@ void NetworkEmulationManagerImpl::ClearRoute(EmulatedRoute* route) { for (auto* node : route->via_nodes) { node->router()->RemoveReceiver(route->to->GetPeerLocalAddress()); } - // Detach endpoint from current send node. - if (route->from->GetSendNode()) { - route->from->GetSendNode()->router()->RemoveReceiver( - route->to->GetPeerLocalAddress()); - route->from->SetSendNode(nullptr); - } + // Remove destination endpoint from source endpoint's router. + route->from->router()->RemoveReceiver(route->to->GetPeerLocalAddress()); route->active = false; }); diff --git a/test/scenario/network/network_emulation_unittest.cc b/test/scenario/network/network_emulation_unittest.cc index 2e7b1d7af8..5da9ce5f0a 100644 --- a/test/scenario/network/network_emulation_unittest.cc +++ b/test/scenario/network/network_emulation_unittest.cc @@ -8,13 +8,16 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include #include #include "absl/memory/memory.h" #include "api/test/simulated_network.h" #include "call/simulated_network.h" #include "rtc_base/event.h" +#include "rtc_base/gunit.h" #include "rtc_base/logging.h" +#include "system_wrappers/include/sleep.h" #include "test/gmock.h" #include "test/gtest.h" #include "test/scenario/network/network_emulation.h" @@ -22,6 +25,9 @@ namespace webrtc { namespace test { +namespace { + +constexpr int kNetworkPacketWaitTimeoutMs = 100; class SocketReader : public sigslot::has_slots<> { public: @@ -57,6 +63,93 @@ class SocketReader : public sigslot::has_slots<> { int received_count_ RTC_GUARDED_BY(lock_) = 0; }; +class MockReceiver : public EmulatedNetworkReceiverInterface { + public: + MOCK_METHOD1(OnPacketReceived, void(EmulatedIpPacket packet)); +}; + +class NetworkEmulationManagerThreeNodesRoutingTest : public ::testing::Test { + public: + NetworkEmulationManagerThreeNodesRoutingTest() { + e1_ = emulation_.CreateEndpoint(EmulatedEndpointConfig()); + e2_ = emulation_.CreateEndpoint(EmulatedEndpointConfig()); + e3_ = emulation_.CreateEndpoint(EmulatedEndpointConfig()); + } + + void SetupRouting( + std::function create_routing_func) { + create_routing_func(e1_, e2_, e3_, &emulation_); + } + + void SendPacketsAndValidateDelivery() { + EXPECT_CALL(r_e1_e2_, OnPacketReceived(testing::_)).Times(1); + EXPECT_CALL(r_e2_e1_, OnPacketReceived(testing::_)).Times(1); + EXPECT_CALL(r_e1_e3_, OnPacketReceived(testing::_)).Times(1); + EXPECT_CALL(r_e3_e1_, OnPacketReceived(testing::_)).Times(1); + + uint16_t common_send_port = 80; + uint16_t r_e1_e2_port = e2_->BindReceiver(0, &r_e1_e2_).value(); + uint16_t r_e2_e1_port = e1_->BindReceiver(0, &r_e2_e1_).value(); + uint16_t r_e1_e3_port = e3_->BindReceiver(0, &r_e1_e3_).value(); + uint16_t r_e3_e1_port = e1_->BindReceiver(0, &r_e3_e1_).value(); + + // Next code is using API of EmulatedEndpoint, that is visible only for + // internals of network emulation layer. Don't use this API in other tests. + // Send packet from e1 to e2. + e1_->SendPacket( + rtc::SocketAddress(e1_->GetPeerLocalAddress(), common_send_port), + rtc::SocketAddress(e2_->GetPeerLocalAddress(), r_e1_e2_port), + rtc::CopyOnWriteBuffer(10)); + + // Send packet from e2 to e1. + e2_->SendPacket( + rtc::SocketAddress(e2_->GetPeerLocalAddress(), common_send_port), + rtc::SocketAddress(e1_->GetPeerLocalAddress(), r_e2_e1_port), + rtc::CopyOnWriteBuffer(10)); + + // Send packet from e1 to e3. + e1_->SendPacket( + rtc::SocketAddress(e1_->GetPeerLocalAddress(), common_send_port), + rtc::SocketAddress(e3_->GetPeerLocalAddress(), r_e1_e3_port), + rtc::CopyOnWriteBuffer(10)); + + // Send packet from e3 to e1. + e3_->SendPacket( + rtc::SocketAddress(e3_->GetPeerLocalAddress(), common_send_port), + rtc::SocketAddress(e1_->GetPeerLocalAddress(), r_e3_e1_port), + rtc::CopyOnWriteBuffer(10)); + + // Sleep at the end to wait for async packets delivery. + SleepMs(kNetworkPacketWaitTimeoutMs); + } + + private: + // Receivers: r__ + // They must be destroyed after emulation, so they should be declared before. + MockReceiver r_e1_e2_; + MockReceiver r_e2_e1_; + MockReceiver r_e1_e3_; + MockReceiver r_e3_e1_; + + NetworkEmulationManagerImpl emulation_; + EmulatedEndpoint* e1_; + EmulatedEndpoint* e2_; + EmulatedEndpoint* e3_; +}; + +EmulatedNetworkNode* CreateEmulatedNodeWithDefaultBuiltInConfig( + NetworkEmulationManager* emulation) { + return emulation->CreateEmulatedNode( + absl::make_unique(BuiltInNetworkBehaviorConfig())); +} + +} // namespace + +using testing::_; + TEST(NetworkEmulationManagerTest, GeneratedIpv4AddressDoesNotCollide) { NetworkEmulationManagerImpl network_manager; std::set ips; @@ -136,5 +229,52 @@ TEST(NetworkEmulationManagerTest, Run) { } } +// Testing that packets are delivered via all routes using a routing scheme as +// follows: +// * e1 -> n1 -> e2 +// * e2 -> n2 -> e1 +// * e1 -> n3 -> e3 +// * e3 -> n4 -> e1 +TEST_F(NetworkEmulationManagerThreeNodesRoutingTest, + PacketsAreDeliveredInBothWaysWhenConnectedToTwoPeers) { + SetupRouting([](EmulatedEndpoint* e1, EmulatedEndpoint* e2, + EmulatedEndpoint* e3, NetworkEmulationManager* emulation) { + auto* node1 = CreateEmulatedNodeWithDefaultBuiltInConfig(emulation); + auto* node2 = CreateEmulatedNodeWithDefaultBuiltInConfig(emulation); + auto* node3 = CreateEmulatedNodeWithDefaultBuiltInConfig(emulation); + auto* node4 = CreateEmulatedNodeWithDefaultBuiltInConfig(emulation); + + emulation->CreateRoute(e1, {node1}, e2); + emulation->CreateRoute(e2, {node2}, e1); + + emulation->CreateRoute(e1, {node3}, e3); + emulation->CreateRoute(e3, {node4}, e1); + }); + SendPacketsAndValidateDelivery(); +} + +// Testing that packets are delivered via all routes using a routing scheme as +// follows: +// * e1 -> n1 -> e2 +// * e2 -> n2 -> e1 +// * e1 -> n1 -> e3 +// * e3 -> n4 -> e1 +TEST_F(NetworkEmulationManagerThreeNodesRoutingTest, + PacketsAreDeliveredInBothWaysWhenConnectedToTwoPeersOverSameSendLink) { + SetupRouting([](EmulatedEndpoint* e1, EmulatedEndpoint* e2, + EmulatedEndpoint* e3, NetworkEmulationManager* emulation) { + auto* node1 = CreateEmulatedNodeWithDefaultBuiltInConfig(emulation); + auto* node2 = CreateEmulatedNodeWithDefaultBuiltInConfig(emulation); + auto* node3 = CreateEmulatedNodeWithDefaultBuiltInConfig(emulation); + + emulation->CreateRoute(e1, {node1}, e2); + emulation->CreateRoute(e2, {node2}, e1); + + emulation->CreateRoute(e1, {node1}, e3); + emulation->CreateRoute(e3, {node3}, e1); + }); + SendPacketsAndValidateDelivery(); +} + } // namespace test } // namespace webrtc