shill: LinkMonitor: Schedule periodic ARP requests

The LinkMonitor performs periodic unicast and broadcast ARP requests
for a connection's gateway IP address.  It creates a weighted
average of response time, and can call a failure callback function
whenever the sum of consecutive unicast and broadcast response
failures exceed a threshold.

BUG=chromium-os:32600
TEST=Unit tests

Change-Id: Id5a66ad9ca80c51f227ef4811eb8c3885a70c1b8
Reviewed-on: https://gerrit.chromium.org/gerrit/28610
Commit-Ready: Paul Stewart <pstew@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/link_monitor.cc b/link_monitor.cc
index e9ab5b0..2485ce5 100644
--- a/link_monitor.cc
+++ b/link_monitor.cc
@@ -4,31 +4,253 @@
 
 #include "shill/link_monitor.h"
 
-#include <base/logging.h>
+#include <vector>
 
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/stringprintf.h>
+#include <base/string_util.h>
+
+#include "shill/arp_client.h"
+#include "shill/arp_packet.h"
+#include "shill/byte_string.h"
 #include "shill/connection.h"
+#include "shill/device_info.h"
 #include "shill/event_dispatcher.h"
-#include "shill/sockets.h"
+#include "shill/ip_address.h"
+#include "shill/scope_logger.h"
+#include "shill/shill_time.h"
+
+using base::Bind;
+using base::Unretained;
+using std::string;
 
 namespace shill {
 
+const unsigned int LinkMonitor::kTestPeriodMilliseconds = 5000;
+const unsigned int LinkMonitor::kFailureThreshold = 5;
+const unsigned int LinkMonitor::kMaxResponseSampleFilterDepth = 5;
+
 LinkMonitor::LinkMonitor(const ConnectionRefPtr &connection,
                          EventDispatcher *dispatcher,
+                         DeviceInfo *device_info,
                          const FailureCallback &failure_callback)
     : connection_(connection),
       dispatcher_(dispatcher),
+      device_info_(device_info),
       failure_callback_(failure_callback),
-      sockets_(new Sockets()) {}
+      broadcast_failure_count_(0),
+      unicast_failure_count_(0),
+      is_unicast_(false),
+      response_sample_count_(0),
+      response_sample_bucket_(0),
+      time_(Time::GetInstance()) {}
 
-LinkMonitor::~LinkMonitor() {}
+LinkMonitor::~LinkMonitor() {
+  Stop();
+}
 
 bool LinkMonitor::Start() {
-  NOTIMPLEMENTED();
-  return false;
+  Stop();
+
+  if (!device_info_->GetMACAddress(
+    connection_->interface_index(), &local_mac_address_)) {
+    LOG(ERROR) << "Could not get local MAC address.";
+    Stop();
+    return false;
+  }
+  gateway_mac_address_ = ByteString(local_mac_address_.GetLength());
+  send_request_callback_.Reset(
+      Bind(&LinkMonitor::SendRequestTask, Unretained(this)));
+  return SendRequest();
 }
 
 void LinkMonitor::Stop() {
-  NOTIMPLEMENTED();
+  SLOG(Link, 2) << "In " << __func__ << ".";
+  local_mac_address_.Clear();
+  gateway_mac_address_.Clear();
+  arp_client_.reset();
+  broadcast_failure_count_ = 0;
+  unicast_failure_count_ = 0;
+  is_unicast_ = false;
+  response_sample_bucket_ = 0;
+  response_sample_count_ = 0;
+  receive_response_handler_.reset();
+  send_request_callback_.Cancel();
+  timerclear(&sent_request_at_);
+}
+
+unsigned int LinkMonitor::GetResponseTimeMilliseconds() {
+  return response_sample_count_ ?
+      response_sample_bucket_ / response_sample_count_ : 0;
+}
+
+void LinkMonitor::AddResponseTimeSample(
+    unsigned int response_time_milliseconds) {
+  SLOG(Link, 2) << "In " << __func__ << " with sample "
+                << 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 LinkMonitor::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 LinkMonitor::CreateClient() {
+  arp_client_.reset(new ArpClient(connection_->interface_index()));
+
+  if (!arp_client_->Start()) {
+   return false;
+  }
+  receive_response_handler_.reset(
+    dispatcher_->CreateReadyHandler(
+        arp_client_->socket(),
+        IOHandler::kModeInput,
+        Bind(&LinkMonitor::ReceiveResponse, Unretained(this))));
+  return true;
+}
+
+bool LinkMonitor::AddMissedResponse() {
+  SLOG(Link, 2) << "In " << __func__ << ".";
+  AddResponseTimeSample(kTestPeriodMilliseconds);
+
+  if (is_unicast_) {
+    ++unicast_failure_count_;
+  } else {
+    ++broadcast_failure_count_;
+  }
+
+  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();
+    Stop();
+    return true;
+  }
+  is_unicast_ = !is_unicast_;
+  return false;
+}
+
+void LinkMonitor::ReceiveResponse(int fd) {
+  SLOG(Link, 2) << "In " << __func__ << ".";
+  ArpPacket packet;
+  ByteString sender;
+  if (!arp_client_->ReceiveReply(&packet, &sender)) {
+    return;
+  }
+
+  if (!connection_->local().Equals(packet.local_ip_address())) {
+    SLOG(Link, 4) << "Response is not for our IP address.";
+    return;
+  }
+
+  if (!local_mac_address_.Equals(packet.local_mac_address())) {
+    SLOG(Link, 4) << "Response is not for our MAC address.";
+    return;
+  }
+
+  if (!connection_->gateway().Equals(packet.remote_ip_address())) {
+    SLOG(Link, 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);
+
+  receive_response_handler_.reset();
+  arp_client_.reset();
+
+  if (is_unicast_) {
+    unicast_failure_count_ = 0;
+  } else {
+    broadcast_failure_count_ = 0;
+  }
+
+  if (!gateway_mac_address_.Equals(packet.remote_mac_address())) {
+    const ByteString &new_mac_address = packet.remote_mac_address();
+    if (gateway_mac_address_.IsZero()) {
+      SLOG(Link, 2) << "Found gateway at "
+                    << HardwareAddressToString(new_mac_address);
+    } else {
+      SLOG(Link, 2) << "Gateway MAC address changed.";
+    }
+    gateway_mac_address_ = new_mac_address;
+  }
+
+  is_unicast_ = !is_unicast_;
+}
+
+bool LinkMonitor::SendRequest() {
+  SLOG(Link, 2) << "In " << __func__ << ".";
+  if (!arp_client_.get()) {
+    if (!CreateClient()) {
+      LOG(ERROR) << "Failed to start ARP client.";
+      Stop();
+      return false;
+    }
+  } else if (AddMissedResponse()) {
+    // If an ARP client is still listening, this means we have timed
+    // out reception of the ARP reply.
+    return false;
+  } else {
+    // We already have an ArpClient instance running.  These aren't
+    // bound sockets in the conventional sense, and we cannot distinguish
+    // which request (from which trial, or even from which component
+    // in the local system) an ARP reply was sent in response to.
+    // Therefore we keep the already open ArpClient in the case of
+    // a non-fatal timeout.
+  }
+
+  ByteString destination_mac_address(gateway_mac_address_.GetLength());
+  if (gateway_mac_address_.IsZero()) {
+    // 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_;
+  }
+
+  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.";
+    Stop();
+    return false;
+  }
+
+  time_->GetTimeMonotonic(&sent_request_at_);
+
+  dispatcher_->PostDelayedTask(send_request_callback_.callback(),
+                               kTestPeriodMilliseconds);
+  return true;
+}
+
+void LinkMonitor::SendRequestTask() {
+  SendRequest();
 }
 
 }  // namespace shill