Reland "Surface ICE candidates that match an updated candidate filter."
This is a reland of cd8d1cf68e4eeed71fba51c97006a91bfd41813d
Original change's description:
> Surface ICE candidates that match an updated candidate filter.
>
> After this change an ICE agent can surface candidates that do not match
> the previous filter but are allowed by the updated one. The candidate
> filter, as part of the internal implementation in the ICE transport,
> manifests the RTCIceTransportPolicy field in RTCConfiguration.
>
> This new feature would allow an ICE agent to gather new candidates when
> the transport policy changes from e.g. 'relay' to 'all' without an ICE
> restart.
>
> A caveat in the current implementation remains, and a candidate can
> surface multiple times if the transport policy, or the candidate filter
> directly, performs multiple transitions from a value that disallows to
> one that allows the underlying candidate type. For example, if the
> transport policy is updated by 'all' -> 'relay' -> 'all', the same host
> candidate can surface after the second update.
>
>
> Bug: webrtc:8939
> Change-Id: I92c2e07dafab225c702c5de28f47958a0d3270cc
> Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/132282
> Commit-Queue: Qingsi Wang <qingsi@webrtc.org>
> Reviewed-by: Jeroen de Borst <jeroendb@webrtc.org>
> Reviewed-by: Seth Hampson <shampson@webrtc.org>
> Cr-Commit-Position: refs/heads/master@{#27674}
Bug: webrtc:8939
Change-Id: I9c32b1ea05028ecd937ab4912779dd958faf734f
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/133582
Reviewed-by: Seth Hampson <shampson@webrtc.org>
Reviewed-by: Jeroen de Borst <jeroendb@webrtc.org>
Commit-Queue: Qingsi Wang <qingsi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27694}
diff --git a/p2p/client/basic_port_allocator.cc b/p2p/client/basic_port_allocator.cc
index 83c8bf2..4f418ee 100644
--- a/p2p/client/basic_port_allocator.cc
+++ b/p2p/client/basic_port_allocator.cc
@@ -31,6 +31,7 @@
using rtc::CreateRandomId;
+namespace cricket {
namespace {
enum {
@@ -112,9 +113,37 @@
networks->erase(start_to_remove, networks->end());
}
+bool IsAllowedByCandidateFilter(const Candidate& c, uint32_t filter) {
+ // When binding to any address, before sending packets out, the getsockname
+ // returns all 0s, but after sending packets, it'll be the NIC used to
+ // send. All 0s is not a valid ICE candidate address and should be filtered
+ // out.
+ if (c.address().IsAnyIP()) {
+ return false;
+ }
+
+ if (c.type() == RELAY_PORT_TYPE) {
+ return ((filter & CF_RELAY) != 0);
+ } else if (c.type() == STUN_PORT_TYPE) {
+ return ((filter & CF_REFLEXIVE) != 0);
+ } else if (c.type() == LOCAL_PORT_TYPE) {
+ if ((filter & CF_REFLEXIVE) && !c.address().IsPrivateIP()) {
+ // We allow host candidates if the filter allows server-reflexive
+ // candidates and the candidate is a public IP. Because we don't generate
+ // server-reflexive candidates if they have the same IP as the host
+ // candidate (i.e. when the host candidate is a public IP), filtering to
+ // only server-reflexive candidates won't work right when the host
+ // candidates have public IPs.
+ return true;
+ }
+
+ return ((filter & CF_HOST) != 0);
+ }
+ return false;
+}
+
} // namespace
-namespace cricket {
const uint32_t DISABLE_ALL_PHASES =
PORTALLOCATOR_DISABLE_UDP | PORTALLOCATOR_DISABLE_TCP |
PORTALLOCATOR_DISABLE_STUN | PORTALLOCATOR_DISABLE_RELAY;
@@ -307,20 +336,57 @@
if (filter == candidate_filter_) {
return;
}
- // We assume the filter will only change from "ALL" to something else.
- RTC_DCHECK(candidate_filter_ == CF_ALL);
+ uint32_t prev_filter = candidate_filter_;
candidate_filter_ = filter;
- for (PortData& port : ports_) {
- if (!port.has_pairable_candidate()) {
+ for (PortData& port_data : ports_) {
+ if (port_data.error() || port_data.pruned()) {
continue;
}
+ PortData::State cur_state = port_data.state();
+ bool found_signalable_candidate = false;
+ bool found_pairable_candidate = false;
+ cricket::Port* port = port_data.port();
+ for (const auto& c : port->Candidates()) {
+ if (!IsStopped() && !IsAllowedByCandidateFilter(c, prev_filter) &&
+ IsAllowedByCandidateFilter(c, filter)) {
+ // This candidate was not signaled because of not matching the previous
+ // filter (see OnCandidateReady below). Let the Port to fire the signal
+ // again.
+ //
+ // Note that
+ // 1) we would need the Port to enter the state of in-progress of
+ // gathering to have candidates signaled;
+ //
+ // 2) firing the signal would also let the session set the port ready
+ // if needed, so that we could form candidate pairs with candidates
+ // from this port;
+ //
+ // * See again OnCandidateReady below for 1) and 2).
+ //
+ // 3) we only try to resurface candidates if we have not stopped
+ // getting ports, which is always true for the continual gathering.
+ if (!found_signalable_candidate) {
+ found_signalable_candidate = true;
+ port_data.set_state(PortData::STATE_INPROGRESS);
+ }
+ port->SignalCandidateReady(port, c);
+ }
+
+ if (CandidatePairable(c, port)) {
+ found_pairable_candidate = true;
+ }
+ }
+ // Restore the previous state.
+ port_data.set_state(cur_state);
// Setting a filter may cause a ready port to become non-ready
// if it no longer has any pairable candidates.
- if (absl::c_none_of(port.port()->Candidates(),
- [this, &port](const Candidate& candidate) {
- return CandidatePairable(candidate, port.port());
- })) {
- port.set_has_pairable_candidate(false);
+ //
+ // Note that we only set for the negative case here, since a port would be
+ // set to have pairable candidates when it signals a ready candidate, which
+ // requires the port is still in the progress of gathering/surfacing
+ // candidates, and would be done in the firing of the signal above.
+ if (!found_pairable_candidate) {
+ port_data.set_has_pairable_candidate(false);
}
}
}
@@ -597,6 +663,7 @@
void BasicPortAllocatorSession::GetPortConfigurations() {
RTC_DCHECK_RUN_ON(network_thread_);
+
PortConfiguration* config =
new PortConfiguration(allocator_->stun_servers(), username(), password());
@@ -633,7 +700,7 @@
if (it->inprogress()) {
// Updating port state to error, which didn't finish allocating candidates
// yet.
- it->set_error();
+ it->set_state(PortData::STATE_ERROR);
send_signal = true;
}
}
@@ -1025,7 +1092,7 @@
}
// Moving to COMPLETE state.
- data->set_complete();
+ data->set_state(PortData::STATE_COMPLETE);
// Send candidate allocation complete signal if this was the last port.
MaybeSignalCandidatesAllocationDone();
}
@@ -1043,7 +1110,7 @@
// SignalAddressError is currently sent from StunPort/TurnPort.
// But this signal itself is generic.
- data->set_error();
+ data->set_state(PortData::STATE_ERROR);
// Send candidate allocation complete signal if this was the last port.
MaybeSignalCandidatesAllocationDone();
}
@@ -1051,34 +1118,7 @@
bool BasicPortAllocatorSession::CheckCandidateFilter(const Candidate& c) const {
RTC_DCHECK_RUN_ON(network_thread_);
- uint32_t filter = candidate_filter_;
-
- // When binding to any address, before sending packets out, the getsockname
- // returns all 0s, but after sending packets, it'll be the NIC used to
- // send. All 0s is not a valid ICE candidate address and should be filtered
- // out.
- if (c.address().IsAnyIP()) {
- return false;
- }
-
- if (c.type() == RELAY_PORT_TYPE) {
- return ((filter & CF_RELAY) != 0);
- } else if (c.type() == STUN_PORT_TYPE) {
- return ((filter & CF_REFLEXIVE) != 0);
- } else if (c.type() == LOCAL_PORT_TYPE) {
- if ((filter & CF_REFLEXIVE) && !c.address().IsPrivateIP()) {
- // We allow host candidates if the filter allows server-reflexive
- // candidates and the candidate is a public IP. Because we don't generate
- // server-reflexive candidates if they have the same IP as the host
- // candidate (i.e. when the host candidate is a public IP), filtering to
- // only server-reflexive candidates won't work right when the host
- // candidates have public IPs.
- return true;
- }
-
- return ((filter & CF_HOST) != 0);
- }
- return false;
+ return IsAllowedByCandidateFilter(c, candidate_filter_);
}
bool BasicPortAllocatorSession::CandidatePairable(const Candidate& c,
@@ -1259,25 +1299,39 @@
// This can happen if, say, there's a network change event right before an
// application-triggered ICE restart. Hopefully this problem will just go
// away if we get rid of the gathering "phases" though, which is planned.
+ //
+ //
+ // PORTALLOCATOR_DISABLE_UDP is used to disable a Port from gathering the host
+ // candidate (and srflx candidate if Port::SharedSocket()), and we do not want
+ // to disable the gathering of these candidates just becaue of an existing
+ // Port over PROTO_UDP, namely a TurnPort over UDP.
if (absl::c_any_of(session_->ports_,
[this](const BasicPortAllocatorSession::PortData& p) {
- return p.port()->Network() == network_ &&
+ return !p.pruned() && p.port()->Network() == network_ &&
p.port()->GetProtocol() == PROTO_UDP &&
- !p.error();
+ p.port()->Type() == LOCAL_PORT_TYPE && !p.error();
})) {
*flags |= PORTALLOCATOR_DISABLE_UDP;
}
+ // Similarly we need to check both the protocol used by an existing Port and
+ // its type.
if (absl::c_any_of(session_->ports_,
[this](const BasicPortAllocatorSession::PortData& p) {
- return p.port()->Network() == network_ &&
+ return !p.pruned() && p.port()->Network() == network_ &&
p.port()->GetProtocol() == PROTO_TCP &&
- !p.error();
+ p.port()->Type() == LOCAL_PORT_TYPE && !p.error();
})) {
*flags |= PORTALLOCATOR_DISABLE_TCP;
}
if (config_ && config) {
- if (config_->StunServers() == config->StunServers()) {
+ // We need to regather srflx candidates if either of the following
+ // conditions occurs:
+ // 1. The STUN servers are different from the previous gathering.
+ // 2. We will regather host candidates, hence possibly inducing new NAT
+ // bindings.
+ if (config_->StunServers() == config->StunServers() &&
+ (*flags & PORTALLOCATOR_DISABLE_UDP)) {
// Already got this STUN servers covered.
*flags |= PORTALLOCATOR_DISABLE_STUN;
}
diff --git a/p2p/client/basic_port_allocator.h b/p2p/client/basic_port_allocator.h
index edc6b87..26eea1e 100644
--- a/p2p/client/basic_port_allocator.h
+++ b/p2p/client/basic_port_allocator.h
@@ -125,6 +125,15 @@
rtc::Thread* network_thread() { return network_thread_; }
rtc::PacketSocketFactory* socket_factory() { return socket_factory_; }
+ // If the new filter allows new types of candidates compared to the previous
+ // filter, gathered candidates that were discarded because of not matching the
+ // previous filter will be signaled if they match the new one.
+ //
+ // We do not perform any regathering since the port allocator flags decide
+ // the type of candidates to gather and the candidate filter only controls the
+ // signaling of candidates. As a result, with the candidate filter changed
+ // alone, all newly allowed candidates for signaling should already be
+ // gathered by the respective cricket::Port.
void SetCandidateFilter(uint32_t filter) override;
void StartGettingPorts() override;
void StopGettingPorts() override;
@@ -158,6 +167,14 @@
private:
class PortData {
public:
+ enum State {
+ STATE_INPROGRESS, // Still gathering candidates.
+ STATE_COMPLETE, // All candidates allocated and ready for process.
+ STATE_ERROR, // Error in gathering candidates.
+ STATE_PRUNED // Pruned by higher priority ports on the same network
+ // interface. Only TURN ports may be pruned.
+ };
+
PortData() {}
PortData(Port* port, AllocationSequence* seq)
: port_(port), sequence_(seq) {}
@@ -165,6 +182,7 @@
Port* port() const { return port_; }
AllocationSequence* sequence() const { return sequence_; }
bool has_pairable_candidate() const { return has_pairable_candidate_; }
+ State state() const { return state_; }
bool complete() const { return state_ == STATE_COMPLETE; }
bool error() const { return state_ == STATE_ERROR; }
bool pruned() const { return state_ == STATE_PRUNED; }
@@ -187,20 +205,12 @@
}
has_pairable_candidate_ = has_pairable_candidate;
}
- void set_complete() { state_ = STATE_COMPLETE; }
- void set_error() {
- RTC_DCHECK(state_ == STATE_INPROGRESS);
- state_ = STATE_ERROR;
+ void set_state(State state) {
+ RTC_DCHECK(state != STATE_ERROR || state_ == STATE_INPROGRESS);
+ state_ = state;
}
private:
- enum State {
- STATE_INPROGRESS, // Still gathering candidates.
- STATE_COMPLETE, // All candidates allocated and ready for process.
- STATE_ERROR, // Error in gathering candidates.
- STATE_PRUNED // Pruned by higher priority ports on the same network
- // interface. Only TURN ports may be pruned.
- };
Port* port_ = nullptr;
AllocationSequence* sequence_ = nullptr;
bool has_pairable_candidate_ = false;
diff --git a/p2p/client/basic_port_allocator_unittest.cc b/p2p/client/basic_port_allocator_unittest.cc
index 1682e9d..067e757 100644
--- a/p2p/client/basic_port_allocator_unittest.cc
+++ b/p2p/client/basic_port_allocator_unittest.cc
@@ -1266,7 +1266,7 @@
TEST_F(BasicPortAllocatorTest,
TestDisableAdapterEnumerationWithoutNatRelayTransportOnly) {
ResetWithStunServerNoNat(kStunAddr);
- allocator().set_candidate_filter(CF_RELAY);
+ allocator().SetCandidateFilter(CF_RELAY);
// Expect to see no ports and no candidates.
CheckDisableAdapterEnumeration(0U, rtc::IPAddress(), rtc::IPAddress(),
rtc::IPAddress(), rtc::IPAddress());
@@ -1527,7 +1527,7 @@
AddInterface(kClientAddr);
ASSERT_TRUE(CreateSession(ICE_CANDIDATE_COMPONENT_RTP));
// Set candidate filter *after* creating the session. Should have no effect.
- allocator().set_candidate_filter(CF_RELAY);
+ allocator().SetCandidateFilter(CF_RELAY);
session_->StartGettingPorts();
// 7 candidates and 4 ports is what we would normally get (see the
// TestGetAllPorts* tests).
@@ -1546,7 +1546,7 @@
AddInterface(kClientAddr);
// GTURN is not configured here.
ResetWithTurnServersNoNat(kTurnUdpIntAddr, rtc::SocketAddress());
- allocator().set_candidate_filter(CF_RELAY);
+ allocator().SetCandidateFilter(CF_RELAY);
ASSERT_TRUE(CreateSession(ICE_CANDIDATE_COMPONENT_RTP));
session_->StartGettingPorts();
EXPECT_TRUE_SIMULATED_WAIT(candidate_allocation_done_,
@@ -1565,7 +1565,7 @@
TEST_F(BasicPortAllocatorTest, TestCandidateFilterWithHostOnly) {
AddInterface(kClientAddr);
allocator().set_flags(PORTALLOCATOR_ENABLE_SHARED_SOCKET);
- allocator().set_candidate_filter(CF_HOST);
+ allocator().SetCandidateFilter(CF_HOST);
ASSERT_TRUE(CreateSession(ICE_CANDIDATE_COMPONENT_RTP));
session_->StartGettingPorts();
EXPECT_TRUE_SIMULATED_WAIT(candidate_allocation_done_,
@@ -1583,7 +1583,7 @@
ResetWithStunServerAndNat(kStunAddr);
allocator().set_flags(PORTALLOCATOR_ENABLE_SHARED_SOCKET);
- allocator().set_candidate_filter(CF_REFLEXIVE);
+ allocator().SetCandidateFilter(CF_REFLEXIVE);
ASSERT_TRUE(CreateSession(ICE_CANDIDATE_COMPONENT_RTP));
session_->StartGettingPorts();
EXPECT_TRUE_SIMULATED_WAIT(candidate_allocation_done_,
@@ -1602,7 +1602,7 @@
TEST_F(BasicPortAllocatorTest, TestCandidateFilterWithReflexiveOnlyAndNoNAT) {
AddInterface(kClientAddr);
allocator().set_flags(PORTALLOCATOR_ENABLE_SHARED_SOCKET);
- allocator().set_candidate_filter(CF_REFLEXIVE);
+ allocator().SetCandidateFilter(CF_REFLEXIVE);
ASSERT_TRUE(CreateSession(ICE_CANDIDATE_COMPONENT_RTP));
session_->StartGettingPorts();
EXPECT_TRUE_SIMULATED_WAIT(candidate_allocation_done_,
@@ -2133,7 +2133,7 @@
kDefaultAllocationTimeout, fake_clock);
size_t initial_candidates_size = peeked_session->ReadyCandidates().size();
size_t initial_ports_size = peeked_session->ReadyPorts().size();
- allocator_->set_candidate_filter(CF_RELAY);
+ allocator_->SetCandidateFilter(CF_RELAY);
// Assume that when TakePooledSession is called, the candidate filter will be
// applied to the pooled session. This is tested by PortAllocatorTest.
session_ =
@@ -2157,6 +2157,145 @@
}
}
+// Test that candidates that do not match a previous candidate filter can be
+// surfaced if they match the new one after setting the filter value.
+TEST_F(BasicPortAllocatorTest,
+ SurfaceNewCandidatesAfterSetCandidateFilterToAddCandidateTypes) {
+ // We would still surface a host candidate if the IP is public, even though it
+ // is disabled by the candidate filter. See
+ // BasicPortAllocatorSession::CheckCandidateFilter. Use the private address so
+ // that the srflx candidate is not equivalent to the host candidate.
+ AddInterface(kPrivateAddr);
+ ResetWithStunServerAndNat(kStunAddr);
+
+ AddTurnServers(kTurnUdpIntAddr, rtc::SocketAddress());
+
+ allocator_->set_flags(allocator().flags() |
+ PORTALLOCATOR_ENABLE_SHARED_SOCKET |
+ PORTALLOCATOR_DISABLE_TCP);
+
+ allocator_->SetCandidateFilter(CF_NONE);
+ ASSERT_TRUE(CreateSession(ICE_CANDIDATE_COMPONENT_RTP));
+ session_->StartGettingPorts();
+ EXPECT_TRUE_SIMULATED_WAIT(candidate_allocation_done_,
+ kDefaultAllocationTimeout, fake_clock);
+ EXPECT_TRUE(candidates_.empty());
+ EXPECT_TRUE(ports_.empty());
+
+ // Surface the relay candidate previously gathered but not signaled.
+ session_->SetCandidateFilter(CF_RELAY);
+ ASSERT_EQ_SIMULATED_WAIT(1u, candidates_.size(), kDefaultAllocationTimeout,
+ fake_clock);
+ EXPECT_EQ(RELAY_PORT_TYPE, candidates_.back().type());
+ EXPECT_EQ(1u, ports_.size());
+
+ // Surface the srflx candidate previously gathered but not signaled.
+ session_->SetCandidateFilter(CF_RELAY | CF_REFLEXIVE);
+ ASSERT_EQ_SIMULATED_WAIT(2u, candidates_.size(), kDefaultAllocationTimeout,
+ fake_clock);
+ EXPECT_EQ(STUN_PORT_TYPE, candidates_.back().type());
+ EXPECT_EQ(2u, ports_.size());
+
+ // Surface the srflx candidate previously gathered but not signaled.
+ session_->SetCandidateFilter(CF_ALL);
+ ASSERT_EQ_SIMULATED_WAIT(3u, candidates_.size(), kDefaultAllocationTimeout,
+ fake_clock);
+ EXPECT_EQ(LOCAL_PORT_TYPE, candidates_.back().type());
+ EXPECT_EQ(2u, ports_.size());
+}
+
+// This is a similar test as
+// SurfaceNewCandidatesAfterSetCandidateFilterToAddCandidateTypes, and we
+// test the transitions for which the new filter value is not a super set of the
+// previous value.
+TEST_F(
+ BasicPortAllocatorTest,
+ SurfaceNewCandidatesAfterSetCandidateFilterToAllowDifferentCandidateTypes) {
+ // We would still surface a host candidate if the IP is public, even though it
+ // is disabled by the candidate filter. See
+ // BasicPortAllocatorSession::CheckCandidateFilter. Use the private address so
+ // that the srflx candidate is not equivalent to the host candidate.
+ AddInterface(kPrivateAddr);
+ ResetWithStunServerAndNat(kStunAddr);
+
+ AddTurnServers(kTurnUdpIntAddr, rtc::SocketAddress());
+
+ allocator_->set_flags(allocator().flags() |
+ PORTALLOCATOR_ENABLE_SHARED_SOCKET |
+ PORTALLOCATOR_DISABLE_TCP);
+
+ allocator_->SetCandidateFilter(CF_NONE);
+ ASSERT_TRUE(CreateSession(ICE_CANDIDATE_COMPONENT_RTP));
+ session_->StartGettingPorts();
+ EXPECT_TRUE_SIMULATED_WAIT(candidate_allocation_done_,
+ kDefaultAllocationTimeout, fake_clock);
+ EXPECT_TRUE(candidates_.empty());
+ EXPECT_TRUE(ports_.empty());
+
+ // Surface the relay candidate previously gathered but not signaled.
+ session_->SetCandidateFilter(CF_RELAY);
+ EXPECT_EQ_SIMULATED_WAIT(1u, candidates_.size(), kDefaultAllocationTimeout,
+ fake_clock);
+ EXPECT_EQ(RELAY_PORT_TYPE, candidates_.back().type());
+ EXPECT_EQ(1u, ports_.size());
+
+ // Surface the srflx candidate previously gathered but not signaled.
+ session_->SetCandidateFilter(CF_REFLEXIVE);
+ EXPECT_EQ_SIMULATED_WAIT(2u, candidates_.size(), kDefaultAllocationTimeout,
+ fake_clock);
+ EXPECT_EQ(STUN_PORT_TYPE, candidates_.back().type());
+ EXPECT_EQ(2u, ports_.size());
+
+ // Surface the host candidate previously gathered but not signaled.
+ session_->SetCandidateFilter(CF_HOST);
+ EXPECT_EQ_SIMULATED_WAIT(3u, candidates_.size(), kDefaultAllocationTimeout,
+ fake_clock);
+ EXPECT_EQ(LOCAL_PORT_TYPE, candidates_.back().type());
+ // We use a shared socket and cricket::UDPPort handles the srflx candidate.
+ EXPECT_EQ(2u, ports_.size());
+}
+
+// Test that after an allocation session has stopped getting ports, changing the
+// candidate filter to allow new types of gathered candidates does not surface
+// any candidate.
+TEST_F(BasicPortAllocatorTest,
+ NoCandidateSurfacedWhenUpdatingCandidateFilterIfSessionStopped) {
+ AddInterface(kPrivateAddr);
+ ResetWithStunServerAndNat(kStunAddr);
+
+ AddTurnServers(kTurnUdpIntAddr, rtc::SocketAddress());
+
+ allocator_->set_flags(allocator().flags() |
+ PORTALLOCATOR_ENABLE_SHARED_SOCKET |
+ PORTALLOCATOR_DISABLE_TCP);
+
+ allocator_->SetCandidateFilter(CF_NONE);
+ ASSERT_TRUE(CreateSession(ICE_CANDIDATE_COMPONENT_RTP));
+ session_->StartGettingPorts();
+ EXPECT_TRUE_SIMULATED_WAIT(candidate_allocation_done_,
+ kDefaultAllocationTimeout, fake_clock);
+ auto test_invariants = [this]() {
+ EXPECT_TRUE(candidates_.empty());
+ EXPECT_TRUE(ports_.empty());
+ };
+
+ test_invariants();
+
+ session_->StopGettingPorts();
+
+ session_->SetCandidateFilter(CF_RELAY);
+ SIMULATED_WAIT(false, kDefaultAllocationTimeout, fake_clock);
+ test_invariants();
+
+ session_->SetCandidateFilter(CF_RELAY | CF_REFLEXIVE);
+ SIMULATED_WAIT(false, kDefaultAllocationTimeout, fake_clock);
+ test_invariants();
+
+ session_->SetCandidateFilter(CF_ALL);
+ SIMULATED_WAIT(false, kDefaultAllocationTimeout, fake_clock);
+ test_invariants();
+}
+
TEST_F(BasicPortAllocatorTest, SetStunKeepaliveIntervalForPorts) {
const int pool_size = 1;
const int expected_stun_keepalive_interval = 123;