Perform device discovery and registration in device_info

Also, do device classification, and create the beginnings of
Device subclasses for WiFi and Ethernet.

BUG=chromium-os:12933
TEST=Added unit tests.  Also manual on a testbed machine to confirm WiFi
device detection.

Change-Id: I48b8fa2b3b966b22acf80f693d9522bff0221884
Reviewed-on: http://gerrit.chromium.org/gerrit/1084
Reviewed-by: Chris Masone <cmasone@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/device_info.cc b/device_info.cc
index 0986301..1e8608c 100644
--- a/device_info.cc
+++ b/device_info.cc
@@ -13,30 +13,51 @@
 #include <net/if_arp.h>
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
+#include <fcntl.h>
 #include <string>
 
 #include <base/callback_old.h>
 #include <base/hash_tables.h>
 #include <base/logging.h>
 #include <base/memory/scoped_ptr.h>
+#include <base/stringprintf.h>
 
 #include "shill/control_interface.h"
 #include "shill/device.h"
 #include "shill/device_info.h"
+#include "shill/ethernet.h"
+#include "shill/manager.h"
 #include "shill/service.h"
+#include "shill/wifi.h"
 
 using std::string;
 
 namespace shill {
-DeviceInfo::DeviceInfo(EventDispatcher *dispatcher)
+
+// static
+const char *DeviceInfo::kInterfaceUevent= "/sys/class/net/%s/uevent";
+// static
+const char *DeviceInfo::kInterfaceDriver= "/sys/class/net/%s/device/driver";
+// static
+const char *DeviceInfo::kModemDrivers[] = {
+    "gobi",
+    "QCUSBNet2k",
+    "GobiNet",
+    NULL
+};
+
+
+DeviceInfo::DeviceInfo(ControlInterface *control_interface,
+                       EventDispatcher *dispatcher,
+                       Manager *manager)
   : running_(false),
+    control_interface_(control_interface),
     dispatcher_(dispatcher),
+    manager_(manager),
     rtnl_callback_(NewCallback(this, &DeviceInfo::ParseRTNL)),
     rtnl_socket_(-1),
     request_flags_(0),
-    request_sequence_(0) {
-  LOG(INFO) << "DeviceInfo initialized";
-}
+    request_sequence_(0) {}
 
 DeviceInfo::~DeviceInfo() {
   Stop();
@@ -59,7 +80,8 @@
   addr.nl_family = AF_NETLINK;
   addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE;
 
-  if (bind(rtnl_socket_, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+  if (bind(rtnl_socket_, reinterpret_cast<struct sockaddr *>(&addr),
+           sizeof(addr)) < 0) {
     close(rtnl_socket_);
     rtnl_socket_ = -1;
     LOG(ERROR) << "RTNL socket bind failed";
@@ -73,7 +95,7 @@
   request_flags_ = REQUEST_LINK | REQUEST_ADDR | REQUEST_ROUTE;
   NextRequest(request_sequence_);
 
-  LOG(INFO) << "DeviceInfo started";
+  VLOG(2) << "DeviceInfo started";
 }
 
 void DeviceInfo::Stop()
@@ -94,8 +116,6 @@
   struct sockaddr_nl addr;
   int flag = 0;
 
-  LOG(INFO) << __func__ << ": " << seq << " :: " << request_sequence_;
-
   if (seq != request_sequence_)
     return;
 
@@ -124,35 +144,153 @@
   addr.nl_family = AF_NETLINK;
 
   if (sendto(rtnl_socket_, &req, sizeof(req), 0,
-             (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+             reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) < 0) {
     LOG(ERROR) << "RTNL sendto failed";
     return;
   }
   request_flags_ &= ~flag;
 }
 
+Device::Technology DeviceInfo::GetDeviceTechnology(const char *interface_name) {
+  char contents[1024];
+  int length;
+  int fd;
+  string uevent_file = StringPrintf(kInterfaceUevent, interface_name);
+  string driver_file = StringPrintf(kInterfaceDriver, interface_name);
+  const char *wifi_type;
+  const char *driver_name;
+  int modem_idx;
 
-void DeviceInfo::LinkMsg(unsigned char *buf, bool del) {
-  struct nlmsghdr *hdr = (struct nlmsghdr *) buf;
-  struct ifinfomsg *msg = (struct ifinfomsg *) NLMSG_DATA(hdr);
+  fd = open(uevent_file.c_str(), O_RDONLY);
+  if (fd < 0)
+    return Device::kUnknown;
+
+  length = read(fd, contents, sizeof(contents) - 1);
+  if (length < 0)
+    return Device::kUnknown;
+
+  /*
+   * If the "uevent" file contains the string "DEVTYPE=wlan\n" at the
+   * start of the file or after a newline, we can safely assume this
+   * is a wifi device.
+   */
+  contents[length] = '\0';
+  wifi_type = strstr(contents, "DEVTYPE=wlan\n");
+  if (wifi_type != NULL && (wifi_type == contents || wifi_type[-1] == '\n'))
+    return Device::kWifi;
+
+  length = readlink(driver_file.c_str(), contents, sizeof(contents)-1);
+  if (length < 0)
+    return Device::kUnknown;
+
+  contents[length] = '\0';
+  driver_name = strrchr(contents, '/');
+  if (driver_name != NULL) {
+    driver_name++;
+    // See if driver for this interface is in a list of known modem driver names
+    for (modem_idx = 0; kModemDrivers[modem_idx] != NULL; modem_idx++)
+      if (strcmp(driver_name, kModemDrivers[modem_idx]) == 0)
+        return Device::kCellular;
+  }
+
+  return Device::kEthernet;
+}
+
+void DeviceInfo::AddLinkMsg(struct nlmsghdr *hdr) {
+  struct ifinfomsg *msg = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(hdr));
+  base::hash_map<int, scoped_refptr<Device> >::iterator ndev =
+      devices_.find(msg->ifi_index);
   int bytes = IFLA_PAYLOAD(hdr);
+  int dev_index = msg->ifi_index;
+  struct rtattr *rta;
+  int rta_bytes;
+  char *link_name = NULL;
+  Device *device;
+  Device::Technology technology;
+  bool is_stub = false;
 
-  LOG(INFO) << "index "  << msg->ifi_index << " flags " << msg->ifi_flags <<
-    ": del = " << del;
+  VLOG(2) << "add link index "  << dev_index << " flags " <<
+    msg->ifi_flags;
+
+  if (ndev == devices_.end()) {
+    rta_bytes = IFLA_PAYLOAD(hdr);
+    for (rta = IFLA_RTA(msg); RTA_OK(rta, rta_bytes);
+         rta = RTA_NEXT(rta, rta_bytes)) {
+      if (rta->rta_type == IFLA_IFNAME) {
+        link_name = reinterpret_cast<char *>(RTA_DATA(rta));
+        break;
+      } else {
+        VLOG(2) << " RTA type " << rta->rta_type;
+      }
+    }
+
+    if (link_name != NULL)
+      technology = GetDeviceTechnology(link_name);
+
+    switch (technology) {
+    case Device::kEthernet:
+      device = new Ethernet(control_interface_, dispatcher_,
+                            link_name, dev_index);
+      break;
+    case Device::kWifi:
+      device = new WiFi(control_interface_, dispatcher_, link_name, dev_index);
+      break;
+    default:
+      device = new StubDevice(control_interface_, dispatcher_,
+                              link_name, dev_index, technology);
+      is_stub = true;
+    }
+
+    devices_[dev_index] = device;
+
+    if (!is_stub)
+      manager_->RegisterDevice(device);
+  } else {
+    device = ndev->second;
+  }
+
+  // TODO(pstew): Send the the flags change upwards to the device
 }
 
-void DeviceInfo::AddrMsg(unsigned char *buf, bool del) {
-  struct nlmsghdr *hdr = (struct nlmsghdr *) buf;
-  struct ifinfomsg *msg = (struct ifinfomsg *) NLMSG_DATA(hdr);
-  LOG(INFO) << "index "  << msg->ifi_index << " flags " << msg->ifi_flags <<
-    ": del = " << del;
+void DeviceInfo::DelLinkMsg(struct nlmsghdr *hdr) {
+  struct ifinfomsg *msg = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(hdr));
+  base::hash_map<int, scoped_refptr<Device> >::iterator ndev =
+      devices_.find(msg->ifi_index);
+  int dev_index = msg->ifi_index;
+  Device *device;
+
+  VLOG(2) << "del link index "  << dev_index << " flags " <<
+    msg->ifi_flags;
+
+  if (ndev != devices_.end()) {
+    device = ndev->second;
+    devices_.erase(ndev);
+    manager_->DeregisterDevice(device);
+  }
 }
 
-void DeviceInfo::RouteMsg(unsigned char *buf, bool del) {
-  struct nlmsghdr *hdr = (struct nlmsghdr *) buf;
-  struct ifinfomsg *msg = (struct ifinfomsg *) NLMSG_DATA(hdr);
-  LOG(INFO) << "index "  << msg->ifi_index << " flags " << msg->ifi_flags <<
-    ": del = " << del;
+void DeviceInfo::AddAddrMsg(struct nlmsghdr *hdr) {
+  struct ifaddrmsg *msg = reinterpret_cast<struct ifaddrmsg *>(NLMSG_DATA(hdr));
+  VLOG(2) << "add addrmsg family "  << (int) msg->ifa_family << " index " <<
+    msg->ifa_index;
+}
+
+void DeviceInfo::DelAddrMsg(struct nlmsghdr *hdr) {
+  struct ifaddrmsg *msg = reinterpret_cast<struct ifaddrmsg *>(NLMSG_DATA(hdr));
+  VLOG(2) << "del addrmsg family "  << (int) msg->ifa_family << " index " <<
+    msg->ifa_index;
+}
+
+void DeviceInfo::AddRouteMsg(struct nlmsghdr *hdr) {
+  struct rtmsg *msg = reinterpret_cast<struct rtmsg *>(NLMSG_DATA(hdr));
+  VLOG(2) << "add routemsg family "  << (int) msg->rtm_family << " table " <<
+    (int) msg->rtm_table << " proto " << (int) msg->rtm_protocol;
+}
+
+void DeviceInfo::DelRouteMsg(struct nlmsghdr *hdr) {
+  struct rtmsg *msg = reinterpret_cast<struct rtmsg *>(NLMSG_DATA(hdr));
+  VLOG(2) << "del routemsg family "  << (int) msg->rtm_family << " table " <<
+    (int) msg->rtm_table << " proto " << (int) msg->rtm_protocol;
 }
 
 void DeviceInfo::ParseRTNL(InputData *data) {
@@ -160,7 +298,7 @@
   unsigned char *end = buf + data->len;
 
   while (buf < end) {
-    struct nlmsghdr *hdr = (struct nlmsghdr *) buf;
+    struct nlmsghdr *hdr = reinterpret_cast<struct nlmsghdr *>(buf);
     struct nlmsgerr *err;
 
     if (!NLMSG_OK(hdr, end - buf))
@@ -174,32 +312,32 @@
       NextRequest(hdr->nlmsg_seq);
       return;
     case NLMSG_ERROR:
-      err = (nlmsgerr *) NLMSG_DATA(hdr);
+      err = reinterpret_cast<nlmsgerr *>(NLMSG_DATA(hdr));
       LOG(ERROR) << "error " << -err->error << " (" << strerror(-err->error) <<
                  ")";
       return;
     case RTM_NEWLINK:
-      LinkMsg(buf, false);
+      AddLinkMsg(hdr);
       break;
     case RTM_DELLINK:
-      LinkMsg(buf, true);
+      DelLinkMsg(hdr);
       break;
     case RTM_NEWADDR:
-      AddrMsg(buf, false);
+      AddAddrMsg(hdr);
       break;
     case RTM_DELADDR:
-      AddrMsg(buf, true);
+      DelAddrMsg(hdr);
       break;
     case RTM_NEWROUTE:
-      RouteMsg(buf, false);
+      AddRouteMsg(hdr);
       break;
     case RTM_DELROUTE:
-      RouteMsg(buf, true);
+      DelRouteMsg(hdr);
       break;
     }
 
     buf += hdr->nlmsg_len;
-    }
+  }
 }
 
 }  // namespace shill