Add support for trying alternate server (STUN 300 error message) on TCP

BUG=3774
R=juberti@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/32979004

git-svn-id: http://webrtc.googlecode.com/svn/trunk/webrtc@8036 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/p2p/base/turnport.cc b/p2p/base/turnport.cc
index 2ea3ff4..37f0c11 100644
--- a/p2p/base/turnport.cc
+++ b/p2p/base/turnport.cc
@@ -348,6 +348,7 @@
 
 void TurnPort::OnSocketClose(rtc::AsyncPacketSocket* socket, int error) {
   LOG_J(LS_WARNING, this) << "Connection with server failed, error=" << error;
+  ASSERT(socket == socket_);
   if (!connected_) {
     OnAllocateError();
   }
@@ -645,6 +646,22 @@
   } else if (message->message_id == MSG_ALLOCATE_MISMATCH) {
     OnAllocateMismatch();
     return;
+  } else if (message->message_id == MSG_TRY_ALTERNATE_SERVER) {
+    if (server_address().proto == PROTO_UDP) {
+      // Send another allocate request to alternate server, with the received
+      // realm and nonce values.
+      SendRequest(new TurnAllocateRequest(this), 0);
+    } else {
+      // Since it's TCP, we have to delete the connected socket and reconnect
+      // with the alternate server. PrepareAddress will send stun binding once
+      // the new socket is connected.
+      ASSERT(server_address().proto == PROTO_TCP);
+      ASSERT(!SharedSocket());
+      delete socket_;
+      socket_ = NULL;
+      PrepareAddress();
+    }
+    return;
   }
 
   Port::OnMessage(message);
@@ -959,15 +976,6 @@
 }
 
 void TurnAllocateRequest::OnTryAlternate(StunMessage* response, int code) {
-  // TODO(guoweis): Currently, we only support UDP redirect
-  if (port_->server_address().proto != PROTO_UDP) {
-    LOG_J(LS_WARNING, port_) << "Receiving 300 Alternate Server on non-UDP "
-                         << "allocating request from ["
-                         << port_->server_address().address.ToSensitiveString()
-                         << "], failed as currently not supported";
-    port_->OnAllocateError();
-    return;
-  }
 
   // According to RFC 5389 section 11, there are use cases where
   // authentication of response is not possible, we're not validating
@@ -1004,9 +1012,10 @@
     port_->set_nonce(nonce_attr->GetString());
   }
 
-  // Send another allocate request to alternate server,
-  // with the received realm and nonce values.
-  port_->SendRequest(new TurnAllocateRequest(port_), 0);
+  // For TCP, we can't close the original Tcp socket during handling a 300 as
+  // we're still inside that socket's event handler. Doing so will cause
+  // deadlock.
+  port_->thread()->Post(port_, TurnPort::MSG_TRY_ALTERNATE_SERVER);
 }
 
 TurnRefreshRequest::TurnRefreshRequest(TurnPort* port)
diff --git a/p2p/base/turnport.h b/p2p/base/turnport.h
index 3d3dad3..5bb7558 100644
--- a/p2p/base/turnport.h
+++ b/p2p/base/turnport.h
@@ -152,7 +152,8 @@
  private:
   enum {
     MSG_ERROR = MSG_FIRST_AVAILABLE,
-    MSG_ALLOCATE_MISMATCH
+    MSG_ALLOCATE_MISMATCH,
+    MSG_TRY_ALTERNATE_SERVER
   };
 
   typedef std::list<TurnEntry*> EntryList;
diff --git a/p2p/base/turnport_unittest.cc b/p2p/base/turnport_unittest.cc
index e871aca..f84d106 100644
--- a/p2p/base/turnport_unittest.cc
+++ b/p2p/base/turnport_unittest.cc
@@ -47,8 +47,13 @@
 static const SocketAddress kTurnTcpIntAddr("99.99.99.4",
                                            cricket::TURN_SERVER_PORT);
 static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0);
-static const SocketAddress kTurnAlternateUdpIntAddr(
-    "99.99.99.6", cricket::TURN_SERVER_PORT);
+static const SocketAddress kTurnAlternateIntAddr("99.99.99.6",
+                                                 cricket::TURN_SERVER_PORT);
+static const SocketAddress kTurnIntAddr("99.99.99.7",
+                                        cricket::TURN_SERVER_PORT);
+static const SocketAddress kTurnIPv6IntAddr(
+    "2400:4030:2:2c00:be30:abcd:efab:cdef",
+    cricket::TURN_SERVER_PORT);
 static const SocketAddress kTurnUdpIPv6IntAddr(
     "2400:4030:1:2c00:be30:abcd:efab:cdef", cricket::TURN_SERVER_PORT);
 static const SocketAddress kTurnUdpIPv6ExtAddr(
@@ -276,6 +281,87 @@
         this, &TurnPortTest::OnUdpPortComplete);
   }
 
