blob: f5bb37cf4b374da4a5e9ba6f39ece36405121c14 [file] [log] [blame]
/*
* libjingle
* Copyright 2009 Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "talk/base/fakenetwork.h"
#include "talk/base/firewallsocketserver.h"
#include "talk/base/gunit.h"
#include "talk/base/helpers.h"
#include "talk/base/logging.h"
#include "talk/base/network.h"
#include "talk/base/physicalsocketserver.h"
#include "talk/base/socketaddress.h"
#include "talk/base/thread.h"
#include "talk/base/virtualsocketserver.h"
#include "talk/p2p/base/p2ptransportchannel.h"
#include "talk/p2p/base/portallocatorsessionproxy.h"
#include "talk/p2p/base/testrelayserver.h"
#include "talk/p2p/base/teststunserver.h"
#include "talk/p2p/client/basicportallocator.h"
#include "talk/p2p/client/httpportallocator.h"
using talk_base::SocketAddress;
using talk_base::Thread;
static const SocketAddress kClientAddr("11.11.11.11", 0);
static const SocketAddress kRemoteClientAddr("22.22.22.22", 0);
static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT);
static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000);
static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001);
static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002);
static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003);
static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004);
static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005);
// Minimum and maximum port for port range tests.
static const int kMinPort = 10000;
static const int kMaxPort = 10099;
// Helper for dumping candidates
std::ostream& operator<<(std::ostream& os, const cricket::Candidate& c) {
os << c.ToString();
return os;
}
class PortAllocatorTest : public testing::Test, public sigslot::has_slots<> {
public:
static void SetUpTestCase() {
// Ensure the RNG is inited.
talk_base::InitRandom(NULL, 0);
}
PortAllocatorTest()
: pss_(new talk_base::PhysicalSocketServer),
vss_(new talk_base::VirtualSocketServer(pss_.get())),
fss_(new talk_base::FirewallSocketServer(vss_.get())),
ss_scope_(fss_.get()),
stun_server_(Thread::Current(), kStunAddr),
relay_server_(Thread::Current(), kRelayUdpIntAddr, kRelayUdpExtAddr,
kRelayTcpIntAddr, kRelayTcpExtAddr,
kRelaySslTcpIntAddr, kRelaySslTcpExtAddr),
allocator_(&network_manager_, kStunAddr,
kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr) {
}
void AddInterface(const SocketAddress& addr) {
network_manager_.AddInterface(addr);
}
bool SetPortRange(int min_port, int max_port) {
return allocator_.SetPortRange(min_port, max_port);
}
bool CreateSession(const std::string& name, const std::string& type) {
session_.reset(allocator_.CreateSession(name, type));
if (!session_.get())
return false;
session_->SignalPortReady.connect(this,
&PortAllocatorTest::OnPortReady);
session_->SignalCandidatesReady.connect(this,
&PortAllocatorTest::OnCandidatesReady);
return true;
}
cricket::PortAllocatorSession* CreateSession(
const std::string& sid, const std::string& name,
const std::string& type, cricket::PortAllocator* allocator) {
cricket::PortAllocatorSession* session =
allocator->CreateSession(sid, name, type);
session->SignalPortReady.connect(this,
&PortAllocatorTest::OnPortReady);
session->SignalCandidatesReady.connect(this,
&PortAllocatorTest::OnCandidatesReady);
return session;
}
static bool CheckCandidate(const cricket::Candidate& c,
const std::string& name, const std::string& type,
const std::string& proto,
const SocketAddress& addr) {
return (c.name() == name && c.type() == type &&
c.protocol() == proto && c.address().ipaddr() == addr.ipaddr() &&
(addr.port() == 0 || (c.address().port() == addr.port())));
}
static bool CheckPort(const talk_base::SocketAddress& addr,
int min_port, int max_port) {
return (addr.port() >= min_port && addr.port() <= max_port);
}
cricket::PortAllocator* CreateAllocator() {
return new cricket::BasicPortAllocator(
&network_manager_, kStunAddr, SocketAddress(),
SocketAddress(), SocketAddress());
}
cricket::P2PTransportChannel* CreateTransportChannel(
const std::string& name, cricket::PortAllocator* allocator) {
return new cricket::P2PTransportChannel(name, "unittest", NULL, allocator);
}
protected:
cricket::BasicPortAllocator& allocator() { return allocator_; }
void OnPortReady(cricket::PortAllocatorSession* ses, cricket::Port* port) {
LOG(LS_INFO) << "OnPortReady: " << port->ToString();
ports_.push_back(port);
}
void OnCandidatesReady(cricket::PortAllocatorSession* ses,
const std::vector<cricket::Candidate>& candidates) {
for (size_t i = 0; i < candidates.size(); ++i) {
LOG(LS_INFO) << "OnCandidatesReady: " << candidates[i].ToString();
candidates_.push_back(candidates[i]);
}
}
talk_base::scoped_ptr<talk_base::PhysicalSocketServer> pss_;
talk_base::scoped_ptr<talk_base::VirtualSocketServer> vss_;
talk_base::scoped_ptr<talk_base::FirewallSocketServer> fss_;
talk_base::SocketServerScope ss_scope_;
cricket::TestStunServer stun_server_;
cricket::TestRelayServer relay_server_;
talk_base::FakeNetworkManager network_manager_;
cricket::BasicPortAllocator allocator_;
talk_base::scoped_ptr<cricket::PortAllocatorSession> session_;
std::vector<cricket::Port*> ports_;
std::vector<cricket::Candidate> candidates_;
};
// Tests that we can init the port allocator and create a session.
TEST_F(PortAllocatorTest, TestBasic) {
EXPECT_EQ(&network_manager_, allocator().network_manager());
EXPECT_EQ(kStunAddr, allocator().stun_address());
EXPECT_EQ(kRelayUdpIntAddr, allocator().relay_address_udp());
EXPECT_EQ(kRelayTcpIntAddr, allocator().relay_address_tcp());
EXPECT_EQ(kRelaySslTcpIntAddr, allocator().relay_address_ssl());
EXPECT_TRUE(CreateSession("rtp", "unittest"));
}
// Tests that we can get the local and STUN addresses successfully.
TEST_F(PortAllocatorTest, TestGetInitialPorts) {
AddInterface(kClientAddr);
EXPECT_TRUE(CreateSession("rtp", "unittest"));
session_->GetInitialPorts();
ASSERT_EQ_WAIT(2U, candidates_.size(), 1000);
EXPECT_PRED5(CheckCandidate, candidates_[0],
"rtp", "local", "udp", kClientAddr);
EXPECT_PRED5(CheckCandidate, candidates_[1],
"rtp", "stun", "udp", kClientAddr);
EXPECT_EQ(2U, ports_.size());
}
// Tests that we can get all the desired addresses successfully.
TEST_F(PortAllocatorTest, TestGetAllPorts) {
AddInterface(kClientAddr);
EXPECT_TRUE(CreateSession("rtp", "unittest"));
session_->GetInitialPorts();
session_->StartGetAllPorts();
ASSERT_EQ_WAIT(2U, candidates_.size(), 1000);
EXPECT_EQ(2U, ports_.size());
ASSERT_EQ_WAIT(4U, candidates_.size(), 2000);
EXPECT_EQ(3U, ports_.size());
EXPECT_PRED5(CheckCandidate, candidates_[2],
"rtp", "relay", "udp", kRelayUdpIntAddr);
EXPECT_PRED5(CheckCandidate, candidates_[3],
"rtp", "relay", "udp", kRelayUdpExtAddr);
ASSERT_EQ_WAIT(6U, candidates_.size(), 1500);
EXPECT_PRED5(CheckCandidate, candidates_[4],
"rtp", "relay", "tcp", kRelayTcpIntAddr);
EXPECT_PRED5(CheckCandidate, candidates_[5],
"rtp", "local", "tcp", kClientAddr);
EXPECT_EQ(4U, ports_.size());
ASSERT_EQ_WAIT(7U, candidates_.size(), 2000);
EXPECT_PRED5(CheckCandidate, candidates_[6],
"rtp", "relay", "ssltcp", kRelaySslTcpIntAddr);
EXPECT_EQ(4U, ports_.size());
}
// Test that we restrict client ports appropriately when a port range is set.
// We check the candidates for udp/stun/tcp ports, and the from address
// for relay ports.
TEST_F(PortAllocatorTest, TestGetAllPortsPortRange) {
AddInterface(kClientAddr);
// Check that an invalid port range fails.
EXPECT_FALSE(SetPortRange(kMaxPort, kMinPort));
// Check that a null port range succeeds.
EXPECT_TRUE(SetPortRange(0, 0));
// Check that a valid port range succeeds.
EXPECT_TRUE(SetPortRange(kMinPort, kMaxPort));
EXPECT_TRUE(CreateSession("rtp", "unittest"));
session_->GetInitialPorts();
session_->StartGetAllPorts();
ASSERT_EQ_WAIT(2U, candidates_.size(), 1000);
EXPECT_EQ(2U, ports_.size());
// Check the port number for the UDP port object.
EXPECT_PRED3(CheckPort, candidates_[0].address(), kMinPort, kMaxPort);
// Check the port number for the STUN port object.
EXPECT_PRED3(CheckPort, candidates_[1].address(), kMinPort, kMaxPort);
ASSERT_EQ_WAIT(4U, candidates_.size(), 2000);
EXPECT_EQ(3U, ports_.size());
// Check the port number used to connect to the relay server.
EXPECT_PRED3(CheckPort, relay_server_.GetConnection(0).source(),
kMinPort, kMaxPort);
ASSERT_EQ_WAIT(6U, candidates_.size(), 1500);
EXPECT_EQ(4U, ports_.size());
// Check the port number for the TCP port object.
EXPECT_PRED3(CheckPort, candidates_[5].address(), kMinPort, kMaxPort);
}
// Test that we don't crash or malfunction if we have no network adapters.
// TODO: Find a way to exit early here.
TEST_F(PortAllocatorTest, TestGetAllPortsNoAdapters) {
EXPECT_TRUE(CreateSession("rtp", "unittest"));
session_->GetInitialPorts();
session_->StartGetAllPorts();
WAIT(candidates_.size() > 0, 2000);
}
// Test that we don't crash or malfunction if we can't create UDP sockets.
TEST_F(PortAllocatorTest, TestGetAllPortsNoUdpSockets) {
AddInterface(kClientAddr);
fss_->set_udp_sockets_enabled(false);
EXPECT_TRUE(CreateSession("rtp", "unittest"));
session_->GetInitialPorts();
session_->StartGetAllPorts();
ASSERT_EQ_WAIT(2U, candidates_.size(), 2000);
EXPECT_PRED5(CheckCandidate, candidates_[0],
"rtp", "relay", "udp", kRelayUdpIntAddr);
EXPECT_PRED5(CheckCandidate, candidates_[1],
"rtp", "relay", "udp", kRelayUdpExtAddr);
ASSERT_EQ_WAIT(4U, candidates_.size(), 2000);
EXPECT_PRED5(CheckCandidate, candidates_[2],
"rtp", "relay", "tcp", kRelayTcpIntAddr);
EXPECT_PRED5(CheckCandidate, candidates_[3],
"rtp", "local", "tcp", kClientAddr);
EXPECT_EQ(2U, ports_.size());
ASSERT_EQ_WAIT(5U, candidates_.size(), 2000);
EXPECT_PRED5(CheckCandidate, candidates_[4],
"rtp", "relay", "ssltcp", kRelaySslTcpIntAddr);
EXPECT_EQ(2U, ports_.size());
}
// Test that we don't crash or malfunction if we can't create UDP sockets or
// listen on TCP sockets. We still give out a local TCP address, since
// apparently this is needed for the remote side to accept our connection.
TEST_F(PortAllocatorTest, TestGetAllPortsNoUdpSocketsNoTcpListen) {
AddInterface(kClientAddr);
fss_->set_udp_sockets_enabled(false);
fss_->set_tcp_listen_enabled(false);
EXPECT_TRUE(CreateSession("rtp", "unittest"));
session_->GetInitialPorts();
session_->StartGetAllPorts();
ASSERT_EQ_WAIT(2U, candidates_.size(), 3000);
EXPECT_PRED5(CheckCandidate, candidates_[0],
"rtp", "relay", "udp", kRelayUdpIntAddr);
EXPECT_PRED5(CheckCandidate, candidates_[1],
"rtp", "relay", "udp", kRelayUdpExtAddr);
ASSERT_EQ_WAIT(4U, candidates_.size(), 2000);
EXPECT_PRED5(CheckCandidate, candidates_[2],
"rtp", "relay", "tcp", kRelayTcpIntAddr);
EXPECT_PRED5(CheckCandidate, candidates_[3],
"rtp", "local", "tcp", kClientAddr);
EXPECT_EQ(2U, ports_.size());
ASSERT_EQ_WAIT(5U, candidates_.size(), 2000);
EXPECT_PRED5(CheckCandidate, candidates_[4],
"rtp", "relay", "ssltcp", kRelaySslTcpIntAddr);
EXPECT_EQ(2U, ports_.size());
}
// Test that we don't crash or malfunction if we can't create any sockets.
// TODO: Find a way to exit early here.
TEST_F(PortAllocatorTest, TestGetAllPortsNoSockets) {
AddInterface(kClientAddr);
fss_->set_tcp_sockets_enabled(false);
fss_->set_udp_sockets_enabled(false);
EXPECT_TRUE(CreateSession("rtp", "unittest"));
session_->GetInitialPorts();
session_->StartGetAllPorts();
WAIT(candidates_.size() > 0, 2000);
}
// Test that the httpportallocator correctly maintains its lists of stun and
// relay servers, by never allowing an empty list.
TEST(HttpPortAllocatorTest, TestHttpPortAllocatorHostLists) {
talk_base::FakeNetworkManager network_manager;
cricket::HttpPortAllocator alloc(&network_manager, "unit test agent");
EXPECT_EQ(1U, alloc.relay_hosts().size());
EXPECT_EQ(1U, alloc.stun_hosts().size());
std::vector<std::string> relay_servers;
std::vector<talk_base::SocketAddress> stun_servers;
alloc.SetRelayHosts(relay_servers);
alloc.SetStunHosts(stun_servers);
EXPECT_EQ(1U, alloc.relay_hosts().size());
EXPECT_EQ(1U, alloc.stun_hosts().size());
relay_servers.push_back("1.unittest.corp.google.com");
relay_servers.push_back("2.unittest.corp.google.com");
stun_servers.push_back(
talk_base::SocketAddress("1.unittest.corp.google.com", 0));
stun_servers.push_back(
talk_base::SocketAddress("2.unittest.corp.google.com", 0));
alloc.SetRelayHosts(relay_servers);
alloc.SetStunHosts(stun_servers);
EXPECT_EQ(2U, alloc.relay_hosts().size());
EXPECT_EQ(2U, alloc.stun_hosts().size());
}
TEST_F(PortAllocatorTest, TestBasicMuxFeatures) {
talk_base::scoped_ptr<cricket::PortAllocator> allocator(CreateAllocator());
allocator->set_flags(cricket::PORTALLOCATOR_ENABLE_BUNDLE);
// Session ID - session1.
talk_base::scoped_ptr<cricket::PortAllocatorSession> session1(
CreateSession("session1", "rtp", "audio", allocator.get()));
talk_base::scoped_ptr<cricket::PortAllocatorSession> session2(
CreateSession("session1", "rtcp", "audio", allocator.get()));
// We know that PortAllocator is creating a proxy session when bundle flag
// is enabled, it's safe to type cast session objects.
cricket::PortAllocatorSessionProxy* proxy1 =
static_cast<cricket::PortAllocatorSessionProxy*>(session1.get());
ASSERT_TRUE(proxy1 != NULL);
cricket::PortAllocatorSessionProxy* proxy2 =
static_cast<cricket::PortAllocatorSessionProxy*>(session2.get());
ASSERT_TRUE(proxy2 != NULL);
EXPECT_EQ(proxy1->impl(), proxy2->impl());
AddInterface(kClientAddr);
session1->GetInitialPorts();
session2->GetInitialPorts();
// Each session should receive two proxy ports of local and stun.
ASSERT_EQ_WAIT(4U, ports_.size(), 1000);
EXPECT_EQ(4U, candidates_.size());
EXPECT_PRED5(CheckCandidate, candidates_[0],
"rtp", "local", "udp", kClientAddr);
EXPECT_PRED5(CheckCandidate, candidates_[1],
"rtcp", "local", "udp", kClientAddr);
EXPECT_PRED5(CheckCandidate, candidates_[2],
"rtp", "stun", "udp", kClientAddr);
EXPECT_PRED5(CheckCandidate, candidates_[3],
"rtcp", "stun", "udp", kClientAddr);
talk_base::scoped_ptr<cricket::PortAllocatorSession> session3(
CreateSession("session1", "video_rtp", "video", allocator.get()));
// ListenToEvents(session3.get());
session3->GetInitialPorts();
// Since real ports and sessions are already allocated and signal sent, no
// new ports will be allocated when new proxy session created.
talk_base::Thread::Current()->ProcessMessages(1000);
EXPECT_NE(6U, ports_.size());
// Creating a PortAllocatorSession with different session name from above.
// In this case proxy PAS should have a different PAS.
// Session ID - session2.
talk_base::scoped_ptr<cricket::PortAllocatorSession> session4(
CreateSession("session2", "video_rtp", "video", allocator.get()));
cricket::PortAllocatorSessionProxy* proxy4 =
static_cast<cricket::PortAllocatorSessionProxy*>(session4.get());
EXPECT_NE(proxy4->impl(), proxy1->impl());
}