dhcp client: use arp to find ip address collisions

This allows the dhcp_client to check if the offered ip
address from dhcp server is being used by others.
While there, this also rename ResetStateVariables() to
ResetState().

Bug: 25642025
TEST=compile, and test using python scripts

Change-Id: Ib5d982943d0be47178039c7a6f36b44eac9133f5
diff --git a/dhcpv4.cc b/dhcpv4.cc
index f213074..c5a3d3a 100644
--- a/dhcpv4.cc
+++ b/dhcpv4.cc
@@ -36,6 +36,8 @@
 #include "dhcp_client/dhcp_options.h"
 #include "dhcp_client/file_io.h"
 #include "dhcp_client/service_adaptor_interface.h"
+#include "shill/net/arp_packet.h"
+#include "shill/net/ip_address.h"
 
 using base::Bind;
 using base::Unretained;
@@ -70,6 +72,9 @@
     kDHCPOptionRenewalTime,
     kDHCPOptionRebindingTime
 };
+const uint8_t kZeroHardwareAddress[] =
+    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const int kArpProbeReplyTimeoutSeconds = 1;
 
 // Socket filter for dhcp packet.
 const sock_filter dhcp_bpf_filter[] = {
@@ -126,6 +131,7 @@
       request_hostname_(request_hostname),
       arp_gateway_(arp_gateway),
       unicast_arp_(unicast_arp),
+      arp_client_(new shill::ArpClient(interface_index)),
       event_dispatcher_(event_dispatcher),
       io_handler_factory_(
           IOHandlerFactoryContainer::GetInstance()->GetIOHandlerFactory()),
@@ -134,7 +140,7 @@
       socket_(kInvalidSocketDescriptor),
       sockets_(new shill::Sockets()),
       random_engine_(time(nullptr)) {
-  ResetStateVariables();
+  ResetState();
 }
 
 DHCPV4::~DHCPV4() {
@@ -143,13 +149,16 @@
   }
 }
 
-void DHCPV4::ResetStateVariables() {
+void DHCPV4::ResetState() {
   state_ = State::INIT;
   server_identifier_ = 0;
   transaction_id_ = 0;
   offered_ip_address_ = 0;
   client_ip_ = INADDR_ANY;
   server_ip_ = INADDR_BROADCAST;
+  renewal_task_callback_.Cancel();
+  rebind_task_callback_.Cancel();
+  arp_probe_reply_timeout_task_callback_.Cancel();
 }
 
 void DHCPV4::ParseRawPacket(shill::InputData* data) {
@@ -226,7 +235,7 @@
     state_ = State::INIT_REBOOT;
     LOG(INFO) << "Start from INIT_REBOOT state";
     if (!SendRequest()) {
-      ResetStateVariables();
+      ResetState();
       EmitEvent(kReasonFail);
       return false;
     }
@@ -267,6 +276,7 @@
     sockets_->Close(socket_);
     socket_ = kInvalidSocketDescriptor;
   }
+  arp_client_->Stop();
 }
 
 bool DHCPV4::CreateRawSocket() {
@@ -389,7 +399,8 @@
     return;
   }
 
-  // TODO(nywang): Validate the ip address using ARP.
+  CheckIpCollision();
+
   // TODO(nywang): Validate the gateway address using ARP.
   // Accept the ACK.
   uint32_t lease_time = msg.lease_time();
@@ -460,7 +471,7 @@
   }
 
   // Set state variables upon receiving a valid Nak.
-  ResetStateVariables();
+  ResetState();
 
   EmitEvent(kReasonNak);
 }
@@ -497,6 +508,72 @@
   return true;
 }
 
