Add a turn port prune policy to keep the first ready turn port.

Bug: webrtc:11026
Change-Id: I6222e9613ee4ce2dcfbb717e2430ea833c0dc373
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/155542
Commit-Queue: Honghai Zhang <honghaiz@webrtc.org>
Reviewed-by: Qingsi Wang <qingsi@webrtc.org>
Reviewed-by: Patrik Höglund <phoglund@webrtc.org>
Reviewed-by: Jonas Oreland <jonaso@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29470}
diff --git a/api/BUILD.gn b/api/BUILD.gn
index 58fd04e..4eed48c 100644
--- a/api/BUILD.gn
+++ b/api/BUILD.gn
@@ -181,6 +181,7 @@
     "task_queue",
     "transport:bitrate_settings",
     "transport:datagram_transport_interface",
+    "transport:enums",
     "transport:network_control",
     "transport/media:audio_interfaces",
     "transport/media:media_transport_interface",
diff --git a/api/peer_connection_interface.h b/api/peer_connection_interface.h
index f526c37..12c5162 100644
--- a/api/peer_connection_interface.h
+++ b/api/peer_connection_interface.h
@@ -99,6 +99,7 @@
 #include "api/stats_types.h"
 #include "api/task_queue/task_queue_factory.h"
 #include "api/transport/bitrate_settings.h"
+#include "api/transport/enums.h"
 #include "api/transport/media/media_transport_interface.h"
 #include "api/transport/network_control.h"
 #include "api/turn_customizer.h"
@@ -394,7 +395,7 @@
     int max_ipv6_networks = cricket::kDefaultMaxIPv6Networks;
 
     // Exclude link-local network interfaces
-    // from considertaion for gathering ICE candidates.
+    // from consideration for gathering ICE candidates.
     bool disable_link_local_networks = false;
 
     // If set to true, use RTP data channels instead of SCTP.
@@ -479,8 +480,18 @@
     // If set to true, only one preferred TURN allocation will be used per
     // network interface. UDP is preferred over TCP and IPv6 over IPv4. This
     // can be used to cut down on the number of candidate pairings.
+    // Deprecated. TODO(webrtc:11026) Remove this flag once the downstream
+    // dependency is removed.
     bool prune_turn_ports = false;
 
+    // The policy used to prune turn port.
+    PortPrunePolicy turn_port_prune_policy = NO_PRUNE;
+
+    PortPrunePolicy GetTurnPortPrunePolicy() const {
+      return prune_turn_ports ? PRUNE_BASED_ON_PRIORITY
+                              : turn_port_prune_policy;
+    }
+
     // If set to true, this means the ICE transport should presume TURN-to-TURN
     // candidate pairs will succeed, even before a binding response is received.
     // This can be used to optimize the initial connection time, since the DTLS
diff --git a/api/transport/enums.h b/api/transport/enums.h
index b1d5770..eb33e91 100644
--- a/api/transport/enums.h
+++ b/api/transport/enums.h
@@ -27,6 +27,13 @@
   kClosed,
 };
 
+enum PortPrunePolicy {
+  NO_PRUNE,                 // Do not prune.
+  PRUNE_BASED_ON_PRIORITY,  // Prune lower-priority ports on the same network.
+  KEEP_FIRST_READY          // Keep the first ready port and prune the rest
+                            // on the same network.
+};
+
 }  // namespace webrtc
 
 #endif  // API_TRANSPORT_ENUMS_H_
diff --git a/p2p/base/p2p_transport_channel_unittest.cc b/p2p/base/p2p_transport_channel_unittest.cc
index 5a060a8..8f7fd4a 100644
--- a/p2p/base/p2p_transport_channel_unittest.cc
+++ b/p2p/base/p2p_transport_channel_unittest.cc
@@ -171,7 +171,7 @@
   cricket::BasicPortAllocator* allocator =
       new cricket::BasicPortAllocator(network_manager);
   allocator->Initialize();
-  allocator->SetConfiguration(stun_servers, turn_servers, 0, false);
+  allocator->SetConfiguration(stun_servers, turn_servers, 0, webrtc::NO_PRUNE);
   return allocator;
 }
 }  // namespace
@@ -2036,9 +2036,11 @@
   auto& allocator_2 = GetEndpoint(1)->allocator_;
   int pool_size = 1;
   allocator_1->SetConfiguration(allocator_1->stun_servers(),
-                                allocator_1->turn_servers(), pool_size, false);
+                                allocator_1->turn_servers(), pool_size,
+                                webrtc::NO_PRUNE);
   allocator_2->SetConfiguration(allocator_2->stun_servers(),
-                                allocator_2->turn_servers(), pool_size, false);
+                                allocator_2->turn_servers(), pool_size,
+                                webrtc::NO_PRUNE);
   const PortAllocatorSession* pooled_session_1 =
       allocator_1->GetPooledSession();
   const PortAllocatorSession* pooled_session_2 =
@@ -2079,9 +2081,11 @@
   auto& allocator_2 = GetEndpoint(1)->allocator_;
   int pool_size = 1;
   allocator_1->SetConfiguration(allocator_1->stun_servers(),
-                                allocator_1->turn_servers(), pool_size, false);
+                                allocator_1->turn_servers(), pool_size,
+                                webrtc::NO_PRUNE);
   allocator_2->SetConfiguration(allocator_2->stun_servers(),
-                                allocator_2->turn_servers(), pool_size, false);
+                                allocator_2->turn_servers(), pool_size,
+                                webrtc::NO_PRUNE);
   const PortAllocatorSession* pooled_session_1 =
       allocator_1->GetPooledSession();
   const PortAllocatorSession* pooled_session_2 =
diff --git a/p2p/base/port_allocator.cc b/p2p/base/port_allocator.cc
index a9d7cb6..b30416f 100644
--- a/p2p/base/port_allocator.cc
+++ b/p2p/base/port_allocator.cc
@@ -116,6 +116,7 @@
   restrict_ice_credentials_change_ = value;
 }
 
