shill: Create and register a Cellular device for each ModemManager.Modem.

BUG=chromium-os:17818
TEST=unit tests

Change-Id: Ic35adf35c8021f4c9689e72ddd03776948d036c1
Reviewed-on: http://gerrit.chromium.org/gerrit/4711
Reviewed-by: Darin Petkov <petkov@chromium.org>
Tested-by: Darin Petkov <petkov@chromium.org>
diff --git a/Makefile b/Makefile
index 247df7f..3124197 100644
--- a/Makefile
+++ b/Makefile
@@ -58,6 +58,7 @@
 	crypto_rot47.o \
 	dbus_adaptor.o \
 	dbus_control.o \
+	dbus_properties.o \
 	dbus_properties_proxy.o \
 	default_profile.o \
 	device.o \
@@ -112,6 +113,7 @@
 	crypto_provider_unittest.o \
 	crypto_rot47_unittest.o \
 	dbus_adaptor_unittest.o \
+	dbus_properties_unittest.o \
 	default_profile_unittest.o \
 	device_info_unittest.o \
 	device_unittest.o \
@@ -128,9 +130,11 @@
 	mock_service.o \
 	modem_info_unittest.o \
 	modem_manager_unittest.o \
+	modem_unittest.o \
 	profile_unittest.o \
 	property_accessor_unittest.o \
 	property_store_unittest.o \
+	rtnl_handler_unittest.o \
 	rtnl_listener_unittest.o \
 	service_unittest.o \
 	shill_unittest.o \
