shill: cellular: Obtain IPv4 configuration from bearer.

This CL refactors the cellular code to obtain an IPv4 configuration from
an active bearer object exposed by ModemManager. ModemManager may choose
one of the IP configuration methods: static, DHCP, or PPP. If a static
IP configuration is chosen, shill bypasses DHCP and uses the IP
configuration provided by the bearer. If a PPP configuration method is
choosen, this CL changes the code to bypass DHCP as well.

BUG=chromium:233918
TEST=Tested the following:
1. Build and run unit tests.
2. Verify that shill obtains IP configuration properly under different
   bearer configurations: static, DHCP, and PPP.

Change-Id: If3a04523455a7a524110eba94cfdee632c538518
Reviewed-on: https://chromium-review.googlesource.com/184762
Reviewed-by: Prathmesh Prabhu <pprabhu@chromium.org>
Tested-by: Ben Chan <benchan@chromium.org>
Commit-Queue: Ben Chan <benchan@chromium.org>
diff --git a/cellular.cc b/cellular.cc
index 6ca43e0..1f726e2 100644
--- a/cellular.cc
+++ b/cellular.cc
@@ -19,6 +19,7 @@
 #include <mobile_provider.h>
 
 #include "shill/adaptor_interfaces.h"
+#include "shill/cellular_bearer.h"
 #include "shill/cellular_capability_cdma.h"
 #include "shill/cellular_capability_gsm.h"
 #include "shill/cellular_capability_universal.h"