+// Deprecated
 bool PortAllocator::SetConfiguration(
     const ServerAddresses& stun_servers,
     const std::vector<RelayServerConfig>& turn_servers,
@@ -123,6 +124,20 @@
     bool prune_turn_ports,
     webrtc::TurnCustomizer* turn_customizer,
     const absl::optional<int>& stun_candidate_keepalive_interval) {
+  webrtc::PortPrunePolicy turn_port_prune_policy =
+      prune_turn_ports ? webrtc::PRUNE_BASED_ON_PRIORITY : webrtc::NO_PRUNE;
+  return SetConfiguration(stun_servers, turn_servers, candidate_pool_size,
+                          turn_port_prune_policy, turn_customizer,
+                          stun_candidate_keepalive_interval);
+}
+
+bool PortAllocator::SetConfiguration(
+    const ServerAddresses& stun_servers,
+    const std::vector<RelayServerConfig>& turn_servers,
+    int candidate_pool_size,
+    webrtc::PortPrunePolicy turn_port_prune_policy,
+    webrtc::TurnCustomizer* turn_customizer,
+    const absl::optional<int>& stun_candidate_keepalive_interval) {
   CheckRunOnValidThreadIfInitialized();
   // A positive candidate pool size would lead to the creation of a pooled
   // allocator session and starting getting ports, which we should only do on
@@ -132,7 +147,7 @@
       (stun_servers != stun_servers_ || turn_servers != turn_servers_);
   stun_servers_ = stun_servers;
   turn_servers_ = turn_servers;
-  prune_turn_ports_ = prune_turn_ports;
+  turn_port_prune_policy_ = turn_port_prune_policy;
 
   if (candidate_pool_frozen_) {
     if (candidate_pool_size != candidate_pool_size_) {
diff --git a/p2p/base/port_allocator.h b/p2p/base/port_allocator.h
index f29877c..eb04cc2 100644
--- a/p2p/base/port_allocator.h
+++ b/p2p/base/port_allocator.h
@@ -16,6 +16,7 @@
 #include <string>
 #include <vector>
 
+#include "api/transport/enums.h"
 #include "p2p/base/port.h"
 #include "p2p/base/port_interface.h"
 #include "rtc_base/helpers.h"
@@ -360,6 +361,7 @@
   // created or destroyed as necessary.
   //
   // Returns true if the configuration could successfully be changed.
+  // Deprecated
   bool SetConfiguration(const ServerAddresses& stun_servers,
                         const std::vector<RelayServerConfig>& turn_servers,
                         int candidate_pool_size,
@@ -367,6 +369,13 @@
                         webrtc::TurnCustomizer* turn_customizer = nullptr,
                         const absl::optional<int>&
                             stun_candidate_keepalive_interval = absl::nullopt);
+  bool SetConfiguration(const ServerAddresses& stun_servers,
+                        const std::vector<RelayServerConfig>& turn_servers,
+                        int candidate_pool_size,
+                        webrtc::PortPrunePolicy turn_port_prune_policy,
+                        webrtc::TurnCustomizer* turn_customizer = nullptr,
+                        const absl::optional<int>&
+                            stun_candidate_keepalive_interval = absl::nullopt);
 
   const ServerAddresses& stun_servers() const {
     CheckRunOnValidThreadIfInitialized();
@@ -555,9 +564,15 @@
   // TODO(qingsi): Remove this after Chromium migrates to the new method.
   void set_candidate_filter(uint32_t filter) { SetCandidateFilter(filter); }
 
+  // Deprecated (by the next method).
   bool prune_turn_ports() const {
     CheckRunOnValidThreadIfInitialized();
-    return prune_turn_ports_;
+    return turn_port_prune_policy_ == webrtc::PRUNE_BASED_ON_PRIORITY;
+  }
+
+  webrtc::PortPrunePolicy turn_port_prune_policy() const {
+    CheckRunOnValidThreadIfInitialized();
+    return turn_port_prune_policy_;
   }
 
   // Gets/Sets the Origin value used for WebRTC STUN requests.
@@ -634,7 +649,7 @@
   int candidate_pool_size_ = 0;  // Last value passed into SetConfiguration.
   std::vector<std::unique_ptr<PortAllocatorSession>> pooled_sessions_;
   bool candidate_pool_frozen_ = false;
-  bool prune_turn_ports_ = false;
+  webrtc::PortPrunePolicy turn_port_prune_policy_ = webrtc::NO_PRUNE;
 
   // Customizer for TURN messages.
   // The instance is owned by application and will be shared among
diff --git a/p2p/base/port_allocator_unittest.cc b/p2p/base/port_allocator_unittest.cc
index 5606168..70946a3 100644
--- a/p2p/base/port_allocator_unittest.cc
+++ b/p2p/base/port_allocator_unittest.cc
@@ -37,13 +37,13 @@
   void SetConfigurationWithPoolSize(int candidate_pool_size) {
     EXPECT_TRUE(allocator_->SetConfiguration(
         cricket::ServerAddresses(), std::vector<cricket::RelayServerConfig>(),
-        candidate_pool_size, false));
+        candidate_pool_size, webrtc::NO_PRUNE));
   }
 
   void SetConfigurationWithPoolSizeExpectFailure(int candidate_pool_size) {
     EXPECT_FALSE(allocator_->SetConfiguration(
         cricket::ServerAddresses(), std::vector<cricket::RelayServerConfig>(),
-        candidate_pool_size, false));
+        candidate_pool_size, webrtc::NO_PRUNE));
   }
 
   std::unique_ptr<cricket::FakePortAllocatorSession> CreateSession(
@@ -114,16 +114,16 @@
 TEST_F(PortAllocatorTest, SetConfigurationUpdatesIceServers) {
   cricket::ServerAddresses stun_servers_1 = {stun_server_1};
   std::vector<cricket::RelayServerConfig> turn_servers_1 = {turn_server_1};
-  EXPECT_TRUE(
-      allocator_->SetConfiguration(stun_servers_1, turn_servers_1, 0, false));
+  EXPECT_TRUE(allocator_->SetConfiguration(stun_servers_1, turn_servers_1, 0,
+                                           webrtc::NO_PRUNE));
   EXPECT_EQ(stun_servers_1, allocator_->stun_servers());
   EXPECT_EQ(turn_servers_1, allocator_->turn_servers());
 
   // Update with a different set of servers.
   cricket::ServerAddresses stun_servers_2 = {stun_server_2};
   std::vector<cricket::RelayServerConfig> turn_servers_2 = {turn_server_2};
-  EXPECT_TRUE(
-      allocator_->SetConfiguration(stun_servers_2, turn_servers_2, 0, false));
+  EXPECT_TRUE(allocator_->SetConfiguration(stun_servers_2, turn_servers_2, 0,
+                                           webrtc::NO_PRUNE));
   EXPECT_EQ(stun_servers_2, allocator_->stun_servers());
   EXPECT_EQ(turn_servers_2, allocator_->turn_servers());
 }
@@ -179,14 +179,16 @@
        SetConfigurationRecreatesPooledSessionsWhenIceServersChange) {
   cricket::ServerAddresses stun_servers_1 = {stun_server_1};
   std::vector<cricket::RelayServerConfig> turn_servers_1 = {turn_server_1};
-  allocator_->SetConfiguration(stun_servers_1, turn_servers_1, 1, false);
+  allocator_->SetConfiguration(stun_servers_1, turn_servers_1, 1,
+                               webrtc::NO_PRUNE);
   EXPECT_EQ(stun_servers_1, allocator_->stun_servers());
   EXPECT_EQ(turn_servers_1, allocator_->turn_servers());
 
   // Update with a different set of servers (and also change pool size).
   cricket::ServerAddresses stun_servers_2 = {stun_server_2};
   std::vector<cricket::RelayServerConfig> turn_servers_2 = {turn_server_2};
-  allocator_->SetConfiguration(stun_servers_2, turn_servers_2, 2, false);
+  allocator_->SetConfiguration(stun_servers_2, turn_servers_2, 2,
+                               webrtc::NO_PRUNE);
   EXPECT_EQ(stun_servers_2, allocator_->stun_servers());
   EXPECT_EQ(turn_servers_2, allocator_->turn_servers());
   auto session_1 = TakePooledSession();
@@ -207,7 +209,8 @@
        SetConfigurationDoesNotRecreatePooledSessionsAfterFreezeCandidatePool) {
   cricket::ServerAddresses stun_servers_1 = {stun_server_1};
   std::vector<cricket::RelayServerConfig> turn_servers_1 = {turn_server_1};
-  allocator_->SetConfiguration(stun_servers_1, turn_servers_1, 1, false);
+  allocator_->SetConfiguration(stun_servers_1, turn_servers_1, 1,
+                               webrtc::NO_PRUNE);
   EXPECT_EQ(stun_servers_1, allocator_->stun_servers());
   EXPECT_EQ(turn_servers_1, allocator_->turn_servers());
 
@@ -215,7 +218,8 @@
   allocator_->FreezeCandidatePool();
   cricket::ServerAddresses stun_servers_2 = {stun_server_2};
   std::vector<cricket::RelayServerConfig> turn_servers_2 = {turn_server_2};
-  allocator_->SetConfiguration(stun_servers_2, turn_servers_2, 2, false);
+  allocator_->SetConfiguration(stun_servers_2, turn_servers_2, 2,
+                               webrtc::NO_PRUNE);
   EXPECT_EQ(stun_servers_2, allocator_->stun_servers());
   EXPECT_EQ(turn_servers_2, allocator_->turn_servers());
   auto session = TakePooledSession();
diff --git a/p2p/base/regathering_controller_unittest.cc b/p2p/base/regathering_controller_unittest.cc
index d583ef8..cee4a67 100644
--- a/p2p/base/regathering_controller_unittest.cc
+++ b/p2p/base/regathering_controller_unittest.cc
@@ -70,7 +70,7 @@
     std::vector<cricket::RelayServerConfig> turn_servers(1, turn_server);
     allocator_->set_flags(kOnlyLocalPorts);
     allocator_->SetConfiguration(stun_servers, turn_servers, 0 /* pool size */,
-                                 false /* prune turn ports */);
+                                 webrtc::NO_PRUNE);
     allocator_session_ = allocator_->CreateSession(
         "test", cricket::ICE_CANDIDATE_COMPONENT_RTP, kIceUfrag, kIcePwd);
     // The gathering will take place on the current thread and the following
