shill: Add DeviceInfo routines for assigning IP address

Accept an IPConfig instance and assign IP address parameters
to device index.  Add code to device superclass to apply any
successful ipconfig (petkov should change this as necessary).

BUG=chromium-os:16846
TEST=Manual -- ran on a cr48 with ethernet attached.  Added throw-away
code to remove the address, and this worked correctly.  Also re-ran
unit tests.

Change-Id: Ib491de2e9f6d4a4317fc305978351e5812514e0f
Reviewed-on: http://gerrit.chromium.org/gerrit/3018
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/device.cc b/device.cc
index 9aab0f0..7eeecf8 100644
--- a/device.cc
+++ b/device.cc
@@ -18,6 +18,7 @@
 #include "shill/dhcp_provider.h"
 #include "shill/error.h"
 #include "shill/manager.h"
+#include "shill/rtnl_handler.h"
 #include "shill/shill_event.h"
 
 using std::string;
@@ -131,6 +132,8 @@
 
 void Device::DestroyIPConfig() {
   if (ipconfig_.get()) {
+    RTNLHandler::GetInstance()->RemoveInterfaceAddress(interface_index_,
+                                                       *ipconfig_);
     ipconfig_->ReleaseIP();
     ipconfig_ = NULL;
   }
@@ -148,6 +151,10 @@
   // TODO(petkov): Use DeviceInfo to configure IP, etc. -- maybe through
   // ConfigIP? Also, maybe allow forwarding the callback to interested listeners
   // (e.g., the Manager).
+  if (success) {
+      RTNLHandler::GetInstance()->AddInterfaceAddress(interface_index_,
+                                                      *ipconfig);
+  }
 }
 
 }  // namespace shill
