diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/test/network | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/test/network')
25 files changed, 4975 insertions, 0 deletions
diff --git a/third_party/libwebrtc/test/network/BUILD.gn b/third_party/libwebrtc/test/network/BUILD.gn new file mode 100644 index 0000000000..5a6cb31f4b --- /dev/null +++ b/third_party/libwebrtc/test/network/BUILD.gn @@ -0,0 +1,199 @@ +# Copyright (c) 2018 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. + +import("../../webrtc.gni") + +rtc_library("emulated_network") { + visibility = [ + ":*", + "../../api:create_network_emulation_manager", + "../../api/test/network_emulation:create_cross_traffic", + ] + if (rtc_include_tests) { + visibility += [ + "../peer_scenario:*", + "../scenario:*", + ] + } + testonly = true + sources = [ + "cross_traffic.cc", + "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", + "network_emulation.h", + "network_emulation_manager.cc", + "network_emulation_manager.h", + "traffic_route.cc", + "traffic_route.h", + ] + deps = [ + "../../api:array_view", + "../../api:field_trials_view", + "../../api:network_emulation_manager_api", + "../../api:packet_socket_factory", + "../../api:scoped_refptr", + "../../api:sequence_checker", + "../../api:simulated_network_api", + "../../api:time_controller", + "../../api/numerics", + "../../api/task_queue:pending_task_safety_flag", + "../../api/test/network_emulation", + "../../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", + "../../p2p:rtc_p2p", + "../../rtc_base:async_packet_socket", + "../../rtc_base:copy_on_write_buffer", + "../../rtc_base:ip_address", + "../../rtc_base:logging", + "../../rtc_base:macromagic", + "../../rtc_base:network", + "../../rtc_base:network_constants", + "../../rtc_base:random", + "../../rtc_base:rtc_base_tests_utils", + "../../rtc_base:rtc_event", + "../../rtc_base:rtc_task_queue", + "../../rtc_base:safe_minmax", + "../../rtc_base:socket", + "../../rtc_base:socket_address", + "../../rtc_base:socket_server", + "../../rtc_base:stringutils", + "../../rtc_base:task_queue_for_test", + "../../rtc_base:threading", + "../../rtc_base/memory:always_valid_pointer", + "../../rtc_base/synchronization:mutex", + "../../rtc_base/system:no_unique_address", + "../../rtc_base/task_utils:repeating_task", + "../../system_wrappers", + "../../test:scoped_key_value_config", + "../scenario:column_printer", + "../time_controller", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/algorithm:container", + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + +rtc_library("network_emulation_unittest") { + testonly = true + sources = [ "network_emulation_unittest.cc" ] + deps = [ + ":emulated_network", + "../:test_support", + "../../api:simulated_network_api", + "../../api/units:time_delta", + "../../call:simulated_network", + "../../rtc_base:gunit_helpers", + "../../rtc_base:logging", + "../../rtc_base:rtc_event", + "../../rtc_base:task_queue_for_test", + "../../rtc_base/synchronization:mutex", + ] +} + +if (rtc_include_tests && !build_with_chromium) { + rtc_library("network_emulation_pc_unittest") { + testonly = true + sources = [ "network_emulation_pc_unittest.cc" ] + deps = [ + ":emulated_network", + "../:test_support", + "../../api:callfactory_api", + "../../api:libjingle_peerconnection_api", + "../../api:scoped_refptr", + "../../api:simulated_network_api", + "../../api/rtc_event_log:rtc_event_log_factory", + "../../api/task_queue:default_task_queue_factory", + "../../api/transport:field_trial_based_config", + "../../call:simulated_network", + "../../media:rtc_audio_video", + "../../media:rtc_media_engine_defaults", + "../../modules/audio_device:test_audio_device_module", + "../../p2p:rtc_p2p", + "../../pc:pc_test_utils", + "../../pc:peerconnection_wrapper", + "../../rtc_base:gunit_helpers", + "../../rtc_base:logging", + "../../rtc_base:rtc_event", + "../../rtc_base:task_queue_for_test", + ] + } +} + +rtc_library("cross_traffic_unittest") { + testonly = true + sources = [ "cross_traffic_unittest.cc" ] + deps = [ + ":emulated_network", + "../:test_support", + "../../api:network_emulation_manager_api", + "../../api:simulated_network_api", + "../../call:simulated_network", + "../../rtc_base:logging", + "../../rtc_base:network_constants", + "../../rtc_base:rtc_event", + "../time_controller", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + +if (rtc_include_tests) { + rtc_library("feedback_generator") { + testonly = true + sources = [ + "feedback_generator.cc", + "feedback_generator.h", + ] + deps = [ + ":emulated_network", + "../../api/transport:network_control", + "../../api/transport:test_feedback_generator_interface", + "../../call:simulated_network", + "../../rtc_base:checks", + "../time_controller", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/memory" ] + } + + rtc_library("feedback_generator_unittest") { + testonly = true + sources = [ "feedback_generator_unittest.cc" ] + deps = [ + "../:test_support", + "../../api/transport:test_feedback_generator", + ] + } + + if (!build_with_chromium) { + rtc_library("network_emulation_unittests") { + testonly = true + deps = [ + ":cross_traffic_unittest", + ":feedback_generator_unittest", + ":network_emulation_pc_unittest", + ":network_emulation_unittest", + ] + } + } +} diff --git a/third_party/libwebrtc/test/network/OWNERS b/third_party/libwebrtc/test/network/OWNERS new file mode 100644 index 0000000000..b177c4eec5 --- /dev/null +++ b/third_party/libwebrtc/test/network/OWNERS @@ -0,0 +1 @@ +titovartem@webrtc.org diff --git a/third_party/libwebrtc/test/network/cross_traffic.cc b/third_party/libwebrtc/test/network/cross_traffic.cc new file mode 100644 index 0000000000..dd34bd02f1 --- /dev/null +++ b/third_party/libwebrtc/test/network/cross_traffic.cc @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2019 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/cross_traffic.h" + +#include <math.h> + +#include <utility> + +#include "absl/memory/memory.h" +#include "absl/types/optional.h" +#include "cross_traffic.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_minmax.h" + +namespace webrtc { +namespace test { + +RandomWalkCrossTraffic::RandomWalkCrossTraffic(RandomWalkConfig config, + CrossTrafficRoute* traffic_route) + : config_(config), + traffic_route_(traffic_route), + random_(config_.random_seed) { + sequence_checker_.Detach(); +} +RandomWalkCrossTraffic::~RandomWalkCrossTraffic() = default; + +void RandomWalkCrossTraffic::Process(Timestamp at_time) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + if (last_process_time_.IsMinusInfinity()) { + last_process_time_ = at_time; + } + TimeDelta delta = at_time - last_process_time_; + last_process_time_ = at_time; + + if (at_time - last_update_time_ >= config_.update_interval) { + intensity_ += random_.Gaussian(config_.bias, config_.variance) * + sqrt((at_time - last_update_time_).seconds<double>()); + intensity_ = rtc::SafeClamp(intensity_, 0.0, 1.0); + last_update_time_ = at_time; + } + pending_size_ += TrafficRate() * delta; + + if (pending_size_ >= config_.min_packet_size && + at_time >= last_send_time_ + config_.min_packet_interval) { + traffic_route_->SendPacket(pending_size_.bytes()); + pending_size_ = DataSize::Zero(); + last_send_time_ = at_time; + } +} + +TimeDelta RandomWalkCrossTraffic::GetProcessInterval() const { + return config_.min_packet_interval; +} + +DataRate RandomWalkCrossTraffic::TrafficRate() const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return config_.peak_rate * intensity_; +} + +ColumnPrinter RandomWalkCrossTraffic::StatsPrinter() { + return ColumnPrinter::Lambda( + "random_walk_cross_traffic_rate", + [this](rtc::SimpleStringBuilder& sb) { + sb.AppendFormat("%.0lf", TrafficRate().bps() / 8.0); + }, + 32); +} + +PulsedPeaksCrossTraffic::PulsedPeaksCrossTraffic( + PulsedPeaksConfig config, + CrossTrafficRoute* traffic_route) + : config_(config), traffic_route_(traffic_route) { + sequence_checker_.Detach(); +} +PulsedPeaksCrossTraffic::~PulsedPeaksCrossTraffic() = default; + +void PulsedPeaksCrossTraffic::Process(Timestamp at_time) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + TimeDelta time_since_toggle = at_time - last_update_time_; + if (time_since_toggle.IsInfinite() || + (sending_ && time_since_toggle >= config_.send_duration)) { + sending_ = false; + last_update_time_ = at_time; + } else if (!sending_ && time_since_toggle >= config_.hold_duration) { + sending_ = true; + last_update_time_ = at_time; + // Start sending period. + last_send_time_ = at_time; + } + + if (sending_) { + DataSize pending_size = config_.peak_rate * (at_time - last_send_time_); + + if (pending_size >= config_.min_packet_size && + at_time >= last_send_time_ + config_.min_packet_interval) { + traffic_route_->SendPacket(pending_size.bytes()); + last_send_time_ = at_time; + } + } +} + +TimeDelta PulsedPeaksCrossTraffic::GetProcessInterval() const { + return config_.min_packet_interval; +} + +DataRate PulsedPeaksCrossTraffic::TrafficRate() const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return sending_ ? config_.peak_rate : DataRate::Zero(); +} + +ColumnPrinter PulsedPeaksCrossTraffic::StatsPrinter() { + return ColumnPrinter::Lambda( + "pulsed_peaks_cross_traffic_rate", + [this](rtc::SimpleStringBuilder& sb) { + sb.AppendFormat("%.0lf", TrafficRate().bps() / 8.0); + }, + 32); +} + +TcpMessageRouteImpl::TcpMessageRouteImpl(Clock* clock, + TaskQueueBase* task_queue, + EmulatedRoute* send_route, + EmulatedRoute* ret_route) + : clock_(clock), + task_queue_(task_queue), + request_route_(send_route, + [this](TcpPacket packet, Timestamp) { + OnRequest(std::move(packet)); + }), + response_route_(ret_route, + [this](TcpPacket packet, Timestamp arrival_time) { + OnResponse(std::move(packet), arrival_time); + }) {} + +void TcpMessageRouteImpl::SendMessage(size_t size, + std::function<void()> on_received) { + task_queue_->PostTask([this, size, handler = std::move(on_received)] { + // If we are currently sending a message we won't reset the connection, + // we'll act as if the messages are sent in the same TCP stream. This is + // intended to simulate recreation of a TCP session for each message + // in the typical case while avoiding the complexity overhead of + // maintaining multiple virtual TCP sessions in parallel. + if (pending_.empty() && in_flight_.empty()) { + cwnd_ = 10; + ssthresh_ = INFINITY; + } + int64_t data_left = static_cast<int64_t>(size); + int64_t kMaxPacketSize = 1200; + int64_t kMinPacketSize = 4; + Message message{std::move(handler)}; + while (data_left > 0) { + int64_t packet_size = std::min(data_left, kMaxPacketSize); + int fragment_id = next_fragment_id_++; + pending_.push_back(MessageFragment{ + fragment_id, + static_cast<size_t>(std::max(kMinPacketSize, packet_size))}); + message.pending_fragment_ids.insert(fragment_id); + data_left -= packet_size; + } + messages_.emplace_back(message); + SendPackets(clock_->CurrentTime()); + }); +} + +void TcpMessageRouteImpl::OnRequest(TcpPacket packet_info) { + for (auto it = messages_.begin(); it != messages_.end(); ++it) { + if (it->pending_fragment_ids.count(packet_info.fragment.fragment_id) != 0) { + it->pending_fragment_ids.erase(packet_info.fragment.fragment_id); + if (it->pending_fragment_ids.empty()) { + it->handler(); + messages_.erase(it); + } + break; + } + } + const size_t kAckPacketSize = 20; + response_route_.SendPacket(kAckPacketSize, packet_info); +} + +void TcpMessageRouteImpl::OnResponse(TcpPacket packet_info, Timestamp at_time) { + auto it = in_flight_.find(packet_info.sequence_number); + if (it != in_flight_.end()) { + last_rtt_ = at_time - packet_info.send_time; + in_flight_.erase(it); + } + auto lost_end = in_flight_.lower_bound(packet_info.sequence_number); + for (auto lost_it = in_flight_.begin(); lost_it != lost_end; + lost_it = in_flight_.erase(lost_it)) { + pending_.push_front(lost_it->second.fragment); + } + + if (packet_info.sequence_number - last_acked_seq_num_ > 1) { + HandleLoss(at_time); + } else if (cwnd_ <= ssthresh_) { + cwnd_ += 1; + } else { + cwnd_ += 1.0f / cwnd_; + } + last_acked_seq_num_ = + std::max(packet_info.sequence_number, last_acked_seq_num_); + SendPackets(at_time); +} + +void TcpMessageRouteImpl::HandleLoss(Timestamp at_time) { + if (at_time - last_reduction_time_ < last_rtt_) + return; + last_reduction_time_ = at_time; + ssthresh_ = std::max(static_cast<int>(in_flight_.size() / 2), 2); + cwnd_ = ssthresh_; +} + +void TcpMessageRouteImpl::SendPackets(Timestamp at_time) { + const TimeDelta kPacketTimeout = TimeDelta::Seconds(1); + int cwnd = std::ceil(cwnd_); + int packets_to_send = std::max(cwnd - static_cast<int>(in_flight_.size()), 0); + while (packets_to_send-- > 0 && !pending_.empty()) { + auto seq_num = next_sequence_number_++; + TcpPacket send; + send.sequence_number = seq_num; + send.send_time = at_time; + send.fragment = pending_.front(); + pending_.pop_front(); + request_route_.SendPacket(send.fragment.size, send); + in_flight_.insert({seq_num, send}); + task_queue_->PostDelayedTask( + [this, seq_num] { + HandlePacketTimeout(seq_num, clock_->CurrentTime()); + }, + kPacketTimeout); + } +} + +void TcpMessageRouteImpl::HandlePacketTimeout(int seq_num, Timestamp at_time) { + auto lost = in_flight_.find(seq_num); + if (lost != in_flight_.end()) { + pending_.push_front(lost->second.fragment); + in_flight_.erase(lost); + HandleLoss(at_time); + SendPackets(at_time); + } +} + +FakeTcpCrossTraffic::FakeTcpCrossTraffic(FakeTcpConfig config, + EmulatedRoute* send_route, + EmulatedRoute* ret_route) + : conf_(config), route_(this, send_route, ret_route) {} + +TimeDelta FakeTcpCrossTraffic::GetProcessInterval() const { + return conf_.process_interval; +} + +void FakeTcpCrossTraffic::Process(Timestamp at_time) { + SendPackets(at_time); +} + +void FakeTcpCrossTraffic::OnRequest(int sequence_number, Timestamp at_time) { + const size_t kAckPacketSize = 20; + route_.SendResponse(kAckPacketSize, sequence_number); +} + +void FakeTcpCrossTraffic::OnResponse(int sequence_number, Timestamp at_time) { + ack_received_ = true; + auto it = in_flight_.find(sequence_number); + if (it != in_flight_.end()) { + last_rtt_ = at_time - in_flight_.at(sequence_number); + in_flight_.erase(sequence_number); + } + if (sequence_number - last_acked_seq_num_ > 1) { + HandleLoss(at_time); + } else if (cwnd_ <= ssthresh_) { + cwnd_ += 1; + } else { + cwnd_ += 1.0f / cwnd_; + } + last_acked_seq_num_ = std::max(sequence_number, last_acked_seq_num_); + SendPackets(at_time); +} + +void FakeTcpCrossTraffic::HandleLoss(Timestamp at_time) { + if (at_time - last_reduction_time_ < last_rtt_) + return; + last_reduction_time_ = at_time; + ssthresh_ = std::max(static_cast<int>(in_flight_.size() / 2), 2); + cwnd_ = ssthresh_; +} + +void FakeTcpCrossTraffic::SendPackets(Timestamp at_time) { + int cwnd = std::ceil(cwnd_); + int packets_to_send = std::max(cwnd - static_cast<int>(in_flight_.size()), 0); + bool timeouts = false; + for (auto it = in_flight_.begin(); it != in_flight_.end();) { + if (it->second < at_time - conf_.packet_timeout) { + it = in_flight_.erase(it); + timeouts = true; + } else { + ++it; + } + } + if (timeouts) + HandleLoss(at_time); + for (int i = 0; i < packets_to_send; ++i) { + if ((total_sent_ + conf_.packet_size) > conf_.send_limit) { + break; + } + in_flight_.insert({next_sequence_number_, at_time}); + route_.SendRequest(conf_.packet_size.bytes<size_t>(), + next_sequence_number_++); + total_sent_ += conf_.packet_size; + } +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/cross_traffic.h b/third_party/libwebrtc/test/network/cross_traffic.h new file mode 100644 index 0000000000..d21e942475 --- /dev/null +++ b/third_party/libwebrtc/test/network/cross_traffic.h @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2019 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_CROSS_TRAFFIC_H_ +#define TEST_NETWORK_CROSS_TRAFFIC_H_ + +#include <algorithm> +#include <map> +#include <memory> + +#include "api/sequence_checker.h" +#include "api/test/network_emulation_manager.h" +#include "api/units/data_rate.h" +#include "api/units/data_size.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/random.h" +#include "test/network/network_emulation.h" +#include "test/scenario/column_printer.h" + +namespace webrtc { +namespace test { + +class RandomWalkCrossTraffic final : public CrossTrafficGenerator { + public: + RandomWalkCrossTraffic(RandomWalkConfig config, + CrossTrafficRoute* traffic_route); + ~RandomWalkCrossTraffic(); + + void Process(Timestamp at_time) override; + TimeDelta GetProcessInterval() const override; + DataRate TrafficRate() const; + ColumnPrinter StatsPrinter(); + + private: + SequenceChecker sequence_checker_; + const RandomWalkConfig config_; + CrossTrafficRoute* const traffic_route_ RTC_PT_GUARDED_BY(sequence_checker_); + webrtc::Random random_ RTC_GUARDED_BY(sequence_checker_); + + Timestamp last_process_time_ RTC_GUARDED_BY(sequence_checker_) = + Timestamp::MinusInfinity(); + Timestamp last_update_time_ RTC_GUARDED_BY(sequence_checker_) = + Timestamp::MinusInfinity(); + Timestamp last_send_time_ RTC_GUARDED_BY(sequence_checker_) = + Timestamp::MinusInfinity(); + double intensity_ RTC_GUARDED_BY(sequence_checker_) = 0; + DataSize pending_size_ RTC_GUARDED_BY(sequence_checker_) = DataSize::Zero(); +}; + +class PulsedPeaksCrossTraffic final : public CrossTrafficGenerator { + public: + PulsedPeaksCrossTraffic(PulsedPeaksConfig config, + CrossTrafficRoute* traffic_route); + ~PulsedPeaksCrossTraffic(); + + void Process(Timestamp at_time) override; + TimeDelta GetProcessInterval() const override; + DataRate TrafficRate() const; + ColumnPrinter StatsPrinter(); + + private: + SequenceChecker sequence_checker_; + const PulsedPeaksConfig config_; + CrossTrafficRoute* const traffic_route_ RTC_PT_GUARDED_BY(sequence_checker_); + + Timestamp last_update_time_ RTC_GUARDED_BY(sequence_checker_) = + Timestamp::MinusInfinity(); + Timestamp last_send_time_ RTC_GUARDED_BY(sequence_checker_) = + Timestamp::MinusInfinity(); + bool sending_ RTC_GUARDED_BY(sequence_checker_) = false; +}; + +class TcpMessageRouteImpl final : public TcpMessageRoute { + public: + TcpMessageRouteImpl(Clock* clock, + TaskQueueBase* task_queue, + EmulatedRoute* send_route, + EmulatedRoute* ret_route); + + // Sends a TCP message of the given `size` over the route, `on_received` is + // called when the message has been delivered. Note that the connection + // parameters are reset iff there's no currently pending message on the route. + void SendMessage(size_t size, std::function<void()> on_received) override; + + private: + // Represents a message sent over the route. When all fragments has been + // delivered, the message is considered delivered and the handler is + // triggered. This only happen once. + struct Message { + std::function<void()> handler; + std::set<int> pending_fragment_ids; + }; + // Represents a piece of a message that fit into a TCP packet. + struct MessageFragment { + int fragment_id; + size_t size; + }; + // Represents a packet sent on the wire. + struct TcpPacket { + int sequence_number; + Timestamp send_time = Timestamp::MinusInfinity(); + MessageFragment fragment; + }; + + void OnRequest(TcpPacket packet_info); + void OnResponse(TcpPacket packet_info, Timestamp at_time); + void HandleLoss(Timestamp at_time); + void SendPackets(Timestamp at_time); + void HandlePacketTimeout(int seq_num, Timestamp at_time); + + Clock* const clock_; + TaskQueueBase* const task_queue_; + FakePacketRoute<TcpPacket> request_route_; + FakePacketRoute<TcpPacket> response_route_; + + std::deque<MessageFragment> pending_; + std::map<int, TcpPacket> in_flight_; + std::list<Message> messages_; + + double cwnd_; + double ssthresh_; + + int last_acked_seq_num_ = 0; + int next_sequence_number_ = 0; + int next_fragment_id_ = 0; + Timestamp last_reduction_time_ = Timestamp::MinusInfinity(); + TimeDelta last_rtt_ = TimeDelta::Zero(); +}; + +class FakeTcpCrossTraffic + : public TwoWayFakeTrafficRoute<int, int>::TrafficHandlerInterface, + public CrossTrafficGenerator { + public: + FakeTcpCrossTraffic(FakeTcpConfig config, + EmulatedRoute* send_route, + EmulatedRoute* ret_route); + + TimeDelta GetProcessInterval() const override; + void Process(Timestamp at_time) override; + + void OnRequest(int sequence_number, Timestamp at_time) override; + void OnResponse(int sequence_number, Timestamp at_time) override; + + void HandleLoss(Timestamp at_time); + + void SendPackets(Timestamp at_time); + + private: + const FakeTcpConfig conf_; + TwoWayFakeTrafficRoute<int, int> route_; + + std::map<int, Timestamp> in_flight_; + double cwnd_ = 10; + double ssthresh_ = INFINITY; + bool ack_received_ = false; + int last_acked_seq_num_ = 0; + int next_sequence_number_ = 0; + Timestamp last_reduction_time_ = Timestamp::MinusInfinity(); + TimeDelta last_rtt_ = TimeDelta::Zero(); + DataSize total_sent_ = DataSize::Zero(); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_NETWORK_CROSS_TRAFFIC_H_ diff --git a/third_party/libwebrtc/test/network/cross_traffic_unittest.cc b/third_party/libwebrtc/test/network/cross_traffic_unittest.cc new file mode 100644 index 0000000000..36aff67bb2 --- /dev/null +++ b/third_party/libwebrtc/test/network/cross_traffic_unittest.cc @@ -0,0 +1,163 @@ +/* + * Copyright 2019 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/cross_traffic.h" + +#include <atomic> +#include <memory> +#include <utility> +#include <vector> + +#include "absl/memory/memory.h" +#include "absl/types/optional.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/simulated_network.h" +#include "call/simulated_network.h" +#include "rtc_base/event.h" +#include "rtc_base/logging.h" +#include "rtc_base/network_constants.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/network/network_emulation_manager.h" +#include "test/network/traffic_route.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { +namespace test { +namespace { + +constexpr uint32_t kTestIpAddress = 0xC0A80011; // 192.168.0.17 + +class CountingReceiver : public EmulatedNetworkReceiverInterface { + public: + void OnPacketReceived(EmulatedIpPacket packet) override { + packets_count_++; + total_packets_size_ += packet.size(); + } + + std::atomic<int> packets_count_{0}; + std::atomic<uint64_t> total_packets_size_{0}; +}; +struct TrafficCounterFixture { + SimulatedClock clock{0}; + CountingReceiver counter; + TaskQueueForTest task_queue_; + EmulatedEndpointImpl endpoint{EmulatedEndpointImpl::Options{ + /*id=*/1, + rtc::IPAddress(kTestIpAddress), + EmulatedEndpointConfig(), + EmulatedNetworkStatsGatheringMode::kDefault, + }, + /*is_enabled=*/true, &task_queue_, &clock}; +}; + +} // namespace + +TEST(CrossTrafficTest, TriggerPacketBurst) { + TrafficCounterFixture fixture; + CrossTrafficRouteImpl traffic(&fixture.clock, &fixture.counter, + &fixture.endpoint); + traffic.TriggerPacketBurst(100, 1000); + + EXPECT_EQ(fixture.counter.packets_count_, 100); + EXPECT_EQ(fixture.counter.total_packets_size_, 100 * 1000ul); +} + +TEST(CrossTrafficTest, PulsedPeaksCrossTraffic) { + TrafficCounterFixture fixture; + CrossTrafficRouteImpl traffic(&fixture.clock, &fixture.counter, + &fixture.endpoint); + + PulsedPeaksConfig config; + config.peak_rate = DataRate::KilobitsPerSec(1000); + config.min_packet_size = DataSize::Bytes(1); + config.min_packet_interval = TimeDelta::Millis(25); + config.send_duration = TimeDelta::Millis(500); + config.hold_duration = TimeDelta::Millis(250); + PulsedPeaksCrossTraffic pulsed_peaks(config, &traffic); + const auto kRunTime = TimeDelta::Seconds(1); + while (fixture.clock.TimeInMilliseconds() < kRunTime.ms()) { + pulsed_peaks.Process(Timestamp::Millis(fixture.clock.TimeInMilliseconds())); + fixture.clock.AdvanceTimeMilliseconds(1); + } + + RTC_LOG(LS_INFO) << fixture.counter.packets_count_ << " packets; " + << fixture.counter.total_packets_size_ << " bytes"; + // Using 50% duty cycle. + const auto kExpectedDataSent = kRunTime * config.peak_rate * 0.5; + EXPECT_NEAR(fixture.counter.total_packets_size_, kExpectedDataSent.bytes(), + kExpectedDataSent.bytes() * 0.1); +} + +TEST(CrossTrafficTest, RandomWalkCrossTraffic) { + TrafficCounterFixture fixture; + CrossTrafficRouteImpl traffic(&fixture.clock, &fixture.counter, + &fixture.endpoint); + + RandomWalkConfig config; + config.peak_rate = DataRate::KilobitsPerSec(1000); + config.min_packet_size = DataSize::Bytes(1); + config.min_packet_interval = TimeDelta::Millis(25); + config.update_interval = TimeDelta::Millis(500); + config.variance = 0.0; + config.bias = 1.0; + + RandomWalkCrossTraffic random_walk(config, &traffic); + const auto kRunTime = TimeDelta::Seconds(1); + while (fixture.clock.TimeInMilliseconds() < kRunTime.ms()) { + random_walk.Process(Timestamp::Millis(fixture.clock.TimeInMilliseconds())); + fixture.clock.AdvanceTimeMilliseconds(1); + } + + RTC_LOG(LS_INFO) << fixture.counter.packets_count_ << " packets; " + << fixture.counter.total_packets_size_ << " bytes"; + // Sending at peak rate since bias = 1. + const auto kExpectedDataSent = kRunTime * config.peak_rate; + EXPECT_NEAR(fixture.counter.total_packets_size_, kExpectedDataSent.bytes(), + kExpectedDataSent.bytes() * 0.1); +} + +TEST(TcpMessageRouteTest, DeliveredOnLossyNetwork) { + NetworkEmulationManagerImpl net(TimeMode::kSimulated, + EmulatedNetworkStatsGatheringMode::kDefault); + BuiltInNetworkBehaviorConfig send; + // 800 kbps means that the 100 kB message would be delivered in ca 1 second + // under ideal conditions and no overhead. + send.link_capacity_kbps = 100 * 8; + send.loss_percent = 50; + send.queue_delay_ms = 100; + send.delay_standard_deviation_ms = 20; + send.allow_reordering = true; + auto ret = send; + ret.loss_percent = 10; + + auto* tcp_route = + net.CreateTcpRoute(net.CreateRoute({net.CreateEmulatedNode(send)}), + net.CreateRoute({net.CreateEmulatedNode(ret)})); + int deliver_count = 0; + // 100 kB is more than what fits into a single packet. + constexpr size_t kMessageSize = 100000; + + tcp_route->SendMessage(kMessageSize, [&] { + RTC_LOG(LS_INFO) << "Received at " << ToString(net.Now()); + deliver_count++; + }); + + // If there was no loss, we would have delivered the message in ca 1 second, + // with 50% it should take much longer. + net.time_controller()->AdvanceTime(TimeDelta::Seconds(5)); + ASSERT_EQ(deliver_count, 0); + // But given enough time the messsage will be delivered, but only once. + net.time_controller()->AdvanceTime(TimeDelta::Seconds(60)); + EXPECT_EQ(deliver_count, 1); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/emulated_network_manager.cc b/third_party/libwebrtc/test/network/emulated_network_manager.cc new file mode 100644 index 0000000000..fa4037e5db --- /dev/null +++ b/third_party/libwebrtc/test/network/emulated_network_manager.cc @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019 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_network_manager.h" + +#include <memory> +#include <utility> + +#include "absl/memory/memory.h" +#include "p2p/base/basic_packet_socket_factory.h" +#include "test/network/fake_network_socket_server.h" + +namespace webrtc { +namespace test { + +EmulatedNetworkManager::EmulatedNetworkManager( + TimeController* time_controller, + TaskQueueForTest* task_queue, + EndpointsContainer* endpoints_container) + : task_queue_(task_queue), + endpoints_container_(endpoints_container), + sent_first_update_(false), + start_count_(0) { + auto socket_server = + std::make_unique<FakeNetworkSocketServer>(endpoints_container); + packet_socket_factory_ = + std::make_unique<rtc::BasicPacketSocketFactory>(socket_server.get()); + // Since we pass ownership of the socket server to `network_thread_`, we must + // arrange that it outlives `packet_socket_factory_` which refers to it. + network_thread_ = + time_controller->CreateThread("net_thread", std::move(socket_server)); +} + +void EmulatedNetworkManager::EnableEndpoint(EmulatedEndpointImpl* endpoint) { + RTC_CHECK(endpoints_container_->HasEndpoint(endpoint)) + << "No such interface: " << endpoint->GetPeerLocalAddress().ToString(); + network_thread_->PostTask([this, endpoint]() { + endpoint->Enable(); + UpdateNetworksOnce(); + }); +} + +void EmulatedNetworkManager::DisableEndpoint(EmulatedEndpointImpl* endpoint) { + RTC_CHECK(endpoints_container_->HasEndpoint(endpoint)) + << "No such interface: " << endpoint->GetPeerLocalAddress().ToString(); + network_thread_->PostTask([this, endpoint]() { + endpoint->Disable(); + UpdateNetworksOnce(); + }); +} + +// Network manager interface. All these methods are supposed to be called from +// the same thread. +void EmulatedNetworkManager::StartUpdating() { + RTC_DCHECK_RUN_ON(network_thread_.get()); + + if (start_count_) { + // If network interfaces are already discovered and signal is sent, + // we should trigger network signal immediately for the new clients + // to start allocating ports. + if (sent_first_update_) + network_thread_->PostTask([this]() { MaybeSignalNetworksChanged(); }); + } else { + network_thread_->PostTask([this]() { UpdateNetworksOnce(); }); + } + ++start_count_; +} + +void EmulatedNetworkManager::StopUpdating() { + RTC_DCHECK_RUN_ON(network_thread_.get()); + if (!start_count_) + return; + + --start_count_; + if (!start_count_) { + sent_first_update_ = false; + } +} + +void EmulatedNetworkManager::GetStats( + std::function<void(EmulatedNetworkStats)> stats_callback) const { + task_queue_->PostTask([stats_callback, this]() { + stats_callback(endpoints_container_->GetStats()); + }); +} + +void EmulatedNetworkManager::UpdateNetworksOnce() { + RTC_DCHECK_RUN_ON(network_thread_.get()); + + std::vector<std::unique_ptr<rtc::Network>> networks; + for (std::unique_ptr<rtc::Network>& net : + endpoints_container_->GetEnabledNetworks()) { + net->set_default_local_address_provider(this); + networks.push_back(std::move(net)); + } + + bool changed; + MergeNetworkList(std::move(networks), &changed); + if (changed || !sent_first_update_) { + MaybeSignalNetworksChanged(); + sent_first_update_ = true; + } +} + +void EmulatedNetworkManager::MaybeSignalNetworksChanged() { + RTC_DCHECK_RUN_ON(network_thread_.get()); + // If manager is stopped we don't need to signal anything. + if (start_count_ == 0) { + return; + } + SignalNetworksChanged(); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/emulated_network_manager.h b/third_party/libwebrtc/test/network/emulated_network_manager.h new file mode 100644 index 0000000000..fb4ee1ee85 --- /dev/null +++ b/third_party/libwebrtc/test/network/emulated_network_manager.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019 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_NETWORK_MANAGER_H_ +#define TEST_NETWORK_EMULATED_NETWORK_MANAGER_H_ + +#include <functional> +#include <memory> +#include <vector> + +#include "api/sequence_checker.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/time_controller.h" +#include "rtc_base/ip_address.h" +#include "rtc_base/network.h" +#include "rtc_base/socket_server.h" +#include "rtc_base/thread.h" +#include "test/network/network_emulation.h" + +namespace webrtc { +namespace test { + +// Framework assumes that rtc::NetworkManager is called from network thread. +class EmulatedNetworkManager : public rtc::NetworkManagerBase, + public sigslot::has_slots<>, + public EmulatedNetworkManagerInterface { + public: + EmulatedNetworkManager(TimeController* time_controller, + TaskQueueForTest* task_queue, + EndpointsContainer* endpoints_container); + + void EnableEndpoint(EmulatedEndpointImpl* endpoint); + void DisableEndpoint(EmulatedEndpointImpl* endpoint); + + // NetworkManager interface. All these methods are supposed to be called from + // the same thread. + void StartUpdating() override; + void StopUpdating() override; + + // We don't support any address interfaces in the network emulation framework. + std::vector<const rtc::Network*> GetAnyAddressNetworks() override { + return {}; + } + + // EmulatedNetworkManagerInterface API + rtc::Thread* network_thread() override { return network_thread_.get(); } + rtc::NetworkManager* network_manager() override { return this; } + rtc::PacketSocketFactory* packet_socket_factory() override { + return packet_socket_factory_.get(); + } + std::vector<EmulatedEndpoint*> endpoints() const override { + return endpoints_container_->GetEndpoints(); + } + void GetStats( + std::function<void(EmulatedNetworkStats)> stats_callback) const override; + + private: + void UpdateNetworksOnce(); + void MaybeSignalNetworksChanged(); + + TaskQueueForTest* const task_queue_; + const EndpointsContainer* const endpoints_container_; + // The `network_thread_` must outlive `packet_socket_factory_`, because they + // both refer to a socket server that is owned by `network_thread_`. Both + // pointers are assigned only in the constructor, but the way they are + // initialized unfortunately doesn't work with const std::unique_ptr<...>. + std::unique_ptr<rtc::Thread> network_thread_; + std::unique_ptr<rtc::PacketSocketFactory> packet_socket_factory_; + bool sent_first_update_ RTC_GUARDED_BY(network_thread_); + int start_count_ RTC_GUARDED_BY(network_thread_); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_NETWORK_EMULATED_NETWORK_MANAGER_H_ diff --git a/third_party/libwebrtc/test/network/emulated_turn_server.cc b/third_party/libwebrtc/test/network/emulated_turn_server.cc new file mode 100644 index 0000000000..0bc7ec6e2a --- /dev/null +++ b/third_party/libwebrtc/test/network/emulated_turn_server.cc @@ -0,0 +1,191 @@ +/* + * 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 <string> +#include <utility> + +#include "api/packet_socket_factory.h" +#include "rtc_base/strings/string_builder.h" +#include "rtc_base/task_queue_for_test.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<const char*>(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::AsyncListenSocket* 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; + } + std::unique_ptr<webrtc::AsyncDnsResolverInterface> CreateAsyncDnsResolver() + override { + return nullptr; + } + + private: + webrtc::test::EmulatedTURNServer* turn_server_; +}; + +} // namespace + +namespace webrtc { +namespace test { + +EmulatedTURNServer::EmulatedTURNServer(std::unique_ptr<rtc::Thread> thread, + EmulatedEndpoint* client, + EmulatedEndpoint* peer) + : thread_(std::move(thread)), client_(client), peer_(peer) { + ice_config_.username = "keso"; + ice_config_.password = "keso"; + SendTask(thread_.get(), [=]() { + RTC_DCHECK_RUN_ON(thread_.get()); + turn_server_ = std::make_unique<cricket::TurnServer>(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() { + SendTask(thread_.get(), [=]() { + RTC_DCHECK_RUN_ON(thread_.get()); + sockets_.clear(); + }); +} + +EmulatedTURNServer::~EmulatedTURNServer() { + SendTask(thread_.get(), [=]() { + 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([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<const char*>(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/third_party/libwebrtc/test/network/emulated_turn_server.h b/third_party/libwebrtc/test/network/emulated_turn_server.h new file mode 100644 index 0000000000..9cb0ceabf6 --- /dev/null +++ b/third_party/libwebrtc/test/network/emulated_turn_server.h @@ -0,0 +1,98 @@ +/* + * 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 <map> +#include <memory> +#include <string> + +#include "absl/strings/string_view.h" +#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<rtc::Thread> 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(absl::string_view username, + absl::string_view realm, + std::string* key) override { + return cricket::ComputeStunCredentialHash( + std::string(username), std::string(realm), std::string(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<rtc::Thread> thread_; + rtc::SocketAddress client_address_; + IceServerConfig ice_config_; + EmulatedEndpoint* const client_; + EmulatedEndpoint* const peer_; + std::unique_ptr<cricket::TurnServer> turn_server_ RTC_GUARDED_BY(&thread_); + std::map<rtc::SocketAddress, rtc::AsyncPacketSocket*> 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/third_party/libwebrtc/test/network/fake_network_socket_server.cc b/third_party/libwebrtc/test/network/fake_network_socket_server.cc new file mode 100644 index 0000000000..63828e5d70 --- /dev/null +++ b/third_party/libwebrtc/test/network/fake_network_socket_server.cc @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2019 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/fake_network_socket_server.h" + +#include <algorithm> +#include <string> +#include <utility> +#include <vector> + +#include "absl/algorithm/container.h" +#include "api/scoped_refptr.h" +#include "api/task_queue/pending_task_safety_flag.h" +#include "rtc_base/event.h" +#include "rtc_base/logging.h" +#include "rtc_base/thread.h" + +namespace webrtc { +namespace test { +namespace { +std::string ToString(const rtc::SocketAddress& addr) { + return addr.HostAsURIString() + ":" + std::to_string(addr.port()); +} + +} // namespace + +// Represents a socket, which will operate with emulated network. +class FakeNetworkSocket : public rtc::Socket, + public EmulatedNetworkReceiverInterface { + public: + explicit FakeNetworkSocket(FakeNetworkSocketServer* scoket_manager, + rtc::Thread* thread); + ~FakeNetworkSocket() override; + + // Will be invoked by EmulatedEndpoint to deliver packets into this socket. + void OnPacketReceived(EmulatedIpPacket packet) override; + + // rtc::Socket methods: + rtc::SocketAddress GetLocalAddress() const override; + rtc::SocketAddress GetRemoteAddress() const override; + int Bind(const rtc::SocketAddress& addr) override; + int Connect(const rtc::SocketAddress& addr) override; + int Close() override; + int Send(const void* pv, size_t cb) override; + int SendTo(const void* pv, + size_t cb, + const rtc::SocketAddress& addr) override; + int Recv(void* pv, size_t cb, int64_t* timestamp) override; + int RecvFrom(void* pv, + size_t cb, + rtc::SocketAddress* paddr, + int64_t* timestamp) override; + int Listen(int backlog) override; + rtc::Socket* Accept(rtc::SocketAddress* paddr) override; + int GetError() const override; + void SetError(int error) override; + ConnState GetState() const override; + int GetOption(Option opt, int* value) override; + int SetOption(Option opt, int value) override; + + private: + FakeNetworkSocketServer* const socket_server_; + rtc::Thread* const thread_; + EmulatedEndpointImpl* endpoint_ RTC_GUARDED_BY(&thread_); + rtc::SocketAddress local_addr_ RTC_GUARDED_BY(&thread_); + rtc::SocketAddress remote_addr_ RTC_GUARDED_BY(&thread_); + ConnState state_ RTC_GUARDED_BY(&thread_); + int error_ RTC_GUARDED_BY(&thread_); + std::map<Option, int> options_map_ RTC_GUARDED_BY(&thread_); + + absl::optional<EmulatedIpPacket> pending_ RTC_GUARDED_BY(thread_); + rtc::scoped_refptr<PendingTaskSafetyFlag> alive_; +}; + +FakeNetworkSocket::FakeNetworkSocket(FakeNetworkSocketServer* socket_server, + rtc::Thread* thread) + : socket_server_(socket_server), + thread_(thread), + state_(CS_CLOSED), + error_(0), + alive_(PendingTaskSafetyFlag::Create()) {} + +FakeNetworkSocket::~FakeNetworkSocket() { + // Abandon all pending packets. + alive_->SetNotAlive(); + + Close(); + socket_server_->Unregister(this); +} + +void FakeNetworkSocket::OnPacketReceived(EmulatedIpPacket packet) { + auto task = [this, packet = std::move(packet)]() mutable { + RTC_DCHECK_RUN_ON(thread_); + if (!endpoint_->Enabled()) + return; + RTC_DCHECK(!pending_); + pending_ = std::move(packet); + // Note that we expect that this will trigger exactly one call to RecvFrom() + // where pending_packet will be read and reset. This call is done without + // any thread switch (see AsyncUDPSocket::OnReadEvent) so it's safe to + // assume that SignalReadEvent() will block until the packet has been read. + SignalReadEvent(this); + RTC_DCHECK(!pending_); + }; + thread_->PostTask(SafeTask(alive_, std::move(task))); + socket_server_->WakeUp(); +} + +rtc::SocketAddress FakeNetworkSocket::GetLocalAddress() const { + RTC_DCHECK_RUN_ON(thread_); + return local_addr_; +} + +rtc::SocketAddress FakeNetworkSocket::GetRemoteAddress() const { + RTC_DCHECK_RUN_ON(thread_); + return remote_addr_; +} + +int FakeNetworkSocket::Bind(const rtc::SocketAddress& addr) { + RTC_DCHECK_RUN_ON(thread_); + RTC_CHECK(local_addr_.IsNil()) + << "Socket already bound to address: " << ToString(local_addr_); + local_addr_ = addr; + endpoint_ = socket_server_->GetEndpointNode(local_addr_.ipaddr()); + if (!endpoint_) { + local_addr_.Clear(); + RTC_LOG(LS_INFO) << "No endpoint for address: " << ToString(addr); + error_ = EADDRNOTAVAIL; + return 2; + } + absl::optional<uint16_t> port = + endpoint_->BindReceiver(local_addr_.port(), this); + if (!port) { + local_addr_.Clear(); + RTC_LOG(LS_INFO) << "Cannot bind to in-use address: " << ToString(addr); + error_ = EADDRINUSE; + return 1; + } + local_addr_.SetPort(port.value()); + return 0; +} + +int FakeNetworkSocket::Connect(const rtc::SocketAddress& addr) { + RTC_DCHECK_RUN_ON(thread_); + RTC_CHECK(remote_addr_.IsNil()) + << "Socket already connected to address: " << ToString(remote_addr_); + RTC_CHECK(!local_addr_.IsNil()) + << "Socket have to be bind to some local address"; + remote_addr_ = addr; + state_ = CS_CONNECTED; + return 0; +} + +int FakeNetworkSocket::Send(const void* pv, size_t cb) { + RTC_DCHECK_RUN_ON(thread_); + RTC_CHECK(state_ == CS_CONNECTED) << "Socket cannot send: not connected"; + return SendTo(pv, cb, remote_addr_); +} + +int FakeNetworkSocket::SendTo(const void* pv, + size_t cb, + const rtc::SocketAddress& addr) { + RTC_DCHECK_RUN_ON(thread_); + RTC_CHECK(!local_addr_.IsNil()) + << "Socket have to be bind to some local address"; + if (!endpoint_->Enabled()) { + error_ = ENETDOWN; + return -1; + } + rtc::CopyOnWriteBuffer packet(static_cast<const uint8_t*>(pv), cb); + endpoint_->SendPacket(local_addr_, addr, packet); + return cb; +} + +int FakeNetworkSocket::Recv(void* pv, size_t cb, int64_t* timestamp) { + rtc::SocketAddress paddr; + return RecvFrom(pv, cb, &paddr, timestamp); +} + +// Reads 1 packet from internal queue. Reads up to `cb` bytes into `pv` +// and returns the length of received packet. +int FakeNetworkSocket::RecvFrom(void* pv, + size_t cb, + rtc::SocketAddress* paddr, + int64_t* timestamp) { + RTC_DCHECK_RUN_ON(thread_); + + if (timestamp) { + *timestamp = -1; + } + RTC_CHECK(pending_); + + *paddr = pending_->from; + size_t data_read = std::min(cb, pending_->size()); + memcpy(pv, pending_->cdata(), data_read); + *timestamp = pending_->arrival_time.us(); + + // According to RECV(2) Linux Man page + // real socket will discard data, that won't fit into provided buffer, + // but we won't to skip such error, so we will assert here. + RTC_CHECK(data_read == pending_->size()) + << "Too small buffer is provided for socket read. " + "Received data size: " + << pending_->size() << "; Provided buffer size: " << cb; + + pending_.reset(); + + // According to RECV(2) Linux Man page + // real socket will return message length, not data read. In our case it is + // actually the same value. + return static_cast<int>(data_read); +} + +int FakeNetworkSocket::Listen(int backlog) { + RTC_CHECK(false) << "Listen() isn't valid for SOCK_DGRAM"; +} + +rtc::Socket* FakeNetworkSocket::Accept(rtc::SocketAddress* /*paddr*/) { + RTC_CHECK(false) << "Accept() isn't valid for SOCK_DGRAM"; +} + +int FakeNetworkSocket::Close() { + RTC_DCHECK_RUN_ON(thread_); + state_ = CS_CLOSED; + if (!local_addr_.IsNil()) { + endpoint_->UnbindReceiver(local_addr_.port()); + } + local_addr_.Clear(); + remote_addr_.Clear(); + return 0; +} + +int FakeNetworkSocket::GetError() const { + RTC_DCHECK_RUN_ON(thread_); + return error_; +} + +void FakeNetworkSocket::SetError(int error) { + RTC_DCHECK_RUN_ON(thread_); + RTC_CHECK(error == 0); + error_ = error; +} + +rtc::Socket::ConnState FakeNetworkSocket::GetState() const { + RTC_DCHECK_RUN_ON(thread_); + return state_; +} + +int FakeNetworkSocket::GetOption(Option opt, int* value) { + RTC_DCHECK_RUN_ON(thread_); + auto it = options_map_.find(opt); + if (it == options_map_.end()) { + return -1; + } + *value = it->second; + return 0; +} + +int FakeNetworkSocket::SetOption(Option opt, int value) { + RTC_DCHECK_RUN_ON(thread_); + options_map_[opt] = value; + return 0; +} + +FakeNetworkSocketServer::FakeNetworkSocketServer( + EndpointsContainer* endpoints_container) + : endpoints_container_(endpoints_container), + wakeup_(/*manual_reset=*/false, /*initially_signaled=*/false) {} +FakeNetworkSocketServer::~FakeNetworkSocketServer() = default; + +EmulatedEndpointImpl* FakeNetworkSocketServer::GetEndpointNode( + const rtc::IPAddress& ip) { + return endpoints_container_->LookupByLocalAddress(ip); +} + +void FakeNetworkSocketServer::Unregister(FakeNetworkSocket* socket) { + MutexLock lock(&lock_); + sockets_.erase(absl::c_find(sockets_, socket)); +} + +rtc::Socket* FakeNetworkSocketServer::CreateSocket(int family, int type) { + RTC_DCHECK(family == AF_INET || family == AF_INET6); + // We support only UDP sockets for now. + RTC_DCHECK(type == SOCK_DGRAM) << "Only UDP sockets are supported"; + RTC_DCHECK(thread_) << "must be attached to thread before creating sockets"; + FakeNetworkSocket* out = new FakeNetworkSocket(this, thread_); + { + MutexLock lock(&lock_); + sockets_.push_back(out); + } + return out; +} + +void FakeNetworkSocketServer::SetMessageQueue(rtc::Thread* thread) { + thread_ = thread; +} + +// Always returns true (if return false, it won't be invoked again...) +bool FakeNetworkSocketServer::Wait(webrtc::TimeDelta max_wait_duration, + bool process_io) { + RTC_DCHECK(thread_ == rtc::Thread::Current()); + if (!max_wait_duration.IsZero()) + wakeup_.Wait(max_wait_duration); + + return true; +} + +void FakeNetworkSocketServer::WakeUp() { + wakeup_.Set(); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/fake_network_socket_server.h b/third_party/libwebrtc/test/network/fake_network_socket_server.h new file mode 100644 index 0000000000..68624a7791 --- /dev/null +++ b/third_party/libwebrtc/test/network/fake_network_socket_server.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 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_FAKE_NETWORK_SOCKET_SERVER_H_ +#define TEST_NETWORK_FAKE_NETWORK_SOCKET_SERVER_H_ + +#include <set> +#include <vector> + +#include "api/units/timestamp.h" +#include "rtc_base/event.h" +#include "rtc_base/socket.h" +#include "rtc_base/socket_server.h" +#include "rtc_base/synchronization/mutex.h" +#include "system_wrappers/include/clock.h" +#include "test/network/network_emulation.h" + +namespace webrtc { +namespace test { +class FakeNetworkSocket; + +// FakeNetworkSocketServer must outlive any sockets it creates. +class FakeNetworkSocketServer : public rtc::SocketServer { + public: + explicit FakeNetworkSocketServer(EndpointsContainer* endpoints_controller); + ~FakeNetworkSocketServer() override; + + // rtc::SocketFactory methods: + rtc::Socket* CreateSocket(int family, int type) override; + + // rtc::SocketServer methods: + // Called by the network thread when this server is installed, kicking off the + // message handler loop. + void SetMessageQueue(rtc::Thread* thread) override; + bool Wait(webrtc::TimeDelta max_wait_duration, bool process_io) override; + void WakeUp() override; + + protected: + friend class FakeNetworkSocket; + EmulatedEndpointImpl* GetEndpointNode(const rtc::IPAddress& ip); + void Unregister(FakeNetworkSocket* socket); + + private: + const EndpointsContainer* endpoints_container_; + rtc::Event wakeup_; + rtc::Thread* thread_ = nullptr; + + Mutex lock_; + std::vector<FakeNetworkSocket*> sockets_ RTC_GUARDED_BY(lock_); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_NETWORK_FAKE_NETWORK_SOCKET_SERVER_H_ diff --git a/third_party/libwebrtc/test/network/feedback_generator.cc b/third_party/libwebrtc/test/network/feedback_generator.cc new file mode 100644 index 0000000000..e339fd87b0 --- /dev/null +++ b/third_party/libwebrtc/test/network/feedback_generator.cc @@ -0,0 +1,111 @@ +/* + * Copyright 2019 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/feedback_generator.h" + +#include "absl/memory/memory.h" +#include "api/transport/network_types.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +FeedbackGeneratorImpl::FeedbackGeneratorImpl( + FeedbackGeneratorImpl::Config config) + : conf_(config), + net_(TimeMode::kSimulated, EmulatedNetworkStatsGatheringMode::kDefault), + send_link_{new SimulatedNetwork(conf_.send_link)}, + ret_link_{new SimulatedNetwork(conf_.return_link)}, + route_(this, + net_.CreateRoute( + {net_.CreateEmulatedNode(absl::WrapUnique(send_link_))}), + net_.CreateRoute( + {net_.CreateEmulatedNode(absl::WrapUnique(ret_link_))})) {} + +Timestamp FeedbackGeneratorImpl::Now() { + return net_.Now(); +} + +void FeedbackGeneratorImpl::Sleep(TimeDelta duration) { + net_.time_controller()->AdvanceTime(duration); +} + +void FeedbackGeneratorImpl::SendPacket(size_t size) { + SentPacket sent; + sent.send_time = Now(); + sent.size = DataSize::Bytes(size); + sent.sequence_number = sequence_number_++; + sent_packets_.push(sent); + route_.SendRequest(size, sent); +} + +std::vector<TransportPacketsFeedback> FeedbackGeneratorImpl::PopFeedback() { + std::vector<TransportPacketsFeedback> ret; + ret.swap(feedback_); + return ret; +} + +void FeedbackGeneratorImpl::SetSendConfig(BuiltInNetworkBehaviorConfig config) { + conf_.send_link = config; + send_link_->SetConfig(conf_.send_link); +} + +void FeedbackGeneratorImpl::SetReturnConfig( + BuiltInNetworkBehaviorConfig config) { + conf_.return_link = config; + ret_link_->SetConfig(conf_.return_link); +} + +void FeedbackGeneratorImpl::SetSendLinkCapacity(DataRate capacity) { + conf_.send_link.link_capacity_kbps = capacity.kbps<int>(); + send_link_->SetConfig(conf_.send_link); +} + +void FeedbackGeneratorImpl::OnRequest(SentPacket packet, + Timestamp arrival_time) { + PacketResult result; + result.sent_packet = packet; + result.receive_time = arrival_time; + received_packets_.push_back(result); + Timestamp first_recv = received_packets_.front().receive_time; + if (Now() - first_recv > conf_.feedback_interval) { + route_.SendResponse(conf_.feedback_packet_size.bytes<size_t>(), + std::move(received_packets_)); + received_packets_ = {}; + } +} + +void FeedbackGeneratorImpl::OnResponse(std::vector<PacketResult> packet_results, + Timestamp arrival_time) { + TransportPacketsFeedback feedback; + feedback.feedback_time = arrival_time; + std::vector<PacketResult>::const_iterator received_packet_iterator = + packet_results.begin(); + while (received_packet_iterator != packet_results.end()) { + RTC_DCHECK(!sent_packets_.empty() && + sent_packets_.front().sequence_number <= + received_packet_iterator->sent_packet.sequence_number) + << "reordering not implemented"; + if (sent_packets_.front().sequence_number < + received_packet_iterator->sent_packet.sequence_number) { + // Packet lost. + PacketResult lost; + lost.sent_packet = sent_packets_.front(); + feedback.packet_feedbacks.push_back(lost); + } + if (sent_packets_.front().sequence_number == + received_packet_iterator->sent_packet.sequence_number) { + feedback.packet_feedbacks.push_back(*received_packet_iterator); + ++received_packet_iterator; + } + sent_packets_.pop(); + } + feedback_.push_back(feedback); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/feedback_generator.h b/third_party/libwebrtc/test/network/feedback_generator.h new file mode 100644 index 0000000000..ecd4597d3f --- /dev/null +++ b/third_party/libwebrtc/test/network/feedback_generator.h @@ -0,0 +1,60 @@ +/* + * Copyright 2019 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_FEEDBACK_GENERATOR_H_ +#define TEST_NETWORK_FEEDBACK_GENERATOR_H_ + +#include <cstdint> +#include <queue> +#include <utility> +#include <vector> + +#include "api/transport/network_types.h" +#include "api/transport/test/feedback_generator_interface.h" +#include "call/simulated_network.h" +#include "test/network/network_emulation.h" +#include "test/network/network_emulation_manager.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { + +class FeedbackGeneratorImpl + : public FeedbackGenerator, + public TwoWayFakeTrafficRoute<SentPacket, std::vector<PacketResult>>:: + TrafficHandlerInterface { + public: + explicit FeedbackGeneratorImpl(Config config); + Timestamp Now() override; + void Sleep(TimeDelta duration) override; + void SendPacket(size_t size) override; + std::vector<TransportPacketsFeedback> PopFeedback() override; + + void SetSendConfig(BuiltInNetworkBehaviorConfig config) override; + void SetReturnConfig(BuiltInNetworkBehaviorConfig config) override; + + void SetSendLinkCapacity(DataRate capacity) override; + + void OnRequest(SentPacket packet, Timestamp arrival_time) override; + void OnResponse(std::vector<PacketResult> packet_results, + Timestamp arrival_time) override; + + private: + Config conf_; + ::webrtc::test::NetworkEmulationManagerImpl net_; + SimulatedNetwork* const send_link_; + SimulatedNetwork* const ret_link_; + TwoWayFakeTrafficRoute<SentPacket, std::vector<PacketResult>> route_; + + std::queue<SentPacket> sent_packets_; + std::vector<PacketResult> received_packets_; + std::vector<TransportPacketsFeedback> feedback_; + int64_t sequence_number_ = 1; +}; +} // namespace webrtc +#endif // TEST_NETWORK_FEEDBACK_GENERATOR_H_ diff --git a/third_party/libwebrtc/test/network/feedback_generator_unittest.cc b/third_party/libwebrtc/test/network/feedback_generator_unittest.cc new file mode 100644 index 0000000000..9a577bea00 --- /dev/null +++ b/third_party/libwebrtc/test/network/feedback_generator_unittest.cc @@ -0,0 +1,47 @@ +/* + * Copyright 2019 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 "api/transport/test/create_feedback_generator.h" +#include "test/gtest.h" + +namespace webrtc { +TEST(FeedbackGeneratorTest, ReportsFeedbackForSentPackets) { + size_t kPacketSize = 1000; + auto gen = CreateFeedbackGenerator(FeedbackGenerator::Config()); + for (int i = 0; i < 10; ++i) { + gen->SendPacket(kPacketSize); + gen->Sleep(TimeDelta::Millis(50)); + } + auto feedback_list = gen->PopFeedback(); + EXPECT_GT(feedback_list.size(), 0u); + for (const auto& feedback : feedback_list) { + EXPECT_GT(feedback.packet_feedbacks.size(), 0u); + for (const auto& packet : feedback.packet_feedbacks) { + EXPECT_EQ(packet.sent_packet.size.bytes<size_t>(), kPacketSize); + } + } +} + +TEST(FeedbackGeneratorTest, FeedbackIncludesLostPackets) { + size_t kPacketSize = 1000; + auto gen = CreateFeedbackGenerator(FeedbackGenerator::Config()); + BuiltInNetworkBehaviorConfig send_config_with_loss; + send_config_with_loss.loss_percent = 50; + gen->SetSendConfig(send_config_with_loss); + for (int i = 0; i < 20; ++i) { + gen->SendPacket(kPacketSize); + gen->Sleep(TimeDelta::Millis(5)); + } + auto feedback_list = gen->PopFeedback(); + ASSERT_GT(feedback_list.size(), 0u); + EXPECT_NEAR(feedback_list[0].LostWithSendInfo().size(), + feedback_list[0].ReceivedWithSendInfo().size(), 2); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/g3doc/index.md b/third_party/libwebrtc/test/network/g3doc/index.md new file mode 100644 index 0000000000..c82b56445e --- /dev/null +++ b/third_party/libwebrtc/test/network/g3doc/index.md @@ -0,0 +1,137 @@ +<!-- go/cmark --> +<!--* freshness: {owner: 'titovartem' reviewed: '2021-03-01'} *--> + +# Network Emulation Framework + +[TOC] + +## Disclamer + +This documentation explain the implementation details of Network Emulation +Framework. Framework's public APIs are located in: + +* [`/api/test/network_emulation_manager.h`](https://source.chromium.org/search?q=%2Fapi%2Ftest%2Fnetwork_emulation_manager.h) +* [`/api/test/create_network_emulation_manager.h`](https://source.chromium.org/search?q=%2Fapi%2Ftest%2Fcreate_network_emulation_manager.h) +* [`/api/test/network_emulation/network_emulation_interfaces.h`](https://source.chromium.org/search?q=%2Fapi%2Ftest%2Fnetwork_emulation%2Fnetwork_emulation_interfaces.h) +* [`/api/test/simulated_network.h`](https://source.chromium.org/search?q=%2Fapi%2Ftest%2Fsimulated_network.h) + +## Overview + +Network Emulation Framework provides an ability to emulate network behavior +between different clients, including a WebRTC PeerConnection client. To +configure network behavior, the user can choose different options: + +* Use predefined implementation that can be configured with parameters such as + packet loss, bandwidth, delay, etc. +* Custom implementation + +Conceptually the framework provides the ability to define multiple endpoints and +routes used to connect them. All network related entities are created and +managed by single factory class `webrtc::NetworkEmulationManager` which is +implemented by `webrtc::test::NetworkEmulationManagerImpl` and can work in two +modes: + +* Real time +* Simulated time + +The manager has a dedicated task queue which pipes all packets through all +network routes from senders to receivers. This task queue behaviour is +determined by `webrtc::TimeController`, which is based on either in real time or +simulated time mode. + +The network operates on IP level and supports only UDP for now. + +## Abstractions + +The framework contains the following public abstractions: + +* `webrtc::NetworkBehaviorInterface` - defines how emulated network should + behave. It operates on packets metadata level and is responsible for telling + which packet at which time have to be delivered to the next receiver. + +* `webrtc::EmulatedIpPacket` - represents a single packet that can be sent or + received via emulated network. It has source and destination address and + payload to transfer. + +* `webrtc::EmulatedNetworkReceiverInterface` - generic packet receiver + interface. + +* `webrtc::EmulatedEndpoint` - primary user facing abstraction of the + framework. It represents a network interface on client's machine. It has its + own unique IP address and can be used to send and receive packets. + + `EmulatedEndpoint` implements `EmulatedNetworkReceiverInterface` to receive + packets from the network and provides an API to send packets to the network + and API to bind other `EmulatedNetworkReceiverInterface` which will be able + to receive packets from the endpoint. `EmulatedEndpoint` interface has the + only implementation: `webrtc::test::EmulatedEndpointImpl`. + +* `webrtc::EmulatedNetworkNode` - represents single network in the real world, + like a 3G network between peers, or Wi-Fi for one peer and LTE for another. + Each `EmulatedNetworkNode` is a single direction connetion and to form + bidirectional connection between endpoints two nodes should be used. + Multiple nodes can be joined into chain emulating a network path from one + peer to another. + + In public API this class is forward declared and fully accessible only by + the framework implementation. + + Internally consist of two parts: `LinkEmulation`, which is responsible for + behavior of current `EmulatedNetworkNode` and `NetworkRouterNode` which is + responsible for routing packets to the next node or to the endpoint. + +* `webrtc::EmulatedRoute` - represents single route from one network interface + on one device to another network interface on another device. + + In public API this class is forward declared and fully accessible only by + the framework implementation. + + It contains start and end endpoint and ordered list of `EmulatedNetworkNode` + which forms the single directional route between those endpoints. + +The framework has also the following private abstractions: + +* `webrtc::test::NetworkRouterNode` - an `EmulatedNetworkReceiverInterface` + that can route incoming packets to the next receiver based on internal IP + routing table. + +* `webrtc::test::LinkEmulation` - an `EmulatedNetworkReceiverInterface` that + can emulate network leg behavior via `webrtc::NetworkBehaviorInterface` + interface. + +For integrating with `webrtc::PeerConnection` there are helper abstractions: + +* `webrtc::EmulatedNetworkManagerInterface` which is implemented by + `webrtc::test::EmulatedNetworkManager` and provides `rtc::Thread` and + `rtc::NetworkManager` for WebRTC to use as network thread for + `PeerConnection` and for `cricket::BasicPortAllocator`. + + Implementation represent framework endpoints as `rtc::Network` to WebRTC. + +## Architecture + +Let's take a look on how framework's abstractions are connected to each other. + +When the user wants to setup emulated network, first of all, they should create +an instance of `NetworkEmulationManager` using +`webrtc::CreateNetworkEmulationManager(...)` API. Then user should use a manager +to create at least one `EmulatedEndpoint` for each client. After endpoints, the +user should create required `EmulatedNetworkNode`s and with help of manager +chain them into `EmulatedRoute`s conecting desired endpoints. + +Here is a visual overview of the emulated network architecture: + +![Architecture](network_emulation_framework.png "Architecture") + +When network is hooked into `PeerConnection` it is done through network thread +and `NetworkManager`. In the network thread the custom `rtc::SocketServer` is +provided: `webrtc::test::FakeNetworkSocketServer`. This custom socket server +will construct custom sockets (`webrtc::test::FakeNetworkSocket`), which +internally bind themselves to the required endpoint. All packets processing +inside socket have to be done on the `PeerConnection`'s network thread. When +packet is going from `PeerConnection` to the network it's already comming from +the network thread and when it's comming from the emulated network switch from +the Network Emulation Framework internal task queue and `PeerConnection`'s +network thread is done inside socket's `OnPacketReceived(...)` method. + +![Network Injection](network_injection_into_peer_connection.png "Network Injection") diff --git a/third_party/libwebrtc/test/network/g3doc/network_emulation_framework.png b/third_party/libwebrtc/test/network/g3doc/network_emulation_framework.png Binary files differnew file mode 100644 index 0000000000..afec47773f --- /dev/null +++ b/third_party/libwebrtc/test/network/g3doc/network_emulation_framework.png diff --git a/third_party/libwebrtc/test/network/g3doc/network_injection_into_peer_connection.png b/third_party/libwebrtc/test/network/g3doc/network_injection_into_peer_connection.png Binary files differnew file mode 100644 index 0000000000..c9e3bf8baf --- /dev/null +++ b/third_party/libwebrtc/test/network/g3doc/network_injection_into_peer_connection.png diff --git a/third_party/libwebrtc/test/network/network_emulation.cc b/third_party/libwebrtc/test/network/network_emulation.cc new file mode 100644 index 0000000000..f1c9ca80dd --- /dev/null +++ b/third_party/libwebrtc/test/network/network_emulation.cc @@ -0,0 +1,767 @@ +/* + * Copyright (c) 2018 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/network_emulation.h" + +#include <algorithm> +#include <limits> +#include <memory> +#include <utility> + +#include "absl/types/optional.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/sequence_checker.h" +#include "api/test/network_emulation/network_emulation_interfaces.h" +#include "api/test/network_emulation_manager.h" +#include "api/units/data_size.h" +#include "api/units/time_delta.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { + +EmulatedNetworkOutgoingStats GetOverallOutgoingStats( + const std::map<rtc::IPAddress, EmulatedNetworkOutgoingStats>& + outgoing_stats, + EmulatedNetworkStatsGatheringMode mode) { + EmulatedNetworkOutgoingStatsBuilder builder(mode); + for (const auto& entry : outgoing_stats) { + builder.AddOutgoingStats(entry.second); + } + return builder.Build(); +} + +EmulatedNetworkIncomingStats GetOverallIncomingStats( + const std::map<rtc::IPAddress, EmulatedNetworkIncomingStats>& + incoming_stats, + EmulatedNetworkStatsGatheringMode mode) { + EmulatedNetworkIncomingStatsBuilder builder(mode); + for (const auto& entry : incoming_stats) { + builder.AddIncomingStats(entry.second); + } + return builder.Build(); +} + +} // namespace + +EmulatedNetworkOutgoingStatsBuilder::EmulatedNetworkOutgoingStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : stats_gathering_mode_(stats_gathering_mode) { + sequence_checker_.Detach(); +} + +void EmulatedNetworkOutgoingStatsBuilder::OnPacketSent(Timestamp sent_time, + DataSize packet_size) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_CHECK_GE(packet_size, DataSize::Zero()); + if (stats_.first_packet_sent_time.IsInfinite()) { + stats_.first_packet_sent_time = sent_time; + stats_.first_sent_packet_size = packet_size; + } + stats_.last_packet_sent_time = sent_time; + stats_.packets_sent++; + stats_.bytes_sent += packet_size; + if (stats_gathering_mode_ == EmulatedNetworkStatsGatheringMode::kDebug) { + stats_.sent_packets_size.AddSample(packet_size.bytes()); + } +} + +void EmulatedNetworkOutgoingStatsBuilder::AddOutgoingStats( + const EmulatedNetworkOutgoingStats& stats) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + stats_.packets_sent += stats.packets_sent; + stats_.bytes_sent += stats.bytes_sent; + stats_.sent_packets_size.AddSamples(stats.sent_packets_size); + if (stats_.first_packet_sent_time > stats.first_packet_sent_time) { + stats_.first_packet_sent_time = stats.first_packet_sent_time; + stats_.first_sent_packet_size = stats.first_sent_packet_size; + } + if (stats_.last_packet_sent_time < stats.last_packet_sent_time) { + stats_.last_packet_sent_time = stats.last_packet_sent_time; + } +} + +EmulatedNetworkOutgoingStats EmulatedNetworkOutgoingStatsBuilder::Build() + const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return stats_; +} + +EmulatedNetworkIncomingStatsBuilder::EmulatedNetworkIncomingStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : stats_gathering_mode_(stats_gathering_mode) { + sequence_checker_.Detach(); +} + +void EmulatedNetworkIncomingStatsBuilder::OnPacketDropped( + DataSize packet_size) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + stats_.packets_discarded_no_receiver++; + stats_.bytes_discarded_no_receiver += packet_size; + if (stats_gathering_mode_ == EmulatedNetworkStatsGatheringMode::kDebug) { + stats_.packets_discarded_no_receiver_size.AddSample(packet_size.bytes()); + } +} + +void EmulatedNetworkIncomingStatsBuilder::OnPacketReceived( + Timestamp received_time, + DataSize packet_size) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_CHECK_GE(packet_size, DataSize::Zero()); + if (stats_.first_packet_received_time.IsInfinite()) { + stats_.first_packet_received_time = received_time; + stats_.first_received_packet_size = packet_size; + } + stats_.last_packet_received_time = received_time; + stats_.packets_received++; + stats_.bytes_received += packet_size; + if (stats_gathering_mode_ == EmulatedNetworkStatsGatheringMode::kDebug) { + stats_.received_packets_size.AddSample(packet_size.bytes()); + } +} + +void EmulatedNetworkIncomingStatsBuilder::AddIncomingStats( + const EmulatedNetworkIncomingStats& stats) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + stats_.packets_received += stats.packets_received; + stats_.bytes_received += stats.bytes_received; + stats_.received_packets_size.AddSamples(stats.received_packets_size); + stats_.packets_discarded_no_receiver += stats.packets_discarded_no_receiver; + stats_.bytes_discarded_no_receiver += stats.bytes_discarded_no_receiver; + stats_.packets_discarded_no_receiver_size.AddSamples( + stats.packets_discarded_no_receiver_size); + if (stats_.first_packet_received_time > stats.first_packet_received_time) { + stats_.first_packet_received_time = stats.first_packet_received_time; + stats_.first_received_packet_size = stats.first_received_packet_size; + } + if (stats_.last_packet_received_time < stats.last_packet_received_time) { + stats_.last_packet_received_time = stats.last_packet_received_time; + } +} + +EmulatedNetworkIncomingStats EmulatedNetworkIncomingStatsBuilder::Build() + const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return stats_; +} + +EmulatedNetworkStatsBuilder::EmulatedNetworkStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : stats_gathering_mode_(stats_gathering_mode) { + sequence_checker_.Detach(); +} + +EmulatedNetworkStatsBuilder::EmulatedNetworkStatsBuilder( + rtc::IPAddress local_ip, + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : stats_gathering_mode_(stats_gathering_mode) { + local_addresses_.push_back(local_ip); + sequence_checker_.Detach(); +} + +void EmulatedNetworkStatsBuilder::OnPacketSent(Timestamp queued_time, + Timestamp sent_time, + rtc::IPAddress destination_ip, + DataSize packet_size) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + if (stats_gathering_mode_ == EmulatedNetworkStatsGatheringMode::kDebug) { + sent_packets_queue_wait_time_us_.AddSample((sent_time - queued_time).us()); + } + auto it = outgoing_stats_per_destination_.find(destination_ip); + if (it == outgoing_stats_per_destination_.end()) { + outgoing_stats_per_destination_ + .emplace(destination_ip, + std::make_unique<EmulatedNetworkOutgoingStatsBuilder>( + stats_gathering_mode_)) + .first->second->OnPacketSent(sent_time, packet_size); + } else { + it->second->OnPacketSent(sent_time, packet_size); + } +} + +void EmulatedNetworkStatsBuilder::OnPacketDropped(rtc::IPAddress source_ip, + DataSize packet_size) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + auto it = incoming_stats_per_source_.find(source_ip); + if (it == incoming_stats_per_source_.end()) { + incoming_stats_per_source_ + .emplace(source_ip, + std::make_unique<EmulatedNetworkIncomingStatsBuilder>( + stats_gathering_mode_)) + .first->second->OnPacketDropped(packet_size); + } else { + it->second->OnPacketDropped(packet_size); + } +} + +void EmulatedNetworkStatsBuilder::OnPacketReceived(Timestamp received_time, + rtc::IPAddress source_ip, + DataSize packet_size) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + auto it = incoming_stats_per_source_.find(source_ip); + if (it == incoming_stats_per_source_.end()) { + incoming_stats_per_source_ + .emplace(source_ip, + std::make_unique<EmulatedNetworkIncomingStatsBuilder>( + stats_gathering_mode_)) + .first->second->OnPacketReceived(received_time, packet_size); + } else { + it->second->OnPacketReceived(received_time, packet_size); + } +} + +void EmulatedNetworkStatsBuilder::AddEmulatedNetworkStats( + const EmulatedNetworkStats& stats) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + // Append IPs from other endpoints stats to the builder. + for (const rtc::IPAddress& addr : stats.local_addresses) { + local_addresses_.push_back(addr); + } + + sent_packets_queue_wait_time_us_.AddSamples( + stats.sent_packets_queue_wait_time_us); + + // Add outgoing stats from other endpoints to the builder. + for (const auto& entry : stats.outgoing_stats_per_destination) { + auto it = outgoing_stats_per_destination_.find(entry.first); + if (it == outgoing_stats_per_destination_.end()) { + outgoing_stats_per_destination_ + .emplace(entry.first, + std::make_unique<EmulatedNetworkOutgoingStatsBuilder>( + stats_gathering_mode_)) + .first->second->AddOutgoingStats(entry.second); + } else { + it->second->AddOutgoingStats(entry.second); + } + } + + // Add incoming stats from other endpoints to the builder. + for (const auto& entry : stats.incoming_stats_per_source) { + auto it = incoming_stats_per_source_.find(entry.first); + if (it == incoming_stats_per_source_.end()) { + incoming_stats_per_source_ + .emplace(entry.first, + std::make_unique<EmulatedNetworkIncomingStatsBuilder>( + stats_gathering_mode_)) + .first->second->AddIncomingStats(entry.second); + } else { + it->second->AddIncomingStats(entry.second); + } + } +} + +EmulatedNetworkStats EmulatedNetworkStatsBuilder::Build() const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + std::map<rtc::IPAddress, EmulatedNetworkOutgoingStats> outgoing_stats; + for (const auto& entry : outgoing_stats_per_destination_) { + outgoing_stats.emplace(entry.first, entry.second->Build()); + } + std::map<rtc::IPAddress, EmulatedNetworkIncomingStats> incoming_stats; + for (const auto& entry : incoming_stats_per_source_) { + incoming_stats.emplace(entry.first, entry.second->Build()); + } + return EmulatedNetworkStats{ + .local_addresses = local_addresses_, + .overall_outgoing_stats = + GetOverallOutgoingStats(outgoing_stats, stats_gathering_mode_), + .overall_incoming_stats = + GetOverallIncomingStats(incoming_stats, stats_gathering_mode_), + .outgoing_stats_per_destination = std::move(outgoing_stats), + .incoming_stats_per_source = std::move(incoming_stats), + .sent_packets_queue_wait_time_us = sent_packets_queue_wait_time_us_}; +} + +EmulatedNetworkNodeStatsBuilder::EmulatedNetworkNodeStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : stats_gathering_mode_(stats_gathering_mode) { + sequence_checker_.Detach(); +} + +void EmulatedNetworkNodeStatsBuilder::AddPacketTransportTime( + TimeDelta time, + size_t packet_size) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + if (stats_gathering_mode_ == EmulatedNetworkStatsGatheringMode::kDebug) { + stats_.packet_transport_time.AddSample(time.ms<double>()); + stats_.size_to_packet_transport_time.AddSample(packet_size / + time.ms<double>()); + } +} + +void EmulatedNetworkNodeStatsBuilder::AddEmulatedNetworkNodeStats( + const EmulatedNetworkNodeStats& stats) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + stats_.packet_transport_time.AddSamples(stats.packet_transport_time); + stats_.size_to_packet_transport_time.AddSamples( + stats.size_to_packet_transport_time); +} + +EmulatedNetworkNodeStats EmulatedNetworkNodeStatsBuilder::Build() const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return stats_; +} + +void LinkEmulation::OnPacketReceived(EmulatedIpPacket packet) { + task_queue_->PostTask([this, packet = std::move(packet)]() mutable { + RTC_DCHECK_RUN_ON(task_queue_); + + uint64_t packet_id = next_packet_id_++; + bool sent = network_behavior_->EnqueuePacket(PacketInFlightInfo( + packet.ip_packet_size(), packet.arrival_time.us(), packet_id)); + if (sent) { + packets_.emplace_back(StoredPacket{.id = packet_id, + .sent_time = clock_->CurrentTime(), + .packet = std::move(packet), + .removed = false}); + } + if (process_task_.Running()) + return; + absl::optional<int64_t> next_time_us = + network_behavior_->NextDeliveryTimeUs(); + if (!next_time_us) + return; + Timestamp current_time = clock_->CurrentTime(); + process_task_ = RepeatingTaskHandle::DelayedStart( + task_queue_->Get(), + std::max(TimeDelta::Zero(), + Timestamp::Micros(*next_time_us) - current_time), + [this]() { + RTC_DCHECK_RUN_ON(task_queue_); + Timestamp current_time = clock_->CurrentTime(); + Process(current_time); + absl::optional<int64_t> next_time_us = + network_behavior_->NextDeliveryTimeUs(); + if (!next_time_us) { + process_task_.Stop(); + return TimeDelta::Zero(); // This is ignored. + } + RTC_DCHECK_GE(*next_time_us, current_time.us()); + return Timestamp::Micros(*next_time_us) - current_time; + }); + }); +} + +EmulatedNetworkNodeStats LinkEmulation::stats() const { + RTC_DCHECK_RUN_ON(task_queue_); + return stats_builder_.Build(); +} + +void LinkEmulation::Process(Timestamp at_time) { + std::vector<PacketDeliveryInfo> delivery_infos = + network_behavior_->DequeueDeliverablePackets(at_time.us()); + for (PacketDeliveryInfo& delivery_info : delivery_infos) { + StoredPacket* packet = nullptr; + for (auto& stored_packet : packets_) { + if (stored_packet.id == delivery_info.packet_id) { + packet = &stored_packet; + break; + } + } + RTC_CHECK(packet); + RTC_DCHECK(!packet->removed); + packet->removed = true; + stats_builder_.AddPacketTransportTime( + clock_->CurrentTime() - packet->sent_time, + packet->packet.ip_packet_size()); + + if (delivery_info.receive_time_us != PacketDeliveryInfo::kNotReceived) { + packet->packet.arrival_time = + Timestamp::Micros(delivery_info.receive_time_us); + receiver_->OnPacketReceived(std::move(packet->packet)); + } + while (!packets_.empty() && packets_.front().removed) { + packets_.pop_front(); + } + } +} + +NetworkRouterNode::NetworkRouterNode(rtc::TaskQueue* task_queue) + : task_queue_(task_queue) {} + +void NetworkRouterNode::OnPacketReceived(EmulatedIpPacket packet) { + RTC_DCHECK_RUN_ON(task_queue_); + if (watcher_) { + watcher_(packet); + } + if (filter_) { + if (!filter_(packet)) + return; + } + auto receiver_it = routing_.find(packet.to.ipaddr()); + if (receiver_it == routing_.end()) { + if (default_receiver_.has_value()) { + (*default_receiver_)->OnPacketReceived(std::move(packet)); + } + return; + } + RTC_CHECK(receiver_it != routing_.end()); + + receiver_it->second->OnPacketReceived(std::move(packet)); +} + +void NetworkRouterNode::SetReceiver( + const rtc::IPAddress& dest_ip, + EmulatedNetworkReceiverInterface* receiver) { + task_queue_->PostTask([=] { + RTC_DCHECK_RUN_ON(task_queue_); + EmulatedNetworkReceiverInterface* cur_receiver = routing_[dest_ip]; + RTC_CHECK(cur_receiver == nullptr || cur_receiver == receiver) + << "Routing for dest_ip=" << dest_ip.ToString() << " already exists"; + routing_[dest_ip] = receiver; + }); +} + +void NetworkRouterNode::RemoveReceiver(const rtc::IPAddress& dest_ip) { + RTC_DCHECK_RUN_ON(task_queue_); + routing_.erase(dest_ip); +} + +void NetworkRouterNode::SetDefaultReceiver( + EmulatedNetworkReceiverInterface* receiver) { + task_queue_->PostTask([=] { + RTC_DCHECK_RUN_ON(task_queue_); + if (default_receiver_.has_value()) { + RTC_CHECK_EQ(*default_receiver_, receiver) + << "Router already default receiver"; + } + default_receiver_ = receiver; + }); +} + +void NetworkRouterNode::RemoveDefaultReceiver() { + RTC_DCHECK_RUN_ON(task_queue_); + default_receiver_ = absl::nullopt; +} + +void NetworkRouterNode::SetWatcher( + std::function<void(const EmulatedIpPacket&)> watcher) { + task_queue_->PostTask([=] { + RTC_DCHECK_RUN_ON(task_queue_); + watcher_ = watcher; + }); +} + +void NetworkRouterNode::SetFilter( + std::function<bool(const EmulatedIpPacket&)> filter) { + task_queue_->PostTask([=] { + RTC_DCHECK_RUN_ON(task_queue_); + filter_ = filter; + }); +} + +EmulatedNetworkNode::EmulatedNetworkNode( + Clock* clock, + rtc::TaskQueue* task_queue, + std::unique_ptr<NetworkBehaviorInterface> network_behavior, + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : router_(task_queue), + link_(clock, + task_queue, + std::move(network_behavior), + &router_, + stats_gathering_mode) {} + +void EmulatedNetworkNode::OnPacketReceived(EmulatedIpPacket packet) { + link_.OnPacketReceived(std::move(packet)); +} + +EmulatedNetworkNodeStats EmulatedNetworkNode::stats() const { + return link_.stats(); +} + +void EmulatedNetworkNode::CreateRoute( + const rtc::IPAddress& receiver_ip, + std::vector<EmulatedNetworkNode*> nodes, + EmulatedNetworkReceiverInterface* receiver) { + RTC_CHECK(!nodes.empty()); + for (size_t i = 0; i + 1 < nodes.size(); ++i) + nodes[i]->router()->SetReceiver(receiver_ip, nodes[i + 1]); + nodes.back()->router()->SetReceiver(receiver_ip, receiver); +} + +void EmulatedNetworkNode::ClearRoute(const rtc::IPAddress& receiver_ip, + std::vector<EmulatedNetworkNode*> nodes) { + for (EmulatedNetworkNode* node : nodes) + node->router()->RemoveReceiver(receiver_ip); +} + +EmulatedNetworkNode::~EmulatedNetworkNode() = default; + +EmulatedEndpointImpl::Options::Options( + uint64_t id, + const rtc::IPAddress& ip, + const EmulatedEndpointConfig& config, + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : id(id), + ip(ip), + stats_gathering_mode(stats_gathering_mode), + type(config.type), + allow_send_packet_with_different_source_ip( + config.allow_send_packet_with_different_source_ip), + allow_receive_packets_with_different_dest_ip( + config.allow_receive_packets_with_different_dest_ip), + log_name(ip.ToString() + " (" + config.name.value_or("") + ")") {} + +EmulatedEndpointImpl::EmulatedEndpointImpl(const Options& options, + bool is_enabled, + rtc::TaskQueue* task_queue, + Clock* clock) + : options_(options), + is_enabled_(is_enabled), + clock_(clock), + task_queue_(task_queue), + router_(task_queue_), + next_port_(kFirstEphemeralPort), + stats_builder_(options_.ip, options_.stats_gathering_mode) { + constexpr int kIPv4NetworkPrefixLength = 24; + constexpr int kIPv6NetworkPrefixLength = 64; + + int prefix_length = 0; + if (options_.ip.family() == AF_INET) { + prefix_length = kIPv4NetworkPrefixLength; + } else if (options_.ip.family() == AF_INET6) { + prefix_length = kIPv6NetworkPrefixLength; + } + rtc::IPAddress prefix = TruncateIP(options_.ip, prefix_length); + network_ = std::make_unique<rtc::Network>( + options_.ip.ToString(), "Endpoint id=" + std::to_string(options_.id), + prefix, prefix_length, options_.type); + network_->AddIP(options_.ip); + + enabled_state_checker_.Detach(); + RTC_LOG(LS_INFO) << "Created emulated endpoint " << options_.log_name + << "; id=" << options_.id; +} +EmulatedEndpointImpl::~EmulatedEndpointImpl() = default; + +uint64_t EmulatedEndpointImpl::GetId() const { + return options_.id; +} + +void EmulatedEndpointImpl::SendPacket(const rtc::SocketAddress& from, + const rtc::SocketAddress& to, + rtc::CopyOnWriteBuffer packet_data, + uint16_t application_overhead) { + if (!options_.allow_send_packet_with_different_source_ip) { + RTC_CHECK(from.ipaddr() == options_.ip); + } + EmulatedIpPacket packet(from, to, std::move(packet_data), + clock_->CurrentTime(), application_overhead); + task_queue_->PostTask([this, packet = std::move(packet)]() mutable { + RTC_DCHECK_RUN_ON(task_queue_); + stats_builder_.OnPacketSent(packet.arrival_time, clock_->CurrentTime(), + packet.to.ipaddr(), + DataSize::Bytes(packet.ip_packet_size())); + + if (packet.to.ipaddr() == options_.ip) { + OnPacketReceived(std::move(packet)); + } else { + router_.OnPacketReceived(std::move(packet)); + } + }); +} + +absl::optional<uint16_t> EmulatedEndpointImpl::BindReceiver( + uint16_t desired_port, + EmulatedNetworkReceiverInterface* receiver) { + return BindReceiverInternal(desired_port, receiver, /*is_one_shot=*/false); +} + +absl::optional<uint16_t> EmulatedEndpointImpl::BindOneShotReceiver( + uint16_t desired_port, + EmulatedNetworkReceiverInterface* receiver) { + return BindReceiverInternal(desired_port, receiver, /*is_one_shot=*/true); +} + +absl::optional<uint16_t> EmulatedEndpointImpl::BindReceiverInternal( + uint16_t desired_port, + EmulatedNetworkReceiverInterface* receiver, + bool is_one_shot) { + MutexLock lock(&receiver_lock_); + uint16_t port = desired_port; + if (port == 0) { + // Because client can specify its own port, next_port_ can be already in + // use, so we need to find next available port. + int ports_pool_size = + std::numeric_limits<uint16_t>::max() - kFirstEphemeralPort + 1; + for (int i = 0; i < ports_pool_size; ++i) { + uint16_t next_port = NextPort(); + if (port_to_receiver_.find(next_port) == port_to_receiver_.end()) { + port = next_port; + break; + } + } + } + RTC_CHECK(port != 0) << "Can't find free port for receiver in endpoint " + << options_.log_name << "; id=" << options_.id; + bool result = + port_to_receiver_.insert({port, {receiver, is_one_shot}}).second; + if (!result) { + RTC_LOG(LS_INFO) << "Can't bind receiver to used port " << desired_port + << " in endpoint " << options_.log_name + << "; id=" << options_.id; + return absl::nullopt; + } + RTC_LOG(LS_INFO) << "New receiver is binded to endpoint " << options_.log_name + << "; id=" << options_.id << " on port " << port; + return port; +} + +uint16_t EmulatedEndpointImpl::NextPort() { + uint16_t out = next_port_; + if (next_port_ == std::numeric_limits<uint16_t>::max()) { + next_port_ = kFirstEphemeralPort; + } else { + next_port_++; + } + return out; +} + +void EmulatedEndpointImpl::UnbindReceiver(uint16_t port) { + MutexLock lock(&receiver_lock_); + RTC_LOG(LS_INFO) << "Receiver is removed on port " << port + << " from endpoint " << options_.log_name + << "; id=" << options_.id; + port_to_receiver_.erase(port); +} + +void EmulatedEndpointImpl::BindDefaultReceiver( + EmulatedNetworkReceiverInterface* receiver) { + MutexLock lock(&receiver_lock_); + RTC_CHECK(!default_receiver_.has_value()) + << "Endpoint " << options_.log_name << "; id=" << options_.id + << " already has default receiver"; + RTC_LOG(LS_INFO) << "Default receiver is binded to endpoint " + << options_.log_name << "; id=" << options_.id; + default_receiver_ = receiver; +} + +void EmulatedEndpointImpl::UnbindDefaultReceiver() { + MutexLock lock(&receiver_lock_); + RTC_LOG(LS_INFO) << "Default receiver is removed from endpoint " + << options_.log_name << "; id=" << options_.id; + default_receiver_ = absl::nullopt; +} + +rtc::IPAddress EmulatedEndpointImpl::GetPeerLocalAddress() const { + return options_.ip; +} + +void EmulatedEndpointImpl::OnPacketReceived(EmulatedIpPacket packet) { + RTC_DCHECK_RUN_ON(task_queue_); + if (!options_.allow_receive_packets_with_different_dest_ip) { + RTC_CHECK(packet.to.ipaddr() == options_.ip) + << "Routing error: wrong destination endpoint. Packet.to.ipaddr()=: " + << packet.to.ipaddr().ToString() + << "; Receiver options_.ip=" << options_.ip.ToString(); + } + MutexLock lock(&receiver_lock_); + stats_builder_.OnPacketReceived(clock_->CurrentTime(), packet.from.ipaddr(), + DataSize::Bytes(packet.ip_packet_size())); + auto it = port_to_receiver_.find(packet.to.port()); + if (it == port_to_receiver_.end()) { + if (default_receiver_.has_value()) { + (*default_receiver_)->OnPacketReceived(std::move(packet)); + return; + } + // It can happen, that remote peer closed connection, but there still some + // packets, that are going to it. It can happen during peer connection close + // process: one peer closed connection, second still sending data. + RTC_LOG(LS_INFO) << "Drop packet: no receiver registered in " + << options_.log_name << "; id=" << options_.id + << " on port " << packet.to.port() + << ". Packet source: " << packet.from.ToString(); + stats_builder_.OnPacketDropped(packet.from.ipaddr(), + DataSize::Bytes(packet.ip_packet_size())); + return; + } + // Endpoint holds lock during packet processing to ensure that a call to + // UnbindReceiver followed by a delete of the receiver cannot race with this + // call to OnPacketReceived. + it->second.receiver->OnPacketReceived(std::move(packet)); + + if (it->second.is_one_shot) { + port_to_receiver_.erase(it); + } +} + +void EmulatedEndpointImpl::Enable() { + RTC_DCHECK_RUN_ON(&enabled_state_checker_); + RTC_CHECK(!is_enabled_); + is_enabled_ = true; +} + +void EmulatedEndpointImpl::Disable() { + RTC_DCHECK_RUN_ON(&enabled_state_checker_); + RTC_CHECK(is_enabled_); + is_enabled_ = false; +} + +bool EmulatedEndpointImpl::Enabled() const { + RTC_DCHECK_RUN_ON(&enabled_state_checker_); + return is_enabled_; +} + +EmulatedNetworkStats EmulatedEndpointImpl::stats() const { + RTC_DCHECK_RUN_ON(task_queue_); + return stats_builder_.Build(); +} + +EmulatedEndpointImpl* EndpointsContainer::LookupByLocalAddress( + const rtc::IPAddress& local_ip) const { + for (auto* endpoint : endpoints_) { + rtc::IPAddress peer_local_address = endpoint->GetPeerLocalAddress(); + if (peer_local_address == local_ip) { + return endpoint; + } + } + RTC_CHECK(false) << "No network found for address" << local_ip.ToString(); +} + +EndpointsContainer::EndpointsContainer( + const std::vector<EmulatedEndpointImpl*>& endpoints, + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : endpoints_(endpoints), stats_gathering_mode_(stats_gathering_mode) {} + +bool EndpointsContainer::HasEndpoint(EmulatedEndpointImpl* endpoint) const { + for (auto* e : endpoints_) { + if (e->GetId() == endpoint->GetId()) { + return true; + } + } + return false; +} + +std::vector<std::unique_ptr<rtc::Network>> +EndpointsContainer::GetEnabledNetworks() const { + std::vector<std::unique_ptr<rtc::Network>> networks; + for (auto* endpoint : endpoints_) { + if (endpoint->Enabled()) { + networks.emplace_back( + std::make_unique<rtc::Network>(endpoint->network())); + } + } + return networks; +} + +std::vector<EmulatedEndpoint*> EndpointsContainer::GetEndpoints() const { + return std::vector<EmulatedEndpoint*>(endpoints_.begin(), endpoints_.end()); +} + +EmulatedNetworkStats EndpointsContainer::GetStats() const { + EmulatedNetworkStatsBuilder stats_builder(stats_gathering_mode_); + for (auto* endpoint : endpoints_) { + stats_builder.AddEmulatedNetworkStats(endpoint->stats()); + } + return stats_builder.Build(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/network_emulation.h b/third_party/libwebrtc/test/network/network_emulation.h new file mode 100644 index 0000000000..dffabafa7c --- /dev/null +++ b/third_party/libwebrtc/test/network/network_emulation.h @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2018 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_NETWORK_EMULATION_H_ +#define TEST_NETWORK_NETWORK_EMULATION_H_ + +#include <cstdint> +#include <deque> +#include <map> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/sequence_checker.h" +#include "api/test/network_emulation/network_emulation_interfaces.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/simulated_network.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "rtc_base/network.h" +#include "rtc_base/network_constants.h" +#include "rtc_base/socket_address.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/system/no_unique_address.h" +#include "rtc_base/task_queue_for_test.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "rtc_base/thread_annotations.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +// All methods of EmulatedNetworkOutgoingStatsBuilder have to be used on a +// single thread. It may be created on another thread. +class EmulatedNetworkOutgoingStatsBuilder { + public: + explicit EmulatedNetworkOutgoingStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + + void OnPacketSent(Timestamp sent_time, DataSize packet_size); + + void AddOutgoingStats(const EmulatedNetworkOutgoingStats& stats); + + EmulatedNetworkOutgoingStats Build() const; + + private: + const EmulatedNetworkStatsGatheringMode stats_gathering_mode_; + + RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; + EmulatedNetworkOutgoingStats stats_ RTC_GUARDED_BY(sequence_checker_); +}; + +// All methods of EmulatedNetworkIncomingStatsBuilder have to be used on a +// single thread. It may be created on another thread. +class EmulatedNetworkIncomingStatsBuilder { + public: + explicit EmulatedNetworkIncomingStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + + void OnPacketDropped(DataSize packet_size); + + void OnPacketReceived(Timestamp received_time, DataSize packet_size); + + // Adds stats collected from another endpoints to the builder. + void AddIncomingStats(const EmulatedNetworkIncomingStats& stats); + + EmulatedNetworkIncomingStats Build() const; + + private: + const EmulatedNetworkStatsGatheringMode stats_gathering_mode_; + + RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; + EmulatedNetworkIncomingStats stats_ RTC_GUARDED_BY(sequence_checker_); +}; + +// All methods of EmulatedNetworkStatsBuilder have to be used on a single +// thread. It may be created on another thread. +class EmulatedNetworkStatsBuilder { + public: + explicit EmulatedNetworkStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + explicit EmulatedNetworkStatsBuilder( + rtc::IPAddress local_ip, + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + + void OnPacketSent(Timestamp queued_time, + Timestamp sent_time, + rtc::IPAddress destination_ip, + DataSize packet_size); + + void OnPacketDropped(rtc::IPAddress source_ip, DataSize packet_size); + + void OnPacketReceived(Timestamp received_time, + rtc::IPAddress source_ip, + DataSize packet_size); + + void AddEmulatedNetworkStats(const EmulatedNetworkStats& stats); + + EmulatedNetworkStats Build() const; + + private: + const EmulatedNetworkStatsGatheringMode stats_gathering_mode_; + + RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; + std::vector<rtc::IPAddress> local_addresses_ + RTC_GUARDED_BY(sequence_checker_); + SamplesStatsCounter sent_packets_queue_wait_time_us_; + std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkOutgoingStatsBuilder>> + outgoing_stats_per_destination_ RTC_GUARDED_BY(sequence_checker_); + std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkIncomingStatsBuilder>> + incoming_stats_per_source_ RTC_GUARDED_BY(sequence_checker_); +}; + +// All methods of EmulatedNetworkNodeStatsBuilder have to be used on a +// single thread. It may be created on another thread. +class EmulatedNetworkNodeStatsBuilder { + public: + explicit EmulatedNetworkNodeStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + + void AddPacketTransportTime(TimeDelta time, size_t packet_size); + + void AddEmulatedNetworkNodeStats(const EmulatedNetworkNodeStats& stats); + + EmulatedNetworkNodeStats Build() const; + + private: + const EmulatedNetworkStatsGatheringMode stats_gathering_mode_; + + RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; + EmulatedNetworkNodeStats stats_ RTC_GUARDED_BY(sequence_checker_); +}; + +class LinkEmulation : public EmulatedNetworkReceiverInterface { + public: + LinkEmulation(Clock* clock, + rtc::TaskQueue* task_queue, + std::unique_ptr<NetworkBehaviorInterface> network_behavior, + EmulatedNetworkReceiverInterface* receiver, + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : clock_(clock), + task_queue_(task_queue), + network_behavior_(std::move(network_behavior)), + receiver_(receiver), + stats_builder_(stats_gathering_mode) {} + void OnPacketReceived(EmulatedIpPacket packet) override; + + EmulatedNetworkNodeStats stats() const; + + private: + struct StoredPacket { + uint64_t id; + Timestamp sent_time; + EmulatedIpPacket packet; + bool removed; + }; + void Process(Timestamp at_time) RTC_RUN_ON(task_queue_); + + Clock* const clock_; + rtc::TaskQueue* const task_queue_; + const std::unique_ptr<NetworkBehaviorInterface> network_behavior_ + RTC_GUARDED_BY(task_queue_); + EmulatedNetworkReceiverInterface* const receiver_; + + RepeatingTaskHandle process_task_ RTC_GUARDED_BY(task_queue_); + std::deque<StoredPacket> packets_ RTC_GUARDED_BY(task_queue_); + uint64_t next_packet_id_ RTC_GUARDED_BY(task_queue_) = 1; + + EmulatedNetworkNodeStatsBuilder stats_builder_ RTC_GUARDED_BY(task_queue_); +}; + +// Represents a component responsible for routing packets based on their IP +// address. All possible routes have to be set explicitly before packet for +// desired destination will be seen for the first time. If route is unknown +// the packet will be silently dropped. +class NetworkRouterNode : public EmulatedNetworkReceiverInterface { + public: + explicit NetworkRouterNode(rtc::TaskQueue* task_queue); + + void OnPacketReceived(EmulatedIpPacket packet) override; + void SetReceiver(const rtc::IPAddress& dest_ip, + EmulatedNetworkReceiverInterface* receiver); + void RemoveReceiver(const rtc::IPAddress& dest_ip); + // Sets a default receive that will be used for all incoming packets for which + // there is no specific receiver binded to their destination port. + void SetDefaultReceiver(EmulatedNetworkReceiverInterface* receiver); + void RemoveDefaultReceiver(); + void SetWatcher(std::function<void(const EmulatedIpPacket&)> watcher); + void SetFilter(std::function<bool(const EmulatedIpPacket&)> filter); + + private: + rtc::TaskQueue* const task_queue_; + absl::optional<EmulatedNetworkReceiverInterface*> default_receiver_ + RTC_GUARDED_BY(task_queue_); + std::map<rtc::IPAddress, EmulatedNetworkReceiverInterface*> routing_ + RTC_GUARDED_BY(task_queue_); + std::function<void(const EmulatedIpPacket&)> watcher_ + RTC_GUARDED_BY(task_queue_); + std::function<bool(const EmulatedIpPacket&)> filter_ + RTC_GUARDED_BY(task_queue_); +}; + +// Represents node in the emulated network. Nodes can be connected with each +// other to form different networks with different behavior. The behavior of +// the node itself is determined by a concrete implementation of +// NetworkBehaviorInterface that is provided on construction. +class EmulatedNetworkNode : public EmulatedNetworkReceiverInterface { + public: + // Creates node based on `network_behavior`. The specified `packet_overhead` + // is added to the size of each packet in the information provided to + // `network_behavior`. + // `task_queue` is used to process packets and to forward the packets when + // they are ready. + EmulatedNetworkNode( + Clock* clock, + rtc::TaskQueue* task_queue, + std::unique_ptr<NetworkBehaviorInterface> network_behavior, + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + ~EmulatedNetworkNode() override; + + EmulatedNetworkNode(const EmulatedNetworkNode&) = delete; + EmulatedNetworkNode& operator=(const EmulatedNetworkNode&) = delete; + + void OnPacketReceived(EmulatedIpPacket packet) override; + + LinkEmulation* link() { return &link_; } + NetworkRouterNode* router() { return &router_; } + EmulatedNetworkNodeStats stats() const; + + // Creates a route for the given receiver_ip over all the given nodes to the + // given receiver. + static void CreateRoute(const rtc::IPAddress& receiver_ip, + std::vector<EmulatedNetworkNode*> nodes, + EmulatedNetworkReceiverInterface* receiver); + static void ClearRoute(const rtc::IPAddress& receiver_ip, + std::vector<EmulatedNetworkNode*> nodes); + + private: + NetworkRouterNode router_; + LinkEmulation link_; +}; + +// Represents single network interface on the device. +// It will be used as sender from socket side to send data to the network and +// will act as packet receiver from emulated network side to receive packets +// from other EmulatedNetworkNodes. +class EmulatedEndpointImpl : public EmulatedEndpoint { + public: + struct Options { + Options(uint64_t id, + const rtc::IPAddress& ip, + const EmulatedEndpointConfig& config, + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + + // TODO(titovartem) check if we can remove id. + uint64_t id; + // Endpoint local IP address. + rtc::IPAddress ip; + EmulatedNetworkStatsGatheringMode stats_gathering_mode; + rtc::AdapterType type; + // Allow endpoint to send packets specifying source IP address different to + // the current endpoint IP address. If false endpoint will crash if attempt + // to send such packet will be done. + bool allow_send_packet_with_different_source_ip; + // Allow endpoint to receive packet with destination IP address different to + // the current endpoint IP address. If false endpoint will crash if such + // packet will arrive. + bool allow_receive_packets_with_different_dest_ip; + // Name of the endpoint used for logging purposes. + std::string log_name; + }; + + EmulatedEndpointImpl(const Options& options, + bool is_enabled, + rtc::TaskQueue* task_queue, + Clock* clock); + ~EmulatedEndpointImpl() override; + + uint64_t GetId() const; + + NetworkRouterNode* router() { return &router_; } + + void SendPacket(const rtc::SocketAddress& from, + const rtc::SocketAddress& to, + rtc::CopyOnWriteBuffer packet_data, + uint16_t application_overhead = 0) override; + + absl::optional<uint16_t> BindReceiver( + uint16_t desired_port, + EmulatedNetworkReceiverInterface* receiver) override; + // Binds a receiver, and automatically removes the binding after first call to + // OnPacketReceived. + absl::optional<uint16_t> BindOneShotReceiver( + uint16_t desired_port, + EmulatedNetworkReceiverInterface* receiver); + void UnbindReceiver(uint16_t port) override; + void BindDefaultReceiver(EmulatedNetworkReceiverInterface* receiver) override; + void UnbindDefaultReceiver() override; + + rtc::IPAddress GetPeerLocalAddress() const override; + + // Will be called to deliver packet into endpoint from network node. + void OnPacketReceived(EmulatedIpPacket packet) override; + + void Enable(); + void Disable(); + bool Enabled() const; + + const rtc::Network& network() const { return *network_.get(); } + + EmulatedNetworkStats stats() const; + + private: + struct ReceiverBinding { + EmulatedNetworkReceiverInterface* receiver; + bool is_one_shot; + }; + + absl::optional<uint16_t> BindReceiverInternal( + uint16_t desired_port, + EmulatedNetworkReceiverInterface* receiver, + bool is_one_shot); + + static constexpr uint16_t kFirstEphemeralPort = 49152; + uint16_t NextPort() RTC_EXCLUSIVE_LOCKS_REQUIRED(receiver_lock_); + + Mutex receiver_lock_; + RTC_NO_UNIQUE_ADDRESS SequenceChecker enabled_state_checker_; + + const Options options_; + bool is_enabled_ RTC_GUARDED_BY(enabled_state_checker_); + Clock* const clock_; + rtc::TaskQueue* const task_queue_; + std::unique_ptr<rtc::Network> network_; + NetworkRouterNode router_; + + uint16_t next_port_ RTC_GUARDED_BY(receiver_lock_); + absl::optional<EmulatedNetworkReceiverInterface*> default_receiver_ + RTC_GUARDED_BY(receiver_lock_); + std::map<uint16_t, ReceiverBinding> port_to_receiver_ + RTC_GUARDED_BY(receiver_lock_); + + EmulatedNetworkStatsBuilder stats_builder_ RTC_GUARDED_BY(task_queue_); +}; + +class EmulatedRoute { + public: + EmulatedRoute(EmulatedEndpointImpl* from, + std::vector<EmulatedNetworkNode*> via_nodes, + EmulatedEndpointImpl* to, + bool is_default) + : from(from), + via_nodes(std::move(via_nodes)), + to(to), + active(true), + is_default(is_default) {} + + EmulatedEndpointImpl* from; + std::vector<EmulatedNetworkNode*> via_nodes; + EmulatedEndpointImpl* to; + bool active; + bool is_default; +}; + +// This object is immutable and so thread safe. +class EndpointsContainer { + public: + EndpointsContainer(const std::vector<EmulatedEndpointImpl*>& endpoints, + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + + EmulatedEndpointImpl* LookupByLocalAddress( + const rtc::IPAddress& local_ip) const; + bool HasEndpoint(EmulatedEndpointImpl* endpoint) const; + // Returns list of networks for enabled endpoints. Caller takes ownership of + // returned rtc::Network objects. + std::vector<std::unique_ptr<rtc::Network>> GetEnabledNetworks() const; + std::vector<EmulatedEndpoint*> GetEndpoints() const; + EmulatedNetworkStats GetStats() const; + + private: + const std::vector<EmulatedEndpointImpl*> endpoints_; + const EmulatedNetworkStatsGatheringMode stats_gathering_mode_; +}; + +template <typename FakePacketType> +class FakePacketRoute : public EmulatedNetworkReceiverInterface { + public: + FakePacketRoute(EmulatedRoute* route, + std::function<void(FakePacketType, Timestamp)> action) + : route_(route), + action_(std::move(action)), + send_addr_(route_->from->GetPeerLocalAddress(), 0), + recv_addr_(route_->to->GetPeerLocalAddress(), + *route_->to->BindReceiver(0, this)) {} + + ~FakePacketRoute() { route_->to->UnbindReceiver(recv_addr_.port()); } + + void SendPacket(size_t size, FakePacketType packet) { + RTC_CHECK_GE(size, sizeof(int)); + sent_.emplace(next_packet_id_, packet); + rtc::CopyOnWriteBuffer buf(size); + reinterpret_cast<int*>(buf.MutableData())[0] = next_packet_id_++; + route_->from->SendPacket(send_addr_, recv_addr_, buf); + } + + void OnPacketReceived(EmulatedIpPacket packet) override { + int packet_id = reinterpret_cast<const int*>(packet.data.data())[0]; + action_(std::move(sent_[packet_id]), packet.arrival_time); + sent_.erase(packet_id); + } + + private: + EmulatedRoute* const route_; + const std::function<void(FakePacketType, Timestamp)> action_; + const rtc::SocketAddress send_addr_; + const rtc::SocketAddress recv_addr_; + int next_packet_id_ = 0; + std::map<int, FakePacketType> sent_; +}; + +template <typename RequestPacketType, typename ResponsePacketType> +class TwoWayFakeTrafficRoute { + public: + class TrafficHandlerInterface { + public: + virtual void OnRequest(RequestPacketType, Timestamp) = 0; + virtual void OnResponse(ResponsePacketType, Timestamp) = 0; + virtual ~TrafficHandlerInterface() = default; + }; + TwoWayFakeTrafficRoute(TrafficHandlerInterface* handler, + EmulatedRoute* send_route, + EmulatedRoute* ret_route) + : handler_(handler), + request_handler_{send_route, + [&](RequestPacketType packet, Timestamp arrival_time) { + handler_->OnRequest(std::move(packet), arrival_time); + }}, + response_handler_{ + ret_route, [&](ResponsePacketType packet, Timestamp arrival_time) { + handler_->OnResponse(std::move(packet), arrival_time); + }} {} + void SendRequest(size_t size, RequestPacketType packet) { + request_handler_.SendPacket(size, std::move(packet)); + } + void SendResponse(size_t size, ResponsePacketType packet) { + response_handler_.SendPacket(size, std::move(packet)); + } + + private: + TrafficHandlerInterface* handler_; + FakePacketRoute<RequestPacketType> request_handler_; + FakePacketRoute<ResponsePacketType> response_handler_; +}; +} // namespace webrtc + +#endif // TEST_NETWORK_NETWORK_EMULATION_H_ diff --git a/third_party/libwebrtc/test/network/network_emulation_manager.cc b/third_party/libwebrtc/test/network/network_emulation_manager.cc new file mode 100644 index 0000000000..97c0bc1ba8 --- /dev/null +++ b/third_party/libwebrtc/test/network/network_emulation_manager.cc @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2019 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/network_emulation_manager.h" + +#include <algorithm> +#include <memory> + +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "call/simulated_network.h" +#include "test/network/emulated_turn_server.h" +#include "test/network/traffic_route.h" +#include "test/time_controller/real_time_controller.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { +namespace test { +namespace { + +// uint32_t representation of 192.168.0.0 address +constexpr uint32_t kMinIPv4Address = 0xC0A80000; +// uint32_t representation of 192.168.255.255 address +constexpr uint32_t kMaxIPv4Address = 0xC0A8FFFF; + +std::unique_ptr<TimeController> CreateTimeController(TimeMode mode) { + switch (mode) { + case TimeMode::kRealTime: + return std::make_unique<RealTimeController>(); + case TimeMode::kSimulated: + // Using an offset of 100000 to get nice fixed width and readable + // timestamps in typical test scenarios. + const Timestamp kSimulatedStartTime = Timestamp::Seconds(100000); + return std::make_unique<GlobalSimulatedTimeController>( + kSimulatedStartTime); + } +} +} // namespace + +NetworkEmulationManagerImpl::NetworkEmulationManagerImpl( + TimeMode mode, + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : time_mode_(mode), + stats_gathering_mode_(stats_gathering_mode), + time_controller_(CreateTimeController(mode)), + clock_(time_controller_->GetClock()), + next_node_id_(1), + next_ip4_address_(kMinIPv4Address), + task_queue_(time_controller_->GetTaskQueueFactory()->CreateTaskQueue( + "NetworkEmulation", + TaskQueueFactory::Priority::NORMAL)) {} + +// 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() { + for (auto& turn_server : turn_servers_) { + turn_server->Stop(); + } +} + +EmulatedNetworkNode* NetworkEmulationManagerImpl::CreateEmulatedNode( + BuiltInNetworkBehaviorConfig config, + uint64_t random_seed) { + return CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(config, random_seed)); +} + +EmulatedNetworkNode* NetworkEmulationManagerImpl::CreateEmulatedNode( + std::unique_ptr<NetworkBehaviorInterface> network_behavior) { + auto node = std::make_unique<EmulatedNetworkNode>( + clock_, &task_queue_, std::move(network_behavior), stats_gathering_mode_); + EmulatedNetworkNode* out = node.get(); + task_queue_.PostTask([this, node = std::move(node)]() mutable { + network_nodes_.push_back(std::move(node)); + }); + return out; +} + +NetworkEmulationManager::SimulatedNetworkNode::Builder +NetworkEmulationManagerImpl::NodeBuilder() { + return SimulatedNetworkNode::Builder(this); +} + +EmulatedEndpointImpl* NetworkEmulationManagerImpl::CreateEndpoint( + EmulatedEndpointConfig config) { + absl::optional<rtc::IPAddress> ip = config.ip; + if (!ip) { + switch (config.generated_ip_family) { + case EmulatedEndpointConfig::IpAddressFamily::kIpv4: + ip = GetNextIPv4Address(); + RTC_CHECK(ip) << "All auto generated IPv4 addresses exhausted"; + break; + case EmulatedEndpointConfig::IpAddressFamily::kIpv6: + ip = GetNextIPv4Address(); + RTC_CHECK(ip) << "All auto generated IPv6 addresses exhausted"; + ip = ip->AsIPv6Address(); + break; + } + } + + bool res = used_ip_addresses_.insert(*ip).second; + RTC_CHECK(res) << "IP=" << ip->ToString() << " already in use"; + auto node = std::make_unique<EmulatedEndpointImpl>( + EmulatedEndpointImpl::Options(next_node_id_++, *ip, config, + stats_gathering_mode_), + config.start_as_enabled, &task_queue_, clock_); + EmulatedEndpointImpl* out = node.get(); + endpoints_.push_back(std::move(node)); + return out; +} + +void NetworkEmulationManagerImpl::EnableEndpoint(EmulatedEndpoint* endpoint) { + EmulatedNetworkManager* network_manager = + endpoint_to_network_manager_[endpoint]; + RTC_CHECK(network_manager); + network_manager->EnableEndpoint(static_cast<EmulatedEndpointImpl*>(endpoint)); +} + +void NetworkEmulationManagerImpl::DisableEndpoint(EmulatedEndpoint* endpoint) { + EmulatedNetworkManager* network_manager = + endpoint_to_network_manager_[endpoint]; + RTC_CHECK(network_manager); + network_manager->DisableEndpoint( + static_cast<EmulatedEndpointImpl*>(endpoint)); +} + +EmulatedRoute* NetworkEmulationManagerImpl::CreateRoute( + EmulatedEndpoint* from, + const std::vector<EmulatedNetworkNode*>& via_nodes, + EmulatedEndpoint* to) { + // Because endpoint has no send node by default at least one should be + // provided here. + RTC_CHECK(!via_nodes.empty()); + + static_cast<EmulatedEndpointImpl*>(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]); + cur_node = via_nodes[i]; + } + cur_node->router()->SetReceiver(to->GetPeerLocalAddress(), to); + + std::unique_ptr<EmulatedRoute> route = std::make_unique<EmulatedRoute>( + static_cast<EmulatedEndpointImpl*>(from), std::move(via_nodes), + static_cast<EmulatedEndpointImpl*>(to), /*is_default=*/false); + EmulatedRoute* out = route.get(); + routes_.push_back(std::move(route)); + return out; +} + +EmulatedRoute* NetworkEmulationManagerImpl::CreateRoute( + const std::vector<EmulatedNetworkNode*>& via_nodes) { + EmulatedEndpoint* from = CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* to = CreateEndpoint(EmulatedEndpointConfig()); + return CreateRoute(from, via_nodes, to); +} + +EmulatedRoute* NetworkEmulationManagerImpl::CreateDefaultRoute( + EmulatedEndpoint* from, + const std::vector<EmulatedNetworkNode*>& via_nodes, + EmulatedEndpoint* to) { + // Because endpoint has no send node by default at least one should be + // provided here. + RTC_CHECK(!via_nodes.empty()); + + static_cast<EmulatedEndpointImpl*>(from)->router()->SetDefaultReceiver( + via_nodes[0]); + EmulatedNetworkNode* cur_node = via_nodes[0]; + for (size_t i = 1; i < via_nodes.size(); ++i) { + cur_node->router()->SetDefaultReceiver(via_nodes[i]); + cur_node = via_nodes[i]; + } + cur_node->router()->SetDefaultReceiver(to); + + std::unique_ptr<EmulatedRoute> route = std::make_unique<EmulatedRoute>( + static_cast<EmulatedEndpointImpl*>(from), std::move(via_nodes), + static_cast<EmulatedEndpointImpl*>(to), /*is_default=*/true); + EmulatedRoute* out = route.get(); + routes_.push_back(std::move(route)); + return out; +} + +void NetworkEmulationManagerImpl::ClearRoute(EmulatedRoute* route) { + RTC_CHECK(route->active) << "Route already cleared"; + task_queue_.SendTask([route]() { + // Remove receiver from intermediate nodes. + for (auto* node : route->via_nodes) { + if (route->is_default) { + node->router()->RemoveDefaultReceiver(); + } else { + node->router()->RemoveReceiver(route->to->GetPeerLocalAddress()); + } + } + // Remove destination endpoint from source endpoint's router. + if (route->is_default) { + route->from->router()->RemoveDefaultReceiver(); + } else { + route->from->router()->RemoveReceiver(route->to->GetPeerLocalAddress()); + } + + route->active = false; + }); +} + +TcpMessageRoute* NetworkEmulationManagerImpl::CreateTcpRoute( + EmulatedRoute* send_route, + EmulatedRoute* ret_route) { + auto tcp_route = std::make_unique<TcpMessageRouteImpl>( + clock_, task_queue_.Get(), send_route, ret_route); + auto* route_ptr = tcp_route.get(); + task_queue_.PostTask([this, tcp_route = std::move(tcp_route)]() mutable { + tcp_message_routes_.push_back(std::move(tcp_route)); + }); + return route_ptr; +} + +CrossTrafficRoute* NetworkEmulationManagerImpl::CreateCrossTrafficRoute( + const std::vector<EmulatedNetworkNode*>& via_nodes) { + RTC_CHECK(!via_nodes.empty()); + EmulatedEndpointImpl* endpoint = CreateEndpoint(EmulatedEndpointConfig()); + + // Setup a route via specified nodes. + EmulatedNetworkNode* cur_node = via_nodes[0]; + for (size_t i = 1; i < via_nodes.size(); ++i) { + cur_node->router()->SetReceiver(endpoint->GetPeerLocalAddress(), + via_nodes[i]); + cur_node = via_nodes[i]; + } + cur_node->router()->SetReceiver(endpoint->GetPeerLocalAddress(), endpoint); + + std::unique_ptr<CrossTrafficRoute> traffic_route = + std::make_unique<CrossTrafficRouteImpl>(clock_, via_nodes[0], endpoint); + CrossTrafficRoute* out = traffic_route.get(); + traffic_routes_.push_back(std::move(traffic_route)); + return out; +} + +CrossTrafficGenerator* NetworkEmulationManagerImpl::StartCrossTraffic( + std::unique_ptr<CrossTrafficGenerator> generator) { + CrossTrafficGenerator* out = generator.get(); + task_queue_.PostTask([this, generator = std::move(generator)]() mutable { + auto* generator_ptr = generator.get(); + + auto repeating_task_handle = + RepeatingTaskHandle::Start(task_queue_.Get(), [this, generator_ptr] { + generator_ptr->Process(Now()); + return generator_ptr->GetProcessInterval(); + }); + + cross_traffics_.push_back(CrossTrafficSource( + std::move(generator), std::move(repeating_task_handle))); + }); + return out; +} + +void NetworkEmulationManagerImpl::StopCrossTraffic( + CrossTrafficGenerator* generator) { + task_queue_.PostTask([=]() { + auto it = std::find_if(cross_traffics_.begin(), cross_traffics_.end(), + [=](const CrossTrafficSource& el) { + return el.first.get() == generator; + }); + it->second.Stop(); + cross_traffics_.erase(it); + }); +} + +EmulatedNetworkManagerInterface* +NetworkEmulationManagerImpl::CreateEmulatedNetworkManagerInterface( + const std::vector<EmulatedEndpoint*>& endpoints) { + std::vector<EmulatedEndpointImpl*> endpoint_impls; + endpoint_impls.reserve(endpoints.size()); + for (EmulatedEndpoint* endpoint : endpoints) { + endpoint_impls.push_back(static_cast<EmulatedEndpointImpl*>(endpoint)); + } + auto endpoints_container = std::make_unique<EndpointsContainer>( + endpoint_impls, stats_gathering_mode_); + auto network_manager = std::make_unique<EmulatedNetworkManager>( + time_controller_.get(), &task_queue_, endpoints_container.get()); + for (auto* endpoint : endpoints) { + // Associate endpoint with network manager. + bool insertion_result = + endpoint_to_network_manager_.insert({endpoint, network_manager.get()}) + .second; + RTC_CHECK(insertion_result) + << "Endpoint ip=" << endpoint->GetPeerLocalAddress().ToString() + << " is already used for another network"; + } + + EmulatedNetworkManagerInterface* out = network_manager.get(); + + endpoints_containers_.push_back(std::move(endpoints_container)); + network_managers_.push_back(std::move(network_manager)); + return out; +} + +void NetworkEmulationManagerImpl::GetStats( + rtc::ArrayView<EmulatedEndpoint* const> endpoints, + std::function<void(EmulatedNetworkStats)> stats_callback) { + task_queue_.PostTask([endpoints, stats_callback, + stats_gathering_mode = stats_gathering_mode_]() { + EmulatedNetworkStatsBuilder stats_builder(stats_gathering_mode); + for (auto* endpoint : endpoints) { + // It's safe to cast here because EmulatedEndpointImpl can be the only + // implementation of EmulatedEndpoint, because only it has access to + // EmulatedEndpoint constructor. + auto endpoint_impl = static_cast<EmulatedEndpointImpl*>(endpoint); + stats_builder.AddEmulatedNetworkStats(endpoint_impl->stats()); + } + stats_callback(stats_builder.Build()); + }); +} + +void NetworkEmulationManagerImpl::GetStats( + rtc::ArrayView<EmulatedNetworkNode* const> nodes, + std::function<void(EmulatedNetworkNodeStats)> stats_callback) { + task_queue_.PostTask( + [nodes, stats_callback, stats_gathering_mode = stats_gathering_mode_]() { + EmulatedNetworkNodeStatsBuilder stats_builder(stats_gathering_mode); + for (auto* node : nodes) { + stats_builder.AddEmulatedNetworkNodeStats(node->stats()); + } + stats_callback(stats_builder.Build()); + }); +} + +absl::optional<rtc::IPAddress> +NetworkEmulationManagerImpl::GetNextIPv4Address() { + uint32_t addresses_count = kMaxIPv4Address - kMinIPv4Address; + for (uint32_t i = 0; i < addresses_count; i++) { + rtc::IPAddress ip(next_ip4_address_); + if (next_ip4_address_ == kMaxIPv4Address) { + next_ip4_address_ = kMinIPv4Address; + } else { + next_ip4_address_++; + } + if (used_ip_addresses_.find(ip) == used_ip_addresses_.end()) { + return ip; + } + } + return absl::nullopt; +} + +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<unsigned>(turn_servers_.size())); + auto turn = std::make_unique<EmulatedTURNServer>( + 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/third_party/libwebrtc/test/network/network_emulation_manager.h b/third_party/libwebrtc/test/network/network_emulation_manager.h new file mode 100644 index 0000000000..29debca693 --- /dev/null +++ b/third_party/libwebrtc/test/network/network_emulation_manager.h @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2019 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_NETWORK_EMULATION_MANAGER_H_ +#define TEST_NETWORK_NETWORK_EMULATION_MANAGER_H_ + +#include <map> +#include <memory> +#include <set> +#include <utility> +#include <vector> + +#include "api/array_view.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/simulated_network.h" +#include "api/test/time_controller.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/task_queue_for_test.h" +#include "rtc_base/task_utils/repeating_task.h" +#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/network_emulation.h" + +namespace webrtc { +namespace test { + +class NetworkEmulationManagerImpl : public NetworkEmulationManager { + public: + NetworkEmulationManagerImpl( + TimeMode mode, + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + ~NetworkEmulationManagerImpl(); + + EmulatedNetworkNode* CreateEmulatedNode(BuiltInNetworkBehaviorConfig config, + uint64_t random_seed = 1) override; + EmulatedNetworkNode* CreateEmulatedNode( + std::unique_ptr<NetworkBehaviorInterface> network_behavior) override; + + SimulatedNetworkNode::Builder NodeBuilder() override; + + EmulatedEndpointImpl* CreateEndpoint(EmulatedEndpointConfig config) override; + void EnableEndpoint(EmulatedEndpoint* endpoint) override; + void DisableEndpoint(EmulatedEndpoint* endpoint) override; + + EmulatedRoute* CreateRoute(EmulatedEndpoint* from, + const std::vector<EmulatedNetworkNode*>& via_nodes, + EmulatedEndpoint* to) override; + + EmulatedRoute* CreateRoute( + const std::vector<EmulatedNetworkNode*>& via_nodes) override; + + EmulatedRoute* CreateDefaultRoute( + EmulatedEndpoint* from, + const std::vector<EmulatedNetworkNode*>& via_nodes, + EmulatedEndpoint* to) override; + + void ClearRoute(EmulatedRoute* route) override; + + TcpMessageRoute* CreateTcpRoute(EmulatedRoute* send_route, + EmulatedRoute* ret_route) override; + + CrossTrafficRoute* CreateCrossTrafficRoute( + const std::vector<EmulatedNetworkNode*>& via_nodes) override; + + CrossTrafficGenerator* StartCrossTraffic( + std::unique_ptr<CrossTrafficGenerator> generator) override; + void StopCrossTraffic(CrossTrafficGenerator* generator) override; + + EmulatedNetworkManagerInterface* CreateEmulatedNetworkManagerInterface( + const std::vector<EmulatedEndpoint*>& endpoints) override; + + void GetStats( + rtc::ArrayView<EmulatedEndpoint* const> endpoints, + std::function<void(EmulatedNetworkStats)> stats_callback) override; + + void GetStats( + rtc::ArrayView<EmulatedNetworkNode* const> nodes, + std::function<void(EmulatedNetworkNodeStats)> stats_callback) override; + + TimeController* time_controller() override { return time_controller_.get(); } + + TimeMode time_mode() const override { return time_mode_; } + + Timestamp Now() const; + + EmulatedTURNServerInterface* CreateTURNServer( + EmulatedTURNServerConfig config) override; + + private: + using CrossTrafficSource = + std::pair<std::unique_ptr<CrossTrafficGenerator>, RepeatingTaskHandle>; + + absl::optional<rtc::IPAddress> GetNextIPv4Address(); + + const TimeMode time_mode_; + const EmulatedNetworkStatsGatheringMode stats_gathering_mode_; + const std::unique_ptr<TimeController> time_controller_; + Clock* const clock_; + int next_node_id_; + + RepeatingTaskHandle process_task_handle_; + + uint32_t next_ip4_address_; + std::set<rtc::IPAddress> used_ip_addresses_; + + // All objects can be added to the manager only when it is idle. + std::vector<std::unique_ptr<EmulatedEndpoint>> endpoints_; + std::vector<std::unique_ptr<EmulatedNetworkNode>> network_nodes_; + std::vector<std::unique_ptr<EmulatedRoute>> routes_; + std::vector<std::unique_ptr<CrossTrafficRoute>> traffic_routes_; + std::vector<CrossTrafficSource> cross_traffics_; + std::list<std::unique_ptr<TcpMessageRouteImpl>> tcp_message_routes_; + std::vector<std::unique_ptr<EndpointsContainer>> endpoints_containers_; + std::vector<std::unique_ptr<EmulatedNetworkManager>> network_managers_; + std::vector<std::unique_ptr<EmulatedTURNServer>> turn_servers_; + + std::map<EmulatedEndpoint*, EmulatedNetworkManager*> + endpoint_to_network_manager_; + + // Must be the last field, so it will be deleted first, because tasks + // in the TaskQueue can access other fields of the instance of this class. + TaskQueueForTest task_queue_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_NETWORK_NETWORK_EMULATION_MANAGER_H_ diff --git a/third_party/libwebrtc/test/network/network_emulation_pc_unittest.cc b/third_party/libwebrtc/test/network/network_emulation_pc_unittest.cc new file mode 100644 index 0000000000..51a45a8234 --- /dev/null +++ b/third_party/libwebrtc/test/network/network_emulation_pc_unittest.cc @@ -0,0 +1,319 @@ +/* + * Copyright 2019 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 <cstdint> +#include <memory> + +#include "api/call/call_factory_interface.h" +#include "api/peer_connection_interface.h" +#include "api/rtc_event_log/rtc_event_log_factory.h" +#include "api/scoped_refptr.h" +#include "api/task_queue/default_task_queue_factory.h" +#include "api/transport/field_trial_based_config.h" +#include "call/simulated_network.h" +#include "media/engine/webrtc_media_engine.h" +#include "media/engine/webrtc_media_engine_defaults.h" +#include "modules/audio_device/include/test_audio_device.h" +#include "p2p/base/basic_packet_socket_factory.h" +#include "p2p/client/basic_port_allocator.h" +#include "pc/peer_connection_wrapper.h" +#include "pc/test/mock_peer_connection_observers.h" +#include "rtc_base/gunit.h" +#include "rtc_base/task_queue_for_test.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/network/network_emulation.h" +#include "test/network/network_emulation_manager.h" + +namespace webrtc { +namespace test { +namespace { + +constexpr int kDefaultTimeoutMs = 1000; +constexpr int kMaxAptitude = 32000; +constexpr int kSamplingFrequency = 48000; +constexpr char kSignalThreadName[] = "signaling_thread"; + +bool AddIceCandidates(PeerConnectionWrapper* peer, + std::vector<const IceCandidateInterface*> candidates) { + bool success = true; + for (const auto candidate : candidates) { + if (!peer->pc()->AddIceCandidate(candidate)) { + success = false; + } + } + return success; +} + +rtc::scoped_refptr<PeerConnectionFactoryInterface> CreatePeerConnectionFactory( + rtc::Thread* signaling_thread, + rtc::Thread* network_thread) { + PeerConnectionFactoryDependencies pcf_deps; + pcf_deps.task_queue_factory = CreateDefaultTaskQueueFactory(); + pcf_deps.call_factory = CreateCallFactory(); + pcf_deps.event_log_factory = + std::make_unique<RtcEventLogFactory>(pcf_deps.task_queue_factory.get()); + pcf_deps.network_thread = network_thread; + pcf_deps.signaling_thread = signaling_thread; + pcf_deps.trials = std::make_unique<FieldTrialBasedConfig>(); + cricket::MediaEngineDependencies media_deps; + media_deps.task_queue_factory = pcf_deps.task_queue_factory.get(); + media_deps.adm = TestAudioDeviceModule::Create( + media_deps.task_queue_factory, + TestAudioDeviceModule::CreatePulsedNoiseCapturer(kMaxAptitude, + kSamplingFrequency), + TestAudioDeviceModule::CreateDiscardRenderer(kSamplingFrequency), + /*speed=*/1.f); + media_deps.trials = pcf_deps.trials.get(); + SetMediaEngineDefaults(&media_deps); + pcf_deps.media_engine = cricket::CreateMediaEngine(std::move(media_deps)); + return CreateModularPeerConnectionFactory(std::move(pcf_deps)); +} + +rtc::scoped_refptr<PeerConnectionInterface> CreatePeerConnection( + const rtc::scoped_refptr<PeerConnectionFactoryInterface>& pcf, + PeerConnectionObserver* observer, + rtc::PacketSocketFactory* packet_socket_factory, + rtc::NetworkManager* network_manager, + EmulatedTURNServerInterface* turn_server = nullptr) { + PeerConnectionDependencies pc_deps(observer); + auto port_allocator = std::make_unique<cricket::BasicPortAllocator>( + network_manager, packet_socket_factory); + + // This test does not support TCP + int flags = cricket::PORTALLOCATOR_DISABLE_TCP; + port_allocator->set_flags(port_allocator->flags() | flags); + + 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); + } + + auto result = + pcf->CreatePeerConnectionOrError(rtc_configuration, std::move(pc_deps)); + if (!result.ok()) { + return nullptr; + } + return result.MoveValue(); +} + +} // namespace + +TEST(NetworkEmulationManagerPCTest, Run) { + std::unique_ptr<rtc::Thread> signaling_thread = rtc::Thread::Create(); + signaling_thread->SetName(kSignalThreadName, nullptr); + signaling_thread->Start(); + + // Setup emulated network + NetworkEmulationManagerImpl emulation( + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault); + + EmulatedNetworkNode* alice_node = emulation.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* bob_node = emulation.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedEndpoint* alice_endpoint = + emulation.CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + emulation.CreateEndpoint(EmulatedEndpointConfig()); + emulation.CreateRoute(alice_endpoint, {alice_node}, bob_endpoint); + emulation.CreateRoute(bob_endpoint, {bob_node}, alice_endpoint); + + EmulatedNetworkManagerInterface* alice_network = + emulation.CreateEmulatedNetworkManagerInterface({alice_endpoint}); + EmulatedNetworkManagerInterface* bob_network = + emulation.CreateEmulatedNetworkManagerInterface({bob_endpoint}); + + // Setup peer connections. + rtc::scoped_refptr<PeerConnectionFactoryInterface> alice_pcf; + rtc::scoped_refptr<PeerConnectionInterface> alice_pc; + std::unique_ptr<MockPeerConnectionObserver> alice_observer = + std::make_unique<MockPeerConnectionObserver>(); + + rtc::scoped_refptr<PeerConnectionFactoryInterface> bob_pcf; + rtc::scoped_refptr<PeerConnectionInterface> bob_pc; + std::unique_ptr<MockPeerConnectionObserver> bob_observer = + std::make_unique<MockPeerConnectionObserver>(); + + SendTask(signaling_thread.get(), [&]() { + alice_pcf = CreatePeerConnectionFactory(signaling_thread.get(), + alice_network->network_thread()); + alice_pc = CreatePeerConnection(alice_pcf, alice_observer.get(), + alice_network->packet_socket_factory(), + alice_network->network_manager()); + + bob_pcf = CreatePeerConnectionFactory(signaling_thread.get(), + bob_network->network_thread()); + bob_pc = CreatePeerConnection(bob_pcf, bob_observer.get(), + bob_network->packet_socket_factory(), + bob_network->network_manager()); + }); + + std::unique_ptr<PeerConnectionWrapper> alice = + std::make_unique<PeerConnectionWrapper>(alice_pcf, alice_pc, + std::move(alice_observer)); + std::unique_ptr<PeerConnectionWrapper> bob = + std::make_unique<PeerConnectionWrapper>(bob_pcf, bob_pc, + std::move(bob_observer)); + + SendTask(signaling_thread.get(), [&]() { + rtc::scoped_refptr<webrtc::AudioSourceInterface> source = + alice_pcf->CreateAudioSource(cricket::AudioOptions()); + rtc::scoped_refptr<AudioTrackInterface> track = + alice_pcf->CreateAudioTrack("audio", source.get()); + 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(); + }); +} + +TEST(NetworkEmulationManagerPCTest, RunTURN) { + std::unique_ptr<rtc::Thread> signaling_thread = rtc::Thread::Create(); + signaling_thread->SetName(kSignalThreadName, nullptr); + signaling_thread->Start(); + + // Setup emulated network + NetworkEmulationManagerImpl emulation( + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault); + + EmulatedNetworkNode* alice_node = emulation.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* bob_node = emulation.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* turn_node = emulation.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(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<PeerConnectionFactoryInterface> alice_pcf; + rtc::scoped_refptr<PeerConnectionInterface> alice_pc; + std::unique_ptr<MockPeerConnectionObserver> alice_observer = + std::make_unique<MockPeerConnectionObserver>(); + + rtc::scoped_refptr<PeerConnectionFactoryInterface> bob_pcf; + rtc::scoped_refptr<PeerConnectionInterface> bob_pc; + std::unique_ptr<MockPeerConnectionObserver> bob_observer = + std::make_unique<MockPeerConnectionObserver>(); + + SendTask(signaling_thread.get(), [&]() { + alice_pcf = CreatePeerConnectionFactory(signaling_thread.get(), + alice_network->network_thread()); + alice_pc = CreatePeerConnection( + alice_pcf, alice_observer.get(), alice_network->packet_socket_factory(), + 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->packet_socket_factory(), + bob_network->network_manager(), bob_turn); + }); + + std::unique_ptr<PeerConnectionWrapper> alice = + std::make_unique<PeerConnectionWrapper>(alice_pcf, alice_pc, + std::move(alice_observer)); + std::unique_ptr<PeerConnectionWrapper> bob = + std::make_unique<PeerConnectionWrapper>(bob_pcf, bob_pc, + std::move(bob_observer)); + + SendTask(signaling_thread.get(), [&]() { + rtc::scoped_refptr<webrtc::AudioSourceInterface> source = + alice_pcf->CreateAudioSource(cricket::AudioOptions()); + rtc::scoped_refptr<AudioTrackInterface> track = + alice_pcf->CreateAudioTrack("audio", source.get()); + 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/third_party/libwebrtc/test/network/network_emulation_unittest.cc b/third_party/libwebrtc/test/network/network_emulation_unittest.cc new file mode 100644 index 0000000000..2e67a5a00a --- /dev/null +++ b/third_party/libwebrtc/test/network/network_emulation_unittest.cc @@ -0,0 +1,676 @@ +/* + * Copyright 2019 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/network_emulation.h" + +#include <atomic> +#include <memory> +#include <set> + +#include "api/test/simulated_network.h" +#include "api/units/time_delta.h" +#include "call/simulated_network.h" +#include "rtc_base/event.h" +#include "rtc_base/gunit.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/task_queue_for_test.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/network/network_emulation_manager.h" + +namespace webrtc { +namespace test { +namespace { + +using ::testing::ElementsAreArray; + +constexpr TimeDelta kNetworkPacketWaitTimeout = TimeDelta::Millis(100); +constexpr TimeDelta kStatsWaitTimeout = TimeDelta::Seconds(1); +constexpr int kOverheadIpv4Udp = 20 + 8; + +class SocketReader : public sigslot::has_slots<> { + public: + explicit SocketReader(rtc::Socket* socket, rtc::Thread* network_thread) + : socket_(socket), network_thread_(network_thread) { + socket_->SignalReadEvent.connect(this, &SocketReader::OnReadEvent); + size_ = 128 * 1024; + buf_ = new char[size_]; + } + ~SocketReader() override { delete[] buf_; } + + void OnReadEvent(rtc::Socket* socket) { + RTC_DCHECK(socket_ == socket); + RTC_DCHECK(network_thread_->IsCurrent()); + int64_t timestamp; + len_ = socket_->Recv(buf_, size_, ×tamp); + + MutexLock lock(&lock_); + received_count_++; + } + + int ReceivedCount() { + MutexLock lock(&lock_); + return received_count_; + } + + private: + rtc::Socket* const socket_; + rtc::Thread* const network_thread_; + char* buf_; + size_t size_; + int len_; + + Mutex lock_; + int received_count_ RTC_GUARDED_BY(lock_) = 0; +}; + +class MockReceiver : public EmulatedNetworkReceiverInterface { + public: + MOCK_METHOD(void, OnPacketReceived, (EmulatedIpPacket packet), (override)); +}; + +class NetworkEmulationManagerThreeNodesRoutingTest : public ::testing::Test { + public: + NetworkEmulationManagerThreeNodesRoutingTest() { + e1_ = emulation_.CreateEndpoint(EmulatedEndpointConfig()); + e2_ = emulation_.CreateEndpoint(EmulatedEndpointConfig()); + e3_ = emulation_.CreateEndpoint(EmulatedEndpointConfig()); + } + + void SetupRouting( + std::function<void(EmulatedEndpoint*, + EmulatedEndpoint*, + EmulatedEndpoint*, + NetworkEmulationManager*)> 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. + emulation_.time_controller()->AdvanceTime(kNetworkPacketWaitTimeout); + } + + private: + // Receivers: r_<source endpoint>_<destination endpoint> + // 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_{ + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault}; + EmulatedEndpoint* e1_; + EmulatedEndpoint* e2_; + EmulatedEndpoint* e3_; +}; + +EmulatedNetworkNode* CreateEmulatedNodeWithDefaultBuiltInConfig( + NetworkEmulationManager* emulation) { + return emulation->CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); +} + +} // namespace + +using ::testing::_; + +TEST(NetworkEmulationManagerTest, GeneratedIpv4AddressDoesNotCollide) { + NetworkEmulationManagerImpl network_manager( + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault); + std::set<rtc::IPAddress> ips; + EmulatedEndpointConfig config; + config.generated_ip_family = EmulatedEndpointConfig::IpAddressFamily::kIpv4; + for (int i = 0; i < 1000; i++) { + EmulatedEndpoint* endpoint = network_manager.CreateEndpoint(config); + ASSERT_EQ(endpoint->GetPeerLocalAddress().family(), AF_INET); + bool result = ips.insert(endpoint->GetPeerLocalAddress()).second; + ASSERT_TRUE(result); + } +} + +TEST(NetworkEmulationManagerTest, GeneratedIpv6AddressDoesNotCollide) { + NetworkEmulationManagerImpl network_manager( + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault); + std::set<rtc::IPAddress> ips; + EmulatedEndpointConfig config; + config.generated_ip_family = EmulatedEndpointConfig::IpAddressFamily::kIpv6; + for (int i = 0; i < 1000; i++) { + EmulatedEndpoint* endpoint = network_manager.CreateEndpoint(config); + ASSERT_EQ(endpoint->GetPeerLocalAddress().family(), AF_INET6); + bool result = ips.insert(endpoint->GetPeerLocalAddress()).second; + ASSERT_TRUE(result); + } +} + +TEST(NetworkEmulationManagerTest, Run) { + NetworkEmulationManagerImpl network_manager( + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault); + + EmulatedNetworkNode* alice_node = network_manager.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* bob_node = network_manager.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedEndpoint* alice_endpoint = + network_manager.CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + network_manager.CreateEndpoint(EmulatedEndpointConfig()); + network_manager.CreateRoute(alice_endpoint, {alice_node}, bob_endpoint); + network_manager.CreateRoute(bob_endpoint, {bob_node}, alice_endpoint); + + EmulatedNetworkManagerInterface* nt1 = + network_manager.CreateEmulatedNetworkManagerInterface({alice_endpoint}); + EmulatedNetworkManagerInterface* nt2 = + network_manager.CreateEmulatedNetworkManagerInterface({bob_endpoint}); + + rtc::Thread* t1 = nt1->network_thread(); + rtc::Thread* t2 = nt2->network_thread(); + + rtc::CopyOnWriteBuffer data("Hello"); + for (uint64_t j = 0; j < 2; j++) { + rtc::Socket* s1 = nullptr; + rtc::Socket* s2 = nullptr; + SendTask(t1, [&] { + s1 = t1->socketserver()->CreateSocket(AF_INET, SOCK_DGRAM); + }); + SendTask(t2, [&] { + s2 = t2->socketserver()->CreateSocket(AF_INET, SOCK_DGRAM); + }); + + SocketReader r1(s1, t1); + SocketReader r2(s2, t2); + + rtc::SocketAddress a1(alice_endpoint->GetPeerLocalAddress(), 0); + rtc::SocketAddress a2(bob_endpoint->GetPeerLocalAddress(), 0); + + SendTask(t1, [&] { + s1->Bind(a1); + a1 = s1->GetLocalAddress(); + }); + SendTask(t2, [&] { + s2->Bind(a2); + a2 = s2->GetLocalAddress(); + }); + + SendTask(t1, [&] { s1->Connect(a2); }); + SendTask(t2, [&] { s2->Connect(a1); }); + + for (uint64_t i = 0; i < 1000; i++) { + t1->PostTask([&]() { s1->Send(data.data(), data.size()); }); + t2->PostTask([&]() { s2->Send(data.data(), data.size()); }); + } + + network_manager.time_controller()->AdvanceTime(TimeDelta::Seconds(1)); + + EXPECT_EQ(r1.ReceivedCount(), 1000); + EXPECT_EQ(r2.ReceivedCount(), 1000); + + SendTask(t1, [&] { delete s1; }); + SendTask(t2, [&] { delete s2; }); + } + + const int64_t single_packet_size = data.size() + kOverheadIpv4Udp; + std::atomic<int> received_stats_count{0}; + nt1->GetStats([&](EmulatedNetworkStats st) { + EXPECT_EQ(st.PacketsSent(), 2000l); + EXPECT_EQ(st.BytesSent().bytes(), single_packet_size * 2000l); + EXPECT_THAT(st.local_addresses, + ElementsAreArray({alice_endpoint->GetPeerLocalAddress()})); + EXPECT_EQ(st.PacketsReceived(), 2000l); + EXPECT_EQ(st.BytesReceived().bytes(), single_packet_size * 2000l); + EXPECT_EQ(st.PacketsDiscardedNoReceiver(), 0l); + EXPECT_EQ(st.BytesDiscardedNoReceiver().bytes(), 0l); + + rtc::IPAddress bob_ip = bob_endpoint->GetPeerLocalAddress(); + std::map<rtc::IPAddress, EmulatedNetworkIncomingStats> source_st = + st.incoming_stats_per_source; + ASSERT_EQ(source_st.size(), 1lu); + EXPECT_EQ(source_st.at(bob_ip).packets_received, 2000l); + EXPECT_EQ(source_st.at(bob_ip).bytes_received.bytes(), + single_packet_size * 2000l); + EXPECT_EQ(source_st.at(bob_ip).packets_discarded_no_receiver, 0l); + EXPECT_EQ(source_st.at(bob_ip).bytes_discarded_no_receiver.bytes(), 0l); + + std::map<rtc::IPAddress, EmulatedNetworkOutgoingStats> dest_st = + st.outgoing_stats_per_destination; + ASSERT_EQ(dest_st.size(), 1lu); + EXPECT_EQ(dest_st.at(bob_ip).packets_sent, 2000l); + EXPECT_EQ(dest_st.at(bob_ip).bytes_sent.bytes(), + single_packet_size * 2000l); + + // No debug stats are collected by default. + EXPECT_TRUE(st.SentPacketsSizeCounter().IsEmpty()); + EXPECT_TRUE(st.sent_packets_queue_wait_time_us.IsEmpty()); + EXPECT_TRUE(st.ReceivedPacketsSizeCounter().IsEmpty()); + EXPECT_TRUE(st.PacketsDiscardedNoReceiverSizeCounter().IsEmpty()); + EXPECT_TRUE(dest_st.at(bob_ip).sent_packets_size.IsEmpty()); + EXPECT_TRUE(source_st.at(bob_ip).received_packets_size.IsEmpty()); + EXPECT_TRUE( + source_st.at(bob_ip).packets_discarded_no_receiver_size.IsEmpty()); + + received_stats_count++; + }); + nt2->GetStats([&](EmulatedNetworkStats st) { + EXPECT_EQ(st.PacketsSent(), 2000l); + EXPECT_EQ(st.BytesSent().bytes(), single_packet_size * 2000l); + EXPECT_THAT(st.local_addresses, + ElementsAreArray({bob_endpoint->GetPeerLocalAddress()})); + EXPECT_EQ(st.PacketsReceived(), 2000l); + EXPECT_EQ(st.BytesReceived().bytes(), single_packet_size * 2000l); + EXPECT_EQ(st.PacketsDiscardedNoReceiver(), 0l); + EXPECT_EQ(st.BytesDiscardedNoReceiver().bytes(), 0l); + EXPECT_GT(st.FirstReceivedPacketSize(), DataSize::Zero()); + EXPECT_TRUE(st.FirstPacketReceivedTime().IsFinite()); + EXPECT_TRUE(st.LastPacketReceivedTime().IsFinite()); + + rtc::IPAddress alice_ip = alice_endpoint->GetPeerLocalAddress(); + std::map<rtc::IPAddress, EmulatedNetworkIncomingStats> source_st = + st.incoming_stats_per_source; + ASSERT_EQ(source_st.size(), 1lu); + EXPECT_EQ(source_st.at(alice_ip).packets_received, 2000l); + EXPECT_EQ(source_st.at(alice_ip).bytes_received.bytes(), + single_packet_size * 2000l); + EXPECT_EQ(source_st.at(alice_ip).packets_discarded_no_receiver, 0l); + EXPECT_EQ(source_st.at(alice_ip).bytes_discarded_no_receiver.bytes(), 0l); + + std::map<rtc::IPAddress, EmulatedNetworkOutgoingStats> dest_st = + st.outgoing_stats_per_destination; + ASSERT_EQ(dest_st.size(), 1lu); + EXPECT_EQ(dest_st.at(alice_ip).packets_sent, 2000l); + EXPECT_EQ(dest_st.at(alice_ip).bytes_sent.bytes(), + single_packet_size * 2000l); + + // No debug stats are collected by default. + EXPECT_TRUE(st.SentPacketsSizeCounter().IsEmpty()); + EXPECT_TRUE(st.sent_packets_queue_wait_time_us.IsEmpty()); + EXPECT_TRUE(st.ReceivedPacketsSizeCounter().IsEmpty()); + EXPECT_TRUE(st.PacketsDiscardedNoReceiverSizeCounter().IsEmpty()); + EXPECT_TRUE(dest_st.at(alice_ip).sent_packets_size.IsEmpty()); + EXPECT_TRUE(source_st.at(alice_ip).received_packets_size.IsEmpty()); + EXPECT_TRUE( + source_st.at(alice_ip).packets_discarded_no_receiver_size.IsEmpty()); + + received_stats_count++; + }); + ASSERT_EQ_SIMULATED_WAIT(received_stats_count.load(), 2, + kStatsWaitTimeout.ms(), + *network_manager.time_controller()); +} + +TEST(NetworkEmulationManagerTest, DebugStatsCollectedInDebugMode) { + NetworkEmulationManagerImpl network_manager( + TimeMode::kSimulated, EmulatedNetworkStatsGatheringMode::kDebug); + + EmulatedNetworkNode* alice_node = network_manager.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* bob_node = network_manager.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedEndpoint* alice_endpoint = + network_manager.CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + network_manager.CreateEndpoint(EmulatedEndpointConfig()); + network_manager.CreateRoute(alice_endpoint, {alice_node}, bob_endpoint); + network_manager.CreateRoute(bob_endpoint, {bob_node}, alice_endpoint); + + EmulatedNetworkManagerInterface* nt1 = + network_manager.CreateEmulatedNetworkManagerInterface({alice_endpoint}); + EmulatedNetworkManagerInterface* nt2 = + network_manager.CreateEmulatedNetworkManagerInterface({bob_endpoint}); + + rtc::Thread* t1 = nt1->network_thread(); + rtc::Thread* t2 = nt2->network_thread(); + + rtc::CopyOnWriteBuffer data("Hello"); + for (uint64_t j = 0; j < 2; j++) { + rtc::Socket* s1 = nullptr; + rtc::Socket* s2 = nullptr; + SendTask(t1, [&] { + s1 = t1->socketserver()->CreateSocket(AF_INET, SOCK_DGRAM); + }); + SendTask(t2, [&] { + s2 = t2->socketserver()->CreateSocket(AF_INET, SOCK_DGRAM); + }); + + SocketReader r1(s1, t1); + SocketReader r2(s2, t2); + + rtc::SocketAddress a1(alice_endpoint->GetPeerLocalAddress(), 0); + rtc::SocketAddress a2(bob_endpoint->GetPeerLocalAddress(), 0); + + SendTask(t1, [&] { + s1->Bind(a1); + a1 = s1->GetLocalAddress(); + }); + SendTask(t2, [&] { + s2->Bind(a2); + a2 = s2->GetLocalAddress(); + }); + + SendTask(t1, [&] { s1->Connect(a2); }); + SendTask(t2, [&] { s2->Connect(a1); }); + + for (uint64_t i = 0; i < 1000; i++) { + t1->PostTask([&]() { s1->Send(data.data(), data.size()); }); + t2->PostTask([&]() { s2->Send(data.data(), data.size()); }); + } + + network_manager.time_controller()->AdvanceTime(TimeDelta::Seconds(1)); + + EXPECT_EQ(r1.ReceivedCount(), 1000); + EXPECT_EQ(r2.ReceivedCount(), 1000); + + SendTask(t1, [&] { delete s1; }); + SendTask(t2, [&] { delete s2; }); + } + + const int64_t single_packet_size = data.size() + kOverheadIpv4Udp; + std::atomic<int> received_stats_count{0}; + nt1->GetStats([&](EmulatedNetworkStats st) { + rtc::IPAddress bob_ip = bob_endpoint->GetPeerLocalAddress(); + std::map<rtc::IPAddress, EmulatedNetworkIncomingStats> source_st = + st.incoming_stats_per_source; + ASSERT_EQ(source_st.size(), 1lu); + + std::map<rtc::IPAddress, EmulatedNetworkOutgoingStats> dest_st = + st.outgoing_stats_per_destination; + ASSERT_EQ(dest_st.size(), 1lu); + + // No debug stats are collected by default. + EXPECT_EQ(st.SentPacketsSizeCounter().NumSamples(), 2000l); + EXPECT_EQ(st.ReceivedPacketsSizeCounter().GetAverage(), single_packet_size); + EXPECT_EQ(st.sent_packets_queue_wait_time_us.NumSamples(), 2000l); + EXPECT_LT(st.sent_packets_queue_wait_time_us.GetMax(), 1); + EXPECT_TRUE(st.PacketsDiscardedNoReceiverSizeCounter().IsEmpty()); + EXPECT_EQ(dest_st.at(bob_ip).sent_packets_size.NumSamples(), 2000l); + EXPECT_EQ(dest_st.at(bob_ip).sent_packets_size.GetAverage(), + single_packet_size); + EXPECT_EQ(source_st.at(bob_ip).received_packets_size.NumSamples(), 2000l); + EXPECT_EQ(source_st.at(bob_ip).received_packets_size.GetAverage(), + single_packet_size); + EXPECT_TRUE( + source_st.at(bob_ip).packets_discarded_no_receiver_size.IsEmpty()); + + received_stats_count++; + }); + ASSERT_EQ_SIMULATED_WAIT(received_stats_count.load(), 1, + kStatsWaitTimeout.ms(), + *network_manager.time_controller()); +} + +TEST(NetworkEmulationManagerTest, ThroughputStats) { + NetworkEmulationManagerImpl network_manager( + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault); + + EmulatedNetworkNode* alice_node = network_manager.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* bob_node = network_manager.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedEndpoint* alice_endpoint = + network_manager.CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + network_manager.CreateEndpoint(EmulatedEndpointConfig()); + network_manager.CreateRoute(alice_endpoint, {alice_node}, bob_endpoint); + network_manager.CreateRoute(bob_endpoint, {bob_node}, alice_endpoint); + + EmulatedNetworkManagerInterface* nt1 = + network_manager.CreateEmulatedNetworkManagerInterface({alice_endpoint}); + EmulatedNetworkManagerInterface* nt2 = + network_manager.CreateEmulatedNetworkManagerInterface({bob_endpoint}); + + rtc::Thread* t1 = nt1->network_thread(); + rtc::Thread* t2 = nt2->network_thread(); + + constexpr int64_t kUdpPayloadSize = 100; + constexpr int64_t kSinglePacketSize = kUdpPayloadSize + kOverheadIpv4Udp; + rtc::CopyOnWriteBuffer data(kUdpPayloadSize); + + rtc::Socket* s1 = nullptr; + rtc::Socket* s2 = nullptr; + SendTask(t1, + [&] { s1 = t1->socketserver()->CreateSocket(AF_INET, SOCK_DGRAM); }); + SendTask(t2, + [&] { s2 = t2->socketserver()->CreateSocket(AF_INET, SOCK_DGRAM); }); + + SocketReader r1(s1, t1); + SocketReader r2(s2, t2); + + rtc::SocketAddress a1(alice_endpoint->GetPeerLocalAddress(), 0); + rtc::SocketAddress a2(bob_endpoint->GetPeerLocalAddress(), 0); + + SendTask(t1, [&] { + s1->Bind(a1); + a1 = s1->GetLocalAddress(); + }); + SendTask(t2, [&] { + s2->Bind(a2); + a2 = s2->GetLocalAddress(); + }); + + SendTask(t1, [&] { s1->Connect(a2); }); + SendTask(t2, [&] { s2->Connect(a1); }); + + // Send 11 packets, totalizing 1 second between the first and the last-> + const int kNumPacketsSent = 11; + const TimeDelta kDelay = TimeDelta::Millis(100); + for (int i = 0; i < kNumPacketsSent; i++) { + t1->PostTask([&]() { s1->Send(data.data(), data.size()); }); + t2->PostTask([&]() { s2->Send(data.data(), data.size()); }); + network_manager.time_controller()->AdvanceTime(kDelay); + } + + std::atomic<int> received_stats_count{0}; + nt1->GetStats([&](EmulatedNetworkStats st) { + EXPECT_EQ(st.PacketsSent(), kNumPacketsSent); + EXPECT_EQ(st.BytesSent().bytes(), kSinglePacketSize * kNumPacketsSent); + + const double tolerance = 0.95; // Accept 5% tolerance for timing. + EXPECT_GE(st.LastPacketSentTime() - st.FirstPacketSentTime(), + (kNumPacketsSent - 1) * kDelay * tolerance); + EXPECT_GT(st.AverageSendRate().bps(), 0); + received_stats_count++; + }); + + ASSERT_EQ_SIMULATED_WAIT(received_stats_count.load(), 1, + kStatsWaitTimeout.ms(), + *network_manager.time_controller()); + + EXPECT_EQ(r1.ReceivedCount(), 11); + EXPECT_EQ(r2.ReceivedCount(), 11); + + SendTask(t1, [&] { delete s1; }); + SendTask(t2, [&] { delete s2; }); +} + +// 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(); +} + +TEST(NetworkEmulationManagerTest, EndpointLoopback) { + NetworkEmulationManagerImpl network_manager( + TimeMode::kSimulated, EmulatedNetworkStatsGatheringMode::kDefault); + auto endpoint = network_manager.CreateEndpoint(EmulatedEndpointConfig()); + + MockReceiver receiver; + EXPECT_CALL(receiver, OnPacketReceived(::testing::_)).Times(1); + ASSERT_EQ(endpoint->BindReceiver(80, &receiver), 80); + + endpoint->SendPacket(rtc::SocketAddress(endpoint->GetPeerLocalAddress(), 80), + rtc::SocketAddress(endpoint->GetPeerLocalAddress(), 80), + "Hello"); + network_manager.time_controller()->AdvanceTime(TimeDelta::Seconds(1)); +} + +TEST(NetworkEmulationManagerTest, EndpointCanSendWithDifferentSourceIp) { + constexpr uint32_t kEndpointIp = 0xC0A80011; // 192.168.0.17 + constexpr uint32_t kSourceIp = 0xC0A80012; // 192.168.0.18 + NetworkEmulationManagerImpl network_manager( + TimeMode::kSimulated, EmulatedNetworkStatsGatheringMode::kDefault); + EmulatedEndpointConfig endpoint_config; + endpoint_config.ip = rtc::IPAddress(kEndpointIp); + endpoint_config.allow_send_packet_with_different_source_ip = true; + auto endpoint = network_manager.CreateEndpoint(endpoint_config); + + MockReceiver receiver; + EXPECT_CALL(receiver, OnPacketReceived(::testing::_)).Times(1); + ASSERT_EQ(endpoint->BindReceiver(80, &receiver), 80); + + endpoint->SendPacket(rtc::SocketAddress(kSourceIp, 80), + rtc::SocketAddress(endpoint->GetPeerLocalAddress(), 80), + "Hello"); + network_manager.time_controller()->AdvanceTime(TimeDelta::Seconds(1)); +} + +TEST(NetworkEmulationManagerTest, + EndpointCanReceiveWithDifferentDestIpThroughDefaultRoute) { + constexpr uint32_t kDestEndpointIp = 0xC0A80011; // 192.168.0.17 + constexpr uint32_t kDestIp = 0xC0A80012; // 192.168.0.18 + NetworkEmulationManagerImpl network_manager( + TimeMode::kSimulated, EmulatedNetworkStatsGatheringMode::kDefault); + auto sender_endpoint = + network_manager.CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpointConfig endpoint_config; + endpoint_config.ip = rtc::IPAddress(kDestEndpointIp); + endpoint_config.allow_receive_packets_with_different_dest_ip = true; + auto receiver_endpoint = network_manager.CreateEndpoint(endpoint_config); + + MockReceiver receiver; + EXPECT_CALL(receiver, OnPacketReceived(::testing::_)).Times(1); + ASSERT_EQ(receiver_endpoint->BindReceiver(80, &receiver), 80); + + network_manager.CreateDefaultRoute( + sender_endpoint, {network_manager.NodeBuilder().Build().node}, + receiver_endpoint); + + sender_endpoint->SendPacket( + rtc::SocketAddress(sender_endpoint->GetPeerLocalAddress(), 80), + rtc::SocketAddress(kDestIp, 80), "Hello"); + network_manager.time_controller()->AdvanceTime(TimeDelta::Seconds(1)); +} + +TEST(NetworkEmulationManagerTURNTest, GetIceServerConfig) { + NetworkEmulationManagerImpl network_manager( + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault); + 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, EmulatedNetworkStatsGatheringMode::kDefault); + 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(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 diff --git a/third_party/libwebrtc/test/network/traffic_route.cc b/third_party/libwebrtc/test/network/traffic_route.cc new file mode 100644 index 0000000000..94db5aede1 --- /dev/null +++ b/third_party/libwebrtc/test/network/traffic_route.cc @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019 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/traffic_route.h" + +#include <algorithm> +#include <memory> +#include <utility> + +#include "absl/types/optional.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_minmax.h" + +namespace webrtc { +namespace test { +namespace { + +class NullReceiver : public EmulatedNetworkReceiverInterface { + public: + void OnPacketReceived(EmulatedIpPacket packet) override {} +}; + +class ActionReceiver : public EmulatedNetworkReceiverInterface { + public: + explicit ActionReceiver(std::function<void()> action) : action_(action) {} + ~ActionReceiver() override = default; + + void OnPacketReceived(EmulatedIpPacket packet) override { action_(); } + + private: + std::function<void()> action_; +}; + +} // namespace + +CrossTrafficRouteImpl::CrossTrafficRouteImpl( + Clock* clock, + EmulatedNetworkReceiverInterface* receiver, + EmulatedEndpointImpl* endpoint) + : clock_(clock), receiver_(receiver), endpoint_(endpoint) { + null_receiver_ = std::make_unique<NullReceiver>(); + absl::optional<uint16_t> port = + endpoint_->BindReceiver(0, null_receiver_.get()); + RTC_DCHECK(port); + null_receiver_port_ = port.value(); +} +CrossTrafficRouteImpl::~CrossTrafficRouteImpl() = default; + +void CrossTrafficRouteImpl::TriggerPacketBurst(size_t num_packets, + size_t packet_size) { + for (size_t i = 0; i < num_packets; ++i) { + SendPacket(packet_size); + } +} + +void CrossTrafficRouteImpl::NetworkDelayedAction(size_t packet_size, + std::function<void()> action) { + auto action_receiver = std::make_unique<ActionReceiver>(action); + // BindOneShotReceiver arranges to free the port in the endpoint after the + // action is done. + absl::optional<uint16_t> port = + endpoint_->BindOneShotReceiver(0, action_receiver.get()); + RTC_DCHECK(port); + actions_.push_back(std::move(action_receiver)); + SendPacket(packet_size, port.value()); +} + +void CrossTrafficRouteImpl::SendPacket(size_t packet_size) { + SendPacket(packet_size, null_receiver_port_); +} + +void CrossTrafficRouteImpl::SendPacket(size_t packet_size, uint16_t dest_port) { + rtc::CopyOnWriteBuffer data(packet_size); + std::fill_n(data.MutableData(), data.size(), 0); + receiver_->OnPacketReceived(EmulatedIpPacket( + /*from=*/rtc::SocketAddress(), + rtc::SocketAddress(endpoint_->GetPeerLocalAddress(), dest_port), data, + clock_->CurrentTime())); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/traffic_route.h b/third_party/libwebrtc/test/network/traffic_route.h new file mode 100644 index 0000000000..dbc41a694f --- /dev/null +++ b/third_party/libwebrtc/test/network/traffic_route.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019 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_TRAFFIC_ROUTE_H_ +#define TEST_NETWORK_TRAFFIC_ROUTE_H_ + +#include <memory> +#include <vector> + +#include "api/test/network_emulation_manager.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "system_wrappers/include/clock.h" +#include "test/network/network_emulation.h" + +namespace webrtc { +namespace test { + +// Represents the endpoint for cross traffic that is going through the network. +// It can be used to emulate unexpected network load. +class CrossTrafficRouteImpl final : public CrossTrafficRoute { + public: + CrossTrafficRouteImpl(Clock* clock, + EmulatedNetworkReceiverInterface* receiver, + EmulatedEndpointImpl* endpoint); + ~CrossTrafficRouteImpl(); + + // Triggers sending of dummy packets with size `packet_size` bytes. + void TriggerPacketBurst(size_t num_packets, size_t packet_size) override; + // Sends a packet over the nodes and runs `action` when it has been delivered. + void NetworkDelayedAction(size_t packet_size, + std::function<void()> action) override; + + void SendPacket(size_t packet_size) override; + + private: + void SendPacket(size_t packet_size, uint16_t dest_port); + + Clock* const clock_; + EmulatedNetworkReceiverInterface* const receiver_; + EmulatedEndpointImpl* const endpoint_; + + uint16_t null_receiver_port_; + std::unique_ptr<EmulatedNetworkReceiverInterface> null_receiver_; + std::vector<std::unique_ptr<EmulatedNetworkReceiverInterface>> actions_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_NETWORK_TRAFFIC_ROUTE_H_ |