diff --git a/p2p/client/basic_port_allocator.cc b/p2p/client/basic_port_allocator.cc
index 3608fe1..b49e2f8 100644
--- a/p2p/client/basic_port_allocator.cc
+++ b/p2p/client/basic_port_allocator.cc
@@ -161,7 +161,7 @@
   RTC_DCHECK(network_manager_ != nullptr);
   RTC_DCHECK(socket_factory_ != nullptr);
   SetConfiguration(ServerAddresses(), std::vector<RelayServerConfig>(), 0,
-                   false, customizer);
+                   webrtc::NO_PRUNE, customizer);
   Construct();
 }
 
@@ -185,8 +185,8 @@
     : network_manager_(network_manager), socket_factory_(socket_factory) {
   InitRelayPortFactory(nullptr);
   RTC_DCHECK(relay_port_factory_ != nullptr);
-  SetConfiguration(stun_servers, std::vector<RelayServerConfig>(), 0, false,
-                   nullptr);
+  SetConfiguration(stun_servers, std::vector<RelayServerConfig>(), 0,
+                   webrtc::NO_PRUNE, nullptr);
   Construct();
 }
 
@@ -242,7 +242,7 @@
   std::vector<RelayServerConfig> new_turn_servers = turn_servers();
   new_turn_servers.push_back(turn_server);
   SetConfiguration(stun_servers(), new_turn_servers, candidate_pool_size(),
-                   prune_turn_ports(), turn_customizer());
+                   turn_port_prune_policy(), turn_customizer());
 }
 
 void BasicPortAllocator::InitRelayPortFactory(
@@ -273,7 +273,7 @@
       allocation_started_(false),
       network_manager_started_(false),
       allocation_sequences_created_(false),
-      prune_turn_ports_(allocator->prune_turn_ports()) {
+      turn_port_prune_policy_(allocator->turn_port_prune_policy()) {
   allocator_->network_manager()->SignalNetworksChanged.connect(
       this, &BasicPortAllocatorSession::OnNetworksChanged);
   allocator_->network_manager()->StartUpdating();
@@ -378,8 +378,8 @@
 
   network_thread_->Post(RTC_FROM_HERE, this, MSG_CONFIG_START);
 
-  RTC_LOG(LS_INFO) << "Start getting ports with prune_turn_ports "
-                   << (prune_turn_ports_ ? "enabled" : "disabled");
+  RTC_LOG(LS_INFO) << "Start getting ports with turn_port_prune_policy "
+                   << turn_port_prune_policy_;
 }
 
 void BasicPortAllocatorSession::StopGettingPorts() {
@@ -967,9 +967,14 @@
   if (CandidatePairable(c, port) && !data->has_pairable_candidate()) {
     data->set_has_pairable_candidate(true);
 
-    if (prune_turn_ports_ && port->Type() == RELAY_PORT_TYPE) {
-      pruned = PruneTurnPorts(port);
+    if (port->Type() == RELAY_PORT_TYPE) {
+      if (turn_port_prune_policy_ == webrtc::KEEP_FIRST_READY) {
+        pruned = PruneNewlyPairableTurnPort(data);
+      } else if (turn_port_prune_policy_ == webrtc::PRUNE_BASED_ON_PRIORITY) {
+        pruned = PruneTurnPorts(port);
+      }
     }
+
     // If the current port is not pruned yet, SignalPortReady.
     if (!data->pruned()) {
       RTC_LOG(LS_INFO) << port->ToString() << ": Port ready.";
@@ -1015,6 +1020,28 @@
   return best_turn_port;
 }
 
+bool BasicPortAllocatorSession::PruneNewlyPairableTurnPort(
+    PortData* newly_pairable_port_data) {
+  RTC_DCHECK_RUN_ON(network_thread_);
+  RTC_DCHECK(newly_pairable_port_data->port()->Type() == RELAY_PORT_TYPE);
+  // If an existing turn port is ready on the same network, prune the newly
+  // pairable port.
+  const std::string& network_name =
+      newly_pairable_port_data->port()->Network()->name();
+
+  for (PortData& data : ports_) {
+    if (data.port()->Network()->name() == network_name &&
+        data.port()->Type() == RELAY_PORT_TYPE && data.ready() &&
+        &data != newly_pairable_port_data) {
+      RTC_LOG(LS_INFO) << "Port pruned: "
+                       << newly_pairable_port_data->port()->ToString();
+      newly_pairable_port_data->Prune();
+      return true;
+    }
+  }
+  return false;
+}
+
 bool BasicPortAllocatorSession::PruneTurnPorts(Port* newly_pairable_turn_port) {
   RTC_DCHECK_RUN_ON(network_thread_);
   // Note: We determine the same network based only on their network names. So
diff --git a/p2p/client/basic_port_allocator.h b/p2p/client/basic_port_allocator.h
index 29c514f..10188ba 100644
--- a/p2p/client/basic_port_allocator.h
+++ b/p2p/client/basic_port_allocator.h
@@ -262,6 +262,7 @@
   Port* GetBestTurnPortForNetwork(const std::string& network_name) const;
   // Returns true if at least one TURN port is pruned.
   bool PruneTurnPorts(Port* newly_pairable_turn_port);
+  bool PruneNewlyPairableTurnPort(PortData* newly_pairable_turn_port);
 
   BasicPortAllocator* allocator_;
   rtc::Thread* network_thread_;
@@ -274,8 +275,8 @@
   std::vector<AllocationSequence*> sequences_;
   std::vector<PortData> ports_;
   uint32_t candidate_filter_ = CF_ALL;
-  // Whether to prune low-priority ports, taken from the port allocator.
-  bool prune_turn_ports_;
+  // Policy on how to prune turn ports, taken from the port allocator.
+  webrtc::PortPrunePolicy turn_port_prune_policy_;
   SessionState state_ = SessionState::CLEARED;
 
   friend class AllocationSequence;
diff --git a/p2p/client/basic_port_allocator_unittest.cc b/p2p/client/basic_port_allocator_unittest.cc
index 9d97dc9..31877ff 100644
--- a/p2p/client/basic_port_allocator_unittest.cc
+++ b/p2p/client/basic_port_allocator_unittest.cc
@@ -571,7 +571,8 @@
     allocator_.reset(new BasicPortAllocator(&network_manager_));
     allocator_->Initialize();
     allocator_->SetConfiguration(allocator_->stun_servers(),
-                                 allocator_->turn_servers(), 0, true);
+                                 allocator_->turn_servers(), 0,
+                                 webrtc::PRUNE_BASED_ON_PRIORITY);
     AddTurnServers(kTurnUdpIntIPv6Addr, rtc::SocketAddress());
     AddTurnServers(kTurnUdpIntAddr, rtc::SocketAddress());
 
@@ -603,13 +604,15 @@
                              rtc::SocketAddress(kTurnUdpExtAddr.ipaddr(), 0)));
   }
 
-  void TestUdpTurnPortPrunesTcpTurnPort() {
+  void TestTurnPortPrunesWithUdpAndTcpPorts(
+      webrtc::PortPrunePolicy prune_policy,
+      bool tcp_pruned) {
     turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
     AddInterface(kClientAddr);
     allocator_.reset(new BasicPortAllocator(&network_manager_));
     allocator_->Initialize();
     allocator_->SetConfiguration(allocator_->stun_servers(),
-                                 allocator_->turn_servers(), 0, true);
+                                 allocator_->turn_servers(), 0, prune_policy);
     AddTurnServers(kTurnUdpIntAddr, kTurnTcpIntAddr);
     allocator_->set_step_delay(kMinimumStepDelay);
     allocator_->set_flags(allocator().flags() |
@@ -627,8 +630,11 @@
     // |ready_ports|, so we only need to verify the content in one of them.
     EXPECT_EQ(2U, ports_.size());
     EXPECT_EQ(1, CountPorts(ports_, "local", PROTO_UDP, kClientAddr));
-    EXPECT_EQ(1, CountPorts(ports_, "relay", PROTO_UDP, kClientAddr));
-    EXPECT_EQ(0, CountPorts(ports_, "relay", PROTO_TCP, kClientAddr));
+    int num_udp_ports = tcp_pruned ? 1 : 0;
+    EXPECT_EQ(num_udp_ports,
+              CountPorts(ports_, "relay", PROTO_UDP, kClientAddr));
+    EXPECT_EQ(1 - num_udp_ports,
+              CountPorts(ports_, "relay", PROTO_TCP, kClientAddr));
 
     // Now that we remove candidates when a TURN port is pruned, |candidates_|
     // should only contains two candidates regardless whether the TCP TURN port
@@ -640,6 +646,8 @@
         session_->ReadyCandidates();
     EXPECT_EQ(2U, ready_candidates.size());
     EXPECT_TRUE(HasCandidate(ready_candidates, "local", "udp", kClientAddr));
+
+    // The external candidate is always udp.
     EXPECT_TRUE(HasCandidate(ready_candidates, "relay", "udp",
                              rtc::SocketAddress(kTurnUdpExtAddr.ipaddr(), 0)));
   }
@@ -656,7 +664,8 @@
     allocator_.reset(new BasicPortAllocator(&network_manager_));
     allocator_->Initialize();
     allocator_->SetConfiguration(allocator_->stun_servers(),
-                                 allocator_->turn_servers(), 0, true);
+                                 allocator_->turn_servers(), 0,
+                                 webrtc::PRUNE_BASED_ON_PRIORITY);
     // Have both UDP/TCP and IPv4/IPv6 TURN ports.
     AddTurnServers(kTurnUdpIntAddr, kTurnTcpIntAddr);
     AddTurnServers(kTurnUdpIntIPv6Addr, kTurnTcpIntIPv6Addr);
@@ -1649,30 +1658,59 @@
                            rtc::SocketAddress(kTurnUdpExtAddr.ipaddr(), 0)));
 }
 
