Introduce cross traffic for emulated network layer.

This CL contains cross traffic and is a second part of landing
CL https://webrtc-review.googlesource.com/c/src/+/116663

Bug: webrtc:10138
Change-Id: Ibe0614f80127e93ee8a92b85685cacbf079dee21
Reviewed-on: https://webrtc-review.googlesource.com/c/120925
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Reviewed-by: Sebastian Jansson <srte@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26649}
diff --git a/test/scenario/BUILD.gn b/test/scenario/BUILD.gn
index ff7ada6..533e43a 100644
--- a/test/scenario/BUILD.gn
+++ b/test/scenario/BUILD.gn
@@ -8,6 +8,19 @@
 
 import("../../webrtc.gni")
 
+rtc_source_set("column_printer") {
+  testonly = true
+  sources = [
+    "column_printer.cc",
+    "column_printer.h",
+  ]
+  deps = [
+    "../../rtc_base:macromagic",
+    "../../rtc_base:stringutils",
+    "../logging:log_writer",
+  ]
+}
+
 if (rtc_include_tests) {
   rtc_source_set("scenario") {
     testonly = true
@@ -17,8 +30,6 @@
       "call_client.cc",
       "call_client.h",
       "call_client.h",
-      "column_printer.cc",
-      "column_printer.h",
       "hardware_codecs.cc",
       "hardware_codecs.h",
       "network_node.cc",
@@ -36,6 +47,7 @@
       "video_stream.h",
     ]
     deps = [
+      ":column_printer",
       "../:fake_video_codecs",
       "../:fileutils",
       "../:test_common",
diff --git a/test/scenario/network/BUILD.gn b/test/scenario/network/BUILD.gn
index c73a889..1e33a33 100644
--- a/test/scenario/network/BUILD.gn
+++ b/test/scenario/network/BUILD.gn
@@ -11,6 +11,8 @@
 rtc_source_set("emulated_network") {
   testonly = true
   sources = [
+    "cross_traffic.cc",
+    "cross_traffic.h",
     "fake_network_socket.cc",
     "fake_network_socket.h",
     "fake_network_socket_server.cc",
@@ -19,8 +21,11 @@
     "network_emulation.h",
     "network_emulation_manager.cc",
     "network_emulation_manager.h",
+    "traffic_route.cc",
+    "traffic_route.h",
   ]
   deps = [
+    "../:column_printer",
     "../../../api:simulated_network_api",
     "../../../api/units:data_rate",
     "../../../api/units:data_size",
@@ -90,9 +95,26 @@
   }
 }
 
+rtc_source_set("cross_traffic_unittest") {
+  testonly = true
+  sources = [
+    "cross_traffic_unittest.cc",
+  ]
+  deps = [
+    ":emulated_network",
+    "../../../api:simulated_network_api",
+    "../../../call:simulated_network",
+    "../../../rtc_base:logging",
+    "../../../rtc_base:rtc_event",
+    "../../../test:test_support",
+    "//third_party/abseil-cpp/absl/memory:memory",
+  ]
+}
+
 rtc_source_set("network_emulation_unittests") {
   testonly = true
   deps = [
+    ":cross_traffic_unittest",
     ":network_emulation_pc_unittest",
     ":network_emulation_unittest",
   ]
diff --git a/test/scenario/network/cross_traffic.cc b/test/scenario/network/cross_traffic.cc
new file mode 100644
index 0000000..bd63a08
--- /dev/null
+++ b/test/scenario/network/cross_traffic.cc
@@ -0,0 +1,110 @@
+/*
+ *  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/scenario/network/cross_traffic.h"
+
+#include <math.h>
+#include <utility>
+
+#include "absl/memory/memory.h"
+#include "absl/types/optional.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/numerics/safe_minmax.h"
+
+namespace webrtc {
+namespace test {
+
+RandomWalkCrossTraffic::RandomWalkCrossTraffic(RandomWalkConfig config,
+                                               TrafficRoute* traffic_route)
+    : config_(config),
+      traffic_route_(traffic_route),
+      random_(config_.random_seed) {}
+RandomWalkCrossTraffic::~RandomWalkCrossTraffic() = default;
+
+void RandomWalkCrossTraffic::Process(Timestamp at_time) {
+  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;
+  }
+}
+
+DataRate RandomWalkCrossTraffic::TrafficRate() const {
+  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,
+                                                 TrafficRoute* traffic_route)
+    : config_(config), traffic_route_(traffic_route) {}
+PulsedPeaksCrossTraffic::~PulsedPeaksCrossTraffic() = default;
+
+void PulsedPeaksCrossTraffic::Process(Timestamp at_time) {
+  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;
+    }
+  }
+}
+
+DataRate PulsedPeaksCrossTraffic::TrafficRate() const {
+  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);
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/test/scenario/network/cross_traffic.h b/test/scenario/network/cross_traffic.h
new file mode 100644
index 0000000..b5a65fb
--- /dev/null
+++ b/test/scenario/network/cross_traffic.h
@@ -0,0 +1,88 @@
+/*
+ *  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_SCENARIO_NETWORK_CROSS_TRAFFIC_H_
+#define TEST_SCENARIO_NETWORK_CROSS_TRAFFIC_H_
+
+#include <memory>
+
+#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/scenario/column_printer.h"
+#include "test/scenario/network/traffic_route.h"
+
+namespace webrtc {
+namespace test {
+
+struct RandomWalkConfig {
+  int random_seed = 1;
+  DataRate peak_rate = DataRate::kbps(100);
+  DataSize min_packet_size = DataSize::bytes(200);
+  TimeDelta min_packet_interval = TimeDelta::ms(1);
+  TimeDelta update_interval = TimeDelta::ms(200);
+  double variance = 0.6;
+  double bias = -0.1;
+};
+
+class RandomWalkCrossTraffic {
+ public:
+  RandomWalkCrossTraffic(RandomWalkConfig config, TrafficRoute* traffic_route);
+  ~RandomWalkCrossTraffic();
+
+  void Process(Timestamp at_time);
+  DataRate TrafficRate() const;
+  ColumnPrinter StatsPrinter();
+
+ private:
+  RandomWalkConfig config_;
+  TrafficRoute* const traffic_route_;
+  webrtc::Random random_;
+
+  Timestamp last_process_time_ = Timestamp::MinusInfinity();
+  Timestamp last_update_time_ = Timestamp::MinusInfinity();
+  Timestamp last_send_time_ = Timestamp::MinusInfinity();
+  double intensity_ = 0;
+  DataSize pending_size_ = DataSize::Zero();
+};
+
+struct PulsedPeaksConfig {
+  DataRate peak_rate = DataRate::kbps(100);
+  DataSize min_packet_size = DataSize::bytes(200);
+  TimeDelta min_packet_interval = TimeDelta::ms(1);
+  TimeDelta send_duration = TimeDelta::ms(100);
+  TimeDelta hold_duration = TimeDelta::ms(2000);
+};
+
+class PulsedPeaksCrossTraffic {
+ public:
+  PulsedPeaksCrossTraffic(PulsedPeaksConfig config,
+                          TrafficRoute* traffic_route);
+  ~PulsedPeaksCrossTraffic();
+
+  void Process(Timestamp at_time);
+  DataRate TrafficRate() const;
+  ColumnPrinter StatsPrinter();
+
+ private:
+  PulsedPeaksConfig config_;
+  TrafficRoute* const traffic_route_;
+
+  Timestamp last_update_time_ = Timestamp::MinusInfinity();
+  Timestamp last_send_time_ = Timestamp::MinusInfinity();
+  bool sending_ = false;
+};
+
+}  // namespace test
+}  // namespace webrtc
+
+#endif  // TEST_SCENARIO_NETWORK_CROSS_TRAFFIC_H_
diff --git a/test/scenario/network/cross_traffic_unittest.cc b/test/scenario/network/cross_traffic_unittest.cc
new file mode 100644
index 0000000..98f2d3c
--- /dev/null
+++ b/test/scenario/network/cross_traffic_unittest.cc
@@ -0,0 +1,156 @@
+/*
+ *  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 <atomic>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "absl/memory/memory.h"
+#include "api/test/simulated_network.h"
+#include "call/simulated_network.h"
+#include "rtc_base/event.h"
+#include "rtc_base/logging.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/scenario/network/cross_traffic.h"
+#include "test/scenario/network/network_emulation.h"
+#include "test/scenario/network/network_emulation_manager.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+
+class CountingBehavior : public NetworkBehaviorInterface {
+ public:
+  bool EnqueuePacket(PacketInFlightInfo packet_info) override {
+    packets_to_send_.push_back(packet_info);
+    return true;
+  }
+
+  std::vector<PacketDeliveryInfo> DequeueDeliverablePackets(
+      int64_t receive_time_us) override {
+    std::vector<PacketDeliveryInfo> out;
+    for (auto packet : packets_to_send_) {
+      // we want to count packets, that went through this behavior.
+      packets_count_++;
+      total_packets_size_ += packet.size;
+      out.push_back(PacketDeliveryInfo(packet, receive_time_us));
+    }
+    packets_to_send_.clear();
+    return out;
+  }
+
+  absl::optional<int64_t> NextDeliveryTimeUs() const override { return 1000; }
+
+  int packets_count() const { return packets_count_; }
+  uint64_t total_packets_size() const { return total_packets_size_; }
+
+ private:
+  std::vector<PacketInFlightInfo> packets_to_send_;
+
+  std::atomic<int> packets_count_{0};
+  std::atomic<uint64_t> total_packets_size_{0};
+};
+
+}  // namespace
+
+TEST(CrossTrafficTest, TriggerPacketBurst) {
+  NetworkEmulationManager network_manager;
+
+  std::unique_ptr<CountingBehavior> behavior =
+      absl::make_unique<CountingBehavior>();
+  CountingBehavior* counter = behavior.get();
+
+  EmulatedNetworkNode* node_a = network_manager.CreateEmulatedNode(
+      absl::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig()));
+  EmulatedNetworkNode* node_b =
+      network_manager.CreateEmulatedNode(std::move(behavior));
+
+  TrafficRoute* traffic = network_manager.CreateTrafficRoute({node_a, node_b});
+
+  traffic->TriggerPacketBurst(100, 1000);
+
+  rtc::Event event;
+  event.Wait(1000);
+
+  EXPECT_EQ(counter->packets_count(), 100);
+  EXPECT_EQ(counter->total_packets_size(), 100 * 1000ul);
+}
+
+TEST(CrossTrafficTest, PulsedPeaksCrossTraffic) {
+  NetworkEmulationManager network_manager;
+
+  std::unique_ptr<CountingBehavior> behavior =
+      absl::make_unique<CountingBehavior>();
+  CountingBehavior* counter = behavior.get();
+
+  EmulatedNetworkNode* node_a = network_manager.CreateEmulatedNode(
+      absl::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig()));
+  EmulatedNetworkNode* node_b =
+      network_manager.CreateEmulatedNode(std::move(behavior));
+
+  PulsedPeaksConfig config;
+  config.peak_rate = DataRate::kbps(1000);
+  config.min_packet_size = DataSize::bytes(1);
+  config.min_packet_interval = TimeDelta::ms(25);
+  config.send_duration = TimeDelta::ms(500);
+  config.hold_duration = TimeDelta::ms(250);
+  TrafficRoute* traffic = network_manager.CreateTrafficRoute({node_a, node_b});
+  network_manager.CreatePulsedPeaksCrossTraffic(traffic, config);
+  const auto kRunTime = TimeDelta::seconds(1);
+
+  rtc::Event event;
+  event.Wait(kRunTime.ms());
+
+  RTC_LOG(INFO) << counter->packets_count() << " packets; "
+                << counter->total_packets_size() << " bytes";
+  // Using 50% duty cycle.
+  const auto kExpectedDataSent = kRunTime * config.peak_rate * 0.5;
+  EXPECT_NEAR(counter->total_packets_size(), kExpectedDataSent.bytes(),
+              kExpectedDataSent.bytes() * 0.1);
+}
+
+TEST(CrossTrafficTest, RandomWalkCrossTraffic) {
+  NetworkEmulationManager network_manager;
+
+  std::unique_ptr<CountingBehavior> behavior =
+      absl::make_unique<CountingBehavior>();
+  CountingBehavior* counter = behavior.get();
+
+  EmulatedNetworkNode* node_a = network_manager.CreateEmulatedNode(
+      absl::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig()));
+  EmulatedNetworkNode* node_b =
+      network_manager.CreateEmulatedNode(std::move(behavior));
+
+  RandomWalkConfig config;
+  config.peak_rate = DataRate::kbps(1000);
+  config.min_packet_size = DataSize::bytes(1);
+  config.min_packet_interval = TimeDelta::ms(25);
+  config.update_interval = TimeDelta::ms(500);
+  config.variance = 0.0;
+  config.bias = 1.0;
+  TrafficRoute* traffic = network_manager.CreateTrafficRoute({node_a, node_b});
+  network_manager.CreateRandomWalkCrossTraffic(traffic, config);
+  const auto kRunTime = TimeDelta::seconds(1);
+
+  rtc::Event event;
+  event.Wait(kRunTime.ms());
+
+  RTC_LOG(INFO) << counter->packets_count() << " packets; "
+                << counter->total_packets_size() << " bytes";
+  // Sending at peak rate since bias = 1.
+  const auto kExpectedDataSent = kRunTime * config.peak_rate;
+  EXPECT_NEAR(counter->total_packets_size(), kExpectedDataSent.bytes(),
+              kExpectedDataSent.bytes() * 0.1);
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/test/scenario/network/network_emulation_manager.cc b/test/scenario/network/network_emulation_manager.cc
index 489529b..2d83936 100644
--- a/test/scenario/network/network_emulation_manager.cc
+++ b/test/scenario/network/network_emulation_manager.cc
@@ -95,6 +95,60 @@
   }
 }
 
+TrafficRoute* NetworkEmulationManager::CreateTrafficRoute(
+    std::vector<EmulatedNetworkNode*> via_nodes) {
+  RTC_CHECK(!via_nodes.empty());
+  EndpointNode* endpoint = CreateEndpoint(rtc::IPAddress(next_node_id_++));
+
+  // Setup a route via specified nodes.
+  EmulatedNetworkNode* cur_node = via_nodes[0];
+  for (size_t i = 1; i < via_nodes.size(); ++i) {
+    cur_node->SetReceiver(endpoint->GetId(), via_nodes[i]);
+    cur_node = via_nodes[i];
+  }
+  cur_node->SetReceiver(endpoint->GetId(), endpoint);
+
+  std::unique_ptr<TrafficRoute> traffic_route =
+      absl::make_unique<TrafficRoute>(clock_, via_nodes[0], endpoint);
+  TrafficRoute* out = traffic_route.get();
+  traffic_routes_.push_back(std::move(traffic_route));
+  return out;
+}
+
+RandomWalkCrossTraffic* NetworkEmulationManager::CreateRandomWalkCrossTraffic(
+    TrafficRoute* traffic_route,
+    RandomWalkConfig config) {
+  auto traffic = absl::make_unique<RandomWalkCrossTraffic>(std::move(config),
+                                                           traffic_route);
+  RandomWalkCrossTraffic* out = traffic.get();
+  struct Closure {
+    void operator()() {
+      manager->random_cross_traffics_.push_back(std::move(traffic));
+    }
+    NetworkEmulationManager* manager;
+    std::unique_ptr<RandomWalkCrossTraffic> traffic;
+  };
+  task_queue_.PostTask(Closure{this, std::move(traffic)});
+  return out;
+}
+
+PulsedPeaksCrossTraffic* NetworkEmulationManager::CreatePulsedPeaksCrossTraffic(
+    TrafficRoute* traffic_route,
+    PulsedPeaksConfig config) {
+  auto traffic = absl::make_unique<PulsedPeaksCrossTraffic>(std::move(config),
+                                                            traffic_route);
+  PulsedPeaksCrossTraffic* out = traffic.get();
+  struct Closure {
+    void operator()() {
+      manager->pulsed_cross_traffics_.push_back(std::move(traffic));
+    }
+    NetworkEmulationManager* manager;
+    std::unique_ptr<PulsedPeaksCrossTraffic> traffic;
+  };
+  task_queue_.PostTask(Closure{this, std::move(traffic)});
+  return out;
+}
+
 rtc::Thread* NetworkEmulationManager::CreateNetworkThread(
     std::vector<EndpointNode*> endpoints) {
   FakeNetworkSocketServer* socket_server = CreateSocketServer(endpoints);
@@ -119,6 +173,12 @@
 
 void NetworkEmulationManager::ProcessNetworkPackets() {
   Timestamp current_time = Now();
+  for (auto& traffic : random_cross_traffics_) {
+    traffic->Process(current_time);
+  }
+  for (auto& traffic : pulsed_cross_traffics_) {
+    traffic->Process(current_time);
+  }
   for (auto& node : network_nodes_) {
     node->Process(current_time);
   }
diff --git a/test/scenario/network/network_emulation_manager.h b/test/scenario/network/network_emulation_manager.h
index 6d42ed7..4718967 100644
--- a/test/scenario/network/network_emulation_manager.h
+++ b/test/scenario/network/network_emulation_manager.h
@@ -23,8 +23,10 @@
 #include "rtc_base/task_utils/repeating_task.h"
 #include "rtc_base/thread.h"
 #include "system_wrappers/include/clock.h"
+#include "test/scenario/network/cross_traffic.h"
 #include "test/scenario/network/fake_network_socket_server.h"
 #include "test/scenario/network/network_emulation.h"
+#include "test/scenario/network/traffic_route.h"
 
 namespace webrtc {
 namespace test {
@@ -48,6 +50,14 @@
                   std::vector<EmulatedNetworkNode*> via_nodes,
                   EndpointNode* to);
 
+  TrafficRoute* CreateTrafficRoute(std::vector<EmulatedNetworkNode*> via_nodes);
+  RandomWalkCrossTraffic* CreateRandomWalkCrossTraffic(
+      TrafficRoute* traffic_route,
+      RandomWalkConfig config);
+  PulsedPeaksCrossTraffic* CreatePulsedPeaksCrossTraffic(
+      TrafficRoute* traffic_route,
+      PulsedPeaksConfig config);
+
   rtc::Thread* CreateNetworkThread(std::vector<EndpointNode*> endpoints);
 
  private:
@@ -64,6 +74,9 @@
   // All objects can be added to the manager only when it is idle.
   std::vector<std::unique_ptr<EndpointNode>> endpoints_;
   std::vector<std::unique_ptr<EmulatedNetworkNode>> network_nodes_;
+  std::vector<std::unique_ptr<TrafficRoute>> traffic_routes_;
+  std::vector<std::unique_ptr<RandomWalkCrossTraffic>> random_cross_traffics_;
+  std::vector<std::unique_ptr<PulsedPeaksCrossTraffic>> pulsed_cross_traffics_;
   std::vector<std::unique_ptr<FakeNetworkSocketServer>> socket_servers_;
   std::vector<std::unique_ptr<rtc::Thread>> threads_;
 
diff --git a/test/scenario/network/traffic_route.cc b/test/scenario/network/traffic_route.cc
new file mode 100644
index 0000000..67a2cb3
--- /dev/null
+++ b/test/scenario/network/traffic_route.cc
@@ -0,0 +1,97 @@
+/*
+ *  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/scenario/network/traffic_route.h"
+
+#include <utility>
+
+#include "absl/memory/memory.h"
+#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:
+  ActionReceiver(std::function<void()> action, EndpointNode* endpoint)
+      : action_(action), endpoint_(endpoint) {}
+  ~ActionReceiver() override = default;
+
+  void OnPacketReceived(EmulatedIpPacket packet) override {
+    RTC_DCHECK(port_);
+    action_();
+    endpoint_->UnbindReceiver(port_.value());
+  };
+
+  // We can't set port in constructor, because port will be provided by
+  // endpoint, when this receiver will be binded to that endpoint.
+  void SetPort(uint16_t port) { port_ = port; }
+
+ private:
+  std::function<void()> action_;
+  // Endpoint and port will be used to free port in the endpoint after action
+  // will be done.
+  EndpointNode* endpoint_;
+  absl::optional<uint16_t> port_ = absl::nullopt;
+};
+
+}  // namespace
+
+TrafficRoute::TrafficRoute(Clock* clock,
+                           EmulatedNetworkReceiverInterface* receiver,
+                           EndpointNode* endpoint)
+    : clock_(clock), receiver_(receiver), endpoint_(endpoint) {
+  null_receiver_ = absl::make_unique<NullReceiver>();
+  absl::optional<uint16_t> port =
+      endpoint_->BindReceiver(0, null_receiver_.get());
+  RTC_DCHECK(port);
+  null_receiver_port_ = port.value();
+}
+TrafficRoute::~TrafficRoute() = default;
+
+void TrafficRoute::TriggerPacketBurst(size_t num_packets, size_t packet_size) {
+  for (size_t i = 0; i < num_packets; ++i) {
+    SendPacket(packet_size);
+  }
+}
+
+void TrafficRoute::NetworkDelayedAction(size_t packet_size,
+                                        std::function<void()> action) {
+  auto action_receiver = absl::make_unique<ActionReceiver>(action, endpoint_);
+  absl::optional<uint16_t> port =
+      endpoint_->BindReceiver(0, action_receiver.get());
+  RTC_DCHECK(port);
+  action_receiver->SetPort(port.value());
+  actions_.push_back(std::move(action_receiver));
+  SendPacket(packet_size, port.value());
+}
+
+void TrafficRoute::SendPacket(size_t packet_size) {
+  SendPacket(packet_size, null_receiver_port_);
+}
+
+void TrafficRoute::SendPacket(size_t packet_size, uint16_t dest_port) {
+  receiver_->OnPacketReceived(EmulatedIpPacket(
+      /*from=*/rtc::SocketAddress(),
+      rtc::SocketAddress(endpoint_->GetPeerLocalAddress(), dest_port),
+      endpoint_->GetId(), rtc::CopyOnWriteBuffer(packet_size),
+      Timestamp::us(clock_->TimeInMicroseconds())));
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/test/scenario/network/traffic_route.h b/test/scenario/network/traffic_route.h
new file mode 100644
index 0000000..42210b9
--- /dev/null
+++ b/test/scenario/network/traffic_route.h
@@ -0,0 +1,55 @@
+/*
+ *  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_SCENARIO_NETWORK_TRAFFIC_ROUTE_H_
+#define TEST_SCENARIO_NETWORK_TRAFFIC_ROUTE_H_
+
+#include <memory>
+#include <vector>
+
+#include "rtc_base/copy_on_write_buffer.h"
+#include "system_wrappers/include/clock.h"
+#include "test/scenario/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 TrafficRoute {
+ public:
+  TrafficRoute(Clock* clock,
+               EmulatedNetworkReceiverInterface* receiver,
+               EndpointNode* endpoint);
+  ~TrafficRoute();
+
+  // Triggers sending of dummy packets with size |packet_size| bytes.
+  void TriggerPacketBurst(size_t num_packets, size_t packet_size);
+  // Sends a packet over the nodes and runs |action| when it has been delivered.
+  void NetworkDelayedAction(size_t packet_size, std::function<void()> action);
+
+  void SendPacket(size_t packet_size);
+
+ private:
+  void SendPacket(size_t packet_size, uint16_t dest_port);
+
+  Clock* const clock_;
+  EmulatedNetworkReceiverInterface* const receiver_;
+  EndpointNode* 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_SCENARIO_NETWORK_TRAFFIC_ROUTE_H_