[shill] Migrate to RTNLMessage instead of nlmsghdr

Now, RTNLHandler deals with nlmsghdrs internally, and passes parsed RTNLMessage
objects to everyone else.

Also, move some code out of device_info_unittest.cc that was testing
RTNLHandler::ParseRTNL

BUG=chromium-os:18908
TEST=unit
STATUS=Verified

Change-Id: I8e1546ea8e996a0e2302fe5e5937b03c13cb1a61
Reviewed-on: http://gerrit.chromium.org/gerrit/5612
Reviewed-by: Chris Masone <cmasone@chromium.org>
Tested-by: Chris Masone <cmasone@chromium.org>
diff --git a/device_info.cc b/device_info.cc
index 908a2f9..ed7d630 100644
--- a/device_info.cc
+++ b/device_info.cc
@@ -31,6 +31,7 @@
 #include "shill/manager.h"
 #include "shill/rtnl_handler.h"
 #include "shill/rtnl_listener.h"
+#include "shill/rtnl_message.h"
 #include "shill/service.h"
 #include "shill/wifi.h"
 
@@ -98,12 +99,12 @@
   return iter->second;
 }
 
-Device::Technology DeviceInfo::GetDeviceTechnology(const char *interface_name) {
+Device::Technology DeviceInfo::GetDeviceTechnology(const string &iface_name) {
   char contents[1024];
   int length;
   int fd;
-  string uevent_file = StringPrintf(kInterfaceUevent, interface_name);
-  string driver_file = StringPrintf(kInterfaceDriver, interface_name);
+  string uevent_file = StringPrintf(kInterfaceUevent, iface_name.c_str());
+  string driver_file = StringPrintf(kInterfaceDriver, iface_name.c_str());
   const char *wifi_type;
   const char *driver_name;
   int modem_idx;
@@ -143,32 +144,25 @@
   return Device::kEthernet;
 }
 
-void DeviceInfo::AddLinkMsgHandler(struct nlmsghdr *hdr) {
-  struct ifinfomsg *msg = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(hdr));
-  int dev_index = msg->ifi_index;
-  struct rtattr *rta;
-  int rta_bytes;
-  char *link_name = NULL;
+void DeviceInfo::AddLinkMsgHandler(const RTNLMessage &msg) {
+  DCHECK(msg.type() == RTNLMessage::kMessageTypeLink &&
+         msg.mode() == RTNLMessage::kMessageModeAdd);
+  int dev_index = msg.interface_index();
   Device::Technology technology = Device::kUnknown;
 
   VLOG(2) << __func__;
 
   DeviceRefPtr device = GetDevice(dev_index);
   if (!device.get()) {
-    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 (!msg.HasAttribute(IFLA_IFNAME)) {
+      LOG(ERROR) << "Add Link message does not have IFLA_IFNAME!";
+      return;
     }
-
+    ByteString b(msg.GetAttribute(IFLA_IFNAME));
+    string link_name(reinterpret_cast<const char*>(b.GetConstData()));
     VLOG(2) << "add link index "  << dev_index << " name " << link_name;
 
-    if (link_name) {
+    if (!link_name.empty()) {
       if (ContainsKey(black_list_, link_name)) {
         technology = Device::kBlacklisted;
       } else {
@@ -198,12 +192,13 @@
     RegisterDevice(device);
   }
 
-  device->LinkEvent(msg->ifi_flags, msg->ifi_change);
+  device->LinkEvent(msg.link_status().flags, msg.link_status().change);
 }
 
-void DeviceInfo::DelLinkMsgHandler(struct nlmsghdr *hdr) {
-  struct ifinfomsg *msg = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(hdr));
-  RemoveDevice(msg->ifi_index);
+void DeviceInfo::DelLinkMsgHandler(const RTNLMessage &msg) {
+  DCHECK(msg.type() == RTNLMessage::kMessageTypeLink &&
+         msg.mode() == RTNLMessage::kMessageModeDelete);
+  RemoveDevice(msg.interface_index());
 }
 
 void DeviceInfo::RemoveDevice(int interface_index) {
@@ -215,11 +210,14 @@
   }
 }
 
-void DeviceInfo::LinkMsgHandler(struct nlmsghdr *hdr) {
-  if (hdr->nlmsg_type == RTM_NEWLINK) {
-    AddLinkMsgHandler(hdr);
-  } else if (hdr->nlmsg_type == RTM_DELLINK) {
-    DelLinkMsgHandler(hdr);
+void DeviceInfo::LinkMsgHandler(const RTNLMessage &msg) {
+  DCHECK(msg.type() == RTNLMessage::kMessageTypeLink);
+  if (msg.mode() == RTNLMessage::kMessageModeAdd) {
+    AddLinkMsgHandler(msg);
+  } else if (msg.mode() == RTNLMessage::kMessageModeDelete) {
+    DelLinkMsgHandler(msg);
+  } else {
+    NOTREACHED();
   }
 }
 
diff --git a/device_info.h b/device_info.h
index 2c29dd7..66fc803 100644
--- a/device_info.h
+++ b/device_info.h
@@ -17,11 +17,10 @@
 
 #include "shill/rtnl_listener.h"
 
-struct nlmsghdr;
-
 namespace shill {
 
 class Manager;
+class RTNLMessage;
 
 class DeviceInfo {
  public:
@@ -47,11 +46,11 @@
   static const char *kInterfaceDriver;
   static const char *kModemDrivers[];
 
-  static Device::Technology GetDeviceTechnology(const char *interface_name);
+  static Device::Technology GetDeviceTechnology(const std::string &face_name);
 
-  void AddLinkMsgHandler(struct nlmsghdr *hdr);
-  void DelLinkMsgHandler(struct nlmsghdr *hdr);
-  void LinkMsgHandler(struct nlmsghdr *hdr);
+  void AddLinkMsgHandler(const RTNLMessage &msg);
+  void DelLinkMsgHandler(const RTNLMessage &msg);
+  void LinkMsgHandler(const RTNLMessage &msg);
 
   void RemoveDevice(int interface_index);
 
@@ -59,7 +58,7 @@
   EventDispatcher *dispatcher_;
   Manager *manager_;
   base::hash_map<int, DeviceRefPtr> devices_;
-  scoped_ptr<Callback1<struct nlmsghdr *>::Type> link_callback_;
+  scoped_ptr<Callback1<const RTNLMessage &>::Type> link_callback_;
   scoped_ptr<RTNLListener> link_listener_;
   std::set<std::string> black_list_;
 };
diff --git a/device_info_unittest.cc b/device_info_unittest.cc
index e3dfd47..8edbd68 100644
--- a/device_info_unittest.cc
+++ b/device_info_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "shill/device_info.h"
+
 #include <glib.h>
 #include <sys/socket.h>
 #include <linux/netlink.h>  // Needs typedefs from sys/socket.h.
@@ -15,13 +17,12 @@
 #include <gtest/gtest.h>
 #include <gmock/gmock.h>
 
-#include "shill/device_info.h"
-#include "shill/dhcp_provider.h"
 #include "shill/manager.h"
 #include "shill/mock_control.h"
 #include "shill/mock_glib.h"
 #include "shill/mock_sockets.h"
 #include "shill/rtnl_handler.h"
+#include "shill/rtnl_message.h"
 
 using std::string;
 using testing::_;
@@ -43,128 +44,73 @@
  public:
   DeviceInfoTest()
       : manager_(&control_interface_, &dispatcher_, &glib_),
-        device_info_(&control_interface_, &dispatcher_, &manager_),
-        task_factory_(this) {
-    DHCPProvider::GetInstance()->glib_ = &glib_;
+        device_info_(&control_interface_, &dispatcher_, &manager_) {
   }
 
   base::hash_map<int, DeviceRefPtr> *DeviceInfoDevices() {
     return &device_info_.devices_;
   }
 
-  void AddLink();
-
-  virtual void TearDown() {
-    RTNLHandler::GetInstance()->Stop();
-  }
-
  protected:
-  static const int kTestSocket;
   static const int kTestDeviceIndex;
   static const char kTestDeviceName[];
 
-  void StartRTNLHandler();
-  void StopRTNLHandler();
+  RTNLMessage *BuildMessage(RTNLMessage::MessageType type,
+                            RTNLMessage::MessageMode mode);
+  void SendMessageToDeviceInfo(const RTNLMessage &message);
 
   MockGLib glib_;
-  MockSockets sockets_;
   MockControl control_interface_;
   Manager manager_;
   DeviceInfo device_info_;
   TestEventDispatcher dispatcher_;
-  ScopedRunnableMethodFactory<DeviceInfoTest> task_factory_;
 };
 
-const int DeviceInfoTest::kTestSocket = 123;
 const int DeviceInfoTest::kTestDeviceIndex = 123456;
 const char DeviceInfoTest::kTestDeviceName[] = "test-device";
 
-void DeviceInfoTest::StartRTNLHandler() {
-  EXPECT_CALL(sockets_, Socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE))
-      .WillOnce(Return(kTestSocket));
-  EXPECT_CALL(sockets_, Bind(kTestSocket, _, sizeof(sockaddr_nl)))
-      .WillOnce(Return(0));
-  RTNLHandler::GetInstance()->Start(&dispatcher_, &sockets_);
+RTNLMessage *DeviceInfoTest::BuildMessage(RTNLMessage::MessageType type,
+                                          RTNLMessage::MessageMode mode) {
+  RTNLMessage *message = new RTNLMessage(type, mode, 0, 0, 0, kTestDeviceIndex,
+                                         IPAddress::kAddressFamilyIPv4);
+  message->SetAttribute(static_cast<uint16>(IFLA_IFNAME),
+                        ByteString(kTestDeviceName, true));
+  return message;
 }
 
-void DeviceInfoTest::StopRTNLHandler() {
-  EXPECT_CALL(sockets_, Close(kTestSocket)).WillOnce(Return(0));
-  RTNLHandler::GetInstance()->Stop();
-}
-
-void DeviceInfoTest::AddLink() {
-  // TODO(petkov): Abstract this away into a separate message parsing/creation
-  // class.
-  struct {
-    struct nlmsghdr hdr;
-    struct ifinfomsg msg;
-    char attrs[256];
-  } message;
-  memset(&message, 0, sizeof(message));
-  message.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(message.msg));
-  message.hdr.nlmsg_type = RTM_NEWLINK;
-  message.msg.ifi_index = kTestDeviceIndex;
-
-  // Append the interface name attribute.
-  string link_name = kTestDeviceName;
-  int len = RTA_LENGTH(link_name.size() + 1);
-  size_t new_msg_size = NLMSG_ALIGN(message.hdr.nlmsg_len) + RTA_ALIGN(len);
-  ASSERT_TRUE(new_msg_size <= sizeof(message));
-  struct rtattr *rt_attr =
-      reinterpret_cast<struct rtattr *>(reinterpret_cast<unsigned char *>
-                                        (&message) +
-                                        NLMSG_ALIGN(message.hdr.nlmsg_len));
-  rt_attr->rta_type = IFLA_IFNAME;
-  rt_attr->rta_len = len;
-  memcpy(RTA_DATA(rt_attr), link_name.c_str(), link_name.size() + 1);
-  message.hdr.nlmsg_len = new_msg_size;
-
-  InputData data(reinterpret_cast<unsigned char *>(&message),
-                 message.hdr.nlmsg_len);
-  RTNLHandler::GetInstance()->ParseRTNL(&data);
+void DeviceInfoTest::SendMessageToDeviceInfo(const RTNLMessage &message) {
+  device_info_.LinkMsgHandler(message);
 }
 
 TEST_F(DeviceInfoTest, DeviceEnumeration) {
   // Start our own private device_info
   device_info_.Start();
-  EXPECT_CALL(sockets_, SendTo(kTestSocket, _, _, 0, _, sizeof(sockaddr_nl)))
-      .WillOnce(Return(0));
-  StartRTNLHandler();
-
-  AddLink();
+  scoped_ptr<RTNLMessage> message(BuildMessage(RTNLMessage::kMessageTypeLink,
+                                               RTNLMessage::kMessageModeAdd));
+  SendMessageToDeviceInfo(*message.get());
 
   // Peek in at the map of devices
   base::hash_map<int, DeviceRefPtr> *device_map = DeviceInfoDevices();
   EXPECT_EQ(1, device_map->size());
   EXPECT_TRUE(ContainsKey(*device_map, kTestDeviceIndex));
 
-  device_info_.Stop();
-  StopRTNLHandler();
-}
-
-TEST_F(DeviceInfoTest, DeviceEnumerationReverse) {
-  StartRTNLHandler();
-  EXPECT_CALL(sockets_, SendTo(kTestSocket, _, _, 0, _, sizeof(sockaddr_nl)))
-      .WillOnce(Return(0));
-  device_info_.Start();
-
-  AddLink();
+  message.reset(BuildMessage(RTNLMessage::kMessageTypeLink,
+                             RTNLMessage::kMessageModeDelete));
+  SendMessageToDeviceInfo(*message.get());
 
   // Peek in at the map of devices
-  base::hash_map<int, DeviceRefPtr> *device_map = DeviceInfoDevices();
-  EXPECT_EQ(1, device_map->size());
-  EXPECT_TRUE(ContainsKey(*device_map, kTestDeviceIndex));
+  EXPECT_EQ(0, device_map->size());
+  EXPECT_FALSE(ContainsKey(*device_map, kTestDeviceIndex));
 
-  StopRTNLHandler();
   device_info_.Stop();
 }
 
 TEST_F(DeviceInfoTest, DeviceBlackList) {
   device_info_.AddDeviceToBlackList(kTestDeviceName);
   device_info_.Start();
-  EXPECT_CALL(sockets_, SendTo(_, _, _, _, _, _));
-  StartRTNLHandler();
-  AddLink();
+  scoped_ptr<RTNLMessage> message(BuildMessage(RTNLMessage::kMessageTypeLink,
+                                               RTNLMessage::kMessageModeAdd));
+  SendMessageToDeviceInfo(*message.get());
 
   // Peek in at the map of devices
   base::hash_map<int, DeviceRefPtr> *device_map(DeviceInfoDevices());
@@ -172,7 +118,6 @@
   EXPECT_TRUE(device_map->find(kTestDeviceIndex)->second->TechnologyIs(
       Device::kBlacklisted));
 
-  StopRTNLHandler();
   device_info_.Stop();
 }
 
diff --git a/ip_address.cc b/ip_address.cc
index 9d300bd..16e07bb 100644
--- a/ip_address.cc
+++ b/ip_address.cc
@@ -4,6 +4,7 @@
 
 #include "shill/ip_address.h"
 
+#include <arpa/inet.h>
 #include <netinet/in.h>
 
 #include <string>
@@ -12,6 +13,13 @@
 
 namespace shill {
 
+// static
+const IPAddress::Family IPAddress::kAddressFamilyUnknown = AF_UNSPEC;
+// static
+const IPAddress::Family IPAddress::kAddressFamilyIPv4 = AF_INET;
+// static
+const IPAddress::Family IPAddress::kAddressFamilyIPv6 = AF_INET6;
+
 IPAddress::IPAddress(Family family, const ByteString &address)
     : family_(family) ,
       address_(address) {}
diff --git a/ip_address.h b/ip_address.h
index 7ff79fe..763522d 100644
--- a/ip_address.h
+++ b/ip_address.h
@@ -5,8 +5,6 @@
 #ifndef SHILL_IP_ADDRESS_
 #define SHILL_IP_ADDRESS_
 
-#include <arpa/inet.h>
-
 #include <string>
 
 #include "shill/byte_string.h"
@@ -16,9 +14,9 @@
 class IPAddress {
  public:
   typedef unsigned char Family;
-  static const Family kAddressFamilyUnknown = AF_UNSPEC;
-  static const Family kAddressFamilyIPv4 = AF_INET;
-  static const Family kAddressFamilyIPv6 = AF_INET6;
+  static const Family kAddressFamilyUnknown;
+  static const Family kAddressFamilyIPv4;
+  static const Family kAddressFamilyIPv6;
 
   explicit IPAddress(Family family);
   IPAddress(Family family, const ByteString &address);
diff --git a/routing_table.cc b/routing_table.cc
index a058b0f..7b337d3 100644
--- a/routing_table.cc
+++ b/routing_table.cc
@@ -194,15 +194,7 @@
   }
 }
 
-void RoutingTable::RouteMsgHandler(struct nlmsghdr *hdr) {
-  RTNLMessage msg;
-
-  // TODO(pstew): When RTNLHandler starts using RTNLMessages, this goes away
-  if (!msg.Decode(ByteString(reinterpret_cast<unsigned char *>(hdr),
-                             hdr->nlmsg_len))) {
-    return;
-  }
-
+void RoutingTable::RouteMsgHandler(const RTNLMessage &msg) {
   VLOG(2) << __func__;
 
   if (msg.type() != RTNLMessage::kMessageTypeRoute ||
diff --git a/routing_table.h b/routing_table.h
index f754250..f0a2cc9 100644
--- a/routing_table.h
+++ b/routing_table.h
@@ -17,8 +17,6 @@
 #include "shill/refptr_types.h"
 #include "shill/rtnl_message.h"
 
-struct nlmsghdr;
-
 namespace shill {
 
 class RoutingTableEntry;
@@ -66,7 +64,7 @@
   RoutingTable();
   ~RoutingTable();
 
-  void RouteMsgHandler(struct nlmsghdr *hdr);
+  void RouteMsgHandler(const RTNLMessage &msg);
   bool ApplyRoute(uint32 interface_index,
                   const RoutingTableEntry &entry,
                   RTNLMessage::MessageMode mode,
@@ -77,7 +75,7 @@
   static const char kRouteFlushPath6[];
 
   base::hash_map<int, std::vector<RoutingTableEntry> > tables_;
-  scoped_ptr<Callback1<struct nlmsghdr *>::Type> route_callback_;
+  scoped_ptr<Callback1<const RTNLMessage &>::Type> route_callback_;
   scoped_ptr<RTNLListener> route_listener_;
 
   DISALLOW_COPY_AND_ASSIGN(RoutingTable);
diff --git a/rtnl_handler.cc b/rtnl_handler.cc
index 8fd4167..185275f 100644
--- a/rtnl_handler.cc
+++ b/rtnl_handler.cc
@@ -150,10 +150,10 @@
     NextRequest(request_sequence_);
 }
 
-void RTNLHandler::DispatchEvent(int type, struct nlmsghdr *hdr) {
+void RTNLHandler::DispatchEvent(int type, const RTNLMessage &msg) {
   std::vector<RTNLListener *>::iterator it;
   for (it = listeners_.begin(); it != listeners_.end(); ++it) {
-    (*it)->NotifyEvent(type, hdr);
+    (*it)->NotifyEvent(type, msg);
   }
 }
 
@@ -226,37 +226,46 @@
 
   while (buf < end) {
     struct nlmsghdr *hdr = reinterpret_cast<struct nlmsghdr *>(buf);
-    struct nlmsgerr *err;
-
     if (!NLMSG_OK(hdr, static_cast<unsigned int>(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 = reinterpret_cast<nlmsgerr *>(NLMSG_DATA(hdr));
-      LOG(ERROR) << "error " << -err->error << " (" << strerror(-err->error) <<
-                 ")";
-      return;
-    case RTM_NEWLINK:
-    case RTM_DELLINK:
-      DispatchEvent(kRequestLink, hdr);
-      break;
-    case RTM_NEWADDR:
-    case RTM_DELADDR:
-      DispatchEvent(kRequestAddr, hdr);
-      break;
-    case RTM_NEWROUTE:
-    case RTM_DELROUTE:
-      DispatchEvent(kRequestRoute, hdr);
-      break;
-    }
+    RTNLMessage msg;
+    if (!msg.Decode(ByteString(reinterpret_cast<unsigned char *>(hdr),
+                               hdr->nlmsg_len))) {
 
+      switch (hdr->nlmsg_type) {
+        case NLMSG_NOOP:
+        case NLMSG_OVERRUN:
+          break;
+        case NLMSG_DONE:
+          NextRequest(hdr->nlmsg_seq);
+          break;
+        case NLMSG_ERROR:
+          {
+            struct nlmsgerr *err =
+                reinterpret_cast<nlmsgerr *>(NLMSG_DATA(hdr));
+            LOG(ERROR) << "error " << -err->error << " ("
+                       << strerror(-err->error) << ")";
+            break;
+          }
+        default:
+          NOTIMPLEMENTED() << "Unknown NL message type.";
+      }
+    } else {
+      switch (msg.type()) {
+        case RTNLMessage::kMessageTypeLink:
+          DispatchEvent(kRequestLink, msg);
+          break;
+        case RTNLMessage::kMessageTypeAddress:
+          DispatchEvent(kRequestAddr, msg);
+          break;
+        case RTNLMessage::kMessageTypeRoute:
+          DispatchEvent(kRequestRoute, msg);
+          break;
+        default:
+          NOTIMPLEMENTED() << "Unknown RTNL message type.";
+      }
+    }
     buf += hdr->nlmsg_len;
   }
 }
diff --git a/rtnl_handler.h b/rtnl_handler.h
index ead1981..7f42d33 100644
--- a/rtnl_handler.h
+++ b/rtnl_handler.h
@@ -107,7 +107,7 @@
   void Stop();
 
   // Dispatches an rtnl message to all listeners
-  void DispatchEvent(int type, struct nlmsghdr *hdr);
+  void DispatchEvent(int type, const RTNLMessage &msg);
   // Send the next table-dump request to the kernel
   void NextRequest(uint32_t seq);
   // Parse an incoming rtnl message from the kernel
diff --git a/rtnl_handler_unittest.cc b/rtnl_handler_unittest.cc
index c730423..3530631 100644
--- a/rtnl_handler_unittest.cc
+++ b/rtnl_handler_unittest.cc
@@ -4,15 +4,24 @@
 
 #include <string>
 
+#include <glib.h>
 #include <gtest/gtest.h>
 #include <net/if.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>  // Needs typedefs from sys/socket.h.
+#include <linux/rtnetlink.h>
 #include <sys/ioctl.h>
 
+#include "shill/manager.h"
+#include "shill/mock_control.h"
+#include "shill/mock_glib.h"
 #include "shill/mock_sockets.h"
 #include "shill/rtnl_handler.h"
+#include "shill/rtnl_message.h"
 
 using std::string;
 using testing::_;
+using testing::A;
 using testing::DoAll;
 using testing::Return;
 using testing::StrictMock;
@@ -30,26 +39,101 @@
   }
 }
 
+class TestEventDispatcher : public EventDispatcher {
+ public:
+  virtual IOInputHandler *CreateInputHandler(
+      int fd,
+      Callback1<InputData*>::Type *callback) {
+    return NULL;
+  }
+};
+
 }  // namespace
 
 class RTNLHandlerTest : public Test {
+ public:
+  RTNLHandlerTest()
+      : manager_(&control_interface_, &dispatcher_, &glib_),
+        callback_(NewCallback(this, &RTNLHandlerTest::Callback)),
+        task_factory_(this) {
+  }
+
+  virtual void TearDown() {
+    RTNLHandler::GetInstance()->Stop();
+    SetSockets(NULL);
+  }
+
+  MOCK_METHOD1(Callback, void(const RTNLMessage &));
+
  protected:
+  static const int kTestSocket;
+  static const int kTestDeviceIndex;
+  static const char kTestDeviceName[];
+
+  void AddLink();
+  void StartRTNLHandler();
+  void StopRTNLHandler();
+
   void SetSockets(Sockets *sockets) {
     RTNLHandler::GetInstance()->sockets_ = sockets;
   }
 
-  virtual void SetUp() {
-    SetSockets(&sockets_);
-  }
-
-  virtual void TearDown() {
-    SetSockets(NULL);
-  }
-
   StrictMock<MockSockets> sockets_;
+  MockGLib glib_;
+  MockControl control_interface_;
+  Manager manager_;
+  TestEventDispatcher dispatcher_;
+  scoped_ptr<Callback1<const RTNLMessage &>::Type> callback_;
+  ScopedRunnableMethodFactory<RTNLHandlerTest> task_factory_;
 };
 
+const int RTNLHandlerTest::kTestSocket = 123;
+const int RTNLHandlerTest::kTestDeviceIndex = 123456;
+const char RTNLHandlerTest::kTestDeviceName[] = "test-device";
+
+void RTNLHandlerTest::StartRTNLHandler() {
+  EXPECT_CALL(sockets_, Socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE))
+      .WillOnce(Return(kTestSocket));
+  EXPECT_CALL(sockets_, Bind(kTestSocket, _, sizeof(sockaddr_nl)))
+      .WillOnce(Return(0));
+  RTNLHandler::GetInstance()->Start(&dispatcher_, &sockets_);
+}
+
+void RTNLHandlerTest::StopRTNLHandler() {
+  EXPECT_CALL(sockets_, Close(kTestSocket)).WillOnce(Return(0));
+  RTNLHandler::GetInstance()->Stop();
+}
+
+void RTNLHandlerTest::AddLink() {
+  RTNLMessage message(RTNLMessage::kMessageTypeLink,
+                      RTNLMessage::kMessageModeAdd,
+                      0,
+                      0,
+                      0,
+                      kTestDeviceIndex,
+                      IPAddress::kAddressFamilyIPv4);
+  message.SetAttribute(static_cast<uint16>(IFLA_IFNAME),
+                       ByteString(kTestDeviceName, true));
+  ByteString b(message.Encode());
+  InputData data(b.GetData(), b.GetLength());
+  RTNLHandler::GetInstance()->ParseRTNL(&data);
+}
+
+TEST_F(RTNLHandlerTest, AddLinkTest) {
+  StartRTNLHandler();
+  scoped_ptr<RTNLListener> link_listener(
+      new RTNLListener(RTNLHandler::kRequestLink, callback_.get()));
+
+  EXPECT_CALL(*this, Callback(A<const RTNLMessage &>())).Times(1);
+
+  AddLink();
+
+  StopRTNLHandler();
+}
+
+
 TEST_F(RTNLHandlerTest, GetInterfaceName) {
+  SetSockets(&sockets_);
   EXPECT_EQ(-1, RTNLHandler::GetInstance()->GetInterfaceIndex(""));
   {
     struct ifreq ifr;
@@ -66,7 +150,7 @@
       .WillOnce(Return(-1))
       .WillOnce(DoAll(SetInterfaceIndex(), Return(0)));
   EXPECT_CALL(sockets_, Close(kTestSocket))
-      .Times(2)
+      .Times(3)
       .WillRepeatedly(Return(0));
   EXPECT_EQ(-1, RTNLHandler::GetInstance()->GetInterfaceIndex("eth0"));
   EXPECT_EQ(-1, RTNLHandler::GetInstance()->GetInterfaceIndex("wlan0"));
diff --git a/rtnl_listener.cc b/rtnl_listener.cc
index 643a19e..7eff73c 100644
--- a/rtnl_listener.cc
+++ b/rtnl_listener.cc
@@ -10,7 +10,7 @@
 namespace shill {
 
 RTNLListener::RTNLListener(int listen_flags,
-                           Callback1<struct nlmsghdr *>::Type *callback)
+                           Callback1<const RTNLMessage &>::Type *callback)
     : listen_flags_(listen_flags), callback_(callback) {
   RTNLHandler::GetInstance()->AddListener(this);
 }
@@ -19,9 +19,9 @@
   RTNLHandler::GetInstance()->RemoveListener(this);
 }
 
-void RTNLListener::NotifyEvent(int type, struct nlmsghdr *hdr) {
+void RTNLListener::NotifyEvent(int type, const RTNLMessage &msg) {
   if ((type & listen_flags_) != 0)
-    callback_->Run(hdr);
+    callback_->Run(msg);
 }
 
 }  // namespace shill
diff --git a/rtnl_listener.h b/rtnl_listener.h
index c606527..0370c92 100644
--- a/rtnl_listener.h
+++ b/rtnl_listener.h
@@ -7,20 +7,21 @@
 
 #include <base/callback_old.h>
 
-struct nlmsghdr;
-
 namespace shill {
 
+class RTNLMessage;
+
 class RTNLListener {
  public:
-  RTNLListener(int listen_flags, Callback1<struct nlmsghdr *>::Type *callback);
+  RTNLListener(int listen_flags,
+               Callback1<const RTNLMessage &>::Type *callback);
   ~RTNLListener();
 
-  void NotifyEvent(int type, struct nlmsghdr *hdr);
+  void NotifyEvent(int type, const RTNLMessage &msg);
 
  private:
   int listen_flags_;
-  Callback1<struct nlmsghdr *>::Type *callback_;
+  Callback1<const RTNLMessage &>::Type *callback_;
 
   DISALLOW_COPY_AND_ASSIGN(RTNLListener);
 };
diff --git a/rtnl_listener_unittest.cc b/rtnl_listener_unittest.cc
index 540d4cc..52461fa 100644
--- a/rtnl_listener_unittest.cc
+++ b/rtnl_listener_unittest.cc
@@ -12,8 +12,10 @@
 
 #include "shill/rtnl_handler.h"
 #include "shill/rtnl_listener.h"
+#include "shill/rtnl_message.h"
 
 using testing::_;
+using testing::A;
 using testing::Test;
 
 namespace shill {
@@ -23,19 +25,19 @@
   RTNLListenerTest()
       : callback_(NewCallback(this, &RTNLListenerTest::Callback)) {}
 
-  MOCK_METHOD1(Callback, void(struct nlmsghdr *));
+  MOCK_METHOD1(Callback, void(const RTNLMessage &));
 
  protected:
-  scoped_ptr<Callback1<struct nlmsghdr *>::Type> callback_;
+  scoped_ptr<Callback1<const RTNLMessage &>::Type> callback_;
 };
 
 TEST_F(RTNLListenerTest, NoRun) {
   {
     RTNLListener listener(RTNLHandler::kRequestAddr, callback_.get());
     EXPECT_EQ(1, RTNLHandler::GetInstance()->listeners_.size());
-    struct nlmsghdr message;
+    RTNLMessage message;
     EXPECT_CALL(*this, Callback(_)).Times(0);
-    listener.NotifyEvent(RTNLHandler::kRequestLink, &message);
+    listener.NotifyEvent(RTNLHandler::kRequestLink, message);
   }
   EXPECT_EQ(0, RTNLHandler::GetInstance()->listeners_.size());
 }
@@ -46,9 +48,9 @@
         RTNLHandler::kRequestLink | RTNLHandler::kRequestAddr,
         callback_.get());
     EXPECT_EQ(1, RTNLHandler::GetInstance()->listeners_.size());
-    struct nlmsghdr message;
-    EXPECT_CALL(*this, Callback(&message)).Times(1);
-    listener.NotifyEvent(RTNLHandler::kRequestLink, &message);
+    RTNLMessage message;
+    EXPECT_CALL(*this, Callback(A<const RTNLMessage &>())).Times(1);
+    listener.NotifyEvent(RTNLHandler::kRequestLink, message);
   }
   EXPECT_EQ(0, RTNLHandler::GetInstance()->listeners_.size());
 }
diff --git a/rtnl_message.h b/rtnl_message.h
index 7d56e9f..dff4571 100644
--- a/rtnl_message.h
+++ b/rtnl_message.h
@@ -115,33 +115,34 @@
     ByteString Encode();
 
     // Getters and setters
-    MessageType type() { return type_; }
-    MessageMode mode() { return mode_; }
-    uint16 flags() { return flags_; }
-    uint32 seq() { return seq_; }
+    MessageType type() const { return type_; }
+    MessageMode mode() const { return mode_; }
+    uint16 flags() const { return flags_; }
+    uint32 seq() const { return seq_; }
     void set_seq(uint32 seq) { seq_ = seq; }
-    uint32 pid() { return pid_; }
-    uint32 interface_index() { return interface_index_; }
-    IPAddress::Family family() { return family_; }
+    uint32 pid() const { return pid_; }
+    uint32 interface_index() const { return interface_index_; }
+    IPAddress::Family family() const { return family_; }
 
-    const LinkStatus &link_status() { return link_status_; }
+    const LinkStatus &link_status() const { return link_status_; }
     void set_link_status(LinkStatus link_status) { link_status_ = link_status; }
-    const AddressStatus &address_status() { return address_status_; }
+    const AddressStatus &address_status() const { return address_status_; }
     void set_address_status(AddressStatus address_status) {
       address_status_ = address_status;
     }
-    const RouteStatus &route_status() { return route_status_; }
+    const RouteStatus &route_status() const { return route_status_; }
     void set_route_status(RouteStatus route_status) {
       route_status_ = route_status;
     }
     // GLint hates "unsigned short", and I don't blame it, but that's the
     // type that's used in the system headers.  Use uint16 instead and hope
     // that the conversion never ends up truncating on some strange platform.
-    bool HasAttribute(uint16 attr) {
+    bool HasAttribute(uint16 attr) const {
       return ContainsKey(attributes_, attr);
     }
-    const ByteString GetAttribute(uint16 attr) {
-      return HasAttribute(attr) ? attributes_[attr] : ByteString(0);
+    const ByteString GetAttribute(uint16 attr) const {
+      return HasAttribute(attr) ?
+          attributes_.find(attr)->second : ByteString(0);
     }
     void SetAttribute(uint16 attr, const ByteString &val) {
       attributes_[attr] = val;