+  void TestTurnAlternateServer(cricket::ProtocolType protocol_type) {
+    std::vector<rtc::SocketAddress> redirect_addresses;
+    redirect_addresses.push_back(kTurnAlternateIntAddr);
+
+    cricket::TestTurnRedirector redirector(redirect_addresses);
+
+    turn_server_.AddInternalSocket(kTurnIntAddr, protocol_type);
+    turn_server_.AddInternalSocket(kTurnAlternateIntAddr, protocol_type);
+    turn_server_.set_redirect_hook(&redirector);
+    CreateTurnPort(kTurnUsername, kTurnPassword,
+                   cricket::ProtocolAddress(kTurnIntAddr, protocol_type));
+
+    // Retrieve the address before we run the state machine.
+    const SocketAddress old_addr = turn_port_->server_address().address;
+
+    turn_port_->PrepareAddress();
+    EXPECT_TRUE_WAIT(turn_ready_, kTimeout * 100);
+    // Retrieve the address again, the turn port's address should be
+    // changed.
+    const SocketAddress new_addr = turn_port_->server_address().address;
+    EXPECT_NE(old_addr, new_addr);
+    ASSERT_EQ(1U, turn_port_->Candidates().size());
+    EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+              turn_port_->Candidates()[0].address().ipaddr());
+    EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
+  }
+
+  void TestTurnAlternateServerV4toV6(cricket::ProtocolType protocol_type) {
+    std::vector<rtc::SocketAddress> redirect_addresses;
+    redirect_addresses.push_back(kTurnIPv6IntAddr);
+
+    cricket::TestTurnRedirector redirector(redirect_addresses);
+    turn_server_.AddInternalSocket(kTurnIntAddr, protocol_type);
+    turn_server_.set_redirect_hook(&redirector);
+    CreateTurnPort(kTurnUsername, kTurnPassword,
+                   cricket::ProtocolAddress(kTurnIntAddr, protocol_type));
+    turn_port_->PrepareAddress();
+    EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+  }
+
+  void TestTurnAlternateServerPingPong(cricket::ProtocolType protocol_type) {
+    std::vector<rtc::SocketAddress> redirect_addresses;
+    redirect_addresses.push_back(kTurnAlternateIntAddr);
+    redirect_addresses.push_back(kTurnIntAddr);
+
+    cricket::TestTurnRedirector redirector(redirect_addresses);
+
+    turn_server_.AddInternalSocket(kTurnIntAddr, protocol_type);
+    turn_server_.AddInternalSocket(kTurnAlternateIntAddr, protocol_type);
+    turn_server_.set_redirect_hook(&redirector);
+    CreateTurnPort(kTurnUsername, kTurnPassword,
+                   cricket::ProtocolAddress(kTurnIntAddr, protocol_type));
+
+    turn_port_->PrepareAddress();
+    EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+    ASSERT_EQ(0U, turn_port_->Candidates().size());
+    rtc::SocketAddress address;
+    // Verify that we have exhausted all alternate servers instead of
+    // failure caused by other errors.
+    EXPECT_FALSE(redirector.ShouldRedirect(address, &address));
+  }
+
+  void TestTurnAlternateServerDetectRepetition(
+      cricket::ProtocolType protocol_type) {
+    std::vector<rtc::SocketAddress> redirect_addresses;
+    redirect_addresses.push_back(kTurnAlternateIntAddr);
+    redirect_addresses.push_back(kTurnAlternateIntAddr);
+
+    cricket::TestTurnRedirector redirector(redirect_addresses);
+
+    turn_server_.AddInternalSocket(kTurnIntAddr, protocol_type);
+    turn_server_.AddInternalSocket(kTurnAlternateIntAddr, protocol_type);
+    turn_server_.set_redirect_hook(&redirector);
+    CreateTurnPort(kTurnUsername, kTurnPassword,
+                   cricket::ProtocolAddress(kTurnIntAddr, protocol_type));
+
+    turn_port_->PrepareAddress();
+    EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+    ASSERT_EQ(0U, turn_port_->Candidates().size());
+  }
+
   void TestTurnConnection() {
     // Create ports and prepare addresses.
     ASSERT_TRUE(turn_port_ != NULL);
@@ -529,6 +615,43 @@
   EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress());
 }
 
