dhcp client: support unicast for renewing and release am: bb41434
am: 3851c78

* commit '3851c780756a65b4b789a71e4533d199b6b176e9':
  dhcp client: support unicast for renewing and release

Change-Id: I281c63c7c241ec7f0103dbe0a0542546ab7ce626
diff --git a/dhcpv4.cc b/dhcpv4.cc
index 90d73a5..2b0a7b4 100644
--- a/dhcpv4.cc
+++ b/dhcpv4.cc
@@ -56,7 +56,7 @@
 const uint32_t kRenewalTimePercentage = 50;
 // Rebinding time in terms of lease duration percentage.
 const uint32_t kRebindTimePercentage = 87;
-const int kInvalidSocketDescriptor = -1;
+const int kInvalidSocketDescriptor = shill::Sockets::kInvalidFileDescriptor;
 // RFC 791: the minimum value for a correct header is 20 octets.
 // The maximum value is 60 octets.
 const size_t kIPHeaderMinLength = 20;
@@ -73,8 +73,7 @@
     kDHCPOptionRenewalTime,
     kDHCPOptionRebindingTime
 };
-const uint8_t kZeroHardwareAddress[] =
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const uint8_t kZeroHardwareAddress[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
 const int kArpProbeReplyTimeoutSeconds = 1;
 
 // Socket filter for dhcp packet.
@@ -140,16 +139,15 @@
           IOHandlerFactoryContainer::GetInstance()->GetIOHandlerFactory()),
       weak_ptr_factory_(this),
       state_(State::INIT),
-      socket_(kInvalidSocketDescriptor),
+      raw_socket_(kInvalidSocketDescriptor),
+      udp_socket_(kInvalidSocketDescriptor),
       sockets_(new shill::Sockets()),
       random_engine_(time(nullptr)) {
   ResetState();
 }
 
 DHCPV4::~DHCPV4() {
-  if (socket_ != kInvalidSocketDescriptor) {
     Stop();
-  }
 }
 
 void DHCPV4::ResetState() {
@@ -231,7 +229,7 @@
     return false;
   }
   input_handler_.reset(io_handler_factory_->CreateIOInputHandler(
-      socket_,
+      raw_socket_,
       Bind(&DHCPV4::ParseRawPacket, Unretained(this)),
       Bind(&DHCPV4::OnReadError, Unretained(this))));
   if (!network_id_.empty() && ReadLease()) {
@@ -272,12 +270,19 @@
 
 void DHCPV4::Stop() {
   input_handler_.reset();
-  if (!SendRelease()) {
-    LOG(ERROR) << "Failed to send DHCP release message";
+  if (HasALease()) {
+    if (!SendRelease()) {
+      LOG(ERROR) << "Failed to send DHCP release message";
+    }
+    ResetState();
   }
-  if (socket_ != kInvalidSocketDescriptor) {
-    sockets_->Close(socket_);
-    socket_ = kInvalidSocketDescriptor;
+  if (raw_socket_ != kInvalidSocketDescriptor) {
+    sockets_->Close(raw_socket_);
+    raw_socket_ = kInvalidSocketDescriptor;
+  }
+  if (udp_socket_ != kInvalidSocketDescriptor) {
+    sockets_->Close(udp_socket_);
+    udp_socket_ = kInvalidSocketDescriptor;
   }
   arp_client_->Stop();
 }
@@ -326,7 +331,53 @@
     return false;
   }
 
-  socket_ = socket_closer.Release();
+  raw_socket_ = socket_closer.Release();
+  return true;
+}
+
+bool DHCPV4::CreateUdpSocket() {
+  // Close previous Udp Socket.
+  if (udp_socket_ != kInvalidSocketDescriptor) {
+    sockets_->Close(udp_socket_);
+    udp_socket_ = kInvalidSocketDescriptor;
+  }
+
+  int fd = sockets_->Socket(PF_INET,
+                            SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+                            IPPROTO_IP);
+  if (fd == kInvalidSocketDescriptor) {
+    PLOG(ERROR) << "Failed to create socket";
+    return false;
+  }
+  shill::ScopedSocketCloser socket_closer(sockets_.get(), fd);
+
+  if (sockets_->ReuseAddress(fd) == -1) {
+    PLOG(ERROR) << "Failed to reuse socket address";
+    return false;
+  }
+
+  if (sockets_->BindToDevice(fd, interface_name_) < 0) {
+    PLOG(ERROR) << "Failed to bind socket to device";
+    return false;
+  }
+
+  struct sockaddr_in local;
+  memset(&local, 0, sizeof(local));
+  local.sin_family = PF_INET;
+  // We do not receive packet from this socket.
+  // At this time the ip address may not be setup by shill yet.
+  // It is a safe choice to use INADDR_ANY.
+  local.sin_addr.s_addr = htonl(INADDR_ANY);
+  local.sin_port = htons(kDHCPClientPort);
+
+  if (sockets_->Bind(fd,
+                     reinterpret_cast<struct sockaddr*>(&local),
+                     sizeof(local)) < 0) {
+    PLOG(ERROR) << "Failed to bind to address";
+    return false;
+  }
+
+  udp_socket_ = socket_closer.Release();
   return true;
 }
 
@@ -443,8 +494,7 @@
   }
   // Send the DHCP configuration to Shill.
   EmitEvent(kReasonBound);
