Prepare for ICE renomination.

Add an ICE nomination attribute. If a connection switched on the controlling side, increase the nomination value set in the attribute.
The controlled side will also be ready for re-nomination option; it will switch if a nomination comes with a higher nomination value even though it may be at a lower priority.
Plus, don't nominate or re-nominate if the nomination value at the current connection has been acknowledged.

BUG=
R=deadbeef@webrtc.org, pthatcher@webrtc.org

Review URL: https://codereview.webrtc.org/2163403002 .

Cr-Commit-Position: refs/heads/master@{#13631}
This commit is contained in:
Honghai Zhang 2016-08-03 19:50:41 -07:00
parent 6ce738da31
commit 8cd8f81748
7 changed files with 368 additions and 129 deletions

View File

@ -403,6 +403,12 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) {
LOG(LS_INFO) << "Set receiving_switching_delay to"
<< *config_.receiving_switching_delay;
}
if (config_.default_nomination_mode != config.default_nomination_mode) {
config_.default_nomination_mode = config.default_nomination_mode;
LOG(LS_INFO) << "Set default nomination mode to "
<< static_cast<int>(config_.default_nomination_mode);
}
}
const IceConfig& P2PTransportChannel::config() const {
@ -1140,10 +1146,10 @@ int P2PTransportChannel::CompareConnections(
if (ice_role_ == ICEROLE_CONTROLLED) {
// Compare the connections based on the nomination states and the last data
// received time if this is on the controlled side.
if (a->nominated() && !b->nominated()) {
if (a->remote_nomination() > b->remote_nomination()) {
return a_is_better;
}
if (!a->nominated() && b->nominated()) {
if (a->remote_nomination() < b->remote_nomination()) {
return b_is_better;
}
@ -1285,6 +1291,7 @@ void P2PTransportChannel::SwitchSelectedConnection(Connection* conn) {
Connection* old_selected_connection = selected_connection_;
selected_connection_ = conn;
if (selected_connection_) {
++nomination_;
if (old_selected_connection) {
LOG_J(LS_INFO, this) << "Previous selected connection: "
<< old_selected_connection->ToString();
@ -1585,32 +1592,66 @@ void P2PTransportChannel::MarkConnectionPinged(Connection* conn) {
}
// Apart from sending ping from |conn| this method also updates
// |use_candidate_attr| flag. The criteria to update this flag is
// explained below.
// Set USE-CANDIDATE if doing ICE AND this channel is in CONTROLLING AND
// a) Channel is in FULL ICE AND
// a.1) |conn| is the selected connection OR
// a.2) there is no selected connection OR
// a.3) the selected connection is unwritable OR
// a.4) |conn| has higher priority than selected_connection.
// b) we're doing LITE ICE AND
// b.1) |conn| is the selected_connection AND
// b.2) |conn| is writable.
// |use_candidate_attr| and |nomination| flags. One of the flags is set to
// nominate |conn| if this channel is in CONTROLLING.
void P2PTransportChannel::PingConnection(Connection* conn) {
bool use_candidate = false;
if (remote_ice_mode_ == ICEMODE_FULL && ice_role_ == ICEROLE_CONTROLLING) {
use_candidate =
(conn == selected_connection_) || (selected_connection_ == NULL) ||
(!selected_connection_->writable()) ||
(CompareConnectionCandidates(selected_connection_, conn) < 0);
} else if (remote_ice_mode_ == ICEMODE_LITE && conn == selected_connection_) {
use_candidate = selected_connection_->writable();
bool use_candidate_attr = false;
uint32_t nomination = 0;
if (ice_role_ == ICEROLE_CONTROLLING) {
if (remote_supports_renomination_) {
nomination = GetNominationAttr(conn);
} else {
use_candidate_attr =
GetUseCandidateAttr(conn, config_.default_nomination_mode);
}
}
conn->set_use_candidate_attr(use_candidate);
conn->set_nomination(nomination);
conn->set_use_candidate_attr(use_candidate_attr);
last_ping_sent_ms_ = rtc::TimeMillis();
conn->Ping(last_ping_sent_ms_);
}
uint32_t P2PTransportChannel::GetNominationAttr(Connection* conn) const {
return (conn == selected_connection_) ? nomination_ : 0;
}
// Nominate a connection based on the NominationMode.
bool P2PTransportChannel::GetUseCandidateAttr(Connection* conn,
NominationMode mode) const {
switch (mode) {
case NominationMode::REGULAR:
// TODO(honghaiz): Implement regular nomination.
return false;
case NominationMode::AGGRESSIVE:
if (remote_ice_mode_ == ICEMODE_LITE) {
return GetUseCandidateAttr(conn, NominationMode::REGULAR);
}
return true;
case NominationMode::SEMI_AGGRESSIVE: {
// Nominate if
// a) Remote is in FULL ICE AND
// a.1) |conn| is the selected connection OR
// a.2) there is no selected connection OR
// a.3) the selected connection is unwritable OR
// a.4) |conn| has higher priority than selected_connection.
// b) Remote is in LITE ICE AND
// b.1) |conn| is the selected_connection AND
// b.2) |conn| is writable.
bool selected = conn == selected_connection_;
if (remote_ice_mode_ == ICEMODE_LITE) {
return selected && conn->writable();
}
bool better_than_selected =
!selected_connection_ || !selected_connection_->writable() ||
CompareConnectionCandidates(selected_connection_, conn) < 0;
return selected || better_than_selected;
}
default:
RTC_DCHECK(false);
return false;
}
}
// When a connection's state changes, we need to figure out who to use as
// the selected connection again. It could have become usable, or become
// unusable.

View File

@ -209,6 +209,11 @@ class P2PTransportChannel : public TransportChannelImpl,
return remote_candidates_;
}
// Public for unit tests.
void set_remote_supports_renomination(bool remote_supports_renomination) {
remote_supports_renomination_ = remote_supports_renomination;
}
private:
rtc::Thread* thread() const { return worker_thread_; }
bool IsGettingPorts() { return allocator_session()->IsGettingPorts(); }
@ -313,6 +318,9 @@ class P2PTransportChannel : public TransportChannelImpl,
void OnCheckAndPing();
void OnRegatherOnFailedNetworks();
uint32_t GetNominationAttr(Connection* conn) const;
bool GetUseCandidateAttr(Connection* conn, NominationMode mode) const;
// Returns true if we should switch to the new connection.
// sets |missed_receiving_unchanged_threshold| to true if either
// the selected connection or the new connection missed its
@ -402,6 +410,13 @@ class P2PTransportChannel : public TransportChannelImpl,
IceConfig config_;
int last_sent_packet_id_ = -1; // -1 indicates no packet was sent before.
bool started_pinging_ = false;
// TODO(honghaiz): Put this and ICE role inside ICEParameters and rename this
// as renomination. Set its value in subsequent CLs based on signaling
// exchange.
bool remote_supports_renomination_ = false;
// The value put in the "nomination" attribute for the next nominated
// connection. A zero-value indicates the connection will not be nominated.
uint32_t nomination_ = 0;
RTC_DISALLOW_COPY_AND_ASSIGN(P2PTransportChannel);
};

View File

@ -849,6 +849,14 @@ class P2PTransportChannelTestBase : public testing::Test,
force_relay_ = relay;
}
void ConnectSignalNominated(Connection* conn) {
conn->SignalNominated.connect(this,
&P2PTransportChannelTestBase::OnNominated);
}
void OnNominated(Connection* conn) { nominated_ = true; }
bool nominated() { return nominated_; }
private:
rtc::Thread* main_;
std::unique_ptr<rtc::PhysicalSocketServer> pss_;
@ -866,6 +874,8 @@ class P2PTransportChannelTestBase : public testing::Test,
RemoteIceCredentialSource remote_ice_credential_source_ = FROM_CANDIDATE;
bool force_relay_;
int selected_candidate_pair_switches_ = 0;
bool nominated_ = false;
};
// The tests have only a few outcomes, which we predefine.
@ -1862,9 +1872,10 @@ class P2PTransportChannelMultihomedTest : public P2PTransportChannelTestBase {
}
void DestroyAllButBestConnection(cricket::P2PTransportChannel* channel) {
const cricket::Connection* best_connection = channel->best_connection();
const cricket::Connection* selected_connection =
channel->selected_connection();
for (cricket::Connection* conn : channel->connections()) {
if (conn != best_connection) {
if (conn != selected_connection) {
conn->Destroy();
}
}
@ -1991,6 +2002,64 @@ TEST_F(P2PTransportChannelMultihomedTest, TestFailoverControllingSide) {
DestroyChannels();
}
// Test that when the controlling side switches the selected connection,
// the nomination of the selected connection on the controlled side will
// increase.
TEST_F(P2PTransportChannelMultihomedTest, TestIceRenomination) {
rtc::ScopedFakeClock clock;
// Adding alternate address will make sure |kPublicAddrs| has the higher
// priority than others. This is due to FakeNetwork::AddInterface method.
AddAddress(0, kAlternateAddrs[0]);
AddAddress(0, kPublicAddrs[0]);
AddAddress(1, kPublicAddrs[1]);
// Use only local ports for simplicity.
SetAllocatorFlags(0, kOnlyLocalPorts);
SetAllocatorFlags(1, kOnlyLocalPorts);
// Create channels and let them go writable, as usual.
CreateChannels(1);
ep1_ch1()->set_remote_supports_renomination(true);
EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
ep2_ch1()->receiving() &&
ep2_ch1()->writable(),
3000, clock);
EXPECT_TRUE_SIMULATED_WAIT(
ep2_ch1()->selected_connection()->remote_nomination() > 0 &&
ep1_ch1()->selected_connection()->acked_nomination() > 0,
kDefaultTimeout, clock);
const Connection* selected_connection1 = ep1_ch1()->selected_connection();
Connection* selected_connection2 =
const_cast<Connection*>(ep2_ch1()->selected_connection());
uint32_t remote_nomination2 = selected_connection2->remote_nomination();
// |selected_connection2| should not be nominated any more since the previous
// nomination has been acknowledged.
ConnectSignalNominated(selected_connection2);
SIMULATED_WAIT(nominated(), 3000, clock);
EXPECT_FALSE(nominated());
// Make the receiving timeout shorter for testing.
IceConfig config = CreateIceConfig(1000, GATHER_ONCE);
ep1_ch1()->SetIceConfig(config);
ep2_ch1()->SetIceConfig(config);
// Blackhole any traffic to or from the public addrs.
LOG(LS_INFO) << "Failing over...";
fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]);
// The selected connection on the controlling side should switch.
EXPECT_TRUE_SIMULATED_WAIT(
ep1_ch1()->selected_connection() != selected_connection1, 3000, clock);
// The connection on the controlled side should be nominated again
// and have an increased nomination.
EXPECT_TRUE_SIMULATED_WAIT(
ep2_ch1()->selected_connection()->remote_nomination() >
remote_nomination2,
kDefaultTimeout, clock);
DestroyChannels();
}
// Test that if an interface fails temporarily and then recovers quickly,
// the selected connection will not switch.
// The case that it will switch over to the backup connection if the selected
@ -2295,26 +2364,27 @@ TEST_F(P2PTransportChannelMultihomedTest,
// to be created and the new one will be the best connection.
AddAddress(1, wifi[1], "test_wifi1", rtc::ADAPTER_TYPE_WIFI);
const cricket::Connection* conn;
EXPECT_TRUE_WAIT((conn = ep1_ch1()->best_connection()) != nullptr &&
EXPECT_TRUE_WAIT((conn = ep1_ch1()->selected_connection()) != nullptr &&
conn->remote_candidate().address().EqualIPs(wifi[1]),
kDefaultTimeout);
EXPECT_TRUE_WAIT((conn = ep2_ch1()->best_connection()) != nullptr &&
EXPECT_TRUE_WAIT((conn = ep2_ch1()->selected_connection()) != nullptr &&
conn->local_candidate().address().EqualIPs(wifi[1]),
kDefaultTimeout);
// Add a new cellular interface on end point 1, we should expect a new
// backup connection created using this new interface.
AddAddress(0, cellular[0], "test_cellular0", rtc::ADAPTER_TYPE_CELLULAR);
EXPECT_TRUE_WAIT(ep1_ch1()->GetState() == cricket::STATE_COMPLETED &&
(conn = GetConnectionWithLocalAddress(
ep1_ch1(), cellular[0])) != nullptr &&
conn != ep1_ch1()->best_connection() && conn->writable(),
kDefaultTimeout);
EXPECT_TRUE_WAIT(
ep1_ch1()->GetState() == cricket::STATE_COMPLETED &&
(conn = GetConnectionWithLocalAddress(ep1_ch1(), cellular[0])) !=
nullptr &&
conn != ep1_ch1()->selected_connection() && conn->writable(),
kDefaultTimeout);
EXPECT_TRUE_WAIT(
ep2_ch1()->GetState() == cricket::STATE_COMPLETED &&
(conn = GetConnectionWithRemoteAddress(ep2_ch1(), cellular[0])) !=
nullptr &&
conn != ep2_ch1()->best_connection() && conn->receiving(),
conn != ep2_ch1()->selected_connection() && conn->receiving(),
kDefaultTimeout);
DestroyChannels();
@ -2360,7 +2430,7 @@ TEST_F(P2PTransportChannelMultihomedTest,
RemoveAddress(1, kAlternateAddrs[1]);
AddAddress(1, kAlternateAddrs[0]);
EXPECT_TRUE_SIMULATED_WAIT(
ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
ep1_ch1()->selected_connection() && ep2_ch1()->selected_connection() &&
LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
RemoteCandidate(ep1_ch1())->address().EqualIPs(kAlternateAddrs[0]),
3000, clock);
@ -2397,7 +2467,8 @@ TEST_F(P2PTransportChannelMultihomedTest,
EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
ep2_ch1()->receiving() && ep2_ch1()->writable(),
3000, clock);
EXPECT_TRUE(ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
EXPECT_TRUE(ep1_ch1()->selected_connection() &&
ep2_ch1()->selected_connection() &&
LocalCandidate(ep1_ch1())->address().EqualIPs(wifi[0]) &&
RemoteCandidate(ep1_ch1())->address().EqualIPs(wifi[1]));
@ -2408,7 +2479,7 @@ TEST_F(P2PTransportChannelMultihomedTest,
// Then the interface of the best connection goes away.
RemoveAddress(0, wifi[0]);
EXPECT_TRUE_SIMULATED_WAIT(
ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
ep1_ch1()->selected_connection() && ep2_ch1()->selected_connection() &&
LocalCandidate(ep1_ch1())->address().EqualIPs(cellular[0]) &&
RemoteCandidate(ep1_ch1())->address().EqualIPs(wifi[1]),
3000, clock);
@ -2440,7 +2511,8 @@ TEST_F(P2PTransportChannelMultihomedTest, TestRestoreBackupConnection) {
ep2_ch1()->receiving() &&
ep2_ch1()->writable(),
3000, clock);
EXPECT_TRUE(ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
EXPECT_TRUE(ep1_ch1()->selected_connection() &&
ep2_ch1()->selected_connection() &&
LocalCandidate(ep1_ch1())->address().EqualIPs(wifi[0]) &&
RemoteCandidate(ep1_ch1())->address().EqualIPs(wifi[1]));
@ -2454,7 +2526,7 @@ TEST_F(P2PTransportChannelMultihomedTest, TestRestoreBackupConnection) {
EXPECT_TRUE_SIMULATED_WAIT(
(conn = GetConnectionWithLocalAddress(ep1_ch1(), cellular[0])) !=
nullptr &&
conn != ep1_ch1()->best_connection() && conn->writable(),
conn != ep1_ch1()->selected_connection() && conn->writable(),
5000, clock);
DestroyChannels();
@ -2544,13 +2616,13 @@ class P2PTransportChannelPingTest : public testing::Test,
Connection* conn = GetConnectionTo(&channel, ip_addr, port);
if (conn && writable) {
conn->ReceivedPingResponse(LOW_RTT); // make it writable
conn->ReceivedPingResponse(LOW_RTT, "id"); // make it writable
}
return conn;
}
void NominateConnection(Connection* conn) {
conn->set_nominated(true);
void NominateConnection(Connection* conn, uint32_t remote_nomination = 1U) {
conn->set_remote_nomination(remote_nomination);
conn->SignalNominated(conn);
}
@ -2566,13 +2638,18 @@ class P2PTransportChannelPingTest : public testing::Test,
void ReceivePingOnConnection(Connection* conn,
const std::string& remote_ufrag,
int priority) {
int priority,
uint32_t nomination = 0) {
IceMessage msg;
msg.SetType(STUN_BINDING_REQUEST);
msg.AddAttribute(new StunByteStringAttribute(
STUN_ATTR_USERNAME,
conn->local_candidate().username() + ":" + remote_ufrag));
msg.AddAttribute(new StunUInt32Attribute(STUN_ATTR_PRIORITY, priority));
if (nomination != 0) {
msg.AddAttribute(
new StunUInt32Attribute(STUN_ATTR_NOMINATION, nomination));
}
msg.SetTransactionID(rtc::CreateRandomString(kStunTransactionIdLength));
msg.AddMessageIntegrity(conn->local_candidate().password());
msg.AddFingerprint();
@ -2651,7 +2728,7 @@ TEST_F(P2PTransportChannelPingTest, TestAllConnectionsPingedSufficiently) {
// Low-priority connection becomes writable so that the other connection
// is not pruned.
conn1->ReceivedPingResponse(LOW_RTT);
conn1->ReceivedPingResponse(LOW_RTT, "id");
EXPECT_TRUE_WAIT(
conn1->num_pings_sent() >= MIN_PINGS_AT_WEAK_PING_INTERVAL &&
conn2->num_pings_sent() >= MIN_PINGS_AT_WEAK_PING_INTERVAL,
@ -2687,7 +2764,7 @@ TEST_F(P2PTransportChannelPingTest, TestStunPingIntervals) {
// Stabilizing.
conn->ReceivedPingResponse(LOW_RTT);
conn->ReceivedPingResponse(LOW_RTT, "id");
int ping_sent_before = conn->num_pings_sent();
start = clock.TimeNanos();
// The connection becomes strong but not stable because we haven't been able
@ -2703,7 +2780,7 @@ TEST_F(P2PTransportChannelPingTest, TestStunPingIntervals) {
// The connection becomes stable after receiving more than RTT_RATIO rtt
// samples.
for (int i = 0; i < RTT_RATIO; i++) {
conn->ReceivedPingResponse(LOW_RTT);
conn->ReceivedPingResponse(LOW_RTT, "id");
}
ping_sent_before = conn->num_pings_sent();
start = clock.TimeNanos();
@ -2715,7 +2792,7 @@ TEST_F(P2PTransportChannelPingTest, TestStunPingIntervals) {
// Destabilized.
conn->ReceivedPingResponse(LOW_RTT);
conn->ReceivedPingResponse(LOW_RTT, "id");
// Create a in-flight ping.
conn->Ping(clock.TimeNanos() / rtc::kNumNanosecsPerMillisec);
start = clock.TimeNanos();
@ -2797,7 +2874,7 @@ TEST_F(P2PTransportChannelPingTest, TestNoTriggeredChecksWhenWritable) {
EXPECT_EQ(conn2, FindNextPingableConnectionAndPingIt(&ch));
EXPECT_EQ(conn1, FindNextPingableConnectionAndPingIt(&ch));
conn1->ReceivedPingResponse(LOW_RTT);
conn1->ReceivedPingResponse(LOW_RTT, "id");
ASSERT_TRUE(conn1->writable());
conn1->ReceivedPing();
@ -2914,7 +2991,7 @@ TEST_F(P2PTransportChannelPingTest, ConnectionResurrection) {
Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
ASSERT_TRUE(conn2 != nullptr);
conn2->ReceivedPing();
conn2->ReceivedPingResponse(LOW_RTT);
conn2->ReceivedPingResponse(LOW_RTT, "id");
// Wait for conn1 to be pruned.
EXPECT_TRUE_WAIT(conn1->pruned(), 3000);
@ -3015,11 +3092,10 @@ TEST_F(P2PTransportChannelPingTest, TestSelectConnectionBeforeNomination) {
ASSERT_TRUE(conn3 != nullptr);
// Because it has a lower priority, the selected connection is still conn2.
EXPECT_EQ(conn2, ch.selected_connection());
conn3->ReceivedPingResponse(LOW_RTT); // Become writable.
conn3->ReceivedPingResponse(LOW_RTT, "id"); // Become writable.
// But if it is nominated via use_candidate, it is chosen as the selected
// connection.
conn3->set_nominated(true);
conn3->SignalNominated(conn3);
NominateConnection(conn3);
EXPECT_EQ(conn3, ch.selected_connection());
EXPECT_EQ(conn3, last_selected_candidate_pair());
EXPECT_EQ(-1, last_sent_packet_id());
@ -3035,13 +3111,12 @@ TEST_F(P2PTransportChannelPingTest, TestSelectConnectionBeforeNomination) {
EXPECT_EQ(conn3, ch.selected_connection());
// But if it is nominated via use_candidate and writable, it will be set as
// the selected connection.
conn4->set_nominated(true);
conn4->SignalNominated(conn4);
NominateConnection(conn4);
// Not switched yet because conn4 is not writable.
EXPECT_EQ(conn3, ch.selected_connection());
reset_channel_ready_to_send();
// The selected connection switches after conn4 becomes writable.
conn4->ReceivedPingResponse(LOW_RTT);
conn4->ReceivedPingResponse(LOW_RTT, "id");
EXPECT_EQ_WAIT(conn4, ch.selected_connection(), kDefaultTimeout);
EXPECT_EQ(conn4, last_selected_candidate_pair());
EXPECT_EQ(last_packet_id, last_sent_packet_id());
@ -3076,7 +3151,7 @@ TEST_F(P2PTransportChannelPingTest, TestSelectConnectionFromUnknownAddress) {
ASSERT_TRUE(conn1 != nullptr);
EXPECT_TRUE(port->sent_binding_response());
EXPECT_EQ(conn1, ch.selected_connection());
conn1->ReceivedPingResponse(LOW_RTT);
conn1->ReceivedPingResponse(LOW_RTT, "id");
EXPECT_EQ(conn1, ch.selected_connection());
port->set_sent_binding_response(false);
@ -3088,9 +3163,8 @@ TEST_F(P2PTransportChannelPingTest, TestSelectConnectionFromUnknownAddress) {
EXPECT_EQ(conn1, ch.selected_connection());
// When it is nominated via use_candidate and writable, it is chosen as the
// selected connection.
conn2->ReceivedPingResponse(LOW_RTT); // Become writable.
conn2->set_nominated(true);
conn2->SignalNominated(conn2);
conn2->ReceivedPingResponse(LOW_RTT, "id"); // Become writable.
NominateConnection(conn2);
EXPECT_EQ(conn2, ch.selected_connection());
// Another request with unknown address, it will not be set as the selected
@ -3101,7 +3175,7 @@ TEST_F(P2PTransportChannelPingTest, TestSelectConnectionFromUnknownAddress) {
Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3);
ASSERT_TRUE(conn3 != nullptr);
EXPECT_TRUE(port->sent_binding_response());
conn3->ReceivedPingResponse(LOW_RTT); // Become writable.
conn3->ReceivedPingResponse(LOW_RTT, "id"); // Become writable.
EXPECT_EQ(conn2, ch.selected_connection());
port->set_sent_binding_response(false);
@ -3115,7 +3189,7 @@ TEST_F(P2PTransportChannelPingTest, TestSelectConnectionFromUnknownAddress) {
EXPECT_TRUE(port->sent_binding_response());
// conn4 is not the selected connection yet because it is not writable.
EXPECT_EQ(conn2, ch.selected_connection());
conn4->ReceivedPingResponse(LOW_RTT); // Become writable.
conn4->ReceivedPingResponse(LOW_RTT, "id"); // Become writable.
EXPECT_EQ_WAIT(conn4, ch.selected_connection(), kDefaultTimeout);
// Test that the request from an unknown address contains a ufrag from an old
@ -3155,7 +3229,7 @@ TEST_F(P2PTransportChannelPingTest, TestSelectConnectionBasedOnMediaReceived) {
conn2->ReceivedPing(); // Start receiving.
conn2->OnReadPacket("ABC", 3, rtc::CreatePacketTime(0));
EXPECT_EQ(conn2, ch.selected_connection());
conn2->ReceivedPingResponse(LOW_RTT); // Become writable.
conn2->ReceivedPingResponse(LOW_RTT, "id"); // Become writable.
// Now another STUN message with an unknown address and use_candidate will
// nominate the selected connection.
@ -3173,13 +3247,13 @@ TEST_F(P2PTransportChannelPingTest, TestSelectConnectionBasedOnMediaReceived) {
Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3);
ASSERT_TRUE(conn3 != nullptr);
EXPECT_EQ(conn2, ch.selected_connection()); // Not writable yet.
conn3->ReceivedPingResponse(LOW_RTT); // Become writable.
conn3->ReceivedPingResponse(LOW_RTT, "id"); // Become writable.
EXPECT_EQ_WAIT(conn3, ch.selected_connection(), kDefaultTimeout);
// Now another data packet will not switch the selected connection because the
// selected connection was nominated by the controlling side.
conn2->ReceivedPing();
conn2->ReceivedPingResponse(LOW_RTT);
conn2->ReceivedPingResponse(LOW_RTT, "id");
conn2->OnReadPacket("XYZ", 3, rtc::CreatePacketTime(0));
EXPECT_EQ_WAIT(conn3, ch.selected_connection(), kDefaultTimeout);
}
@ -3268,6 +3342,65 @@ TEST_F(P2PTransportChannelPingTest,
EXPECT_EQ(0, reset_selected_candidate_pair_switches());
}
TEST_F(P2PTransportChannelPingTest,
TestControlledAgentSelectsConnectionWithHigherNomination) {
rtc::ScopedFakeClock clock;
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
P2PTransportChannel ch("test", 1, &pa);
PrepareChannel(&ch);
ch.SetIceRole(ICEROLE_CONTROLLED);
ch.MaybeStartGathering();
// The connections have decreasing priority.
Connection* conn1 =
CreateConnectionWithCandidate(ch, clock, "1.1.1.1", 1, 10, false);
ASSERT_TRUE(conn1 != nullptr);
Connection* conn2 =
CreateConnectionWithCandidate(ch, clock, "2.2.2.2", 2, 9, false);
ASSERT_TRUE(conn2 != nullptr);
// conn1 is the selected connection because it has a higher priority,
EXPECT_EQ_SIMULATED_WAIT(conn1, last_selected_candidate_pair(),
kDefaultTimeout, clock);
reset_selected_candidate_pair_switches();
// conn2 is nominated; it becomes selected.
NominateConnection(conn2);
EXPECT_EQ(1, reset_selected_candidate_pair_switches());
EXPECT_EQ(conn2, last_selected_candidate_pair());
// conn1 is selected because of its priority.
NominateConnection(conn1);
EXPECT_EQ(1, reset_selected_candidate_pair_switches());
EXPECT_EQ(conn1, last_selected_candidate_pair());
// conn2 gets higher remote nomination; it is selected again.
NominateConnection(conn2, 2U);
EXPECT_EQ(1, reset_selected_candidate_pair_switches());
EXPECT_EQ(conn2, last_selected_candidate_pair());
// Make sure sorting won't reselect candidate pair.
SIMULATED_WAIT(false, 100, clock);
EXPECT_EQ(0, reset_selected_candidate_pair_switches());
}
TEST_F(P2PTransportChannelPingTest,
TestControlledAgentIgnoresSmallerNomination) {
rtc::ScopedFakeClock clock;
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
P2PTransportChannel ch("test", 1, &pa);
PrepareChannel(&ch);
ch.SetIceRole(ICEROLE_CONTROLLED);
ch.MaybeStartGathering();
Connection* conn =
CreateConnectionWithCandidate(ch, clock, "1.1.1.1", 1, 10, false);
ReceivePingOnConnection(conn, kIceUfrag[1], 1, 2U);
EXPECT_EQ(2U, conn->remote_nomination());
// Smaller nomination is ignored.
ReceivePingOnConnection(conn, kIceUfrag[1], 1, 1U);
EXPECT_EQ(2U, conn->remote_nomination());
}
TEST_F(P2PTransportChannelPingTest,
TestControlledAgentWriteStateTakesHigherPrecedenceThanNomination) {
rtc::ScopedFakeClock clock;
@ -3289,7 +3422,7 @@ TEST_F(P2PTransportChannelPingTest,
EXPECT_EQ(1, reset_selected_candidate_pair_switches());
// conn2 becomes writable; it is selected even though it is not nominated.
conn2->ReceivedPingResponse(LOW_RTT);
conn2->ReceivedPingResponse(LOW_RTT, "id");
EXPECT_EQ_SIMULATED_WAIT(1, reset_selected_candidate_pair_switches(),
kDefaultTimeout, clock);
@ -3297,7 +3430,7 @@ TEST_F(P2PTransportChannelPingTest,
kDefaultTimeout, clock);
// If conn1 is also writable, it will become selected.
conn1->ReceivedPingResponse(LOW_RTT);
conn1->ReceivedPingResponse(LOW_RTT, "id");
EXPECT_EQ_SIMULATED_WAIT(1, reset_selected_candidate_pair_switches(),
kDefaultTimeout, clock);
EXPECT_EQ_SIMULATED_WAIT(conn1, last_selected_candidate_pair(),
@ -3358,16 +3491,15 @@ TEST_F(P2PTransportChannelPingTest, TestDontPruneWhenWeak) {
Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
ASSERT_TRUE(conn1 != nullptr);
EXPECT_EQ(conn1, ch.selected_connection());
conn1->ReceivedPingResponse(LOW_RTT); // Becomes writable and receiving
conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
// When a higher-priority, nominated candidate comes in, the connections with
// lower-priority are pruned.
ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 10));
Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
ASSERT_TRUE(conn2 != nullptr);
conn2->ReceivedPingResponse(LOW_RTT); // Becomes writable and receiving
conn2->set_nominated(true);
conn2->SignalNominated(conn2);
conn2->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
NominateConnection(conn2);
EXPECT_TRUE_WAIT(conn1->pruned(), 3000);
ch.SetIceConfig(CreateIceConfig(500, GATHER_ONCE));
@ -3422,7 +3554,7 @@ TEST_F(P2PTransportChannelPingTest, TestGetState) {
// Now there are two connections, so the transport channel is connecting.
EXPECT_EQ(TransportChannelState::STATE_CONNECTING, ch.GetState());
// |conn1| becomes writable and receiving; it then should prune |conn2|.
conn1->ReceivedPingResponse(LOW_RTT);
conn1->ReceivedPingResponse(LOW_RTT, "id");
EXPECT_TRUE_WAIT(conn2->pruned(), 1000);
EXPECT_EQ(TransportChannelState::STATE_COMPLETED, ch.GetState());
conn1->Prune(); // All connections are pruned.
@ -3444,7 +3576,7 @@ TEST_F(P2PTransportChannelPingTest, TestConnectionPrunedAgain) {
Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
ASSERT_TRUE(conn1 != nullptr);
EXPECT_EQ(conn1, ch.selected_connection());
conn1->ReceivedPingResponse(LOW_RTT); // Becomes writable and receiving
conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
// Add a low-priority connection |conn2|, which will be pruned, but it will
// not be deleted right away. Once the current selected connection becomes not
@ -3463,12 +3595,12 @@ TEST_F(P2PTransportChannelPingTest, TestConnectionPrunedAgain) {
conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
ASSERT_TRUE(conn2 != nullptr);
EXPECT_EQ_WAIT(Connection::STATE_INPROGRESS, conn2->state(), 1000);
conn2->ReceivedPingResponse(LOW_RTT);
conn2->ReceivedPingResponse(LOW_RTT, "id");
EXPECT_EQ_WAIT(conn2, ch.selected_connection(), 1000);
EXPECT_EQ(TransportChannelState::STATE_CONNECTING, ch.GetState());
// When |conn1| comes back again, |conn2| will be pruned again.
conn1->ReceivedPingResponse(LOW_RTT);
conn1->ReceivedPingResponse(LOW_RTT, "id");
EXPECT_EQ_WAIT(conn1, ch.selected_connection(), 1000);
EXPECT_TRUE_WAIT(!conn2->active(), 1000);
EXPECT_EQ(TransportChannelState::STATE_COMPLETED, ch.GetState());
@ -3517,7 +3649,7 @@ TEST_F(P2PTransportChannelPingTest, TestStopPortAllocatorSessions) {
ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
ASSERT_TRUE(conn1 != nullptr);
conn1->ReceivedPingResponse(LOW_RTT); // Becomes writable and receiving
conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
EXPECT_TRUE(!ch.allocator_session()->IsGettingPorts());
// Start a new session. Even though conn1, which belongs to an older
@ -3526,7 +3658,7 @@ TEST_F(P2PTransportChannelPingTest, TestStopPortAllocatorSessions) {
ch.SetIceCredentials(kIceUfrag[1], kIcePwd[1]);
ch.MaybeStartGathering();
conn1->Prune();
conn1->ReceivedPingResponse(LOW_RTT);
conn1->ReceivedPingResponse(LOW_RTT, "id");
EXPECT_TRUE(ch.allocator_session()->IsGettingPorts());
// But if a new connection created from the new session becomes writable,
@ -3534,7 +3666,7 @@ TEST_F(P2PTransportChannelPingTest, TestStopPortAllocatorSessions) {
ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 100));
Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
ASSERT_TRUE(conn2 != nullptr);
conn2->ReceivedPingResponse(LOW_RTT); // Becomes writable and receiving
conn2->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
EXPECT_TRUE(!ch.allocator_session()->IsGettingPorts());
}
@ -3707,7 +3839,7 @@ TEST_F(P2PTransportChannelMostLikelyToWorkFirstTest,
Connection* conn3 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
EXPECT_EQ(conn3->local_candidate().type(), LOCAL_PORT_TYPE);
EXPECT_EQ(conn3->remote_candidate().type(), RELAY_PORT_TYPE);
conn3->ReceivedPingResponse(LOW_RTT);
conn3->ReceivedPingResponse(LOW_RTT, "id");
ASSERT_TRUE(conn3->writable());
conn3->ReceivedPing();
@ -3725,7 +3857,7 @@ TEST_F(P2PTransportChannelMostLikelyToWorkFirstTest,
// pingable connection.
EXPECT_TRUE_WAIT(conn3 == ch.selected_connection(), 5000);
WAIT(false, max_strong_interval + 100);
conn3->ReceivedPingResponse(LOW_RTT);
conn3->ReceivedPingResponse(LOW_RTT, "id");
ASSERT_TRUE(conn3->writable());
EXPECT_EQ(conn3, FindNextPingableConnectionAndPingIt(&ch));

View File

@ -775,15 +775,17 @@ class ConnectionRequest : public StunRequest {
if (connection_->port()->GetIceRole() == ICEROLE_CONTROLLING) {
request->AddAttribute(new StunUInt64Attribute(
STUN_ATTR_ICE_CONTROLLING, connection_->port()->IceTiebreaker()));
// Since we are trying aggressive nomination, sending USE-CANDIDATE
// attribute in every ping.
// If we are dealing with a ice-lite end point, nomination flag
// in Connection will be set to false by default. Once the connection
// becomes "best connection", nomination flag will be turned on.
// We should have either USE_CANDIDATE attribute or ICE_NOMINATION
// attribute but not both. That was enforced in p2ptransportchannel.
if (connection_->use_candidate_attr()) {
request->AddAttribute(new StunByteStringAttribute(
STUN_ATTR_USE_CANDIDATE));
}
if (connection_->nomination() &&
connection_->nomination() != connection_->acked_nomination()) {
request->AddAttribute(new StunUInt32Attribute(
STUN_ATTR_NOMINATION, connection_->nomination()));
}
} else if (connection_->port()->GetIceRole() == ICEROLE_CONTROLLED) {
request->AddAttribute(new StunUInt64Attribute(
STUN_ATTR_ICE_CONTROLLED, connection_->port()->IceTiebreaker()));
@ -846,12 +848,13 @@ Connection::Connection(Port* port,
: port_(port),
local_candidate_index_(index),
remote_candidate_(remote_candidate),
recv_rate_tracker_(100, 10u),
send_rate_tracker_(100, 10u),
write_state_(STATE_WRITE_INIT),
receiving_(false),
connected_(true),
pruned_(false),
use_candidate_attr_(false),
nominated_(false),
remote_ice_mode_(ICEMODE_FULL),
requests_(port->thread()),
rtt_(DEFAULT_RTT),
@ -859,8 +862,6 @@ Connection::Connection(Port* port,
last_ping_received_(0),
last_data_received_(0),
last_ping_response_received_(0),
recv_rate_tracker_(100, 10u),
send_rate_tracker_(100, 10u),
reported_(false),
state_(STATE_WAITING),
receiving_timeout_(WEAK_CONNECTION_RECEIVE_TIMEOUT),
@ -1062,10 +1063,24 @@ void Connection::HandleBindingRequest(IceMessage* msg) {
}
if (port_->GetIceRole() == ICEROLE_CONTROLLED) {
const StunByteStringAttribute* use_candidate_attr =
msg->GetByteString(STUN_ATTR_USE_CANDIDATE);
if (use_candidate_attr) {
set_nominated(true);
const StunUInt32Attribute* nomination_attr =
msg->GetUInt32(STUN_ATTR_NOMINATION);
uint32_t nomination = 0;
if (nomination_attr) {
nomination = nomination_attr->value();
if (nomination == 0) {
LOG(LS_ERROR) << "Invalid nomination: " << nomination;
}
} else {
const StunByteStringAttribute* use_candidate_attr =
msg->GetByteString(STUN_ATTR_USE_CANDIDATE);
if (use_candidate_attr) {
nomination = 1;
}
}
// We don't un-nominate a connection, so we only keep a larger nomination.
if (nomination > remote_nomination_) {
set_remote_nomination(nomination);
SignalNominated(this);
}
}
@ -1199,9 +1214,10 @@ void Connection::UpdateState(int64_t now) {
void Connection::Ping(int64_t now) {
last_ping_sent_ = now;
ConnectionRequest *req = new ConnectionRequest(this);
pings_since_last_response_.push_back(SentPing(req->id(), now));
pings_since_last_response_.push_back(SentPing(req->id(), now, nomination_));
LOG_J(LS_VERBOSE, this) << "Sending STUN ping "
<< ", id=" << rtc::hex_encode(req->id());
<< ", id=" << rtc::hex_encode(req->id())
<< ", nomination=" << nomination_;
requests_.Send(req);
state_ = STATE_INPROGRESS;
num_pings_sent_++;
@ -1212,17 +1228,25 @@ void Connection::ReceivedPing() {
UpdateReceiving(last_ping_received_);
}
void Connection::ReceivedPingResponse(int rtt) {
void Connection::ReceivedPingResponse(int rtt, const std::string& request_id) {
// We've already validated that this is a STUN binding response with
// the correct local and remote username for this connection.
// So if we're not already, become writable. We may be bringing a pruned
// connection back to life, but if we don't really want it, we can always
// prune it again.
auto iter = std::find_if(
pings_since_last_response_.begin(), pings_since_last_response_.end(),
[request_id](const SentPing& ping) { return ping.id == request_id; });
if (iter != pings_since_last_response_.end() &&
iter->nomination > acked_nomination_) {
acked_nomination_ = iter->nomination;
}
pings_since_last_response_.clear();
last_ping_response_received_ = rtc::TimeMillis();
UpdateReceiving(last_ping_response_received_);
set_write_state(STATE_WRITABLE);
set_state(STATE_SUCCEEDED);
pings_since_last_response_.clear();
rtt_samples_++;
rtt_ = (RTT_RATIO * rtt_ + rtt) / (RTT_RATIO + 1);
}
@ -1295,21 +1319,16 @@ std::string Connection::ToString() const {
const Candidate& local = local_candidate();
const Candidate& remote = remote_candidate();
std::stringstream ss;
ss << "Conn[" << ToDebugId()
<< ":" << port_->content_name()
<< ":" << local.id() << ":" << local.component()
<< ":" << local.generation()
<< ":" << local.type() << ":" << local.protocol()
<< ":" << local.address().ToSensitiveString()
<< "->" << remote.id() << ":" << remote.component()
<< ":" << remote.priority()
<< ":" << remote.type() << ":"
<< remote.protocol() << ":" << remote.address().ToSensitiveString() << "|"
<< CONNECT_STATE_ABBREV[connected()]
<< RECEIVE_STATE_ABBREV[receiving()]
<< WRITE_STATE_ABBREV[write_state()]
<< ICESTATE[state()] << "|"
<< priority() << "|";
ss << "Conn[" << ToDebugId() << ":" << port_->content_name() << ":"
<< local.id() << ":" << local.component() << ":" << local.generation()
<< ":" << local.type() << ":" << local.protocol() << ":"
<< local.address().ToSensitiveString() << "->" << remote.id() << ":"
<< remote.component() << ":" << remote.priority() << ":" << remote.type()
<< ":" << remote.protocol() << ":" << remote.address().ToSensitiveString()
<< "|" << CONNECT_STATE_ABBREV[connected()]
<< RECEIVE_STATE_ABBREV[receiving()] << WRITE_STATE_ABBREV[write_state()]
<< ICESTATE[state()] << "|" << remote_nomination() << "|" << nomination()
<< "|" << priority() << "|";
if (rtt_ < DEFAULT_RTT) {
ss << rtt_ << "]";
} else {
@ -1330,20 +1349,16 @@ void Connection::OnConnectionRequestResponse(ConnectionRequest* request,
int rtt = request->Elapsed();
ReceivedPingResponse(rtt);
if (LOG_CHECK_LEVEL_V(sev)) {
bool use_candidate = (
response->GetByteString(STUN_ATTR_USE_CANDIDATE) != nullptr);
std::string pings;
PrintPingsSinceLastResponse(&pings, 5);
LOG_JV(sev, this) << "Received STUN ping response"
<< ", id=" << rtc::hex_encode(request->id())
<< ", code=0" // Makes logging easier to parse.
<< ", rtt=" << rtt
<< ", use_candidate=" << use_candidate
<< ", pings_since_last_response=" << pings;
}
ReceivedPingResponse(rtt, request->id());
stats_.recv_ping_responses++;
@ -1390,10 +1405,10 @@ void Connection::OnConnectionRequestTimeout(ConnectionRequest* request) {
void Connection::OnConnectionRequestSent(ConnectionRequest* request) {
// Log at LS_INFO if we send a ping on an unwritable connection.
rtc::LoggingSeverity sev = !writable() ? rtc::LS_INFO : rtc::LS_VERBOSE;
bool use_candidate = use_candidate_attr();
LOG_JV(sev, this) << "Sent STUN ping"
<< ", id=" << rtc::hex_encode(request->id())
<< ", use_candidate=" << use_candidate;
<< ", use_candidate=" << use_candidate_attr()
<< ", nomination=" << nomination();
stats_.sent_ping_requests_total++;
if (stats_.recv_ping_responses == 0) {
stats_.sent_ping_requests_before_first_response++;

View File

@ -417,11 +417,12 @@ class Connection : public CandidatePairInterface,
public sigslot::has_slots<> {
public:
struct SentPing {
SentPing(const std::string id, int64_t sent_time)
: id(id), sent_time(sent_time) {}
SentPing(const std::string id, int64_t sent_time, uint32_t nomination)
: id(id), sent_time(sent_time), nomination(nomination) {}
std::string id;
int64_t sent_time;
uint32_t nomination;
};
// States are from RFC 5245. http://tools.ietf.org/html/rfc5245#section-5.7.4
@ -511,8 +512,16 @@ class Connection : public CandidatePairInterface,
bool use_candidate_attr() const { return use_candidate_attr_; }
void set_use_candidate_attr(bool enable);
bool nominated() const { return nominated_; }
void set_nominated(bool nominated) { nominated_ = nominated; }
void set_nomination(uint32_t value) { nomination_ = value; }
uint32_t remote_nomination() const { return remote_nomination_; }
bool nominated() const { return remote_nomination_ > 0; }
// Public for unit tests.
void set_remote_nomination(uint32_t remote_nomination) {
remote_nomination_ = remote_nomination;
}
// Public for unit tests.
uint32_t acked_nomination() const { return acked_nomination_; }
void set_remote_ice_mode(IceMode mode) {
remote_ice_mode_ = mode;
@ -539,7 +548,7 @@ class Connection : public CandidatePairInterface,
// Called when this connection should try checking writability again.
int64_t last_ping_sent() const { return last_ping_sent_; }
void Ping(int64_t now);
void ReceivedPingResponse(int rtt);
void ReceivedPingResponse(int rtt, const std::string& request_id);
int64_t last_ping_response_received() const {
return last_ping_response_received_;
}
@ -631,11 +640,19 @@ class Connection : public CandidatePairInterface,
void set_state(State state);
void set_connected(bool value);
uint32_t nomination() const { return nomination_; }
void OnMessage(rtc::Message *pmsg);
Port* port_;
size_t local_candidate_index_;
Candidate remote_candidate_;
ConnectionInfo stats_;
rtc::RateTracker recv_rate_tracker_;
rtc::RateTracker send_rate_tracker_;
private:
WriteState write_state_;
bool receiving_;
bool connected_;
@ -645,9 +662,19 @@ class Connection : public CandidatePairInterface,
// But when peer is ice-lite, this flag "must" be initialized to false and
// turn on when connection becomes "best connection".
bool use_candidate_attr_;
// Whether this connection has been nominated by the controlling side via
// the use_candidate attribute.
bool nominated_;
// Used by the controlling side to indicate that this connection will be
// selected for transmission if the peer supports ICE-renomination when this
// value is positive. A larger-value indicates that a connection is nominated
// later and should be selected by the controlled side with higher precedence.
// A zero-value indicates not nominating this connection.
uint32_t nomination_ = 0;
// The last nomination that has been acknowledged.
uint32_t acked_nomination_ = 0;
// Used by the controlled side to remember the nomination value received from
// the controlling side. When the peer does not support ICE re-nomination,
// its value will be 1 if the connection has been nominated.
uint32_t remote_nomination_ = 0;
IceMode remote_ice_mode_;
StunRequestManager requests_;
int rtt_;
@ -660,12 +687,6 @@ class Connection : public CandidatePairInterface,
int64_t receiving_unchanged_since_ = 0;
std::vector<SentPing> pings_since_last_response_;
rtc::RateTracker recv_rate_tracker_;
rtc::RateTracker send_rate_tracker_;
ConnectionInfo stats_;
private:
void MaybeAddPrflxCandidate(ConnectionRequest* request,
StunMessage* response);

View File

@ -606,6 +606,7 @@ enum IceAttributeType {
STUN_ATTR_USE_CANDIDATE = 0x0025, // No content, Length = 0
STUN_ATTR_ICE_CONTROLLED = 0x8029, // UInt64
STUN_ATTR_ICE_CONTROLLING = 0x802A, // UInt64
STUN_ATTR_NOMINATION = 0xC001, // UInt32
// UInt32. The higher 16 bits are the network ID. The lower 16 bits are the
// network cost.
STUN_ATTR_NETWORK_INFO = 0xC057
@ -624,6 +625,7 @@ class IceMessage : public StunMessage {
switch (type) {
case STUN_ATTR_PRIORITY:
case STUN_ATTR_NETWORK_INFO:
case STUN_ATTR_NOMINATION:
return STUN_VALUE_UINT32;
case STUN_ATTR_USE_CANDIDATE: return STUN_VALUE_BYTE_STRING;
case STUN_ATTR_ICE_CONTROLLED: return STUN_VALUE_UINT64;

View File

@ -162,6 +162,15 @@ struct TransportStats {
TransportChannelStatsList channel_stats;
};
// ICE Nomination mode.
enum class NominationMode {
REGULAR, // Nominate once per ICE restart (Not implemented yet).
AGGRESSIVE, // Nominate every connection except that it will behave as if
// REGULAR when the remote is an ICE-LITE endpoint.
SEMI_AGGRESSIVE // Our current implementation of the nomination algorithm.
// The details are described in P2PTransportChannel.
};
// Information about ICE configuration.
// TODO(deadbeef): Use rtc::Optional to represent unset values, instead of
// -1.
@ -199,6 +208,10 @@ struct IceConfig {
// in case that the selected connection may become receiving soon.
rtc::Optional<int> receiving_switching_delay;
// TODO(honghaiz): Change the default to regular nomination.
// Default nomination mode if the remote does not support renomination.
NominationMode default_nomination_mode = NominationMode::SEMI_AGGRESSIVE;
IceConfig() {}
IceConfig(int receiving_timeout_ms,
int backup_connection_ping_interval,