API for periodically regathering ICE candidates

Adds to the RTCConfiguration `ice_regather_interval_range` which, when
set, specifies the randomized delay between automatic runs of ICE
regathering. The regathering will occur on all networks and re-use the
existing ICE ufrag/password. New connections are established once the
candidates come back and WebRTC will automatically switch to the new
connection that corresponds to the currently selected connection.

Bug: webrtc:7969
Change-Id: I6bbf5439a48e285f704aed9f408631cba038c82b
Reviewed-on: https://chromium-review.googlesource.com/562505
Reviewed-by: Peter Thatcher <pthatcher@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#18978}
diff --git a/webrtc/api/peerconnectioninterface.h b/webrtc/api/peerconnectioninterface.h
index d6cabf4..6649f2a 100644
--- a/webrtc/api/peerconnectioninterface.h
+++ b/webrtc/api/peerconnectioninterface.h
@@ -453,6 +453,13 @@
     // (STUN pings), in milliseconds.
     rtc::Optional<int> ice_check_min_interval;
 
+
+    // ICE Periodic Regathering
+    // If set, WebRTC will periodically create and propose candidates without
+    // starting a new ICE generation. The regathering happens continuously with
+    // interval specified in milliseconds by the uniform distribution [a, b].
+    rtc::Optional<rtc::IntervalRange> ice_regather_interval_range;
+
     //
     // Don't forget to update operator== if adding something.
     //
diff --git a/webrtc/p2p/base/jseptransport.h b/webrtc/p2p/base/jseptransport.h
index 9353cd7..aee0718 100644
--- a/webrtc/p2p/base/jseptransport.h
+++ b/webrtc/p2p/base/jseptransport.h
@@ -184,6 +184,10 @@
   // active network having no connection on it.
   rtc::Optional<int> regather_on_failed_networks_interval;
 
+  // Interval to perform ICE regathering on all networks
+  // The delay in milliseconds is sampled from the uniform distribution [a, b]
+  rtc::Optional<rtc::IntervalRange> regather_all_networks_interval_range;
+
   // The time period in which we will not switch the selected connection
   // when a new connection becomes receiving but the selected connection is not
   // in case that the selected connection may become receiving soon.
diff --git a/webrtc/p2p/base/p2ptransportchannel.cc b/webrtc/p2p/base/p2ptransportchannel.cc
index 78b4c93..2c9ee13 100644
--- a/webrtc/p2p/base/p2ptransportchannel.cc
+++ b/webrtc/p2p/base/p2ptransportchannel.cc
@@ -12,6 +12,7 @@
 
 #include <algorithm>
 #include <iterator>
+#include <random>
 #include <set>
 
 #include "webrtc/api/umametrics.h"
@@ -32,7 +33,8 @@
 enum {
   MSG_SORT_AND_UPDATE_STATE = 1,
   MSG_CHECK_AND_PING,
-  MSG_REGATHER_ON_FAILED_NETWORKS
+  MSG_REGATHER_ON_FAILED_NETWORKS,
+  MSG_REGATHER_ON_ALL_NETWORKS
 };
 
 // The minimum improvement in RTT that justifies a switch.
@@ -111,6 +113,7 @@
       ice_role_(ICEROLE_UNKNOWN),
       tiebreaker_(0),
       gathering_state_(kIceGatheringNew),
+      rand_(std::random_device()()),
       check_receiving_interval_(MIN_CHECK_RECEIVING_INTERVAL * 5),
       config_(MIN_CHECK_RECEIVING_INTERVAL * 50 /* receiving_timeout */,
               DEFAULT_BACKUP_CONNECTION_PING_INTERVAL,
@@ -428,6 +431,14 @@
     LOG(LS_INFO) << "Set regather_on_failed_networks_interval to "
                  << *config_.regather_on_failed_networks_interval;
   }
