First step of passive aggressive nomination.

On the controlled side, a stun request without use-candidate attribute will
be used for sending media.

BUG=4900

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

Cr-Commit-Position: refs/heads/master@{#9747}
This commit is contained in:
honghaiz
2015-08-20 15:53:17 -07:00
committed by Commit bot
parent fe3bc9d5ae
commit 5a3acd8964
5 changed files with 264 additions and 54 deletions

View File

@ -229,8 +229,7 @@ void P2PTransportChannel::AddConnection(Connection* connection) {
this, &P2PTransportChannel::OnConnectionStateChange);
connection->SignalDestroyed.connect(
this, &P2PTransportChannel::OnConnectionDestroyed);
connection->SignalUseCandidate.connect(
this, &P2PTransportChannel::OnUseCandidate);
connection->SignalNominated.connect(this, &P2PTransportChannel::OnNominated);
}
void P2PTransportChannel::SetIceRole(IceRole ice_role) {
@ -522,7 +521,7 @@ void P2PTransportChannel::OnUnknownAddress(
// There shouldn't be an existing connection with this remote address.
// When ports are muxed, this channel might get multiple unknown address
// signals. In that case if the connection is already exists, we should
// simply ignore the signal othewise send server error.
// simply ignore the signal otherwise send server error.
if (port->GetConnection(remote_candidate.address())) {
if (port_muxed) {
LOG(LS_INFO) << "Connection already exists for peer reflexive "
@ -553,6 +552,13 @@ void P2PTransportChannel::OnUnknownAddress(
AddConnection(connection);
connection->ReceivedPing();
bool received_use_candidate =
stun_msg->GetByteString(STUN_ATTR_USE_CANDIDATE) != nullptr;
if (received_use_candidate && ice_role_ == ICEROLE_CONTROLLED) {
connection->set_nominated(true);
OnNominated(connection);
}
// Update the list of connections since we just added another. We do this
// after sending the response since it could (in principle) delete the
// connection in question.
@ -574,7 +580,7 @@ void P2PTransportChannel::OnSignalingReady() {
}
}
void P2PTransportChannel::OnUseCandidate(Connection* conn) {
void P2PTransportChannel::OnNominated(Connection* conn) {
ASSERT(worker_thread_ == rtc::Thread::Current());
ASSERT(ice_role_ == ICEROLE_CONTROLLED);
@ -917,16 +923,10 @@ void P2PTransportChannel::SortConnections() {
// Any changes after this point will require a re-sort.
sort_dirty_ = false;
// Get a list of the networks that we are using.
std::set<rtc::Network*> networks;
for (uint32 i = 0; i < connections_.size(); ++i)
networks.insert(connections_[i]->port()->Network());
// Find the best alternative connection by sorting. It is important to note
// that amongst equal preference, writable connections, this will choose the
// one whose estimated latency is lowest. So it is the only one that we
// need to consider switching to.
ConnectionCompare cmp;
std::stable_sort(connections_.begin(), connections_.end(), cmp);
LOG(LS_VERBOSE) << "Sorting available connections:";
@ -934,46 +934,25 @@ void P2PTransportChannel::SortConnections() {
LOG(LS_VERBOSE) << connections_[i]->ToString();
}
Connection* top_connection = NULL;
if (connections_.size() > 0)
top_connection = connections_[0];
// We don't want to pick the best connections if channel is
// CONTROLLED, as connections will be selected by the CONTROLLING
// agent.
Connection* top_connection =
(connections_.size() > 0) ? connections_[0] : nullptr;
// If necessary, switch to the new choice.
if (ice_role_ == ICEROLE_CONTROLLING) {
if (ShouldSwitch(best_connection_, top_connection)) {
LOG(LS_INFO) << "Switching best connection on controlling side: "
<< top_connection->ToString();
SwitchBestConnectionTo(top_connection);
}
// Note that |top_connection| doesn't have to be writable to become the best
// connection although it will have higher priority if it is writable.
// The controlled side can switch the best connection only if the current
// |best connection_| has not been nominated by the controlling side yet.
if ((ice_role_ == ICEROLE_CONTROLLING || !best_nominated_connection()) &&
ShouldSwitch(best_connection_, top_connection)) {
LOG(LS_INFO) << "Switching best connection: " << top_connection->ToString();
SwitchBestConnectionTo(top_connection);
}
// We can prune any connection for which there is a connected, writable
// connection on the same network with better or equal priority. We leave
// those with better priority just in case they become writable later (at
// which point, we would prune out the current best connection). We leave
// connections on other networks because they may not be using the same
// resources and they may represent very distinct paths over which we can
// switch. If the |primier| connection is not connected, we may be
// reconnecting a TCP connection and temporarily do not prune connections in
// this network. See the big comment in CompareConnections.
std::set<rtc::Network*>::iterator network;
for (network = networks.begin(); network != networks.end(); ++network) {
Connection* primier = GetBestConnectionOnNetwork(*network);
if (!primier || (primier->write_state() != Connection::STATE_WRITABLE) ||
!primier->connected())
continue;
for (uint32 i = 0; i < connections_.size(); ++i) {
if ((connections_[i] != primier) &&
(connections_[i]->port()->Network() == *network) &&
(CompareConnectionCandidates(primier, connections_[i]) >= 0)) {
connections_[i]->Prune();
}
}
// Controlled side can prune only if the best connection has been nominated.
// because otherwise it may delete the connection that will be selected by
// the controlling side.
if (ice_role_ == ICEROLE_CONTROLLING || best_nominated_connection()) {
PruneConnections();
}
// Check if all connections are timedout.
@ -1000,6 +979,41 @@ void P2PTransportChannel::SortConnections() {
UpdateChannelState();
}
Connection* P2PTransportChannel::best_nominated_connection() const {
return (best_connection_ && best_connection_->nominated()) ? best_connection_
: nullptr;
}
void P2PTransportChannel::PruneConnections() {
// We can prune any connection for which there is a connected, writable
// connection on the same network with better or equal priority. We leave
// those with better priority just in case they become writable later (at
// which point, we would prune out the current best connection). We leave
// connections on other networks because they may not be using the same
// resources and they may represent very distinct paths over which we can
// switch. If the |primier| connection is not connected, we may be
// reconnecting a TCP connection and temporarily do not prune connections in
// this network. See the big comment in CompareConnections.
// Get a list of the networks that we are using.
std::set<rtc::Network*> networks;
for (const Connection* conn : connections_) {
networks.insert(conn->port()->Network());
}
for (rtc::Network* network : networks) {
Connection* primier = GetBestConnectionOnNetwork(network);
if (!(primier && primier->writable() && primier->connected())) {
continue;
}
for (Connection* conn : connections_) {
if ((conn != primier) && (conn->port()->Network() == network) &&
(CompareConnectionCandidates(primier, conn) >= 0)) {
conn->Prune();
}
}
}
}
// Track the best connection, and let listeners know
void P2PTransportChannel::SwitchBestConnectionTo(Connection* conn) {
@ -1332,6 +1346,13 @@ void P2PTransportChannel::OnReadPacket(
// Let the client know of an incoming packet
SignalReadPacket(this, data, len, packet_time, 0);
// May need to switch the sending connection based on the receiving media path
// if this is the controlled side.
if (ice_role_ == ICEROLE_CONTROLLED && !best_nominated_connection() &&
connection->writable() && best_connection_ != connection) {
SwitchBestConnectionTo(connection);
}
}
void P2PTransportChannel::OnReadyToSend(Connection* connection) {

View File

@ -211,7 +211,7 @@ class P2PTransportChannel : public TransportChannelImpl,
void OnReadyToSend(Connection* connection);
void OnConnectionDestroyed(Connection *connection);
void OnUseCandidate(Connection* conn);
void OnNominated(Connection* conn);
virtual void OnMessage(rtc::Message *pmsg);
void OnSort();
@ -219,6 +219,9 @@ class P2PTransportChannel : public TransportChannelImpl,
void OnCheckReceiving();
void PruneConnections();
Connection* best_nominated_connection() const;
P2PTransport* transport_;
PortAllocator *allocator_;
rtc::Thread *worker_thread_;

View File

@ -1764,3 +1764,181 @@ TEST_F(P2PTransportChannelPingTest, TestReceivingStateChange) {
EXPECT_TRUE_WAIT(ch.receiving(), 1000);
EXPECT_TRUE_WAIT(!ch.receiving(), 1000);
}
// The controlled side will select a connection as the "best connection" based
// on priority until the controlling side nominates a connection, at which
// point the controlled side will select that connection as the
// "best connection".
TEST_F(P2PTransportChannelPingTest, TestSelectConnectionBeforeNomination) {
cricket::FakePortAllocator pa(rtc::Thread::Current(), nullptr);
cricket::P2PTransportChannel ch("receiving state change", 1, nullptr, &pa);
PrepareChannel(&ch);
ch.SetIceRole(cricket::ICEROLE_CONTROLLED);
ch.Connect();
ch.OnCandidate(CreateCandidate("1.1.1.1", 1, 1));
cricket::Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
ASSERT_TRUE(conn1 != nullptr);
EXPECT_EQ(conn1, ch.best_connection());
// When a higher priority candidate comes in, the new connection is chosen
// as the best connection.
ch.OnCandidate(CreateCandidate("2.2.2.2", 2, 10));
cricket::Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
ASSERT_TRUE(conn1 != nullptr);
EXPECT_EQ(conn2, ch.best_connection());
// If a stun request with use-candidate attribute arrives, the receiving
// connection will be set as the best connection, even though
// its priority is lower.
ch.OnCandidate(CreateCandidate("3.3.3.3", 3, 1));
cricket::Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3);
ASSERT_TRUE(conn3 != nullptr);
// Because it has a lower priority, the best connection is still conn2.
EXPECT_EQ(conn2, ch.best_connection());
conn3->ReceivedPingResponse(); // Become writable.
// But if it is nominated via use_candidate, it is chosen as the best
// connection.
conn3->set_nominated(true);
conn3->SignalNominated(conn3);
EXPECT_EQ(conn3, ch.best_connection());
// Even if another higher priority candidate arrives,
// it will not be set as the best connection because the best connection
// is nominated by the controlling side.
ch.OnCandidate(CreateCandidate("4.4.4.4", 4, 100));
cricket::Connection* conn4 = WaitForConnectionTo(&ch, "4.4.4.4", 4);
ASSERT_TRUE(conn4 != nullptr);
EXPECT_EQ(conn3, ch.best_connection());
// But if it is nominated via use_candidate and writable, it will be set as
// the best connection.
conn4->set_nominated(true);
conn4->SignalNominated(conn4);
// Not switched yet because conn4 is not writable.
EXPECT_EQ(conn3, ch.best_connection());
// The best connection switches after conn4 becomes writable.
conn4->ReceivedPingResponse();
EXPECT_EQ(conn4, ch.best_connection());
}
// The controlled side will select a connection as the "best connection" based
// on requests from an unknown address before the controlling side nominates
// a connection, and will nominate a connection from an unknown address if the
// request contains the use_candidate attribute.
TEST_F(P2PTransportChannelPingTest, TestSelectConnectionFromUnknownAddress) {
cricket::FakePortAllocator pa(rtc::Thread::Current(), nullptr);
cricket::P2PTransportChannel ch("receiving state change", 1, nullptr, &pa);
PrepareChannel(&ch);
ch.SetIceRole(cricket::ICEROLE_CONTROLLED);
ch.Connect();
// A minimal STUN message with prflx priority.
cricket::IceMessage request;
request.SetType(cricket::STUN_BINDING_REQUEST);
request.AddAttribute(new cricket::StunByteStringAttribute(
cricket::STUN_ATTR_USERNAME, kIceUfrag[1]));
uint32 prflx_priority = cricket::ICE_TYPE_PREFERENCE_PRFLX << 24;
request.AddAttribute(new cricket::StunUInt32Attribute(
cricket::STUN_ATTR_PRIORITY, prflx_priority));
cricket::Port* port = GetPort(&ch);
port->SignalUnknownAddress(port, rtc::SocketAddress("1.1.1.1", 1),
cricket::PROTO_UDP, &request, kIceUfrag[1], false);
cricket::Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
ASSERT_TRUE(conn1 != nullptr);
EXPECT_EQ(conn1, ch.best_connection());
conn1->ReceivedPingResponse();
EXPECT_EQ(conn1, ch.best_connection());
// Another connection is nominated via use_candidate.
ch.OnCandidate(CreateCandidate("2.2.2.2", 2, 1));
cricket::Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
ASSERT_TRUE(conn2 != nullptr);
// Because it has a lower priority, the best connection is still conn1.
EXPECT_EQ(conn1, ch.best_connection());
// When it is nominated via use_candidate and writable, it is chosen as the
// best connection.
conn2->ReceivedPingResponse(); // Become writable.
conn2->set_nominated(true);
conn2->SignalNominated(conn2);
EXPECT_EQ(conn2, ch.best_connection());
// Another request with unknown address, it will not be set as the best
// connection because the best connection was nominated by the controlling
// side.
port->SignalUnknownAddress(port, rtc::SocketAddress("3.3.3.3", 3),
cricket::PROTO_UDP, &request, kIceUfrag[1], false);
cricket::Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3);
ASSERT_TRUE(conn3 != nullptr);
conn3->ReceivedPingResponse(); // Become writable.
EXPECT_EQ(conn2, ch.best_connection());
// However if the request contains use_candidate attribute, it will be
// selected as the best connection.
request.AddAttribute(
new cricket::StunByteStringAttribute(cricket::STUN_ATTR_USE_CANDIDATE));
port->SignalUnknownAddress(port, rtc::SocketAddress("4.4.4.4", 4),
cricket::PROTO_UDP, &request, kIceUfrag[1], false);
cricket::Connection* conn4 = WaitForConnectionTo(&ch, "4.4.4.4", 4);
ASSERT_TRUE(conn4 != nullptr);
// conn4 is not the best connection yet because it is not writable.
EXPECT_EQ(conn2, ch.best_connection());
conn4->ReceivedPingResponse(); // Become writable.
EXPECT_EQ(conn4, ch.best_connection());
}
// The controlled side will select a connection as the "best connection"
// based on media received until the controlling side nominates a connection,
// at which point the controlled side will select that connection as
// the "best connection".
TEST_F(P2PTransportChannelPingTest, TestSelectConnectionBasedOnMediaReceived) {
cricket::FakePortAllocator pa(rtc::Thread::Current(), nullptr);
cricket::P2PTransportChannel ch("receiving state change", 1, nullptr, &pa);
PrepareChannel(&ch);
ch.SetIceRole(cricket::ICEROLE_CONTROLLED);
ch.Connect();
ch.OnCandidate(CreateCandidate("1.1.1.1", 1, 10));
cricket::Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
ASSERT_TRUE(conn1 != nullptr);
EXPECT_EQ(conn1, ch.best_connection());
// If a data packet is received on conn2, the best connection should
// switch to conn2 because the controlled side must mirror the media path
// chosen by the controlling side.
ch.OnCandidate(CreateCandidate("2.2.2.2", 2, 1));
cricket::Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
ASSERT_TRUE(conn2 != nullptr);
conn2->ReceivedPing(); // Become readable.
// Do not switch because it is not writable.
conn2->OnReadPacket("ABC", 3, rtc::CreatePacketTime(0));
EXPECT_EQ(conn1, ch.best_connection());
conn2->ReceivedPingResponse(); // Become writable.
// Switch because it is writable.
conn2->OnReadPacket("DEF", 3, rtc::CreatePacketTime(0));
EXPECT_EQ(conn2, ch.best_connection());
// Now another STUN message with an unknown address and use_candidate will
// nominate the best connection.
cricket::IceMessage request;
request.SetType(cricket::STUN_BINDING_REQUEST);
request.AddAttribute(new cricket::StunByteStringAttribute(
cricket::STUN_ATTR_USERNAME, kIceUfrag[1]));
uint32 prflx_priority = cricket::ICE_TYPE_PREFERENCE_PRFLX << 24;
request.AddAttribute(new cricket::StunUInt32Attribute(
cricket::STUN_ATTR_PRIORITY, prflx_priority));
request.AddAttribute(
new cricket::StunByteStringAttribute(cricket::STUN_ATTR_USE_CANDIDATE));
cricket::Port* port = GetPort(&ch);
port->SignalUnknownAddress(port, rtc::SocketAddress("3.3.3.3", 3),
cricket::PROTO_UDP, &request, kIceUfrag[1], false);
cricket::Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3);
ASSERT_TRUE(conn3 != nullptr);
EXPECT_EQ(conn2, ch.best_connection()); // Not writable yet.
conn3->ReceivedPingResponse(); // Become writable.
EXPECT_EQ(conn3, ch.best_connection());
// Now another data packet will not switch the best connection because the
// best connection was nominated by the controlling side.
conn2->ReceivedPing();
conn2->ReceivedPingResponse();
conn2->OnReadPacket("XYZ", 3, rtc::CreatePacketTime(0));
EXPECT_EQ(conn3, ch.best_connection());
}

View File

@ -787,6 +787,7 @@ Connection::Connection(Port* port,
connected_(true),
pruned_(false),
use_candidate_attr_(false),
nominated_(false),
remote_ice_mode_(ICEMODE_FULL),
requests_(port->thread()),
rtt_(DEFAULT_RTT),
@ -954,8 +955,10 @@ void Connection::OnReadPacket(
if (port_->GetIceRole() == ICEROLE_CONTROLLED) {
const StunByteStringAttribute* use_candidate_attr =
msg->GetByteString(STUN_ATTR_USE_CANDIDATE);
if (use_candidate_attr)
SignalUseCandidate(this);
if (use_candidate_attr) {
set_nominated(true);
SignalNominated(this);
}
}
} else {
// The packet had the right local username, but the remote username

View File

@ -488,6 +488,9 @@ class Connection : public rtc::MessageHandler,
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_remote_ice_mode(IceMode mode) {
remote_ice_mode_ = mode;
}
@ -519,10 +522,9 @@ class Connection : public rtc::MessageHandler,
bool reported() const { return reported_; }
void set_reported(bool reported) { reported_ = reported;}
// This flag will be set if this connection is the chosen one for media
// transmission. This connection will send STUN ping with USE-CANDIDATE
// attribute.
sigslot::signal1<Connection*> SignalUseCandidate;
// This signal will be fired if this connection is nominated by the
// controlling side.
sigslot::signal1<Connection*> SignalNominated;
// Invoked when Connection receives STUN error response with 487 code.
void HandleRoleConflictFromPeer();
@ -581,10 +583,13 @@ class Connection : public rtc::MessageHandler,
bool connected_;
bool pruned_;
// By default |use_candidate_attr_| flag will be true,
// as we will be using agrressive nomination.
// as we will be using aggressive nomination.
// 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_;
IceMode remote_ice_mode_;
StunRequestManager requests_;
uint32 rtt_;