shill: Add ConnectionHealthChecker class to check connectivity status.

ConnectionHealthChecker asynchronously creates a TCP connection with a
remote server and checks that either (1) a small amount of data can be
sent over the connection, or (2) the connection can be cleanly shut
down. ConnectionHealthChecker may be provided multiple remote server
addresses, and it attempts them in a round-robin fashion if one attempt
fails.

BUG=chromium:225913
TEST=Build and run unit tests.

Change-Id: I01013af2214fd5614b1fca820670de12757a8904
Reviewed-on: https://gerrit.chromium.org/gerrit/47752
Commit-Queue: Ben Chan <benchan@chromium.org>
Reviewed-by: Ben Chan <benchan@chromium.org>
Tested-by: Ben Chan <benchan@chromium.org>
diff --git a/Makefile b/Makefile
index 388c982..cea0c5e 100644
--- a/Makefile
+++ b/Makefile
@@ -204,6 +204,7 @@
 	certificate_file.o \
 	config80211.o \
 	connection.o \
+	connection_health_checker.o \
 	control_netlink_attribute.o \
 	crypto_des_cbc.o \
 	crypto_provider.o \
@@ -332,6 +333,7 @@
 	crypto_util_proxy_unittest.o \
 	config80211_unittest.o \
 	connection_unittest.o \
+	connection_health_checker_unittest.o \
 	dbus_adaptor_unittest.o \
 	dbus_manager_unittest.o \
 	dbus_properties_unittest.o \