+
+  if (config.regather_all_networks_interval_range) {
+    config_.regather_all_networks_interval_range =
+        config.regather_all_networks_interval_range;
+    LOG(LS_INFO) << "Set regather_all_networks_interval_range to "
+                 << config.regather_all_networks_interval_range->ToString();
+  }
+
   if (config.receiving_switching_delay) {
     config_.receiving_switching_delay = config.receiving_switching_delay;
     LOG(LS_INFO) << "Set receiving_switching_delay to"
@@ -1077,6 +1088,11 @@
     thread()->PostDelayed(RTC_FROM_HERE,
                           *config_.regather_on_failed_networks_interval, this,
                           MSG_REGATHER_ON_FAILED_NETWORKS);
+    if (config_.regather_all_networks_interval_range) {
+      thread()->PostDelayed(RTC_FROM_HERE,
+                            SampleRegatherAllNetworksInterval(), this,
+                            MSG_REGATHER_ON_ALL_NETWORKS);
+    }
     started_pinging_ = true;
   }
 }
@@ -1182,8 +1198,39 @@
 
   // If we're still tied at this point, prefer a younger generation.
   // (Younger generation means a larger generation number).
-  return (a->remote_candidate().generation() + a->port()->generation()) -
-         (b->remote_candidate().generation() + b->port()->generation());
+  int cmp = (a->remote_candidate().generation() + a->port()->generation()) -
+            (b->remote_candidate().generation() + b->port()->generation());
+  if (cmp != 0) {
+    return cmp;
+  }
+
+  // A periodic regather (triggered by the regather_all_networks_interval_range)
+  // will produce candidates that appear the same but would use a new port. We
+  // want to use the new candidates and purge the old candidates as they come
+  // in, so use the fact that the old ports get pruned immediately to rank the
+  // candidates with an active port/remote candidate higher.
+  bool a_pruned = IsPortPruned(a->port()) ||
+      IsRemoteCandidatePruned(a->remote_candidate());
+  bool b_pruned = IsPortPruned(b->port()) ||
+      IsRemoteCandidatePruned(b->remote_candidate());
+  if (!a_pruned && b_pruned) {
+    return a_is_better;
+  }
+  if (a_pruned && !b_pruned) {
+    return b_is_better;
+  }
+
+  // Otherwise, must be equal
+  return 0;
+}
+
+bool P2PTransportChannel::IsPortPruned(const Port* port) const {
+  return std::find(ports_.begin(), ports_.end(), port) == ports_.end();
+}
+
+bool P2PTransportChannel::IsRemoteCandidatePruned(const Candidate& cand) const {
+  return std::find(remote_candidates_.begin(), remote_candidates_.end(), cand)
+      == remote_candidates_.end();
 }
 
 int P2PTransportChannel::CompareConnections(
@@ -1528,6 +1575,9 @@
     case MSG_REGATHER_ON_FAILED_NETWORKS:
       OnRegatherOnFailedNetworks();
       break;
+    case MSG_REGATHER_ON_ALL_NETWORKS:
+      OnRegatherOnAllNetworks();
+      break;
     default:
       RTC_NOTREACHED();
       break;
@@ -1913,6 +1963,16 @@
                         MSG_REGATHER_ON_FAILED_NETWORKS);
 }
 
+void P2PTransportChannel::OnRegatherOnAllNetworks() {
+  if (!allocator_sessions_.empty() && allocator_session()->IsCleared()) {
+    allocator_session()->RegatherOnAllNetworks();
+  }
+
+  thread()->PostDelayed(RTC_FROM_HERE,
+                        SampleRegatherAllNetworksInterval(), this,
+                        MSG_REGATHER_ON_ALL_NETWORKS);
+}
+
 void P2PTransportChannel::PruneAllPorts() {
   pruned_ports_.insert(pruned_ports_.end(), ports_.begin(), ports_.end());
   ports_.clear();
@@ -2066,4 +2126,10 @@
   SignalReceivingState(this);
 }
 
