blob: 71c848397d14fda379ab53b30d29a003ea10c6da [file] [log] [blame]
/*
* Copyright 2011 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <string>
#include "webrtc/p2p/base/basicpacketsocketfactory.h"
#include "webrtc/p2p/base/relayport.h"
#include "webrtc/p2p/base/stunport.h"
#include "webrtc/p2p/client/connectivitychecker.h"
#include "webrtc/p2p/client/httpportallocator.h"
#include "webrtc/base/asynchttprequest.h"
#include "webrtc/base/fakenetwork.h"
#include "webrtc/base/gunit.h"
#include "webrtc/base/scoped_ptr.h"
#include "webrtc/base/socketaddress.h"
namespace cricket {
static const rtc::SocketAddress kClientAddr1("11.11.11.11", 0);
static const rtc::SocketAddress kClientAddr2("22.22.22.22", 0);
static const rtc::SocketAddress kExternalAddr("33.33.33.33", 3333);
static const rtc::SocketAddress kStunAddr("44.44.44.44", 4444);
static const rtc::SocketAddress kRelayAddr("55.55.55.55", 5555);
static const rtc::SocketAddress kProxyAddr("66.66.66.66", 6666);
static const rtc::ProxyType kProxyType = rtc::PROXY_HTTPS;
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(rtc::Thread* thread,
rtc::PacketSocketFactory* factory,
rtc::Network* network, const rtc::IPAddress& ip,
int min_port, int max_port,
const std::string& username, const std::string& password)
: RelayPort(thread, factory, network, ip, min_port, max_port,
username, password) {
}
// Just signal that we are done.
virtual void PrepareAddress() {
SignalPortComplete(this);
}
};
// Fake implementation to mock away real network usage.
class FakeStunPort : public StunPort {
public:
FakeStunPort(rtc::Thread* thread,
rtc::PacketSocketFactory* factory,
rtc::Network* network,
const rtc::IPAddress& ip,
int min_port, int max_port,
const std::string& username, const std::string& password,
const ServerAddresses& server_addr)
: StunPort(thread, factory, network, ip, min_port, max_port,
username, password, server_addr, std::string()) {
}
// Just set external address and signal that we are done.
virtual void PrepareAddress() {
AddAddress(kExternalAddr, kExternalAddr, rtc::SocketAddress(), "udp", "",
STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, 0, true);
SignalPortComplete(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& content_name,
int component,
const std::string& ice_ufrag, const std::string& ice_pwd,
const std::vector<rtc::SocketAddress>& stun_hosts,
const std::vector<std::string>& relay_hosts,
const std::string& relay_token,
const std::string& agent)
: TestHttpPortAllocatorSession(allocator,
content_name,
component,
ice_ufrag,
ice_pwd,
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) {
rtc::AsyncHttpRequest* response = CreateAsyncHttpResponse(port);
TestHttpPortAllocatorSession::OnRequestDone(response);
response->Destroy(true);
}
private:
// Helper method for creating a response to a relay session request.
rtc::AsyncHttpRequest* CreateAsyncHttpResponse(int port) {
rtc::AsyncHttpRequest* request =
new rtc::AsyncHttpRequest(kBrowserAgent);
std::stringstream ss;
ss << "username=" << kUserName << std::endl
<< "password=" << kPassword << std::endl
<< "magic_cookie=" << kMagicCookie << std::endl
<< "relay.ip=" << kRelayAddr.ipaddr().ToString() << 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 rtc::MemoryStream(ss.str().c_str()));
request->response().set_success();
request->set_port(port);
request->set_secure(port == rtc::HTTP_SECURE_PORT);
return request;
}
};
// Fake implementation for creating fake http sessions.
class FakeHttpPortAllocator : public HttpPortAllocator {
public:
FakeHttpPortAllocator(rtc::NetworkManager* network_manager,
const std::string& user_agent)
: HttpPortAllocator(network_manager, user_agent) {
}
virtual PortAllocatorSession* CreateSessionInternal(
const std::string& content_name, int component,
const std::string& ice_ufrag, const std::string& ice_pwd) {
std::vector<rtc::SocketAddress> stun_hosts;
stun_hosts.push_back(kStunAddr);
std::vector<std::string> relay_hosts;
relay_hosts.push_back(kRelayHost);
return new FakeHttpPortAllocatorSession(this,
content_name,
component,
ice_ufrag,
ice_pwd,
stun_hosts,
relay_hosts,
kRelayToken,
kBrowserAgent);
}
};
class ConnectivityCheckerForTest : public ConnectivityChecker {
public:
ConnectivityCheckerForTest(rtc::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) {
}
rtc::FakeNetworkManager* network_manager() const {
return network_manager_;
}
FakeHttpPortAllocator* port_allocator() const {
return fake_port_allocator_;
}
protected:
// Overridden methods for faking a real network.
virtual rtc::NetworkManager* CreateNetworkManager() {
network_manager_ = new rtc::FakeNetworkManager();
return network_manager_;
}
virtual rtc::BasicPacketSocketFactory* CreateSocketFactory(
rtc::Thread* thread) {
// Create socket factory, for simplicity, let it run on the current thread.
socket_factory_ =
new rtc::BasicPacketSocketFactory(rtc::Thread::Current());
return socket_factory_;
}
virtual HttpPortAllocator* CreatePortAllocator(
rtc::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 std::string& username, const std::string& password,
const PortConfiguration* config, rtc::Network* network) {
return new FakeStunPort(worker(),
socket_factory_,
network,
network->GetBestIP(),
kMinPort,
kMaxPort,
username,
password,
config->stun_servers);
}
virtual RelayPort* CreateRelayPort(
const std::string& username, const std::string& password,
const PortConfiguration* config, rtc::Network* network) {
return new FakeRelayPort(worker(),
socket_factory_,
network,
network->GetBestIP(),
kMinPort,
kMaxPort,
username,
password);
}
virtual void InitiateProxyDetection() {
if (!proxy_initiated_) {
proxy_initiated_ = true;
proxy_info_.address = kProxyAddr;
proxy_info_.type = kProxyType;
SetProxyInfo(proxy_info_);
}
}
virtual rtc::ProxyInfo GetProxyInfo() const {
return proxy_info_;
}
private:
rtc::BasicPacketSocketFactory* socket_factory_;
FakeHttpPortAllocator* fake_port_allocator_;
rtc::FakeNetworkManager* network_manager_;
rtc::ProxyInfo proxy_info_;
bool proxy_initiated_;
};
class ConnectivityCheckerTest : public testing::Test {
protected:
void VerifyNic(const NicInfo& info,
const rtc::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(1U, info.stun_server_addresses.size());
EXPECT_EQ(kStunAddr, *(info.stun_server_addresses.begin()));
// 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(rtc::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();
rtc::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.
rtc::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(rtc::Thread::Current(),
kJid,
kSessionId,
kBrowserAgent,
kRelayToken,
kConnection);
connectivity_checker.Initialize();
connectivity_checker.Start();
rtc::Thread::Current()->ProcessMessages(1000);
NicMap nics = connectivity_checker.GetResults();
// Verify that no nics where checked.
EXPECT_EQ(0U, nics.size());
}
} // namespace cricket