diff --git a/dhcp_config.cc b/dhcp_config.cc
index e01c772..8e3bf52 100644
--- a/dhcp_config.cc
+++ b/dhcp_config.cc
@@ -188,6 +188,7 @@
 bool DHCPConfig::ParseConfiguration(const Configuration& configuration,
                                     IPConfig::Properties *properties) {
   VLOG(2) << __func__;
+  properties->address_family = IPConfig::kAddressFamilyIPv4;
   for (Configuration::const_iterator it = configuration.begin();
        it != configuration.end(); ++it) {
     const string &key = it->first;
diff --git a/ipconfig.h b/ipconfig.h
index 1a10083..6708b12 100644
--- a/ipconfig.h
+++ b/ipconfig.h
@@ -23,9 +23,18 @@
 // class.
 class IPConfig : public base::RefCounted<IPConfig> {
  public:
-  struct Properties {
-    Properties() : subnet_cidr(0), mtu(0) {}
+  enum AddressFamily {
+    kAddressFamilyUnknown,
+    kAddressFamilyIPv4,
+    kAddressFamilyIPv6
+  };
 
+  struct Properties {
+    Properties() : address_family(kAddressFamilyUnknown),
+                   subnet_cidr(0),
+                   mtu(0) {}
+
+    AddressFamily address_family;
     std::string address;
     int subnet_cidr;
     std::string broadcast_address;
diff --git a/rtnl_handler.cc b/rtnl_handler.cc
index addc827..bcfd020 100644
--- a/rtnl_handler.cc
+++ b/rtnl_handler.cc
@@ -22,6 +22,7 @@
 #include <base/memory/singleton.h>
 
 #include "shill/io_handler.h"
+#include "shill/ipconfig.h"
 #include "shill/rtnl_handler.h"
 #include "shill/rtnl_listener.h"
 #include "shill/shill_event.h"
@@ -245,4 +246,91 @@
   }
 }
 
+static bool AddAtribute(struct nlmsghdr *hdr, int max_msg_size, int attr_type,
+                        const void *attr_data, int attr_len) {
+  int len = RTA_LENGTH(attr_len);
+  int new_msg_size = NLMSG_ALIGN(hdr->nlmsg_len) + RTA_ALIGN(len);
+  struct rtattr *rt_attr;
+
+  if (new_msg_size > max_msg_size)
+    return false;
+
+  rt_attr = reinterpret_cast<struct rtattr *> (reinterpret_cast<unsigned char *>
+                                               (hdr) +
+                                               NLMSG_ALIGN(hdr->nlmsg_len));
+  rt_attr->rta_type = attr_type;
+  rt_attr->rta_len = len;
+  memcpy(RTA_DATA(rt_attr), attr_data, attr_len);
+  hdr->nlmsg_len = new_msg_size;
+  return true;
+}
+
+bool RTNLHandler::AddressRequest(int interface_index, int cmd, int flags,
+                                 const IPConfig &ipconfig) {
+  const IPConfig::Properties &properties = ipconfig.properties();
+  int address_family;
+  int address_size;
+  unsigned char *attrs, *attrs_end;
+  int max_msg_size;
+  struct {
+    struct nlmsghdr   hdr;
+    struct ifaddrmsg  ifa;
+    unsigned char     attrs[256];
+  } req;
+  union {
+    in_addr ip4;
+    in6_addr in6;
+  } addr;
+
+  if (properties.address_family == IPConfig::kAddressFamilyIPv4) {
+    address_family = AF_INET;
+    address_size = sizeof(struct in_addr);
+  } else if (properties.address_family == IPConfig::kAddressFamilyIPv6) {
+    address_family = AF_INET6;
+    address_size = sizeof(struct in6_addr);
+  } else
+    return false;
+
+  request_sequence_++;
+  memset(&req, 0, sizeof(req));
+  req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+  req.hdr.nlmsg_flags = NLM_F_REQUEST | flags;
+  req.hdr.nlmsg_type = cmd;
+  req.ifa.ifa_family = address_family;
+  req.ifa.ifa_index = interface_index;
+
+  max_msg_size = req.hdr.nlmsg_len + sizeof(req.attrs);
+
+  // TODO(pstew): This code only works for Ethernet-like setups,
+  //              not with devices that have a peer address like PPP.
+  if (inet_pton(address_family, properties.address.c_str(), &addr) <= 0 ||
+      !AddAtribute(&req.hdr, max_msg_size, IFA_LOCAL, &addr, address_size))
+    return false;
+
+  if (inet_pton(address_family, properties.broadcast_address.c_str(),
+                &addr) <= 0 ||
+      !AddAtribute(&req.hdr, max_msg_size, IFA_BROADCAST, &addr, address_size))
+    return false;
+
+  req.ifa.ifa_prefixlen = properties.subnet_cidr;
+
+  if (send(rtnl_socket_, &req, req.hdr.nlmsg_len, 0) < 0) {
+    LOG(ERROR) << "RTNL sendto failed: " << strerror(errno);
+    return false;
+  }
+
+  return true;
+}
+
+bool RTNLHandler::AddInterfaceAddress(int interface_index,
+                                      const IPConfig &ipconfig) {
+  return AddressRequest(interface_index, RTM_NEWADDR, NLM_F_CREATE | NLM_F_EXCL,
+                        ipconfig);
+}
+
+bool RTNLHandler::RemoveInterfaceAddress(int interface_index,
+                                         const IPConfig &ipconfig) {
+  return AddressRequest(interface_index, RTM_DELADDR, 0, ipconfig);
+}
+
 }  // namespace shill
diff --git a/rtnl_handler.h b/rtnl_handler.h
index 00426a7..27c8b10 100644
--- a/rtnl_handler.h
+++ b/rtnl_handler.h
@@ -22,6 +22,8 @@
 
 namespace shill {
 
+class IPConfig;
+
 // This singleton class is responsible for interacting with the RTNL
 // subsystem.  RTNL provides (among other things) access to interface
 // discovery (add/remove events), interface state monitoring and the
@@ -52,11 +54,17 @@
   // Remove a previously added RTNL event listener
   void RemoveListener(RTNLListener *to_remove);
 
-  // Set flags on a network interface that has an kernel index of
+  // Set flags on a network interface that has a kernel index of
   // 'interface_index'.  Only the flags bits set in 'change' will
   // be set, and they will be set to the corresponding bit in 'flags'.
   void SetInterfaceFlags(int interface_index, unsigned int flags,
                          unsigned int change);
+  // Set address of a network interface that has a kernel index of
+  // 'interface_index'.
+  bool AddInterfaceAddress(int interface_index, const IPConfig &config);
+  // Remove address from a network interface that has a kernel index of
+  // 'interface_index'.
+  bool RemoveInterfaceAddress(int interface_index, const IPConfig &config);
   // Request that various tables (link, address, routing) tables be
   // exhaustively dumped via RTNL.  As results arrive from the kernel
   // they will be broadcast to all listeners.  The possible values
@@ -74,6 +82,8 @@
   void NextRequest(uint32_t seq);
   // Parse an incoming rtnl message from the kernel
   void ParseRTNL(InputData *data);
+  bool AddressRequest(int interface_index, int cmd, int flags,
+                      const IPConfig &config);
 
   bool running_;
   bool in_request_;