+int P2PTransportChannel::SampleRegatherAllNetworksInterval() {
+  auto interval = config_.regather_all_networks_interval_range;
+  RTC_DCHECK(interval);
+  return rand_.Rand(interval->min(), interval->max());
+}
+
 }  // namespace cricket
diff --git a/webrtc/p2p/base/p2ptransportchannel.h b/webrtc/p2p/base/p2ptransportchannel.h
index beca558..d2461de 100644
--- a/webrtc/p2p/base/p2ptransportchannel.h
+++ b/webrtc/p2p/base/p2ptransportchannel.h
@@ -33,6 +33,7 @@
 #include "webrtc/p2p/base/portinterface.h"
 #include "webrtc/rtc_base/asyncpacketsocket.h"
 #include "webrtc/rtc_base/constructormagic.h"
+#include "webrtc/rtc_base/random.h"
 #include "webrtc/rtc_base/sigslot.h"
 
 namespace cricket {
@@ -280,6 +281,7 @@
   void OnMessage(rtc::Message* pmsg) override;
   void OnCheckAndPing();
   void OnRegatherOnFailedNetworks();
+  void OnRegatherOnAllNetworks();
 
   uint32_t GetNominationAttr(Connection* conn) const;
   bool GetUseCandidateAttr(Connection* conn, NominationMode mode) const;
@@ -328,6 +330,16 @@
                : static_cast<uint32_t>(remote_ice_parameters_.size() - 1);
   }
 
+  // Samples a delay from the uniform distribution defined by the
+  // regather_on_all_networks_interval ICE configuration pair.
+  int SampleRegatherAllNetworksInterval();
+
+  // Indicates if the given local port has been pruned.
+  bool IsPortPruned(const Port* port) const;
+
+  // Indicates if the given remote candidate has been pruned.
+  bool IsRemoteCandidatePruned(const Candidate& cand) const;
+
   // Sets the writable state, signaling if necessary.
   void set_writable(bool writable);
   // Sets the receiving state, signaling if necessary.
@@ -372,6 +384,9 @@
   uint64_t tiebreaker_;
   IceGatheringState gathering_state_;
 
+  // Used to generate random intervals for regather_all_networks_interval_range.
+  webrtc::Random rand_;
+
   int check_receiving_interval_;
   int64_t last_ping_sent_ms_ = 0;
   int weak_ping_interval_ = WEAK_PING_INTERVAL;
diff --git a/webrtc/p2p/base/p2ptransportchannel_unittest.cc b/webrtc/p2p/base/p2ptransportchannel_unittest.cc
index 7b3fa6a..c37b818 100644
--- a/webrtc/p2p/base/p2ptransportchannel_unittest.cc
+++ b/webrtc/p2p/base/p2ptransportchannel_unittest.cc
@@ -1362,6 +1362,106 @@
   DestroyChannels();
 }
 