-  // TODO(nywang): Setup a udp socket for future unicast, so that kernel can
-  // fill the ethernet header with gateway mac address for us.
+  CreateUdpSocket();
 }
 
 void DHCPV4::HandleNak(const DHCPMessage& msg) {
@@ -501,7 +551,7 @@
     LOG(ERROR) << "Failed to serialize a DHCP discover message";
     return false;
   }
-  if (!SendRawPacket(packet)) {
+  if (!SendBroadcastPacket(packet)) {
     LOG(ERROR) << "Failed to send a DHCP discover packet";
     return false;
   }
@@ -584,9 +634,7 @@
   DHCPMessage::InitRequest(&message);
   message.SetMessageType(kDHCPMessageTypeRequest);
   message.SetClientHardwareAddress(hardware_address_);
-  if (state_ == State::BOUND ||
-      state_ == State::RENEW ||
-      state_ == State::REBIND) {
+  if (HasALease()) {
     message.SetClientIPAddress(client_ip_);
   }
   if (state_ == State::SELECT || state_ == State::INIT_REBOOT) {
@@ -601,14 +649,27 @@
                            kDefaultParameterRequestList +
                                sizeof(kDefaultParameterRequestList)));
   ByteString packet;
-  if (!MakeRawPacket(message, &packet)) {
-    LOG(ERROR) << "Failed to serialize a DHCP request message";
-    return false;
-  }
-  // TODO(nywang): Use unicast for renewal.
-  if (!SendRawPacket(packet)) {
-    LOG(ERROR) << "Failed to send a DHCP request packet";
-    return false;
+
+  // Use unicast for renewal.
+  if (state_ == State::BOUND && udp_socket_ != kInvalidSocketDescriptor) {
+    if (!MakePacket(message, &packet)) {
+      LOG(ERROR) << "Failed to make a DHCP request packet";
+      return false;
+    }
+    if (!SendUnicastPacket(packet)) {
+      LOG(ERROR) << "Failed to send a DHCP request packet";
+      return false;
+    }
+  } else {
+    // Use broadcast for other cases.
+    if (!MakeRawPacket(message, &packet)) {
+      LOG(ERROR) << "Failed to make a DHCP request raw packet";
+      return false;
+    }
+    if (!SendBroadcastPacket(packet)) {
+      LOG(ERROR) << "Failed to send a DHCP request packet";
+      return false;
+    }
   }
   return true;
 }
@@ -627,11 +688,17 @@
   message.SetTransactionID(transaction_id);
   message.SetServerIdentifier(server_identifier_);
   ByteString packet;
-  if (!MakeRawPacket(message, &packet)) {
-    LOG(ERROR) << "Failed to serialize a DHCP release message";
+  if (!MakePacket(message, &packet)) {
+    LOG(ERROR) << "Failed to make a DHCP release packet";
     return false;
   }
-  if (!SendRawPacket(packet)) {
+  // Use unicast for release.
+  if (udp_socket_ == kInvalidSocketDescriptor) {
+    LOG(ERROR) << "Failed to send a DHCP release message becasuse "
+                  "there is no socket for unicast";
+    return false;
+  }
+  if (!SendUnicastPacket(packet)) {
     LOG(ERROR) << "Failed to send a DHCP release packet";
     return false;
   }
@@ -640,6 +707,16 @@
   return true;
 }
 
+bool DHCPV4::MakePacket(const DHCPMessage& message, ByteString* output) {
+  ByteString payload;
+  if (!message.Serialize(&payload)) {
+    LOG(ERROR) << "Failed to serialzie dhcp message";
+    return false;
+  }
+  *output = payload;
+  return true;
+}
+
 bool DHCPV4::MakeRawPacket(const DHCPMessage& message, ByteString* output) {
   ByteString payload;
   if (!message.Serialize(&payload)) {
@@ -703,7 +780,7 @@
   return true;
 }
 
-bool DHCPV4::SendRawPacket(const ByteString& packet) {
+bool DHCPV4::SendBroadcastPacket(const ByteString& packet) {
   LOG(INFO) << __func__;
   struct sockaddr_ll remote;
   memset(&remote, 0, sizeof(remote));
@@ -715,7 +792,29 @@
   remote.sll_halen = IFHWADDRLEN;
   memset(remote.sll_addr, 0xff, IFHWADDRLEN);
 
-  size_t result = sockets_->SendTo(socket_,
+  size_t result = sockets_->SendTo(raw_socket_,
+                                   packet.GetConstData(),
+                                   packet.GetLength(),
+                                   0,
+                                   reinterpret_cast<struct sockaddr *>(&remote),
+                                   sizeof(remote));
+
+  if (result != packet.GetLength()) {
+    PLOG(ERROR) << "Socket sento failed";
+    return false;
+  }
+  return true;
+}
+
+bool DHCPV4::SendUnicastPacket(const ByteString& packet) {
+  LOG(INFO) << __func__;
+  struct sockaddr_in remote;
+  memset(&remote, 0, sizeof(remote));
+  remote.sin_family = AF_INET;
+  remote.sin_port = htons(kDHCPServerPort);
+  remote.sin_addr.s_addr = htonl(server_ip_);
+
+  size_t result = sockets_->SendTo(udp_socket_,
                                    packet.GetConstData(),
                                    packet.GetLength(),
                                    0,
@@ -819,9 +918,7 @@
   if (state_ != State::BOUND) {
     LOG(ERROR) << "Ingore renewal task";
   }
-  if (!SendRequest()) {
-    return;
-  }
+  SendRequest();
   state_ = State::RENEW;
 }
 
@@ -830,9 +927,7 @@
   if (state_ != State::RENEW) {
     LOG(ERROR) << "Ingore rebind task";
   }
-  if (!SendRequest()) {
-    return;
-  }
+  SendRequest();
   state_ = State::REBIND;
 }
 
@@ -853,6 +948,15 @@
   adaptor_->EmitEvent(reason, configs);
 }
 
+bool DHCPV4::HasALease() {
+  if (state_ == State::BOUND ||
+      state_ == State::RENEW ||
+      state_ == State::REBIND) {
+    return true;
+  }
+  return false;
+}
+
 uint32_t DHCPV4::MasktoCIDR(uint32_t subnet_mask) {
   subnet_mask = ~subnet_mask;
   uint32_t count = 0;
diff --git a/dhcpv4.h b/dhcpv4.h
index 411cf66..8631cc5 100644
--- a/dhcpv4.h
+++ b/dhcpv4.h
@@ -59,6 +59,9 @@
   void ArpProbeReplyTimeoutTask();
   void CheckIpCollision();
   bool CreateRawSocket();
+  bool CreateUdpSocket();
+  bool HasALease();
+  bool MakePacket(const DHCPMessage& message, shill::ByteString* buffer);
   bool MakeRawPacket(const DHCPMessage& message, shill::ByteString* buffer);
   void OnReadError(const std::string& error_msg);
   void ParseRawPacket(shill::InputData* data);
@@ -67,7 +70,8 @@
   bool SendDiscover();
   bool SendRequest();
   bool SendRelease();
-  bool SendRawPacket(const shill::ByteString& buffer);
+  bool SendBroadcastPacket(const shill::ByteString& buffer);
+  bool SendUnicastPacket(const shill::ByteString& buffer);
   // Validate the IP and UDP header and return the total headers length.
   // Return -1 if any header is invalid.
   int ValidatePacketHeader(const unsigned char* buffer, size_t len);
@@ -148,7 +152,9 @@
   base::CancelableClosure arp_probe_reply_timeout_task_callback_;
 
   // Socket used for sending and receiving DHCP messages.
-  int socket_;
+  int raw_socket_;
+  // Socket used for sending unicast DHCP messages.
+  int udp_socket_;
   // Helper class with wrapped socket relavent functions.
   std::unique_ptr<shill::Sockets> sockets_;