/* * libjingle * Copyright 2011, 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 #include "talk/p2p/base/basicpacketsocketfactory.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" #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) { } // 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& stun_hosts, const std::vector& 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 stun_hosts; stun_hosts.push_back(kStunAddr); std::vector 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, #ifdef USE_WEBRTC_DEV_BRANCH network->GetBestIP(), #else // USE_WEBRTC_DEV_BRANCH network->ip(), #endif // USE_WEBRTC_DEV_BRANCH 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, #ifdef USE_WEBRTC_DEV_BRANCH network->GetBestIP(), #else // USE_WEBRTC_DEV_BRANCH network->ip(), #endif // USE_WEBRTC_DEV_BRANCH 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