+// Tests that ICE regathering occurs regularly when
+// regather_all_networks_interval_range configuration value is set.
+TEST_F(P2PTransportChannelTest, TestIceRegatherOnAllNetworksContinual) {
+  rtc::ScopedFakeClock clock;
+  ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+
+  // ep1 gathers continually but ep2 does not.
+  const int kRegatherInterval = 2000;
+  IceConfig config1 = CreateIceConfig(1000, GATHER_CONTINUALLY);
+  config1.regather_all_networks_interval_range.emplace(
+      kRegatherInterval, kRegatherInterval);
+  IceConfig config2;
+  config2.regather_all_networks_interval_range.emplace(
+      kRegatherInterval, kRegatherInterval);
+  CreateChannels(config1, config2);
+
+  EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
+                                 ep2_ch1()->receiving() &&
+                                 ep2_ch1()->writable(),
+                             kDefaultTimeout, clock);
+
+  fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]);
+  // Timeout value such that all connections are deleted.
+  const int kNetworkGatherDuration = 11000;
+  SIMULATED_WAIT(false, kNetworkGatherDuration, clock);
+  // Expect regathering to happen 5 times in 11s with 2s interval.
+  EXPECT_LE(5, GetMetricsObserver(0)->GetEnumCounter(
+                   webrtc::kEnumCounterIceRegathering,
+                   static_cast<int>(IceRegatheringReason::OCCASIONAL_REFRESH)));
+  // Expect no regathering if continual gathering not configured.
+  EXPECT_EQ(0, GetMetricsObserver(1)->GetEnumCounter(
+                   webrtc::kEnumCounterIceRegathering,
+                   static_cast<int>(IceRegatheringReason::OCCASIONAL_REFRESH)));
+
+  DestroyChannels();
+}
+
+// Test that ICE periodic regathering can change the selected connection on the
+// specified interval and that the peers can communicate over the new
+// connection. The test is parameterized to test that it works when regathering
+// is done by the ICE controlling peer and when done by the controlled peer.
+class P2PTransportRegatherAllNetworksTest : public P2PTransportChannelTest {
+ protected:
+  void TestWithRoles(IceRole p1_role, IceRole p2_role) {
+    rtc::ScopedFakeClock clock;
+    ConfigureEndpoints(NAT_SYMMETRIC, NAT_SYMMETRIC, kDefaultPortAllocatorFlags,
+        kDefaultPortAllocatorFlags);
+    set_force_relay(true);
+
+    const int kRegatherInterval = 2000;
+    const int kNumRegathers = 2;
+
+    // Set up peer 1 to auto regather every 2s.
+    IceConfig config1 = CreateIceConfig(1000, GATHER_CONTINUALLY);
+    config1.regather_all_networks_interval_range.emplace(
+        kRegatherInterval, kRegatherInterval);
+    IceConfig config2 = CreateIceConfig(1000, GATHER_CONTINUALLY);
+
+    // Set peer roles.
+    SetIceRole(0, p1_role);
+    SetIceRole(1, p2_role);
+
+    CreateChannels(config1, config2);
+
+    // Wait for initial connection to be made.
+    EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() &&
+                                   ep1_ch1()->writable() &&
+                                   ep2_ch1()->receiving() &&
+                                   ep2_ch1()->writable(),
+                               kMediumTimeout, clock);
+
+    const Connection* initial_selected = ep1_ch1()->selected_connection();
+
+    // Wait long enough for 2 regathering cycles to happen plus some extra so
+    // the new connection has time to settle.
+    const int kWaitRegather =
+        kRegatherInterval * kNumRegathers + kRegatherInterval / 2;
+    SIMULATED_WAIT(false, kWaitRegather, clock);
+    EXPECT_EQ(kNumRegathers, GetMetricsObserver(0)->GetEnumCounter(
+        webrtc::kEnumCounterIceRegathering,
+        static_cast<int>(IceRegatheringReason::OCCASIONAL_REFRESH)));
+
+    const Connection* new_selected = ep1_ch1()->selected_connection();
+
+    // Want the new selected connection to be different.
+    ASSERT_NE(initial_selected, new_selected);
+
+    // Make sure we can communicate over the new connection too.
+    TestSendRecv(clock);
+  }
+};
+
+TEST_F(P2PTransportRegatherAllNetworksTest, TestControlling) {
+  TestWithRoles(ICEROLE_CONTROLLING, ICEROLE_CONTROLLED);
+}
+
+TEST_F(P2PTransportRegatherAllNetworksTest, TestControlled) {
+  TestWithRoles(ICEROLE_CONTROLLED, ICEROLE_CONTROLLING);
+}
+
 // Test that we properly create a connection on a STUN ping from unknown address
 // when the signaling is slow.
 TEST_F(P2PTransportChannelTest, PeerReflexiveCandidateBeforeSignaling) {
diff --git a/webrtc/p2p/base/portallocator.h b/webrtc/p2p/base/portallocator.h
index 74b67af..9540e40 100644
--- a/webrtc/p2p/base/portallocator.h
+++ b/webrtc/p2p/base/portallocator.h
@@ -89,7 +89,12 @@
 };
 
 // Defines various reasons that have caused ICE regathering.