-// Test that if prune_turn_ports is set, TCP TURN port will not be used
-// if UDP TurnPort is used, given that TCP TURN port becomes ready first.
+// Test that if the turn port prune policy is PRUNE_BASED_ON_PRIORITY, TCP TURN
+// port will not be used if UDP TurnPort is used, given that TCP TURN port
+// becomes ready first.
 TEST_F(BasicPortAllocatorTest,
        TestUdpTurnPortPrunesTcpTurnPortWithTcpPortReadyFirst) {
   // UDP has longer delay than TCP so that TCP TURN port becomes ready first.
   virtual_socket_server()->SetDelayOnAddress(kTurnUdpIntAddr, 200);
   virtual_socket_server()->SetDelayOnAddress(kTurnTcpIntAddr, 100);
 
-  TestUdpTurnPortPrunesTcpTurnPort();
+  TestTurnPortPrunesWithUdpAndTcpPorts(webrtc::PRUNE_BASED_ON_PRIORITY,
+                                       true /* tcp_pruned */);
 }
 
-// Test that if prune_turn_ports is set, TCP TURN port will not be used
-// if UDP TurnPort is used, given that UDP TURN port becomes ready first.
+// Test that if turn port prune policy is PRUNE_BASED_ON_PRIORITY, TCP TURN port
+// will not be used if UDP TurnPort is used, given that UDP TURN port becomes
+// ready first.
 TEST_F(BasicPortAllocatorTest,
        TestUdpTurnPortPrunesTcpTurnPortsWithUdpPortReadyFirst) {
   // UDP has shorter delay than TCP so that UDP TURN port becomes ready first.
   virtual_socket_server()->SetDelayOnAddress(kTurnUdpIntAddr, 100);
   virtual_socket_server()->SetDelayOnAddress(kTurnTcpIntAddr, 200);
 
-  TestUdpTurnPortPrunesTcpTurnPort();
+  TestTurnPortPrunesWithUdpAndTcpPorts(webrtc::PRUNE_BASED_ON_PRIORITY,
+                                       true /* tcp_pruned */);
 }
 
