network: Add new network emulation options for WebRTC

Three new options are added to the protocol and NetworkConditions to
control the packet loss (expressed as a percent from 0 to 100),
the packet queue length and if packet reordering is allowed.

Those are optional and should allow to keep compatibility with older
versions of the protocol.

Bug: 490143
Change-Id: Ibdbde23db1a7f73e5aae9ad8864cb3cceac0bfba
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5349261
Commit-Queue: Florent Castelli <orphis@chromium.org>
Reviewed-by: Alex Rudenko <alexrudenko@chromium.org>
Reviewed-by: Arthur Sonzogni <arthursonzogni@chromium.org>
Reviewed-by: Adam Rice <ricea@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1270884}
diff --git a/content/browser/devtools/protocol/network_handler.cc b/content/browser/devtools/protocol/network_handler.cc
index 3047b26..b6266b27 100644
--- a/content/browser/devtools/protocol/network_handler.cc
+++ b/content/browser/devtools/protocol/network_handler.cc
@@ -1802,7 +1802,10 @@
     double latency,
     double download_throughput,
     double upload_throughput,
-    Maybe<protocol::Network::ConnectionType>) {
+    Maybe<protocol::Network::ConnectionType>,
+    Maybe<double> packet_loss,
+    Maybe<int> packet_queue_length,
+    Maybe<bool> packet_reordering) {
   network::mojom::NetworkConditionsPtr network_conditions;
   bool throttling_enabled = offline || latency > 0 || download_throughput > 0 ||
                             upload_throughput > 0;
@@ -1812,6 +1815,9 @@
     network_conditions->latency = base::Milliseconds(latency);
     network_conditions->download_throughput = download_throughput;
     network_conditions->upload_throughput = upload_throughput;
+    network_conditions->packet_loss = packet_loss.value_or(0.);
+    network_conditions->packet_queue_length = packet_queue_length.value_or(0);
+    network_conditions->packet_reordering = packet_reordering.value_or(false);
   }
   SetNetworkConditions(std::move(network_conditions));
   return Response::FallThrough();
diff --git a/content/browser/devtools/protocol/network_handler.h b/content/browser/devtools/protocol/network_handler.h
index 4480e436..418b91d 100644
--- a/content/browser/devtools/protocol/network_handler.h
+++ b/content/browser/devtools/protocol/network_handler.h
@@ -170,7 +170,10 @@
       double latency,
       double download_throughput,
       double upload_throughput,
-      Maybe<protocol::Network::ConnectionType> connection_type) override;
+      Maybe<protocol::Network::ConnectionType> connection_type,
+      Maybe<double> packet_loss,
+      Maybe<int> packet_queue_length,
+      Maybe<bool> packet_reordering) override;
   Response SetBypassServiceWorker(bool bypass) override;
 
   DispatchResponse SetRequestInterception(
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index ced5854..32dce17 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -1503,7 +1503,9 @@
   if (conditions) {
     network_conditions = std::make_unique<NetworkConditions>(
         conditions->offline, conditions->latency.InMillisecondsF(),
-        conditions->download_throughput, conditions->upload_throughput);
+        conditions->download_throughput, conditions->upload_throughput,
+        conditions->packet_loss, conditions->packet_queue_length,
+        conditions->packet_reordering);
   }
   ThrottlingController::SetConditions(throttling_profile_id,
                                       std::move(network_conditions));
diff --git a/services/network/p2p/socket_udp_unittest.cc b/services/network/p2p/socket_udp_unittest.cc
index af0030af..c36a8fb 100644
--- a/services/network/p2p/socket_udp_unittest.cc
+++ b/services/network/p2p/socket_udp_unittest.cc
@@ -959,7 +959,7 @@
                                                devtools_token_)) {}
 
   void SetUp() override {
-    SetNetworkState(false, 1000000, 1000000, base::Milliseconds(0));
+    SetNetworkState({});
     P2PSocketUdpTest::SetUp();
   }
 
@@ -968,12 +968,17 @@
     P2PSocketUdpTest::TearDown();
   }
 
