Do not delete the turn port entry right away when the respective connection is deleted.
BUG=webrtc:5120

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

Cr-Commit-Position: refs/heads/master@{#10641}
diff --git a/webrtc/p2p/base/turnport.cc b/webrtc/p2p/base/turnport.cc
index 32f63be..5e88365 100644
--- a/webrtc/p2p/base/turnport.cc
+++ b/webrtc/p2p/base/turnport.cc
@@ -140,6 +140,11 @@
   const rtc::SocketAddress& address() const { return ext_addr_; }
   BindState state() const { return state_; }
 
+  uint32_t destruction_timestamp() { return destruction_timestamp_; }
+  void set_destruction_timestamp(uint32_t destruction_timestamp) {
+    destruction_timestamp_ = destruction_timestamp;
+  }
+
   // Helper methods to send permission and channel bind requests.
   void SendCreatePermissionRequest(int delay);
   void SendChannelBindRequest(int delay);
@@ -160,6 +165,11 @@
   int channel_id_;
   rtc::SocketAddress ext_addr_;
   BindState state_;
+  // A non-zero value indicates that this entry is scheduled to be destroyed.
+  // It is also used as an ID of the event scheduling. When the destruction
+  // event actually fires, the TurnEntry will be destroyed only if the
+  // timestamp here matches the one in the firing event.
+  uint32_t destruction_timestamp_ = 0;
 };
 
 TurnPort::TurnPort(rtc::Thread* thread,
@@ -239,7 +249,7 @@
   }
 
   while (!entries_.empty()) {
-    DestroyEntry(entries_.front()->address());
+    DestroyEntry(entries_.front());
   }
   if (resolver_) {
     resolver_->Destroy(false);
@@ -438,7 +448,7 @@
   }
 
   // Create an entry, if needed, so we can get our permissions set up correctly.
-  CreateEntry(address.address());
+  CreateOrRefreshEntry(address.address());
 
   // A TURN port will have two candiates, STUN and TURN. STUN may not
   // present in all cases. If present stun candidate will be added first
@@ -713,7 +723,6 @@
     }
     return;
   }
-
   Port::OnMessage(message);
 }
 
@@ -898,24 +907,55 @@
   return (it != entries_.end()) ? *it : NULL;
 }
 
