shill: factor out ActiveLinkMonitor from LinkMonitor

ActiveLinkMonitor monitors the link status by sending ARP requests
and probing for ARP replies. Once the active link monitor is started,
the link will be declared as failure if it failed to receive ARP reply
for 5 consecutive broadcast ARP requests or unicast ARP requests in the
case when gateway unicast ARP support is established. A callback will be
invoked and ActiveLinkMonitor will automatically stopped when the link
status is deteremined.

BUG=chromium:422159
TEST=USE="asan clang" FEATURES=test emerge-$BOARD shill

Change-Id: I8a662b5f1c0b4ee4450f2327f5cdd03feb231b8b
Reviewed-on: https://chromium-review.googlesource.com/242278
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Zeping Qiu <zqiu@chromium.org>
Trybot-Ready: Zeping Qiu <zqiu@chromium.org>
Tested-by: Zeping Qiu <zqiu@chromium.org>
diff --git a/active_link_monitor.cc b/active_link_monitor.cc
new file mode 100644
index 0000000..85644c0
--- /dev/null
+++ b/active_link_monitor.cc
@@ -0,0 +1,330 @@
+// Copyright 2015 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/active_link_monitor.h"
+
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/strings/stringprintf.h>
+#include <base/strings/string_util.h>
+
+#include "shill/arp_client.h"
+#include "shill/arp_packet.h"
+#include "shill/connection.h"
+#include "shill/device_info.h"
+#include "shill/event_dispatcher.h"
+#include "shill/logging.h"
+#include "shill/metrics.h"
+#include "shill/net/ip_address.h"
+#include "shill/net/shill_time.h"
+
+using base::Bind;
+using base::Unretained;
+using std::string;
+
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kLink;
+static string ObjectID(Connection *c) { return c->interface_name(); }
+}
+
+const int ActiveLinkMonitor::kDefaultTestPeriodMilliseconds = 5000;
+const int ActiveLinkMonitor::kFailureThreshold = 5;
+const int ActiveLinkMonitor::kFastTestPeriodMilliseconds = 200;
+const int ActiveLinkMonitor::kMaxResponseSampleFilterDepth = 5;
+const int ActiveLinkMonitor::kUnicastReplyReliabilityThreshold = 10;
+
+ActiveLinkMonitor::ActiveLinkMonitor(const ConnectionRefPtr &connection,
+                                     EventDispatcher *dispatcher,
+                                     Metrics *metrics,
+                                     DeviceInfo *device_info,
+                                     const FailureCallback &failure_callback,
+                                     const SuccessCallback &success_callback)
+    : connection_(connection),
+      dispatcher_(dispatcher),
+      metrics_(metrics),
+      device_info_(device_info),
+      failure_callback_(failure_callback),
+      success_callback_(success_callback),
+      arp_client_(new ArpClient(connection->interface_index())),
+      test_period_milliseconds_(kDefaultTestPeriodMilliseconds),
+      broadcast_failure_count_(0),
+      unicast_failure_count_(0),
+      broadcast_success_count_(0),
+      unicast_success_count_(0),
+      is_unicast_(false),
+      gateway_supports_unicast_arp_(false),
+      response_sample_count_(0),
+      response_sample_bucket_(0),
+      time_(Time::GetInstance()) {
+}
+
+ActiveLinkMonitor::~ActiveLinkMonitor() {
+  Stop();
+}
+
+bool ActiveLinkMonitor::Start(int test_period) {
+  StopMonitorCycle();
+  return StartInternal(test_period);
+}
+
+void ActiveLinkMonitor::Stop() {
+  SLOG(connection_.get(), 2) << "In " << __func__ << ".";
+  // Stop current cycle.
+  StopMonitorCycle();
+
+  // Clear stats accumulated from previous monitor cycles.
+  local_mac_address_.Clear();
+  gateway_mac_address_.Clear();
+  broadcast_success_count_ = 0;
+  unicast_success_count_ = 0;
+  broadcast_failure_count_ = 0;
+  unicast_failure_count_ = 0;
+  is_unicast_ = false;
+  gateway_supports_unicast_arp_ = false;
+  response_sample_bucket_ = 0;
+  response_sample_count_ = 0;
+}
+
+int ActiveLinkMonitor::GetResponseTimeMilliseconds() const {
+  return response_sample_count_ ?
+      response_sample_bucket_ / response_sample_count_ : 0;
+}
+
+bool ActiveLinkMonitor::IsGatewayFound() const {
+  return !gateway_mac_address_.IsZero();
+}
+
+bool ActiveLinkMonitor::StartInternal(int probe_period_milliseconds) {
+  test_period_milliseconds_ = probe_period_milliseconds;
+  if (test_period_milliseconds_ > kDefaultTestPeriodMilliseconds) {
+    LOG(WARNING) << "Long test period; UMA stats will be truncated.";
+  }
+
+  if (!device_info_->GetMACAddress(
+          connection_->interface_index(), &local_mac_address_)) {
+    LOG(ERROR) << "Could not get local MAC address.";
+    metrics_->NotifyLinkMonitorFailure(
+        connection_->technology(),
+        Metrics::kLinkMonitorMacAddressNotFound,
+        0, 0, 0);
+    Stop();
+    return false;
+  }
+
+  if (!StartArpClient()) {
+    LOG(ERROR) << "Failed to start ARP client.";
+    metrics_->NotifyLinkMonitorFailure(
+        connection_->technology(),
+        Metrics::kLinkMonitorClientStartFailure,
+        0, 0, 0);
+    Stop();
+    return false;
+  }
+
+  if (gateway_mac_address_.IsEmpty()) {
+    gateway_mac_address_ = ByteString(local_mac_address_.GetLength());
+  }
+  send_request_callback_.Reset(
+      Bind(&ActiveLinkMonitor::SendRequest, Unretained(this)));
+  // Post a task to send ARP request instead of calling it synchronously, to
+  // maintain consistent expectation in the case of send failures, which will
+  // always invoke failure callback.
+  dispatcher_->PostTask(send_request_callback_.callback());
+  return true;
+}
+
+void ActiveLinkMonitor::StopMonitorCycle() {
+  StopArpClient();
+  send_request_callback_.Cancel();
+  timerclear(&sent_request_at_);
+}
+
+void ActiveLinkMonitor::AddResponseTimeSample(int response_time_milliseconds) {
+  SLOG(connection_.get(), 2) << "In " << __func__ << " with sample "
+                             << response_time_milliseconds << ".";
+  metrics_->NotifyLinkMonitorResponseTimeSampleAdded(
+      connection_->technology(), response_time_milliseconds);
+  response_sample_bucket_ += response_time_milliseconds;
+  if (response_sample_count_ < kMaxResponseSampleFilterDepth) {
+    ++response_sample_count_;
+  } else {
+    response_sample_bucket_ =
+        response_sample_bucket_ * kMaxResponseSampleFilterDepth /
+            (kMaxResponseSampleFilterDepth + 1);
+  }
+}
+
+// static
+string ActiveLinkMonitor::HardwareAddressToString(const ByteString &address) {
+  std::vector<string> address_parts;
+  for (size_t i = 0; i < address.GetLength(); ++i) {
+    address_parts.push_back(
+        base::StringPrintf("%02x", address.GetConstData()[i]));
+  }
+  return JoinString(address_parts, ':');
+}
+
+bool ActiveLinkMonitor::StartArpClient() {
+  if (!arp_client_->StartReplyListener()) {
+    return false;
+  }
+  SLOG(connection_.get(), 4) << "Created ARP client; listening on socket "
+                             << arp_client_->socket() << ".";
+  receive_response_handler_.reset(
+    dispatcher_->CreateReadyHandler(
+        arp_client_->socket(),
+        IOHandler::kModeInput,
+        Bind(&ActiveLinkMonitor::ReceiveResponse, Unretained(this))));
+  return true;
+}
+
+void ActiveLinkMonitor::StopArpClient() {
+  arp_client_->Stop();
+  receive_response_handler_.reset();
+}
+
+bool ActiveLinkMonitor::AddMissedResponse() {
+  SLOG(connection_.get(), 2) << "In " << __func__ << ".";
+  AddResponseTimeSample(test_period_milliseconds_);
+
+  if (is_unicast_) {
+    if (gateway_supports_unicast_arp_) {
+      ++unicast_failure_count_;
+    }
+    unicast_success_count_ = 0;
+  } else {
+    ++broadcast_failure_count_;
+    broadcast_success_count_ = 0;
+  }
+
+  if (unicast_failure_count_ + broadcast_failure_count_ >= kFailureThreshold) {
+    LOG(ERROR) << "Link monitor has reached the failure threshold with "
+               << broadcast_failure_count_
+               << " broadcast failures and "
+               << unicast_failure_count_
+               << " unicast failures.";
+    failure_callback_.Run(Metrics::kLinkMonitorFailureThresholdReached,
+                          broadcast_failure_count_,
+                          unicast_failure_count_);
+    Stop();
+    return true;
+  }
+  is_unicast_ = !is_unicast_;
+  return false;
+}
+
+void ActiveLinkMonitor::ReceiveResponse(int fd) {
+  SLOG(connection_.get(), 2) << "In " << __func__ << ".";
+  ArpPacket packet;
+  ByteString sender;
+  if (!arp_client_->ReceivePacket(&packet, &sender)) {
+    return;
+  }
+
+  if (!packet.IsReply()) {
+    SLOG(connection_.get(), 4) << "This is not a reply packet.  Ignoring.";
+    return;
+  }
+
+  if (!connection_->local().address().Equals(
+           packet.remote_ip_address().address())) {
+    SLOG(connection_.get(), 4) << "Response is not for our IP address.";
+    return;
+  }
+
+  if (!local_mac_address_.Equals(packet.remote_mac_address())) {
+    SLOG(connection_.get(), 4) << "Response is not for our MAC address.";
+    return;
+  }
+
+  if (!connection_->gateway().address().Equals(
+           packet.local_ip_address().address())) {
+    SLOG(connection_.get(), 4)
+        << "Response is not from the gateway IP address.";
+    return;
+  }
+
+  struct timeval now, elapsed_time;
+  time_->GetTimeMonotonic(&now);
+  timersub(&now, &sent_request_at_, &elapsed_time);
+
+  AddResponseTimeSample(elapsed_time.tv_sec * 1000 +
+                        elapsed_time.tv_usec / 1000);
+
+  if (is_unicast_) {
+    ++unicast_success_count_;
+    unicast_failure_count_ = 0;
+    if (unicast_success_count_ >= kUnicastReplyReliabilityThreshold) {
+      SLOG_IF(Link, 2, !gateway_supports_unicast_arp_)
+          << "Gateway is now considered a reliable unicast responder.  "
+             "Unicast failures will now count.";
+      gateway_supports_unicast_arp_ = true;
+    }
+  } else {
+    ++broadcast_success_count_;
+    broadcast_failure_count_ = 0;
+  }
+
+  if (!gateway_mac_address_.Equals(packet.local_mac_address())) {
+    const ByteString &new_mac_address = packet.local_mac_address();
+    if (!IsGatewayFound()) {
+      SLOG(connection_.get(), 2) << "Found gateway at "
+                                 << HardwareAddressToString(new_mac_address);
+    } else {
+      SLOG(connection_.get(), 2) << "Gateway MAC address changed.";
+    }
+    gateway_mac_address_ = new_mac_address;
+  }
+
+  is_unicast_ = !is_unicast_;
+
+  // Stop the current cycle, and invoke the success callback. All the
+  // accumulated stats regarding the gateway are not cleared.
+  StopMonitorCycle();
+  success_callback_.Run();
+}
+
+void ActiveLinkMonitor::SendRequest() {
+  SLOG(connection_.get(), 2) << "In " << __func__ << ".";
+
+  // Timeout waiting for ARP reply and exceed the failure threshold.
+  if (timerisset(&sent_request_at_) && AddMissedResponse()) {
+    return;
+  }
+
+  ByteString destination_mac_address(gateway_mac_address_.GetLength());
+  if (!IsGatewayFound()) {
+    // The remote MAC addess is set by convention to be all-zeroes in the
+    // ARP header if not known.  The ArpClient will translate an all-zeroes
+    // remote address into a send to the broadcast (all-ones) address in
+    // the Ethernet frame header.
+    SLOG_IF(Link, 2, is_unicast_) << "Sending broadcast since "
+                                  << "gateway MAC is unknown";
+    is_unicast_ = false;
+  } else if (is_unicast_) {
+    destination_mac_address = gateway_mac_address_;
+  }
+  LOG(INFO) << "IsGatway " << IsGatewayFound() << " unicast: " << is_unicast_;
+  ArpPacket request(connection_->local(), connection_->gateway(),
+                    local_mac_address_, destination_mac_address);
+  if (!arp_client_->TransmitRequest(request)) {
+    LOG(ERROR) << "Failed to send ARP request.  Stopping.";
+    failure_callback_.Run(Metrics::kLinkMonitorTransmitFailure,
+                          broadcast_failure_count_,
+                          unicast_failure_count_);
+    Stop();
+    return;
+  }
+
+  time_->GetTimeMonotonic(&sent_request_at_);
+
+  dispatcher_->PostDelayedTask(send_request_callback_.callback(),
+                               test_period_milliseconds_);
+}
+
+}  // namespace shill
diff --git a/active_link_monitor.h b/active_link_monitor.h
new file mode 100644
index 0000000..ac49efb
--- /dev/null
+++ b/active_link_monitor.h
@@ -0,0 +1,191 @@
+// Copyright 2015 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_ACTIVE_LINK_MONITOR_H_
+#define SHILL_ACTIVE_LINK_MONITOR_H_
+
+#include <time.h>
+
+#include <memory>
+#include <string>
+
+#include <base/callback.h>
+#include <base/cancelable_callback.h>
+
+#include "shill/metrics.h"
+#include "shill/net/byte_string.h"
+#include "shill/refptr_types.h"
+
+namespace shill {
+
+class ArpClient;
+class DeviceInfo;
+class EventDispatcher;
+class IOHandler;
+class Time;
+
+// ActiveLinkMonitor probes the status of a connection by sending ARP
+// messages to the default gateway for a connection. The link will be declared
+// as failure if no ARP reply is received for 5 consecutive broadcast ARP
+// requests or unicast ARP requests in the case when gateway unicast ARP
+// support is established. And active when an ARP reply is received.
+// A callback will be invoked  when the link is detected as failure or active.
+// The active link monitor will automatically stop when the link status is
+// determined. It also keeps track of response times which can be an indicator
+// of link quality.
+class ActiveLinkMonitor {
+ public:
+  // FailureCallback takes monitor failure code, broadcast failure count, and
+  // unicast failure count as arguments.
+  typedef base::Callback<void(Metrics::LinkMonitorFailure, int, int)>
+      FailureCallback;
+  typedef base::Closure SuccessCallback;
+
+  // The default number of milliseconds between ARP requests. Needed by Metrics.
+  static const int kDefaultTestPeriodMilliseconds;
+
+  // The number of milliseconds between ARP requests when running a quick test.
+  // Used when the device just resume from suspend. Also needed by unit tests.
+  static const int kFastTestPeriodMilliseconds;
+
+  // When the sum of consecutive counted unicast and broadcast failures
+  // equals this value, the failure callback is called, the counters
+  // are reset, and the link monitoring quiesces.  Needed by Metrics.
+  static const int kFailureThreshold;
+
+  ActiveLinkMonitor(const ConnectionRefPtr &connection,
+                    EventDispatcher *dispatcher,
+                    Metrics *metrics,
+                    DeviceInfo *device_info,
+                    const FailureCallback &failure_callback,
+                    const SuccessCallback &success_callback);
+  virtual ~ActiveLinkMonitor();
+
+  // Starts an active link-monitoring cycle on the selected connection, with
+  // specified |probe_period_millisecond| milliseconds between each ARP
+  // requests. Returns true if successful, false otherwise.
+  virtual bool Start(int probe_period_millisecond);
+  // Stop active link-monitoring on the selected connection. Clears any
+  // accumulated statistics.
+  virtual void Stop();
+
+  // Return modified cumulative average of the gateway ARP response
+  // time.  Returns zero if no samples are available.  For each
+  // missed ARP response, the sample is assumed to be the full
+  // test period.
+  int GetResponseTimeMilliseconds() const;
+
+  // Returns true if the ActiveLinkMonitor was ever able to find the default
+  // gateway via broadcast ARP.
+  bool IsGatewayFound() const;
+
+  const ByteString &gateway_mac_address() const {
+    return gateway_mac_address_;
+  }
+
+ private:
+  friend class ActiveLinkMonitorTest;
+
+  // The number of samples to compute a "strict" average over.  When
+  // more samples than this number arrive, this determines how "slow"
+  // our simple low-pass filter works.
+  static const int kMaxResponseSampleFilterDepth;
+
+  // When the sum of consecutive unicast successes equals this value,
+  // we can assume that in general this gateway supports unicast ARP
+  // requests, and we will count future unicast failures.
+  static const int kUnicastReplyReliabilityThreshold;
+
+  // Similar to Start, except that the initial probes use
+  // |probe_period_milliseconds|. After successfully probing with both
+  // broadcast and unicast ARPs (at least one of each), LinkMonitor
+  // switches itself to kDefaultTestPeriodMilliseconds.
+  virtual bool StartInternal(int probe_period_milliseconds);
+  // Stop the current monitoring cycle. It is called when current monitor cycle
+  // results in success.
+  void StopMonitorCycle();
+  // Add a response time sample to the buffer.
+  void AddResponseTimeSample(int response_time_milliseconds);
+  // Start and stop ARP client for sending/receiving ARP requests/replies.
+  bool StartArpClient();
+  void StopArpClient();
+  // Convert a hardware address byte-string to a colon-separated string.
+  static std::string HardwareAddressToString(const ByteString &address);
+  // Denote a missed response.  Returns true if this loss has caused us
+  // to exceed the failure threshold.
+  bool AddMissedResponse();
+  // This I/O callback is triggered whenever the ARP reception socket
+  // has data available to be received.
+  void ReceiveResponse(int fd);
+  // Send the next ARP request.
+  void SendRequest();
+
+  // The connection on which to perform link monitoring.
+  ConnectionRefPtr connection_;
+  // Dispatcher on which to create delayed tasks.
+  EventDispatcher *dispatcher_;
+  // Metrics instance on which to post performance results.
+  Metrics *metrics_;
+  // DeviceInfo instance for retrieving the MAC address of a device.
+  DeviceInfo *device_info_;
+  // Callback methods to call when ActiveLinkMonitor completes a cycle.
+  FailureCallback failure_callback_;
+  SuccessCallback success_callback_;
+  // The MAC address of device associated with this connection.
+  ByteString local_mac_address_;
+  // The MAC address of the default gateway.
+  ByteString gateway_mac_address_;
+  // ArpClient instance used for performing link tests.
+  std::unique_ptr<ArpClient> arp_client_;
+
+  // How frequently we send an ARP request. This is also the timeout
+  // for a pending request.
+  int test_period_milliseconds_;
+  // The number of consecutive times we have failed in receiving
+  // responses to broadcast ARP requests.
+  int broadcast_failure_count_;
+  // The number of consecutive times we have failed in receiving
+  // responses to unicast ARP requests.
+  int unicast_failure_count_;
+  // The number of consecutive times we have succeeded in receiving
+  // responses to broadcast ARP requests.
+  int broadcast_success_count_;
+  // The number of consecutive times we have succeeded in receiving
+  // responses to unicast ARP requests.
+  int unicast_success_count_;
+
+  // Whether this iteration of the test was a unicast request
+  // to the gateway instead of broadcast.  The active link monitor
+  // alternates between unicast and broadcast requests so that
+  // both types of network traffic is monitored.
+  bool is_unicast_;
+
+  // Whether we have observed that the gateway reliably responds
+  // to unicast ARP requests.
+  bool gateway_supports_unicast_arp_;
+
+  // Number of response samples received in our rolling averge.
+  int response_sample_count_;
+  // The sum of response samples in our rolling average.
+  int response_sample_bucket_;
+
+  // IOCallback that fires when the socket associated with our ArpClient
+  // has a packet to be received.  Calls ReceiveResponse().
+  std::unique_ptr<IOHandler> receive_response_handler_;
+  // Callback method used for periodic transmission of ARP requests.
+  // When the timer expires this will call SendRequest() through the
+  // void callback function SendRequestTask().
+  base::CancelableClosure send_request_callback_;
+
+  // The time at which the last ARP request was sent.
+  struct timeval sent_request_at_;
+  // Time instance for performing GetTimeMonotonic().
+  Time *time_;
+
+  DISALLOW_COPY_AND_ASSIGN(ActiveLinkMonitor);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_ACTIVE_LINK_MONITOR_H_
diff --git a/active_link_monitor_unittest.cc b/active_link_monitor_unittest.cc
new file mode 100644
index 0000000..a084243
--- /dev/null
+++ b/active_link_monitor_unittest.cc
@@ -0,0 +1,646 @@
+// Copyright 2015 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/active_link_monitor.h"
+
+#include <net/if_arp.h>
+
+#include <string>
+
+#include <base/bind.h>
+#include <gtest/gtest.h>
+
+#include "shill/arp_client_test_helper.h"
+#include "shill/arp_packet.h"
+#include "shill/logging.h"
+#include "shill/mock_arp_client.h"
+#include "shill/mock_connection.h"
+#include "shill/mock_control.h"
+#include "shill/mock_device_info.h"
+#include "shill/mock_event_dispatcher.h"
+#include "shill/mock_log.h"
+#include "shill/mock_metrics.h"
+#include "shill/net/byte_string.h"
+#include "shill/net/ip_address.h"
+#include "shill/net/mock_sockets.h"
+#include "shill/net/mock_time.h"
+
+using base::Bind;
+using base::Unretained;
+using std::string;
+using testing::_;
+using testing::AnyNumber;
+using testing::HasSubstr;
+using testing::Invoke;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Return;
+using testing::ReturnRef;
+using testing::SetArgumentPointee;
+using testing::StrictMock;
+using testing::Test;
+
+namespace shill {
+
+namespace {
+const char kInterfaceName[] = "int0";
+const char kLocalIPAddress[] = "10.0.1.1";
+const uint8_t kLocalMACAddress[] = { 0, 1, 2, 3, 4, 5 };
+const char kRemoteIPAddress[] = "10.0.1.2";
+const uint8_t kRemoteMACAddress[] = { 6, 7, 8, 9, 10, 11 };
+const char kDBusPath[] = "/dbus/path";
+}  // namespace
+
+
+class ActiveLinkMonitorObserver {
+ public:
+  ActiveLinkMonitorObserver()
+      : failure_callback_(
+            Bind(&ActiveLinkMonitorObserver::OnFailureCallback,
+                 Unretained(this))),
+        success_callback_(
+            Bind(&ActiveLinkMonitorObserver::OnSuccessCallback,
+                 Unretained(this))) {}
+  virtual ~ActiveLinkMonitorObserver() {}
+
+  MOCK_METHOD3(OnFailureCallback,
+               void(Metrics::LinkMonitorFailure failrue_code,
+                    int broadcast_failure_count,
+                    int unicast_failure_count));
+  MOCK_METHOD0(OnSuccessCallback, void());
+
+  const ActiveLinkMonitor::FailureCallback failure_callback() {
+    return failure_callback_;
+  }
+
+  const ActiveLinkMonitor::SuccessCallback success_callback() {
+    return success_callback_;
+  }
+
+ private:
+  ActiveLinkMonitor::FailureCallback failure_callback_;
+  ActiveLinkMonitor::SuccessCallback success_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(ActiveLinkMonitorObserver);
+};
+
+MATCHER_P4(IsArpRequest, local_ip, remote_ip, local_mac, remote_mac, "") {
+  if (local_ip.Equals(arg.local_ip_address()) &&
+      remote_ip.Equals(arg.remote_ip_address()) &&
+      local_mac.Equals(arg.local_mac_address()) &&
+      remote_mac.Equals(arg.remote_mac_address()))
+    return true;
+
+  if (!local_ip.Equals(arg.local_ip_address())) {
+    *result_listener << "Local IP '" << arg.local_ip_address().ToString()
+                     << "' (wanted '" << local_ip.ToString() << "').";
+  }
+
+  if (!remote_ip.Equals(arg.remote_ip_address())) {
+    *result_listener << "Remote IP '" << arg.remote_ip_address().ToString()
+                     << "' (wanted '" << remote_ip.ToString() << "').";
+  }
+
+  if (!local_mac.Equals(arg.local_mac_address())) {
+    *result_listener << "Local MAC '" << arg.local_mac_address().HexEncode()
+                     << "' (wanted " << local_mac.HexEncode() << ")'.";
+  }
+
+  if (!remote_mac.Equals(arg.remote_mac_address())) {
+    *result_listener << "Remote MAC '" << arg.remote_mac_address().HexEncode()
+                     << "' (wanted " << remote_mac.HexEncode() << ")'.";
+  }
+
+  return false;
+}
+
+class ActiveLinkMonitorTest : public Test {
+ public:
+  ActiveLinkMonitorTest()
+      : metrics_(&dispatcher_),
+        device_info_(&control_, nullptr, nullptr, nullptr),
+        connection_(new StrictMock<MockConnection>(&device_info_)),
+        client_(new MockArpClient()),
+        client_test_helper_(client_),
+        gateway_ip_(IPAddress::kFamilyIPv4),
+        local_ip_(IPAddress::kFamilyIPv4),
+        gateway_mac_(kRemoteMACAddress, arraysize(kRemoteMACAddress)),
+        local_mac_(kLocalMACAddress, arraysize(kLocalMACAddress)),
+        zero_mac_(arraysize(kLocalMACAddress)),
+        link_scope_logging_was_enabled_(false),
+        interface_name_(kInterfaceName),
+        monitor_(connection_,
+                 &dispatcher_,
+                 &metrics_,
+                 &device_info_,
+                 observer_.failure_callback(),
+                 observer_.success_callback()) {}
+  virtual ~ActiveLinkMonitorTest() {}
+
+  virtual void SetUp() {
+    link_scope_logging_was_enabled_ = SLOG_IS_ON(Link, 0);
+    if (!link_scope_logging_was_enabled_) {
+      ScopeLogger::GetInstance()->EnableScopesByName("link");
+      ScopeLogger::GetInstance()->set_verbose_level(4);
+    }
+    monitor_.arp_client_.reset(client_);
+    monitor_.time_ = &time_;
+    time_val_.tv_sec = 0;
+    time_val_.tv_usec = 0;
+    EXPECT_CALL(time_, GetTimeMonotonic(_))
+        .WillRepeatedly(DoAll(SetArgumentPointee<0>(time_val_), Return(0)));
+    EXPECT_TRUE(local_ip_.SetAddressFromString(kLocalIPAddress));
+    EXPECT_CALL(*connection_, local()).WillRepeatedly(ReturnRef(local_ip_));
+    EXPECT_TRUE(gateway_ip_.SetAddressFromString(kRemoteIPAddress));
+    EXPECT_CALL(*connection_, gateway()).WillRepeatedly(ReturnRef(gateway_ip_));
+    EXPECT_CALL(*connection_, technology())
+        .WillRepeatedly(Return(Technology::kEthernet));
+    EXPECT_CALL(*connection_, ipconfig_rpc_identifier())
+        .WillRepeatedly(testing::ReturnPointee(&kDBusPath));
+    EXPECT_CALL(*connection_, interface_name())
+        .WillRepeatedly(ReturnRef(interface_name_));
+  }
+
+  virtual void TearDown() {
+    if (!link_scope_logging_was_enabled_) {
+      ScopeLogger::GetInstance()->EnableScopesByName("-link");
+      ScopeLogger::GetInstance()->set_verbose_level(0);
+    }
+  }
+
+  void AdvanceTime(int time_ms) {
+    struct timeval adv_time = {
+      static_cast<time_t>(time_ms/1000),
+      static_cast<time_t>((time_ms % 1000) * 1000) };
+    timeradd(&time_val_, &adv_time, &time_val_);
+    EXPECT_CALL(time_, GetTimeMonotonic(_))
+        .WillRepeatedly(DoAll(SetArgumentPointee<0>(time_val_), Return(0)));
+  }
+
+  string HardwareAddressToString(const ByteString &address) {
+    return ActiveLinkMonitor::HardwareAddressToString(address);
+  }
+
+ protected:
+  void ExpectReset() {
+    EXPECT_FALSE(monitor_.GetResponseTimeMilliseconds());
+    EXPECT_TRUE(GetSendRequestCallback().IsCancelled());
+    EXPECT_EQ(0, GetBroadcastFailureCount());
+    EXPECT_EQ(0, GetUnicastFailureCount());
+    EXPECT_EQ(0, GetBroadcastSuccessCount());
+    EXPECT_EQ(0, GetUnicastSuccessCount());
+    EXPECT_FALSE(IsUnicast());
+    EXPECT_FALSE(GatewaySupportsUnicastArp());
+  }
+  void TriggerRequestTimer() {
+    GetSendRequestCallback().callback().Run();
+  }
+  const base::CancelableClosure &GetSendRequestCallback() {
+    return monitor_.send_request_callback_;
+  }
+  int GetBroadcastFailureCount() {
+    return monitor_.broadcast_failure_count_;
+  }
+  int GetUnicastFailureCount() {
+    return monitor_.unicast_failure_count_;
+  }
+  int GetBroadcastSuccessCount() {
+    return monitor_.broadcast_success_count_;
+  }
+  int GetUnicastSuccessCount() {
+    return monitor_.unicast_success_count_;
+  }
+  bool IsUnicast() { return monitor_.is_unicast_; }
+  bool GatewaySupportsUnicastArp() {
+    return monitor_.gateway_supports_unicast_arp_;
+  }
+  int GetCurrentTestPeriodMilliseconds() {
+    return monitor_.test_period_milliseconds_;
+  }
+  int GetDefaultTestPeriodMilliseconds() {
+    return ActiveLinkMonitor::kDefaultTestPeriodMilliseconds;
+  }
+  size_t GetFailureThreshold() {
+    return ActiveLinkMonitor::kFailureThreshold;
+  }
+  size_t GetUnicastReplyReliabilityThreshold() {
+    return ActiveLinkMonitor::kUnicastReplyReliabilityThreshold;
+  }
+  int GetFastTestPeriodMilliseconds() {
+    return ActiveLinkMonitor::kFastTestPeriodMilliseconds;
+  }
+  int GetMaxResponseSampleFilterDepth() {
+    return ActiveLinkMonitor::kMaxResponseSampleFilterDepth;
+  }
+  void ExpectTransmit(bool is_unicast, int transmit_period_milliseconds) {
+    const ByteString &destination_mac = is_unicast ? gateway_mac_ : zero_mac_;
+    EXPECT_CALL(*client_, TransmitRequest(
+        IsArpRequest(local_ip_, gateway_ip_, local_mac_, destination_mac)))
+        .WillOnce(Return(true));
+    EXPECT_CALL(dispatcher_,
+                PostDelayedTask(_, transmit_period_milliseconds));
+  }
+  void SendNextRequest() {
+    EXPECT_CALL(*client_, TransmitRequest(_)).WillOnce(Return(true));
+    EXPECT_CALL(dispatcher_,
+                PostDelayedTask(_, GetCurrentTestPeriodMilliseconds()));
+    TriggerRequestTimer();
+  }
+  void ExpectNoTransmit() {
+    EXPECT_CALL(*client_, TransmitRequest(_)).Times(0);
+  }
+  void StartMonitor() {
+    EXPECT_CALL(device_info_, GetMACAddress(0, _))
+        .WillOnce(DoAll(SetArgumentPointee<1>(local_mac_), Return(true)));
+    EXPECT_CALL(*client_, StartReplyListener()).WillOnce(Return(true));
+    EXPECT_CALL(dispatcher_, PostTask(_)).Times(1);
+    EXPECT_TRUE(monitor_.Start(
+        ActiveLinkMonitor::kDefaultTestPeriodMilliseconds));
+    EXPECT_FALSE(GetSendRequestCallback().IsCancelled());
+  }
+  void ReceiveResponse(uint16_t operation,
+                       const IPAddress &local_ip,
+                       const ByteString &local_mac,
+                       const IPAddress &remote_ip,
+                       const ByteString &remote_mac) {
+    client_test_helper_.GeneratePacket(operation,
+                                       local_ip,
+                                       local_mac,
+                                       remote_ip,
+                                       remote_mac);
+    monitor_.ReceiveResponse(0);
+  }
+  void ReceiveCorrectResponse() {
+    ReceiveResponse(ARPOP_REPLY, gateway_ip_, gateway_mac_,
+                    local_ip_, local_mac_);
+  }
+  void ReceiveReplyAndRestartMonitorCycle() {
+    EXPECT_CALL(observer_, OnSuccessCallback()).Times(1);
+    ReceiveCorrectResponse();
+    Mock::VerifyAndClearExpectations(&observer_);
+    StartMonitor();
+  }
+  void RunUnicastResponseCycle(int cycle_count,
+                               bool should_respond_to_unicast_probes,
+                               bool should_count_failures) {
+    // This method expects the ActiveLinkMonitor to be in a state where it
+    // is waiting for a broadcast response.  It also returns with the
+    // ActiveLinkMonitor in the same state.
+    // Successful receptions.
+    EXPECT_CALL(metrics_, SendToUMA(
+        HasSubstr("LinkMonitorResponseTimeSample"), 0, _, _, _))
+        .Times(cycle_count * (should_respond_to_unicast_probes ? 2 : 1));
+    // Unsuccessful unicast receptions.
+    EXPECT_CALL(metrics_, SendToUMA(
+        HasSubstr("LinkMonitorResponseTimeSample"),
+        GetDefaultTestPeriodMilliseconds(),
+        _, _, _)).Times(cycle_count *
+                        (should_respond_to_unicast_probes ? 0 : 1));
+
+    // Account for any successes / failures before we started.
+    int expected_broadcast_success_count = GetBroadcastSuccessCount();
+    int expected_unicast_success_count = GetUnicastSuccessCount();
+    int expected_unicast_failure_count = GetUnicastFailureCount();
+
+    LOG(INFO) << "RunUnicastResponseCycle: " << cycle_count;
+
+    for (int i = 0; i < cycle_count; ++i) {
+      // Respond to the pending broadcast request.
+      ReceiveReplyAndRestartMonitorCycle();
+
+      // Unicast ARP.
+      ExpectTransmit(true, GetDefaultTestPeriodMilliseconds());
+      TriggerRequestTimer();
+      if (should_respond_to_unicast_probes) {
+        ReceiveReplyAndRestartMonitorCycle();
+      }
+
+      // Initiate broadcast ARP.
+      ExpectTransmit(false, GetDefaultTestPeriodMilliseconds());
+      TriggerRequestTimer();
+
+      ++expected_broadcast_success_count;
+      if (should_respond_to_unicast_probes) {
+        ++expected_unicast_success_count;
+        expected_unicast_failure_count = 0;
+      } else {
+        if (should_count_failures) {
+          ++expected_unicast_failure_count;
+        }
+        expected_unicast_success_count = 0;
+      }
+      EXPECT_EQ(expected_unicast_failure_count, GetUnicastFailureCount());
+      EXPECT_EQ(expected_unicast_success_count, GetUnicastSuccessCount());
+      EXPECT_EQ(0, GetBroadcastFailureCount());
+      EXPECT_EQ(expected_broadcast_success_count, GetBroadcastSuccessCount());
+    }
+  }
+
+  MockEventDispatcher dispatcher_;
+  StrictMock<MockMetrics> metrics_;
+  MockControl control_;
+  NiceMock<MockDeviceInfo> device_info_;
+  scoped_refptr<MockConnection> connection_;
+  MockTime time_;
+  struct timeval time_val_;
+  // This is owned by the LinkMonitor, and only tracked here for EXPECT*().
+  MockArpClient *client_;
+  ArpClientTestHelper client_test_helper_;
+  ActiveLinkMonitorObserver observer_;
+  IPAddress gateway_ip_;
+  IPAddress local_ip_;
+  ByteString gateway_mac_;
+  ByteString local_mac_;
+  ByteString zero_mac_;
+  bool link_scope_logging_was_enabled_;
+  const string interface_name_;
+  ActiveLinkMonitor monitor_;
+};
+
+
+TEST_F(ActiveLinkMonitorTest, Constructor) {
+  ExpectReset();
+}
+
+TEST_F(ActiveLinkMonitorTest, StartFailedGetMACAddress) {
+  ScopedMockLog log;
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("Could not get local MAC address"))).Times(1);
+  EXPECT_CALL(device_info_, GetMACAddress(0, _)).WillOnce(Return(false));
+  EXPECT_CALL(metrics_, SendEnumToUMA(
+      HasSubstr("LinkMonitorFailure"), Metrics::kLinkMonitorMacAddressNotFound,
+      _));
+  EXPECT_CALL(*client_, StartReplyListener()).Times(0);
+  EXPECT_FALSE(monitor_.Start(
+      ActiveLinkMonitor::kDefaultTestPeriodMilliseconds));
+  ExpectReset();
+}
+
+TEST_F(ActiveLinkMonitorTest, StartFailedArpClient) {
+  ScopedMockLog log;
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("Failed to start ARP client"))).Times(1);
+  EXPECT_CALL(metrics_, SendEnumToUMA(
+      HasSubstr("LinkMonitorFailure"), Metrics::kLinkMonitorClientStartFailure,
+      _));
+  EXPECT_CALL(device_info_, GetMACAddress(0, _)).WillOnce(Return(true));
+  EXPECT_CALL(*client_, StartReplyListener()).WillOnce(Return(false));
+  EXPECT_FALSE(monitor_.Start(
+      ActiveLinkMonitor::kDefaultTestPeriodMilliseconds));
+  ExpectReset();
+}
+
+TEST_F(ActiveLinkMonitorTest, StartSuccess) {
+  StartMonitor();
+}
+
+TEST_F(ActiveLinkMonitorTest, Stop) {
+  StartMonitor();
+  EXPECT_CALL(*client_, Stop()).Times(1);
+  monitor_.Stop();
+  ExpectReset();
+  Mock::VerifyAndClearExpectations(client_);
+}
+
+TEST_F(ActiveLinkMonitorTest, ReplyReception) {
+  StartMonitor();
+  const int kResponseTime = 1234;
+  AdvanceTime(kResponseTime);
+  ScopedMockLog log;
+
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+  EXPECT_CALL(log, Log(_, _, HasSubstr("not for our IP"))).Times(1);
+  ReceiveResponse(ARPOP_REPLY, gateway_ip_, gateway_mac_,
+                  gateway_ip_, local_mac_);
+  Mock::VerifyAndClearExpectations(&log);
+
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+  EXPECT_CALL(log, Log(_, _, HasSubstr("not for our MAC"))).Times(1);
+  ReceiveResponse(ARPOP_REPLY, gateway_ip_, gateway_mac_,
+                  local_ip_, gateway_mac_);
+  Mock::VerifyAndClearExpectations(&log);
+
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+  EXPECT_CALL(log, Log(_, _, HasSubstr("not from the gateway"))).Times(1);
+  ReceiveResponse(ARPOP_REPLY, local_ip_, gateway_mac_, local_ip_, local_mac_);
+  Mock::VerifyAndClearExpectations(&log);
+
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+  EXPECT_CALL(log, Log(_, _, HasSubstr("This is not a reply packet"))).Times(1);
+  ReceiveResponse(ARPOP_REQUEST, gateway_ip_, gateway_mac_,
+                  local_ip_, local_mac_);
+  Mock::VerifyAndClearExpectations(&log);
+
+  EXPECT_FALSE(monitor_.GetResponseTimeMilliseconds());
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+  EXPECT_CALL(log, Log(_, _, HasSubstr("Found gateway"))).Times(1);
+  EXPECT_CALL(metrics_, SendToUMA(
+      HasSubstr("LinkMonitorResponseTimeSample"), kResponseTime,
+       _, _, _)).Times(1);
+  EXPECT_CALL(*client_, Stop()).Times(1);
+  EXPECT_CALL(observer_, OnSuccessCallback()).Times(1);
+  ReceiveCorrectResponse();
+  EXPECT_EQ(kResponseTime, monitor_.GetResponseTimeMilliseconds());
+  EXPECT_TRUE(IsUnicast());
+  Mock::VerifyAndClearExpectations(client_);
+}
+
+TEST_F(ActiveLinkMonitorTest, TimeoutBroadcast) {
+  EXPECT_CALL(metrics_, SendToUMA(
+      HasSubstr("LinkMonitorResponseTimeSample"),
+      GetDefaultTestPeriodMilliseconds(),
+      _, _, _)).Times(GetFailureThreshold());
+  StartMonitor();
+  // This value doesn't match real life (the timer in this scenario
+  // should advance by LinkMonitor::kDefaultTestPeriodMilliseconds),
+  // but this demonstrates the LinkMonitorSecondsToFailure independent
+  // from the response-time figures.
+  const int kTimeIncrement = 1000;
+  // Transmit initial request.
+  ExpectTransmit(false, GetDefaultTestPeriodMilliseconds());
+  AdvanceTime(kTimeIncrement);
+  TriggerRequestTimer();
+  for (size_t i = 1; i < GetFailureThreshold(); ++i) {
+    ExpectTransmit(false, GetDefaultTestPeriodMilliseconds());
+    AdvanceTime(kTimeIncrement);
+    TriggerRequestTimer();
+    EXPECT_FALSE(IsUnicast());
+    EXPECT_EQ(i, GetBroadcastFailureCount());
+    EXPECT_EQ(0, GetUnicastFailureCount());
+    EXPECT_EQ(0, GetBroadcastSuccessCount());
+    EXPECT_EQ(0, GetUnicastSuccessCount());
+    EXPECT_EQ(GetDefaultTestPeriodMilliseconds(),
+              monitor_.GetResponseTimeMilliseconds());
+  }
+  ScopedMockLog log;
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("monitor has reached the failure threshold"))).Times(1);
+  EXPECT_CALL(observer_,
+              OnFailureCallback(Metrics::kLinkMonitorFailureThresholdReached,
+                                GetFailureThreshold(),
+                                0)).Times(1);
+  EXPECT_FALSE(GetSendRequestCallback().IsCancelled());
+  // Transmit final request.
+  ExpectNoTransmit();
+  AdvanceTime(kTimeIncrement);
+  TriggerRequestTimer();
+  ExpectReset();
+}
+
+TEST_F(ActiveLinkMonitorTest, TimeoutUnicast) {
+  StartMonitor();
+
+  // Setup expectation for Time::GetTimeMonotonic.
+  const int kTimeIncrement = 1000;
+  AdvanceTime(kTimeIncrement);
+  // Initiate a broadcast ARP.
+  ExpectTransmit(false, GetDefaultTestPeriodMilliseconds());
+  TriggerRequestTimer();
+
+  ScopedMockLog log;
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("monitor has reached the failure threshold"))).Times(0);
+
+  // Unicast failures should not cause LinkMonitor errors if we haven't
+  // noted the gateway as reliably replying to unicast ARP messages.  Test
+  // this by doing threshold - 1 successful unicast responses, followed
+  // by a ton of unicast failures.
+  // Initiate broadcast ARP.
+  RunUnicastResponseCycle(GetUnicastReplyReliabilityThreshold() - 1,
+                          true, false);
+  EXPECT_EQ(GetUnicastReplyReliabilityThreshold() - 1,
+            GetUnicastSuccessCount());
+  RunUnicastResponseCycle(GetFailureThreshold() +
+                          GetUnicastReplyReliabilityThreshold(), false, false);
+  EXPECT_FALSE(GetSendRequestCallback().IsCancelled());
+  EXPECT_FALSE(GatewaySupportsUnicastArp());
+  EXPECT_EQ(0, GetUnicastSuccessCount());
+  EXPECT_EQ(0, GetUnicastFailureCount());
+
+  // Cross the the unicast reliability threshold.
+  RunUnicastResponseCycle(GetUnicastReplyReliabilityThreshold() - 1,
+                          true, false);
+  EXPECT_CALL(log,
+      Log(_, _, HasSubstr("Unicast failures will now count")));
+  EXPECT_FALSE(GatewaySupportsUnicastArp());
+  RunUnicastResponseCycle(1, true, false);
+  EXPECT_TRUE(GatewaySupportsUnicastArp());
+
+  // Induce one less failures than will cause a link monitor failure, and
+  // confirm that these failures are counted.
+  RunUnicastResponseCycle(GetFailureThreshold() - 1, false, true);
+  EXPECT_EQ(GetFailureThreshold() - 1, GetUnicastFailureCount());
+
+  Mock::VerifyAndClearExpectations(&log);
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+
+  // Induce a final broadcast success followed by a unicast failure.
+  EXPECT_CALL(metrics_, SendToUMA(
+      HasSubstr("LinkMonitorResponseTimeSample"), 0, _, _, _));
+  ReceiveReplyAndRestartMonitorCycle();
+
+  ExpectTransmit(true, GetDefaultTestPeriodMilliseconds());
+  TriggerRequestTimer();
+  EXPECT_FALSE(GetSendRequestCallback().IsCancelled());
+
+  EXPECT_CALL(metrics_, SendToUMA(
+      HasSubstr("LinkMonitorResponseTimeSample"),
+      GetDefaultTestPeriodMilliseconds(),
+      _, _, _));
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("monitor has reached the failure threshold"))).Times(1);
+  EXPECT_CALL(observer_,
+              OnFailureCallback(Metrics::kLinkMonitorFailureThresholdReached,
+                                0,
+                                GetFailureThreshold())).Times(1);
+  ExpectNoTransmit();
+  TriggerRequestTimer();
+  ExpectReset();
+}
+
+TEST_F(ActiveLinkMonitorTest, Average) {
+  const int kSamples[] = { 200, 950, 1200, 4096, 5000,
+                           86, 120, 3060, 842, 750 };
+  const size_t filter_depth = GetMaxResponseSampleFilterDepth();
+  EXPECT_CALL(metrics_, SendToUMA(
+      HasSubstr("LinkMonitorResponseTimeSample"), _, _, _, _))
+      .Times(arraysize(kSamples));
+  ASSERT_GT(arraysize(kSamples), filter_depth);
+  StartMonitor();
+  size_t i = 0;
+  int sum = 0;
+  for (; i < filter_depth; ++i) {
+    AdvanceTime(kSamples[i]);
+    ReceiveReplyAndRestartMonitorCycle();
+    sum += kSamples[i];
+    EXPECT_EQ(sum / (i + 1), monitor_.GetResponseTimeMilliseconds());
+    SendNextRequest();
+  }
+  for (; i < arraysize(kSamples); ++i) {
+    AdvanceTime(kSamples[i]);
+    ReceiveReplyAndRestartMonitorCycle();
+    sum = (sum + kSamples[i]) * filter_depth / (filter_depth + 1);
+    EXPECT_EQ(sum / filter_depth, monitor_.GetResponseTimeMilliseconds());
+    SendNextRequest();
+  }
+}
+
+TEST_F(ActiveLinkMonitorTest, ImpulseResponse) {
+  const int kNormalValue = 50;
+  const int kExceptionalValue = 5000;
+  const int filter_depth = GetMaxResponseSampleFilterDepth();
+  EXPECT_CALL(metrics_, SendToUMA(
+      HasSubstr("LinkMonitorResponseTimeSample"), _, _, _, _))
+      .Times(AnyNumber());
+  StartMonitor();
+  for (int i = 0; i < filter_depth * 2; ++i) {
+    AdvanceTime(kNormalValue);
+    ReceiveReplyAndRestartMonitorCycle();
+    EXPECT_EQ(kNormalValue, monitor_.GetResponseTimeMilliseconds());
+    SendNextRequest();
+  }
+  AdvanceTime(kExceptionalValue);
+  ReceiveReplyAndRestartMonitorCycle();
+  // Our expectation is that an impulse input will be a
+  // impulse_height / (filter_depth + 1) increase to the running average.
+  int expected_impulse_response =
+      kNormalValue + (kExceptionalValue - kNormalValue) / (filter_depth + 1);
+  EXPECT_EQ(expected_impulse_response, monitor_.GetResponseTimeMilliseconds());
+  SendNextRequest();
+
+  // From here, if we end up continuing to receive normal values, our
+  // running average should decay backwards to the normal value.
+  const int failsafe = 100;
+  int last_value = monitor_.GetResponseTimeMilliseconds();
+  for (int i = 0; i < failsafe && last_value != kNormalValue; ++i) {
+    AdvanceTime(kNormalValue);
+    ReceiveReplyAndRestartMonitorCycle();
+    // We should advance monotonically (but not necessarily linearly)
+    // back towards the normal value.
+    EXPECT_GE(last_value, monitor_.GetResponseTimeMilliseconds());
+    SendNextRequest();
+    last_value = monitor_.GetResponseTimeMilliseconds();
+  }
+  EXPECT_EQ(kNormalValue, last_value);
+}
+
+TEST_F(ActiveLinkMonitorTest, HardwareAddressToString) {
+  const uint8_t address0[] = { 0, 1, 2, 3, 4, 5 };
+  EXPECT_EQ("00:01:02:03:04:05",
+            HardwareAddressToString(ByteString(address0, arraysize(address0))));
+  const uint8_t address1[] = { 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd };
+  EXPECT_EQ("88:99:aa:bb:cc:dd",
+            HardwareAddressToString(ByteString(address1, arraysize(address1))));
+}
+
+}  // namespace shill
diff --git a/mock_active_link_monitor.cc b/mock_active_link_monitor.cc
new file mode 100644
index 0000000..f256ff3
--- /dev/null
+++ b/mock_active_link_monitor.cc
@@ -0,0 +1,17 @@
+// Copyright 2015 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/mock_active_link_monitor.h"
+
+#include "shill/connection.h"
+
+namespace shill {
+
+MockActiveLinkMonitor::MockActiveLinkMonitor()
+    : ActiveLinkMonitor(nullptr, nullptr, nullptr, nullptr, FailureCallback(),
+                        SuccessCallback()) {}
+
+MockActiveLinkMonitor::~MockActiveLinkMonitor() {}
+
+}  // namespace shill
diff --git a/mock_active_link_monitor.h b/mock_active_link_monitor.h
new file mode 100644
index 0000000..96e8b02
--- /dev/null
+++ b/mock_active_link_monitor.h
@@ -0,0 +1,29 @@
+// Copyright 2015 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_MOCK_ACTIVE_LINK_MONITOR_H_
+#define SHILL_MOCK_ACTIVE_LINK_MONITOR_H_
+
+#include "shill/active_link_monitor.h"
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+namespace shill {
+
+class MockActiveLinkMonitor : public ActiveLinkMonitor {
+ public:
+  MockActiveLinkMonitor();
+  ~MockActiveLinkMonitor() override;
+
+  MOCK_METHOD1(Start, bool(int));
+  MOCK_METHOD0(Stop, void());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockActiveLinkMonitor);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_MOCK_ACTIVE_LINK_MONITOR_H_
diff --git a/shill.gyp b/shill.gyp
index 66f3008..f1b9b71 100644
--- a/shill.gyp
+++ b/shill.gyp
@@ -289,6 +289,7 @@
         }],
       ],
       'sources': [
+        'active_link_monitor.cc',
         'arp_client.cc',
         'arp_packet.cc',
         'async_connection.cc',
@@ -529,6 +530,7 @@
             'SYSROOT="<(sysroot)"',
           ],
           'sources': [
+            'active_link_monitor_unittest.cc',
             'arp_client_test_helper.cc',
             'arp_client_unittest.cc',
             'arp_packet_unittest.cc',
@@ -582,6 +584,7 @@
             'link_monitor_unittest.cc',
             'manager_unittest.cc',
             'metrics_unittest.cc',
+            'mock_active_link_monitor.cc',
             'mock_adaptors.cc',
             'mock_ares.cc',
             'mock_arp_client.cc',