diff --git a/talk/libjingle_tests.gyp b/talk/libjingle_tests.gyp index 4157ad2c5..cbff0b5e7 100755 --- a/talk/libjingle_tests.gyp +++ b/talk/libjingle_tests.gyp @@ -356,6 +356,7 @@ 'p2p/base/testturnserver.h', 'p2p/base/transport_unittest.cc', 'p2p/base/transportdescriptionfactory_unittest.cc', + 'p2p/base/turnport_unittest.cc', 'p2p/client/connectivitychecker_unittest.cc', 'p2p/client/fakeportallocator.h', 'p2p/client/portallocator_unittest.cc', diff --git a/talk/p2p/base/turnport.cc b/talk/p2p/base/turnport.cc index afd8efce7..de0875aac 100644 --- a/talk/p2p/base/turnport.cc +++ b/talk/p2p/base/turnport.cc @@ -256,43 +256,9 @@ void TurnPort::PrepareAddress() { LOG_J(LS_INFO, this) << "Trying to connect to TURN server via " << ProtoToString(server_address_.proto) << " @ " << server_address_.address.ToSensitiveString(); - if (server_address_.proto == PROTO_UDP && !SharedSocket()) { - socket_ = socket_factory()->CreateUdpSocket( - talk_base::SocketAddress(ip(), 0), min_port(), max_port()); - } else if (server_address_.proto == PROTO_TCP) { - ASSERT(!SharedSocket()); - int opts = talk_base::PacketSocketFactory::OPT_STUN; - // If secure bit is enabled in server address, use TLS over TCP. - if (server_address_.secure) { - opts |= talk_base::PacketSocketFactory::OPT_TLS; - } - socket_ = socket_factory()->CreateClientTcpSocket( - talk_base::SocketAddress(ip(), 0), server_address_.address, - proxy(), user_agent(), opts); - } - - if (!socket_) { + if (!CreateTurnClientSocket()) { OnAllocateError(); - return; - } - - // Apply options if any. - for (SocketOptionsMap::iterator iter = socket_options_.begin(); - iter != socket_options_.end(); ++iter) { - socket_->SetOption(iter->first, iter->second); - } - - if (!SharedSocket()) { - // If socket is shared, AllocationSequence will receive the packet. - socket_->SignalReadPacket.connect(this, &TurnPort::OnReadPacket); - } - - socket_->SignalReadyToSend.connect(this, &TurnPort::OnReadyToSend); - - if (server_address_.proto == PROTO_TCP) { - socket_->SignalConnect.connect(this, &TurnPort::OnSocketConnect); - socket_->SignalClose.connect(this, &TurnPort::OnSocketClose); - } else { + } else 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); @@ -300,6 +266,47 @@ void TurnPort::PrepareAddress() { } } +bool TurnPort::CreateTurnClientSocket() { + if (server_address_.proto == PROTO_UDP && !SharedSocket()) { + socket_ = socket_factory()->CreateUdpSocket( + talk_base::SocketAddress(ip(), 0), min_port(), max_port()); + } else if (server_address_.proto == PROTO_TCP) { + ASSERT(!SharedSocket()); + int opts = talk_base::PacketSocketFactory::OPT_STUN; + // If secure bit is enabled in server address, use TLS over TCP. + if (server_address_.secure) { + opts |= talk_base::PacketSocketFactory::OPT_TLS; + } + socket_ = socket_factory()->CreateClientTcpSocket( + talk_base::SocketAddress(ip(), 0), server_address_.address, + proxy(), user_agent(), opts); + } + + if (!socket_) { + error_ = SOCKET_ERROR; + return false; + } + + // Apply options if any. + for (SocketOptionsMap::iterator iter = socket_options_.begin(); + iter != socket_options_.end(); ++iter) { + socket_->SetOption(iter->first, iter->second); + } + + if (!SharedSocket()) { + // If socket is shared, AllocationSequence will receive the packet. + socket_->SignalReadPacket.connect(this, &TurnPort::OnReadPacket); + } + + socket_->SignalReadyToSend.connect(this, &TurnPort::OnReadyToSend); + + if (server_address_.proto == PROTO_TCP) { + socket_->SignalConnect.connect(this, &TurnPort::OnSocketConnect); + socket_->SignalClose.connect(this, &TurnPort::OnSocketClose); + } + return true; +} + void TurnPort::OnSocketConnect(talk_base::AsyncPacketSocket* socket) { ASSERT(server_address_.proto == PROTO_TCP); // Do not use this port if the socket bound to a different address than @@ -313,6 +320,10 @@ void TurnPort::OnSocketConnect(talk_base::AsyncPacketSocket* socket) { return; } + if (server_address_.address.IsUnresolved()) { + server_address_.address = socket_->GetRemoteAddress(); + } + LOG(LS_INFO) << "TurnPort connected to " << socket->GetRemoteAddress() << " using tcp."; SendRequest(new TurnAllocateRequest(this), 0); @@ -458,6 +469,17 @@ void TurnPort::ResolveTurnAddress(const talk_base::SocketAddress& address) { void TurnPort::OnResolveResult(talk_base::AsyncResolverInterface* resolver) { ASSERT(resolver == resolver_); + // If DNS resolve is failed when trying to connect to the server using TCP, + // one of the reason could be due to DNS queries blocked by firewall. + // In such cases we will try to connect to the server with hostname, assuming + // socket layer will resolve the hostname through a HTTP proxy (if any). + if (resolver_->GetError() != 0 && server_address_.proto == PROTO_TCP) { + if (!CreateTurnClientSocket()) { + OnAllocateError(); + } + return; + } + // Copy the original server address in |resolved_address|. For TLS based // sockets we need hostname along with resolved address. talk_base::SocketAddress resolved_address = server_address_.address; @@ -465,6 +487,7 @@ void TurnPort::OnResolveResult(talk_base::AsyncResolverInterface* resolver) { !resolver_->GetResolvedAddress(ip().family(), &resolved_address)) { LOG_J(LS_WARNING, this) << "TURN host lookup received error " << resolver_->GetError(); + error_ = resolver_->GetError(); OnAllocateError(); return; } diff --git a/talk/p2p/base/turnport.h b/talk/p2p/base/turnport.h index 3eea34e4a..456644ad6 100644 --- a/talk/p2p/base/turnport.h +++ b/talk/p2p/base/turnport.h @@ -117,6 +117,8 @@ class TurnPort : public Port { const std::string& hash() const { return hash_; } const std::string& nonce() const { return nonce_; } + int error() const { return error_; } + // 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. @@ -158,6 +160,8 @@ class TurnPort : public Port { virtual void OnMessage(talk_base::Message* pmsg); + bool CreateTurnClientSocket(); + void set_nonce(const std::string& nonce) { nonce_ = nonce; } void set_realm(const std::string& realm) { if (realm != realm_) { diff --git a/talk/p2p/base/turnport_unittest.cc b/talk/p2p/base/turnport_unittest.cc index bef85e623..cc6d28395 100644 --- a/talk/p2p/base/turnport_unittest.cc +++ b/talk/p2p/base/turnport_unittest.cc @@ -38,6 +38,7 @@ #include "talk/base/physicalsocketserver.h" #include "talk/base/scoped_ptr.h" #include "talk/base/socketaddress.h" +#include "talk/base/ssladapter.h" #include "talk/base/thread.h" #include "talk/base/virtualsocketserver.h" #include "talk/p2p/base/basicpacketsocketfactory.h" @@ -121,6 +122,14 @@ class TurnPortTest : public testing::Test, network_.AddIP(talk_base::IPAddress(INADDR_ANY)); } + static void SetUpTestCase() { + talk_base::InitializeSSL(); + } + + static void TearDownTestCase() { + talk_base::CleanupSSL(); + } + virtual void OnMessage(talk_base::Message* msg) { ASSERT(msg->message_id == MSG_TESTFINISH); if (msg->message_id == MSG_TESTFINISH) @@ -301,9 +310,9 @@ class TurnPortTest : public testing::Test, // Send some data. size_t num_packets = 256; for (size_t i = 0; i < num_packets; ++i) { - char buf[256]; + unsigned char buf[256] = { 0 }; for (size_t j = 0; j < i + 1; ++j) { - buf[j] = 0xFF - j; + buf[j] = 0xFF - static_cast<unsigned char>(j); } conn1->Send(buf, i + 1, options); conn2->Send(buf, i + 1, options); @@ -354,6 +363,7 @@ TEST_F(TurnPortTest, TestTurnAllocate) { EXPECT_NE(0, turn_port_->Candidates()[0].address().port()); } +// Testing a normal UDP allocation using TCP connection. TEST_F(TurnPortTest, TestTurnTcpAllocate) { turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr); @@ -366,6 +376,33 @@ TEST_F(TurnPortTest, TestTurnTcpAllocate) { EXPECT_NE(0, turn_port_->Candidates()[0].address().port()); } +// Testing turn port will attempt to create TCP socket on address resolution +// failure. +TEST_F(TurnPortTest, TestTurnTcpOnAddressResolveFailure) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); + CreateTurnPort(kTurnUsername, kTurnPassword, cricket::ProtocolAddress( + talk_base::SocketAddress("www.webrtc-blah-blah.com", 3478), + cricket::PROTO_TCP)); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kTimeout); + // As VSS doesn't provide a DNS resolution, name resolve will fail. TurnPort + // will proceed in creating a TCP socket which will fail as there is no + // server on the above domain and error will be set to SOCKET_ERROR. + EXPECT_EQ(SOCKET_ERROR, turn_port_->error()); +} + +// In case of UDP on address resolve failure, TurnPort will not create socket +// and return allocate failure. +TEST_F(TurnPortTest, TestTurnUdpOnAdressResolveFailure) { + CreateTurnPort(kTurnUsername, kTurnPassword, cricket::ProtocolAddress( + talk_base::SocketAddress("www.webrtc-blah-blah.com", 3478), + cricket::PROTO_UDP)); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kTimeout); + // Error from turn port will not be socket error. + EXPECT_NE(SOCKET_ERROR, turn_port_->error()); +} + // Try to do a TURN allocation with an invalid password. TEST_F(TurnPortTest, TestTurnAllocateBadPassword) { CreateTurnPort(kTurnUsername, "bad", kTurnUdpProtoAddr);