shill: Add support for detecting PPP devices

Use the device ARP Hardware Type to detect both PPP devices.  Fix
up loopback to use the same.  Also add unit tests for
DeviceInfo::GetDeviceTechnology().

BUG=chromium-os:29771
TEST=New unit tests.  Manual: Run on device for loopback, ppp, tunnel and wifi

Change-Id: I8b39bcf68926757d10756b9cbc1692ec891e117f
Reviewed-on: https://gerrit.chromium.org/gerrit/20802
Reviewed-by: Darin Petkov <petkov@chromium.org>
Commit-Ready: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/device_info.cc b/device_info.cc
index 7cf2104..d153ade 100644
--- a/device_info.cc
+++ b/device_info.cc
@@ -52,14 +52,14 @@
 namespace shill {
 
 // static
+const char DeviceInfo::kDeviceInfoRoot[] = "/sys/class/net";
 const char DeviceInfo::kDriverVirtioNet[] = "virtio_net";
-const char DeviceInfo::kInterfaceUevent[] = "/sys/class/net/%s/uevent";
+const char DeviceInfo::kInterfaceUevent[] = "uevent";
 const char DeviceInfo::kInterfaceUeventWifiSignature[] = "DEVTYPE=wlan\n";
-const char DeviceInfo::kInterfaceDevice[] = "/sys/class/net/%s/device";
-const char DeviceInfo::kInterfaceDriver[] = "/sys/class/net/%s/device/driver";
-const char DeviceInfo::kInterfaceTunFlags[] = "/sys/class/net/%s/tun_flags";
-const char DeviceInfo::kInterfaceType[] = "/sys/class/net/%s/type";
-const char DeviceInfo::kLoopbackDeviceName[] = "lo";
+const char DeviceInfo::kInterfaceDevice[] = "device";
+const char DeviceInfo::kInterfaceDriver[] = "device/driver";
+const char DeviceInfo::kInterfaceTunFlags[] = "tun_flags";
+const char DeviceInfo::kInterfaceType[] = "type";
 const char DeviceInfo::kDriverCdcEther[] = "cdc_ether";
 const char *DeviceInfo::kModemDrivers[] = {
     "gobi",
@@ -80,6 +80,7 @@
       address_callback_(Bind(&DeviceInfo::AddressMsgHandler, Unretained(this))),
       link_listener_(NULL),
       address_listener_(NULL),
+      device_info_root_(kDeviceInfoRoot),
       rtnl_handler_(RTNLHandler::GetInstance()) {
 }
 
@@ -138,12 +139,36 @@
   }
 }
 
