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_;