-TurnEntry* TurnPort::CreateEntry(const rtc::SocketAddress& addr) {
-  ASSERT(FindEntry(addr) == NULL);
-  TurnEntry* entry = new TurnEntry(this, next_channel_number_++, addr);
-  entries_.push_back(entry);
-  return entry;
+void TurnPort::CreateOrRefreshEntry(const rtc::SocketAddress& addr) {
+  TurnEntry* entry = FindEntry(addr);
+  if (entry == nullptr) {
+    entry = new TurnEntry(this, next_channel_number_++, addr);
+    entries_.push_back(entry);
+  } else {
+    // The channel binding request for the entry will be refreshed automatically
+    // until the entry is destroyed.
+    CancelEntryDestruction(entry);
+  }
 }
 
-void TurnPort::DestroyEntry(const rtc::SocketAddress& addr) {
-  TurnEntry* entry = FindEntry(addr);
+void TurnPort::DestroyEntry(TurnEntry* entry) {
   ASSERT(entry != NULL);
   entry->SignalDestroyed(entry);
   entries_.remove(entry);
   delete entry;
 }
 
+void TurnPort::DestroyEntryIfNotCancelled(TurnEntry* entry,
+                                          uint32_t timestamp) {
+  bool cancelled = timestamp != entry->destruction_timestamp();
+  if (!cancelled) {
+    DestroyEntry(entry);
+  }
+}
+
 void TurnPort::OnConnectionDestroyed(Connection* conn) {
-  // Destroying TurnEntry for the connection, which is already destroyed.
-  DestroyEntry(conn->remote_candidate().address());
+  // Schedule an event to destroy TurnEntry for the connection, which is
+  // already destroyed.
+  const rtc::SocketAddress& remote_address = conn->remote_candidate().address();
+  TurnEntry* entry = FindEntry(remote_address);
+  ASSERT(entry != NULL);
+  ScheduleEntryDestruction(entry);
+}
+
+void TurnPort::ScheduleEntryDestruction(TurnEntry* entry) {
+  ASSERT(entry->destruction_timestamp() == 0);
+  uint32_t timestamp = rtc::Time();
+  entry->set_destruction_timestamp(timestamp);
+  invoker_.AsyncInvokeDelayed<void>(
+      thread(),
+      rtc::Bind(&TurnPort::DestroyEntryIfNotCancelled, this, entry, timestamp),
+      TURN_PERMISSION_TIMEOUT);
+}
+
+void TurnPort::CancelEntryDestruction(TurnEntry* entry) {
+  ASSERT(entry->destruction_timestamp() != 0);
+  entry->set_destruction_timestamp(0);
 }
 
 TurnAllocateRequest::TurnAllocateRequest(TurnPort* port)
diff --git a/webrtc/p2p/base/turnport.h b/webrtc/p2p/base/turnport.h
index 3a1b432..db2a3ce 100644
--- a/webrtc/p2p/base/turnport.h
+++ b/webrtc/p2p/base/turnport.h
@@ -16,9 +16,10 @@
 #include <set>
 #include <string>
 
+#include "webrtc/base/asyncinvoker.h"
+#include "webrtc/base/asyncpacketsocket.h"
 #include "webrtc/p2p/base/port.h"
 #include "webrtc/p2p/client/basicportallocator.h"
-#include "webrtc/base/asyncpacketsocket.h"
 
 namespace rtc {
 class AsyncResolver;
@@ -122,6 +123,9 @@
     return socket_;
   }
 
+  // For testing only.
+  rtc::AsyncInvoker* invoker() { return &invoker_; }
+
   // Signal with resolved server address.
   // Parameters are port, server address and resolved server address.
   // This signal will be sent only if server address is resolved successfully.
@@ -215,8 +219,13 @@
   bool HasPermission(const rtc::IPAddress& ipaddr) const;
   TurnEntry* FindEntry(const rtc::SocketAddress& address) const;
   TurnEntry* FindEntry(int channel_id) const;
-  TurnEntry* CreateEntry(const rtc::SocketAddress& address);
-  void DestroyEntry(const rtc::SocketAddress& address);
+  void CreateOrRefreshEntry(const rtc::SocketAddress& address);
+  void DestroyEntry(TurnEntry* entry);
+  // Destroys the entry only if |timestamp| matches the destruction timestamp
+  // in |entry|.
+  void DestroyEntryIfNotCancelled(TurnEntry* entry, uint32_t timestamp);
+  void ScheduleEntryDestruction(TurnEntry* entry);
+  void CancelEntryDestruction(TurnEntry* entry);
   void OnConnectionDestroyed(Connection* conn);
 
   ProtocolAddress server_address_;
@@ -244,6 +253,8 @@
   // The number of retries made due to allocate mismatch error.
   size_t allocate_mismatch_retries_;
 
+  rtc::AsyncInvoker invoker_;
+
   friend class TurnEntry;
   friend class TurnAllocateRequest;
   friend class TurnRefreshRequest;
diff --git a/webrtc/p2p/base/turnport_unittest.cc b/webrtc/p2p/base/turnport_unittest.cc
index 484b152..dcc88bb 100644
--- a/webrtc/p2p/base/turnport_unittest.cc
+++ b/webrtc/p2p/base/turnport_unittest.cc
@@ -385,6 +385,48 @@
     EXPECT_TRUE(conn2->receiving());
   }
 
+  void TestDestroyTurnConnection() {
+    turn_port_->PrepareAddress();
+    ASSERT_TRUE_WAIT(turn_ready_, kTimeout);
+    // Create a remote UDP port
+    CreateUdpPort();
+    udp_port_->PrepareAddress();
+    ASSERT_TRUE_WAIT(udp_ready_, kTimeout);
+
+    // Create connections on both ends.
+    Connection* conn1 = udp_port_->CreateConnection(turn_port_->Candidates()[0],
+                                                    Port::ORIGIN_MESSAGE);
+    Connection* conn2 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+                                                     Port::ORIGIN_MESSAGE);
+    ASSERT_TRUE(conn2 != NULL);
+    ASSERT_TRUE_WAIT(turn_create_permission_success_, kTimeout);
+    // Make sure turn connection can receive.
+    conn1->Ping(0);
+    EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, conn1->write_state(), kTimeout);
+    EXPECT_FALSE(turn_unknown_address_);
+
+    // Destroy the connection on the turn port. The TurnEntry is still
+    // there. So the turn port gets ping from unknown address if it is pinged.
+    conn2->Destroy();
+    conn1->Ping(0);
+    EXPECT_TRUE_WAIT(turn_unknown_address_, kTimeout);
+
+    // Flush all requests in the invoker to destroy the TurnEntry.
+    // Now the turn port cannot receive the ping.
+    turn_unknown_address_ = false;
+    turn_port_->invoker()->Flush(rtc::Thread::Current());
+    conn1->Ping(0);
+    rtc::Thread::Current()->ProcessMessages(500);
+    EXPECT_FALSE(turn_unknown_address_);
+
+    // If the connection is created again, it will start to receive pings.
+    conn2 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+                                         Port::ORIGIN_MESSAGE);
+    conn1->Ping(0);
+    EXPECT_TRUE_WAIT(conn2->receiving(), kTimeout);
+    EXPECT_FALSE(turn_unknown_address_);
+  }
+
   void TestTurnSendData() {
     turn_port_->PrepareAddress();
     EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
@@ -694,6 +736,20 @@
   TestTurnConnection();
 }
 
+// Test that if a connection on a TURN port is destroyed, the TURN port can
+// still receive ping on that connection as if it is from an unknown address.
+// If the connection is created again, it will be used to receive ping.
+TEST_F(TurnPortTest, TestDestroyTurnConnection) {
+  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+  TestDestroyTurnConnection();
+}
+
+// Similar to above, except that this test will use the shared socket.
+TEST_F(TurnPortTest, TestDestroyTurnConnectionUsingSharedSocket) {
+  CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+  TestDestroyTurnConnection();
+}
+
 // Test that we fail to create a connection when we want to use TLS over TCP.
 // This test should be removed once we have TLS support.
 TEST_F(TurnPortTest, TestTurnTlsTcpConnectionFails) {