diff --git a/dbus_properties.cc b/dbus_properties.cc
new file mode 100644
index 0000000..6d639a5
--- /dev/null
+++ b/dbus_properties.cc
@@ -0,0 +1,33 @@
+// 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 "shill/dbus_properties.h"
+
+namespace shill {
+
+// static
+bool DBusProperties::GetString(const DBusPropertiesMap &properties,
+                               const std::string &key,
+                               std::string *value) {
+  DBusPropertiesMap::const_iterator it = properties.find(key);
+  if (it == properties.end()) {
+    return false;
+  }
+  *value = it->second.reader().get_string();
+  return true;
+}
+
+// static
+bool DBusProperties::GetUint32(const DBusPropertiesMap &properties,
+                               const std::string &key,
+                               uint32 *value) {
+  DBusPropertiesMap::const_iterator it = properties.find(key);
+  if (it == properties.end()) {
+    return false;
+  }
+  *value = it->second.reader().get_uint32();
+  return true;
+}
+
+}  // namespace shill
diff --git a/dbus_properties.h b/dbus_properties.h
new file mode 100644
index 0000000..ca22db5
--- /dev/null
+++ b/dbus_properties.h
@@ -0,0 +1,34 @@
+// 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.
+
+#ifndef SHILL_DBUS_PROPERTIES_
+#define SHILL_DBUS_PROPERTIES_
+
+#include <map>
+#include <string>
+
+#include <base/basictypes.h>
+#include <dbus-c++/types.h>
+
+namespace shill {
+
+typedef std::map<std::string, DBus::Variant> DBusPropertiesMap;
+
+class DBusProperties {
+ public:
+  static bool GetString(const DBusPropertiesMap &properties,
+                        const std::string &key,
+                        std::string *value);
+
+  static bool GetUint32(const DBusPropertiesMap &properties,
+                        const std::string &key,
+                        uint32 *value);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DBusProperties);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_DBUS_PROPERTIES_
diff --git a/dbus_properties_proxy.cc b/dbus_properties_proxy.cc
index 620ec58..6f43fbc 100644
--- a/dbus_properties_proxy.cc
+++ b/dbus_properties_proxy.cc
@@ -10,7 +10,6 @@
 
 namespace shill {
 
-using std::map;
 using std::string;
 using std::vector;
 
@@ -22,8 +21,7 @@
 
 DBusPropertiesProxy::~DBusPropertiesProxy() {}
 
-map<string, DBus::Variant> DBusPropertiesProxy::GetAll(
-    const string &interface_name) {
+DBusPropertiesMap DBusPropertiesProxy::GetAll(const string &interface_name) {
   return proxy_.GetAll(interface_name);
 }
 
@@ -38,13 +36,13 @@
 
 void DBusPropertiesProxy::Proxy::MmPropertiesChanged(
     const string &interface,
-    const map<string, DBus::Variant> &properties) {
+    const DBusPropertiesMap &properties) {
   LOG(INFO) << "MmPropertiesChanged: " << interface;
 }
 
 void DBusPropertiesProxy::Proxy::PropertiesChanged(
     const string &interface,
-    const map<string, DBus::Variant> &changed_properties,
+    const DBusPropertiesMap &changed_properties,
     const vector<string> &invalidated_properties) {
   LOG(INFO) << "PropertiesChanged: " << interface;
 }
diff --git a/dbus_properties_proxy.h b/dbus_properties_proxy.h
index 7b4efb9..f3ab239 100644
--- a/dbus_properties_proxy.h
+++ b/dbus_properties_proxy.h
@@ -23,8 +23,7 @@
   virtual ~DBusPropertiesProxy();
 
   // Inherited from DBusPropertiesProxyInterface.
-  virtual std::map<std::string, DBus::Variant> GetAll(
-      const std::string &interface_name);
+  virtual DBusPropertiesMap GetAll(const std::string &interface_name);
 
  private:
   class Proxy : public org::freedesktop::DBus::Properties_proxy,
@@ -38,13 +37,12 @@
 
    private:
     // Signal callbacks inherited from DBusProperties_proxy.
-    virtual void MmPropertiesChanged(
-        const std::string &interface,
-        const std::map<std::string, DBus::Variant> &properties);
+    virtual void MmPropertiesChanged(const std::string &interface,
+                                     const DBusPropertiesMap &properties);
 
     virtual void PropertiesChanged(
         const std::string &interface,
-        const std::map<std::string, DBus::Variant> &changed_properties,
+        const DBusPropertiesMap &changed_properties,
         const std::vector<std::string> &invalidated_properties);
 
     Modem *modem_;
diff --git a/dbus_properties_proxy_interface.h b/dbus_properties_proxy_interface.h
index 6f2c503..7aeea03 100644
--- a/dbus_properties_proxy_interface.h
+++ b/dbus_properties_proxy_interface.h
@@ -5,10 +5,7 @@
 #ifndef SHILL_DBUS_PROPERTIES_PROXY_INTERFACE_
 #define SHILL_DBUS_PROPERTIES_PROXY_INTERFACE_
 
-#include <map>
-#include <string>
-
-#include <dbus-c++/types.h>
+#include "shill/dbus_properties.h"
 
 namespace shill {
 
@@ -18,8 +15,7 @@
  public:
   virtual ~DBusPropertiesProxyInterface() {}
 
-  virtual std::map<std::string, DBus::Variant> GetAll(
-      const std::string &interface_name) = 0;
+  virtual DBusPropertiesMap GetAll(const std::string &interface_name) = 0;
 };
 
 }  // namespace shill
diff --git a/dbus_properties_unittest.cc b/dbus_properties_unittest.cc
new file mode 100644
index 0000000..a843734
--- /dev/null
+++ b/dbus_properties_unittest.cc
@@ -0,0 +1,43 @@
+// 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 <gtest/gtest.h>
+
+#include "shill/dbus_properties.h"
+
+using std::string;
+using testing::Test;
+
+namespace shill {
+
+class DBusPropertiesTest : public Test {
+};
+
+TEST_F(DBusPropertiesTest, GetString) {
+  static const char kTestProperty[] = "RandomKey";
+  static const char kTestValue[] = "random-value";
+  static const char kOldValue[] = "old-value";
+  string value = kOldValue;
+  DBusPropertiesMap props;
+  EXPECT_FALSE(DBusProperties::GetString(props, kTestProperty, &value));
+  EXPECT_EQ(kOldValue, value);
+  props[kTestProperty].writer().append_string(kTestValue);
+  EXPECT_TRUE(DBusProperties::GetString(props, kTestProperty, &value));
+  EXPECT_EQ(kTestValue, value);
+}
+
+TEST_F(DBusPropertiesTest, GetUint32) {
+  static const char kTestProperty[] = "AKey";
+  const uint32 kTestValue = 35;
+  const uint32 kOldValue = 74;
+  uint32 value = kOldValue;
+  DBusPropertiesMap props;
+  EXPECT_FALSE(DBusProperties::GetUint32(props, kTestProperty, &value));
+  EXPECT_EQ(kOldValue, value);
+  props[kTestProperty].writer().append_uint32(kTestValue);
+  EXPECT_TRUE(DBusProperties::GetUint32(props, kTestProperty, &value));
+  EXPECT_EQ(kTestValue, value);
+}
+
+}  // namespace shill
diff --git a/device.h b/device.h
index caa49aa..28da466 100644
--- a/device.h
+++ b/device.h
@@ -62,6 +62,9 @@
 
   std::string GetRpcIdentifier();
 
+  const std::string &link_name() const { return link_name_; }
+  int interface_index() const { return interface_index_; }
+
   const std::string &FriendlyName() const;
 
   // Returns a string that is guaranteed to uniquely identify this Device
@@ -101,7 +104,7 @@
   PropertyStore store_;
 
   std::vector<ServiceRefPtr> services_;
-  int interface_index_;
+  const int interface_index_;
   bool running_;
   const std::string link_name_;
   const std::string unique_id_;
diff --git a/mock_control.h b/mock_control.h
index 8590796..b9c2c78 100644
--- a/mock_control.h
+++ b/mock_control.h
@@ -5,6 +5,7 @@
 #ifndef SHILL_MOCK_CONTROL_
 #define SHILL_MOCK_CONTROL_
 
+#include <base/basictypes.h>
 #include <base/scoped_ptr.h>
 
 #include "shill/control_interface.h"
diff --git a/mock_dbus_properties_proxy.h b/mock_dbus_properties_proxy.h
new file mode 100644
index 0000000..18b5d6e
--- /dev/null
+++ b/mock_dbus_properties_proxy.h
@@ -0,0 +1,21 @@
+// 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.
+
+#ifndef SHILL_MOCK_DBUS_PROPERTIES_PROXY_H_
+#define SHILL_MOCK_DBUS_PROPERTIES_PROXY_H_
+
+#include <gmock/gmock.h>
+
+#include "shill/dbus_properties_proxy_interface.h"
+
+namespace shill {
+
+class MockDBusPropertiesProxy : public DBusPropertiesProxyInterface {
+ public:
+  MOCK_METHOD1(GetAll, DBusPropertiesMap(const std::string &interface_name));
+};
+
+}  // namespace shill
+
+#endif  // SHILL_MOCK_DBUS_PROPERTIES_PROXY_H_
diff --git a/mock_sockets.h b/mock_sockets.h
index cca5435..a54beef 100644
--- a/mock_sockets.h
+++ b/mock_sockets.h
@@ -16,6 +16,7 @@
   MOCK_METHOD3(Bind,
                int(int sockfd, const struct sockaddr *addr, socklen_t addrlen));
   MOCK_METHOD1(Close, int(int fd));
+  MOCK_METHOD3(Ioctl, int(int d, int request, void *argp));
   MOCK_METHOD4(Send,
                ssize_t(int sockfd, const void *buf, size_t len, int flags));
   MOCK_METHOD6(SendTo, ssize_t(int sockfd,
diff --git a/modem.cc b/modem.cc
index c640789..9c8b4ad 100644
--- a/modem.cc
+++ b/modem.cc
@@ -4,22 +4,92 @@
 
 #include "shill/modem.h"
 
-#include "shill/dbus_properties_proxy_interface.h"
+#include <base/logging.h>
+#include <mm/mm-modem.h>
+
+#include "shill/cellular.h"
+#include "shill/manager.h"
 #include "shill/proxy_factory.h"
+#include "shill/rtnl_handler.h"
+
+using std::string;
 
 namespace shill {
 
+// TODO(petkov): Consider generating these in mm/mm-modem.h.
+const char Modem::kPropertyLinkName[] = "Device";
+const char Modem::kPropertyIPMethod[] = "IpMethod";
+const char Modem::kPropertyType[] = "Type";
+const char Modem::kPropertyUnlockRequired[] = "UnlockRequired";
+const char Modem::kPropertyUnlockRetries[] = "UnlockRetries";
+
 Modem::Modem(const std::string &owner,
              const std::string &path,
              ControlInterface *control_interface,
              EventDispatcher *dispatcher,
              Manager *manager)
-    : dbus_properties_proxy_(
-        ProxyFactory::factory()->CreateDBusPropertiesProxy(this, path, owner)),
+    : owner_(owner),
+      path_(path),
       control_interface_(control_interface),
       dispatcher_(dispatcher),
-      manager_(manager) {}
+      manager_(manager) {
+  LOG(INFO) << "Modem created: " << owner << " at " << path;
+}
 
 Modem::~Modem() {}
 
+void Modem::Init() {
+  CHECK(!device_.get());
+  dbus_properties_proxy_.reset(
+      ProxyFactory::factory()->CreateDBusPropertiesProxy(this, path_, owner_));
+  // TODO(petkov): Switch to asynchronous calls (crosbug.com/17583).
+  DBusPropertiesMap properties =
+      dbus_properties_proxy_->GetAll(MM_MODEM_INTERFACE);
+  CreateCellularDevice(properties);
+}
+
+void Modem::CreateCellularDevice(const DBusPropertiesMap &properties) {
+  uint32 ip_method = kuint32max;
+  if (!DBusProperties::GetUint32(properties, kPropertyIPMethod, &ip_method) ||
+      ip_method != MM_MODEM_IP_METHOD_DHCP) {
+    LOG(ERROR) << "Unsupported IP method: " << ip_method;
+    return;
+  }
+
+  string link_name;
+  if (!DBusProperties::GetString(properties, kPropertyLinkName, &link_name)) {
+    LOG(ERROR) << "Unable to create cellular device without a link name.";
+    return;
+  }
+  int interface_index =
+      RTNLHandler::GetInstance()->GetInterfaceIndex(link_name);
+  if (interface_index < 0) {
+    LOG(ERROR) << "Unable to create cellular device -- no interface index.";
+    return;
+  }
+
+  uint32 type = 0;
+  DBusProperties::GetUint32(properties, kPropertyType, &type);
+  // TODO(petkov): Use type.
+
+  LOG(INFO) << "Creating a cellular device on link " << link_name
+            << " interface index " << interface_index << ".";
+  device_ = new Cellular(control_interface_,
+                         dispatcher_,
+                         manager_,
+                         link_name,
+                         interface_index);
+  manager_->RegisterDevice(device_);
+
+  string unlock_required;
+  if (DBusProperties::GetString(
+          properties, kPropertyUnlockRequired, &unlock_required)) {
+    uint32 unlock_retries = 0;
+    DBusProperties::GetUint32(properties,
+                              kPropertyUnlockRetries,
+                              &unlock_retries);
+    // TODO(petkov): Set these properties on the device instance.
+  }
+}
+
 }  // namespace shill
diff --git a/modem.h b/modem.h
index a8f6adf..110d8f7 100644
--- a/modem.h
+++ b/modem.h
@@ -9,17 +9,24 @@
 
 #include <base/basictypes.h>
 #include <base/memory/scoped_ptr.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "shill/dbus_properties_proxy_interface.h"
+#include "shill/refptr_types.h"
 
 namespace shill {
 
 class ControlInterface;
-class DBusPropertiesProxyInterface;
 class EventDispatcher;
 class Manager;
 
-// Handles an instance of ModemManager.Modem and an instance of CellularDevice.
+// Handles an instance of ModemManager.Modem and an instance of a Cellular
+// device.
 class Modem {
  public:
+  // |owner| is the ModemManager DBus service owner (e.g., ":1.17"). |path| is
+  // the ModemManager.Modem DBus object path (e.g.,
+  // "/org/chromium/ModemManager/Gobi/0").
   Modem(const std::string &owner,
         const std::string &path,
         ControlInterface *control_interface,
@@ -27,9 +34,32 @@
         Manager *manager);
   ~Modem();
 
+  // Initializes support for the modem, possibly constructing a Cellular device.
+  void Init();
+
  private:
+  friend class ModemManagerTest;
+  friend class ModemTest;
+  FRIEND_TEST(ModemTest, CreateCellularDevice);
+  FRIEND_TEST(ModemTest, Init);
+
+  static const char kPropertyLinkName[];
+  static const char kPropertyIPMethod[];
+  static const char kPropertyType[];
+  static const char kPropertyUnlockRequired[];
+  static const char kPropertyUnlockRetries[];
+
+  void CreateCellularDevice(const DBusPropertiesMap &properties);
+
+  // A proxy to the org.freedesktop.DBus.Properties interface used to obtain
+  // ModemManager.Modem properties and watch for property changes.
   scoped_ptr<DBusPropertiesProxyInterface> dbus_properties_proxy_;
 
+  const std::string owner_;
+  const std::string path_;
+
+  CellularRefPtr device_;
+
   ControlInterface *control_interface_;
   EventDispatcher *dispatcher_;
   Manager *manager_;
diff --git a/modem_manager.cc b/modem_manager.cc
index 2e151cd..d8c30dd 100644
--- a/modem_manager.cc
+++ b/modem_manager.cc
@@ -99,12 +99,10 @@
     LOG(INFO) << "Modem already exists; ignored.";
     return;
   }
-  shared_ptr<Modem> modem(new Modem(owner_,
-                                    path,
-                                    control_interface_,
-                                    dispatcher_,
-                                    manager_));
+  shared_ptr<Modem> modem(
+      new Modem(owner_, path, control_interface_, dispatcher_, manager_));
   modems_[path] = modem;
+  modem->Init();
 }
 
 void ModemManager::RemoveModem(const std::string &path) {
diff --git a/modem_manager_unittest.cc b/modem_manager_unittest.cc
index 3ad94ee..aa4961a 100644
--- a/modem_manager_unittest.cc
+++ b/modem_manager_unittest.cc
@@ -4,11 +4,14 @@
 
 #include <base/stl_util-inl.h>
 #include <gtest/gtest.h>
+#include <mm/mm-modem.h>
 
 #include "shill/manager.h"
 #include "shill/mock_control.h"
+#include "shill/mock_dbus_properties_proxy.h"
 #include "shill/mock_glib.h"
 #include "shill/mock_modem_manager_proxy.h"
+#include "shill/modem.h"
 #include "shill/modem_manager.h"
 #include "shill/proxy_factory.h"
 
@@ -31,27 +34,25 @@
                        &dispatcher_,
                        &manager_,
                        &glib_),
-        proxy_factory_(&proxy_) {
+        proxy_factory_(&proxy_, &dbus_properties_proxy_) {
     ProxyFactory::set_factory(&proxy_factory_);
   }
 
-  virtual void TearDown() {
-    modem_manager_.watcher_id_ = 0;
-    ModemManagerProxyInterface *proxy = modem_manager_.proxy_.release();
-    EXPECT_TRUE(proxy == NULL || proxy == &proxy_);
-    ProxyFactory::set_factory(NULL);
-  }
+  virtual void TearDown();
 
  protected:
   class TestProxyFactory : public ProxyFactory {
    public:
-    TestProxyFactory(ModemManagerProxyInterface *proxy) : proxy_(proxy) {}
+    TestProxyFactory(ModemManagerProxyInterface *proxy,
+                     DBusPropertiesProxyInterface *dbus_properties_proxy)
+        : proxy_(proxy),
+          dbus_properties_proxy_(dbus_properties_proxy) {}
 
     virtual DBusPropertiesProxyInterface *CreateDBusPropertiesProxy(
         Modem *modem,
         const string &path,
         const string &service) {
-      return NULL;
+      return dbus_properties_proxy_;
     }
 
     virtual ModemManagerProxyInterface *CreateModemManagerProxy(
@@ -63,6 +64,7 @@
 
    private:
     ModemManagerProxyInterface *proxy_;
+    DBusPropertiesProxyInterface *dbus_properties_proxy_;
   };
 
   static const char kService[];
@@ -70,12 +72,15 @@
   static const char kOwner[];
   static const char kModemPath[];
 
+  void ReleaseDBusPropertiesProxy();
+
   MockGLib glib_;
   MockControl control_interface_;
   EventDispatcher dispatcher_;
   Manager manager_;
   ModemManager modem_manager_;
   MockModemManagerProxy proxy_;
+  MockDBusPropertiesProxy dbus_properties_proxy_;
   TestProxyFactory proxy_factory_;
 };
 
@@ -84,6 +89,26 @@
 const char ModemManagerTest::kOwner[] = ":1.17";
 const char ModemManagerTest::kModemPath[] = "/org/chromium/ModemManager/Gobi/0";
 
+void ModemManagerTest::TearDown() {
+  modem_manager_.watcher_id_ = 0;
+  ModemManagerProxyInterface *proxy = modem_manager_.proxy_.release();
+  EXPECT_TRUE(proxy == NULL || proxy == &proxy_);
+  ReleaseDBusPropertiesProxy();
+  ProxyFactory::set_factory(NULL);
+}
+
+void ModemManagerTest::ReleaseDBusPropertiesProxy() {
+  if (modem_manager_.modems_.empty()) {
+    return;
+  }
+  EXPECT_EQ(1, modem_manager_.modems_.size());
+  ModemManager::Modems::iterator iter = modem_manager_.modems_.begin();
+  DBusPropertiesProxyInterface *dbus_properties_proxy =
+      iter->second->dbus_properties_proxy_.release();
+  EXPECT_TRUE(dbus_properties_proxy == NULL ||
+              dbus_properties_proxy == &dbus_properties_proxy_);
+}
+
 TEST_F(ModemManagerTest, Start) {
   const int kWatcher = 123;
   EXPECT_CALL(glib_, BusWatchName(G_BUS_TYPE_SYSTEM,
@@ -113,6 +138,8 @@
   EXPECT_EQ("", modem_manager_.owner_);
   EXPECT_CALL(proxy_, EnumerateDevices())
       .WillOnce(Return(vector<DBus::Path>(1, kModemPath)));
+  EXPECT_CALL(dbus_properties_proxy_, GetAll(MM_MODEM_INTERFACE))
+      .WillOnce(Return(DBusPropertiesMap()));
   modem_manager_.Connect(kOwner);
   EXPECT_EQ(kOwner, modem_manager_.owner_);
   EXPECT_EQ(1, modem_manager_.modems_.size());
@@ -141,9 +168,13 @@
 
 TEST_F(ModemManagerTest, AddRemoveModem) {
   modem_manager_.owner_ = kOwner;
+  EXPECT_CALL(dbus_properties_proxy_, GetAll(MM_MODEM_INTERFACE))
+      .WillOnce(Return(DBusPropertiesMap()));
   modem_manager_.AddModem(kModemPath);
   EXPECT_EQ(1, modem_manager_.modems_.size());
   EXPECT_TRUE(ContainsKey(modem_manager_.modems_, kModemPath));
+
+  ReleaseDBusPropertiesProxy();
   modem_manager_.RemoveModem(kModemPath);
   EXPECT_EQ(0, modem_manager_.modems_.size());
 }
diff --git a/modem_unittest.cc b/modem_unittest.cc
new file mode 100644
index 0000000..bb26344
--- /dev/null
+++ b/modem_unittest.cc
@@ -0,0 +1,157 @@
+// 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 <gtest/gtest.h>
+#include <mm/mm-modem.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+
+#include "shill/cellular.h"
+#include "shill/manager.h"
+#include "shill/mock_control.h"
+#include "shill/mock_dbus_properties_proxy.h"
+#include "shill/mock_glib.h"
+#include "shill/mock_sockets.h"
+#include "shill/modem.h"
+#include "shill/proxy_factory.h"
+#include "shill/rtnl_handler.h"
+#include "shill/shill_event.h"
+
+using std::string;
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::StrictMock;
+using testing::Test;
+
+namespace shill {
+
+namespace {
+
+const int kTestInterfaceIndex = 5;
+
+ACTION(SetInterfaceIndex) {
+  if (arg2) {
+    reinterpret_cast<struct ifreq *>(arg2)->ifr_ifindex = kTestInterfaceIndex;
+  }
+}
+
+}  // namespace
+
+class ModemTest : public Test {
+ public:
+  ModemTest()
+      : manager_(&control_interface_, &dispatcher_, &glib_),
+        proxy_factory_(&dbus_properties_proxy_),
+        modem_(kOwner, kPath, &control_interface_, &dispatcher_, &manager_) {}
+
+  virtual void SetUp();
+  virtual void TearDown();
+
+  void SetSockets(Sockets *sockets) {
+    RTNLHandler::GetInstance()->sockets_ = sockets;
+  }
+
+ protected:
+  class TestProxyFactory : public ProxyFactory {
+   public:
+    TestProxyFactory(DBusPropertiesProxyInterface *dbus_properties_proxy)
+        : dbus_properties_proxy_(dbus_properties_proxy) {}
+
+    virtual DBusPropertiesProxyInterface *CreateDBusPropertiesProxy(
+        Modem *modem,
+        const string &path,
+        const string &service) {
+      return dbus_properties_proxy_;
+    }
+
+   private:
+    DBusPropertiesProxyInterface *dbus_properties_proxy_;
+  };
+
+  static const char kOwner[];
+  static const char kPath[];
+
+  void ReleaseDBusPropertiesProxy();
+
+  MockGLib glib_;
+  MockControl control_interface_;
+  EventDispatcher dispatcher_;
+  Manager manager_;
+  MockDBusPropertiesProxy dbus_properties_proxy_;
+  TestProxyFactory proxy_factory_;
+  Modem modem_;
+  StrictMock<MockSockets> sockets_;
+};
+
+const char ModemTest::kOwner[] = ":1.18";
+const char ModemTest::kPath[] = "/org/chromium/ModemManager/Gobi/0";
+
+void ModemTest::SetUp() {
+  EXPECT_EQ(kOwner, modem_.owner_);
+  EXPECT_EQ(kPath, modem_.path_);
+  ProxyFactory::set_factory(&proxy_factory_);
+  SetSockets(&sockets_);
+}
+
+void ModemTest::TearDown() {
+  ReleaseDBusPropertiesProxy();
+  ProxyFactory::set_factory(NULL);
+  SetSockets(NULL);
+}
+
+void ModemTest::ReleaseDBusPropertiesProxy() {
+  DBusPropertiesProxyInterface *dbus_properties_proxy =
+      modem_.dbus_properties_proxy_.release();
+  EXPECT_TRUE(dbus_properties_proxy == NULL ||
+              dbus_properties_proxy == &dbus_properties_proxy_);
+}
+
+TEST_F(ModemTest, Init) {
+  static const char kLinkName[] = "usb1";
+  const int kTestSocket = 10;
+  DBusPropertiesMap props;
+  props[Modem::kPropertyIPMethod].writer().append_uint32(
+      MM_MODEM_IP_METHOD_DHCP);
+  props[Modem::kPropertyLinkName].writer().append_string("usb1");
+  EXPECT_CALL(dbus_properties_proxy_, GetAll(MM_MODEM_INTERFACE))
+      .WillOnce(Return(props));
+  EXPECT_CALL(sockets_, Socket(PF_INET, SOCK_DGRAM, 0)).WillOnce(Return(-1));
+  modem_.Init();
+}
+
+TEST_F(ModemTest, CreateCellularDevice) {
+  DBusPropertiesMap props;
+
+  modem_.CreateCellularDevice(props);
+  EXPECT_FALSE(modem_.device_.get());
+
+  props[Modem::kPropertyIPMethod].writer().append_uint32(
+      MM_MODEM_IP_METHOD_PPP);
+  modem_.CreateCellularDevice(props);
+  EXPECT_FALSE(modem_.device_.get());
+
+  props.erase(Modem::kPropertyIPMethod);
+  props[Modem::kPropertyIPMethod].writer().append_uint32(
+      MM_MODEM_IP_METHOD_DHCP);
+  modem_.CreateCellularDevice(props);
+  EXPECT_FALSE(modem_.device_.get());
+
+  static const char kLinkName[] = "usb0";
+  const int kTestSocket = 10;
+  props[Modem::kPropertyLinkName].writer().append_string(kLinkName);
+  EXPECT_CALL(sockets_, Socket(PF_INET, SOCK_DGRAM, 0))
+      .WillOnce(Return(kTestSocket));
+  EXPECT_CALL(sockets_, Ioctl(kTestSocket, SIOCGIFINDEX, _))
+      .WillOnce(DoAll(SetInterfaceIndex(), Return(0)));
+  EXPECT_CALL(sockets_, Close(kTestSocket))
+      .WillOnce(Return(0));
+  modem_.CreateCellularDevice(props);
+  EXPECT_TRUE(modem_.device_.get());
+  EXPECT_EQ(kLinkName, modem_.device_->link_name());
+  EXPECT_EQ(kTestInterfaceIndex, modem_.device_->interface_index());
+  // TODO(petkov): Confirm the device is register by the manager.
+}
+
+}  // namespace shill
diff --git a/rtnl_handler.cc b/rtnl_handler.cc
index 089180e..443d1cf 100644
--- a/rtnl_handler.cc
+++ b/rtnl_handler.cc
@@ -6,6 +6,7 @@
 #include <time.h>
 #include <unistd.h>
 #include <string.h>
+#include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <netinet/ether.h>
@@ -14,7 +15,6 @@
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
 #include <fcntl.h>
-#include <string>
 
 #include <base/logging.h>
 
@@ -336,4 +336,30 @@
   return AddressRequest(interface_index, RTM_DELADDR, 0, ipconfig);
 }
 
+int RTNLHandler::GetInterfaceIndex(const string &interface_name) {
+  if (interface_name.empty()) {
+    LOG(ERROR) << "Empty interface name -- unable to obtain index.";
+    return -1;
+  }
+  struct ifreq ifr;
+  if (interface_name.size() >= sizeof(ifr.ifr_name)) {
+    LOG(ERROR) << "Interface name too long: " << interface_name.size() << " >= "
+               << sizeof(ifr.ifr_name);
+    return -1;
+  }
+  int socket = sockets_->Socket(PF_INET, SOCK_DGRAM, 0);
+  if (socket < 0) {
+    PLOG(ERROR) << "Unable to open INET socket";
+    return -1;
+  }
+  ScopedSocketCloser socket_closer(sockets_, socket);
+  memset(&ifr, 0, sizeof(ifr));
+  strncpy(ifr.ifr_name, interface_name.c_str(), sizeof(ifr.ifr_name));
+  if (sockets_->Ioctl(socket, SIOCGIFINDEX, &ifr) < 0) {
+    PLOG(ERROR) << "SIOCGIFINDEX error for " << interface_name;
+    return -1;
+  }
+  return ifr.ifr_ifindex;
+}
+
 }  // namespace shill
diff --git a/rtnl_handler.h b/rtnl_handler.h
index 905676b..62a7389 100644
--- a/rtnl_handler.h
+++ b/rtnl_handler.h
@@ -5,6 +5,7 @@
 #ifndef SHILL_RTNL_HANDLER_
 #define SHILL_RTNL_HANDLER_
 
+#include <string>
 #include <vector>
 
 #include <base/callback_old.h>
@@ -49,12 +50,14 @@
   // function requires an EventDispatcher pointer so it can add itself to the
   // event loop.
   void Start(EventDispatcher *dispatcher, Sockets *sockets);
+
   // This stops the event-monitoring function of the RTNL handler
   void Stop();
 
   // Add an RTNL event listener to the list of entities that will
   // be notified of RTNL events.
   void AddListener(RTNLListener *to_add);
+
   // Remove a previously added RTNL event listener
   void RemoveListener(RTNLListener *to_remove);
 
@@ -63,20 +66,29 @@
   // 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
   // (multiple can be ORred together) are below.
   void RequestDump(int request_flags);
 
+  // Returns the index of interface |interface_name|, or -1 if unable to
+  // determine the index.
+  int GetInterfaceIndex(const std::string &interface_name);
+
  private:
   friend class DeviceInfoTest;
+  friend class ModemTest;
+  friend class RTNLHandlerTest;
   friend struct DefaultSingletonTraits<RTNLHandler>;
   FRIEND_TEST(RTNLListenerTest, NoRun);
   FRIEND_TEST(RTNLListenerTest, Run);
diff --git a/rtnl_handler_unittest.cc b/rtnl_handler_unittest.cc
new file mode 100644
index 0000000..c730423
--- /dev/null
+++ b/rtnl_handler_unittest.cc
@@ -0,0 +1,77 @@
+// 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 <string>
+
+#include <gtest/gtest.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+
+#include "shill/mock_sockets.h"
+#include "shill/rtnl_handler.h"
+
+using std::string;
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::StrictMock;
+using testing::Test;
+
+namespace shill {
+
+namespace {
+
+const int kTestInterfaceIndex = 4;
+
+ACTION(SetInterfaceIndex) {
+  if (arg2) {
+    reinterpret_cast<struct ifreq *>(arg2)->ifr_ifindex = kTestInterfaceIndex;
+  }
+}
+
+}  // namespace
+
+class RTNLHandlerTest : public Test {
+ protected:
+  void SetSockets(Sockets *sockets) {
+    RTNLHandler::GetInstance()->sockets_ = sockets;
+  }
+
+  virtual void SetUp() {
+    SetSockets(&sockets_);
+  }
+
+  virtual void TearDown() {
+    SetSockets(NULL);
+  }
+
+  StrictMock<MockSockets> sockets_;
+};
+
+TEST_F(RTNLHandlerTest, GetInterfaceName) {
+  EXPECT_EQ(-1, RTNLHandler::GetInstance()->GetInterfaceIndex(""));
+  {
+    struct ifreq ifr;
+    string name(sizeof(ifr.ifr_name), 'x');
+    EXPECT_EQ(-1, RTNLHandler::GetInstance()->GetInterfaceIndex(name));
+  }
+
+  const int kTestSocket = 123;
+  EXPECT_CALL(sockets_, Socket(PF_INET, SOCK_DGRAM, 0))
+      .Times(3)
+      .WillOnce(Return(-1))
+      .WillRepeatedly(Return(kTestSocket));
+  EXPECT_CALL(sockets_, Ioctl(kTestSocket, SIOCGIFINDEX, _))
+      .WillOnce(Return(-1))
+      .WillOnce(DoAll(SetInterfaceIndex(), Return(0)));
+  EXPECT_CALL(sockets_, Close(kTestSocket))
+      .Times(2)
+      .WillRepeatedly(Return(0));
+  EXPECT_EQ(-1, RTNLHandler::GetInstance()->GetInterfaceIndex("eth0"));
+  EXPECT_EQ(-1, RTNLHandler::GetInstance()->GetInterfaceIndex("wlan0"));
+  EXPECT_EQ(kTestInterfaceIndex,
+            RTNLHandler::GetInstance()->GetInterfaceIndex("usb0"));
+}
+
+}  // namespace shill
diff --git a/sockets.cc b/sockets.cc
index b23e021..b1662f7 100644
--- a/sockets.cc
+++ b/sockets.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <unistd.h>
 
@@ -19,6 +20,10 @@
   return close(fd);
 }
 
+int Sockets::Ioctl(int d, int request, void *argp) {
+  return ioctl(d, request, argp);
+}
+
 ssize_t Sockets::Send(int sockfd, const void *buf, size_t len, int flags) {
   return send(sockfd, buf, len, flags);
 }
@@ -32,4 +37,13 @@
   return socket(domain, type, protocol);
 }
 
+ScopedSocketCloser::ScopedSocketCloser(Sockets *sockets, int fd)
+    : sockets_(sockets),
+      fd_(fd) {}
+
+ScopedSocketCloser::~ScopedSocketCloser() {
+  sockets_->Close(fd_);
+  fd_ = -1;
+}
+
 }  // namespace shill
diff --git a/sockets.h b/sockets.h
index 833157d..bface16 100644
--- a/sockets.h
+++ b/sockets.h
@@ -7,6 +7,8 @@
 
 #include <sys/types.h>
 
+#include <base/basictypes.h>
+
 namespace shill {
 
 // A "sys/socket.h" abstraction allowing mocking in tests.
@@ -20,6 +22,9 @@
   // close
   virtual int Close(int fd);
 
+  // ioctl
+  virtual int Ioctl(int d, int request, void *argp);
+
   // send
   virtual ssize_t Send(int sockfd, const void *buf, size_t len, int flags);
 
@@ -31,6 +36,18 @@
   virtual int Socket(int domain, int type, int protocol);
 };
 
+class ScopedSocketCloser {
+ public:
+  ScopedSocketCloser(Sockets *sockets, int fd);
+  ~ScopedSocketCloser();
+
+ private:
+  Sockets *sockets_;
+  int fd_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedSocketCloser);
+};
+
 }  // namespace shill
 
 #endif  // SHILL_SOCKETS_H_