diff --git a/connection_health_checker.cc b/connection_health_checker.cc
new file mode 100644
index 0000000..a362942
--- /dev/null
+++ b/connection_health_checker.cc
@@ -0,0 +1,319 @@
+// Copyright (c) 2013 The Chromium OS 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 "shill/connection_health_checker.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <vector>
+
+#include <base/bind.h>
+
+#include "shill/async_connection.h"
+#include "shill/connection.h"
+#include "shill/dns_client.h"
+#include "shill/error.h"
+#include "shill/http_url.h"
+#include "shill/ip_address.h"
+#include "shill/logging.h"
+#include "shill/sockets.h"
+#include "shill/socket_info.h"
+#include "shill/socket_info_reader.h"
+
+using base::Bind;
+using std::string;
+using std::vector;
+
+namespace shill {
+
+const int ConnectionHealthChecker::kDNSTimeoutSeconds = 5;
+const int ConnectionHealthChecker::kMaxConnectionAttempts = 3;
+const uint16 ConnectionHealthChecker::kRemotePort = 80;
+
+ConnectionHealthChecker::ConnectionHealthChecker(
+    ConnectionRefPtr connection,
+    EventDispatcher *dispatcher,
+    const base::Callback<void(Result)> &result_callback)
+    : connection_(connection),
+      dispatcher_(dispatcher),
+      result_callback_(result_callback),
+      socket_info_reader_(new SocketInfoReader()),
+      socket_(new Sockets()),
+      weak_ptr_factory_(this),
+      connection_complete_callback_(
+          Bind(&ConnectionHealthChecker::OnConnectionComplete,
+               weak_ptr_factory_.GetWeakPtr())),
+      dns_client_callback_(Bind(&ConnectionHealthChecker::GetDNSResult,
+                                weak_ptr_factory_.GetWeakPtr())),
+      tcp_connection_(new AsyncConnection(connection_->interface_name(),
+                                          dispatcher_,
+                                          socket_.get(),
+                                          connection_complete_callback_)),
+      dns_client_(new DNSClient(IPAddress::kFamilyIPv4,
+                                connection->interface_name(),
+                                connection->dns_servers(),
+                                kDNSTimeoutSeconds * 1000,
+                                dispatcher,
+                                dns_client_callback_)),
+      run_data_test_(true),
+      health_check_in_progress_(false),
+      num_connection_attempts_(0) {}
+
+ConnectionHealthChecker::~ConnectionHealthChecker() {
+  Stop();
+}
+
+void ConnectionHealthChecker::AddRemoteIP(IPAddress ip) {
+  remote_ips_.push(ip);
+}
+
+void ConnectionHealthChecker::AddRemoteURL(const string &url_string) {
+  HTTPURL url;
+  if (!url.ParseFromString(url_string)) {
+    SLOG(Connection, 2) << __func__ << ": Malformed url: " << url_string << ".";
+    return;
+  }
+  if (url.port() != kRemotePort) {
+    SLOG(Connection, 2) << __func__ << ": Remote connections only supported "
+                        << " to port 80, requested " << url.port() << ".";
+    return;
+  }
+  Error error;
+  if (!dns_client_->Start(url.host(), &error)) {
+    SLOG(Connection, 2) << __func__ << ": Failed to start DNS client: "
+                        << error.message();
+  }
+}
+
+void ConnectionHealthChecker::Start() {
+  if (health_check_in_progress_) {
+    SLOG(Connection, 2) << __func__ << ": Health Check already in progress.";
+    return;
+  }
+  if (!connection_.get()) {
+    SLOG(Connection, 2) << __func__ << ": Connection not ready yet.";
+    result_callback_.Run(kResultUnknown);
+    return;
+  }
+
+  health_check_in_progress_ = true;
+  num_connection_attempts_ = 0;
+
+  // Initiate the first attempt.
+  if (remote_ips_.empty()) {
+    // Nothing to try.
+    Stop();
+    SLOG(Connection, 2) << __func__ << ": Not enough IPs.";
+    result_callback_.Run(kResultUnknown);
+    return;
+  }
+  SetupTcpConnection();
+}
+
+void ConnectionHealthChecker::Stop() {
+  if (tcp_connection_ != NULL)
+    tcp_connection_->Stop();
+  health_check_in_progress_ = false;
+}
+
+void ConnectionHealthChecker::SetupTcpConnection() {
+  IPAddress ip = remote_ips_.front();
+  if (tcp_connection_->Start(ip, kRemotePort)) {
+    // TCP connection successful, no need to try more.
+    return;
+  }
+
+  SLOG(Connection, 2) << __func__ << ": Connection attempt failed.";
+  TryNextIP();
+}
+
+void ConnectionHealthChecker::OnConnectionComplete(bool success, int sock_fd) {
+  if (!success) {
+    SLOG(Connection, 2) << __func__
+                        << ": AsyncConnection connection attempt failed.";
+    TryNextIP();  // Make sure TryNextIP() is the last statement.
+    return;
+  }
+  // Transferred owndership of valid sock_fd.
+
+  // Check if the established connection is healthy.
+  Result result = run_data_test_ ? SendData(sock_fd) : ShutDown(sock_fd);
+
+  // The health check routine(s) may further indicate a problem requiring a
+  // reattempt.
+  if (result == kResultConnectionFailure || result == kResultUnknown) {
+    socket_->Close(sock_fd);
+    TryNextIP();  // Make sure TryNextIP() is the last statement.
+  } else {
+    socket_->Close(sock_fd);
+    Stop();
+    result_callback_.Run(result);  // Make sure this is the last statement.
+  }
+}
+
+void ConnectionHealthChecker::GetDNSResult(const Error &error,
+                                           const IPAddress& ip) {
+  if (!error.IsSuccess()) {
+    SLOG(Connection, 2) << __func__ << "DNSClient returned failure: "
+                        << error.message();
+    return;
+  }
+  remote_ips_.push(ip);
+}
+
+void ConnectionHealthChecker::TryNextIP() {
+  ++num_connection_attempts_;
+  // Check if enough attempts have been made already.
+  if (num_connection_attempts_ >= kMaxConnectionAttempts ||
+      static_cast<IPAddressQueue::size_type>(num_connection_attempts_)
+           >= remote_ips_.size()) {
+    LOG(INFO) << __func__
+              << ": multiple failed attempts to established a TCP connection.";
+    // Give up. Clean up and notify client.
+    Stop();
+    result_callback_.Run(kResultConnectionFailure);
+    return;
+  }
+  IPAddress recycle_addr = remote_ips_.front();
+  remote_ips_.pop();
+  remote_ips_.push(recycle_addr);
+
+  SetupTcpConnection();
+}
+
+// Send data on the connection and observe the TxCount.
+ConnectionHealthChecker::Result ConnectionHealthChecker::SendData(int sock_fd) {
+  SocketInfo sock_info;
+  uint64 old_transmit_queue_value;
+  if (!GetSocketInfo(sock_fd, &sock_info) ||
+      sock_info.connection_state() !=
+          SocketInfo::kConnectionStateEstablished) {
+    SLOG(Connection, 2) << __func__
+                        << ": Connection originally not in established state..";
+    // Count this as a failed connection attempt.
+    return kResultUnknown;
+  }
+  old_transmit_queue_value = sock_info.transmit_queue_value();
+
+  char buf;
+  if (socket_->Send(sock_fd, &buf, sizeof(buf), 0) == -1) {
+    SLOG(Connection, 2) << __func__ << ": " << socket_->ErrorString();
+    // Count this as a failed connection attempt.
+    return kResultConnectionFailure;
+  }
+
+  // Wait to give enough time for the TxCount to be updated.
+  // TODO(pprabhu) Check that this is reliable wrt timing effects.
+  if (!GetSocketInfo(sock_fd, &sock_info) ||
+      sock_info.connection_state() !=
+          SocketInfo::kConnectionStateEstablished) {
+    SLOG(Connection, 2) << __func__
+                        << ": Connection not in established state after send.";
+    // Count this as a failed connection attempt.
+    return kResultUnknown;
+  }
+
+  if (sock_info.transmit_queue_value() > old_transmit_queue_value) {
+    return kResultCongestedTxQueue;
+  }
+
+  return kResultSuccess;
+}
+
+// Attempt to shutdown the connection and check if the connection is stuck in
+// the TIME_WAIT tcp state.
+ConnectionHealthChecker::Result ConnectionHealthChecker::ShutDown(int sock_fd) {
+  if (socket_->ShutDown(sock_fd, SHUT_RDWR) == -1) {
+    SLOG(Connection, 2) << __func__
+                        << ": Failed to cleanly shut down the connection.";
+    // Count this as a failed connection attempt.
+    return kResultUnknown;
+  }
+  // Wait to give enough time for a normal TCP shutdown?
+  // TODO(pprabhu) Check that this is reliable wrt timing effects.
+
+  SocketInfo sock_info;
+  if (!GetSocketInfo(sock_fd, &sock_info)) {
+    // The TCP socket for the connection has been cleaned.
+    // This means ShutDown was successful.
+    return kResultSuccess;
+  }
+  if (sock_info.connection_state() == SocketInfo::kConnectionStateFinWait1 ||
+      sock_info.connection_state() == SocketInfo::kConnectionStateFinWait2 ||
+      sock_info.connection_state() == SocketInfo::kConnectionStateTimeWait)
+    return kResultElongatedTimeWait;
+
+  return kResultUnknown;
+}
+
+//TODO(pprabhu): Scrub IP address logging.
+bool ConnectionHealthChecker::GetSocketInfo(int sock_fd,
+                                            SocketInfo *sock_info) {
+  struct sockaddr_storage addr;
+  socklen_t addrlen = sizeof(addr);
+  memset(&addr, 0, sizeof(addr));
+  if (socket_->GetSockName(sock_fd,
+                           reinterpret_cast<struct sockaddr *>(&addr),
+                           &addrlen) != 0) {
+    SLOG(Connection, 2) << __func__
+                        << ": Failed to get address of created socket.";
+    return false;
+  }
+  if (addr.ss_family != AF_INET) {
+    SLOG(Connection, 2) << __func__ << ": IPv6 socket address found.";
+    return false;
+  }
+
+  CHECK_EQ(sizeof(struct sockaddr_in), addrlen);
+  struct sockaddr_in *addr_in = reinterpret_cast<sockaddr_in *>(&addr);
+  uint16 local_port = ntohs(addr_in->sin_port);
+  char ipstr[INET_ADDRSTRLEN];
+  const char *res = inet_ntop(AF_INET, &addr_in->sin_addr,
+                              ipstr, sizeof(ipstr));
+  if (res == NULL) {
+    SLOG(Connection, 2) << __func__
+                        << ": Could not convert IP address to string.";
+    return false;
+  }
+
+  IPAddress local_ip_address(IPAddress::kFamilyIPv4);
+  CHECK(local_ip_address.SetAddressFromString(ipstr));
+  SLOG(Connection, 3) << "Local IP = " << local_ip_address.ToString()
+                      << ":" << local_port;
+
+  vector<SocketInfo> info_list;
+  if (!socket_info_reader_->LoadTcpSocketInfo(&info_list)) {
+    SLOG(Connection, 2) << __func__ << ": Failed to load TCP socket info.";
+    return false;
+  }
+
+  for (vector<SocketInfo>::const_iterator info_list_it = info_list.begin();
+       info_list_it != info_list.end();
+       ++info_list_it) {
+    const SocketInfo &cur_sock_info = *info_list_it;
+
+    SLOG(Connection, 3)
+        << "Testing against IP = "
+        << cur_sock_info.local_ip_address().ToString()
+        << ":" << cur_sock_info.local_port()
+        << " (addresses equal:"
+        << cur_sock_info.local_ip_address().Equals(local_ip_address)
+        << ", ports equal:" << (cur_sock_info.local_port() == local_port)
+        << ")";
+
+    if (cur_sock_info.local_ip_address().Equals(local_ip_address) &&
+        cur_sock_info.local_port() == local_port) {
+      // Copy SocketInfo.
+      *sock_info = cur_sock_info;
+      return true;
+    }
+  }
+
+  SLOG(Connection, 2) << __func__ << ": No matching TCP socket info.";
+  return false;
+}
+
+}  // namespace shill
diff --git a/connection_health_checker.h b/connection_health_checker.h
new file mode 100644
index 0000000..8aac9dc
--- /dev/null
+++ b/connection_health_checker.h
@@ -0,0 +1,144 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_CONNECTION_HEALTH_CHECKER_H_
+#define SHILL_CONNECTION_HEALTH_CHECKER_H_
+
+#include <queue>
+
+#include <base/basictypes.h>
+#include <base/callback.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/memory/scoped_vector.h>
+#include <base/memory/weak_ptr.h>
+#include <gtest/gtest_prod.h>
+
+#include "shill/refptr_types.h"
+#include "shill/sockets.h"
+#include "shill/socket_info.h"
+
+namespace shill {
+
+class AsyncConnection;
+class DNSClient;
+class Error;
+class EventDispatcher;
+class IPAddress;
+class SocketInfoReader;
+
+// The ConnectionHealthChecker class implements the facilities to test
+// connectivity status on some connection asynchronously.
+// In particular, the class can distinguish between three states of the
+// connection:
+//   -(1)- No connectivity (TCP connection can not be established)
+//   -(2)- Partial connectivity (TCP connection can be established, but no data
+//         transfer)
+//   -(3)- Connectivity OK (TCP connection established, is healthy)
+class ConnectionHealthChecker {
+ public:
+  typedef std::queue<IPAddress> IPAddressQueue;
+
+  enum Result {
+    // There was some problem in the setup of ConnctionHealthChecker.
+    // Could not attempt a tcp connection.
+    kResultUnknown,
+    // New health check request made successfully. The result of the health
+    // check is returned asynchronously.
+    kResultInProgress,
+    // Failed to create TCP connection. Condition -(1)-.
+    kResultConnectionFailure,
+    // Failed to destroy TCP connection. Condition -(2)-.
+    kResultElongatedTimeWait,
+    // Failed to send data on TCP connection. Condition -(2)-.
+    kResultCongestedTxQueue,
+    // Condition -(3)-.
+    kResultSuccess
+  };
+
+  ConnectionHealthChecker(ConnectionRefPtr connection,
+                          EventDispatcher *dispatcher,
+                          const base::Callback<void(Result)> &result_callback);
+  virtual ~ConnectionHealthChecker();
+
+  // A new ConnectionHealthChecker is created with a default URL to attempt the
+  // TCP connection with. Add a URL to try.
+  void AddRemoteURL(const std::string &url_string);
+
+  // Name resolution can fail in conditions -(1)- and -(2)-. Add an IP address
+  // to attempt the TCP connection with.
+  void AddRemoteIP(IPAddress ip);
+
+  // Start a connection health check. The health check involves one or more
+  // attempts at establishing and using a TCP connection. |result_callback_| is
+  // called with the final result of the check. |result_callback_| will always
+  // be called after a call to Start() unless Stop() is called in the meantime.
+  // |result_callback_| may be called before Start() completes.
+  //
+  // Calling Start() while a health check is in progress is a no-op.
+  virtual void Start();
+
+  // Stop the current health check. No callback is called as a side effect of
+  // this function.
+  //
+  // Calling Stop() on a Stop()ed health check is a no-op.
+  virtual void Stop();
+
+  // Accessors.
+  const IPAddressQueue &remote_ips() { return remote_ips_; }
+  void set_run_data_test(bool val) { run_data_test_ = val; }
+
+ private:
+  friend class ConnectionHealthCheckerTest;
+  FRIEND_TEST(ConnectionHealthCheckerTest, GetSocketInfo);
+  FRIEND_TEST(ConnectionHealthCheckerTest, SendData);
+  FRIEND_TEST(ConnectionHealthCheckerTest, ShutDown);
+
+  // Time to wait for DNS server.
+  static const int kDNSTimeoutSeconds;
+  // Number of connection attempts before failure per health check request.
+  static const int kMaxConnectionAttempts;
+  static const uint16 kRemotePort;
+
+  // Start a new AsyncConnection with callback set to OnConnectionComplete().
+  void SetupTcpConnection();
+
+  // Callback for AsyncConnection.
+  // Observe the setup connection to test health state
+  void OnConnectionComplete(bool success, int sock_fd);
+
+  // Callback for DnsClient
+  void GetDNSResult(const Error &error, const IPAddress &ip);
+
+  void TryNextIP();
+  Result SendData(int sock_fd);
+  Result ShutDown(int sock_fd);
+  bool GetSocketInfo(int sock_fd, SocketInfo *sock_info);
+
+  ConnectionRefPtr connection_;
+  EventDispatcher *dispatcher_;
+  base::Callback<void(Result)> result_callback_;
+
+  IPAddressQueue remote_ips_;
+  scoped_ptr<SocketInfoReader> socket_info_reader_;
+  scoped_ptr<Sockets> socket_;
+  base::WeakPtrFactory<ConnectionHealthChecker> weak_ptr_factory_;
+  const base::Callback<void(bool, int)> connection_complete_callback_;
+  const base::Callback<void(const Error&, const IPAddress&)>
+      dns_client_callback_;
+  scoped_ptr<AsyncConnection> tcp_connection_;
+  scoped_ptr<DNSClient> dns_client_;
+  // If true, HealthChecker attempts to send a small amount of data over
+  // the network during the test. Otherwise, the inference is based on
+  // the connection open/close behaviour.
+  // Default: true
+  bool run_data_test_;
+  bool health_check_in_progress_;
+  short num_connection_attempts_;
+
+  DISALLOW_COPY_AND_ASSIGN(ConnectionHealthChecker);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CONNECTION_HEALTH_CHECKER_H_
diff --git a/connection_health_checker_unittest.cc b/connection_health_checker_unittest.cc
new file mode 100644
index 0000000..df382ca
--- /dev/null
+++ b/connection_health_checker_unittest.cc
@@ -0,0 +1,953 @@
+// Copyright (c) 2013 The Chromium OS 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 "shill/connection_health_checker.h"
+
+#include <arpa/inet.h>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/memory/scoped_ptr.h>
+#include <gtest/gtest.h>
+
+#include "shill/mock_async_connection.h"
+#include "shill/mock_connection.h"
+#include "shill/mock_control.h"
+#include "shill/mock_dns_client.h"
+#include "shill/mock_device_info.h"
+#include "shill/mock_event_dispatcher.h"
+#include "shill/mock_sockets.h"
+#include "shill/mock_socket_info_reader.h"
+
+using base::Bind;
+using base::Callback;
+using base::Unretained;
+using std::string;
+using std::vector;
+using ::testing::DoAll;
+using ::testing::Gt;
+using ::testing::Invoke;
+using ::testing::Mock;
+using ::testing::NiceMock;
+using ::testing::Return;
+using ::testing::ReturnRef;
+using ::testing::Sequence;
+using ::testing::SetArgumentPointee;
+using ::testing::StrictMock;
+using ::testing::Test;
+using ::testing::_;
+
+namespace shill {
+
+namespace {
+const char kInterfaceName[] = "int0";
+const char kIPAddress_0_0_0_0[] = "0.0.0.0";
+const char kIPAddress_8_8_8_8[] = "8.8.8.8";
+const char kProxyIPAddressRemote[] = "74.125.224.84";
+const char kProxyIPAddressLocal[] = "192.23.34.1";
+const char kProxyIPv6AddressLocal[] = "::ffff:192.23.34.1";
+const char kProxyURLRemote[] = "http://www.google.com";
+const int kProxyFD = 100;
+const short kProxyPortLocal = 5540;
+const short kProxyPortRemote = 80;
+}  // namespace {}
+
+MATCHER_P(IsSameIPAddress, ip_addr, "") {
+  return arg.Equals(ip_addr);
+}
+
+class ConnectionHealthCheckerTest : public Test {
+ public:
+  ConnectionHealthCheckerTest()
+      : interface_name_(kInterfaceName),
+        device_info_(&control_, &dispatcher_,
+                     reinterpret_cast<Metrics*>(NULL),
+                     reinterpret_cast<Manager*>(NULL)),
+        connection_(new MockConnection(&device_info_)),
+        socket_(NULL) {}
+
+  // Invokes
+  int GetSockName(int fd, struct sockaddr *addr_out, socklen_t *sockaddr_size) {
+    struct sockaddr_in addr;
+    EXPECT_EQ(kProxyFD, fd);
+    EXPECT_LE(sizeof(sockaddr_in), *sockaddr_size);
+    addr.sin_family = AF_INET;
+    inet_pton(AF_INET, kProxyIPAddressLocal, &addr.sin_addr);
+    addr.sin_port = htons(kProxyPortLocal);
+    memcpy(addr_out, &addr, sizeof(addr));
+    *sockaddr_size = sizeof(sockaddr_in);
+    return 0;
+  }
+
+  int GetSockNameReturnsIPv6(int fd, struct sockaddr *addr_out,
+                             socklen_t *sockaddr_size) {
+    struct sockaddr_in6 addr;
+    EXPECT_EQ(kProxyFD, fd);
+    EXPECT_LE(sizeof(sockaddr_in6), *sockaddr_size);
+    addr.sin6_family = AF_INET6;
+    inet_pton(AF_INET6, kProxyIPv6AddressLocal, &addr.sin6_addr);
+    addr.sin6_port = htons(kProxyPortLocal);
+    memcpy(addr_out, &addr, sizeof(addr));
+    *sockaddr_size = sizeof(sockaddr_in6);
+    return 0;
+  }
+
+  void InvokeOnConnectionComplete(bool success, int sock_fd) {
+    health_checker_->OnConnectionComplete(success, sock_fd);
+  }
+
+ protected:
+  void SetUp() {
+    EXPECT_CALL(*connection_.get(), interface_name())
+        .WillRepeatedly(ReturnRef(interface_name_));
+    EXPECT_CALL(*connection_.get(), dns_servers())
+        .WillOnce(ReturnRef(dns_servers_));
+    health_checker_.reset(
+        new ConnectionHealthChecker(
+             connection_,
+             &dispatcher_,
+             Bind(&ConnectionHealthCheckerTest::ResultCallbackTarget,
+                  Unretained(this))));
+
+    socket_ = new StrictMock<MockSockets>();
+    tcp_connection_ = new StrictMock<MockAsyncConnection>();
+    socket_info_reader_ = new StrictMock<MockSocketInfoReader>();
+    dns_client_ = new MockDNSClient();
+    // Passes ownership for all of these.
+    health_checker_->socket_.reset(socket_);
+    health_checker_->tcp_connection_.reset(tcp_connection_);
+    health_checker_->socket_info_reader_.reset(socket_info_reader_);
+    health_checker_->dns_client_.reset(dns_client_);
+  }
+
+  void TearDown() {
+    EXPECT_CALL(*tcp_connection_, Stop())
+        .Times(1);
+  }
+
+  // Accessors for private data in ConnectionHealthChecker.
+  const Sockets *socket() {
+    return health_checker_->socket_.get();
+  }
+  const AsyncConnection *tcp_connection() {
+    return health_checker_->tcp_connection_.get(); }
+  bool health_check_in_progress() {
+    return health_checker_->health_check_in_progress_;
+  }
+  short num_connection_attempts() {
+    return health_checker_->num_connection_attempts_;
+  }
+  const ConnectionHealthChecker::IPAddressQueue &remote_ips() {
+    return health_checker_->remote_ips_;
+  }
+  int MaxConnectionAttempts() {
+    return ConnectionHealthChecker::kMaxConnectionAttempts;
+  }
+
+  // Mock Callbacks
+  MOCK_METHOD1(ResultCallbackTarget,
+               void(ConnectionHealthChecker::Result result));
+
+
+
+  // Helper methods
+  IPAddress StringToIPv4Address(const string &address_string) {
+    IPAddress ip_address(IPAddress::kFamilyIPv4);
+    EXPECT_TRUE(ip_address.SetAddressFromString(address_string));
+    return ip_address;
+  }
+  // Naming: CreateSocketInfo
+  //         + (Proxy/Other) : TCP connection for proxy socket / some other
+  //         socket.
+  //         + arg1: Pass in any SocketInfo::ConnectionState you want.
+  //         + arg2: Pass in any value of transmit_queue_value you want.
+  SocketInfo CreateSocketInfoOther() {
+    return SocketInfo(
+        SocketInfo::kConnectionStateUnknown,
+        StringToIPv4Address(kIPAddress_8_8_8_8),
+        0,
+        StringToIPv4Address(kProxyIPAddressRemote),
+        kProxyPortRemote,
+        0,
+        0,
+        SocketInfo::kTimerStateUnknown);
+  }
+  SocketInfo CreateSocketInfoProxy(SocketInfo::ConnectionState state) {
+    return SocketInfo(
+        state,
+        StringToIPv4Address(kProxyIPAddressLocal),
+        kProxyPortLocal,
+        StringToIPv4Address(kProxyIPAddressRemote),
+        kProxyPortRemote,
+        0,
+        0,
+        SocketInfo::kTimerStateUnknown);
+  }
+  SocketInfo CreateSocketInfoProxy(SocketInfo::ConnectionState state,
+                                   uint64 transmit_queue_value) {
+    return SocketInfo(
+        state,
+        StringToIPv4Address(kProxyIPAddressLocal),
+        kProxyPortLocal,
+        StringToIPv4Address(kProxyIPAddressRemote),
+        kProxyPortRemote,
+        transmit_queue_value,
+        0,
+        SocketInfo::kTimerStateUnknown);
+  }
+  void GetDNSResultFailure() {
+    Error error(Error::kOperationFailed, "");
+    IPAddress address(IPAddress::kFamilyUnknown);
+    health_checker_->GetDNSResult(error, address);
+  }
+  void GetDNSResultSuccess(const IPAddress &address) {
+    Error error;
+    health_checker_->GetDNSResult(error, address);
+  }
+  void VerifyAndClearAllExpectations() {
+    Mock::VerifyAndClearExpectations(this);
+    Mock::VerifyAndClearExpectations(tcp_connection_);
+    Mock::VerifyAndClearExpectations(socket_);
+    Mock::VerifyAndClearExpectations(socket_info_reader_);
+  }
+
+  // Expectations
+  void ExpectReset() {
+    EXPECT_EQ(connection_.get(), health_checker_->connection_.get());
+    EXPECT_EQ(&dispatcher_, health_checker_->dispatcher_);
+    EXPECT_EQ(socket_, health_checker_->socket_.get());
+    EXPECT_FALSE(socket_ == NULL);
+    EXPECT_EQ(socket_info_reader_, health_checker_->socket_info_reader_.get());
+    EXPECT_FALSE(socket_info_reader_ == NULL);
+    EXPECT_FALSE(health_checker_->connection_complete_callback_.is_null());
+    EXPECT_EQ(tcp_connection_, health_checker_->tcp_connection_.get());
+    EXPECT_FALSE(tcp_connection_ == NULL);
+    EXPECT_FALSE(health_checker_->health_check_in_progress_);
+    EXPECT_EQ(0, health_checker_->num_connection_attempts_);
+  }
+  // Setup ConnectionHealthChecker::GetSocketInfo to return sock_info.
+  // This only works if GetSocketInfo is called with kProxyFD.
+  // If no matching sock_info is provided (Does not belong to proxy socket),
+  // GetSocketInfo will (correctly) return false.
+  void ExpectGetSocketInfoReturns(SocketInfo sock_info) {
+    vector<SocketInfo> info_list;
+    info_list.push_back(sock_info);
+    EXPECT_CALL(*socket_, GetSockName(kProxyFD, _, _))
+        .InSequence(seq_)
+        .WillOnce(Invoke(this,
+                         &ConnectionHealthCheckerTest::GetSockName));
+    EXPECT_CALL(*socket_info_reader_, LoadTcpSocketInfo(_))
+        .InSequence(seq_)
+        .WillOnce(DoAll(SetArgumentPointee<0>(info_list),
+                        Return(true)));
+
+  }
+  void ExpectSuccessfulStart() {
+    EXPECT_CALL(
+        *tcp_connection_,
+        Start(IsSameIPAddress(StringToIPv4Address(kProxyIPAddressRemote)),
+              kProxyPortRemote))
+        .InSequence(seq_)
+        .WillOnce(Return(true));
+  }
+  void ExpectRetry() {
+    EXPECT_CALL(*socket_, Close(kProxyFD))
+        .Times(1)
+        .InSequence(seq_);
+    EXPECT_CALL(
+        *tcp_connection_,
+        Start(IsSameIPAddress(StringToIPv4Address(kProxyIPAddressRemote)),
+              kProxyPortRemote))
+        .InSequence(seq_)
+        .WillOnce(Return(true));
+  }
+  void ExpectSendDataReturns(ConnectionHealthChecker::Result result) {
+    // Turn on SendData
+    health_checker_->set_run_data_test(true);
+    // These scenarios are copied from the SendData unit-test, and must match
+    // those in the test.
+    switch(result) {
+      case ConnectionHealthChecker::kResultSuccess:
+        ExpectGetSocketInfoReturns(
+            CreateSocketInfoProxy(
+                SocketInfo::kConnectionStateEstablished, 0));
+        EXPECT_CALL(*socket_, Send(kProxyFD, _,Gt(0),_))
+            .InSequence(seq_)
+            .WillOnce(Return(0));
+        ExpectGetSocketInfoReturns(
+            CreateSocketInfoProxy(
+                SocketInfo::kConnectionStateEstablished, 0));
+        break;
+      case ConnectionHealthChecker::kResultConnectionFailure:
+        ExpectGetSocketInfoReturns(
+            CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished));
+        EXPECT_CALL(*socket_, Send(kProxyFD, _,Gt(0),_))
+            .InSequence(seq_)
+            .WillOnce(Return(-1));
+        EXPECT_CALL(*socket_, Error())
+            .InSequence(seq_)
+            .WillOnce(Return(0));
+        break;
+      case ConnectionHealthChecker::kResultUnknown:
+        ExpectGetSocketInfoReturns(CreateSocketInfoOther());
+        break;
+      case ConnectionHealthChecker::kResultCongestedTxQueue:
+        ExpectGetSocketInfoReturns(
+            CreateSocketInfoProxy(
+                SocketInfo::kConnectionStateEstablished, 1));
+        EXPECT_CALL(*socket_, Send(kProxyFD, _,Gt(0),_))
+            .InSequence(seq_)
+            .WillOnce(Return(0));
+        ExpectGetSocketInfoReturns(
+            CreateSocketInfoProxy(
+                SocketInfo::kConnectionStateEstablished, 2));
+        break;
+      default:
+        LOG(WARNING) << __func__ << "Unknown ConnectionHealthChecker::Result";
+    }
+  }
+  void ExpectShutDownReturns(ConnectionHealthChecker::Result result) {
+    // Turn on ShutDown
+    health_checker_->set_run_data_test(false);
+    if (result != ConnectionHealthChecker::kResultElongatedTimeWait) {
+      LOG(WARNING) << __func__ << ": Only implements expectation for "
+                   << "kResultElongatedTimeWait.";
+      return;
+    }
+    EXPECT_CALL(*socket_, ShutDown(kProxyFD, _))
+        .InSequence(seq_)
+        .WillOnce(Return(0));
+    ExpectGetSocketInfoReturns(
+        CreateSocketInfoProxy(SocketInfo::kConnectionStateFinWait1));
+  }
+  void ExpectCleanUp() {
+    EXPECT_CALL(*socket_, Close(kProxyFD))
+        .Times(1)
+        .InSequence(seq_);
+    EXPECT_CALL(*tcp_connection_, Stop())
+        .Times(1)
+        .InSequence(seq_);
+  }
+
+  // Needed for other mocks, but not for the tests directly.
+  const string interface_name_;
+  NiceMock<MockControl> control_;
+  NiceMock<MockDeviceInfo> device_info_;
+  vector<string> dns_servers_;
+
+  scoped_refptr<MockConnection> connection_;
+  StrictMock<MockEventDispatcher> dispatcher_;
+  StrictMock<MockSockets> *socket_;
+  StrictMock<MockSocketInfoReader> *socket_info_reader_;
+  StrictMock<MockAsyncConnection> *tcp_connection_;
+  MockDNSClient *dns_client_;
+  // Exepctations inthe Expect* functions are put in this sequence.
+  // This allows us to chain calls to Expect* functions.
+  Sequence seq_;
+
+  scoped_ptr<ConnectionHealthChecker> health_checker_;
+};
+
+TEST_F(ConnectionHealthCheckerTest, Constructor) {
+  ExpectReset();
+}
+
+TEST_F(ConnectionHealthCheckerTest, AddRemoteIP) {
+  EXPECT_EQ(0, remote_ips().size());
+
+  IPAddress ip = StringToIPv4Address(kIPAddress_0_0_0_0);
+  health_checker_->AddRemoteIP(ip);
+  EXPECT_EQ(1, remote_ips().size());
+  EXPECT_TRUE(remote_ips().front().Equals(ip));
+
+  health_checker_->AddRemoteIP(
+      StringToIPv4Address(kIPAddress_0_0_0_0));
+  EXPECT_EQ(2, remote_ips().size());
+}
+
+TEST_F(ConnectionHealthCheckerTest, AddRemoteURL) {
+  ConnectionHealthChecker::IPAddressQueue::size_type num_old_ips;
+
+  // DNS query fails synchronously.
+  EXPECT_CALL(*dns_client_, Start(_,_))
+      .WillOnce(Return(false));
+  num_old_ips = remote_ips().size();
+  health_checker_->AddRemoteURL(kProxyURLRemote);
+  EXPECT_EQ(num_old_ips, remote_ips().size());
+  Mock::VerifyAndClearExpectations(dns_client_);
+
+  // DNS query fails asynchronously.
+  EXPECT_CALL(*dns_client_, Start(_,_))
+      .WillOnce(Return(true));
+  num_old_ips = remote_ips().size();
+  health_checker_->AddRemoteURL(kProxyURLRemote);
+  GetDNSResultFailure();
+  EXPECT_EQ(num_old_ips, remote_ips().size());
+  Mock::VerifyAndClearExpectations(dns_client_);
+
+  // Success
+  EXPECT_CALL(*dns_client_, Start(_,_))
+      .WillOnce(Return(true));
+  num_old_ips = remote_ips().size();
+  health_checker_->AddRemoteURL(kProxyURLRemote);
+  IPAddress remote_ip = StringToIPv4Address(kProxyIPAddressRemote);
+  GetDNSResultSuccess(remote_ip);
+  EXPECT_EQ(num_old_ips + 1, remote_ips().size());
+  EXPECT_TRUE(remote_ip.Equals(remote_ips().front()));
+  Mock::VerifyAndClearExpectations(dns_client_);
+}
+
+TEST_F(ConnectionHealthCheckerTest, GetSocketInfo) {
+  SocketInfo sock_info;
+  vector<SocketInfo> info_list;
+
+  // GetSockName fails.
+  LOG(INFO) << "ConnectionHealthCheckerTest::GetSocketInfo::1";
+  EXPECT_CALL(*socket_, GetSockName(_,_,_))
+      .WillOnce(Return(-1));
+  EXPECT_FALSE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
+  Mock::VerifyAndClearExpectations(socket_);
+
+  // GetSockName returns IPv6.
+  LOG(INFO) << "ConnectionHealthCheckerTest::GetSocketInfo::2";
+  EXPECT_CALL(*socket_, GetSockName(_,_,_))
+      .WillOnce(
+          Invoke(this,
+                 &ConnectionHealthCheckerTest::GetSockNameReturnsIPv6));
+  EXPECT_FALSE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
+  Mock::VerifyAndClearExpectations(socket_);
+
+  // LoadTcpSocketInfo fails.
+  LOG(INFO) << "ConnectionHealthCheckerTest::GetSocketInfo::3";
+  EXPECT_CALL(*socket_, GetSockName(kProxyFD, _, _))
+      .WillOnce(Invoke(this, &ConnectionHealthCheckerTest::GetSockName));
+  EXPECT_CALL(*socket_info_reader_, LoadTcpSocketInfo(_))
+    .WillOnce(Return(false));
+  EXPECT_FALSE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  // LoadTcpSocketInfo returns empty list.
+  LOG(INFO) << "ConnectionHealthCheckerTest::GetSocketInfo::4";
+  info_list.clear();
+  EXPECT_CALL(*socket_, GetSockName(kProxyFD, _, _))
+      .WillOnce(Invoke(this, &ConnectionHealthCheckerTest::GetSockName));
+  EXPECT_CALL(*socket_info_reader_, LoadTcpSocketInfo(_))
+      .WillOnce(DoAll(SetArgumentPointee<0>(info_list),
+                      Return(true)));
+  EXPECT_FALSE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  // LoadTcpSocketInfo returns a list without our socket.
+  LOG(INFO) << "ConnectionHealthCheckerTest::GetSocketInfo::5";
+  info_list.clear();
+  info_list.push_back(CreateSocketInfoOther());
+  EXPECT_CALL(*socket_, GetSockName(kProxyFD, _, _))
+      .WillOnce(Invoke(this, &ConnectionHealthCheckerTest::GetSockName));
+  EXPECT_CALL(*socket_info_reader_, LoadTcpSocketInfo(_))
+      .WillOnce(DoAll(SetArgumentPointee<0>(info_list),
+                      Return(true)));
+  EXPECT_FALSE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  // LoadTcpSocketInfo returns a list with only our socket.
+  LOG(INFO) << "ConnectionHealthCheckerTest::GetSocketInfo::6";
+  info_list.clear();
+  info_list.push_back(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateUnknown));
+  EXPECT_CALL(*socket_, GetSockName(kProxyFD, _, _))
+      .WillOnce(Invoke(this, &ConnectionHealthCheckerTest::GetSockName));
+  EXPECT_CALL(*socket_info_reader_, LoadTcpSocketInfo(_))
+      .WillOnce(DoAll(SetArgumentPointee<0>(info_list),
+                      Return(true)));
+  EXPECT_TRUE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
+  EXPECT_TRUE(CreateSocketInfoProxy(SocketInfo::kConnectionStateUnknown)
+                  .IsSameSocketAs(sock_info));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  // LoadTcpSocketInfo returns a list with two sockets, including ours.
+  LOG(INFO) << "ConnectionHealthCheckerTest::GetSocketInfo::7";
+  info_list.clear();
+  info_list.push_back(CreateSocketInfoOther());
+  info_list.push_back(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateUnknown));
+  EXPECT_CALL(*socket_, GetSockName(kProxyFD, _, _))
+      .WillOnce(Invoke(this, &ConnectionHealthCheckerTest::GetSockName));
+  EXPECT_CALL(*socket_info_reader_, LoadTcpSocketInfo(_))
+      .WillOnce(DoAll(SetArgumentPointee<0>(info_list),
+                      Return(true)));
+  EXPECT_TRUE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
+  EXPECT_TRUE(CreateSocketInfoProxy(SocketInfo::kConnectionStateUnknown)
+                  .IsSameSocketAs(sock_info));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  LOG(INFO) << "ConnectionHealthCheckerTest::GetSocketInfo::8";
+  info_list.clear();
+  info_list.push_back(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateUnknown));
+  info_list.push_back(CreateSocketInfoOther());
+  EXPECT_CALL(*socket_, GetSockName(kProxyFD, _, _))
+      .WillOnce(Invoke(this, &ConnectionHealthCheckerTest::GetSockName));
+  EXPECT_CALL(*socket_info_reader_, LoadTcpSocketInfo(_))
+      .WillOnce(DoAll(SetArgumentPointee<0>(info_list),
+                      Return(true)));
+  EXPECT_TRUE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
+  EXPECT_TRUE(CreateSocketInfoProxy(SocketInfo::kConnectionStateUnknown)
+                  .IsSameSocketAs(sock_info));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+}
+
+TEST_F(ConnectionHealthCheckerTest, ShutDown) {
+  // Sockets::ShutDown fails.
+  EXPECT_CALL(*socket_, ShutDown(kProxyFD, _))
+      .WillOnce(Return(-1));
+  EXPECT_EQ(ConnectionHealthChecker::kResultUnknown,
+            health_checker_->ShutDown(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+
+  // SocketInfo in different connection states.
+  EXPECT_CALL(*socket_, ShutDown(kProxyFD, _))
+      .WillOnce(Return(0));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished));
+  EXPECT_EQ(ConnectionHealthChecker::kResultUnknown,
+            health_checker_->ShutDown(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  EXPECT_CALL(*socket_, ShutDown(kProxyFD, _))
+      .WillOnce(Return(0));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateSynSent));
+  EXPECT_EQ(ConnectionHealthChecker::kResultUnknown,
+            health_checker_->ShutDown(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  EXPECT_CALL(*socket_, ShutDown(kProxyFD, _))
+      .WillOnce(Return(0));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateSynRecv));
+  EXPECT_EQ(ConnectionHealthChecker::kResultUnknown,
+            health_checker_->ShutDown(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  EXPECT_CALL(*socket_, ShutDown(kProxyFD, _))
+      .WillOnce(Return(0));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateFinWait1));
+  EXPECT_EQ(ConnectionHealthChecker::kResultElongatedTimeWait,
+            health_checker_->ShutDown(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  EXPECT_CALL(*socket_, ShutDown(kProxyFD, _))
+      .WillOnce(Return(0));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateFinWait2));
+  EXPECT_EQ(ConnectionHealthChecker::kResultElongatedTimeWait,
+            health_checker_->ShutDown(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  EXPECT_CALL(*socket_, ShutDown(kProxyFD, _))
+      .WillOnce(Return(0));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateTimeWait));
+  EXPECT_EQ(ConnectionHealthChecker::kResultElongatedTimeWait,
+            health_checker_->ShutDown(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  EXPECT_CALL(*socket_, ShutDown(kProxyFD, _))
+      .WillOnce(Return(0));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateClose));
+  EXPECT_EQ(ConnectionHealthChecker::kResultUnknown,
+            health_checker_->ShutDown(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  EXPECT_CALL(*socket_, ShutDown(kProxyFD, _))
+      .WillOnce(Return(0));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateCloseWait));
+  EXPECT_EQ(ConnectionHealthChecker::kResultUnknown,
+            health_checker_->ShutDown(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  EXPECT_CALL(*socket_, ShutDown(kProxyFD, _))
+      .WillOnce(Return(0));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateLastAck));
+  EXPECT_EQ(ConnectionHealthChecker::kResultUnknown,
+            health_checker_->ShutDown(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  EXPECT_CALL(*socket_, ShutDown(kProxyFD, _))
+      .WillOnce(Return(0));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateListen));
+  EXPECT_EQ(ConnectionHealthChecker::kResultUnknown,
+            health_checker_->ShutDown(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  EXPECT_CALL(*socket_, ShutDown(kProxyFD, _))
+      .WillOnce(Return(0));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateClosing));
+  EXPECT_EQ(ConnectionHealthChecker::kResultUnknown,
+            health_checker_->ShutDown(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  // ConnectionHealthChecker::GetSocketInfo returns false.
+  // Since there is no entry in /proc, the shutdown must have been
+  // successful.
+  // TODO(pprabhu) Verify this can't happen: We check /proc *before* the
+  // established connection is even put there.
+  EXPECT_CALL(*socket_, ShutDown(kProxyFD, _))
+      .WillOnce(Return(0));
+  ExpectGetSocketInfoReturns(CreateSocketInfoOther());
+  EXPECT_EQ(ConnectionHealthChecker::kResultSuccess,
+            health_checker_->ShutDown(kProxyFD));
+}
+
+TEST_F(ConnectionHealthCheckerTest, SendData) {
+  // Connection doesn't exist.
+  ExpectGetSocketInfoReturns(CreateSocketInfoOther());
+  EXPECT_EQ(ConnectionHealthChecker::kResultUnknown,
+            health_checker_->SendData(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  // Connection in state other than Established
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateTimeWait));
+  EXPECT_EQ(ConnectionHealthChecker::kResultUnknown,
+            health_checker_->SendData(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  // Connection established, send fails.
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished));
+  EXPECT_CALL(*socket_, Send(kProxyFD, _,Gt(0),_))
+      .WillOnce(Return(-1));
+  EXPECT_CALL(*socket_, Error())
+      .WillOnce(Return(0));
+  EXPECT_EQ(ConnectionHealthChecker::kResultConnectionFailure,
+            health_checker_->SendData(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  // Connection established, send succeeds, and
+  // Connection dissapears
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished));
+  ExpectGetSocketInfoReturns(CreateSocketInfoOther());
+  EXPECT_CALL(*socket_, Send(kProxyFD, _,Gt(0),_))
+      .WillOnce(Return(0));
+  EXPECT_EQ(ConnectionHealthChecker::kResultUnknown,
+            health_checker_->SendData(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  // Connection switches to bad state
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateClose));
+  EXPECT_CALL(*socket_, Send(kProxyFD, _,Gt(0),_))
+      .WillOnce(Return(0));
+  EXPECT_EQ(ConnectionHealthChecker::kResultUnknown,
+            health_checker_->SendData(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  // Connection remains good, data not sent
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished, 0));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished, 1));
+  EXPECT_CALL(*socket_, Send(kProxyFD, _,Gt(0),_))
+      .WillOnce(Return(0));
+  EXPECT_EQ(ConnectionHealthChecker::kResultCongestedTxQueue,
+            health_checker_->SendData(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished, 1));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished, 2));
+  EXPECT_CALL(*socket_, Send(kProxyFD, _,Gt(0),_))
+      .WillOnce(Return(0));
+  EXPECT_EQ(ConnectionHealthChecker::kResultCongestedTxQueue,
+            health_checker_->SendData(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished, 0));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished, 10));
+  EXPECT_CALL(*socket_, Send(kProxyFD, _,Gt(0),_))
+      .WillOnce(Return(0));
+  EXPECT_EQ(ConnectionHealthChecker::kResultCongestedTxQueue,
+            health_checker_->SendData(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  // Connection remains good, data sent.
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished, 0));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished, 0));
+  EXPECT_CALL(*socket_, Send(kProxyFD, _,Gt(0),_))
+      .WillOnce(Return(0));
+  EXPECT_EQ(ConnectionHealthChecker::kResultSuccess,
+            health_checker_->SendData(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished, 3));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished, 3));
+  EXPECT_CALL(*socket_, Send(kProxyFD, _,Gt(0),_))
+      .WillOnce(Return(0));
+  EXPECT_EQ(ConnectionHealthChecker::kResultSuccess,
+            health_checker_->SendData(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished, 10));
+  ExpectGetSocketInfoReturns(
+      CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished, 0));
+  EXPECT_CALL(*socket_, Send(kProxyFD, _,Gt(0),_))
+      .WillOnce(Return(0));
+  EXPECT_EQ(ConnectionHealthChecker::kResultSuccess,
+            health_checker_->SendData(kProxyFD));
+  Mock::VerifyAndClearExpectations(socket_);
+  Mock::VerifyAndClearExpectations(socket_info_reader_);
+}
+
+// Scenario tests.
+// Flow: Start() -> Start()
+// Expectation: Only one AsyncConnection is setup
+TEST_F(ConnectionHealthCheckerTest, StartStartSkipsSecond) {
+  EXPECT_CALL(*tcp_connection_, Start(_,_))
+      .WillOnce(Return(true));
+  health_checker_->AddRemoteIP(StringToIPv4Address(kProxyIPAddressRemote));
+  health_checker_->Start();
+  health_checker_->Start();
+}
+
+// Precondition: size(|remote_ips_|) > 0
+// Flow: Start() -> Stop() before ConnectionComplete()
+// Expectation: No call to |result_callback|
+TEST_F(ConnectionHealthCheckerTest, StartStopNoCallback) {
+  EXPECT_CALL(*tcp_connection_, Start(_,_))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*tcp_connection_, Stop())
+      .Times(1);
+  EXPECT_CALL(*this, ResultCallbackTarget(_))
+      .Times(0);
+  health_checker_->AddRemoteIP(StringToIPv4Address(kProxyIPAddressRemote));
+  health_checker_->Start();
+  health_checker_->Stop();
+}
+
+// Precondition: Empty remote_ips_
+// Flow: Start()
+// Expectation: call |result_callback| with kResultUnknown
+// Precondition: Non-empty remote_ips_
+// Flow: Start() -> synchronous async_connection failure
+// Expectation: call |result_callback| with kResultConnectionFailure
+TEST_F(ConnectionHealthCheckerTest, StartImmediateFailure) {
+  EXPECT_CALL(*tcp_connection_, Stop())
+      .Times(1);
+  EXPECT_CALL(*this, ResultCallbackTarget(
+                           ConnectionHealthChecker::kResultUnknown))
+      .Times(1);
+  health_checker_->Start();
+  Mock::VerifyAndClearExpectations(this);
+  Mock::VerifyAndClearExpectations(tcp_connection_);
+
+  health_checker_->AddRemoteIP(StringToIPv4Address(kProxyIPAddressRemote));
+  EXPECT_CALL(*tcp_connection_,
+              Start(IsSameIPAddress(StringToIPv4Address(kProxyIPAddressRemote)),
+                    kProxyPortRemote))
+      .WillOnce(Return(false));
+  EXPECT_CALL(*tcp_connection_, Stop())
+      .Times(1);
+  EXPECT_CALL(*this, ResultCallbackTarget(
+                           ConnectionHealthChecker::kResultConnectionFailure))
+      .Times(1);
+  health_checker_->Start();
+  Mock::VerifyAndClearExpectations(this);
+  Mock::VerifyAndClearExpectations(tcp_connection_);
+}
+
+// Precondition: len(|remote_ips_|) == 1
+// Flow: Start() -> asynchronous async_connection failure
+// Expectation: call |result_callback| with kResultConnectionFailure
+TEST_F(ConnectionHealthCheckerTest, StartAsynchrnousFailure) {
+  EXPECT_CALL(*tcp_connection_,
+              Start(IsSameIPAddress(StringToIPv4Address(kProxyIPAddressRemote)),
+                    kProxyPortRemote))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*tcp_connection_, Stop())
+      .Times(1);
+  EXPECT_CALL(*this, ResultCallbackTarget(
+                           ConnectionHealthChecker::kResultConnectionFailure))
+      .Times(1);
+  health_checker_->AddRemoteIP(StringToIPv4Address(kProxyIPAddressRemote));
+  health_checker_->Start();
+  InvokeOnConnectionComplete(false, -1);  // second argument ignored.
+  Mock::VerifyAndClearExpectations(this);
+  Mock::VerifyAndClearExpectations(tcp_connection_);
+}
+
+// Precondition: len(|remote_ips_|) == 1
+// Flow: Start() -> Connection Successful -> SendData() returns kResultSucess
+// Expectation: call |result_callback| with kResultSuccess
+TEST_F(ConnectionHealthCheckerTest, HealthCheckSuccess) {
+  ExpectSuccessfulStart();
+  ExpectSendDataReturns(ConnectionHealthChecker::kResultSuccess);
+  ExpectCleanUp();
+  // Expectation:
+  EXPECT_CALL(*this, ResultCallbackTarget(
+                           ConnectionHealthChecker::kResultSuccess))
+      .Times(1);
+  health_checker_->AddRemoteIP(StringToIPv4Address(kProxyIPAddressRemote));
+  health_checker_->Start();
+  InvokeOnConnectionComplete(true, kProxyFD);
+  VerifyAndClearAllExpectations();
+}
+
+// Precondition: len(|remote_ips_|) == 1
+// Flow: Start() -> Connection Successful
+//       -> SendData() returns kResultCongestedTxQueue
+// Expectation: call |result_callback| with kResultCongestedTxQueue
+TEST_F(ConnectionHealthCheckerTest, HealthCheckCongestedQueue) {
+  ExpectSuccessfulStart();
+  ExpectSendDataReturns(ConnectionHealthChecker::kResultCongestedTxQueue);
+  ExpectCleanUp();
+  // Expectation:
+  EXPECT_CALL(*this, ResultCallbackTarget(
+                           ConnectionHealthChecker::kResultCongestedTxQueue))
+      .Times(1);
+  health_checker_->AddRemoteIP(StringToIPv4Address(kProxyIPAddressRemote));
+  health_checker_->Start();
+  InvokeOnConnectionComplete(true, kProxyFD);
+  VerifyAndClearAllExpectations();
+}
+
+// Precondition: len(|remote_ips_|) == 1
+// Flow: Start() -> Connection Successful
+//       -> ShutDown() returns kResultElongatedTimeWait
+// Expectation: call |result_callback| with kResultElongatedTimeWait
+TEST_F(ConnectionHealthCheckerTest, HealthCheckElongatedTimeWait) {
+  ExpectSuccessfulStart();
+  ExpectShutDownReturns(ConnectionHealthChecker::kResultElongatedTimeWait);
+  ExpectCleanUp();
+  // Expectation:
+  EXPECT_CALL(*this, ResultCallbackTarget(
+                           ConnectionHealthChecker::kResultElongatedTimeWait))
+      .Times(1);
+  health_checker_->AddRemoteIP(StringToIPv4Address(kProxyIPAddressRemote));
+  health_checker_->Start();
+  InvokeOnConnectionComplete(true, kProxyFD);
+  VerifyAndClearAllExpectations();
+}
+
+// Precondition: len(|remote_ips_|) == 2
+// Flow: Start() -> Connection Successful ->
+//       SendData() returns kResultUnknown ->
+//       SendData() returns kResultUnknown
+// Expectation: (1) call |result_callback| with kResultConnectionFailure
+//              (2) Sockets::Close called twice
+TEST_F(ConnectionHealthCheckerTest, HealthCheckFailOutOfIPs) {
+  ExpectSuccessfulStart();
+  ExpectSendDataReturns(ConnectionHealthChecker::kResultUnknown);
+  ExpectRetry();
+  ExpectSendDataReturns(ConnectionHealthChecker::kResultUnknown);
+  ExpectCleanUp();
+  // Expectation:
+  EXPECT_CALL(*this, ResultCallbackTarget(
+                           ConnectionHealthChecker::kResultConnectionFailure))
+      .Times(1);
+  health_checker_->AddRemoteIP(StringToIPv4Address(kProxyIPAddressRemote));
+  health_checker_->AddRemoteIP(StringToIPv4Address(kProxyIPAddressRemote));
+  health_checker_->Start();
+  InvokeOnConnectionComplete(true, kProxyFD);
+  InvokeOnConnectionComplete(true, kProxyFD);
+  VerifyAndClearAllExpectations();
+}
+
+// Precondition: len(|remote_ips_|) == 2
+// Flow: Start() -> Connection Successful -> SendData() returns kResultUnknown
+//       -> SendData() returns kResultSuccess
+// Expectation: call |result_callback| with kResultSuccess
+//              (2) Sockets::Close called twice
+TEST_F(ConnectionHealthCheckerTest, HealthCheckSuccessSecondIP) {
+  ExpectSuccessfulStart();
+  ExpectSendDataReturns(ConnectionHealthChecker::kResultUnknown);
+  ExpectRetry();
+  ExpectSendDataReturns(ConnectionHealthChecker::kResultSuccess);
+  ExpectCleanUp();
+  // Expectation:
+  EXPECT_CALL(*this, ResultCallbackTarget(
+                           ConnectionHealthChecker::kResultSuccess))
+      .Times(1);
+  health_checker_->AddRemoteIP(StringToIPv4Address(kProxyIPAddressRemote));
+  health_checker_->AddRemoteIP(StringToIPv4Address(kProxyIPAddressRemote));
+  health_checker_->Start();
+  InvokeOnConnectionComplete(true, kProxyFD);
+  InvokeOnConnectionComplete(true, kProxyFD);
+  VerifyAndClearAllExpectations();
+}
+
+//
+// Precondition: len(|remote_ips_|) > |kMaxConnectionAttempts|
+// Flow: Start() -> Connection Successful
+//               -> Forever(SendData() returns kResultUnknown)
+// Expectation: call |result_callback| with kResultConnectionFailure
+//              (2) Sockets::Close called |kMaxConnectionAttempts| times
+//
+TEST_F(ConnectionHealthCheckerTest, HealthCheckFailHitMaxConnectionAttempts) {
+  ExpectSuccessfulStart();
+  ExpectSendDataReturns(ConnectionHealthChecker::kResultUnknown);
+  for (int i = 0; i < MaxConnectionAttempts()-1; ++i) {
+    ExpectRetry();
+    ExpectSendDataReturns(ConnectionHealthChecker::kResultUnknown);
+  }
+  ExpectCleanUp();
+  // Expectation:
+  EXPECT_CALL(*this, ResultCallbackTarget(
+                           ConnectionHealthChecker::kResultConnectionFailure))
+      .Times(1);
+  for (int i = 0; i < MaxConnectionAttempts() + 2; ++i)
+    health_checker_->AddRemoteIP(StringToIPv4Address(kProxyIPAddressRemote));
+  health_checker_->Start();
+  for (int i = 0; i < MaxConnectionAttempts(); ++i)
+    InvokeOnConnectionComplete(true, kProxyFD);
+  VerifyAndClearAllExpectations();
+}
+
+}  // namespace shill
diff --git a/mock_sockets.h b/mock_sockets.h
index 0a8a5a8..6b92a8f 100644
--- a/mock_sockets.h
+++ b/mock_sockets.h
@@ -48,6 +48,7 @@
                                socklen_t addrlen));
   MOCK_METHOD1(SetNonBlocking, int(int sockfd));
   MOCK_METHOD2(SetReceiveBuffer, int(int sockfd, int size));
+  MOCK_METHOD2(ShutDown, int(int sockfd, int how));
   MOCK_METHOD3(Socket, int(int domain, int type, int protocol));
 
  private:
diff --git a/sockets.cc b/sockets.cc
index a5f081a..f1cd221 100644
--- a/sockets.cc
+++ b/sockets.cc
@@ -113,6 +113,10 @@
   return setsockopt(sockfd, SOL_SOCKET, SO_RCVBUFFORCE, &size, sizeof(size));
 }
 
+int Sockets::ShutDown(int sockfd, int how) {
+  return HANDLE_EINTR(shutdown(sockfd, how));
+}
+
 int Sockets::Socket(int domain, int type, int protocol) {
   return socket(domain, type, protocol);
 }
diff --git a/sockets.h b/sockets.h
index e177ba4..6246f84 100644
--- a/sockets.h
+++ b/sockets.h
@@ -75,6 +75,9 @@
   // setsockopt(SO_RCVBUFFORCE)
   virtual int SetReceiveBuffer(int sockfd, int size);
 
+  // shutdown
+  virtual int ShutDown(int sockfd, int how);
+
   // socket
   virtual int Socket(int domain, int type, int protocol);
 };