Fix Turn TCP port issue.

Sometimes the port still try to send stun packet when the connection is disconnected,
causing an assertion error.

BUG=4859

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

Cr-Commit-Position: refs/heads/master@{#9671}
diff --git a/webrtc/base/asynctcpsocket.cc b/webrtc/base/asynctcpsocket.cc
index 0f7abd5..97d1e17 100644
--- a/webrtc/base/asynctcpsocket.cc
+++ b/webrtc/base/asynctcpsocket.cc
@@ -127,10 +127,11 @@
 int AsyncTCPSocketBase::SendTo(const void *pv, size_t cb,
                                const SocketAddress& addr,
                                const rtc::PacketOptions& options) {
-  if (addr == GetRemoteAddress())
+  const SocketAddress& remote_address = GetRemoteAddress();
+  if (addr == remote_address)
     return Send(pv, cb, options);
-
-  ASSERT(false);
+  // Remote address may be empty if there is a sudden network change.
+  ASSERT(remote_address.IsNil());
   socket_->SetError(ENOTCONN);
   return -1;
 }
diff --git a/webrtc/p2p/base/turnport.cc b/webrtc/p2p/base/turnport.cc
index 27f88dc..2c9cd13 100644
--- a/webrtc/p2p/base/turnport.cc
+++ b/webrtc/p2p/base/turnport.cc
@@ -172,8 +172,12 @@
                    const RelayCredentials& credentials,
                    int server_priority,
                    const std::string& origin)
-    : Port(thread, factory, network, socket->GetLocalAddress().ipaddr(),
-           username, password),
+    : Port(thread,
+           factory,
+           network,
+           socket->GetLocalAddress().ipaddr(),
+           username,
+           password),
       server_address_(server_address),
       credentials_(credentials),
       socket_(socket),
@@ -181,7 +185,7 @@
       error_(0),
       request_manager_(thread),
       next_channel_number_(TURN_CHANNEL_NUMBER_START),
-      connected_(false),
+      state_(STATE_CONNECTING),
       server_priority_(server_priority),
       allocate_mismatch_retries_(0) {
   request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket);
@@ -200,8 +204,15 @@
                    const RelayCredentials& credentials,
                    int server_priority,
                    const std::string& origin)
-    : Port(thread, RELAY_PORT_TYPE, factory, network, ip, min_port, max_port,
-           username, password),
+    : Port(thread,
+           RELAY_PORT_TYPE,
+           factory,
+           network,
+           ip,
+           min_port,
+           max_port,
+           username,
+           password),
       server_address_(server_address),
       credentials_(credentials),
       socket_(NULL),
@@ -209,7 +220,7 @@
       error_(0),
       request_manager_(thread),
       next_channel_number_(TURN_CHANNEL_NUMBER_START),
-      connected_(false),
+      state_(STATE_CONNECTING),
       server_priority_(server_priority),
       allocate_mismatch_retries_(0) {
   request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket);
@@ -221,7 +232,7 @@
 
   // release the allocation by sending a refresh with
   // lifetime 0.
-  if (connected_) {
+  if (ready()) {
     TurnRefreshRequest bye(this);
     bye.set_lifetime(0);
     SendRequest(&bye, 0);
@@ -261,8 +272,9 @@
   } else {
     // If protocol family of server address doesn't match with local, return.
     if (!IsCompatibleAddress(server_address_.address)) {
-      LOG(LS_ERROR) << "Server IP address family does not match with "
-                    << "local host address family type";
+      LOG(LS_ERROR) << "IP address family does not match: "
+                    << "server: " << server_address_.address.family()
+                    << "local: " << ip().family();
       OnAllocateError();
       return;
     }
@@ -274,8 +286,11 @@
                          << ProtoToString(server_address_.proto) << " @ "
                          << server_address_.address.ToSensitiveString();
     if (!CreateTurnClientSocket()) {
+      LOG(LS_ERROR) << "Failed to create TURN client socket";
       OnAllocateError();
-    } else if (server_address_.proto == PROTO_UDP) {
+      return;
+    }
+    if (server_address_.proto == PROTO_UDP) {
       // If its UDP, send AllocateRequest now.
       // For TCP and TLS AllcateRequest will be sent by OnSocketConnect.
       SendRequest(new TurnAllocateRequest(this), 0);
@@ -319,9 +334,13 @@
 
   socket_->SignalReadyToSend.connect(this, &TurnPort::OnReadyToSend);
 
+  // TCP port is ready to send stun requests after the socket is connected,
+  // while UDP port is ready to do so once the socket is created.
   if (server_address_.proto == PROTO_TCP) {
     socket_->SignalConnect.connect(this, &TurnPort::OnSocketConnect);
     socket_->SignalClose.connect(this, &TurnPort::OnSocketClose);
+  } else {
+    state_ = STATE_CONNECTED;
   }
   return true;
 }
@@ -360,6 +379,7 @@
     }
   }
 
+  state_ = STATE_CONNECTED;  // It is ready to send stun requests.
   if (server_address_.address.IsUnresolved()) {
     server_address_.address = socket_->GetRemoteAddress();
   }