-// Tests that if prune_turn_ports is set, IPv4 TurnPort will not be used
-// if IPv6 TurnPort is used, given that IPv4 TURN port becomes ready first.
+// Test that if turn_port_prune policy is KEEP_FIRST_READY, the first ready port
+// will be kept regardless of the priority.
+TEST_F(BasicPortAllocatorTest,
+       TestUdpTurnPortPrunesTcpTurnPortIfUdpReadyFirst) {
+  // UDP has shorter delay than TCP so that UDP TURN port becomes ready first.
+  virtual_socket_server()->SetDelayOnAddress(kTurnUdpIntAddr, 100);
+  virtual_socket_server()->SetDelayOnAddress(kTurnTcpIntAddr, 200);
+
+  TestTurnPortPrunesWithUdpAndTcpPorts(webrtc::KEEP_FIRST_READY,
+                                       true /* tcp_pruned */);
+}
+
+// Test that if turn_port_prune policy is KEEP_FIRST_READY, the first ready port
+// will be kept regardless of the priority.
+TEST_F(BasicPortAllocatorTest,
+       TestTcpTurnPortPrunesUdpTurnPortIfTcpReadyFirst) {
+  // UDP has longer delay than TCP so that TCP TURN port becomes ready first.
+  virtual_socket_server()->SetDelayOnAddress(kTurnUdpIntAddr, 200);
+  virtual_socket_server()->SetDelayOnAddress(kTurnTcpIntAddr, 100);
+
+  TestTurnPortPrunesWithUdpAndTcpPorts(webrtc::KEEP_FIRST_READY,
+                                       false /* tcp_pruned */);
+}
+
+// Tests that if turn port prune policy is PRUNE_BASED_ON_PRIORITY, IPv4
+// TurnPort will not be used if IPv6 TurnPort is used, given that IPv4 TURN port
+// becomes ready first.
 TEST_F(BasicPortAllocatorTest,
        TestIPv6TurnPortPrunesIPv4TurnPortWithIPv4PortReadyFirst) {
   // IPv6 has longer delay than IPv4, so that IPv4 TURN port becomes ready
@@ -1683,8 +1721,9 @@
   TestIPv6TurnPortPrunesIPv4TurnPort();
 }
 
-// Tests that if prune_turn_ports is set, IPv4 TurnPort will not be used
-// if IPv6 TurnPort is used, given that IPv6 TURN port becomes ready first.
+// Tests that if turn port prune policy is PRUNE_BASED_ON_PRIORITY, IPv4
+// TurnPort will not be used if IPv6 TurnPort is used, given that IPv6 TURN port
+// becomes ready first.
 TEST_F(BasicPortAllocatorTest,
        TestIPv6TurnPortPrunesIPv4TurnPortWithIPv6PortReadyFirst) {
   // IPv6 has longer delay than IPv4, so that IPv6 TURN port becomes ready
@@ -1695,16 +1734,16 @@
   TestIPv6TurnPortPrunesIPv4TurnPort();
 }
 