@@ -768,6 +769,14 @@
 void Cellular::EstablishLink() {
   SLOG(Cellular, 2) << __func__;
   CHECK_EQ(kStateConnected, state_);
+
+  CellularBearer *bearer = capability_->GetActiveBearer();
+  if (bearer && bearer->ipv4_config_method() == IPConfig::kMethodPPP) {
+    LOG(INFO) << "Start PPP connection on " << bearer->data_interface();
+    StartPPP(bearer->data_interface());
+    return;
+  }
+
   unsigned int flags = 0;
   if (manager()->device_info()->GetFlags(interface_index(), &flags) &&
       (flags & IFF_UP) != 0) {
@@ -791,13 +800,31 @@
   if ((flags & IFF_UP) != 0 && state_ == kStateConnected) {
     LOG(INFO) << link_name() << " is up.";
     SetState(kStateLinked);
-    if (AcquireIPConfig()) {
+
+    // TODO(benchan): IPv6 support is currently disabled for cellular devices.
+    // Check and obtain IPv6 configuration from the bearer when we later enable
+    // IPv6 support on cellular devices.
+    CellularBearer *bearer = capability_->GetActiveBearer();
+    if (bearer && bearer->ipv4_config_method() == IPConfig::kMethodStatic) {
+      SLOG(Cellular, 2) << "Assign static IP configuration from bearer.";
       SelectService(service_);
       SetServiceState(Service::kStateConfiguring);
-    } else {
-      LOG(ERROR) << "Unable to acquire DHCP config.";
+      AssignIPConfig(*bearer->ipv4_config_properties());
+      return;
     }
-  } else if ((flags & IFF_UP) == 0 && state_ == kStateLinked) {
+
+    if (AcquireIPConfig()) {
+      SLOG(Cellular, 2) << "Start DHCP to acquire IP configuration.";
+      SelectService(service_);
+      SetServiceState(Service::kStateConfiguring);
+      return;
+    }
+
+    LOG(ERROR) << "Unable to acquire IP configuration over DHCP.";
+    return;
+  }
+
+  if ((flags & IFF_UP) == 0 && state_ == kStateLinked) {
     LOG(INFO) << link_name() << " is down.";
     SetState(kStateConnected);
     DropConnection();
diff --git a/cellular.h b/cellular.h
index afa49b1..dcec319 100644
--- a/cellular.h
+++ b/cellular.h
@@ -240,6 +240,7 @@
 
   // getters
   const std::string &dbus_owner() const { return dbus_owner_; }
+  const std::string &dbus_service() const { return dbus_service_; }
   const std::string &dbus_path() const { return dbus_path_; }
   const Operator &home_provider() const { return home_provider_; }
   const std::string &carrier() const { return carrier_; }
@@ -355,6 +356,9 @@
   FRIEND_TEST(CellularTest, DropConnection);
   FRIEND_TEST(CellularTest, DropConnectionPPP);
   FRIEND_TEST(CellularTest, EnableTrafficMonitor);
+  FRIEND_TEST(CellularTest, EstablishLinkDHCP);
+  FRIEND_TEST(CellularTest, EstablishLinkPPP);
+  FRIEND_TEST(CellularTest, EstablishLinkStatic);
   FRIEND_TEST(CellularTest,
               HandleNewRegistrationStateForServiceRequiringActivation);
   FRIEND_TEST(CellularTest, LinkEventUpWithPPP);
diff --git a/cellular_bearer.h b/cellular_bearer.h
index 098677f..2ab9200 100644
--- a/cellular_bearer.h
+++ b/cellular_bearer.h
@@ -10,6 +10,7 @@
 
 #include <base/basictypes.h>
 #include <base/memory/scoped_ptr.h>
+#include <gtest/gtest_prod.h>
 
 #include "shill/dbus_properties.h"
 #include "shill/ipconfig.h"
@@ -63,6 +64,9 @@
 
  private:
   friend class CellularBearerTest;
+  FRIEND_TEST(CellularTest, EstablishLinkDHCP);
+  FRIEND_TEST(CellularTest, EstablishLinkPPP);
+  FRIEND_TEST(CellularTest, EstablishLinkStatic);
 
   // Gets the IP configuration method and properties from |properties|.
   // |address_family| specifies the IP address family of the configuration.
diff --git a/cellular_capability.cc b/cellular_capability.cc
index 5a136ab..3524887 100644
--- a/cellular_capability.cc
+++ b/cellular_capability.cc
@@ -103,6 +103,10 @@
   OnUnsupportedOperation(__func__, error);
 }
 
+CellularBearer *CellularCapability::GetActiveBearer() const {
+  return NULL;
+}
+
 bool CellularCapability::IsActivating() const {
   return false;
 }
diff --git a/cellular_capability.h b/cellular_capability.h
index ee995b7..1f12291 100644
--- a/cellular_capability.h
+++ b/cellular_capability.h
@@ -21,6 +21,7 @@
 namespace shill {
 
 class Cellular;
+class CellularBearer;
 class Error;
 class ModemInfo;
 class ProxyFactory;
@@ -173,6 +174,11 @@
   // TODO(jglasgow): Implement real error handling.
   virtual void Scan(Error *error, const ResultStringmapsCallback &callback);
 
+  // Returns a pointer to the current active bearer object or NULL if no active
+  // bearer exists. The returned bearer object is managed by this capability
+  // object. This implementation returns NULL by default.
+  virtual CellularBearer *GetActiveBearer() const;
+
   // Returns an empty string if the network technology is unknown.
   virtual std::string GetNetworkTechnologyString() const = 0;
 
@@ -222,7 +228,7 @@
   friend class CellularCapabilityUniversalCDMATest;
   friend class CellularTest;
   FRIEND_TEST(CellularCapabilityTest, AllowRoaming);
-  FRIEND_TEST(CellularCapabilityUniversalMainTest, UpdateActiveBearerPath);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, UpdateActiveBearer);
   FRIEND_TEST(CellularTest, Connect);
   FRIEND_TEST(CellularTest, TearDown);
 
diff --git a/cellular_capability_universal.cc b/cellular_capability_universal.cc
index e2896ba..7374eb0 100644
--- a/cellular_capability_universal.cc
+++ b/cellular_capability_universal.cc
@@ -16,6 +16,7 @@
 #include <vector>
 
 #include "shill/adaptor_interfaces.h"
+#include "shill/cellular_bearer.h"
 #include "shill/cellular_service.h"
 #include "shill/dbus_properties_proxy_interface.h"
 #include "shill/error.h"
@@ -65,7 +66,6 @@
     "operator-code";
 const char CellularCapabilityUniversal::kOperatorAccessTechnologyProperty[] =
     "access-technology";
-const char CellularCapabilityUniversal::kIpConfigPropertyMethod[] = "method";
 const char CellularCapabilityUniversal::kALT3100ModelId[] = "ALT3100";
 const char CellularCapabilityUniversal::kE362ModelId[] = "E362 WWAN";
 const int CellularCapabilityUniversal::kSetPowerStateTimeoutMilliseconds =
@@ -385,12 +385,11 @@
     SLOG(Cellular, 1) << "Processed deferred registration loss before "
                       << "disconnect request.";
   }
-  if (active_bearer_path_.empty()) {
-    LOG(WARNING) << "In " << __func__ << "(): "
-                 << "Ignoring attempt to disconnect without bearer";
-  } else if (modem_simple_proxy_.get()) {
-    SLOG(Cellular, 2) << "Disconnect bearer " << active_bearer_path_;
-    modem_simple_proxy_->Disconnect(active_bearer_path_,
+  if (modem_simple_proxy_.get()) {
+    SLOG(Cellular, 2) << "Disconnect all bearers.";
+    // If "/" is passed as the bearer path, ModemManager will disconnect all
+    // bearers.
+    modem_simple_proxy_->Disconnect(DBus::Path(kRootPath),
                                     error,
                                     callback,
                                     kTimeoutDisconnect);
@@ -788,8 +787,7 @@
       service->SetLastGoodApn(apn_try_list_.front());
       apn_try_list_.clear();
     }
-    active_bearer_path_ = path;
-    SLOG(Cellular, 2) << "Connected bearer " << active_bearer_path_;
+    SLOG(Cellular, 2) << "Connected bearer " << path;
   }
 
   if (!callback.is_null())
@@ -1011,78 +1009,30 @@
   }
 }
 
-void CellularCapabilityUniversal::UpdateActiveBearerPath() {
+void CellularCapabilityUniversal::UpdateActiveBearer() {
+  SLOG(Cellular, 3) << __func__;
+
   // Look for the first active bearer and use its path as the connected
   // one. Right now, we don't allow more than one active bearer.
-  DBus::Path new_bearer_path;
-  uint32 ipconfig_method(MM_BEARER_IP_METHOD_UNKNOWN);
-  string network_device;
-
+  active_bearer_.reset();
   for (const auto &path : bearer_paths_) {
-    scoped_ptr<DBusPropertiesProxyInterface> properties_proxy(
-        proxy_factory()->CreateDBusPropertiesProxy(path,
-                                                   cellular()->dbus_owner()));
-    DBusPropertiesMap properties(
-        properties_proxy->GetAll(MM_DBUS_INTERFACE_BEARER));
-    if (properties.empty()) {
-      LOG(WARNING) << "Could not get properties of bearer \"" << path
-                   << "\". Bearer is likely gone and thus ignored.";
+    scoped_ptr<CellularBearer> bearer(
+        new CellularBearer(proxy_factory(), path, cellular()->dbus_service()));
+    // The bearer object may have vanished before ModemManager updates the
+    // 'Bearers' property.
+    if (!bearer->Init())
       continue;
-    }
 
-    bool connected = false;
-    if (!DBusProperties::GetBool(
-            properties, MM_BEARER_PROPERTY_CONNECTED, &connected)) {
-      SLOG(Cellular, 2) << "Bearer does not indicate whether it is connected "
-                           "or not. Assume it is not connected.";
+    if (!bearer->connected())
       continue;
-    }
-
-    if (!connected) {
-      continue;
-    }
 
     SLOG(Cellular, 2) << "Found active bearer \"" << path << "\".";
-    CHECK(new_bearer_path.empty()) << "Found more than one active bearer.";
-
-    if (DBusProperties::GetString(
-            properties, MM_BEARER_PROPERTY_INTERFACE, &network_device)) {
-      SLOG(Cellular, 2) << "Bearer uses network interface \"" << network_device
-                        << "\".";
-    } else {
-      SLOG(Cellular, 2) << "Bearer does not specify network interface.";
-    }
-
-    // TODO(quiche): Add support for scenarios where the bearer is
-    // IPv6 only, or where there are conflicting configuration methods
-    // for IPv4 and IPv6. crbug.com/248360.
-    DBusPropertiesMap bearer_ip4config;
-    DBusProperties::GetDBusPropertiesMap(
-        properties, MM_BEARER_PROPERTY_IP4CONFIG, &bearer_ip4config);
-
-    if (DBusProperties::GetUint32(
-            bearer_ip4config, kIpConfigPropertyMethod, &ipconfig_method)) {
-      SLOG(Cellular, 2) << "Bearer has IPv4 config method " << ipconfig_method;
-    } else {
-      SLOG(Cellular, 2) << "Bearer does not specify IPv4 config method.";
-      for (const auto &i : bearer_ip4config) {
-        SLOG(Cellular, 5) << "Bearer IPv4 config has key \""
-                          << i.first
-                          << "\".";
-      }
-    }
-    new_bearer_path = path;
+    CHECK(!active_bearer_) << "Found more than one active bearer.";
+    active_bearer_ = bearer.Pass();
   }
-  active_bearer_path_ = new_bearer_path;
-  if (new_bearer_path.empty()) {
+
+  if (!active_bearer_)
     SLOG(Cellular, 2) << "No active bearer found.";
-    return;
-  }
-
-  // TODO(benchan): Move the following code outside this method.
-  if (ipconfig_method == MM_BEARER_IP_METHOD_PPP) {
-    cellular()->StartPPP(network_device);
-  }
 }
 
 void CellularCapabilityUniversal::InitAPNList() {
@@ -1391,6 +1341,10 @@
   return parsed;
 }
 
+CellularBearer *CellularCapabilityUniversal::GetActiveBearer() const {
+  return active_bearer_.get();
+}
+
 string CellularCapabilityUniversal::GetNetworkTechnologyString() const {
   // If we know that the modem is an E362, return LTE here to make sure that
   // Chrome sees LTE as the network technology even if the actual technology is
@@ -1435,8 +1389,8 @@
     const vector<string> &/* invalidated_properties */) {
 
   // Update the bearers property before the modem state property as
-  // OnModemStateChanged may call UpdateActiveBearerPath, which reads the
-  // bearers property.
+  // OnModemStateChanged may call UpdateActiveBearer, which reads the bearers
+  // property.
   RpcIdentifiers bearers;
   if (DBusProperties::GetRpcIdentifiers(properties, MM_MODEM_PROPERTY_BEARERS,
                                         &bearers)) {
@@ -1672,6 +1626,13 @@
     Cellular::ModemState state) {
   SLOG(Cellular, 3) << __func__ << ": " << Cellular::GetModemStateString(state);
 
+  if (state == Cellular::kModemStateConnected) {
+    // This assumes that ModemManager updates the Bearers list and the Bearer
+    // properties before changing Modem state to Connected.
+    SLOG(Cellular, 2) << "Update active bearer.";
+    UpdateActiveBearer();
+  }
+
   cellular()->OnModemStateChanged(state);
   // TODO(armansito): Move the deferred enable logic to Cellular
   // (See crbug.com/279499).
@@ -1680,15 +1641,6 @@
     SLOG(Cellular, 2) << "Enabling modem after deferring.";
     deferred_enable_modem_callback_.Run();
     deferred_enable_modem_callback_.Reset();
-  } else if (state == Cellular::kModemStateConnected) {
-    // This assumes that ModemManager updates the Bearers list and the Bearer
-    // properties before changing Modem state to Connected.
-    //
-    // TODO(benchan): Instead of explicitly querying the properties of bearers
-    // to determine the active bearer, refactor the bearer related code into a
-    // proper bearer class that observes DBus properties changes.
-    SLOG(Cellular, 2) << "Updating bearer path to reflect the active bearer.";
-    UpdateActiveBearerPath();
   }
 }
 
diff --git a/cellular_capability_universal.h b/cellular_capability_universal.h
index 03dd688..cf75b31 100644
--- a/cellular_capability_universal.h
+++ b/cellular_capability_universal.h
@@ -29,6 +29,7 @@
 
 namespace shill {
 
+class CellularBearer;
 class ModemInfo;
 
 // CellularCapabilityUniversal handles modems using the
@@ -98,6 +99,7 @@
   virtual void Reset(Error *error, const ResultCallback &callback);
 
   virtual void Scan(Error *error, const ResultStringmapsCallback &callback);
+  virtual CellularBearer *GetActiveBearer() const;
   virtual std::string GetNetworkTechnologyString() const;
   virtual std::string GetRoamingStateString() const;
   virtual void GetSignalQuality();
@@ -155,9 +157,6 @@
   static const char kOperatorCodeProperty[];
   static const char kOperatorAccessTechnologyProperty[];
 
-  // As above, consider having this in ModemManager-names.h.
-  static const char kIpConfigPropertyMethod[];
-
   // Modem Model ID strings.  From modem firmware via modemmanager.
   static const char kALT3100ModelId[];
   static const char kE362ModelId[];
@@ -229,7 +228,7 @@
   FRIEND_TEST(CellularCapabilityUniversalMainTest, TerminationAction);
   FRIEND_TEST(CellularCapabilityUniversalMainTest,
               TerminationActionRemovedByStopModem);
-  FRIEND_TEST(CellularCapabilityUniversalMainTest, UpdateActiveBearerPath);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, UpdateActiveBearer);
   FRIEND_TEST(CellularCapabilityUniversalMainTest,
               UpdatePendingActivationState);
   FRIEND_TEST(CellularCapabilityUniversalMainTest,
@@ -310,8 +309,8 @@
   // |home_provider_info_|.
   void InitAPNList();
 
-  // Updates |active_bearer_path_| to match the currently active bearer.
-  void UpdateActiveBearerPath();
+  // Updates |active_bearer_| to match the currently active bearer.
+  void UpdateActiveBearer();
 
   Stringmap ParseScanResult(const ScanResult &result);
 
@@ -447,7 +446,7 @@
   SimLockStatus sim_lock_status_;
   SubscriptionState subscription_state_;
   std::string sim_path_;
-  DBus::Path active_bearer_path_;
+  scoped_ptr<CellularBearer> active_bearer_;
   RpcIdentifiers bearer_paths_;
   bool reset_done_;
 
diff --git a/cellular_capability_universal_cdma.cc b/cellular_capability_universal_cdma.cc
index 059e98b..df1e589 100644
--- a/cellular_capability_universal_cdma.cc
+++ b/cellular_capability_universal_cdma.cc
@@ -9,6 +9,7 @@
 #include <base/string_number_conversions.h>
 #include <base/string_util.h>
 
+#include "shill/cellular_bearer.h"
 #include "shill/cellular_operator_info.h"
 #include "shill/dbus_properties_proxy_interface.h"
 #include "shill/error.h"
diff --git a/cellular_capability_universal_unittest.cc b/cellular_capability_universal_unittest.cc
index b4ec6ac..360a82d 100644
--- a/cellular_capability_universal_unittest.cc
+++ b/cellular_capability_universal_unittest.cc
@@ -17,6 +17,7 @@
 #include <ModemManager/ModemManager.h>
 
 #include "shill/cellular.h"
+#include "shill/cellular_bearer.h"
 #include "shill/cellular_service.h"
 #include "shill/dbus_adaptor.h"
 #include "shill/error.h"
@@ -232,8 +233,7 @@
           .append_string("/dev/fake");
 
       DBusPropertiesMap ip4config;
-      ip4config[CellularCapabilityUniversal::kIpConfigPropertyMethod].writer()
-          .append_uint32(MM_BEARER_IP_METHOD_DHCP);
+      ip4config["method"].writer().append_uint32(MM_BEARER_IP_METHOD_DHCP);
       DBus::MessageIter writer =
           active_bearer_properties_[MM_BEARER_PROPERTY_IP4CONFIG].writer();
       writer << ip4config;
@@ -613,7 +613,6 @@
 TEST_F(CellularCapabilityUniversalMainTest, DisconnectNoProxy) {
   Error error;
   ResultCallback disconnect_callback;
-  capability_->active_bearer_path_ = "/foo";
   EXPECT_CALL(*modem_simple_proxy_,
               Disconnect(_, _, _, CellularCapability::kTimeoutDisconnect))
       .Times(0);
@@ -624,7 +623,6 @@
 TEST_F(CellularCapabilityUniversalMainTest, DisconnectWithDeferredCallback) {
   Error error;
   ResultCallback disconnect_callback;
-  capability_->active_bearer_path_ = "/foo";
   EXPECT_CALL(*modem_simple_proxy_,
               Disconnect(_, _, _, CellularCapability::kTimeoutDisconnect));
   SetSimpleProxy();
@@ -1241,7 +1239,7 @@
   EXPECT_FALSE(capability_->resetting_);
 }
 
-TEST_F(CellularCapabilityUniversalMainTest, UpdateActiveBearerPath) {
+TEST_F(CellularCapabilityUniversalMainTest, UpdateActiveBearer) {
   // Common resources.
   const size_t kPathCount = 3;
   DBus::Path active_paths[kPathCount], inactive_paths[kPathCount];
@@ -1251,26 +1249,26 @@
         base::StringPrintf("%s/%zu", kInactiveBearerPathPrefix, i);
   }
 
-  EXPECT_TRUE(capability_->active_bearer_path_.empty());
+  EXPECT_TRUE(capability_->GetActiveBearer() == NULL);
 
-  // Check that |active_bearer_path_| is set correctly when an active bearer
-  // is returned.
+  // Check that |active_bearer_| is set correctly when an active bearer is
+  // returned.
   capability_->OnBearersChanged({inactive_paths[0],
                                  inactive_paths[1],
                                  active_paths[2],
                                  inactive_paths[1],
                                  inactive_paths[2]});
-  capability_->UpdateActiveBearerPath();
-  EXPECT_EQ(active_paths[2], capability_->active_bearer_path_);
+  capability_->UpdateActiveBearer();
+  ASSERT_TRUE(capability_->GetActiveBearer() != NULL);
+  EXPECT_EQ(active_paths[2], capability_->GetActiveBearer()->dbus_path());
 
-  // Check that |active_bearer_path_| is empty if no active bearers are
-  // returned.
+  // Check that |active_bearer_| is NULL if no active bearers are returned.
   capability_->OnBearersChanged({inactive_paths[0],
                                  inactive_paths[1],
                                  inactive_paths[2],
                                  inactive_paths[1]});
-  capability_->UpdateActiveBearerPath();
-  EXPECT_TRUE(capability_->active_bearer_path_.empty());
+  capability_->UpdateActiveBearer();
+  EXPECT_TRUE(capability_->GetActiveBearer() == NULL);
 
   // Check that returning multiple bearers causes death.
   capability_->OnBearersChanged({active_paths[0],
@@ -1278,37 +1276,12 @@
                                  inactive_paths[2],
                                  active_paths[1],
                                  inactive_paths[1]});
-  EXPECT_DEATH(capability_->UpdateActiveBearerPath(),
+  EXPECT_DEATH(capability_->UpdateActiveBearer(),
                "Found more than one active bearer.");
 
   capability_->OnBearersChanged({});
-  capability_->UpdateActiveBearerPath();
-  EXPECT_TRUE(capability_->active_bearer_path_.empty());
-
-  // By default, Bearers created by this test fixture indicate that
-  // they use DHCP for IP configuration. Hence, we should not start
-  // PPP for them...
-  scoped_refptr<MockCellular> mock_cellular =
-      new MockCellular(&modem_info_, "", "", -1, Cellular::kTypeUniversal,
-                       "", "", "", &proxy_factory_);
-  capability_->cellular_ = mock_cellular;
-  EXPECT_CALL(*mock_cellular, StartPPP(_)).Times(0);
-  capability_->OnBearersChanged({active_paths[2]});
-  capability_->UpdateActiveBearerPath();
-
-  // ...but if we change the Bearer to PPP, then we should StartPPP.
-  DBusPropertiesMap ip4config;
-  DBusPropertiesMap &bearer_properties(
-      *proxy_factory_.mutable_active_bearer_properties());
-  ip4config[CellularCapabilityUniversal::kIpConfigPropertyMethod].writer()
-      .append_uint32(MM_BEARER_IP_METHOD_PPP);
-  bearer_properties.erase(MM_BEARER_PROPERTY_IP4CONFIG);
-  DBus::MessageIter writer =
-      bearer_properties[MM_BEARER_PROPERTY_IP4CONFIG].writer();
-  writer << ip4config;
-  EXPECT_CALL(*mock_cellular, StartPPP(_)).Times(1);
-  capability_->OnBearersChanged({active_paths[2]});
-  capability_->UpdateActiveBearerPath();
+  capability_->UpdateActiveBearer();
+  EXPECT_TRUE(capability_->GetActiveBearer() == NULL);
 }
 
 // Validates expected behavior of Connect function
diff --git a/cellular_unittest.cc b/cellular_unittest.cc
index cf91afa..7fd6c16 100644
--- a/cellular_unittest.cc
+++ b/cellular_unittest.cc
@@ -11,6 +11,7 @@
 #include <base/bind.h>
 #include <chromeos/dbus/service_constants.h>
 
+#include "shill/cellular_bearer.h"
 #include "shill/cellular_capability_cdma.h"
 #include "shill/cellular_capability_classic.h"
 #include "shill/cellular_capability_gsm.h"
@@ -354,7 +355,6 @@
         .WillOnce(Invoke(this, &CellularTest::InvokeDisconnectMM1));
     GetCapabilityUniversal()->modem_simple_proxy_.reset(
         mm1_simple_proxy_.release());
-    GetCapabilityUniversal()->active_bearer_path_ = "/fake/path";
   }
 
   void VerifyDisconnect() {
@@ -544,6 +544,12 @@
     device_->enabled_persistent_ = new_value;
   }
 
+  void SetCapabilityUniversalActiveBearer(scoped_ptr<CellularBearer> bearer) {
+    SetCellularType(Cellular::kTypeUniversal);
+    CellularCapabilityUniversal *capability = GetCapabilityUniversal();
+    capability->active_bearer_ = bearer.Pass();
+  }
+
   EventDispatcher dispatcher_;
   MockModemInfo modem_info_;
   MockDeviceInfo device_info_;
@@ -1679,4 +1685,86 @@
   EXPECT_EQ(kTestNetworksCellular, device_->found_networks());
 }
 
+TEST_F(CellularTest, EstablishLinkDHCP) {
+  scoped_ptr<CellularBearer> bearer(
+      new CellularBearer(&proxy_factory_, "", ""));
+  bearer->set_ipv4_config_method(IPConfig::kMethodDHCP);
+  SetCapabilityUniversalActiveBearer(bearer.Pass());
+  device_->state_ = Cellular::kStateConnected;
+
+  MockCellularService *service = SetMockService();
+  ON_CALL(*service, state()).WillByDefault(Return(Service::kStateUnknown));
+
+  EXPECT_CALL(device_info_, GetFlags(device_->interface_index(), _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(IFF_UP), Return(true)));
+  EXPECT_CALL(dhcp_provider_, CreateConfig(kTestDeviceName, _, _, _, _))
+      .WillOnce(Return(dhcp_config_));
+  EXPECT_CALL(*dhcp_config_, RequestIP()).WillOnce(Return(true));
+  EXPECT_CALL(*service, SetState(Service::kStateConfiguring));
+  device_->EstablishLink();
+  EXPECT_EQ(service, device_->selected_service());
+  Mock::VerifyAndClearExpectations(service);  // before Cellular dtor
+}
+
+TEST_F(CellularTest, EstablishLinkPPP) {
+  scoped_ptr<CellularBearer> bearer(
+      new CellularBearer(&proxy_factory_, "", ""));
+  bearer->set_ipv4_config_method(IPConfig::kMethodPPP);
+  SetCapabilityUniversalActiveBearer(bearer.Pass());
+  device_->state_ = Cellular::kStateConnected;
+
+  const int kPID = 123;
+  MockGLib &mock_glib(*dynamic_cast<MockGLib *>(modem_info_.glib()));
+  EXPECT_CALL(mock_glib, ChildWatchAdd(kPID, _, _));
+  EXPECT_CALL(mock_glib, SpawnAsync(_, _, _, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgumentPointee<6>(kPID), Return(true)));
+  device_->EstablishLink();
+  EXPECT_FALSE(device_->ipconfig());  // No DHCP client.
+  EXPECT_FALSE(device_->selected_service());
+  EXPECT_FALSE(device_->is_ppp_authenticating_);
+  EXPECT_TRUE(device_->ppp_task_);
+}
+
+TEST_F(CellularTest, EstablishLinkStatic) {
+  IPAddress::Family kAddressFamily = IPAddress::kFamilyIPv4;
+  const char kAddress[] = "10.0.0.1";
+  const char kGateway[] = "10.0.0.254";
+  const int32 kSubnetPrefix = 16;
+  const char *const kDNS[] = {"10.0.0.2", "8.8.4.4", "8.8.8.8"};
+
+  scoped_ptr<IPConfig::Properties> ipconfig_properties(
+      new IPConfig::Properties);
+  ipconfig_properties->address_family = kAddressFamily;
+  ipconfig_properties->address = kAddress;
+  ipconfig_properties->gateway = kGateway;
+  ipconfig_properties->subnet_prefix = kSubnetPrefix;
+  ipconfig_properties->dns_servers = vector<string>{kDNS[0], kDNS[1], kDNS[2]};
+
+  scoped_ptr<CellularBearer> bearer(
+      new CellularBearer(&proxy_factory_, "", ""));
+  bearer->set_ipv4_config_method(IPConfig::kMethodStatic);
+  bearer->set_ipv4_config_properties(ipconfig_properties.Pass());
+  SetCapabilityUniversalActiveBearer(bearer.Pass());
+  device_->state_ = Cellular::kStateConnected;
+
+  MockCellularService *service = SetMockService();
+  ON_CALL(*service, state()).WillByDefault(Return(Service::kStateUnknown));
+
+  EXPECT_CALL(device_info_, GetFlags(device_->interface_index(), _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(IFF_UP), Return(true)));
+  EXPECT_CALL(*service, SetState(Service::kStateConfiguring));
+  device_->EstablishLink();
+  EXPECT_EQ(service, device_->selected_service());
+  ASSERT_TRUE(device_->ipconfig());
+  EXPECT_EQ(kAddressFamily, device_->ipconfig()->properties().address_family);
+  EXPECT_EQ(kAddress, device_->ipconfig()->properties().address);
+  EXPECT_EQ(kGateway, device_->ipconfig()->properties().gateway);
+  EXPECT_EQ(kSubnetPrefix, device_->ipconfig()->properties().subnet_prefix);
+  ASSERT_EQ(3, device_->ipconfig()->properties().dns_servers.size());
+  EXPECT_EQ(kDNS[0], device_->ipconfig()->properties().dns_servers[0]);
+  EXPECT_EQ(kDNS[1], device_->ipconfig()->properties().dns_servers[1]);
+  EXPECT_EQ(kDNS[2], device_->ipconfig()->properties().dns_servers[2]);
+  Mock::VerifyAndClearExpectations(service);  // before Cellular dtor
+}
+
 }  // namespace shill
diff --git a/device.cc b/device.cc
index 135fb09..23b7534 100644
--- a/device.cc
+++ b/device.cc
@@ -427,6 +427,15 @@
   return ipconfig_->RequestIP();
 }
 
+void Device::AssignIPConfig(const IPConfig::Properties &properties) {
+  DestroyIPConfig();
+  EnableIPv6();
+  ipconfig_ = new IPConfig(control_interface_, link_name_);
+  ipconfig_->set_properties(properties);
+  dispatcher_->PostTask(Bind(&Device::OnIPConfigUpdated,
+                             weak_ptr_factory_.GetWeakPtr(), ipconfig_));
+}
+
 void Device::DestroyIPConfigLease(const string &name) {
   dhcp_provider_->DestroyLease(name);
 }
diff --git a/device.h b/device.h
index 618adba..00eff35 100644
--- a/device.h
+++ b/device.h
@@ -360,6 +360,9 @@
   // request was successfully sent.
   bool AcquireIPConfigWithLeaseName(const std::string &lease_name);
 
+  // Assigns the IP configuration |properties| to |ipconfig_|.
+  void AssignIPConfig(const IPConfig::Properties &properties);
+
   // Callback invoked on successful IP configuration updates.
   void OnIPConfigUpdated(const IPConfigRefPtr &ipconfig);