shill: Differentiate between various cdc_ether devices.
Add code to distinguish between a cdc_ether cellular device and a
cdc_ether ethernet device.
BUG=chromium-os:28499
TEST=New unit tests, manually verify detection of cdc_ether modem devices
on y3300 and y3400.
Change-Id: I5ca68b68edaac25bdb4b86abb96366c13f0e6e23
Reviewed-on: https://gerrit.chromium.org/gerrit/20317
Reviewed-by: Jason Glasgow <jglasgow@chromium.org>
Commit-Ready: Thieu Le <thieule@chromium.org>
Tested-by: Thieu Le <thieule@chromium.org>
diff --git a/device_info.cc b/device_info.cc
index a424a56..34680bc 100644
--- a/device_info.cc
+++ b/device_info.cc
@@ -41,6 +41,7 @@
#include "shill/wifi.h"
using base::Bind;
+using base::StringPrintf;
using base::Unretained;
using std::map;
using std::string;
@@ -50,25 +51,18 @@
// static
const char DeviceInfo::kInterfaceUevent[] = "/sys/class/net/%s/uevent";
-// static
const char DeviceInfo::kInterfaceUeventWifiSignature[] = "DEVTYPE=wlan\n";
-// static
+const char DeviceInfo::kInterfaceDevice[] = "/sys/class/net/%s/device";
const char DeviceInfo::kInterfaceDriver[] = "/sys/class/net/%s/device/driver";
-// static
const char DeviceInfo::kInterfaceTunFlags[] = "/sys/class/net/%s/tun_flags";
-// static
const char DeviceInfo::kInterfaceType[] = "/sys/class/net/%s/type";
-// static
const char DeviceInfo::kLoopbackDeviceName[] = "lo";
-// static
+const char DeviceInfo::kDriverCdcEther[] = "cdc_ether";
const char *DeviceInfo::kModemDrivers[] = {
"gobi",
"QCUSBNet2k",
- "GobiNet",
- "cdc_ether",
- NULL
+ "GobiNet"
};
-// static
const char DeviceInfo::kTunDeviceName[] = "/dev/net/tun";
DeviceInfo::DeviceInfo(ControlInterface *control_interface,
@@ -140,6 +134,7 @@
}
}
+// static
Technology::Identifier DeviceInfo::GetDeviceTechnology(
const string &iface_name) {
FilePath uevent_file(StringPrintf(kInterfaceUevent, iface_name.c_str()));
@@ -150,11 +145,9 @@
return Technology::kUnknown;
}
- /*
- * If the "uevent" file contains the string "DEVTYPE=wlan\n" at the
- * start of the file or after a newline, we can safely assume this
- * is a wifi device.
- */
+ // If the "uevent" file contains the string "DEVTYPE=wlan\n" at the
+ // start of the file or after a newline, we can safely assume this
+ // is a wifi device.
if (contents.find(kInterfaceUeventWifiSignature) != string::npos) {
VLOG(2) << StringPrintf("%s: device %s has wifi signature in uevent file",
__func__, iface_name.c_str());
@@ -198,11 +191,9 @@
}
string driver_name(driver_path.BaseName().value());
- // See if driver for this interface is in a list of known modem driver names
- for (int modem_idx = 0; kModemDrivers[modem_idx] != NULL; ++modem_idx) {
- // TODO(ers): should have additional checks to make sure a cdc_ether
- // device is really a modem. flimflam uses udev to make such checks,
- // looking to see whether a ttyACM or ttyUSB device is associated.
+ // See if driver for this interface is in a list of known modem driver names.
+ for (size_t modem_idx = 0; modem_idx < arraysize(kModemDrivers);
+ ++modem_idx) {
if (driver_name == kModemDrivers[modem_idx]) {
VLOG(2) << StringPrintf("%s: device %s is matched with modem driver %s",
__func__, iface_name.c_str(),
@@ -211,11 +202,81 @@
}
}
+ // For cdc_ether devices, make sure it's a modem because this driver
+ // can be used for other ethernet devices.
+ if (driver_name == kDriverCdcEther &&
+ IsCdcEtherModemDevice(iface_name)) {
+ VLOG(2) << StringPrintf("%s: device %s is a modem cdc_ether device",
+ __func__, iface_name.c_str());
+ return Technology::kCellular;
+ }
+
VLOG(2) << StringPrintf("%s: device %s is defaulted to type ethernet",
__func__, iface_name.c_str());
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
+ // USB device sysfs tree.
+ //
+ // A typical sysfs dir hierarchy for a cdc_ether modem USB device is as
+ // follows:
+ //
+ // /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-2
+ // 1-2:1.0
+ // tty
+ // ttyACM0
+ // 1-2:1.1
+ // net
+ // usb0
+ // 1-2:1.2
+ // tty
+ // ttyACM1
+ // ...
+ //
+ // /sys/class/net/usb0/device symlinks to
+ // /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-2/1-2:1.1
+
+ FilePath device_file(StringPrintf(kInterfaceDevice, iface_name.c_str()));
+ FilePath device_path;
+ if (!file_util::ReadSymbolicLink(device_file, &device_path)) {
+ VLOG(2) << StringPrintf("%s: device %s has no device symlink",
+ __func__, iface_name.c_str());
+ return false;
+ }
+ if (!device_path.IsAbsolute()) {
+ device_path = device_file.DirName().Append(device_path);
+ file_util::AbsolutePath(&device_path);
+ }
+
+ // Look for tty interface by enumerating all directories under the parent
+ // USB device and seeing if there's a subdirectory "tty" inside of the
+ // enumerated directory. In other words, using the example dir hierarchy
+ // above, find /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-2/1-2*/tty.
+ // If this exists, then this is a modem device.
+ FilePath usb_device_path(device_path.DirName());
+ string search_pattern(usb_device_path.BaseName().MaybeAsASCII() + "*");
+ return IsGrandchildSubdir(usb_device_path, search_pattern, "tty");
+}
+
+// static
+bool DeviceInfo::IsGrandchildSubdir(const FilePath &base_dir,
+ const string &children_filter,
+ const string &grandchild) {
+ file_util::FileEnumerator dir_enum(base_dir, false,
+ file_util::FileEnumerator::DIRECTORIES,
+ children_filter);
+ for (FilePath curr_dir = dir_enum.Next(); !curr_dir.empty();
+ curr_dir = dir_enum.Next()) {
+ if (file_util::DirectoryExists(curr_dir.Append(grandchild)))
+ return true;
+ }
+ return false;
+}
+
void DeviceInfo::AddLinkMsgHandler(const RTNLMessage &msg) {
DCHECK(msg.type() == RTNLMessage::kTypeLink &&
msg.mode() == RTNLMessage::kModeAdd);
diff --git a/device_info.h b/device_info.h
index 441573a..91c354a 100644
--- a/device_info.h
+++ b/device_info.h
@@ -21,6 +21,8 @@
#include "shill/rtnl_listener.h"
#include "shill/technology.h"
+class FilePath;
+
namespace shill {
class Manager;
@@ -76,6 +78,7 @@
friend class DeviceInfoTest;
FRIEND_TEST(CellularTest, StartLinked);
FRIEND_TEST(DeviceInfoTest, AddLoopbackDevice); // For kLoopbackDeviceName.
+ FRIEND_TEST(DeviceInfoTest, GrandchildSubdir); // For IsGrandChildSubdir.
struct Info {
Info() : flags(0) {}
@@ -86,17 +89,41 @@
unsigned int flags;
};
+ // Sysfs path to a device uevent file.
static const char kInterfaceUevent[];
+ // Content of a device uevent file that indicates it is a wifi device.
static const char kInterfaceUeventWifiSignature[];
+ // Sysfs path to a device via its interface name.
+ static const char kInterfaceDevice[];
+ // Sysfs path to the driver of a device via its interface name.
static const char kInterfaceDriver[];
+ // Sysfs path to the file that is used to determine if this is tun device.
static const char kInterfaceTunFlags[];
+ // Sysfs path to the file that is used to determine if a wifi device is
+ // operating in monitor mode.
static const char kInterfaceType[];
+ // Name of the interface for the loopback device.
static const char kLoopbackDeviceName[];
+ // Name of the "cdc_ether" driver. This driver is not included in the
+ // kModemDrivers list because we need to do additional checking.
+ static const char kDriverCdcEther[];
+ // Modem drivers that we support.
static const char *kModemDrivers[];
+ // Path to the tun device.
static const char kTunDeviceName[];
static Technology::Identifier GetDeviceTechnology(
- const std::string &face_name);
+ const std::string &iface_name);
+ // Checks the device specified by |iface_name| to see if it's a modem device.
+ // This method assumes that |iface_name| has already been determined to be
+ // using the cdc_ether driver.
+ static bool IsCdcEtherModemDevice(const std::string &iface_name);
+ // Returns true if |grandchild| is a grandchild of the |base_dir|.
+ // This method only searches for |grandchild| in children specified by
+ // the regex in |children_filter|.
+ static bool IsGrandchildSubdir(const FilePath &base_dir,
+ const std::string &children_filter,
+ const std::string &grandchild);
void AddLinkMsgHandler(const RTNLMessage &msg);
void DelLinkMsgHandler(const RTNLMessage &msg);
diff --git a/device_info_unittest.cc b/device_info_unittest.cc
index 0df149f..b918d95 100644
--- a/device_info_unittest.cc
+++ b/device_info_unittest.cc
@@ -10,9 +10,11 @@
#include <linux/netlink.h> // Needs typedefs from sys/socket.h.
#include <linux/rtnetlink.h>
+#include <base/file_util.h>
#include <base/logging.h>
#include <base/memory/ref_counted.h>
#include <base/message_loop.h>
+#include <base/scoped_temp_dir.h>
#include <base/stl_util.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
@@ -322,4 +324,32 @@
SendMessageToDeviceInfo(*message);
}
+TEST_F(DeviceInfoTest, GrandchildSubdir) {
+ device_info_.Start();
+ ScopedTempDir temp_dir;
+ EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
+ EXPECT_TRUE(file_util::CreateDirectory(temp_dir.path().Append("child11")));
+ EXPECT_TRUE(file_util::CreateDirectory(temp_dir.path().Append("child12")));
+ FilePath child21 = temp_dir.path().Append("child21");
+ EXPECT_TRUE(file_util::CreateDirectory(child21));
+ FilePath grandchild = child21.Append("grandchild");
+ EXPECT_TRUE(file_util::CreateDirectory(grandchild));
+ EXPECT_TRUE(file_util::CreateDirectory(grandchild.Append("greatgrandchild")));
+ EXPECT_TRUE(DeviceInfo::IsGrandchildSubdir(temp_dir.path(),
+ "*",
+ "grandchild"));
+ EXPECT_FALSE(DeviceInfo::IsGrandchildSubdir(temp_dir.path(),
+ "*",
+ "nonexistent"));
+ EXPECT_FALSE(DeviceInfo::IsGrandchildSubdir(temp_dir.path(),
+ "child1*",
+ "grandchild"));
+ EXPECT_TRUE(DeviceInfo::IsGrandchildSubdir(temp_dir.path(),
+ "child2*",
+ "grandchild"));
+ EXPECT_FALSE(DeviceInfo::IsGrandchildSubdir(temp_dir.path(),
+ "*",
+ "greatgrandchild"));
+}
+
} // namespace shill