Commit 3ca5141f authored by zea's avatar zea Committed by Commit bot

[GCM] Fix crash during connection races

If a connection attempt is triggered while a connection is open, but not
active (for example due to a socket error), make sure the old connection
is closed before reattempting to connect.

BUG=462319

Review URL: https://codereview.chromium.org/980433003

Cr-Commit-Position: refs/heads/master@{#319201}
parent bd907bb6
...@@ -145,6 +145,10 @@ void ConnectionFactoryImpl::ConnectWithBackoff() { ...@@ -145,6 +145,10 @@ void ConnectionFactoryImpl::ConnectWithBackoff() {
DVLOG(1) << "Attempting connection to MCS endpoint."; DVLOG(1) << "Attempting connection to MCS endpoint.";
waiting_for_backoff_ = false; waiting_for_backoff_ = false;
// It's necessary to close the socket before attempting any new connection,
// otherwise it's possible to hit a use-after-free in the connection handler.
// crbug.com/462319
CloseSocket();
ConnectImpl(); ConnectImpl();
} }
...@@ -212,7 +216,12 @@ void ConnectionFactoryImpl::SignalConnectionReset( ...@@ -212,7 +216,12 @@ void ConnectionFactoryImpl::SignalConnectionReset(
return; return;
} }
if (logging_in_) { if (reason == NETWORK_CHANGE) {
// Canary attempts bypass backoff without resetting it. These will have no
// effect if we're already in the process of connecting.
ConnectImpl();
return;
} else if (logging_in_) {
// Failures prior to login completion just reuse the existing backoff entry. // Failures prior to login completion just reuse the existing backoff entry.
logging_in_ = false; logging_in_ = false;
backoff_entry_->InformOfRequest(false); backoff_entry_->InformOfRequest(false);
...@@ -222,9 +231,6 @@ void ConnectionFactoryImpl::SignalConnectionReset( ...@@ -222,9 +231,6 @@ void ConnectionFactoryImpl::SignalConnectionReset(
// the backoff entry that was saved off at login completion time. // the backoff entry that was saved off at login completion time.
backoff_entry_.swap(previous_backoff_); backoff_entry_.swap(previous_backoff_);
backoff_entry_->InformOfRequest(false); backoff_entry_->InformOfRequest(false);
} else if (reason == NETWORK_CHANGE) {
ConnectImpl(); // Canary attempts bypass backoff without resetting it.
return;
} else { } else {
// We shouldn't be in backoff in thise case. // We shouldn't be in backoff in thise case.
DCHECK_EQ(0, backoff_entry_->failure_count()); DCHECK_EQ(0, backoff_entry_->failure_count());
...@@ -287,7 +293,8 @@ net::IPEndPoint ConnectionFactoryImpl::GetPeerIP() { ...@@ -287,7 +293,8 @@ net::IPEndPoint ConnectionFactoryImpl::GetPeerIP() {
void ConnectionFactoryImpl::ConnectImpl() { void ConnectionFactoryImpl::ConnectImpl() {
DCHECK(!IsEndpointReachable()); DCHECK(!IsEndpointReachable());
DCHECK(!socket_handle_.socket()); // TODO(zea): Make this a dcheck again. crbug.com/462319
CHECK(!socket_handle_.socket());
// TODO(zea): if the network is offline, don't attempt to connect. // TODO(zea): if the network is offline, don't attempt to connect.
// See crbug.com/396687 // See crbug.com/396687
......
...@@ -132,6 +132,9 @@ class TestConnectionFactoryImpl : public ConnectionFactoryImpl { ...@@ -132,6 +132,9 @@ class TestConnectionFactoryImpl : public ConnectionFactoryImpl {
// Force a login handshake to be delayed. // Force a login handshake to be delayed.
void SetDelayLogin(bool delay_login); void SetDelayLogin(bool delay_login);
// Simulate a socket error.
void SetSocketError();
base::SimpleTestTickClock* tick_clock() { return &tick_clock_; } base::SimpleTestTickClock* tick_clock() { return &tick_clock_; }
private: private:
...@@ -184,6 +187,7 @@ TestConnectionFactoryImpl::~TestConnectionFactoryImpl() { ...@@ -184,6 +187,7 @@ TestConnectionFactoryImpl::~TestConnectionFactoryImpl() {
void TestConnectionFactoryImpl::ConnectImpl() { void TestConnectionFactoryImpl::ConnectImpl() {
ASSERT_GT(num_expected_attempts_, 0); ASSERT_GT(num_expected_attempts_, 0);
ASSERT_FALSE(GetConnectionHandler()->CanSendMessage());
scoped_ptr<mcs_proto::LoginRequest> request(BuildLoginRequest(0, 0, "")); scoped_ptr<mcs_proto::LoginRequest> request(BuildLoginRequest(0, 0, ""));
GetConnectionHandler()->Init(*request, NULL); GetConnectionHandler()->Init(*request, NULL);
OnConnectDone(connect_result_); OnConnectDone(connect_result_);
...@@ -255,6 +259,10 @@ void TestConnectionFactoryImpl::SetDelayLogin(bool delay_login) { ...@@ -255,6 +259,10 @@ void TestConnectionFactoryImpl::SetDelayLogin(bool delay_login) {
fake_handler_->set_fail_login(delay_login_); fake_handler_->set_fail_login(delay_login_);
} }
void TestConnectionFactoryImpl::SetSocketError() {
fake_handler_->set_had_error(true);
}
} // namespace } // namespace
class ConnectionFactoryImplTest class ConnectionFactoryImplTest
...@@ -561,4 +569,26 @@ TEST_F(ConnectionFactoryImplTest, NetworkChangeBeforeFirstConnection) { ...@@ -561,4 +569,26 @@ TEST_F(ConnectionFactoryImplTest, NetworkChangeBeforeFirstConnection) {
EXPECT_TRUE(factory()->IsEndpointReachable()); EXPECT_TRUE(factory()->IsEndpointReachable());
} }
// Test that if the client attempts to reconnect while a connection is already
// open, we don't crash.
TEST_F(ConnectionFactoryImplTest, ConnectionResetRace) {
// Initial successful connection.
factory()->SetConnectResult(net::OK);
factory()->Connect();
WaitForConnections();
EXPECT_TRUE(factory()->IsEndpointReachable());
// Trigger a connection error under the hood.
factory()->SetSocketError();
EXPECT_FALSE(factory()->IsEndpointReachable());
// Now trigger force a re-connection.
factory()->SetConnectResult(net::OK);
factory()->Connect();
WaitForConnections();
// Re-connection should succeed.
EXPECT_TRUE(factory()->IsEndpointReachable());
}
} // namespace gcm } // namespace gcm
...@@ -32,7 +32,8 @@ FakeConnectionHandler::FakeConnectionHandler( ...@@ -32,7 +32,8 @@ FakeConnectionHandler::FakeConnectionHandler(
write_callback_(write_callback), write_callback_(write_callback),
fail_login_(false), fail_login_(false),
fail_send_(false), fail_send_(false),
initialized_(false) { initialized_(false),
had_error_(false) {
} }
FakeConnectionHandler::~FakeConnectionHandler() { FakeConnectionHandler::~FakeConnectionHandler() {
...@@ -51,10 +52,11 @@ void FakeConnectionHandler::Init(const mcs_proto::LoginRequest& login_request, ...@@ -51,10 +52,11 @@ void FakeConnectionHandler::Init(const mcs_proto::LoginRequest& login_request,
void FakeConnectionHandler::Reset() { void FakeConnectionHandler::Reset() {
initialized_ = false; initialized_ = false;
had_error_ = false;
} }
bool FakeConnectionHandler::CanSendMessage() const { bool FakeConnectionHandler::CanSendMessage() const {
return initialized_; return initialized_ && !had_error_;
} }
void FakeConnectionHandler::SendMessage( void FakeConnectionHandler::SendMessage(
......
...@@ -51,6 +51,13 @@ class FakeConnectionHandler : public ConnectionHandler { ...@@ -51,6 +51,13 @@ class FakeConnectionHandler : public ConnectionHandler {
fail_send_ = fail_send; fail_send_ = fail_send;
} }
// Whether a socket-level error was encountered or not.
void set_had_error(bool had_error) {
had_error_ = had_error;
}
bool initialized() const { return initialized_; }
private: private:
ConnectionHandler::ProtoReceivedCallback read_callback_; ConnectionHandler::ProtoReceivedCallback read_callback_;
ConnectionHandler::ProtoSentCallback write_callback_; ConnectionHandler::ProtoSentCallback write_callback_;
...@@ -66,6 +73,9 @@ class FakeConnectionHandler : public ConnectionHandler { ...@@ -66,6 +73,9 @@ class FakeConnectionHandler : public ConnectionHandler {
// Whether a successful login has completed. // Whether a successful login has completed.
bool initialized_; bool initialized_;
// Whether an error was encountered after a successful login.
bool had_error_;
DISALLOW_COPY_AND_ASSIGN(FakeConnectionHandler); DISALLOW_COPY_AND_ASSIGN(FakeConnectionHandler);
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment