1 // Copyright 2011 Google Inc. All Rights Reserved.
2
3
4 #include <string>
5
6 #include "talk/base/asynchttprequest.h"
7 #include "talk/base/gunit.h"
8 #include "talk/base/fakenetwork.h"
9 #include "talk/base/scoped_ptr.h"
10 #include "talk/base/socketaddress.h"
11 #include "talk/p2p/base/basicpacketsocketfactory.h"
12 #include "talk/p2p/base/relayport.h"
13 #include "talk/p2p/base/stunport.h"
14 #include "talk/p2p/client/connectivitychecker.h"
15 #include "talk/p2p/client/httpportallocator.h"
16
17 namespace cricket {
18
19 static const talk_base::SocketAddress kClientAddr1("11.11.11.11", 0);
20 static const talk_base::SocketAddress kClientAddr2("22.22.22.22", 0);
21 static const talk_base::SocketAddress kExternalAddr("33.33.33.33", 3333);
22 static const talk_base::SocketAddress kStunAddr("44.44.44.44", 4444);
23 static const talk_base::SocketAddress kRelayAddr("55.55.55.55", 5555);
24 static const talk_base::SocketAddress kProxyAddr("66.66.66.66", 6666);
25 static const talk_base::ProxyType kProxyType = talk_base::PROXY_HTTPS;
26 static const char kChannelName[] = "rtp_test";
27 static const int kComponent = 1;
28 static const char kRelayHost[] = "relay.google.com";
29 static const char kRelayToken[] =
30 "CAESFwoOb2phQGdvb2dsZS5jb20Q043h47MmGhBTB1rbfIXkhuarDCZe+xF6";
31 static const char kBrowserAgent[] = "browser_test";
32 static const char kJid[] = "a.b@c";
33 static const char kUserName[] = "testuser";
34 static const char kPassword[] = "testpassword";
35 static const char kMagicCookie[] = "testcookie";
36 static const char kRelayUdpPort[] = "4444";
37 static const char kRelayTcpPort[] = "5555";
38 static const char kRelaySsltcpPort[] = "6666";
39 static const char kSessionId[] = "testsession";
40 static const char kConnection[] = "testconnection";
41 static const int kMinPort = 1000;
42 static const int kMaxPort = 2000;
43
44 // Fake implementation to mock away real network usage.
45 class FakeRelayPort : public RelayPort {
46 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)47 FakeRelayPort(talk_base::Thread* thread,
48 talk_base::PacketSocketFactory* factory,
49 talk_base::Network* network, const talk_base::IPAddress& ip,
50 int min_port, int max_port,
51 const std::string& username, const std::string& password)
52 : RelayPort(thread, factory, network, ip, min_port, max_port,
53 username, password) {
54 }
55
56 // Just signal that we are done.
PrepareAddress()57 virtual void PrepareAddress() {
58 SignalPortComplete(this);
59 }
60 };
61
62 // Fake implementation to mock away real network usage.
63 class FakeStunPort : public StunPort {
64 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 std::string & username,const std::string & password,const talk_base::SocketAddress & server_addr)65 FakeStunPort(talk_base::Thread* thread,
66 talk_base::PacketSocketFactory* factory,
67 talk_base::Network* network,
68 const talk_base::IPAddress& ip,
69 int min_port, int max_port,
70 const std::string& username, const std::string& password,
71 const talk_base::SocketAddress& server_addr)
72 : StunPort(thread, factory, network, ip, min_port, max_port,
73 username, password, server_addr) {
74 }
75
76 // Just set external address and signal that we are done.
PrepareAddress()77 virtual void PrepareAddress() {
78 AddAddress(kExternalAddr, kExternalAddr, "udp",
79 STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, true);
80 SignalPortComplete(this);
81 }
82 };
83
84 // Fake implementation to mock away real network usage by responding
85 // to http requests immediately.
86 class FakeHttpPortAllocatorSession : public TestHttpPortAllocatorSession {
87 public:
FakeHttpPortAllocatorSession(HttpPortAllocator * allocator,const std::string & content_name,int component,const std::string & ice_ufrag,const std::string & ice_pwd,const std::vector<talk_base::SocketAddress> & stun_hosts,const std::vector<std::string> & relay_hosts,const std::string & relay_token,const std::string & agent)88 FakeHttpPortAllocatorSession(
89 HttpPortAllocator* allocator,
90 const std::string& content_name,
91 int component,
92 const std::string& ice_ufrag, const std::string& ice_pwd,
93 const std::vector<talk_base::SocketAddress>& stun_hosts,
94 const std::vector<std::string>& relay_hosts,
95 const std::string& relay_token,
96 const std::string& agent)
97 : TestHttpPortAllocatorSession(allocator,
98 content_name,
99 component,
100 ice_ufrag,
101 ice_pwd,
102 stun_hosts,
103 relay_hosts,
104 relay_token,
105 agent) {
106 }
SendSessionRequest(const std::string & host,int port)107 virtual void SendSessionRequest(const std::string& host, int port) {
108 FakeReceiveSessionResponse(host, port);
109 }
110
111 // Pass results to the real implementation.
FakeReceiveSessionResponse(const std::string & host,int port)112 void FakeReceiveSessionResponse(const std::string& host, int port) {
113 talk_base::AsyncHttpRequest* response = CreateAsyncHttpResponse(port);
114 TestHttpPortAllocatorSession::OnRequestDone(response);
115 response->Destroy(true);
116 }
117
118 private:
119 // Helper method for creating a response to a relay session request.
CreateAsyncHttpResponse(int port)120 talk_base::AsyncHttpRequest* CreateAsyncHttpResponse(int port) {
121 talk_base::AsyncHttpRequest* request =
122 new talk_base::AsyncHttpRequest(kBrowserAgent);
123 std::stringstream ss;
124 ss << "username=" << kUserName << std::endl
125 << "password=" << kPassword << std::endl
126 << "magic_cookie=" << kMagicCookie << std::endl
127 << "relay.ip=" << kRelayAddr.ipaddr().ToString() << std::endl
128 << "relay.udp_port=" << kRelayUdpPort << std::endl
129 << "relay.tcp_port=" << kRelayTcpPort << std::endl
130 << "relay.ssltcp_port=" << kRelaySsltcpPort << std::endl;
131 request->response().document.reset(
132 new talk_base::MemoryStream(ss.str().c_str()));
133 request->response().set_success();
134 request->set_port(port);
135 request->set_secure(port == talk_base::HTTP_SECURE_PORT);
136 return request;
137 }
138 };
139
140 // Fake implementation for creating fake http sessions.
141 class FakeHttpPortAllocator : public HttpPortAllocator {
142 public:
FakeHttpPortAllocator(talk_base::NetworkManager * network_manager,const std::string & user_agent)143 FakeHttpPortAllocator(talk_base::NetworkManager* network_manager,
144 const std::string& user_agent)
145 : HttpPortAllocator(network_manager, user_agent) {
146 }
147
CreateSessionInternal(const std::string & content_name,int component,const std::string & ice_ufrag,const std::string & ice_pwd)148 virtual PortAllocatorSession* CreateSessionInternal(
149 const std::string& content_name, int component,
150 const std::string& ice_ufrag, const std::string& ice_pwd) {
151 std::vector<talk_base::SocketAddress> stun_hosts;
152 stun_hosts.push_back(kStunAddr);
153 std::vector<std::string> relay_hosts;
154 relay_hosts.push_back(kRelayHost);
155 return new FakeHttpPortAllocatorSession(this,
156 content_name,
157 component,
158 ice_ufrag,
159 ice_pwd,
160 stun_hosts,
161 relay_hosts,
162 kRelayToken,
163 kBrowserAgent);
164 }
165 };
166
167 class ConnectivityCheckerForTest : public ConnectivityChecker {
168 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)169 ConnectivityCheckerForTest(talk_base::Thread* worker,
170 const std::string& jid,
171 const std::string& session_id,
172 const std::string& user_agent,
173 const std::string& relay_token,
174 const std::string& connection)
175 : ConnectivityChecker(worker,
176 jid,
177 session_id,
178 user_agent,
179 relay_token,
180 connection),
181 proxy_initiated_(false) {
182 }
183
network_manager() const184 talk_base::FakeNetworkManager* network_manager() const {
185 return network_manager_;
186 }
187
port_allocator() const188 FakeHttpPortAllocator* port_allocator() const {
189 return fake_port_allocator_;
190 }
191
192 protected:
193 // Overridden methods for faking a real network.
CreateNetworkManager()194 virtual talk_base::NetworkManager* CreateNetworkManager() {
195 network_manager_ = new talk_base::FakeNetworkManager();
196 return network_manager_;
197 }
CreateSocketFactory(talk_base::Thread * thread)198 virtual talk_base::BasicPacketSocketFactory* CreateSocketFactory(
199 talk_base::Thread* thread) {
200 // Create socket factory, for simplicity, let it run on the current thread.
201 socket_factory_ =
202 new talk_base::BasicPacketSocketFactory(talk_base::Thread::Current());
203 return socket_factory_;
204 }
CreatePortAllocator(talk_base::NetworkManager * network_manager,const std::string & user_agent,const std::string & relay_token)205 virtual HttpPortAllocator* CreatePortAllocator(
206 talk_base::NetworkManager* network_manager,
207 const std::string& user_agent,
208 const std::string& relay_token) {
209 fake_port_allocator_ =
210 new FakeHttpPortAllocator(network_manager, user_agent);
211 return fake_port_allocator_;
212 }
CreateStunPort(const std::string & username,const std::string & password,const PortConfiguration * config,talk_base::Network * network)213 virtual StunPort* CreateStunPort(
214 const std::string& username, const std::string& password,
215 const PortConfiguration* config, talk_base::Network* network) {
216 return new FakeStunPort(worker(), socket_factory_,
217 network, network->ip(),
218 kMinPort, kMaxPort,
219 username, password,
220 config->stun_address);
221 }
CreateRelayPort(const std::string & username,const std::string & password,const PortConfiguration * config,talk_base::Network * network)222 virtual RelayPort* CreateRelayPort(
223 const std::string& username, const std::string& password,
224 const PortConfiguration* config, talk_base::Network* network) {
225 return new FakeRelayPort(worker(), socket_factory_,
226 network, network->ip(),
227 kMinPort, kMaxPort,
228 username, password);
229 }
InitiateProxyDetection()230 virtual void InitiateProxyDetection() {
231 if (!proxy_initiated_) {
232 proxy_initiated_ = true;
233 proxy_info_.address = kProxyAddr;
234 proxy_info_.type = kProxyType;
235 SetProxyInfo(proxy_info_);
236 }
237 }
238
GetProxyInfo() const239 virtual talk_base::ProxyInfo GetProxyInfo() const {
240 return proxy_info_;
241 }
242
243 private:
244 talk_base::BasicPacketSocketFactory* socket_factory_;
245 FakeHttpPortAllocator* fake_port_allocator_;
246 talk_base::FakeNetworkManager* network_manager_;
247 talk_base::ProxyInfo proxy_info_;
248 bool proxy_initiated_;
249 };
250
251 class ConnectivityCheckerTest : public testing::Test {
252 protected:
VerifyNic(const NicInfo & info,const talk_base::SocketAddress & local_address)253 void VerifyNic(const NicInfo& info,
254 const talk_base::SocketAddress& local_address) {
255 // Verify that the external address has been set.
256 EXPECT_EQ(kExternalAddr, info.external_address);
257
258 // Verify that the stun server address has been set.
259 EXPECT_EQ(kStunAddr, info.stun_server_address);
260
261 // Verify that the media server address has been set. Don't care
262 // about port since it is different for different protocols.
263 EXPECT_EQ(kRelayAddr.ipaddr(), info.media_server_address.ipaddr());
264
265 // Verify that local ip matches.
266 EXPECT_EQ(local_address.ipaddr(), info.ip);
267
268 // Verify that we have received responses for our
269 // pings. Unsuccessful ping has rtt value -1, successful >= 0.
270 EXPECT_GE(info.stun.rtt, 0);
271 EXPECT_GE(info.udp.rtt, 0);
272 EXPECT_GE(info.tcp.rtt, 0);
273 EXPECT_GE(info.ssltcp.rtt, 0);
274
275 // If proxy has been set, verify address and type.
276 if (!info.proxy_info.address.IsNil()) {
277 EXPECT_EQ(kProxyAddr, info.proxy_info.address);
278 EXPECT_EQ(kProxyType, info.proxy_info.type);
279 }
280 }
281 };
282
283 // Tests a configuration with two network interfaces. Verifies that 4
284 // combinations of ip/proxy are created and that all protocols are
285 // tested on each combination.
TEST_F(ConnectivityCheckerTest,TestStart)286 TEST_F(ConnectivityCheckerTest, TestStart) {
287 ConnectivityCheckerForTest connectivity_checker(talk_base::Thread::Current(),
288 kJid,
289 kSessionId,
290 kBrowserAgent,
291 kRelayToken,
292 kConnection);
293 connectivity_checker.Initialize();
294 connectivity_checker.set_stun_address(kStunAddr);
295 connectivity_checker.network_manager()->AddInterface(kClientAddr1);
296 connectivity_checker.network_manager()->AddInterface(kClientAddr2);
297
298 connectivity_checker.Start();
299 talk_base::Thread::Current()->ProcessMessages(1000);
300
301 NicMap nics = connectivity_checker.GetResults();
302
303 // There should be 4 nics in our map. 2 for each interface added,
304 // one with proxy set and one without.
305 EXPECT_EQ(4U, nics.size());
306
307 // First verify interfaces without proxy.
308 talk_base::SocketAddress nilAddress;
309
310 // First lookup the address of the first nic combined with no proxy.
311 NicMap::iterator i = nics.find(NicId(kClientAddr1.ipaddr(), nilAddress));
312 ASSERT(i != nics.end());
313 NicInfo info = i->second;
314 VerifyNic(info, kClientAddr1);
315
316 // Then make sure the second device has been tested without proxy.
317 i = nics.find(NicId(kClientAddr2.ipaddr(), nilAddress));
318 ASSERT(i != nics.end());
319 info = i->second;
320 VerifyNic(info, kClientAddr2);
321
322 // Now verify both interfaces with proxy.
323 i = nics.find(NicId(kClientAddr1.ipaddr(), kProxyAddr));
324 ASSERT(i != nics.end());
325 info = i->second;
326 VerifyNic(info, kClientAddr1);
327
328 i = nics.find(NicId(kClientAddr2.ipaddr(), kProxyAddr));
329 ASSERT(i != nics.end());
330 info = i->second;
331 VerifyNic(info, kClientAddr2);
332 };
333
334 // Tests that nothing bad happens if thera are no network interfaces
335 // available to check.
TEST_F(ConnectivityCheckerTest,TestStartNoNetwork)336 TEST_F(ConnectivityCheckerTest, TestStartNoNetwork) {
337 ConnectivityCheckerForTest connectivity_checker(talk_base::Thread::Current(),
338 kJid,
339 kSessionId,
340 kBrowserAgent,
341 kRelayToken,
342 kConnection);
343 connectivity_checker.Initialize();
344 connectivity_checker.Start();
345 talk_base::Thread::Current()->ProcessMessages(1000);
346
347 NicMap nics = connectivity_checker.GetResults();
348
349 // Verify that no nics where checked.
350 EXPECT_EQ(0U, nics.size());
351 }
352
353 } // namespace cricket
354