-// static
+FilePath DeviceInfo::GetDeviceInfoPath(const string &iface_name,
+                                       const string &path_name) {
+  return device_info_root_.Append(iface_name).Append(path_name);
+}
+
+bool DeviceInfo::GetDeviceInfoContents(const string &iface_name,
+                                       const string &path_name,
+                                       string *contents_out) {
+  return file_util::ReadFileToString(GetDeviceInfoPath(iface_name, path_name),
+                                     contents_out);
+}
+bool DeviceInfo::GetDeviceInfoSymbolicLink(const string &iface_name,
+                                      const string &path_name,
+                                      FilePath *path_out) {
+  return file_util::ReadSymbolicLink(GetDeviceInfoPath(iface_name, path_name),
+                                     path_out);
+}
+
 Technology::Identifier DeviceInfo::GetDeviceTechnology(
     const string &iface_name) {
-  FilePath uevent_file(StringPrintf(kInterfaceUevent, iface_name.c_str()));
+  string type_string;
+  int arp_type = ARPHRD_VOID;;
+  if (GetDeviceInfoContents(iface_name, kInterfaceType, &type_string) &&
+      TrimString(type_string, "\n", &type_string) &&
+      !base::StringToInt(type_string, &arp_type)) {
+    arp_type = ARPHRD_VOID;
+  }
+
   string contents;
-  if (!file_util::ReadFileToString(uevent_file, &contents)) {
+  if (!GetDeviceInfoContents(iface_name, kInterfaceUevent, &contents)) {
     SLOG(Device, 2) << StringPrintf("%s: device %s has no uevent file",
                                     __func__, iface_name.c_str());
     return Technology::kUnknown;
@@ -156,13 +181,7 @@
     SLOG(Device, 2)
         << StringPrintf("%s: device %s has wifi signature in uevent file",
                         __func__, iface_name.c_str());
-    FilePath type_file(StringPrintf(kInterfaceType, iface_name.c_str()));
-    string type_string;
-    int type_val = 0;
-    if (file_util::ReadFileToString(type_file, &type_string) &&
-        TrimString(type_string, "\n", &type_string) &&
-        base::StringToInt(type_string, &type_val) &&
-        type_val == ARPHRD_IEEE80211_RADIOTAP) {
+    if (arp_type == ARPHRD_IEEE80211_RADIOTAP) {
       SLOG(Device, 2) << StringPrintf("%s: wifi device %s is in monitor mode",
                                       __func__, iface_name.c_str());
       return Technology::kWiFiMonitor;
@@ -170,23 +189,25 @@
     return Technology::kWifi;
   }
 
-  FilePath driver_file(StringPrintf(kInterfaceDriver, iface_name.c_str()));
   FilePath driver_path;
-  if (!file_util::ReadSymbolicLink(driver_file, &driver_path)) {
+  if (!GetDeviceInfoSymbolicLink(iface_name, kInterfaceDriver, &driver_path)) {
     SLOG(Device, 2) << StringPrintf("%s: device %s has no device symlink",
                                     __func__, iface_name.c_str());
-    if (iface_name == kLoopbackDeviceName) {
+    if (arp_type == ARPHRD_LOOPBACK) {
       SLOG(Device, 2) << StringPrintf("%s: device %s is a loopback device",
                                       __func__, iface_name.c_str());
       return Technology::kLoopback;
     }
-    FilePath tun_flags_file(StringPrintf(kInterfaceTunFlags,
-                                         iface_name.c_str()));
-    string tun_flags_string;
+    if (arp_type == ARPHRD_PPP) {
+      SLOG(Device, 2) << StringPrintf("%s: device %s is a ppp device",
+                                      __func__, iface_name.c_str());
+      return Technology::kPPP;
+    }
+    string tun_flags_str;
     int tun_flags = 0;
-    if (file_util::ReadFileToString(tun_flags_file, &tun_flags_string) &&
-        TrimString(tun_flags_string, "\n", &tun_flags_string) &&
-        base::HexStringToInt(tun_flags_string, &tun_flags) &&
+    if (GetDeviceInfoContents(iface_name, kInterfaceTunFlags, &tun_flags_str) &&
+        TrimString(tun_flags_str, "\n", &tun_flags_str) &&
+        base::HexStringToInt(tun_flags_str, &tun_flags) &&
         (tun_flags & IFF_TUN)) {
       SLOG(Device, 2) << StringPrintf("%s: device %s is tun device",
                                       __func__, iface_name.c_str());
@@ -231,7 +252,6 @@
   return Technology::kEthernet;
 }
 
-// static
 bool DeviceInfo::IsCdcEtherModemDevice(const std::string &iface_name) {
   // A cdc_ether device is a modem device if it also exposes tty interfaces.
   // To determine this, we look for the existence of the tty interface in the
@@ -259,7 +279,7 @@
   // (eg. E362), so the device tree for the tty interface is:
   // /sys/devices/pci0000:00/0000:00:1d.7/usb/1-2/1-2:1.0/ttyUSB0/tty/ttyUSB0
 
-  FilePath device_file(StringPrintf(kInterfaceDevice, iface_name.c_str()));
+  FilePath device_file = GetDeviceInfoPath(iface_name, kInterfaceDevice);
   FilePath device_path;
   if (!file_util::ReadSymbolicLink(device_file, &device_path)) {
     SLOG(Device, 2) << StringPrintf("%s: device %s has no device symlink",
@@ -333,7 +353,8 @@
       address = StringToLowerASCII(infos_[dev_index].mac_address.HexEncode());
       SLOG(Device, 2) << "link index " << dev_index << " address "
                       << infos_[dev_index].mac_address.HexEncode();
-    } else if (technology != Technology::kTunnel) {
+    } else if (technology != Technology::kTunnel &&
+               technology != Technology::kPPP) {
       LOG(ERROR) << "Add Link message does not have IFLA_ADDRESS!";
       return;
     }
@@ -360,15 +381,18 @@
                           link_name, address, dev_index);
         device->EnableIPv6Privacy();
         break;
+      case Technology::kPPP:
       case Technology::kTunnel:
-        // Tunnel devices are managed by the VPN code.  Notify the VPN Provider
-        // only if this is the first time we have seen this device index.
+        // Tunnel and PPP devices are managed by the VPN code (PPP for
+        // l2tpipsec).  Notify the VPN Provider only if this is the first
+        // time we have seen this device index.
         if (new_device) {
-          SLOG(Device, 2) << "Tunnel link " << link_name
+          SLOG(Device, 2) << "Tunnel / PPP link " << link_name
                           << " at index " << dev_index
                           << " -- notifying VPNProvider.";
           if (!manager_->vpn_provider()->OnDeviceInfoAvailable(link_name,
-                                                               dev_index)) {
+                                                               dev_index) &&
+              technology == Technology::kTunnel) {
             // If VPN does not know anything about this tunnel, it is probably
             // left over from a previous instance and should not exist.
             SLOG(Device, 2) << "Tunnel link is unused.  Deleting.";