-// Tests that if prune_turn_ports is set, each network interface
-// will has its own set of TurnPorts based on their priorities, in the default
-// case where no transit delay is set.
+// Tests that if turn port prune policy is PRUNE_BASED_ON_PRIORITY, each network
+// interface will has its own set of TurnPorts based on their priorities, in the
+// default case where no transit delay is set.
 TEST_F(BasicPortAllocatorTest, TestEachInterfaceHasItsOwnTurnPortsNoDelay) {
   TestEachInterfaceHasItsOwnTurnPorts();
 }
 
-// Tests that if prune_turn_ports is set, each network interface
-// will has its own set of TurnPorts based on their priorities, given that
-// IPv4/TCP TURN port becomes ready first.
+// Tests that if turn port prune policy is PRUNE_BASED_ON_PRIORITY, each network
+// interface will has its own set of TurnPorts based on their priorities, given
+// that IPv4/TCP TURN port becomes ready first.
 TEST_F(BasicPortAllocatorTest,
        TestEachInterfaceHasItsOwnTurnPortsWithTcpIPv4ReadyFirst) {
   // IPv6/UDP have longer delay than IPv4/TCP, so that IPv4/TCP TURN port
@@ -2038,7 +2077,8 @@
   AddInterface(kClientAddr);
   int pool_size = 1;
   allocator_->SetConfiguration(allocator_->stun_servers(),
-                               allocator_->turn_servers(), pool_size, false);
+                               allocator_->turn_servers(), pool_size,
+                               webrtc::NO_PRUNE);
   const PortAllocatorSession* peeked_session = allocator_->GetPooledSession();
   ASSERT_NE(nullptr, peeked_session);
   EXPECT_EQ_SIMULATED_WAIT(true, peeked_session->CandidatesAllocationDone(),
@@ -2074,7 +2114,8 @@
   AddInterface(kClientAddr);
   int pool_size = 1;
   allocator_->SetConfiguration(allocator_->stun_servers(),
-                               allocator_->turn_servers(), pool_size, false);
+                               allocator_->turn_servers(), pool_size,
+                               webrtc::NO_PRUNE);
   const PortAllocatorSession* peeked_session = allocator_->GetPooledSession();
   ASSERT_NE(nullptr, peeked_session);
   EXPECT_EQ_SIMULATED_WAIT(true, peeked_session->CandidatesAllocationDone(),
@@ -2248,9 +2289,9 @@
   const int pool_size = 1;
   const int expected_stun_keepalive_interval = 123;
   AddInterface(kClientAddr);
-  allocator_->SetConfiguration(allocator_->stun_servers(),
-                               allocator_->turn_servers(), pool_size, false,
-                               nullptr, expected_stun_keepalive_interval);
+  allocator_->SetConfiguration(
+      allocator_->stun_servers(), allocator_->turn_servers(), pool_size,
+      webrtc::NO_PRUNE, nullptr, expected_stun_keepalive_interval);
   auto* pooled_session = allocator_->GetPooledSession();
   ASSERT_NE(nullptr, pooled_session);
   EXPECT_EQ_SIMULATED_WAIT(true, pooled_session->CandidatesAllocationDone(),
@@ -2263,17 +2304,17 @@
        ChangeStunKeepaliveIntervalForPortsAfterInitialConfig) {
   const int pool_size = 1;
   AddInterface(kClientAddr);
-  allocator_->SetConfiguration(allocator_->stun_servers(),
-                               allocator_->turn_servers(), pool_size, false,
-                               nullptr, 123 /* stun keepalive interval */);
+  allocator_->SetConfiguration(
+      allocator_->stun_servers(), allocator_->turn_servers(), pool_size,
+      webrtc::NO_PRUNE, nullptr, 123 /* stun keepalive interval */);
   auto* pooled_session = allocator_->GetPooledSession();
   ASSERT_NE(nullptr, pooled_session);
   EXPECT_EQ_SIMULATED_WAIT(true, pooled_session->CandidatesAllocationDone(),
                            kDefaultAllocationTimeout, fake_clock);
   const int expected_stun_keepalive_interval = 321;
-  allocator_->SetConfiguration(allocator_->stun_servers(),
-                               allocator_->turn_servers(), pool_size, false,
-                               nullptr, expected_stun_keepalive_interval);
+  allocator_->SetConfiguration(
+      allocator_->stun_servers(), allocator_->turn_servers(), pool_size,
+      webrtc::NO_PRUNE, nullptr, expected_stun_keepalive_interval);
   CheckStunKeepaliveIntervalOfAllReadyPorts(pooled_session,
                                             expected_stun_keepalive_interval);
 }
@@ -2285,9 +2326,9 @@
   AddInterface(kClientAddr);
   allocator_->set_flags(allocator().flags() |
                         PORTALLOCATOR_ENABLE_SHARED_SOCKET);
-  allocator_->SetConfiguration(allocator_->stun_servers(),
-                               allocator_->turn_servers(), pool_size, false,
-                               nullptr, expected_stun_keepalive_interval);
+  allocator_->SetConfiguration(
+      allocator_->stun_servers(), allocator_->turn_servers(), pool_size,
+      webrtc::NO_PRUNE, nullptr, expected_stun_keepalive_interval);
   ASSERT_TRUE(CreateSession(ICE_CANDIDATE_COMPONENT_RTP));
   session_->StartGettingPorts();
   EXPECT_TRUE_SIMULATED_WAIT(candidate_allocation_done_,
@@ -2303,9 +2344,9 @@
   AddInterface(kClientAddr);
   allocator_->set_flags(allocator().flags() &
                         ~(PORTALLOCATOR_ENABLE_SHARED_SOCKET));
-  allocator_->SetConfiguration(allocator_->stun_servers(),
-                               allocator_->turn_servers(), pool_size, false,
-                               nullptr, expected_stun_keepalive_interval);
+  allocator_->SetConfiguration(
+      allocator_->stun_servers(), allocator_->turn_servers(), pool_size,
+      webrtc::NO_PRUNE, nullptr, expected_stun_keepalive_interval);
   ASSERT_TRUE(CreateSession(ICE_CANDIDATE_COMPONENT_RTP));
   session_->StartGettingPorts();
   EXPECT_TRUE_SIMULATED_WAIT(candidate_allocation_done_,
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index c0e1831..f019ec9 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -758,6 +758,7 @@
     bool prioritize_most_likely_ice_candidate_pairs;
     struct cricket::MediaConfig media_config;
     bool prune_turn_ports;
+    PortPrunePolicy turn_port_prune_policy;
     bool presume_writable_when_fully_relayed;
     bool enable_ice_renomination;
     bool redetermine_role_on_ice_restart;
@@ -817,6 +818,7 @@
          enable_dtls_srtp == o.enable_dtls_srtp &&
          ice_candidate_pool_size == o.ice_candidate_pool_size &&
          prune_turn_ports == o.prune_turn_ports &&
+         turn_port_prune_policy == o.turn_port_prune_policy &&
          presume_writable_when_fully_relayed ==
              o.presume_writable_when_fully_relayed &&
          enable_ice_renomination == o.enable_ice_renomination &&
@@ -3675,6 +3677,7 @@
   modified_config.ice_candidate_pool_size =
       configuration.ice_candidate_pool_size;
   modified_config.prune_turn_ports = configuration.prune_turn_ports;
+  modified_config.turn_port_prune_policy = configuration.turn_port_prune_policy;
   modified_config.surface_ice_candidates_on_ice_transport_type_changed =
       configuration.surface_ice_candidates_on_ice_transport_type_changed;
   modified_config.ice_check_min_interval = configuration.ice_check_min_interval;
@@ -3746,7 +3749,7 @@
           rtc::Bind(&PeerConnection::ReconfigurePortAllocator_n, this,
                     stun_servers, turn_servers, modified_config.type,
                     modified_config.ice_candidate_pool_size,
-                    modified_config.prune_turn_ports,
+                    modified_config.GetTurnPortPrunePolicy(),
                     modified_config.turn_customizer,
                     modified_config.stun_candidate_keepalive_interval,
                     static_cast<bool>(local_description())))) {
@@ -3759,7 +3762,8 @@
   // triggers an ICE restart which will pick up the changes.
   if (modified_config.servers != configuration_.servers ||
       modified_config.type != configuration_.type ||
-      modified_config.prune_turn_ports != configuration_.prune_turn_ports) {
+      modified_config.GetTurnPortPrunePolicy() !=
+          configuration_.GetTurnPortPrunePolicy()) {
     transport_controller_->SetNeedsIceRestartFlag();
   }
 
@@ -5736,8 +5740,8 @@
   // properties set above.
   port_allocator_->SetConfiguration(
       stun_servers, std::move(turn_servers_copy),
-      configuration.ice_candidate_pool_size, configuration.prune_turn_ports,
-      configuration.turn_customizer,
+      configuration.ice_candidate_pool_size,
+      configuration.GetTurnPortPrunePolicy(), configuration.turn_customizer,
       configuration.stun_candidate_keepalive_interval);
 
   InitializePortAllocatorResult res;
@@ -5750,7 +5754,7 @@
     const std::vector<cricket::RelayServerConfig>& turn_servers,
     IceTransportsType type,
     int candidate_pool_size,
-    bool prune_turn_ports,
+    PortPrunePolicy turn_port_prune_policy,
     webrtc::TurnCustomizer* turn_customizer,
     absl::optional<int> stun_candidate_keepalive_interval,
     bool have_local_description) {
@@ -5771,7 +5775,8 @@
   // candidate filter set above.
   return port_allocator_->SetConfiguration(
       stun_servers, std::move(turn_servers_copy), candidate_pool_size,
-      prune_turn_ports, turn_customizer, stun_candidate_keepalive_interval);
+      turn_port_prune_policy, turn_customizer,
+      stun_candidate_keepalive_interval);
 }
 
 cricket::ChannelManager* PeerConnection::channel_manager() const {
diff --git a/pc/peer_connection.h b/pc/peer_connection.h
index 393a1dd..428c2e8 100644
--- a/pc/peer_connection.h
+++ b/pc/peer_connection.h
@@ -907,7 +907,7 @@
       const std::vector<cricket::RelayServerConfig>& turn_servers,
       IceTransportsType type,
       int candidate_pool_size,
-      bool prune_turn_ports,
+      PortPrunePolicy turn_port_prune_policy,
       webrtc::TurnCustomizer* turn_customizer,
       absl::optional<int> stun_candidate_keepalive_interval,
       bool have_local_description);