-enum class IceRegatheringReason { NETWORK_CHANGE, NETWORK_FAILURE, MAX_VALUE };
+enum class IceRegatheringReason {
+  NETWORK_CHANGE,      // Network interfaces on the device changed
+  NETWORK_FAILURE,     // Regather only on networks that have failed
+  OCCASIONAL_REFRESH,  // Periodic regather on all networks
+  MAX_VALUE
+};
 
 const uint32_t kDefaultPortAllocatorFlags = 0;
 
@@ -238,7 +243,6 @@
   // implementation should start re-gathering on all networks of that interface.
   virtual void RegatherOnFailedNetworks() {}
   // Re-gathers candidates on all networks.
-  // TODO(honghaiz): Implement this in BasicPortAllocator.
   virtual void RegatherOnAllNetworks() {}
 
   // Another way of getting the information provided by the signals below.
diff --git a/webrtc/p2p/client/basicportallocator.cc b/webrtc/p2p/client/basicportallocator.cc
index 5ddc475..2970987 100644
--- a/webrtc/p2p/client/basicportallocator.cc
+++ b/webrtc/p2p/client/basicportallocator.cc
@@ -339,19 +339,43 @@
       sequence->set_network_failed();
     }
   }
+
+  bool disable_equivalent_phases = true;
+  Regather(failed_networks, disable_equivalent_phases,
+           IceRegatheringReason::NETWORK_FAILURE);
+}
+
+void BasicPortAllocatorSession::RegatherOnAllNetworks() {
+  std::vector<rtc::Network*> networks = GetNetworks();
+  if (networks.empty()) {
+    return;
+  }
+
+  LOG(LS_INFO) << "Regather candidates on all networks";
+
+  // We expect to generate candidates that are equivalent to what we have now.
+  // Force DoAllocate to generate them instead of skipping.
+  bool disable_equivalent_phases = false;
+  Regather(networks, disable_equivalent_phases,
+           IceRegatheringReason::OCCASIONAL_REFRESH);
+}
+
+void BasicPortAllocatorSession::Regather(
+    const std::vector<rtc::Network*>& networks,
+    bool disable_equivalent_phases,
+    IceRegatheringReason reason) {
   // Remove ports from being used locally and send signaling to remove
   // the candidates on the remote side.
-  std::vector<PortData*> ports_to_prune = GetUnprunedPorts(failed_networks);
+  std::vector<PortData*> ports_to_prune = GetUnprunedPorts(networks);
   if (!ports_to_prune.empty()) {
-    LOG(LS_INFO) << "Prune " << ports_to_prune.size()
-                 << " ports because their networks failed";
+    LOG(LS_INFO) << "Prune " << ports_to_prune.size() << " ports";
     PrunePortsAndRemoveCandidates(ports_to_prune);
   }
 
   if (allocation_started_ && network_manager_started_ && !IsStopped()) {
-    SignalIceRegathering(this, IceRegatheringReason::NETWORK_FAILURE);
+    SignalIceRegathering(this, reason);
 
-    DoAllocate();
+    DoAllocate(disable_equivalent_phases);
   }
 }
 
@@ -530,8 +554,10 @@
 }
 
 void BasicPortAllocatorSession::OnAllocate() {
-  if (network_manager_started_ && !IsStopped())
-    DoAllocate();
+  if (network_manager_started_ && !IsStopped()) {
+    bool disable_equivalent_phases = true;
+    DoAllocate(disable_equivalent_phases);
+  }
 
   allocation_started_ = true;
 }
@@ -586,7 +612,7 @@
 
 // For each network, see if we have a sequence that covers it already.  If not,
 // create a new sequence to create the appropriate ports.
