blob: 109b04f9d2d4f15898007586c6f8af180751135a [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/natserver.h"
#include "talk/base/natsocketfactory.h"
#include "talk/base/physicalsocketserver.h"
#include "talk/base/proxyserver.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/testrelayserver.h"
#include "talk/p2p/base/teststunserver.h"
#include "talk/p2p/client/basicportallocator.h"
using talk_base::SocketAddress;
static const int kDefaultTimeout = 1000;
static const int kOnlyLocalPorts = cricket::PORTALLOCATOR_DISABLE_STUN |
cricket::PORTALLOCATOR_DISABLE_RELAY |
cricket::PORTALLOCATOR_DISABLE_TCP;
// Addresses on the public internet.
static const SocketAddress kPublicAddrs[2] =
{ SocketAddress("11.11.11.11", 0), SocketAddress("22.22.22.22", 0) };
// For configuring multihomed clients.
static const SocketAddress kAlternateAddrs[2] =
{ SocketAddress("11.11.11.101", 0), SocketAddress("22.22.22.202", 0) };
// Addresses for HTTP proxy servers.
static const SocketAddress kHttpsProxyAddrs[2] =
{ SocketAddress("11.11.11.1", 443), SocketAddress("22.22.22.1", 443) };
// Addresses for SOCKS proxy servers.
static const SocketAddress kSocksProxyAddrs[2] =
{ SocketAddress("11.11.11.1", 1080), SocketAddress("22.22.22.1", 1080) };
// Internal addresses for NAT boxes.
static const SocketAddress kNatAddrs[2] =
{ SocketAddress("192.168.1.1", 0), SocketAddress("192.168.2.1", 0) };
// Private addresses inside the NAT private networks.
static const SocketAddress kPrivateAddrs[2] =
{ SocketAddress("192.168.1.11", 0), SocketAddress("192.168.2.22", 0) };
// For cascaded NATs, the internal addresses of the inner NAT boxes.
static const SocketAddress kCascadedNatAddrs[2] =
{ SocketAddress("192.168.10.1", 0), SocketAddress("192.168.20.1", 0) };
// For cascaded NATs, private addresses inside the inner private networks.
static const SocketAddress kCascadedPrivateAddrs[2] =
{ SocketAddress("192.168.10.11", 0), SocketAddress("192.168.20.22", 0) };
// The address of the public STUN server.
static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT);
// The addresses for the public relay server.
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);
// This test simulates 2 P2P endpoints that want to establish connectivity
// with each other over various network topologies and conditions, which can be
// specified in each individial test.
// A virtual network (via VirtualSocketServer) along with virtual firewalls and
// NATs (via Firewall/NATSocketServer) are used to simulate the various network
// conditions. We can configure the IP addresses of the endpoints,
// block various types of connectivity, or add arbitrary levels of NAT.
// We also run a STUN server and a relay server on the virtual network to allow
// our typical P2P mechanisms to do their thing.
// For each case, we expect the P2P stack to eventually settle on a specific
// form of connectivity to the other side. The test checks that the P2P
// negotiation successfully establishes connectivity within a certain time,
// and that the result is what we expect.
// Note that this class is a base class for use by other tests, who will provide
// specialized test behavior.
class P2PTransportChannelTestBase : public testing::Test,
public sigslot::has_slots<> {
public:
P2PTransportChannelTestBase()
: main_(talk_base::Thread::Current()),
pss_(new talk_base::PhysicalSocketServer),
vss_(new talk_base::VirtualSocketServer(pss_.get())),
nss_(new talk_base::NATSocketServer(vss_.get())),
ss_(new talk_base::FirewallSocketServer(nss_.get())),
ss_scope_(ss_.get()),
stun_server_(main_, kStunAddr),
relay_server_(main_, kRelayUdpIntAddr, kRelayUdpExtAddr,
kRelayTcpIntAddr, kRelayTcpExtAddr,
kRelaySslTcpIntAddr, kRelaySslTcpExtAddr),
socks_server1_(ss_.get(), kSocksProxyAddrs[0],
ss_.get(), kSocksProxyAddrs[0]),
socks_server2_(ss_.get(), kSocksProxyAddrs[1],
ss_.get(), kSocksProxyAddrs[1]),
allocator1_(&network_manager1_, kStunAddr,
kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr),
allocator2_(&network_manager2_, kStunAddr,
kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr) {
}
protected:
enum Config {
OPEN, // Open to the Internet
NAT_FULL_CONE, // NAT, no filtering
NAT_ADDR_RESTRICTED, // NAT, must send to an addr to recv
NAT_PORT_RESTRICTED, // NAT, must send to an addr+port to recv
NAT_SYMMETRIC, // NAT, endpoint-dependent bindings
NAT_DOUBLE_CONE, // Double NAT, both cone
NAT_SYMMETRIC_THEN_CONE, // Double NAT, symmetric outer, cone inner
BLOCK_UDP, // Firewall, UDP in/out blocked
BLOCK_UDP_AND_INCOMING_TCP, // Firewall, UDP in/out and TCP in blocked
BLOCK_ALL_BUT_OUTGOING_HTTP, // Firewall, only TCP out on 80/443
PROXY_HTTPS, // All traffic through HTTPS proxy
PROXY_SOCKS, // All traffic through SOCKS proxy
NUM_CONFIGS
};
struct Result {
Result(const std::string& lt, const std::string lp,
const std::string& rt, const std::string rp, int wait)
: local_type(lt), local_proto(lp), remote_type(rt), remote_proto(rp),
connect_wait(wait) {
}
std::string local_type;
std::string local_proto;
std::string remote_type;
std::string remote_proto;
int connect_wait;
};
// Common results.
static const Result kLocalUdpToLocalUdp;
static const Result kLocalUdpToStunUdp;
static const Result kStunUdpToLocalUdp;
static const Result kStunUdpToStunUdp;
static const Result kLocalUdpToRelayUdp;
static const Result kLocalTcpToLocalTcp;
static void SetUpTestCase() {
// Ensure the RNG is inited.
talk_base::InitRandom(NULL, 0);
}
talk_base::NATSocketServer* nat() { return nss_.get(); }
talk_base::FirewallSocketServer* fw() { return ss_.get(); }
cricket::PortAllocator* GetAllocator(int endpoint) {
return (endpoint == 0) ? &allocator1_ : &allocator2_;
}
void AddAddress(int endpoint, const SocketAddress& addr) {
talk_base::FakeNetworkManager& manager = (endpoint == 0) ?
network_manager1_ : network_manager2_;
manager.AddInterface(addr);
}
void RemoveAddress(int endpoint, const SocketAddress& addr) {
talk_base::FakeNetworkManager& manager = (endpoint == 0) ?
network_manager1_ : network_manager2_;
manager.RemoveInterface(addr);
}
void SetProxy(int endpoint, talk_base::ProxyType type) {
talk_base::ProxyInfo info;
info.type = type;
info.address = (type == talk_base::PROXY_HTTPS) ?
kHttpsProxyAddrs[endpoint] : kSocksProxyAddrs[endpoint];
GetAllocator(endpoint)->set_proxy("unittest/1.0", info);
}
void SetAllocatorFlags(int endpoint, int flags) {
GetAllocator(endpoint)->set_flags(flags);
}
void Test(const Result& expected) {
int32 connect_start = talk_base::Time(), connect_time;
// Create the channels and wait for them to connect.
CreateChannels();
EXPECT_TRUE_WAIT_MARGIN(ch1_.get() != NULL && ch2_.get() != NULL &&
ch1_->readable() && ch1_->writable() &&
ch2_->readable() && ch2_->writable(),
expected.connect_wait,
1000);
connect_time = talk_base::TimeSince(connect_start);
if (connect_time < expected.connect_wait) {
LOG(LS_INFO) << "Connect time: " << connect_time << " ms";
} else {
LOG(LS_INFO) << "Connect time: " << "TIMEOUT ("
<< expected.connect_wait << " ms)";
}
// Allow a few turns of the crank for the best connections to emerge.
// This may take up to 2 seconds.
if (ch1_->best_connection() && ch2_->best_connection()) {
int32 converge_start = talk_base::Time(), converge_time;
int converge_wait = 2000;
EXPECT_TRUE_WAIT_MARGIN(
LocalCandidate(ch1_.get())->type() == expected.local_type &&
LocalCandidate(ch1_.get())->protocol() == expected.local_proto &&
RemoteCandidate(ch1_.get())->type() == expected.remote_type &&
RemoteCandidate(ch1_.get())->protocol() == expected.remote_proto,
converge_wait,
converge_wait);
// Also do EXPECT_EQ on each part so that failures are more verbose.
EXPECT_EQ(expected.local_type, LocalCandidate(ch1_.get())->type());
EXPECT_EQ(expected.local_proto, LocalCandidate(ch1_.get())->protocol());
EXPECT_EQ(expected.remote_type, RemoteCandidate(ch1_.get())->type());
EXPECT_EQ(expected.remote_proto, RemoteCandidate(ch1_.get())->protocol());
/* TODO: Check ch2 candidates.
EXPECT_EQ(expected.local_type2, LocalCandidate(ch1_.get())->type());
EXPECT_EQ(expected.local_proto2, LocalCandidate(ch1_.get())->protocol());
EXPECT_EQ(expected.remote_type2, RemoteCandidate(ch1_.get())->type());
EXPECT_EQ(expected.remote_proto2, RemoteCandidate(ch1_.get())->protocol());
*/
converge_time = talk_base::TimeSince(converge_start);
if (converge_time < converge_wait) {
LOG(LS_INFO) << "Converge time: " << converge_time << " ms";
} else {
LOG(LS_INFO) << "Converge time: " << "TIMEOUT ("
<< converge_wait << " ms)";
}
}
// TODO: Send some data and make sure it gets there.
// Destroy the channels, and wait for them to be fully cleaned up.
DestroyChannels();
}
void CreateChannels() {
ch1_.reset(new cricket::P2PTransportChannel("a", "unittest",
NULL, &allocator1_));
ch2_.reset(new cricket::P2PTransportChannel("b", "unittest",
NULL, &allocator2_));
ch1_->SignalRequestSignaling.connect(ch1_.get(),
&cricket::P2PTransportChannel::OnSignalingReady);
ch2_->SignalRequestSignaling.connect(ch2_.get(),
&cricket::P2PTransportChannel::OnSignalingReady);
ch1_->SignalCandidateReady.connect(this,
&P2PTransportChannelTestBase::OnCandidate);
ch2_->SignalCandidateReady.connect(this,
&P2PTransportChannelTestBase::OnCandidate);
ch1_->Connect();
ch2_->Connect();
}
void DestroyChannels() {
ch1_.reset();
ch2_.reset();
}
cricket::P2PTransportChannel* ch1() { return ch1_.get(); }
cricket::P2PTransportChannel* ch2() { return ch2_.get(); }
// We pass the candidates directly to the other side.
void OnCandidate(cricket::TransportChannelImpl* ch,
const cricket::Candidate& c) {
if (ch == ch1_.get()) {
LOG(LS_INFO) << "Candidate(1->2): " << c.type() << ", " << c.protocol()
<< ", " << c.address().ToString() << ", " << c.username()
<< ", " << c.generation();
ch2_->OnCandidate(c);
} else {
LOG(LS_INFO) << "Candidate(2->1): " << c.type() << ", " << c.protocol()
<< ", " << c.address().ToString() << ", " << c.username()
<< ", " << c.generation();
ch1_->OnCandidate(c);
}
}
static const cricket::Candidate* LocalCandidate(
cricket::P2PTransportChannel* ch) {
return (ch && ch->best_connection()) ?
&ch->best_connection()->local_candidate() : NULL;
}
static const cricket::Candidate* RemoteCandidate(
cricket::P2PTransportChannel* ch) {
return (ch && ch->best_connection()) ?
&ch->best_connection()->remote_candidate() : NULL;
}
private:
enum { MSG_CONNECT, MSG_DISCONNECT };
talk_base::Thread* main_;
talk_base::scoped_ptr<talk_base::PhysicalSocketServer> pss_;
talk_base::scoped_ptr<talk_base::VirtualSocketServer> vss_;
talk_base::scoped_ptr<talk_base::NATSocketServer> nss_;
talk_base::scoped_ptr<talk_base::FirewallSocketServer> ss_;
talk_base::SocketServerScope ss_scope_;
cricket::TestStunServer stun_server_;
cricket::TestRelayServer relay_server_;
talk_base::SocksProxyServer socks_server1_;
talk_base::SocksProxyServer socks_server2_;
talk_base::FakeNetworkManager network_manager1_;
talk_base::FakeNetworkManager network_manager2_;
cricket::BasicPortAllocator allocator1_;
cricket::BasicPortAllocator allocator2_;
talk_base::scoped_ptr<cricket::P2PTransportChannel> ch1_;
talk_base::scoped_ptr<cricket::P2PTransportChannel> ch2_;
};
// The tests have only a few outcomes, which we predefine.
const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
kLocalUdpToLocalUdp("local", "udp", "local", "udp", 1000);
const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
kLocalUdpToStunUdp("local", "udp", "stun", "udp", 1000);
const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
kStunUdpToLocalUdp("stun", "udp", "local", "udp", 1000);
const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
kStunUdpToStunUdp("stun", "udp", "stun", "udp", 1000);
const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
kLocalUdpToRelayUdp("local", "udp", "relay", "udp", 2000);
const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
kLocalTcpToLocalTcp("local", "tcp", "local", "tcp", 3000);
// Test the matrix of all the connectivity types we expect to see in the wild.
// Just test every combination of the configs in the Config enum.
class P2PTransportChannelTest : public P2PTransportChannelTestBase {
protected:
static const Result* kMatrix[NUM_CONFIGS][NUM_CONFIGS];
void ConfigureEndpoints(Config config1, Config config2) {
ConfigureEndpoint(0, config1);
ConfigureEndpoint(1, config2);
}
void ConfigureEndpoint(int endpoint, Config config) {
switch (config) {
case OPEN:
AddAddress(endpoint, kPublicAddrs[endpoint]);
break;
case NAT_FULL_CONE:
case NAT_ADDR_RESTRICTED:
case NAT_PORT_RESTRICTED:
case NAT_SYMMETRIC:
AddAddress(endpoint, kPrivateAddrs[endpoint]);
// Add a single NAT of the desired type
nat()->AddTranslator(kPublicAddrs[endpoint], kNatAddrs[endpoint],
static_cast<talk_base::NATType>(config - NAT_FULL_CONE))->
AddClient(kPrivateAddrs[endpoint]);
break;
case NAT_DOUBLE_CONE:
case NAT_SYMMETRIC_THEN_CONE:
AddAddress(endpoint, kCascadedPrivateAddrs[endpoint]);
// Add a two cascaded NATs of the desired types
nat()->AddTranslator(kPublicAddrs[endpoint], kNatAddrs[endpoint],
(config == NAT_DOUBLE_CONE) ?
talk_base::NAT_OPEN_CONE : talk_base::NAT_SYMMETRIC)->
AddTranslator(kPrivateAddrs[endpoint], kCascadedNatAddrs[endpoint],
talk_base::NAT_OPEN_CONE)->
AddClient(kCascadedPrivateAddrs[endpoint]);
break;
case BLOCK_UDP:
case BLOCK_UDP_AND_INCOMING_TCP:
case BLOCK_ALL_BUT_OUTGOING_HTTP:
case PROXY_HTTPS:
case PROXY_SOCKS:
AddAddress(endpoint, kPublicAddrs[endpoint]);
// Block all UDP
fw()->AddRule(false, talk_base::FP_UDP, talk_base::FD_ANY,
kPublicAddrs[endpoint]);
if (config == BLOCK_UDP_AND_INCOMING_TCP) {
// Block TCP inbound to the endpoint
fw()->AddRule(false, talk_base::FP_TCP, SocketAddress(),
kPublicAddrs[endpoint]);
} else if (config == BLOCK_ALL_BUT_OUTGOING_HTTP) {
// Block all TCP to/from the endpoint except 80/443 out
fw()->AddRule(true, talk_base::FP_TCP, kPublicAddrs[endpoint],
SocketAddress(0, 80));
fw()->AddRule(true, talk_base::FP_TCP, kPublicAddrs[endpoint],
SocketAddress(0, 443));
fw()->AddRule(false, talk_base::FP_TCP, talk_base::FD_ANY,
kPublicAddrs[endpoint]);
} else if (config == PROXY_HTTPS) {
// Block all TCP to/from the endpoint except to the proxy server
fw()->AddRule(true, talk_base::FP_TCP, kPublicAddrs[endpoint],
kHttpsProxyAddrs[endpoint]);
fw()->AddRule(false, talk_base::FP_TCP, talk_base::FD_ANY,
kPublicAddrs[endpoint]);
SetProxy(endpoint, talk_base::PROXY_HTTPS);
} else if (config == PROXY_SOCKS) {
// Block all TCP to/from the endpoint except to the proxy server
fw()->AddRule(true, talk_base::FP_TCP, kPublicAddrs[endpoint],
kSocksProxyAddrs[endpoint]);
fw()->AddRule(false, talk_base::FP_TCP, talk_base::FD_ANY,
kPublicAddrs[endpoint]);
SetProxy(endpoint, talk_base::PROXY_SOCKS5);
}
break;
default:
break;
}
}
};
// Shorthands for use in the test matrix.
#define LULU &kLocalUdpToLocalUdp
#define LUSU &kLocalUdpToStunUdp
#define SULU &kStunUdpToLocalUdp
#define SUSU &kStunUdpToStunUdp
#define LURU &kLocalUdpToRelayUdp
#define LTLT &kLocalTcpToLocalTcp
// TODO: Enable these once TestRelayServer can accept external TCP.
#define LTRT NULL
#define LSRS NULL
// Test matrix. Originator behavior defined by rows, receiever by columns.
// TODO: Fix NULLs caused by lack of TCP support in NATSocket.
// TODO: Fix NULLs caused by no HTTP proxy support.
// TODO: Rearrange rows/columns from best to worst.
const P2PTransportChannelTest::Result*
P2PTransportChannelTest::kMatrix[NUM_CONFIGS][NUM_CONFIGS] = {
// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH PRXS
/*OP*/ {LULU, LULU, LULU, LULU, LULU, LULU, LULU, LTLT, LTLT, LSRS, NULL, LTLT},
/*CO*/ {LULU, LULU, LULU, SULU, SULU, LULU, SULU, NULL, NULL, LSRS, NULL, LTRT},
/*AD*/ {LULU, LULU, LULU, SUSU, SUSU, LULU, SUSU, NULL, NULL, LSRS, NULL, LTRT},
/*PO*/ {LULU, LUSU, SUSU, SUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
/*SY*/ {LULU, LUSU, SUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
/*2C*/ {LULU, LULU, LULU, SULU, SULU, LULU, SULU, NULL, NULL, LSRS, NULL, LTRT},
/*SC*/ {LULU, LUSU, SUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
/*!U*/ {LTLT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTLT, LSRS, NULL, LTRT},
/*!T*/ {LTRT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTRT, LSRS, NULL, LTRT},
/*HT*/ {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL, LSRS},
/*PR*/ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL},
/*PR*/ {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL, LTRT},
};
// The actual tests that exercise all the various configurations.
// Test names are of the form P2PTransportChannelTest_TestOPENToNAT_FULL_CONE
#define P2P_TEST_DECLARATION(x, y, z) \
TEST_F(P2PTransportChannelTest, z##Test##x##To##y) { \
ConfigureEndpoints(x, y); \
if (kMatrix[x][y] != NULL) \
Test(*kMatrix[x][y]); \
else \
LOG(LS_WARNING) << "Not yet implemented"; \
}
#define P2P_TEST(x, y) \
P2P_TEST_DECLARATION(x, y,)
#define FLAKY_P2P_TEST(x, y) \
P2P_TEST_DECLARATION(x, y, DISABLED_)
#define P2P_TEST_SET(x) \
P2P_TEST(x, OPEN) \
P2P_TEST(x, NAT_FULL_CONE) \
P2P_TEST(x, NAT_ADDR_RESTRICTED) \
P2P_TEST(x, NAT_PORT_RESTRICTED) \
P2P_TEST(x, NAT_SYMMETRIC) \
P2P_TEST(x, NAT_DOUBLE_CONE) \
P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \
P2P_TEST(x, BLOCK_UDP) \
P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \
P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \
P2P_TEST(x, PROXY_HTTPS) \
P2P_TEST(x, PROXY_SOCKS)
#define FLAKY_P2P_TEST_SET(x) \
P2P_TEST(x, OPEN) \
P2P_TEST(x, NAT_FULL_CONE) \
FLAKY_P2P_TEST(x, NAT_ADDR_RESTRICTED) \
P2P_TEST(x, NAT_PORT_RESTRICTED) \
P2P_TEST(x, NAT_SYMMETRIC) \
P2P_TEST(x, NAT_DOUBLE_CONE) \
P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \
P2P_TEST(x, BLOCK_UDP) \
P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \
P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \
P2P_TEST(x, PROXY_HTTPS) \
P2P_TEST(x, PROXY_SOCKS)
P2P_TEST_SET(OPEN)
P2P_TEST_SET(NAT_FULL_CONE)
FLAKY_P2P_TEST_SET(NAT_ADDR_RESTRICTED)
FLAKY_P2P_TEST_SET(NAT_PORT_RESTRICTED)
FLAKY_P2P_TEST_SET(NAT_SYMMETRIC)
P2P_TEST_SET(NAT_DOUBLE_CONE)
FLAKY_P2P_TEST_SET(NAT_SYMMETRIC_THEN_CONE)
P2P_TEST_SET(BLOCK_UDP)
P2P_TEST_SET(BLOCK_UDP_AND_INCOMING_TCP)
P2P_TEST_SET(BLOCK_ALL_BUT_OUTGOING_HTTP)
P2P_TEST_SET(PROXY_HTTPS)
P2P_TEST_SET(PROXY_SOCKS)
// Test that a host behind NAT cannot be reached when incoming_only
// is set to true.
TEST_F(P2PTransportChannelTest, IncomingOnlyBlocked) {
ConfigureEndpoints(NAT_FULL_CONE, OPEN);
CreateChannels();
ch1()->set_incoming_only(true);
// Sleep for 1 second and verify that the channels are not connected.
talk_base::Thread::SleepMs(1000);
EXPECT_FALSE(ch1()->readable());
EXPECT_FALSE(ch1()->writable());
EXPECT_FALSE(ch2()->readable());
EXPECT_FALSE(ch2()->writable());
DestroyChannels();
}
// Test that a peer behind NAT can connect to a peer that has
// incoming_only flag set.
TEST_F(P2PTransportChannelTest, IncomingOnlyOpen) {
ConfigureEndpoints(OPEN, NAT_FULL_CONE);
CreateChannels();
ch1()->set_incoming_only(true);
EXPECT_TRUE_WAIT_MARGIN(ch1() != NULL && ch2() != NULL &&
ch1()->readable() && ch1()->writable() &&
ch2()->readable() && ch2()->writable(),
1000, 1000);
DestroyChannels();
}
// Test what happens when we have 2 users behind the same NAT. This can lead
// to interesting behavior because the STUN server will only give out the
// address of the outermost NAT.
class P2PTransportChannelSameNatTest : public P2PTransportChannelTestBase {
protected:
void ConfigureEndpoints(Config nat_type, Config config1, Config config2) {
ASSERT(nat_type >= NAT_FULL_CONE && nat_type <= NAT_SYMMETRIC);
talk_base::NATSocketServer::Translator* outer_nat =
nat()->AddTranslator(kPublicAddrs[0], kNatAddrs[0],
static_cast<talk_base::NATType>(nat_type - NAT_FULL_CONE));
ConfigureEndpoint(outer_nat, 0, config1);
ConfigureEndpoint(outer_nat, 1, config2);
}
void ConfigureEndpoint(talk_base::NATSocketServer::Translator* nat,
int endpoint, Config config) {
ASSERT(config <= NAT_SYMMETRIC);
if (config == OPEN) {
AddAddress(endpoint, kPrivateAddrs[endpoint]);
nat->AddClient(kPrivateAddrs[endpoint]);
} else {
AddAddress(endpoint, kCascadedPrivateAddrs[endpoint]);
nat->AddTranslator(kPrivateAddrs[endpoint], kCascadedNatAddrs[endpoint],
static_cast<talk_base::NATType>(config - NAT_FULL_CONE))->AddClient(
kCascadedPrivateAddrs[endpoint]);
}
}
};
TEST_F(P2PTransportChannelSameNatTest, TestConesBehindSameCone) {
ConfigureEndpoints(NAT_FULL_CONE, NAT_FULL_CONE, NAT_FULL_CONE);
Test(kLocalUdpToLocalUdp);
}
// Test what happens when we have multiple available pathways.
// In the future we will try different RTTs and configs for the different
// interfaces, so that we can simulate a user with Ethernet and VPN networks.
class P2PTransportChannelMultihomedTest : public P2PTransportChannelTestBase {
};
// Test that we can establish connectivity when both peers are multihomed.
TEST_F(P2PTransportChannelMultihomedTest, TestBasic) {
AddAddress(0, kPublicAddrs[0]);
AddAddress(0, kAlternateAddrs[0]);
AddAddress(1, kPublicAddrs[1]);
AddAddress(1, kAlternateAddrs[1]);
Test(kLocalUdpToLocalUdp);
}
// Test that we can quickly switch links if an interface goes down.
TEST_F(P2PTransportChannelMultihomedTest, TestFailover) {
AddAddress(0, kPublicAddrs[0]);
AddAddress(1, kPublicAddrs[1]);
AddAddress(1, kAlternateAddrs[1]);
// Use only local ports for simplicity.
SetAllocatorFlags(0, kOnlyLocalPorts);
SetAllocatorFlags(1, kOnlyLocalPorts);
// Create channels and let them go writable, as usual.
CreateChannels();
EXPECT_TRUE_WAIT(ch1()->readable() && ch1()->writable() &&
ch2()->readable() && ch2()->writable(),
1000);
EXPECT_TRUE(
ch1()->best_connection() && ch2()->best_connection() &&
LocalCandidate(ch1())->address().EqualIPs(kPublicAddrs[0]) &&
RemoteCandidate(ch1())->address().EqualIPs(kPublicAddrs[1]));
// Blackhole any traffic to or from the public addrs.
LOG(LS_INFO) << "Failing over...";
fw()->AddRule(false, talk_base::FP_ANY, talk_base::FD_ANY,
kPublicAddrs[1]);
// We should detect loss of connectivity within 5 seconds or so.
EXPECT_TRUE_WAIT(!ch1()->writable(), 7000);
// We should switch over to use the alternate addr immediately
// when we lose writability.
EXPECT_TRUE_WAIT(
ch1()->best_connection() && ch2()->best_connection() &&
LocalCandidate(ch1())->address().EqualIPs(kPublicAddrs[0]) &&
RemoteCandidate(ch1())->address().EqualIPs(kAlternateAddrs[1]),
3000);
DestroyChannels();
}
// Test that we can switch links in a coordinated fashion.
TEST_F(P2PTransportChannelMultihomedTest, TestDrain) {
AddAddress(0, kPublicAddrs[0]);
AddAddress(1, kPublicAddrs[1]);
// Use only local ports for simplicity.
SetAllocatorFlags(0, kOnlyLocalPorts);
SetAllocatorFlags(1, kOnlyLocalPorts);
// Create channels and let them go writable, as usual.
CreateChannels();
EXPECT_TRUE_WAIT(ch1()->readable() && ch1()->writable() &&
ch2()->readable() && ch2()->writable(),
1000);
EXPECT_TRUE(
ch1()->best_connection() && ch2()->best_connection() &&
LocalCandidate(ch1())->address().EqualIPs(kPublicAddrs[0]) &&
RemoteCandidate(ch1())->address().EqualIPs(kPublicAddrs[1]));
// Remove the public interface, add the alternate interface, and allocate
// a new generation of candidates for the new interface (via Connect()).
LOG(LS_INFO) << "Draining...";
AddAddress(1, kAlternateAddrs[1]);
RemoveAddress(1, kPublicAddrs[1]);
ch2()->Connect();
// We should switch over to use the alternate address after
// an exchange of pings.
EXPECT_TRUE_WAIT(
ch1()->best_connection() && ch2()->best_connection() &&
LocalCandidate(ch1())->address().EqualIPs(kPublicAddrs[0]) &&
RemoteCandidate(ch1())->address().EqualIPs(kAlternateAddrs[1]),
3000);
DestroyChannels();
}