summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/net/websocket_browsertest.cc94
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--net/base/nss_memio.c8
-rw-r--r--net/base/nss_memio.h5
-rw-r--r--net/data/websocket/README13
-rw-r--r--net/data/websocket/close-with-split-packet_wsh.py30
-rw-r--r--net/data/websocket/split_packet_check.html37
-rw-r--r--net/socket/ssl_client_socket_nss.cc165
8 files changed, 324 insertions, 29 deletions
diff --git a/chrome/browser/net/websocket_browsertest.cc b/chrome/browser/net/websocket_browsertest.cc
new file mode 100644
index 0000000..4537e4d
--- /dev/null
+++ b/chrome/browser/net/websocket_browsertest.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/base/test_data_directory.h"
+#include "net/test/test_server.h"
+
+namespace {
+
+class WebSocketBrowserTest : public InProcessBrowserTest {
+ public:
+ WebSocketBrowserTest()
+ : ws_server_(net::TestServer::TYPE_WS,
+ net::TestServer::kLocalhost,
+ net::GetWebSocketTestDataDirectory()),
+ wss_server_(net::TestServer::TYPE_WSS,
+ SSLOptions(SSLOptions::CERT_OK),
+ net::GetWebSocketTestDataDirectory()) {
+ }
+
+ protected:
+ net::TestServer ws_server_;
+ net::TestServer wss_server_;
+
+ private:
+ typedef net::TestServer::SSLOptions SSLOptions;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketBrowserTest);
+};
+
+// Test that the browser can handle a WebSocket frame split into multiple TCP
+// segments.
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserTest, WebSocketSplitSegments) {
+ // Launch a WebSocket server.
+ ASSERT_TRUE(ws_server_.Start());
+
+ // Setup page title observer.
+ content::WebContents* tab = chrome::GetActiveWebContents(browser());
+ content::TitleWatcher watcher(tab, ASCIIToUTF16("PASS"));
+ watcher.AlsoWaitForTitle(ASCIIToUTF16("FAIL"));
+
+ // Visit a HTTP page for testing.
+ std::string scheme("http");
+ GURL::Replacements replacements;
+ replacements.SetSchemeStr(scheme);
+ ui_test_utils::NavigateToURL(
+ browser(),
+ ws_server_.GetURL(
+ "split_packet_check.html").ReplaceComponents(replacements));
+
+ const string16 result = watcher.WaitAndGetTitle();
+ EXPECT_TRUE(EqualsASCII(result, "PASS"));
+}
+
+#if defined(USE_OPENSSL)
+// TODO(toyoshim): Enable this test for OpenSSL once http://crbug.com/160033
+// is fixed against OpenSSL.
+#define MAYBE_SecureWebSocketSplitRecords DISABLED_SecureWebSocketSplitRecords
+#else
+#define MAYBE_SecureWebSocketSplitRecords SecureWebSocketSplitRecords
+#endif
+// Test that the browser can handle a WebSocket frame split into multiple SSL
+// records.
+IN_PROC_BROWSER_TEST_F(WebSocketBrowserTest,
+ MAYBE_SecureWebSocketSplitRecords) {
+ // Launch a secure WebSocket server.
+ ASSERT_TRUE(wss_server_.Start());
+
+ // Setup page title observer.
+ content::WebContents* tab = chrome::GetActiveWebContents(browser());
+ content::TitleWatcher watcher(tab, ASCIIToUTF16("PASS"));
+ watcher.AlsoWaitForTitle(ASCIIToUTF16("FAIL"));
+
+ // Visit a HTTPS page for testing.
+ std::string scheme("https");
+ GURL::Replacements replacements;
+ replacements.SetSchemeStr(scheme);
+ ui_test_utils::NavigateToURL(
+ browser(),
+ wss_server_.GetURL(
+ "split_packet_check.html").ReplaceComponents(replacements));
+
+ const string16 result = watcher.WaitAndGetTitle();
+ EXPECT_TRUE(EqualsASCII(result, "PASS"));
+}
+
+} // namespace
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index a50f187..69b3876 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1182,6 +1182,7 @@
'browser/net/load_timing_observer_browsertest.cc',
'browser/net/predictor_browsertest.cc',
'browser/net/proxy_browsertest.cc',
+ 'browser/net/websocket_browsertest.cc',
'browser/page_cycler/page_cycler_browsertest.cc',
'browser/performance_monitor/performance_monitor_browsertest.cc',
'browser/policy/cloud_policy_browsertest.cc',
diff --git a/net/base/nss_memio.c b/net/base/nss_memio.c
index 4092c17..bd047106 100644
--- a/net/base/nss_memio.c
+++ b/net/base/nss_memio.c
@@ -402,6 +402,14 @@ int memio_GetReadParams(memio_Private *secret, char **buf)
return memio_buffer_unused_contiguous(mb);
}
+int memio_GetReadableBufferSize(memio_Private *secret)
+{
+ struct memio_buffer* mb = &((PRFilePrivate *)secret)->readbuf;
+ PR_ASSERT(mb->bufsize);
+
+ return memio_buffer_used_contiguous(mb);
+}
+
void memio_PutReadResult(memio_Private *secret, int bytes_read)
{
struct memio_buffer* mb = &((PRFilePrivate *)secret)->readbuf;
diff --git a/net/base/nss_memio.h b/net/base/nss_memio.h
index a8bbc2c..8481d15 100644
--- a/net/base/nss_memio.h
+++ b/net/base/nss_memio.h
@@ -63,6 +63,11 @@ int memio_GetReadRequest(memio_Private *secret);
*/
int memio_GetReadParams(memio_Private *secret, char **buf);
+/* Ask memio how many bytes are contained in the internal buffer.
+ * Returns bytes available to read, or 0 if none available.
+ */
+int memio_GetReadableBufferSize(memio_Private *secret);
+
/* Tell memio how many bytes were read from the network.
* If bytes_read is 0, causes EOF to be reported to
* NSS after it reads the last byte from the circular buffer.
diff --git a/net/data/websocket/README b/net/data/websocket/README
index 034a30d..9dab697 100644
--- a/net/data/websocket/README
+++ b/net/data/websocket/README
@@ -7,6 +7,13 @@ Multiple tests may share one resource, or URI handler.
SSLUITest.TestWSSInvalidCertAndGoForward, SSLUITest.TestWSSClientCert,
and SSLUITestIgnoreCertErrors.TestWSS.
+- split_packet_check.html : A page for testing split packet handling. Here,
+ packets mean TCP segments for WebSocket, or SSL records for secure
+ WebSocket. This page changes the title to "PASS" to notify
+ content::TitleWatcher.
+ Used by WebSocketBrowserTest.WebSocketSplitSegments and
+ WebSocketBrowserTest.SecureWebSocketSplitRecords.
+
- websocket_shared_worker.html : A page provides simple WebSocket test in
shared worker. This page changes page title to "PASS" to notify
content::TitleWatcher.
@@ -31,6 +38,12 @@ Multiple tests may share one resource, or URI handler.
connection with arbitrary code and reason.
Used by kinds of PPAPI tests for WebSocket.
+- close-with-split-packet_wsh.py : A WebSocket URL handler for testing split
+ packet handling. Here, packets mean TCP segments for WebSocket, or SSL
+ records for secure WebSocket.
+ Used by WebSocketBrowserTest.WebSocketSplitSegments and
+ WebSocketBrowserTest.SecureWebSocketSplitRecords.
+
- protocol-test_wsh.py : A WebSocket URL handler for testing outgoing opening
handshake protocol.
Used by kinds of PPAPI tests for WebSocket.
diff --git a/net/data/websocket/close-with-split-packet_wsh.py b/net/data/websocket/close-with-split-packet_wsh.py
new file mode 100644
index 0000000..d5185c4
--- /dev/null
+++ b/net/data/websocket/close-with-split-packet_wsh.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import struct
+
+from mod_pywebsocket import handshake
+from mod_pywebsocket import stream
+
+
+def web_socket_do_extra_handshake(_request):
+ pass
+
+
+def web_socket_transfer_data(request):
+ line = request.ws_stream.receive_message()
+ if line is None:
+ return
+ if isinstance(line, unicode):
+ request.ws_stream.send_message(line, binary=False)
+ else:
+ request.ws_stream.send_message(line, binary=True)
+
+
+def web_socket_passive_closing_handshake(request):
+ code = struct.pack('!H', 1000)
+ packet = stream.create_close_frame(code + 'split test'.encode('utf-8'))
+ request.connection.write(packet[:1])
+ request.connection.write(packet[1:])
+ raise handshake.AbortedByUserException('Abort the connection')
diff --git a/net/data/websocket/split_packet_check.html b/net/data/websocket/split_packet_check.html
new file mode 100644
index 0000000..8e273ec
--- /dev/null
+++ b/net/data/websocket/split_packet_check.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test ws split packet</title>
+<script type="text/javascript">
+
+var href = window.location.href;
+var hostBegin = href.indexOf('/') + 2;
+var hostEnd = href.lastIndexOf(':');
+var host = href.slice(hostBegin, hostEnd);
+var portBegin = hostEnd + 1;
+var portEnd = href.lastIndexOf('/');
+var port = href.slice(portBegin, portEnd);
+var scheme = href.indexOf('https') >= 0 ? 'wss' : 'ws';
+var url = scheme + '://' + host + ':' + port + '/close-with-split-packet';
+
+// Do connection test.
+var ws = new WebSocket(url);
+
+ws.onopen = function()
+{
+ // Close WebSocket connection once it is established.
+ ws.close();
+}
+
+ws.onclose = function(event)
+{
+ // Check wasClean, then set proper title.
+ if (event.wasClean)
+ document.title = 'PASS';
+ else
+ document.title = 'FAIL';
+}
+
+</script>
+</head>
+</html>
diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc
index 40ed798..1b33032 100644
--- a/net/socket/ssl_client_socket_nss.cc
+++ b/net/socket/ssl_client_socket_nss.cc
@@ -642,6 +642,11 @@ class SSLClientSocketNSS::Core : public base::RefCountedThreadSafe<Core> {
int Read(IOBuffer* buf, int buf_len, const CompletionCallback& callback);
int Write(IOBuffer* buf, int buf_len, const CompletionCallback& callback);
+ // Called on the network task runner.
+ bool IsConnected();
+ bool HasPendingAsyncOperation();
+ bool HasUnhandledReceivedData();
+
private:
friend class base::RefCountedThreadSafe<Core>;
~Core();
@@ -774,6 +779,9 @@ class SSLClientSocketNSS::Core : public base::RefCountedThreadSafe<Core> {
void OnGetDomainBoundCertComplete(int result);
void OnHandshakeStateUpdated(const HandshakeState& state);
+ void OnNSSBufferUpdated(int amount_in_read_buffer);
+ void DidNSSRead(int result);
+ void DidNSSWrite(int result);
////////////////////////////////////////////////////////////////////////////
// Methods that are called on both the network task runner and the NSS
@@ -817,6 +825,12 @@ class SSLClientSocketNSS::Core : public base::RefCountedThreadSafe<Core> {
ServerBoundCertService* server_bound_cert_service_;
ServerBoundCertService::RequestHandle domain_bound_cert_request_handle_;
+ // The information about NSS task runner.
+ int unhandled_buffer_size_;
+ bool nss_waiting_read_;
+ bool nss_waiting_write_;
+ bool nss_is_closed_;
+
////////////////////////////////////////////////////////////////////////////
// Members that are ONLY accessed on the NSS task runner:
////////////////////////////////////////////////////////////////////////////
@@ -896,6 +910,10 @@ SSLClientSocketNSS::Core::Core(
transport_(transport),
weak_net_log_factory_(net_log),
server_bound_cert_service_(server_bound_cert_service),
+ unhandled_buffer_size_(0),
+ nss_waiting_read_(false),
+ nss_waiting_write_(false),
+ nss_is_closed_(false),
host_and_port_(host_and_port),
ssl_config_(ssl_config),
nss_fd_(NULL),
@@ -1089,11 +1107,17 @@ int SSLClientSocketNSS::Core::Read(IOBuffer* buf, int buf_len,
DCHECK(OnNetworkTaskRunner());
DCHECK(!detached_);
DCHECK(transport_);
+ DCHECK(!nss_waiting_read_);
+ nss_waiting_read_ = true;
bool posted = nss_task_runner_->PostTask(
FROM_HERE,
base::Bind(IgnoreResult(&Core::Read), this, make_scoped_refptr(buf),
buf_len, callback));
+ if (!posted) {
+ nss_is_closed_ = true;
+ nss_waiting_read_ = false;
+ }
return posted ? ERR_IO_PENDING : ERR_ABORTED;
}
@@ -1110,14 +1134,21 @@ int SSLClientSocketNSS::Core::Read(IOBuffer* buf, int buf_len,
int rv = DoReadLoop(OK);
if (rv == ERR_IO_PENDING) {
+ if (OnNetworkTaskRunner())
+ nss_waiting_read_ = true;
user_read_callback_ = callback;
} else {
user_read_buf_ = NULL;
user_read_buf_len_ = 0;
if (!OnNetworkTaskRunner()) {
+ PostOrRunCallback(FROM_HERE, base::Bind(&Core::DidNSSRead, this, rv));
PostOrRunCallback(FROM_HERE, base::Bind(callback, rv));
return ERR_IO_PENDING;
+ } else {
+ DCHECK(!nss_waiting_read_);
+ if (rv <= 0)
+ nss_is_closed_ = true;
}
}
@@ -1130,13 +1161,18 @@ int SSLClientSocketNSS::Core::Write(IOBuffer* buf, int buf_len,
DCHECK(OnNetworkTaskRunner());
DCHECK(!detached_);
DCHECK(transport_);
+ DCHECK(!nss_waiting_write_);
+ nss_waiting_write_ = true;
bool posted = nss_task_runner_->PostTask(
FROM_HERE,
base::Bind(IgnoreResult(&Core::Write), this, make_scoped_refptr(buf),
buf_len, callback));
- int rv = posted ? ERR_IO_PENDING : ERR_ABORTED;
- return rv;
+ if (!posted) {
+ nss_is_closed_ = true;
+ nss_waiting_write_ = false;
+ }
+ return posted ? ERR_IO_PENDING : ERR_ABORTED;
}
DCHECK(OnNSSTaskRunner());
@@ -1152,20 +1188,42 @@ int SSLClientSocketNSS::Core::Write(IOBuffer* buf, int buf_len,
int rv = DoWriteLoop(OK);
if (rv == ERR_IO_PENDING) {
+ if (OnNetworkTaskRunner())
+ nss_waiting_write_ = true;
user_write_callback_ = callback;
} else {
user_write_buf_ = NULL;
user_write_buf_len_ = 0;
if (!OnNetworkTaskRunner()) {
+ PostOrRunCallback(FROM_HERE, base::Bind(&Core::DidNSSWrite, this, rv));
PostOrRunCallback(FROM_HERE, base::Bind(callback, rv));
return ERR_IO_PENDING;
+ } else {
+ DCHECK(!nss_waiting_write_);
+ if (rv < 0)
+ nss_is_closed_ = true;
}
}
return rv;
}
+bool SSLClientSocketNSS::Core::IsConnected() {
+ DCHECK(OnNetworkTaskRunner());
+ return !nss_is_closed_;
+}
+
+bool SSLClientSocketNSS::Core::HasPendingAsyncOperation() {
+ DCHECK(OnNetworkTaskRunner());
+ return nss_waiting_read_ || nss_waiting_write_;
+}
+
+bool SSLClientSocketNSS::Core::HasUnhandledReceivedData() {
+ DCHECK(OnNetworkTaskRunner());
+ return unhandled_buffer_size_ != 0;
+}
+
bool SSLClientSocketNSS::Core::OnNSSTaskRunner() const {
return nss_task_runner_->RunsTasksOnCurrentThread();
}
@@ -2044,6 +2102,13 @@ int SSLClientSocketNSS::Core::DoPayloadRead() {
DCHECK_GT(user_read_buf_len_, 0);
int rv = PR_Read(nss_fd_, user_read_buf_->data(), user_read_buf_len_);
+ // Update NSSTaskRunner status because PR_Read may consume |nss_bufs_|, then
+ // following |amount_in_read_buffer| may be changed here.
+ int amount_in_read_buffer = memio_GetReadableBufferSize(nss_bufs_);
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&Core::OnNSSBufferUpdated, this, amount_in_read_buffer));
+
if (client_auth_cert_needed_) {
// We don't need to invalidate the non-client-authenticated SSL session
// because the server will renegotiate anyway.
@@ -2081,7 +2146,17 @@ int SSLClientSocketNSS::Core::DoPayloadWrite() {
DCHECK(user_write_buf_);
+ int old_amount_in_read_buffer = memio_GetReadableBufferSize(nss_bufs_);
int rv = PR_Write(nss_fd_, user_write_buf_->data(), user_write_buf_len_);
+ int new_amount_in_read_buffer = memio_GetReadableBufferSize(nss_bufs_);
+ // PR_Write could potentially consume the unhandled data in the memio read
+ // buffer if a renegotiation is in progress. If the buffer is consumed,
+ // notify the latest buffer size to NetworkRunner.
+ if (old_amount_in_read_buffer != new_amount_in_read_buffer) {
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&Core::OnNSSBufferUpdated, this, new_amount_in_read_buffer));
+ }
if (rv >= 0) {
PostOrRunCallback(
FROM_HERE,
@@ -2287,10 +2362,18 @@ void SSLClientSocketNSS::Core::DoReadCallback(int rv) {
user_read_buf_ = NULL;
user_read_buf_len_ = 0;
- base::Closure c = base::Bind(
- base::ResetAndReturn(&user_read_callback_),
- rv);
- PostOrRunCallback(FROM_HERE, c);
+ int amount_in_read_buffer = memio_GetReadableBufferSize(nss_bufs_);
+ // This is used to curry the |amount_int_read_buffer| and |user_cb| back to
+ // the network task runner.
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&Core::OnNSSBufferUpdated, this, amount_in_read_buffer));
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&Core::DidNSSRead, this, rv));
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(base::ResetAndReturn(&user_read_callback_), rv));
}
void SSLClientSocketNSS::Core::DoWriteCallback(int rv) {
@@ -2302,10 +2385,20 @@ void SSLClientSocketNSS::Core::DoWriteCallback(int rv) {
// up front.
user_write_buf_ = NULL;
user_write_buf_len_ = 0;
- base::Closure c = base::Bind(
- base::ResetAndReturn(&user_write_callback_),
- rv);
- PostOrRunCallback(FROM_HERE, c);
+ // Update buffer status because DoWriteLoop called DoTransportIO which may
+ // perform read operations.
+ int amount_in_read_buffer = memio_GetReadableBufferSize(nss_bufs_);
+ // This is used to curry the |amount_int_read_buffer| and |user_cb| back to
+ // the network task runner.
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&Core::OnNSSBufferUpdated, this, amount_in_read_buffer));
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&Core::DidNSSWrite, this, rv));
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(base::ResetAndReturn(&user_write_callback_), rv));
}
SECStatus SSLClientSocketNSS::Core::ClientChannelIDHandler(
@@ -2565,9 +2658,9 @@ int SSLClientSocketNSS::Core::DoBufferSend(IOBuffer* send_buffer, int len) {
return ERR_ABORTED;
int rv = transport_->socket()->Write(
- send_buffer, len,
- base::Bind(&Core::BufferSendComplete,
- base::Unretained(this)));
+ send_buffer, len,
+ base::Bind(&Core::BufferSendComplete,
+ base::Unretained(this)));
if (!OnNSSTaskRunner() && rv != ERR_IO_PENDING) {
nss_task_runner_->PostTask(
@@ -2610,9 +2703,31 @@ int SSLClientSocketNSS::Core::DoGetDomainBoundCert(
void SSLClientSocketNSS::Core::OnHandshakeStateUpdated(
const HandshakeState& state) {
+ DCHECK(OnNetworkTaskRunner());
network_handshake_state_ = state;
}
+void SSLClientSocketNSS::Core::OnNSSBufferUpdated(int amount_in_read_buffer) {
+ DCHECK(OnNetworkTaskRunner());
+ unhandled_buffer_size_ = amount_in_read_buffer;
+}
+
+void SSLClientSocketNSS::Core::DidNSSRead(int result) {
+ DCHECK(OnNetworkTaskRunner());
+ DCHECK(nss_waiting_read_);
+ nss_waiting_read_ = false;
+ if (result <= 0)
+ nss_is_closed_ = true;
+}
+
+void SSLClientSocketNSS::Core::DidNSSWrite(int result) {
+ DCHECK(OnNetworkTaskRunner());
+ DCHECK(nss_waiting_write_);
+ nss_waiting_write_ = false;
+ if (result < 0)
+ nss_is_closed_ = true;
+}
+
void SSLClientSocketNSS::Core::BufferSendComplete(int result) {
if (!OnNSSTaskRunner()) {
if (detached_)
@@ -2922,29 +3037,21 @@ void SSLClientSocketNSS::Disconnect() {
}
bool SSLClientSocketNSS::IsConnected() const {
- // Ideally, we should also check if we have received the close_notify alert
- // message from the server, and return false in that case. We're not doing
- // that, so this function may return a false positive. Since the upper
- // layer (HttpNetworkTransaction) needs to handle a persistent connection
- // closed by the server when we send a request anyway, a false positive in
- // exchange for simpler code is a good trade-off.
EnterFunction("");
- bool ret = completed_handshake_ && transport_->socket()->IsConnected();
+ bool ret = completed_handshake_ &&
+ (core_->HasPendingAsyncOperation() ||
+ (core_->IsConnected() && core_->HasUnhandledReceivedData()) ||
+ transport_->socket()->IsConnected());
LeaveFunction("");
return ret;
}
bool SSLClientSocketNSS::IsConnectedAndIdle() const {
- // Unlike IsConnected, this method doesn't return a false positive.
- //
- // Strictly speaking, we should check if we have received the close_notify
- // alert message from the server, and return false in that case. Although
- // the close_notify alert message means EOF in the SSL layer, it is just
- // bytes to the transport layer below, so
- // transport_->socket()->IsConnectedAndIdle() returns the desired false
- // when we receive close_notify.
EnterFunction("");
- bool ret = completed_handshake_ && transport_->socket()->IsConnectedAndIdle();
+ bool ret = completed_handshake_ &&
+ !core_->HasPendingAsyncOperation() &&
+ !(core_->IsConnected() && core_->HasUnhandledReceivedData()) &&
+ transport_->socket()->IsConnectedAndIdle();
LeaveFunction("");
return ret;
}