blob: d63704a05e981636cf2736cf0f37cb885ab72a7d [file] [log] [blame]
// Copyright 2011 Google Inc. All Rights Reserved.
#include <string>
#include "talk/base/asynchttprequest.h"
#include "talk/base/basicpacketsocketfactory.h"
#include "talk/base/gunit.h"
#include "talk/base/fakenetwork.h"
#include "talk/base/scoped_ptr.h"
#include "talk/base/socketaddress.h"
#include "talk/p2p/base/relayport.h"
#include "talk/p2p/base/stunport.h"
#include "talk/p2p/client/connectivitychecker.h"
#include "talk/p2p/client/httpportallocator.h"
namespace cricket {
static const talk_base::SocketAddress kClientAddr1("11.11.11.11", 0);
static const talk_base::SocketAddress kClientAddr2("22.22.22.22", 0);
static const talk_base::SocketAddress kExternalAddr("33.33.33.33", 3333);
static const talk_base::SocketAddress kStunAddr("44.44.44.44", 4444);
static const talk_base::SocketAddress kRelayAddr("55.55.55.55", 5555);
static const talk_base::SocketAddress kProxyAddr("66.66.66.66", 6666);
static const talk_base::ProxyType kProxyType = talk_base::PROXY_HTTPS;
static const char kSessionName[] = "rtp_test";
static const char kSessionType[] = "http://www.google.com/session/test";
static const char kRelayHost[] = "relay.google.com";
static const char kRelayToken[] =
"CAESFwoOb2phQGdvb2dsZS5jb20Q043h47MmGhBTB1rbfIXkhuarDCZe+xF6";
static const char kBrowserAgent[] = "browser_test";
static const char kJid[] = "a.b@c";
static const char kUserName[] = "testuser";
static const char kPassword[] = "testpassword";
static const char kMagicCookie[] = "testcookie";
static const char kRelayUdpPort[] = "4444";
static const char kRelayTcpPort[] = "5555";
static const char kRelaySsltcpPort[] = "6666";
static const char kSessionId[] = "testsession";
static const char kConnection[] = "testconnection";
static const int kMinPort = 1000;
static const int kMaxPort = 2000;
// Fake implementation to mock away real network usage.
class FakeRelayPort : public RelayPort {
public:
FakeRelayPort(talk_base::Thread* thread,
talk_base::PacketSocketFactory* factory,
talk_base::Network* network,
const talk_base::IPAddress& ip,
int min_port,
int max_port,
const std::string& username,
const std::string& password,
const std::string& magic_cookie)
: RelayPort(thread,
factory,
network,
ip,
min_port,
max_port,
username,
password,
magic_cookie) {
}
// Just signal that we are done.
virtual void PrepareAddress() {
SignalAddressReady(this);
}
};
// Fake implementation to mock away real network usage.
class FakeStunPort : public StunPort {
public:
FakeStunPort(talk_base::Thread* thread,
talk_base::PacketSocketFactory* factory,
talk_base::Network* network,
const talk_base::IPAddress& ip,
int min_port,
int max_port,
const talk_base::SocketAddress& server_addr)
: StunPort(thread,
factory,
network,
ip,
min_port,
max_port,
server_addr) {
}
// Just set external address and signal that we are done.
virtual void PrepareAddress() {
AddAddress(kExternalAddr, "udp", true);
SignalAddressReady(this);
}
};
// Fake implementation to mock away real network usage by responding
// to http requests immediately.
class FakeHttpPortAllocatorSession : public TestHttpPortAllocatorSession {
public:
FakeHttpPortAllocatorSession(
HttpPortAllocator* allocator,
const std::string& name,
const std::string& session_type,
const std::vector<talk_base::SocketAddress>& stun_hosts,
const std::vector<std::string>& relay_hosts,
const std::string& relay_token,
const std::string& agent)
: TestHttpPortAllocatorSession(allocator,
name,
session_type,
stun_hosts,
relay_hosts,
relay_token,
agent) {
}
virtual void SendSessionRequest(const std::string& host, int port) {
FakeReceiveSessionResponse(host, port);
}
// Pass results to the real implementation.
void FakeReceiveSessionResponse(const std::string& host, int port) {
talk_base::AsyncHttpRequest* response = CreateAsyncHttpResponse(port);
TestHttpPortAllocatorSession::OnRequestDone(response);
response->Destroy(true);
}
private:
// Helper method for creating a response to a relay session request.
talk_base::AsyncHttpRequest* CreateAsyncHttpResponse(int port) {
talk_base::AsyncHttpRequest* request =
new talk_base::AsyncHttpRequest(kBrowserAgent);
std::stringstream ss;
ss << "username=" << kUserName << std::endl
<< "password=" << kPassword << std::endl
<< "magic_cookie=" << kMagicCookie << std::endl
<< "relay.ip=" << kRelayAddr.IPAsString() << std::endl
<< "relay.udp_port=" << kRelayUdpPort << std::endl
<< "relay.tcp_port=" << kRelayTcpPort << std::endl
<< "relay.ssltcp_port=" << kRelaySsltcpPort << std::endl;
request->response().document.reset(
new talk_base::MemoryStream(ss.str().c_str()));
request->response().set_success();
request->set_port(port);
request->set_secure(port == talk_base::HTTP_SECURE_PORT);
return request;
}
};
// Fake implementation for creating fake http sessions.
class FakeHttpPortAllocator : public HttpPortAllocator {
public:
FakeHttpPortAllocator(talk_base::NetworkManager* network_manager,
const std::string& user_agent)
: HttpPortAllocator(network_manager, user_agent) {
}
virtual PortAllocatorSession* CreateSession(const std::string& name,
const std::string& session_type) {
std::vector<talk_base::SocketAddress> stun_hosts;
stun_hosts.push_back(kStunAddr);
std::vector<std::string> relay_hosts;
relay_hosts.push_back(kRelayHost);
return new FakeHttpPortAllocatorSession(this,
kSessionName,
kSessionType,
stun_hosts,
relay_hosts,
kRelayToken,
kBrowserAgent);
}
};
class ConnectivityCheckerForTest : public ConnectivityChecker {
public:
ConnectivityCheckerForTest(talk_base::Thread* worker,
const std::string& jid,
const std::string& session_id,
const std::string& user_agent,
const std::string& relay_token,
const std::string& connection)
: ConnectivityChecker(worker,
jid,
session_id,
user_agent,
relay_token,
connection),
proxy_initiated_(false) {
}
talk_base::FakeNetworkManager* network_manager() const {
return network_manager_;
}
FakeHttpPortAllocator* port_allocator() const {
return fake_port_allocator_;
}
protected:
// Overridden methods for faking a real network.
virtual talk_base::NetworkManager* CreateNetworkManager() {
network_manager_ = new talk_base::FakeNetworkManager();
return network_manager_;
}
virtual talk_base::BasicPacketSocketFactory* CreateSocketFactory(
talk_base::Thread* thread) {
// Create socket factory, for simplicity, let it run on the current thread.
socket_factory_ =
new talk_base::BasicPacketSocketFactory(talk_base::Thread::Current());
return socket_factory_;
}
virtual HttpPortAllocator* CreatePortAllocator(
talk_base::NetworkManager* network_manager,
const std::string& user_agent,
const std::string& relay_token) {
fake_port_allocator_ =
new FakeHttpPortAllocator(network_manager, user_agent);
return fake_port_allocator_;
}
virtual StunPort* CreateStunPort(const PortConfiguration* config,
talk_base::Network* network) {
return new FakeStunPort(worker(),
socket_factory_,
network,
network->ip(),
kMinPort,
kMaxPort,
config->stun_address);
}
virtual RelayPort* CreateRelayPort(const PortConfiguration* config,
talk_base::Network* network) {
return new FakeRelayPort(worker(),
socket_factory_,
network,
network->ip(),
kMinPort,
kMaxPort,
config->username,
config->password,
config->magic_cookie);
}
virtual void InitiateProxyDetection() {
if (!proxy_initiated_) {
proxy_initiated_ = true;
proxy_info_.address = kProxyAddr;
proxy_info_.type = kProxyType;
SetProxyInfo(proxy_info_);
}
}
virtual talk_base::ProxyInfo GetProxyInfo() const {
return proxy_info_;
}
private:
talk_base::BasicPacketSocketFactory* socket_factory_;
FakeHttpPortAllocator* fake_port_allocator_;
talk_base::FakeNetworkManager* network_manager_;
talk_base::ProxyInfo proxy_info_;
bool proxy_initiated_;
};
class ConnectivityCheckerTest : public testing::Test {
protected:
void VerifyNic(const NicInfo& info,
const talk_base::SocketAddress& local_address) {
// Verify that the external address has been set.
EXPECT_EQ(kExternalAddr, info.external_address);
// Verify that the stun server address has been set.
EXPECT_EQ(kStunAddr, info.stun_server_address);
// Verify that the media server address has been set. Don't care
// about port since it is different for different protocols.
EXPECT_EQ(kRelayAddr.ipaddr(), info.media_server_address.ipaddr());
// Verify that local ip matches.
EXPECT_EQ(local_address.ipaddr(), info.ip);
// Verify that we have received responses for our
// pings. Unsuccessful ping has rtt value -1, successful >= 0.
EXPECT_GE(info.stun.rtt, 0);
EXPECT_GE(info.udp.rtt, 0);
EXPECT_GE(info.tcp.rtt, 0);
EXPECT_GE(info.ssltcp.rtt, 0);
// If proxy has been set, verify address and type.
if (!info.proxy_info.address.IsNil()) {
EXPECT_EQ(kProxyAddr, info.proxy_info.address);
EXPECT_EQ(kProxyType, info.proxy_info.type);
}
}
};
// Tests a configuration with two network interfaces. Verifies that 4
// combinations of ip/proxy are created and that all protocols are
// tested on each combination.
TEST_F(ConnectivityCheckerTest, TestStart) {
ConnectivityCheckerForTest connectivity_checker(talk_base::Thread::Current(),
kJid,
kSessionId,
kBrowserAgent,
kRelayToken,
kConnection);
connectivity_checker.Initialize();
connectivity_checker.set_stun_address(kStunAddr);
connectivity_checker.network_manager()->AddInterface(kClientAddr1);
connectivity_checker.network_manager()->AddInterface(kClientAddr2);
connectivity_checker.Start();
talk_base::Thread::Current()->ProcessMessages(1000);
NicMap nics = connectivity_checker.GetResults();
// There should be 4 nics in our map. 2 for each interface added,
// one with proxy set and one without.
EXPECT_EQ(4U, nics.size());
// First verify interfaces without proxy.
talk_base::SocketAddress nilAddress;
// First lookup the address of the first nic combined with no proxy.
NicMap::iterator i = nics.find(NicId(kClientAddr1.ipaddr(), nilAddress));
ASSERT(i != nics.end());
NicInfo info = i->second;
VerifyNic(info, kClientAddr1);
// Then make sure the second device has been tested without proxy.
i = nics.find(NicId(kClientAddr2.ipaddr(), nilAddress));
ASSERT(i != nics.end());
info = i->second;
VerifyNic(info, kClientAddr2);
// Now verify both interfaces with proxy.
i = nics.find(NicId(kClientAddr1.ipaddr(), kProxyAddr));
ASSERT(i != nics.end());
info = i->second;
VerifyNic(info, kClientAddr1);
i = nics.find(NicId(kClientAddr2.ipaddr(), kProxyAddr));
ASSERT(i != nics.end());
info = i->second;
VerifyNic(info, kClientAddr2);
};
// Tests that nothing bad happens if thera are no network interfaces
// available to check.
TEST_F(ConnectivityCheckerTest, TestStartNoNetwork) {
ConnectivityCheckerForTest connectivity_checker(talk_base::Thread::Current(),
kJid,
kSessionId,
kBrowserAgent,
kRelayToken,
kConnection);
connectivity_checker.Initialize();
connectivity_checker.Start();
talk_base::Thread::Current()->ProcessMessages(1000);
NicMap nics = connectivity_checker.GetResults();
// Verify that no nics where checked.
EXPECT_EQ(0U, nics.size());
}
} // namespace cricket