| // |
| // Copyright (C) 2014 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| |
| #include "apmanager/device_info.h" |
| |
| #include <linux/netlink.h> |
| #include <linux/rtnetlink.h> |
| |
| #include <map> |
| #include <string> |
| #include <vector> |
| |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <shill/net/byte_string.h> |
| #include <shill/net/mock_netlink_manager.h> |
| #include "shill/net/netlink_message_matchers.h" |
| #include "shill/net/nl80211_attribute.h" |
| #include "shill/net/nl80211_message.h" |
| #include <shill/net/rtnl_message.h> |
| |
| #include "apmanager/fake_device_adaptor.h" |
| #include "apmanager/mock_control.h" |
| #include "apmanager/mock_device.h" |
| #include "apmanager/mock_manager.h" |
| |
| using shill::ByteString; |
| using shill::Nl80211Message; |
| using shill::RTNLMessage; |
| using std::map; |
| using std::string; |
| using std::vector; |
| using ::testing::_; |
| using ::testing::Mock; |
| using ::testing::ReturnNew; |
| |
| namespace apmanager { |
| |
| namespace { |
| |
| const char kTestDeviceName[] = "test-phy"; |
| const char kTestInterface0Name[] = "test-interface0"; |
| const char kTestInterface1Name[] = "test-interface1"; |
| const uint32_t kTestInterface0Index = 1000; |
| const uint32_t kTestInterface1Index = 1001; |
| |
| } // namespace |
| |
| class DeviceInfoTest : public testing::Test { |
| public: |
| DeviceInfoTest() |
| : manager_(&control_interface_), |
| device_info_(&manager_) {} |
| virtual ~DeviceInfoTest() {} |
| |
| virtual void SetUp() { |
| // Setup temporary directory for device info files. |
| CHECK(temp_dir_.CreateUniqueTempDir()); |
| device_info_root_ = temp_dir_.path().Append("sys/class/net"); |
| device_info_.device_info_root_ = device_info_root_; |
| |
| // Setup mock pointers; |
| device_info_.netlink_manager_ = &netlink_manager_; |
| |
| ON_CALL(control_interface_, CreateDeviceAdaptorRaw()) |
| .WillByDefault(ReturnNew<FakeDeviceAdaptor>()); |
| } |
| |
| bool IsWifiInterface(const string& interface_name) { |
| return device_info_.IsWifiInterface(interface_name); |
| } |
| |
| void CreateDeviceInfoFile(const string& interface_name, |
| const string& file_name, |
| const string& contents) { |
| base::FilePath info_path = |
| device_info_root_.Append(interface_name).Append(file_name); |
| EXPECT_TRUE(base::CreateDirectory(info_path.DirName())); |
| EXPECT_TRUE(base::WriteFile(info_path, contents.c_str(), contents.size())); |
| } |
| |
| void SendLinkMsg(RTNLMessage::Mode mode, |
| uint32_t interface_index, |
| const string& interface_name) { |
| RTNLMessage message(RTNLMessage::kTypeLink, |
| mode, |
| 0, |
| 0, |
| 0, |
| interface_index, |
| shill::IPAddress::kFamilyIPv4); |
| message.SetAttribute(static_cast<uint16_t>(IFLA_IFNAME), |
| ByteString(interface_name, true)); |
| device_info_.LinkMsgHandler(message); |
| } |
| |
| void VerifyInterfaceList(const vector<Device::WiFiInterface>& interfaces) { |
| // Verify number of elements in the interface infos map and interface index |
| // of the elements in the map. |
| EXPECT_EQ(interfaces.size(), device_info_.interface_infos_.size()); |
| for (const auto& interface : interfaces) { |
| map<uint32_t, Device::WiFiInterface>::iterator it = |
| device_info_.interface_infos_.find(interface.iface_index); |
| EXPECT_NE(device_info_.interface_infos_.end(), it); |
| EXPECT_TRUE(interface.Equals(it->second)); |
| } |
| } |
| |
| void VerifyDeviceList(const vector<scoped_refptr<Device>>& devices) { |
| // Verify number of elements in the device map and the elements in the map. |
| EXPECT_EQ(devices.size(), device_info_.devices_.size()); |
| for (const auto& device : devices) { |
| map<string, scoped_refptr<Device>>::iterator it = |
| device_info_.devices_.find(device->GetDeviceName()); |
| EXPECT_NE(device_info_.devices_.end(), it); |
| EXPECT_EQ(device, it->second); |
| } |
| } |
| void AddInterface(const Device::WiFiInterface& interface) { |
| device_info_.interface_infos_[interface.iface_index] = interface; |
| } |
| |
| void OnWiFiPhyInfoReceived(const Nl80211Message& message) { |
| device_info_.OnWiFiPhyInfoReceived(message); |
| } |
| |
| void OnWiFiInterfaceInfoReceived(const Nl80211Message& message) { |
| device_info_.OnWiFiInterfaceInfoReceived(message); |
| } |
| |
| void OnWiFiInterfacePhyInfoReceived(uint32_t interface_index, |
| const Nl80211Message& message) { |
| device_info_.OnWiFiInterfacePhyInfoReceived(interface_index, message); |
| } |
| |
| void RegisterDevice(scoped_refptr<Device> device) { |
| device_info_.RegisterDevice(device); |
| } |
| |
| protected: |
| MockControl control_interface_; |
| MockManager manager_; |
| |
| shill::MockNetlinkManager netlink_manager_; |
| base::ScopedTempDir temp_dir_; |
| base::FilePath device_info_root_; |
| DeviceInfo device_info_; |
| }; |
| |
| MATCHER_P2(IsGetInfoMessage, command, index, "") { |
| if (arg->message_type() != Nl80211Message::GetMessageType()) { |
| return false; |
| } |
| const Nl80211Message *msg = reinterpret_cast<const Nl80211Message *>(arg); |
| if (msg->command() != command) { |
| return false; |
| } |
| uint32_t interface_index; |
| if (!msg->const_attributes()->GetU32AttributeValue(NL80211_ATTR_IFINDEX, |
| &interface_index)) { |
| return false; |
| } |
| // kInterfaceIndex is signed, but the attribute as handed from the kernel |
| // is unsigned. We're silently casting it away with this assignment. |
| uint32_t test_interface_index = index; |
| return interface_index == test_interface_index; |
| } |
| |
| MATCHER_P(IsInterface, interface, "") { |
| return arg.Equals(interface); |
| } |
| |
| MATCHER_P(IsDevice, device_name, "") { |
| return arg->GetDeviceName() == device_name; |
| } |
| |
| TEST_F(DeviceInfoTest, EnumerateDevices) { |
| shill::NewWiphyMessage message; |
| |
| // No device name in the message, failed to create device. |
| EXPECT_CALL(manager_, RegisterDevice(_)).Times(0); |
| OnWiFiPhyInfoReceived(message); |
| |
| // Device name in the message, device should be created/register to manager. |
| message.attributes()->CreateNl80211Attribute( |
| NL80211_ATTR_WIPHY_NAME, shill::NetlinkMessage::MessageContext()); |
| message.attributes()->SetStringAttributeValue(NL80211_ATTR_WIPHY_NAME, |
| kTestDeviceName); |
| EXPECT_CALL(manager_, RegisterDevice(IsDevice(kTestDeviceName))).Times(1); |
| OnWiFiPhyInfoReceived(message); |
| Mock::VerifyAndClearExpectations(&manager_); |
| |
| // Receive a message for a device already created, should not create/register |
| // device again. |
| EXPECT_CALL(manager_, RegisterDevice(_)).Times(0); |
| OnWiFiPhyInfoReceived(message); |
| } |
| |
| TEST_F(DeviceInfoTest, IsWiFiInterface) { |
| // No device info file exist, not a wifi interface. |
| EXPECT_FALSE(IsWifiInterface(kTestInterface0Name)); |
| |
| // Device info for an ethernet device, not a wifi interface |
| CreateDeviceInfoFile(kTestInterface0Name, "uevent", "INTERFACE=eth0\n"); |
| EXPECT_FALSE(IsWifiInterface(kTestInterface0Name)); |
| |
| // Device info for a wifi interface. |
| CreateDeviceInfoFile(kTestInterface1Name, "uevent", "DEVTYPE=wlan\n"); |
| EXPECT_TRUE(IsWifiInterface(kTestInterface1Name)); |
| } |
| |
| TEST_F(DeviceInfoTest, InterfaceDetection) { |
| vector<Device::WiFiInterface> interface_list; |
| // Ignore non-wifi interface. |
| SendLinkMsg(RTNLMessage::kModeAdd, |
| kTestInterface0Index, |
| kTestInterface0Name); |
| VerifyInterfaceList(interface_list); |
| |
| // AddLink event for wifi interface. |
| CreateDeviceInfoFile(kTestInterface0Name, "uevent", "DEVTYPE=wlan\n"); |
| EXPECT_CALL(netlink_manager_, SendNl80211Message( |
| IsGetInfoMessage(NL80211_CMD_GET_INTERFACE, kTestInterface0Index), |
| _, _, _)).Times(1); |
| SendLinkMsg(RTNLMessage::kModeAdd, |
| kTestInterface0Index, |
| kTestInterface0Name); |
| interface_list.push_back(Device::WiFiInterface( |
| kTestInterface0Name, "", kTestInterface0Index, 0)); |
| VerifyInterfaceList(interface_list); |
| Mock::VerifyAndClearExpectations(&netlink_manager_); |
| |
| // AddLink event for another wifi interface. |
| CreateDeviceInfoFile(kTestInterface1Name, "uevent", "DEVTYPE=wlan\n"); |
| EXPECT_CALL(netlink_manager_, SendNl80211Message( |
| IsGetInfoMessage(NL80211_CMD_GET_INTERFACE, kTestInterface1Index), |
| _, _, _)).Times(1); |
| SendLinkMsg(RTNLMessage::kModeAdd, |
| kTestInterface1Index, |
| kTestInterface1Name); |
| interface_list.push_back(Device::WiFiInterface( |
| kTestInterface1Name, "", kTestInterface1Index, 0)); |
| VerifyInterfaceList(interface_list); |
| Mock::VerifyAndClearExpectations(&netlink_manager_); |
| |
| // AddLink event for an interface that's already added, no change to interface |
| // list. |
| EXPECT_CALL(netlink_manager_, SendNl80211Message(_, _, _, _)).Times(0); |
| SendLinkMsg(RTNLMessage::kModeAdd, |
| kTestInterface0Index, |
| kTestInterface0Name); |
| VerifyInterfaceList(interface_list); |
| Mock::VerifyAndClearExpectations(&netlink_manager_); |
| |
| // Remove the first wifi interface. |
| SendLinkMsg(RTNLMessage::kModeDelete, |
| kTestInterface0Index, |
| kTestInterface0Name); |
| interface_list.clear(); |
| interface_list.push_back(Device::WiFiInterface( |
| kTestInterface1Name, "", kTestInterface1Index, 0)); |
| VerifyInterfaceList(interface_list); |
| |
| // Remove the non-exist interface, no change to the list. |
| SendLinkMsg(RTNLMessage::kModeDelete, |
| kTestInterface0Index, |
| kTestInterface0Name); |
| VerifyInterfaceList(interface_list); |
| |
| // Remove the last interface, list should be empty now. |
| SendLinkMsg(RTNLMessage::kModeDelete, |
| kTestInterface1Index, |
| kTestInterface1Name); |
| interface_list.clear(); |
| VerifyInterfaceList(interface_list); |
| } |
| |
| TEST_F(DeviceInfoTest, ParseWifiInterfaceInfo) { |
| // Add an interface without interface type info. |
| Device::WiFiInterface interface( |
| kTestInterface0Name, "", kTestInterface0Index, 0); |
| AddInterface(interface); |
| vector<Device::WiFiInterface> interface_list; |
| interface_list.push_back(interface); |
| |
| // Message contain no interface index, no change to the interface info. |
| shill::NewInterfaceMessage message; |
| OnWiFiInterfaceInfoReceived(message); |
| VerifyInterfaceList(interface_list); |
| |
| // Message contain no interface type, no change to the interface info. |
| message.attributes()->CreateNl80211Attribute( |
| NL80211_ATTR_IFINDEX, shill::NetlinkMessage::MessageContext()); |
| message.attributes()->SetU32AttributeValue(NL80211_ATTR_IFINDEX, |
| kTestInterface0Index); |
| OnWiFiInterfaceInfoReceived(message); |
| |
| // Message contain interface type, interface info should be updated with |
| // the interface type, and a new Nl80211 message should be send to query for |
| // the PHY info. |
| EXPECT_CALL(netlink_manager_, SendNl80211Message( |
| IsGetInfoMessage(NL80211_CMD_GET_WIPHY, kTestInterface0Index), |
| _, _, _)).Times(1); |
| message.attributes()->CreateNl80211Attribute( |
| NL80211_ATTR_IFTYPE, shill::NetlinkMessage::MessageContext()); |
| message.attributes()->SetU32AttributeValue(NL80211_ATTR_IFTYPE, |
| NL80211_IFTYPE_AP); |
| OnWiFiInterfaceInfoReceived(message); |
| interface_list[0].iface_type = NL80211_IFTYPE_AP; |
| VerifyInterfaceList(interface_list); |
| } |
| |
| TEST_F(DeviceInfoTest, ParsePhyInfoForWifiInterface) { |
| // Register a mock device. |
| scoped_refptr<MockDevice> device = new MockDevice(&manager_); |
| device->SetDeviceName(kTestDeviceName); |
| EXPECT_CALL(manager_, RegisterDevice(_)).Times(1); |
| RegisterDevice(device); |
| |
| // PHY info message. |
| shill::NewWiphyMessage message; |
| message.attributes()->CreateNl80211Attribute( |
| NL80211_ATTR_WIPHY_NAME, shill::NetlinkMessage::MessageContext()); |
| message.attributes()->SetStringAttributeValue(NL80211_ATTR_WIPHY_NAME, |
| kTestDeviceName); |
| |
| // Receive PHY info message for an interface that have not been detected yet. |
| EXPECT_CALL(*device.get(), RegisterInterface(_)).Times(0); |
| OnWiFiInterfacePhyInfoReceived(kTestInterface0Index, message); |
| |
| // Pretend interface is detected through AddLink with interface info already |
| // received (interface type), and still missing PHY info for that interface. |
| Device::WiFiInterface interface( |
| kTestInterface0Name, "", kTestInterface0Index, NL80211_IFTYPE_AP); |
| AddInterface(interface); |
| |
| // PHY info is received for a detected interface, should register that |
| // interface to the corresponding Device. |
| interface.device_name = kTestDeviceName; |
| EXPECT_CALL(*device.get(), |
| RegisterInterface(IsInterface(interface))).Times(1); |
| OnWiFiInterfacePhyInfoReceived(kTestInterface0Index, message); |
| } |
| |
| TEST_F(DeviceInfoTest, ReceivePhyInfoBeforePhyIsEnumerated) { |
| // New interface is detected. |
| Device::WiFiInterface interface( |
| kTestInterface0Name, "", kTestInterface0Index, NL80211_IFTYPE_AP); |
| AddInterface(interface); |
| vector<Device::WiFiInterface> interface_list; |
| interface_list.push_back(interface); |
| |
| // Received PHY info for the interface when the corresponding PHY is not |
| // enumerated yet, new device should be created and register to manager. |
| shill::NewWiphyMessage message; |
| message.attributes()->CreateNl80211Attribute( |
| NL80211_ATTR_WIPHY_NAME, shill::NetlinkMessage::MessageContext()); |
| message.attributes()->SetStringAttributeValue(NL80211_ATTR_WIPHY_NAME, |
| kTestDeviceName); |
| EXPECT_CALL(manager_, RegisterDevice(IsDevice(kTestDeviceName))).Times(1); |
| OnWiFiInterfacePhyInfoReceived(kTestInterface0Index, message); |
| interface_list[0].device_name = kTestDeviceName; |
| VerifyInterfaceList(interface_list); |
| } |
| |
| TEST_F(DeviceInfoTest, RegisterDevice) { |
| vector<scoped_refptr<Device>> device_list; |
| |
| // Register a nullptr. |
| RegisterDevice(nullptr); |
| VerifyDeviceList(device_list); |
| |
| // Register a device. |
| device_list.push_back(new Device(&manager_, kTestDeviceName, 0)); |
| EXPECT_CALL(manager_, RegisterDevice(device_list[0])); |
| RegisterDevice(device_list[0]); |
| VerifyDeviceList(device_list); |
| } |
| |
| } // namespace apmanager |