1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "net/websockets/websocket_throttle.h"
6
7#include <string>
8
9#include "base/message_loop/message_loop.h"
10#include "net/base/address_list.h"
11#include "net/base/test_completion_callback.h"
12#include "net/socket_stream/socket_stream.h"
13#include "net/url_request/url_request_test_util.h"
14#include "net/websockets/websocket_job.h"
15#include "testing/gtest/include/gtest/gtest.h"
16#include "testing/platform_test.h"
17#include "url/gurl.h"
18
19namespace net {
20
21namespace {
22
23class DummySocketStreamDelegate : public SocketStream::Delegate {
24 public:
25  DummySocketStreamDelegate() {}
26  virtual ~DummySocketStreamDelegate() {}
27  virtual void OnConnected(
28      SocketStream* socket, int max_pending_send_allowed) OVERRIDE {}
29  virtual void OnSentData(SocketStream* socket,
30                          int amount_sent) OVERRIDE {}
31  virtual void OnReceivedData(SocketStream* socket,
32                              const char* data, int len) OVERRIDE {}
33  virtual void OnClose(SocketStream* socket) OVERRIDE {}
34};
35
36class WebSocketThrottleTestContext : public TestURLRequestContext {
37 public:
38  explicit WebSocketThrottleTestContext(bool enable_websocket_over_spdy)
39      : TestURLRequestContext(true) {
40    HttpNetworkSession::Params params;
41    params.enable_websocket_over_spdy = enable_websocket_over_spdy;
42    Init();
43  }
44};
45
46}  // namespace
47
48class WebSocketThrottleTest : public PlatformTest {
49 protected:
50  static IPEndPoint MakeAddr(int a1, int a2, int a3, int a4) {
51    IPAddressNumber ip;
52    ip.push_back(a1);
53    ip.push_back(a2);
54    ip.push_back(a3);
55    ip.push_back(a4);
56    return IPEndPoint(ip, 0);
57  }
58
59  static void MockSocketStreamConnect(
60      SocketStream* socket, const AddressList& list) {
61    socket->set_addresses(list);
62    // TODO(toyoshim): We should introduce additional tests on cases via proxy.
63    socket->proxy_info_.UseDirect();
64    // In SocketStream::Connect(), it adds reference to socket, which is
65    // balanced with SocketStream::Finish() that is finally called from
66    // SocketStream::Close() or SocketStream::DetachDelegate(), when
67    // next_state_ is not STATE_NONE.
68    // If next_state_ is STATE_NONE, SocketStream::Close() or
69    // SocketStream::DetachDelegate() won't call SocketStream::Finish(),
70    // so Release() won't be called.  Thus, we don't need socket->AddRef()
71    // here.
72    DCHECK_EQ(socket->next_state_, SocketStream::STATE_NONE);
73  }
74};
75
76TEST_F(WebSocketThrottleTest, Throttle) {
77  // TODO(toyoshim): We need to consider both spdy-enabled and spdy-disabled
78  // configuration.
79  WebSocketThrottleTestContext context(true);
80  DummySocketStreamDelegate delegate;
81
82  // For host1: 1.2.3.4, 1.2.3.5, 1.2.3.6
83  AddressList addr;
84  addr.push_back(MakeAddr(1, 2, 3, 4));
85  addr.push_back(MakeAddr(1, 2, 3, 5));
86  addr.push_back(MakeAddr(1, 2, 3, 6));
87  scoped_refptr<WebSocketJob> w1(new WebSocketJob(&delegate));
88  scoped_refptr<SocketStream> s1(
89      new SocketStream(GURL("ws://host1/"), w1.get(), &context, NULL));
90  w1->InitSocketStream(s1.get());
91  WebSocketThrottleTest::MockSocketStreamConnect(s1.get(), addr);
92
93  DVLOG(1) << "socket1";
94  TestCompletionCallback callback_s1;
95  // Trying to open connection to host1 will start without wait.
96  EXPECT_EQ(OK, w1->OnStartOpenConnection(s1.get(), callback_s1.callback()));
97
98  // Now connecting to host1, so waiting queue looks like
99  // Address | head -> tail
100  // 1.2.3.4 | w1
101  // 1.2.3.5 | w1
102  // 1.2.3.6 | w1
103
104  // For host2: 1.2.3.4
105  addr.clear();
106  addr.push_back(MakeAddr(1, 2, 3, 4));
107  scoped_refptr<WebSocketJob> w2(new WebSocketJob(&delegate));
108  scoped_refptr<SocketStream> s2(
109      new SocketStream(GURL("ws://host2/"), w2.get(), &context, NULL));
110  w2->InitSocketStream(s2.get());
111  WebSocketThrottleTest::MockSocketStreamConnect(s2.get(), addr);
112
113  DVLOG(1) << "socket2";
114  TestCompletionCallback callback_s2;
115  // Trying to open connection to host2 will wait for w1.
116  EXPECT_EQ(ERR_IO_PENDING,
117            w2->OnStartOpenConnection(s2.get(), callback_s2.callback()));
118  // Now waiting queue looks like
119  // Address | head -> tail
120  // 1.2.3.4 | w1 w2
121  // 1.2.3.5 | w1
122  // 1.2.3.6 | w1
123
124  // For host3: 1.2.3.5
125  addr.clear();
126  addr.push_back(MakeAddr(1, 2, 3, 5));
127  scoped_refptr<WebSocketJob> w3(new WebSocketJob(&delegate));
128  scoped_refptr<SocketStream> s3(
129      new SocketStream(GURL("ws://host3/"), w3.get(), &context, NULL));
130  w3->InitSocketStream(s3.get());
131  WebSocketThrottleTest::MockSocketStreamConnect(s3.get(), addr);
132
133  DVLOG(1) << "socket3";
134  TestCompletionCallback callback_s3;
135  // Trying to open connection to host3 will wait for w1.
136  EXPECT_EQ(ERR_IO_PENDING,
137            w3->OnStartOpenConnection(s3.get(), callback_s3.callback()));
138  // Address | head -> tail
139  // 1.2.3.4 | w1 w2
140  // 1.2.3.5 | w1    w3
141  // 1.2.3.6 | w1
142
143  // For host4: 1.2.3.4, 1.2.3.6
144  addr.clear();
145  addr.push_back(MakeAddr(1, 2, 3, 4));
146  addr.push_back(MakeAddr(1, 2, 3, 6));
147  scoped_refptr<WebSocketJob> w4(new WebSocketJob(&delegate));
148  scoped_refptr<SocketStream> s4(
149      new SocketStream(GURL("ws://host4/"), w4.get(), &context, NULL));
150  w4->InitSocketStream(s4.get());
151  WebSocketThrottleTest::MockSocketStreamConnect(s4.get(), addr);
152
153  DVLOG(1) << "socket4";
154  TestCompletionCallback callback_s4;
155  // Trying to open connection to host4 will wait for w1, w2.
156  EXPECT_EQ(ERR_IO_PENDING,
157            w4->OnStartOpenConnection(s4.get(), callback_s4.callback()));
158  // Address | head -> tail
159  // 1.2.3.4 | w1 w2    w4
160  // 1.2.3.5 | w1    w3
161  // 1.2.3.6 | w1       w4
162
163  // For host5: 1.2.3.6
164  addr.clear();
165  addr.push_back(MakeAddr(1, 2, 3, 6));
166  scoped_refptr<WebSocketJob> w5(new WebSocketJob(&delegate));
167  scoped_refptr<SocketStream> s5(
168      new SocketStream(GURL("ws://host5/"), w5.get(), &context, NULL));
169  w5->InitSocketStream(s5.get());
170  WebSocketThrottleTest::MockSocketStreamConnect(s5.get(), addr);
171
172  DVLOG(1) << "socket5";
173  TestCompletionCallback callback_s5;
174  // Trying to open connection to host5 will wait for w1, w4
175  EXPECT_EQ(ERR_IO_PENDING,
176            w5->OnStartOpenConnection(s5.get(), callback_s5.callback()));
177  // Address | head -> tail
178  // 1.2.3.4 | w1 w2    w4
179  // 1.2.3.5 | w1    w3
180  // 1.2.3.6 | w1       w4 w5
181
182  // For host6: 1.2.3.6
183  addr.clear();
184  addr.push_back(MakeAddr(1, 2, 3, 6));
185  scoped_refptr<WebSocketJob> w6(new WebSocketJob(&delegate));
186  scoped_refptr<SocketStream> s6(
187      new SocketStream(GURL("ws://host6/"), w6.get(), &context, NULL));
188  w6->InitSocketStream(s6.get());
189  WebSocketThrottleTest::MockSocketStreamConnect(s6.get(), addr);
190
191  DVLOG(1) << "socket6";
192  TestCompletionCallback callback_s6;
193  // Trying to open connection to host6 will wait for w1, w4, w5
194  EXPECT_EQ(ERR_IO_PENDING,
195            w6->OnStartOpenConnection(s6.get(), callback_s6.callback()));
196  // Address | head -> tail
197  // 1.2.3.4 | w1 w2    w4
198  // 1.2.3.5 | w1    w3
199  // 1.2.3.6 | w1       w4 w5 w6
200
201  // Receive partial response on w1, still connecting.
202  DVLOG(1) << "socket1 1";
203  static const char kHeader[] = "HTTP/1.1 101 WebSocket Protocol\r\n";
204  w1->OnReceivedData(s1.get(), kHeader, sizeof(kHeader) - 1);
205  EXPECT_FALSE(callback_s2.have_result());
206  EXPECT_FALSE(callback_s3.have_result());
207  EXPECT_FALSE(callback_s4.have_result());
208  EXPECT_FALSE(callback_s5.have_result());
209  EXPECT_FALSE(callback_s6.have_result());
210
211  // Receive rest of handshake response on w1.
212  DVLOG(1) << "socket1 2";
213  static const char kHeader2[] =
214      "Upgrade: WebSocket\r\n"
215      "Connection: Upgrade\r\n"
216      "Sec-WebSocket-Origin: http://www.google.com\r\n"
217      "Sec-WebSocket-Location: ws://websocket.chromium.org\r\n"
218      "\r\n"
219      "8jKS'y:G*Co,Wxa-";
220  w1->OnReceivedData(s1.get(), kHeader2, sizeof(kHeader2) - 1);
221  base::MessageLoopForIO::current()->RunUntilIdle();
222  // Now, w1 is open.
223  EXPECT_EQ(WebSocketJob::OPEN, w1->state());
224  // So, w2 and w3 can start connecting. w4 needs to wait w2 (1.2.3.4)
225  EXPECT_TRUE(callback_s2.have_result());
226  EXPECT_TRUE(callback_s3.have_result());
227  EXPECT_FALSE(callback_s4.have_result());
228  // Address | head -> tail
229  // 1.2.3.4 |    w2    w4
230  // 1.2.3.5 |       w3
231  // 1.2.3.6 |          w4 w5 w6
232
233  // Closing s1 doesn't change waiting queue.
234  DVLOG(1) << "socket1 close";
235  w1->OnClose(s1.get());
236  base::MessageLoopForIO::current()->RunUntilIdle();
237  EXPECT_FALSE(callback_s4.have_result());
238  s1->DetachDelegate();
239  // Address | head -> tail
240  // 1.2.3.4 |    w2    w4
241  // 1.2.3.5 |       w3
242  // 1.2.3.6 |          w4 w5 w6
243
244  // w5 can close while waiting in queue.
245  DVLOG(1) << "socket5 close";
246  // w5 close() closes SocketStream that change state to STATE_CLOSE, calls
247  // DoLoop(), so OnClose() callback will be called.
248  w5->OnClose(s5.get());
249  base::MessageLoopForIO::current()->RunUntilIdle();
250  EXPECT_FALSE(callback_s4.have_result());
251  // Address | head -> tail
252  // 1.2.3.4 |    w2    w4
253  // 1.2.3.5 |       w3
254  // 1.2.3.6 |          w4 w6
255  s5->DetachDelegate();
256
257  // w6 close abnormally (e.g. renderer finishes) while waiting in queue.
258  DVLOG(1) << "socket6 close abnormally";
259  w6->DetachDelegate();
260  base::MessageLoopForIO::current()->RunUntilIdle();
261  EXPECT_FALSE(callback_s4.have_result());
262  // Address | head -> tail
263  // 1.2.3.4 |    w2    w4
264  // 1.2.3.5 |       w3
265  // 1.2.3.6 |          w4
266
267  // Closing s2 kicks w4 to start connecting.
268  DVLOG(1) << "socket2 close";
269  w2->OnClose(s2.get());
270  base::MessageLoopForIO::current()->RunUntilIdle();
271  EXPECT_TRUE(callback_s4.have_result());
272  // Address | head -> tail
273  // 1.2.3.4 |          w4
274  // 1.2.3.5 |       w3
275  // 1.2.3.6 |          w4
276  s2->DetachDelegate();
277
278  DVLOG(1) << "socket3 close";
279  w3->OnClose(s3.get());
280  base::MessageLoopForIO::current()->RunUntilIdle();
281  s3->DetachDelegate();
282  w4->OnClose(s4.get());
283  s4->DetachDelegate();
284  DVLOG(1) << "Done";
285  base::MessageLoopForIO::current()->RunUntilIdle();
286}
287
288TEST_F(WebSocketThrottleTest, NoThrottleForDuplicateAddress) {
289  WebSocketThrottleTestContext context(true);
290  DummySocketStreamDelegate delegate;
291
292  // For localhost: 127.0.0.1, 127.0.0.1
293  AddressList addr;
294  addr.push_back(MakeAddr(127, 0, 0, 1));
295  addr.push_back(MakeAddr(127, 0, 0, 1));
296  scoped_refptr<WebSocketJob> w1(new WebSocketJob(&delegate));
297  scoped_refptr<SocketStream> s1(
298      new SocketStream(GURL("ws://localhost/"), w1.get(), &context, NULL));
299  w1->InitSocketStream(s1.get());
300  WebSocketThrottleTest::MockSocketStreamConnect(s1.get(), addr);
301
302  DVLOG(1) << "socket1";
303  TestCompletionCallback callback_s1;
304  // Trying to open connection to localhost will start without wait.
305  EXPECT_EQ(OK, w1->OnStartOpenConnection(s1.get(), callback_s1.callback()));
306
307  DVLOG(1) << "socket1 close";
308  w1->OnClose(s1.get());
309  s1->DetachDelegate();
310  DVLOG(1) << "Done";
311  base::MessageLoopForIO::current()->RunUntilIdle();
312}
313
314// A connection should not be blocked by another connection to the same IP
315// with a different port.
316TEST_F(WebSocketThrottleTest, NoThrottleForDistinctPort) {
317  WebSocketThrottleTestContext context(false);
318  DummySocketStreamDelegate delegate;
319  IPAddressNumber localhost;
320  ParseIPLiteralToNumber("127.0.0.1", &localhost);
321
322  // socket1: 127.0.0.1:80
323  scoped_refptr<WebSocketJob> w1(new WebSocketJob(&delegate));
324  scoped_refptr<SocketStream> s1(
325      new SocketStream(GURL("ws://localhost:80/"), w1.get(), &context, NULL));
326  w1->InitSocketStream(s1.get());
327  MockSocketStreamConnect(s1.get(),
328                          AddressList::CreateFromIPAddress(localhost, 80));
329
330  DVLOG(1) << "connecting socket1";
331  TestCompletionCallback callback_s1;
332  // Trying to open connection to localhost:80 will start without waiting.
333  EXPECT_EQ(OK, w1->OnStartOpenConnection(s1.get(), callback_s1.callback()));
334
335  // socket2: 127.0.0.1:81
336  scoped_refptr<WebSocketJob> w2(new WebSocketJob(&delegate));
337  scoped_refptr<SocketStream> s2(
338      new SocketStream(GURL("ws://localhost:81/"), w2.get(), &context, NULL));
339  w2->InitSocketStream(s2.get());
340  MockSocketStreamConnect(s2.get(),
341                          AddressList::CreateFromIPAddress(localhost, 81));
342
343  DVLOG(1) << "connecting socket2";
344  TestCompletionCallback callback_s2;
345  // Trying to open connection to localhost:81 will start without waiting.
346  EXPECT_EQ(OK, w2->OnStartOpenConnection(s2.get(), callback_s2.callback()));
347
348  DVLOG(1) << "closing socket1";
349  w1->OnClose(s1.get());
350  s1->DetachDelegate();
351
352  DVLOG(1) << "closing socket2";
353  w2->OnClose(s2.get());
354  s2->DetachDelegate();
355  DVLOG(1) << "Done";
356  base::MessageLoopForIO::current()->RunUntilIdle();
357}
358
359}  // namespace net
360