diff --git a/pc/peer_connection_interface_unittest.cc b/pc/peer_connection_interface_unittest.cc
index 2d0687c..5a01430 100644
--- a/pc/peer_connection_interface_unittest.cc
+++ b/pc/peer_connection_interface_unittest.cc
@@ -1395,7 +1395,8 @@
   EXPECT_TRUE(raw_port_allocator->flags() & cricket::PORTALLOCATOR_DISABLE_TCP);
   EXPECT_TRUE(raw_port_allocator->flags() &
               cricket::PORTALLOCATOR_DISABLE_COSTLY_NETWORKS);
-  EXPECT_TRUE(raw_port_allocator->prune_turn_ports());
+  EXPECT_EQ(webrtc::PRUNE_BASED_ON_PRIORITY,
+            raw_port_allocator->turn_port_prune_policy());
 }
 
 // Check that GetConfiguration returns the configuration the PeerConnection was
@@ -2448,11 +2449,12 @@
   config.prune_turn_ports = false;
   CreatePeerConnection(config);
   config = pc_->GetConfiguration();
-  EXPECT_FALSE(port_allocator_->prune_turn_ports());
+  EXPECT_EQ(webrtc::NO_PRUNE, port_allocator_->turn_port_prune_policy());
 
   config.prune_turn_ports = true;
   EXPECT_TRUE(pc_->SetConfiguration(config).ok());
-  EXPECT_TRUE(port_allocator_->prune_turn_ports());
+  EXPECT_EQ(webrtc::PRUNE_BASED_ON_PRIORITY,
+            port_allocator_->turn_port_prune_policy());
 }
 
 // Test that the ice check interval can be changed. This does not verify that
diff --git a/sdk/android/api/org/webrtc/PeerConnection.java b/sdk/android/api/org/webrtc/PeerConnection.java
index 7317573..b981520 100644
--- a/sdk/android/api/org/webrtc/PeerConnection.java
+++ b/sdk/android/api/org/webrtc/PeerConnection.java
@@ -407,6 +407,13 @@
   /** Java version of PeerConnectionInterface.ContinualGatheringPolicy */
   public enum ContinualGatheringPolicy { GATHER_ONCE, GATHER_CONTINUALLY }
 
