From 19e4e8d751478d34c05bc2d73e065429ed65a1c1 Mon Sep 17 00:00:00 2001 From: "guoweis@webrtc.org" Date: Sat, 10 Jan 2015 02:41:32 +0000 Subject: [PATCH] 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@8036 4adac7df-926f-26a2-2b94-8c16560cd09d --- webrtc/p2p/base/turnport.cc | 33 ++-- webrtc/p2p/base/turnport.h | 3 +- webrtc/p2p/base/turnport_unittest.cc | 224 +++++++++++++++------------ 3 files changed, 148 insertions(+), 112 deletions(-) diff --git a/webrtc/p2p/base/turnport.cc b/webrtc/p2p/base/turnport.cc index 2ea3ff445..37f0c1111 100644 --- a/webrtc/p2p/base/turnport.cc +++ b/webrtc/p2p/base/turnport.cc @@ -348,6 +348,7 @@ void TurnPort::OnSocketConnect(rtc::AsyncPacketSocket* socket) { 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 @@ void TurnPort::OnMessage(rtc::Message* message) { } 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::OnAuthChallenge(StunMessage* response, int code) { } 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 @@ void TurnAllocateRequest::OnTryAlternate(StunMessage* response, int code) { 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/webrtc/p2p/base/turnport.h b/webrtc/p2p/base/turnport.h index 3d3dad39e..5bb755859 100644 --- a/webrtc/p2p/base/turnport.h +++ b/webrtc/p2p/base/turnport.h @@ -152,7 +152,8 @@ class TurnPort : public Port { private: enum { MSG_ERROR = MSG_FIRST_AVAILABLE, - MSG_ALLOCATE_MISMATCH + MSG_ALLOCATE_MISMATCH, + MSG_TRY_ALTERNATE_SERVER }; typedef std::list EntryList; diff --git a/webrtc/p2p/base/turnport_unittest.cc b/webrtc/p2p/base/turnport_unittest.cc index e871acab2..f84d106a2 100644 --- a/webrtc/p2p/base/turnport_unittest.cc +++ b/webrtc/p2p/base/turnport_unittest.cc @@ -47,8 +47,13 @@ static const SocketAddress kTurnUdpIntAddr("99.99.99.3", 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 @@ class TurnPortTest : public testing::Test, this, &TurnPortTest::OnUdpPortComplete); } + void TestTurnAlternateServer(cricket::ProtocolType protocol_type) { + std::vector 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 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 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 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 @@ TEST_F(TurnPortTest, TestTurnTcpAllocateMismatch) { 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 @@ TEST_F(TurnPortTest, TestTurnTlsTcpConnectionFails) { ASSERT_EQ(0U, turn_port_->Candidates().size()); } -// Test try-alternate-server feature. -TEST_F(TurnPortTest, TestTurnAlternateServer) { - std::vector 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 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 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 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 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.