-  void SetNetworkState(bool offline,
-                       double download,
-                       double upload,
-                       base::TimeDelta latency) {
+  struct NetworkState {
+    bool offline = false;
+    base::TimeDelta latency;
+    double packet_loss = 0.0;
+    int packet_queue_length = 0;
+  };
+
+  void SetNetworkState(NetworkState state) {
     std::unique_ptr<NetworkConditions> conditions(new NetworkConditions(
-        offline, latency.InMillisecondsF(), download, upload));
+        state.offline, state.latency.InMillisecondsF(), 0.0, 0.0,
+        state.packet_loss, state.packet_queue_length, false));
     ThrottlingController::SetConditions(*devtools_token_,
                                         std::move(conditions));
   }
@@ -1032,12 +1037,12 @@
   std::vector<uint8_t> packet;
   CreateRandomPacket(&packet);
 
-  SetNetworkState(true, 1000000, 1000000, base::Milliseconds(0));
+  SetNetworkState({.offline = true});
   socket_impl_->Send(packet, P2PPacketInfo(dest1_, options, 0));
   AdvanceClock(base::Milliseconds(100));
   EXPECT_EQ(0U, sent_packets_.size());
 
-  SetNetworkState(false, 1000000, 1000000, base::Milliseconds(0));
+  SetNetworkState({});
   socket_impl_->Send(packet, P2PPacketInfo(dest1_, options, 1));
   AdvanceClock(base::Milliseconds(100));
   EXPECT_EQ(1U, sent_packets_.size());
@@ -1059,7 +1064,7 @@
   std::vector<uint8_t> packet;
   CreateRandomPacket(&packet);
 
-  SetNetworkState(false, 1000000, 1000000, base::Milliseconds(1000));
+  SetNetworkState({.latency = base::Milliseconds(1000)});
   socket_impl_->Send(packet, P2PPacketInfo(dest1_, options, 0));
 
   AdvanceClock(base::Milliseconds(100));
@@ -1068,7 +1073,7 @@
   AdvanceClock(base::Milliseconds(2000));
   EXPECT_EQ(1U, sent_packets_.size());
 
-  SetNetworkState(false, 1000000, 1000000, base::Milliseconds(0));
+  SetNetworkState({});
   socket_impl_->Send(packet, P2PPacketInfo(dest1_, options, 1));
 
   AdvanceClock(base::Milliseconds(100));
@@ -1105,9 +1110,8 @@
 }
 
 TEST_F(P2PSocketUdpWithInterceptorTest, SendPacketDropsLongQueue) {
-  // Hardcoded value that matches the details in ThrottlingP2PNetworkInterceptor
-  // until we can configure it.
-  constexpr size_t kMaxQueueLength = 300;
+  constexpr size_t kMaxQueueLength = 100;
+  SetNetworkState({.packet_queue_length = kMaxQueueLength});
 
   // Receive packet from |dest1_|.
   std::vector<uint8_t> request_packet;
@@ -1132,6 +1136,33 @@
   EXPECT_EQ(kMaxQueueLength, sent_packets_.size());
 }
 
+TEST_F(P2PSocketUdpWithInterceptorTest, SendPacketWithPacketDrop) {
+  // Receive packet from |dest1_|.
+  std::vector<uint8_t> request_packet;
+  CreateStunRequest(&request_packet);
+
+  EXPECT_CALL(*fake_client_.get(), DataReceived(_)).Times(1);
+  EXPECT_CALL(*this, SinglePacketReceptionHelper(_, SpanEq(request_packet), _));
+  EXPECT_CALL(*fake_client_.get(), SendComplete(_)).Times(2);
+
+  socket_->ReceivePacket(dest1_, request_packet);
+  AdvanceClock(base::Milliseconds(100));
+
+  rtc::PacketOptions options;
+  std::vector<uint8_t> packet;
+  CreateRandomPacket(&packet);
+
+  SetNetworkState({.packet_loss = 100.0});
+  socket_impl_->Send(packet, P2PPacketInfo(dest1_, options, 0));
+  AdvanceClock(base::Milliseconds(100));
+  EXPECT_EQ(0U, sent_packets_.size());
+
+  SetNetworkState({});
+  socket_impl_->Send(packet, P2PPacketInfo(dest1_, options, 1));
+  AdvanceClock(base::Milliseconds(100));
+  EXPECT_EQ(1U, sent_packets_.size());
+}
+
 TEST_F(P2PSocketUdpWithInterceptorTest, ReceivePackets) {
   // Receive STUN request from |dest1_|.
   std::vector<uint8_t> request_packet;
@@ -1177,7 +1208,7 @@
 
   AdvanceClock(base::Milliseconds(100));
 
-  SetNetworkState(false, 1000000, 1000000, base::Milliseconds(1000));
+  SetNetworkState({.latency = base::Milliseconds(1000)});
 
   EXPECT_CALL(*fake_client_.get(), DataReceived(_)).Times(0);
   std::vector<uint8_t> packet;
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
index 8495a3c..2fe535e15 100644
--- a/services/network/public/mojom/network_context.mojom
+++ b/services/network/public/mojom/network_context.mojom
@@ -623,6 +623,17 @@
   // Maximal aggregated upload throughput (bytes/sec). <=0 disables upload
   // throttling.
   double upload_throughput;
+
+  // Packet loss for WebRTC connections (percent, 0 to 100).<= 0 disables
+  // packet loss.
+  double packet_loss;
+
+  // Packet queue length for WebRTC connections (packet count). <=0 allows for
+  // an infinite queue.
+  int32 packet_queue_length;
+
+  // Allow packet reordering for WebRTC connections.
+  bool packet_reordering;
 };
 
 // Represents a shared dictionary.