+void DHCPV4::CheckIpCollision() {
+  if (!arp_client_->StartReplyListener()) {
+    LOG(ERROR) << "Failed to start ARP client";
+    return;
+  }
+  receive_arp_response_handler_.reset(
+      io_handler_factory_->CreateIOReadyHandler(
+          arp_client_->socket(),
+          shill::IOHandler::kModeInput,
+          Bind(&DHCPV4::ArpProbeReplyReceivedTask,
+               weak_ptr_factory_.GetWeakPtr())));
+
+  // According to RFC 5227, ARP Probe uses all-zero 'sender address' to avoid
+  // polluting ARP caches. The 'target address' should be set to address being
+  // probed.
+  // RFC 5227 also states that target MAC address should be all-zero.
+  shill::ArpPacket request(shill::IPAddress(IPtoString(INADDR_ANY)),
+                           shill::IPAddress(IPtoString(offered_ip_address_)),
+                           hardware_address_,
+                           ByteString(kZeroHardwareAddress, IFHWADDRLEN));
+  LOG(INFO) << "Probing expected ip address: "
+            << IPtoString(offered_ip_address_);
+  if (!arp_client_->TransmitRequest(request)) {
+    LOG(ERROR) << "Failed to send ARP Probe request";
+    arp_client_->Stop();
+    receive_arp_response_handler_.reset();
+    return;
+  }
+  // We succeeded to send ARP probe:
+  arp_probe_reply_timeout_task_callback_.Reset(
+      Bind(&DHCPV4::ArpProbeReplyTimeoutTask, weak_ptr_factory_.GetWeakPtr()));
+  event_dispatcher_->PostDelayedTask(
+      arp_probe_reply_timeout_task_callback_.callback(),
+      1000 * kArpProbeReplyTimeoutSeconds);
+}
+
+void DHCPV4::ArpProbeReplyReceivedTask(int fd) {
+  shill::ArpPacket packet;
+  shill::ByteString sender;
+  if (!arp_client_->ReceivePacket(&packet, &sender)) {
+    return;
+  }
+  // According to RFC 5227, we only check the sender's ip address.
+  shill::IPAddress offered_ip_address =
+      shill::IPAddress(IPtoString(offered_ip_address_));
+  if (!offered_ip_address.Equals(packet.local_ip_address())) {
+    LOG(ERROR) << "Response is not for the expecte IP address.";
+    return;
+  }
+
+  // Collision is found:
+  LOG(ERROR) << "IP Collision found. Discard the lease.";
+  arp_client_->Stop();
+  receive_arp_response_handler_.reset();
+  arp_probe_reply_timeout_task_callback_.Cancel();
+  // TODO(nywang): send DHCP_DECLINE to server.
+  ResetState();
+}
+
+void DHCPV4::ArpProbeReplyTimeoutTask() {
+  // TODO(nywang): Broadcast ARP announcement.
+  arp_client_->Stop();
+  receive_arp_response_handler_.reset();
+  arp_probe_reply_timeout_task_callback_.Cancel();
+}
+
 bool DHCPV4::SendRequest() {
   LOG(INFO) << __func__;
   DHCPMessage message;
@@ -555,7 +632,7 @@
     return false;
   }
   // Set state variables upon success.
-  ResetStateVariables();
+  ResetState();
   return true;
 }
 
diff --git a/dhcpv4.h b/dhcpv4.h
index e8083f5..0d29467 100644
--- a/dhcpv4.h
+++ b/dhcpv4.h
@@ -31,6 +31,7 @@
 #include "dhcp_client/dhcp.h"
 #include "dhcp_client/dhcp_message.h"
 #include "dhcp_client/event_dispatcher_interface.h"
+#include "shill/net/arp_client.h"
 
 namespace dhcp_client {
 
@@ -54,6 +55,9 @@
   void Stop();
 
  private:
+  void ArpProbeReplyReceivedTask(int fd);
+  void ArpProbeReplyTimeoutTask();
+  void CheckIpCollision();
   bool CreateRawSocket();
   bool MakeRawPacket(const DHCPMessage& message, shill::ByteString* buffer);
   void OnReadError(const std::string& error_msg);
@@ -68,7 +72,7 @@
   // Return -1 if any header is invalid.
   int ValidatePacketHeader(const unsigned char* buffer, size_t len);
   bool ValidateOptions(const DHCPMessage& msg);
-  void ResetStateVariables();
+  void ResetState();
 
   // Util functions.
   uint32_t MasktoCIDR(uint32_t subnet_mask);
@@ -106,9 +110,11 @@
   // Enable unicast ARP on renew.
   bool unicast_arp_;
 
+  std::unique_ptr<shill::ArpClient> arp_client_;
   EventDispatcherInterface* event_dispatcher_;
   shill::IOHandlerFactory *io_handler_factory_;
   std::unique_ptr<shill::IOHandler> input_handler_;
+  std::unique_ptr<shill::IOHandler> receive_arp_response_handler_;
   base::WeakPtrFactory<DHCPV4> weak_ptr_factory_;
 
   // DHCP state variables.
@@ -137,6 +143,7 @@
   // Timeout callbacks.
   base::CancelableClosure renewal_task_callback_;
   base::CancelableClosure rebind_task_callback_;
+  base::CancelableClosure arp_probe_reply_timeout_task_callback_;
 
   // Socket used for sending and receiving DHCP messages.
   int socket_;