+// Test try-alternate-server feature.
+TEST_F(TurnPortTest, TestTurnAlternateServerUDP) {
+  TestTurnAlternateServer(cricket::PROTO_UDP);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerTCP) {
+  TestTurnAlternateServer(cricket::PROTO_TCP);
+}
+
+// Test that we fail when we redirect to an address different from
+// current IP family.
+TEST_F(TurnPortTest, TestTurnAlternateServerV4toV6UDP) {
+  TestTurnAlternateServerV4toV6(cricket::PROTO_UDP);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerV4toV6TCP) {
+  TestTurnAlternateServerV4toV6(cricket::PROTO_TCP);
+}
+
+// Test try-alternate-server catches the case of pingpong.
+TEST_F(TurnPortTest, TestTurnAlternateServerPingPongUDP) {
+  TestTurnAlternateServerPingPong(cricket::PROTO_UDP);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerPingPongTCP) {
+  TestTurnAlternateServerPingPong(cricket::PROTO_TCP);
+}
+
+// Test try-alternate-server catch the case of repeated server.
+TEST_F(TurnPortTest, TestTurnAlternateServerDetectRepetitionUDP) {
+  TestTurnAlternateServerDetectRepetition(cricket::PROTO_UDP);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerDetectRepetitionTCP) {
+  TestTurnAlternateServerDetectRepetition(cricket::PROTO_TCP);
+}
+
 // Do a TURN allocation and try to send a packet to it from the outside.
 // The packet should be dropped. Then, try to send a packet from TURN to the
 // outside. It should reach its destination. Finally, try again from the
@@ -563,103 +686,6 @@
   ASSERT_EQ(0U, turn_port_->Candidates().size());
 }
 
-// Test try-alternate-server feature.
-TEST_F(TurnPortTest, TestTurnAlternateServer) {
-  std::vector<rtc::SocketAddress> redirect_addresses;
-  redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
-
-  cricket::TestTurnRedirector redirector(redirect_addresses);
-  turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr,
-                                 cricket::PROTO_UDP);
-  turn_server_.set_redirect_hook(&redirector);
-  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
-
-  // Retrieve the address before we run the state machine.
-  const SocketAddress old_addr = turn_port_->server_address().address;
-
-  turn_port_->PrepareAddress();
-  EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
-  // Retrieve the address again, the turn port's address should be
-  // changed.
-  const SocketAddress new_addr = turn_port_->server_address().address;
-  EXPECT_NE(old_addr, new_addr);
-  ASSERT_EQ(1U, turn_port_->Candidates().size());
-  EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
-            turn_port_->Candidates()[0].address().ipaddr());
-  EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
-}
-
-// Test that we fail when we redirect to an address different from
-// current IP family.
-TEST_F(TurnPortTest, TestTurnAlternateServerV4toV6) {
-  std::vector<rtc::SocketAddress> redirect_addresses;
-  redirect_addresses.push_back(kTurnUdpIPv6IntAddr);
-
-  cricket::TestTurnRedirector redirector(redirect_addresses);
-  turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr,
-                                 cricket::PROTO_UDP);
-  turn_server_.set_redirect_hook(&redirector);
-  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
-  turn_port_->PrepareAddress();
-  EXPECT_TRUE_WAIT(turn_error_, kTimeout);
-}
-
-// Test that we fail to handle alternate-server response over TCP protocol.
-TEST_F(TurnPortTest, TestTurnAlternateServerTcp) {
-  std::vector<rtc::SocketAddress> redirect_addresses;
-  redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
-
-  cricket::TestTurnRedirector redirector(redirect_addresses);
-  turn_server_.set_redirect_hook(&redirector);
-  turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP);
-  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
-
-  turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr, cricket::PROTO_TCP);
-  turn_port_->PrepareAddress();
-  EXPECT_TRUE_WAIT(turn_error_, kTimeout);
-}
-
-// Test try-alternate-server catches the case of pingpong.
-TEST_F(TurnPortTest, TestTurnAlternateServerPingPong) {
-  std::vector<rtc::SocketAddress> redirect_addresses;
-  redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
-  redirect_addresses.push_back(kTurnUdpIntAddr);
-
-  cricket::TestTurnRedirector redirector(redirect_addresses);
-
-  turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr,
-                                 cricket::PROTO_UDP);
-  turn_server_.set_redirect_hook(&redirector);
-  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
-
-  turn_port_->PrepareAddress();
-  EXPECT_TRUE_WAIT(turn_error_, kTimeout);
-  ASSERT_EQ(0U, turn_port_->Candidates().size());
-  rtc::SocketAddress address;
-  // Verify that we have exhausted all alternate servers instead of
-  // failure caused by other errors.
-  EXPECT_FALSE(redirector.ShouldRedirect(address, &address));
-}
-
-// Test try-alternate-server catch the case of repeated server.
-TEST_F(TurnPortTest, TestTurnAlternateServerDetectRepetition) {
-  std::vector<rtc::SocketAddress> redirect_addresses;
-  redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
-  redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
-
-  cricket::TestTurnRedirector redirector(redirect_addresses);
-
-  turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr,
-                                 cricket::PROTO_UDP);
-  turn_server_.set_redirect_hook(&redirector);
-  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
-
-  turn_port_->PrepareAddress();
-  EXPECT_TRUE_WAIT(turn_error_, kTimeout);
-  ASSERT_EQ(0U, turn_port_->Candidates().size());
-}
-
-
 // Run TurnConnectionTest with one-time-use nonce feature.
 // Here server will send a 438 STALE_NONCE error message for
 // every TURN transaction.