diff --git a/services/network/throttling/network_conditions.cc b/services/network/throttling/network_conditions.cc
index b894168c..bc052d5 100644
--- a/services/network/throttling/network_conditions.cc
+++ b/services/network/throttling/network_conditions.cc
@@ -14,22 +14,41 @@
     default;
 
 NetworkConditions::NetworkConditions(bool offline)
-    : NetworkConditions(offline, 0, 0, 0) {}
+    : NetworkConditions(offline, 0, 0, 0, 0.0, 0, false) {}
 
 NetworkConditions::NetworkConditions(bool offline,
                                      double latency,
                                      double download_throughput,
                                      double upload_throughput)
+    : NetworkConditions(offline,
+                        latency,
+                        download_throughput,
+                        upload_throughput,
+                        0.0,
+                        0,
+                        false) {}
+
+NetworkConditions::NetworkConditions(bool offline,
+                                     double latency,
+                                     double download_throughput,
+                                     double upload_throughput,
+                                     double packet_loss,
+                                     int packet_queue_length,
+                                     bool packet_reordering)
     : offline_(offline),
       latency_(std::max(latency, 0.0)),
       download_throughput_(std::max(download_throughput, 0.0)),
-      upload_throughput_(std::max(upload_throughput, 0.0)) {}
+      upload_throughput_(std::max(upload_throughput, 0.0)),
+      packet_loss_(std::max(packet_loss, 0.0)),
+      packet_queue_length_(packet_queue_length),
+      packet_reordering_(packet_reordering) {}
 
 NetworkConditions::~NetworkConditions() {}
 
 bool NetworkConditions::IsThrottling() const {
   return !offline_ && ((latency_ != 0) || (download_throughput_ != 0.0) ||
-                       (upload_throughput_ != 0));
+                       (upload_throughput_ != 0) || (packet_loss_ != 0.0) ||
+                       (packet_queue_length_ != 0) || packet_reordering_);
 }
 
 }  // namespace network
diff --git a/services/network/throttling/network_conditions.h b/services/network/throttling/network_conditions.h
index f1524ba..1dd9012 100644
--- a/services/network/throttling/network_conditions.h
+++ b/services/network/throttling/network_conditions.h
@@ -24,6 +24,13 @@
                     double latency,
                     double download_throughput,
                     double upload_throughput);
+  NetworkConditions(bool offline,
+                    double latency,
+                    double download_throughput,
+                    double upload_throughput,
+                    double packet_loss,
+                    int packet_queue_length,
+                    bool packet_reordering);
 
   bool IsThrottling() const;
 
@@ -33,12 +40,18 @@
   double latency() const { return latency_; }
   double download_throughput() const { return download_throughput_; }
   double upload_throughput() const { return upload_throughput_; }
+  double packet_loss() const { return packet_loss_; }
+  int packet_queue_length() const { return packet_queue_length_; }
+  bool packet_reordering() const { return packet_reordering_; }
 
  private:
   bool offline_;
   double latency_;
   double download_throughput_;
   double upload_throughput_;
+  double packet_loss_;
+  int packet_queue_length_;
+  bool packet_reordering_;
 };
 
 }  // namespace network