@@ -372,10 +392,11 @@
 void TurnPort::OnSocketClose(rtc::AsyncPacketSocket* socket, int error) {
   LOG_J(LS_WARNING, this) << "Connection with server failed, error=" << error;
   ASSERT(socket == socket_);
-  if (!connected_) {
+  if (!ready()) {
     OnAllocateError();
   }
-  connected_ = false;
+  request_manager_.Clear();
+  state_ = STATE_DISCONNECTED;
 }
 
 void TurnPort::OnAllocateMismatch() {
@@ -412,6 +433,10 @@
     return NULL;
   }
 
+  if (state_ == STATE_DISCONNECTED) {
+    return NULL;
+  }
+
   // Create an entry, if needed, so we can get our permissions set up correctly.
   CreateEntry(address.address());
 
@@ -462,12 +487,12 @@
                      bool payload) {
   // Try to find an entry for this specific address; we should have one.
   TurnEntry* entry = FindEntry(addr);
-  ASSERT(entry != NULL);
   if (!entry) {
+    LOG(LS_ERROR) << "Did not find the TurnEntry for address " << addr;
     return 0;
   }
 
-  if (!connected()) {
+  if (!ready()) {
     error_ = EWOULDBLOCK;
     return SOCKET_ERROR;
   }
@@ -536,7 +561,7 @@
 }
 
 void TurnPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
-  if (connected_) {
+  if (ready()) {
     Port::OnReadyToSend();
   }
 }
@@ -616,6 +641,7 @@
 
 void TurnPort::OnSendStunPacket(const void* data, size_t size,
                                 StunRequest* request) {
+  ASSERT(connected());
   rtc::PacketOptions options(DefaultDscpValue());
   if (Send(data, size, options) < 0) {
     LOG_J(LS_ERROR, this) << "Failed to send TURN message, err="
@@ -635,7 +661,7 @@
 
 void TurnPort::OnAllocateSuccess(const rtc::SocketAddress& address,
                                  const rtc::SocketAddress& stun_address) {
-  connected_ = true;
+  state_ = STATE_READY;
 
   rtc::SocketAddress related_address = stun_address;
     if (!(candidate_filter() & CF_REFLEXIVE)) {
diff --git a/webrtc/p2p/base/turnport.h b/webrtc/p2p/base/turnport.h
index 5bb7558..52546e0 100644
--- a/webrtc/p2p/base/turnport.h
+++ b/webrtc/p2p/base/turnport.h
@@ -33,6 +33,12 @@
 
 class TurnPort : public Port {
  public:
+  enum PortState {
+    STATE_CONNECTING,    // Initial state, cannot send any packets.
+    STATE_CONNECTED,     // Socket connected, ready to send stun requests.
+    STATE_READY,         // Received allocate success, can send any packets.
+    STATE_DISCONNECTED,  // TCP connection died, cannot send any packets.
+  };
   static TurnPort* Create(rtc::Thread* thread,
                           rtc::PacketSocketFactory* factory,
                           rtc::Network* network,
@@ -70,7 +76,10 @@
   // Returns an empty address if the local address has not been assigned.
   rtc::SocketAddress GetLocalAddress() const;
 
-  bool connected() const { return connected_; }
+  bool ready() const { return state_ == STATE_READY; }
+  bool connected() const {
+    return state_ == STATE_READY || state_ == STATE_CONNECTED;
+  }
   const RelayCredentials& credentials() const { return credentials_; }
 
   virtual void PrepareAddress();
@@ -225,7 +234,7 @@
   int next_channel_number_;
   EntryList entries_;
 
-  bool connected_;
+  PortState state_;
   // By default the value will be set to 0. This value will be used in
   // calculating the candidate priority.
   int server_priority_;
diff --git a/webrtc/p2p/base/turnport_unittest.cc b/webrtc/p2p/base/turnport_unittest.cc
index 3172ba2..00a62fa 100644
--- a/webrtc/p2p/base/turnport_unittest.cc
+++ b/webrtc/p2p/base/turnport_unittest.cc
@@ -627,6 +627,28 @@
   EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress());
 }
 
+// Test that CreateConnection will return null if port becomes disconnected.
+TEST_F(TurnPortTest, TestCreateConnectionWhenSocketClosed) {
+  turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP);
+  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+  turn_port_->PrepareAddress();
+  ASSERT_TRUE_WAIT(turn_ready_, kTimeout);
+
+  CreateUdpPort();
+  udp_port_->PrepareAddress();
+  ASSERT_TRUE_WAIT(udp_ready_, kTimeout);
+  // Create a connection.
+  Connection* conn1 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+                                                   Port::ORIGIN_MESSAGE);
+  ASSERT_TRUE(conn1 != NULL);
+
+  // Close the socket and create a connection again.
+  turn_port_->OnSocketClose(turn_port_->socket(), 1);
+  conn1 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+                                       Port::ORIGIN_MESSAGE);
+  ASSERT_TRUE(conn1 == NULL);
+}
+
 // Test try-alternate-server feature.
 TEST_F(TurnPortTest, TestTurnAlternateServerUDP) {
   TestTurnAlternateServer(cricket::PROTO_UDP);