shill: First step for rtnl code -- device discovery

Add device_info object and add it to the manager.  Once started,
this will perform rtnl enumeration.  Current tests just assert
that each enumeration completes successfully, but since our code
does not yet store any information based on what it sees, this
cannot be tested yet.

BUG=chromium-os:12933
TEST=New unit tests added

Change-Id: Ie1958222d9831c6117d0ace127857340a11b0bb1
Reviewed-on: http://gerrit.chromium.org/gerrit/655
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/device_info.cc b/device_info.cc
new file mode 100644
index 0000000..d86c66f
--- /dev/null
+++ b/device_info.cc
@@ -0,0 +1,201 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <time.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/ether.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <string>
+
+#include <base/logging.h>
+
+#include "shill/control_interface.h"
+#include "shill/service.h"
+#include "shill/device_info.h"
+
+using std::string;
+
+namespace shill {
+DeviceInfo::DeviceInfo(EventDispatcher *dispatcher)
+  : running_(false),
+    dispatcher_(dispatcher),
+    rtnl_callback_(this, &DeviceInfo::ParseRTNL),
+    rtnl_socket_(-1),
+    request_flags_(0),
+    request_sequence_(0) {
+  LOG(INFO) << "DeviceInfo initialized";
+}
+
+DeviceInfo::~DeviceInfo() {
+  Stop();
+}
+
+void DeviceInfo::Start()
+{
+  struct sockaddr_nl addr;
+
+  if (running_)
+    return;
+
+  rtnl_socket_ = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
+  if (rtnl_socket_ < 0) {
+    LOG(ERROR) << "Failed to open rtnl socket";
+    return;
+  }
+
+  memset(&addr, 0, sizeof(addr));
+  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) {
+    close(rtnl_socket_);
+    rtnl_socket_ = -1;
+    LOG(ERROR) << "RTNL socket bind failed";
+    return;
+  }
+
+  rtnl_handler_.reset(dispatcher_->CreateInputHandler(rtnl_socket_,
+                                                      &rtnl_callback_));
+  running_ = true;
+
+  request_flags_ = REQUEST_LINK | REQUEST_ADDR | REQUEST_ROUTE;
+  NextRequest(request_sequence_);
+
+  LOG(INFO) << "DeviceInfo started";
+}
+
+void DeviceInfo::Stop()
+{
+  if (!running_)
+    return;
+
+  rtnl_handler_.reset(NULL);
+  close(rtnl_socket_);
+  running_ = false;
+}
+
+void DeviceInfo::NextRequest(uint32_t seq) {
+  struct rtnl_request {
+    struct nlmsghdr hdr;
+    struct rtgenmsg msg;
+  } req;
+  struct sockaddr_nl addr;
+  int flag = 0;
+
+  LOG(INFO) << __func__ << ": " << seq << " :: " << request_sequence_;
+
+  if (seq != request_sequence_)
+    return;
+
+  request_sequence_++;
+  memset(&req, 0, sizeof(req));
+
+  req.hdr.nlmsg_len = sizeof(req);
+  req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+  req.hdr.nlmsg_pid = 0;
+  req.hdr.nlmsg_seq = request_sequence_;
+  req.msg.rtgen_family = AF_INET;
+
+  if ((request_flags_ & REQUEST_LINK) != 0) {
+    req.hdr.nlmsg_type = RTM_GETLINK;
+    flag = REQUEST_LINK;
+  } else if ((request_flags_ & REQUEST_ADDR) != 0) {
+    req.hdr.nlmsg_type = RTM_GETADDR;
+    flag = REQUEST_ADDR;
+  } else if ((request_flags_ & REQUEST_ROUTE) != 0) {
+    req.hdr.nlmsg_type = RTM_GETROUTE;
+    flag = REQUEST_ROUTE;
+  } else
+    return;
+
+  memset(&addr, 0, sizeof(addr));
+  addr.nl_family = AF_NETLINK;
+
+  if (sendto(rtnl_socket_, &req, sizeof(req), 0,
+             (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+    LOG(ERROR) << "RTNL sendto failed";
+    return;
+  }
+  request_flags_ &= ~flag;
+}
+
+
+void DeviceInfo::LinkMsg(unsigned char *buf, bool del) {
+  struct nlmsghdr *hdr = (struct nlmsghdr *) buf;
+  struct ifinfomsg *msg = (struct ifinfomsg *) NLMSG_DATA(hdr);
+  int bytes = IFLA_PAYLOAD(hdr);
+
+  LOG(INFO) << "index "  << msg->ifi_index << " flags " << msg->ifi_flags <<
+    ": del = " << del;
+}
+
+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::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::ParseRTNL(InputData *data) {
+  unsigned char *buf = data->buf;
+  unsigned char *end = buf + data->len;
+
+  while (buf < end) {
+    struct nlmsghdr *hdr = (struct nlmsghdr *) buf;
+    struct nlmsgerr *err;
+
+    if (!NLMSG_OK(hdr, end - buf))
+      break;
+
+    switch (hdr->nlmsg_type) {
+    case NLMSG_NOOP:
+    case NLMSG_OVERRUN:
+      return;
+    case NLMSG_DONE:
+      NextRequest(hdr->nlmsg_seq);
+      return;
+    case NLMSG_ERROR:
+      err = (nlmsgerr *) NLMSG_DATA(hdr);
+      LOG(ERROR) << "error " << -err->error << " (" << strerror(-err->error) <<
+                 ")";
+      return;
+    case RTM_NEWLINK:
+      LinkMsg(buf, false);
+      break;
+    case RTM_DELLINK:
+      LinkMsg(buf, true);
+      break;
+    case RTM_NEWADDR:
+      AddrMsg(buf, false);
+      break;
+    case RTM_DELADDR:
+      AddrMsg(buf, true);
+      break;
+    case RTM_NEWROUTE:
+      RouteMsg(buf, false);
+      break;
+    case RTM_DELROUTE:
+      RouteMsg(buf, true);
+      break;
+    }
+
+    buf += hdr->nlmsg_len;
+    }
+}
+
+}  // namespace shill