+  /** Java version of webrtc::PortPrunePolicy */
+  public enum PortPrunePolicy {
+    NO_PRUNE, // Do not prune turn port.
+    PRUNE_BASED_ON_PRIORITY, // Prune turn port based the priority on the same network
+    KEEP_FIRST_READY // Keep the first ready port and prune the rest on the same network.
+  }
+
   /** Java version of rtc::IntervalRange */
   public static class IntervalRange {
     private final int min;
@@ -472,7 +479,9 @@
     public KeyType keyType;
     public ContinualGatheringPolicy continualGatheringPolicy;
     public int iceCandidatePoolSize;
+    @Deprecated // by the turnPortPrunePolicy. See bugs.webrtc.org/11026
     public boolean pruneTurnPorts;
+    public PortPrunePolicy turnPortPrunePolicy;
     public boolean presumeWritableWhenFullyRelayed;
     public boolean surfaceIceCandidatesOnIceTransportTypeChanged;
     // The following fields define intervals in milliseconds at which ICE
@@ -583,6 +592,7 @@
       continualGatheringPolicy = ContinualGatheringPolicy.GATHER_ONCE;
       iceCandidatePoolSize = 0;
       pruneTurnPorts = false;
+      turnPortPrunePolicy = PortPrunePolicy.NO_PRUNE;
       presumeWritableWhenFullyRelayed = false;
       surfaceIceCandidatesOnIceTransportTypeChanged = false;
       iceCheckIntervalStrongConnectivityMs = null;
@@ -626,6 +636,11 @@
       return bundlePolicy;
     }
 
+    @CalledByNative("RTCConfiguration")
+    PortPrunePolicy getTurnPortPrunePolicy() {
+      return turnPortPrunePolicy;
+    }
+
     @Nullable
     @CalledByNative("RTCConfiguration")
     RtcCertificatePem getCertificate() {
diff --git a/sdk/android/src/jni/pc/ice_candidate.cc b/sdk/android/src/jni/pc/ice_candidate.cc
index 247e8fa..af92ff8 100644
--- a/sdk/android/src/jni/pc/ice_candidate.cc
+++ b/sdk/android/src/jni/pc/ice_candidate.cc
@@ -194,6 +194,25 @@
   return PeerConnectionInterface::GATHER_ONCE;
 }
 
+webrtc::PortPrunePolicy JavaToNativePortPrunePolicy(
+    JNIEnv* jni,
+    const JavaRef<jobject>& j_port_prune_policy) {
+  std::string enum_name = GetJavaEnumName(jni, j_port_prune_policy);
+  if (enum_name == "NO_PRUNE") {
+    return webrtc::NO_PRUNE;
+  }
+  if (enum_name == "PRUNE_BASED_ON_PRIORITY") {
+    return webrtc::PRUNE_BASED_ON_PRIORITY;
+  }
+  if (enum_name == "KEEP_FIRST_READY") {
+    return webrtc::KEEP_FIRST_READY;
+  }
+
+  RTC_CHECK(false) << " Unexpected PortPrunePolicy enum name " << enum_name;
+
+  return webrtc::NO_PRUNE;
+}
+
 PeerConnectionInterface::TlsCertPolicy JavaToNativeTlsCertPolicy(
     JNIEnv* jni,
     const JavaRef<jobject>& j_ice_server_tls_cert_policy) {
diff --git a/sdk/android/src/jni/pc/ice_candidate.h b/sdk/android/src/jni/pc/ice_candidate.h
index 0feeeb4..4bdeea6 100644
--- a/sdk/android/src/jni/pc/ice_candidate.h
+++ b/sdk/android/src/jni/pc/ice_candidate.h
@@ -71,6 +71,10 @@
     JNIEnv* jni,
     const JavaRef<jobject>& j_gathering_policy);
 
+webrtc::PortPrunePolicy JavaToNativePortPrunePolicy(
+    JNIEnv* jni,
+    const JavaRef<jobject>& j_port_prune_policy);
+
 PeerConnectionInterface::TlsCertPolicy JavaToNativeTlsCertPolicy(
     JNIEnv* jni,
     const JavaRef<jobject>& j_ice_server_tls_cert_policy);
diff --git a/sdk/android/src/jni/pc/peer_connection.cc b/sdk/android/src/jni/pc/peer_connection.cc
index 20804de..f40a7bf 100644
--- a/sdk/android/src/jni/pc/peer_connection.cc
+++ b/sdk/android/src/jni/pc/peer_connection.cc
@@ -158,6 +158,8 @@
       Java_RTCConfiguration_getIceServers(jni, j_rtc_config);
   ScopedJavaLocalRef<jobject> j_continual_gathering_policy =
       Java_RTCConfiguration_getContinualGatheringPolicy(jni, j_rtc_config);
+  ScopedJavaLocalRef<jobject> j_turn_port_prune_policy =
+      Java_RTCConfiguration_getTurnPortPrunePolicy(jni, j_rtc_config);
   ScopedJavaLocalRef<jobject> j_turn_customizer =
       Java_RTCConfiguration_getTurnCustomizer(jni, j_rtc_config);
   ScopedJavaLocalRef<jobject> j_network_preference =
@@ -199,6 +201,8 @@
       Java_RTCConfiguration_getIceCandidatePoolSize(jni, j_rtc_config);
   rtc_config->prune_turn_ports =
       Java_RTCConfiguration_getPruneTurnPorts(jni, j_rtc_config);
+  rtc_config->turn_port_prune_policy =
+      JavaToNativePortPrunePolicy(jni, j_turn_port_prune_policy);
   rtc_config->presume_writable_when_fully_relayed =
       Java_RTCConfiguration_getPresumeWritableWhenFullyRelayed(jni,
                                                                j_rtc_config);
diff --git a/test/peer_scenario/scenario_connection.cc b/test/peer_scenario/scenario_connection.cc
index 2c0ed36..9f0d3bf 100644
--- a/test/peer_scenario/scenario_connection.cc
+++ b/test/peer_scenario/scenario_connection.cc
@@ -109,7 +109,8 @@
     port_allocator_->set_flags(port_allocator_->flags() | flags);
     port_allocator_->Initialize();
     RTC_CHECK(port_allocator_->SetConfiguration(/*stun_servers*/ {},
-                                                /*turn_servers*/ {}, 0, false));
+                                                /*turn_servers*/ {}, 0,
+                                                webrtc::NO_PRUNE));
     jsep_controller_->SetLocalCertificate(certificate_);
   });
 }