diff --git a/services/network/throttling/throttling_network_interceptor.cc b/services/network/throttling/throttling_network_interceptor.cc
index 777ec67..675081d 100644
--- a/services/network/throttling/throttling_network_interceptor.cc
+++ b/services/network/throttling/throttling_network_interceptor.cc
@@ -74,8 +74,6 @@
   }
 
   // Throttling.
-  DCHECK(conditions_.download_throughput() != 0 ||
-         conditions_.upload_throughput() != 0);
   offset_ = now;
 
   download_last_tick_ = 0;
diff --git a/services/network/throttling/throttling_p2p_network_interceptor.cc b/services/network/throttling/throttling_p2p_network_interceptor.cc
index 8b9a195..347789b 100644
--- a/services/network/throttling/throttling_p2p_network_interceptor.cc
+++ b/services/network/throttling/throttling_p2p_network_interceptor.cc
@@ -80,19 +80,25 @@
           << static_cast<uint64_t>(conditions.download_throughput() *
                                    kBitsPerByte)
           << "kbps, latency: " << static_cast<uint64_t>(conditions.latency())
-          << "ms";
+          << "ms, packet drop: " << conditions.packet_loss()
+          << "%, packet queue: " << conditions.packet_queue_length()
+          << ", packet reordering: " << conditions.packet_reordering();
 
   webrtc::SimulatedNetwork::Config send_config;
-  send_config.queue_length_packets = 300;
   send_config.link_capacity_kbps =
       (conditions.upload_throughput() * kBitsPerByte) / 1000;
   send_config.queue_delay_ms = conditions.latency();
+  send_config.allow_reordering = conditions.packet_reordering();
+  send_config.loss_percent = conditions.packet_loss();
+  send_config.queue_length_packets = conditions.packet_queue_length();
   send_network_.SetConfig(send_config);
 
   webrtc::SimulatedNetwork::Config receive_config;
   receive_config.link_capacity_kbps =
       (conditions.download_throughput() * kBitsPerByte) / 1000;
   receive_config.queue_delay_ms = conditions.latency();
+  receive_config.allow_reordering = conditions.packet_reordering();
+  receive_config.loss_percent = conditions.packet_loss();
   receive_network_.SetConfig(receive_config);
 }
 
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index 1fd197e..aba51cd 100644
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -6356,6 +6356,12 @@
       number uploadThroughput
       # Connection type if known.
       optional ConnectionType connectionType
+      # WebRTC packet loss (percent, 0-100). 0 disables packet loss emulation, 100 drops all the packets.
+      experimental optional number packetLoss
+      # WebRTC packet queue length (packet). 0 removes any queue length limitations.
+      experimental optional integer packetQueueLength
+      # WebRTC packetReordering feature.
+      experimental optional boolean packetReordering
 
   # Enables network tracking, network events will now be delivered to the client.
   command enable
diff --git a/third_party/blink/renderer/core/inspector/inspector_network_agent.cc b/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
index 6681081..89eacc5 100644
--- a/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
@@ -2219,7 +2219,10 @@
     double latency,
     double download_throughput,
     double upload_throughput,
-    Maybe<String> connection_type) {
+    Maybe<String> connection_type,
+    Maybe<double> packet_loss,
+    Maybe<int> packet_queue_length,
+    Maybe<bool> packet_reordering) {
   WebConnectionType type = kWebConnectionTypeUnknown;
   if (connection_type.has_value()) {
     type = ToWebConnectionType(connection_type.value());
diff --git a/third_party/blink/renderer/core/inspector/inspector_network_agent.h b/third_party/blink/renderer/core/inspector/inspector_network_agent.h
index 7ae06124..360bb3a 100644
--- a/third_party/blink/renderer/core/inspector/inspector_network_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_network_agent.h
@@ -245,7 +245,10 @@
       double latency,
       double download_throughput,
       double upload_throughput,
-      Maybe<String> connection_type) override;
+      Maybe<String> connection_type,
+      Maybe<double> packet_loss,
+      Maybe<int> packet_queue_length,
+      Maybe<bool> packet_reordering) override;
   protocol::Response setCacheDisabled(bool) override;
   protocol::Response setBypassServiceWorker(bool) override;
   protocol::Response getCertificate(