-void BasicPortAllocatorSession::DoAllocate() {
+void BasicPortAllocatorSession::DoAllocate(bool disable_equivalent) {
   bool done_signal_needed = false;
   std::vector<rtc::Network*> networks = GetNetworks();
   if (networks.empty()) {
@@ -622,13 +648,15 @@
         continue;
       }
 
-      // Disable phases that would only create ports equivalent to
-      // ones that we have already made.
-      DisableEquivalentPhases(networks[i], config, &sequence_flags);
+      if (disable_equivalent) {
+        // Disable phases that would only create ports equivalent to
+        // ones that we have already made.
+        DisableEquivalentPhases(networks[i], config, &sequence_flags);
 
-      if ((sequence_flags & DISABLE_ALL_PHASES) == DISABLE_ALL_PHASES) {
-        // New AllocationSequence would have nothing to do, so don't make it.
-        continue;
+        if ((sequence_flags & DISABLE_ALL_PHASES) == DISABLE_ALL_PHASES) {
+          // New AllocationSequence would have nothing to do, so don't make it.
+          continue;
+        }
       }
 
       AllocationSequence* sequence =
@@ -671,7 +699,8 @@
       // If the network manager has started, it must be regathering.
       SignalIceRegathering(this, IceRegatheringReason::NETWORK_CHANGE);
     }
-    DoAllocate();
+    bool disable_equivalent_phases = true;
+    DoAllocate(disable_equivalent_phases);
   }
 
   if (!network_manager_started_) {
diff --git a/webrtc/p2p/client/basicportallocator.h b/webrtc/p2p/client/basicportallocator.h
index 7cdf0ba..a991417 100644
--- a/webrtc/p2p/client/basicportallocator.h
+++ b/webrtc/p2p/client/basicportallocator.h
@@ -112,6 +112,7 @@
   std::vector<Candidate> ReadyCandidates() const override;
   bool CandidatesAllocationDone() const override;
   void RegatherOnFailedNetworks() override;
+  void RegatherOnAllNetworks() override;
   void PruneAllPorts() override;
 
  protected:
@@ -185,7 +186,7 @@
   void OnConfigStop();
   void AllocatePorts();
   void OnAllocate();
-  void DoAllocate();
+  void DoAllocate(bool disable_equivalent_phases);
   void OnNetworksChanged();
   void OnAllocationSequenceObjectsCreated();
   void DisableEquivalentPhases(rtc::Network* network,
@@ -203,6 +204,9 @@
   PortData* FindPort(Port* port);
   std::vector<rtc::Network*> GetNetworks();
   std::vector<rtc::Network*> GetFailedNetworks();
+  void Regather(const std::vector<rtc::Network*>& networks,
+                bool disable_equivalent_phases,
+                IceRegatheringReason reason);
 
   bool CheckCandidateFilter(const Candidate& c) const;
   bool CandidatePairable(const Candidate& c, const Port* port) const;
diff --git a/webrtc/pc/peerconnection.cc b/webrtc/pc/peerconnection.cc
index a947c30..42664b0 100644
--- a/webrtc/pc/peerconnection.cc
+++ b/webrtc/pc/peerconnection.cc
@@ -223,11 +223,20 @@
 bool PeerConnectionInterface::RTCConfiguration::operator==(
     const PeerConnectionInterface::RTCConfiguration& o) const {
   // This static_assert prevents us from accidentally breaking operator==.
+  // Note: Order matters! Fields must be ordered the same as RTCConfiguration.
   struct stuff_being_tested_for_equality {
-    IceTransportsType type;
     IceServers servers;
+    IceTransportsType type;
     BundlePolicy bundle_policy;
     RtcpMuxPolicy rtcp_mux_policy;
+    std::vector<rtc::scoped_refptr<rtc::RTCCertificate>> certificates;
+    int ice_candidate_pool_size;
+    bool disable_ipv6;
+    bool disable_ipv6_on_wifi;
+    bool enable_rtp_data_channel;
+    rtc::Optional<int> screencast_min_bitrate;
+    rtc::Optional<bool> combined_audio_video_bwe;
+    rtc::Optional<bool> enable_dtls_srtp;
     TcpCandidatePolicy tcp_candidate_policy;
     CandidateNetworkPolicy candidate_network_policy;
     int audio_jitter_buffer_max_packets;
@@ -235,22 +244,15 @@
     int ice_connection_receiving_timeout;
     int ice_backup_candidate_pair_ping_interval;
     ContinualGatheringPolicy continual_gathering_policy;
-    std::vector<rtc::scoped_refptr<rtc::RTCCertificate>> certificates;
     bool prioritize_most_likely_ice_candidate_pairs;
     struct cricket::MediaConfig media_config;
-    bool disable_ipv6;
-    bool disable_ipv6_on_wifi;
-    bool enable_rtp_data_channel;
     bool enable_quic;
-    rtc::Optional<int> screencast_min_bitrate;
-    rtc::Optional<bool> combined_audio_video_bwe;
-    rtc::Optional<bool> enable_dtls_srtp;
-    int ice_candidate_pool_size;
     bool prune_turn_ports;
     bool presume_writable_when_fully_relayed;
     bool enable_ice_renomination;
     bool redetermine_role_on_ice_restart;
     rtc::Optional<int> ice_check_min_interval;
+    rtc::Optional<rtc::IntervalRange> ice_regather_interval_range;
   };
   static_assert(sizeof(stuff_being_tested_for_equality) == sizeof(*this),
                 "Did you add something to RTCConfiguration and forget to "
@@ -284,7 +286,8 @@
              o.presume_writable_when_fully_relayed &&
          enable_ice_renomination == o.enable_ice_renomination &&
          redetermine_role_on_ice_restart == o.redetermine_role_on_ice_restart &&
-         ice_check_min_interval == o.ice_check_min_interval;
+         ice_check_min_interval == o.ice_check_min_interval &&
+         ice_regather_interval_range == o.ice_regather_interval_range;
 }
 
 bool PeerConnectionInterface::RTCConfiguration::operator!=(
diff --git a/webrtc/pc/webrtcsession.cc b/webrtc/pc/webrtcsession.cc
index 4ca4528..b4b30a0 100644
--- a/webrtc/pc/webrtcsession.cc
+++ b/webrtc/pc/webrtcsession.cc
@@ -1222,6 +1222,8 @@
   ice_config.presume_writable_when_fully_relayed =
       config.presume_writable_when_fully_relayed;
   ice_config.ice_check_min_interval = config.ice_check_min_interval;
+  ice_config.regather_all_networks_interval_range =
+      config.ice_regather_interval_range;
   return ice_config;
 }
 
diff --git a/webrtc/rtc_base/timeutils.h b/webrtc/rtc_base/timeutils.h
index ea7b17d..c7f035c 100644
--- a/webrtc/rtc_base/timeutils.h
+++ b/webrtc/rtc_base/timeutils.h
@@ -15,6 +15,9 @@
 #include <time.h>
 
 #include <ctime>
+#include <string>
+
+#include "webrtc/rtc_base/checks.h"
 
 namespace rtc {
 
@@ -124,6 +127,32 @@
 // measuring time intervals and timeouts.
 int64_t TimeUTCMicros();
 
+// Interval of time from the range [min, max] inclusive.
+class IntervalRange {
+ public:
+  IntervalRange() : min_(0), max_(0) {}
+  IntervalRange(int min, int max) : min_(min), max_(max) {
+    RTC_DCHECK_LE(min, max);
+  }
+
+  int min() const { return min_; }
+  int max() const { return max_; }
+
+  std::string ToString() const {
+    std::stringstream ss;
+    ss << "[" << min_ << "," << max_ << "]";
+    return ss.str();
+  }
+
+  bool operator==(const IntervalRange& o) const {
+    return min_ == o.min_ && max_ == o.max_;
+  }
+
+ private:
+  int min_;
+  int max_;
+};
+
 }  // namespace rtc
 
 #endif  // WEBRTC_RTC_BASE_TIMEUTILS_H_