Peter Qiu | fb39ba4 | 2014-11-21 09:09:59 -0800 | [diff] [blame] | 1 | // Copyright 2014 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "apmanager/device_info.h" |
| 6 | |
| 7 | #include <linux/netlink.h> |
| 8 | |
| 9 | #include <map> |
| 10 | #include <string> |
| 11 | #include <vector> |
| 12 | |
| 13 | #include <base/files/file_util.h> |
| 14 | #include <base/files/scoped_temp_dir.h> |
| 15 | #include <gmock/gmock.h> |
| 16 | #include <gtest/gtest.h> |
| 17 | #include <shill/net/byte_string.h> |
| 18 | #include <shill/net/mock_netlink_manager.h> |
| 19 | #include "shill/net/netlink_message_matchers.h" |
| 20 | #include "shill/net/nl80211_attribute.h" |
| 21 | #include "shill/net/nl80211_message.h" |
| 22 | #include <shill/net/rtnl_message.h> |
| 23 | |
| 24 | #include "apmanager/mock_device.h" |
| 25 | #include "apmanager/mock_manager.h" |
| 26 | |
| 27 | using shill::ByteString; |
| 28 | using shill::Nl80211Message; |
| 29 | using shill::RTNLMessage; |
| 30 | using std::map; |
| 31 | using std::string; |
| 32 | using std::vector; |
| 33 | using ::testing::_; |
| 34 | using ::testing::Mock; |
| 35 | |
| 36 | namespace apmanager { |
| 37 | |
| 38 | namespace { |
| 39 | |
| 40 | const char kTestDeviceName[] = "test-phy"; |
| 41 | const char kTestInterface0Name[] = "test-interface0"; |
| 42 | const char kTestInterface1Name[] = "test-interface1"; |
| 43 | const uint32_t kTestInterface0Index = 1000; |
| 44 | const uint32_t kTestInterface1Index = 1001; |
| 45 | |
| 46 | } // namespace |
| 47 | |
| 48 | class DeviceInfoTest : public testing::Test { |
| 49 | public: |
| 50 | DeviceInfoTest() : device_info_(&manager_) {} |
| 51 | virtual ~DeviceInfoTest() {} |
| 52 | |
| 53 | virtual void SetUp() { |
| 54 | // Setup temporary directory for device info files. |
| 55 | CHECK(temp_dir_.CreateUniqueTempDir()); |
| 56 | device_info_root_ = temp_dir_.path().Append("sys/class/net"); |
| 57 | device_info_.device_info_root_ = device_info_root_; |
| 58 | |
| 59 | // Setup mock pointers; |
| 60 | device_info_.netlink_manager_ = &netlink_manager_; |
| 61 | } |
| 62 | |
| 63 | bool IsWifiInterface(const string& interface_name) { |
| 64 | return device_info_.IsWifiInterface(interface_name); |
| 65 | } |
| 66 | |
| 67 | void CreateDeviceInfoFile(const string& interface_name, |
| 68 | const string& file_name, |
| 69 | const string& contents) { |
| 70 | base::FilePath info_path = |
| 71 | device_info_root_.Append(interface_name).Append(file_name); |
| 72 | EXPECT_TRUE(base::CreateDirectory(info_path.DirName())); |
| 73 | EXPECT_TRUE(base::WriteFile(info_path, contents.c_str(), contents.size())); |
| 74 | } |
| 75 | |
| 76 | void SendLinkMsg(RTNLMessage::Mode mode, |
| 77 | uint32_t interface_index, |
| 78 | const string& interface_name) { |
| 79 | RTNLMessage message(RTNLMessage::kTypeLink, |
| 80 | mode, |
| 81 | 0, |
| 82 | 0, |
| 83 | 0, |
| 84 | interface_index, |
| 85 | shill::IPAddress::kFamilyIPv4); |
| 86 | message.SetAttribute(static_cast<uint16_t>(IFLA_IFNAME), |
| 87 | ByteString(interface_name, true)); |
| 88 | device_info_.LinkMsgHandler(message); |
| 89 | } |
| 90 | |
| 91 | void VerifyInterfaceList(const vector<Device::WiFiInterface>& interfaces) { |
| 92 | // Verify number of elements in the interface infos map and interface index |
| 93 | // of the elements in the map. |
| 94 | EXPECT_EQ(interfaces.size(), device_info_.interface_infos_.size()); |
| 95 | for (const auto& interface : interfaces) { |
| 96 | map<uint32_t, Device::WiFiInterface>::iterator it = |
| 97 | device_info_.interface_infos_.find(interface.iface_index); |
| 98 | EXPECT_NE(device_info_.interface_infos_.end(), it); |
| 99 | EXPECT_TRUE(interface.Equals(it->second)); |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | void VerifyDeviceList(const vector<scoped_refptr<Device>>& devices) { |
| 104 | // Verify number of elements in the device map and the elements in the map. |
| 105 | EXPECT_EQ(devices.size(), device_info_.devices_.size()); |
| 106 | for (const auto& device : devices) { |
| 107 | map<string, scoped_refptr<Device>>::iterator it = |
| 108 | device_info_.devices_.find(device->GetDeviceName()); |
| 109 | EXPECT_NE(device_info_.devices_.end(), it); |
| 110 | EXPECT_EQ(device, it->second); |
| 111 | } |
| 112 | } |
| 113 | void AddInterface(const Device::WiFiInterface& interface) { |
| 114 | device_info_.interface_infos_[interface.iface_index] = interface; |
| 115 | } |
| 116 | |
Peter Qiu | 1ff67a7 | 2014-11-22 07:06:10 -0800 | [diff] [blame] | 117 | void OnWiFiPhyInfoReceived(const Nl80211Message& message) { |
| 118 | device_info_.OnWiFiPhyInfoReceived(message); |
| 119 | } |
| 120 | |
Peter Qiu | fb39ba4 | 2014-11-21 09:09:59 -0800 | [diff] [blame] | 121 | void OnWiFiInterfaceInfoReceived(const Nl80211Message& message) { |
| 122 | device_info_.OnWiFiInterfaceInfoReceived(message); |
| 123 | } |
| 124 | |
| 125 | void OnWiFiInterfacePhyInfoReceived(uint32_t interface_index, |
| 126 | const Nl80211Message& message) { |
| 127 | device_info_.OnWiFiInterfacePhyInfoReceived(interface_index, message); |
| 128 | } |
| 129 | |
| 130 | void RegisterDevice(scoped_refptr<Device> device) { |
| 131 | device_info_.RegisterDevice(device); |
| 132 | } |
| 133 | |
| 134 | protected: |
| 135 | DeviceInfo device_info_; |
| 136 | MockManager manager_; |
| 137 | shill::MockNetlinkManager netlink_manager_; |
| 138 | base::ScopedTempDir temp_dir_; |
| 139 | base::FilePath device_info_root_; |
| 140 | }; |
| 141 | |
| 142 | MATCHER_P2(IsGetInfoMessage, command, index, "") { |
| 143 | if (arg->message_type() != Nl80211Message::GetMessageType()) { |
| 144 | return false; |
| 145 | } |
| 146 | const Nl80211Message *msg = reinterpret_cast<const Nl80211Message *>(arg); |
| 147 | if (msg->command() != command) { |
| 148 | return false; |
| 149 | } |
| 150 | uint32_t interface_index; |
| 151 | if (!msg->const_attributes()->GetU32AttributeValue(NL80211_ATTR_IFINDEX, |
| 152 | &interface_index)) { |
| 153 | return false; |
| 154 | } |
| 155 | // kInterfaceIndex is signed, but the attribute as handed from the kernel |
| 156 | // is unsigned. We're silently casting it away with this assignment. |
| 157 | uint32_t test_interface_index = index; |
| 158 | return interface_index == test_interface_index; |
| 159 | } |
| 160 | |
| 161 | MATCHER_P(IsInterface, interface, "") { |
| 162 | return arg.Equals(interface); |
| 163 | } |
| 164 | |
Peter Qiu | 1ff67a7 | 2014-11-22 07:06:10 -0800 | [diff] [blame] | 165 | MATCHER_P(IsDevice, device_name, "") { |
| 166 | return arg->GetDeviceName() == device_name; |
| 167 | } |
| 168 | |
| 169 | TEST_F(DeviceInfoTest, EnumerateDevices) { |
| 170 | shill::NewWiphyMessage message; |
| 171 | |
| 172 | // No device name in the message, failed to create device. |
| 173 | EXPECT_CALL(manager_, RegisterDevice(_)).Times(0); |
| 174 | OnWiFiPhyInfoReceived(message); |
| 175 | |
| 176 | // Device name in the message, device should be created/register to manager. |
Samuel Tan | 3109666 | 2015-02-04 14:44:10 -0800 | [diff] [blame^] | 177 | message.attributes()->CreateNl80211Attribute( |
| 178 | NL80211_ATTR_WIPHY_NAME, shill::NetlinkMessage::MessageContext()); |
Peter Qiu | 1ff67a7 | 2014-11-22 07:06:10 -0800 | [diff] [blame] | 179 | message.attributes()->SetStringAttributeValue(NL80211_ATTR_WIPHY_NAME, |
| 180 | kTestDeviceName); |
| 181 | EXPECT_CALL(manager_, RegisterDevice(IsDevice(kTestDeviceName))).Times(1); |
| 182 | OnWiFiPhyInfoReceived(message); |
| 183 | Mock::VerifyAndClearExpectations(&manager_); |
| 184 | |
| 185 | // Receive a message for a device already created, should not create/register |
| 186 | // device again. |
| 187 | EXPECT_CALL(manager_, RegisterDevice(_)).Times(0); |
| 188 | OnWiFiPhyInfoReceived(message); |
| 189 | } |
| 190 | |
Peter Qiu | fb39ba4 | 2014-11-21 09:09:59 -0800 | [diff] [blame] | 191 | TEST_F(DeviceInfoTest, IsWiFiInterface) { |
| 192 | // No device info file exist, not a wifi interface. |
| 193 | EXPECT_FALSE(IsWifiInterface(kTestInterface0Name)); |
| 194 | |
| 195 | // Device info for an ethernet device, not a wifi interface |
| 196 | CreateDeviceInfoFile(kTestInterface0Name, "uevent", "INTERFACE=eth0\n"); |
| 197 | EXPECT_FALSE(IsWifiInterface(kTestInterface0Name)); |
| 198 | |
| 199 | // Device info for a wifi interface. |
| 200 | CreateDeviceInfoFile(kTestInterface1Name, "uevent", "DEVTYPE=wlan\n"); |
| 201 | EXPECT_TRUE(IsWifiInterface(kTestInterface1Name)); |
| 202 | } |
| 203 | |
| 204 | TEST_F(DeviceInfoTest, InterfaceDetection) { |
| 205 | vector<Device::WiFiInterface> interface_list; |
| 206 | // Ignore non-wifi interface. |
| 207 | SendLinkMsg(RTNLMessage::kModeAdd, |
| 208 | kTestInterface0Index, |
| 209 | kTestInterface0Name); |
| 210 | VerifyInterfaceList(interface_list); |
| 211 | |
| 212 | // AddLink event for wifi interface. |
| 213 | CreateDeviceInfoFile(kTestInterface0Name, "uevent", "DEVTYPE=wlan\n"); |
| 214 | EXPECT_CALL(netlink_manager_, SendNl80211Message( |
| 215 | IsGetInfoMessage(NL80211_CMD_GET_INTERFACE, kTestInterface0Index), |
| 216 | _, _, _)).Times(1); |
| 217 | SendLinkMsg(RTNLMessage::kModeAdd, |
| 218 | kTestInterface0Index, |
| 219 | kTestInterface0Name); |
| 220 | interface_list.push_back(Device::WiFiInterface( |
| 221 | kTestInterface0Name, "", kTestInterface0Index, 0)); |
| 222 | VerifyInterfaceList(interface_list); |
| 223 | Mock::VerifyAndClearExpectations(&netlink_manager_); |
| 224 | |
| 225 | // AddLink event for another wifi interface. |
| 226 | CreateDeviceInfoFile(kTestInterface1Name, "uevent", "DEVTYPE=wlan\n"); |
| 227 | EXPECT_CALL(netlink_manager_, SendNl80211Message( |
| 228 | IsGetInfoMessage(NL80211_CMD_GET_INTERFACE, kTestInterface1Index), |
| 229 | _, _, _)).Times(1); |
| 230 | SendLinkMsg(RTNLMessage::kModeAdd, |
| 231 | kTestInterface1Index, |
| 232 | kTestInterface1Name); |
| 233 | interface_list.push_back(Device::WiFiInterface( |
| 234 | kTestInterface1Name, "", kTestInterface1Index, 0)); |
| 235 | VerifyInterfaceList(interface_list); |
| 236 | Mock::VerifyAndClearExpectations(&netlink_manager_); |
| 237 | |
| 238 | // AddLink event for an interface that's already added, no change to interface |
| 239 | // list. |
| 240 | EXPECT_CALL(netlink_manager_, SendNl80211Message(_, _, _, _)).Times(0); |
| 241 | SendLinkMsg(RTNLMessage::kModeAdd, |
| 242 | kTestInterface0Index, |
| 243 | kTestInterface0Name); |
| 244 | VerifyInterfaceList(interface_list); |
| 245 | Mock::VerifyAndClearExpectations(&netlink_manager_); |
| 246 | |
| 247 | // Remove the first wifi interface. |
| 248 | SendLinkMsg(RTNLMessage::kModeDelete, |
| 249 | kTestInterface0Index, |
| 250 | kTestInterface0Name); |
| 251 | interface_list.clear(); |
| 252 | interface_list.push_back(Device::WiFiInterface( |
| 253 | kTestInterface1Name, "", kTestInterface1Index, 0)); |
| 254 | VerifyInterfaceList(interface_list); |
| 255 | |
| 256 | // Remove the non-exist interface, no change to the list. |
| 257 | SendLinkMsg(RTNLMessage::kModeDelete, |
| 258 | kTestInterface0Index, |
| 259 | kTestInterface0Name); |
| 260 | VerifyInterfaceList(interface_list); |
| 261 | |
| 262 | // Remove the last interface, list should be empty now. |
| 263 | SendLinkMsg(RTNLMessage::kModeDelete, |
| 264 | kTestInterface1Index, |
| 265 | kTestInterface1Name); |
| 266 | interface_list.clear(); |
| 267 | VerifyInterfaceList(interface_list); |
| 268 | } |
| 269 | |
| 270 | TEST_F(DeviceInfoTest, ParseWifiInterfaceInfo) { |
| 271 | // Add an interface without interface type info. |
| 272 | Device::WiFiInterface interface( |
| 273 | kTestInterface0Name, "", kTestInterface0Index, 0); |
| 274 | AddInterface(interface); |
| 275 | vector<Device::WiFiInterface> interface_list; |
| 276 | interface_list.push_back(interface); |
| 277 | |
| 278 | // Message contain no interface index, no change to the interface info. |
| 279 | shill::NewInterfaceMessage message; |
| 280 | OnWiFiInterfaceInfoReceived(message); |
| 281 | VerifyInterfaceList(interface_list); |
| 282 | |
| 283 | // Message contain no interface type, no change to the interface info. |
Samuel Tan | 3109666 | 2015-02-04 14:44:10 -0800 | [diff] [blame^] | 284 | message.attributes()->CreateNl80211Attribute( |
| 285 | NL80211_ATTR_IFINDEX, shill::NetlinkMessage::MessageContext()); |
Peter Qiu | fb39ba4 | 2014-11-21 09:09:59 -0800 | [diff] [blame] | 286 | message.attributes()->SetU32AttributeValue(NL80211_ATTR_IFINDEX, |
| 287 | kTestInterface0Index); |
| 288 | OnWiFiInterfaceInfoReceived(message); |
| 289 | |
| 290 | // Message contain interface type, interface info should be updated with |
| 291 | // the interface type, and a new Nl80211 message should be send to query for |
| 292 | // the PHY info. |
| 293 | EXPECT_CALL(netlink_manager_, SendNl80211Message( |
| 294 | IsGetInfoMessage(NL80211_CMD_GET_WIPHY, kTestInterface0Index), |
| 295 | _, _, _)).Times(1); |
Samuel Tan | 3109666 | 2015-02-04 14:44:10 -0800 | [diff] [blame^] | 296 | message.attributes()->CreateNl80211Attribute( |
| 297 | NL80211_ATTR_IFTYPE, shill::NetlinkMessage::MessageContext()); |
Peter Qiu | fb39ba4 | 2014-11-21 09:09:59 -0800 | [diff] [blame] | 298 | message.attributes()->SetU32AttributeValue(NL80211_ATTR_IFTYPE, |
| 299 | NL80211_IFTYPE_AP); |
| 300 | OnWiFiInterfaceInfoReceived(message); |
| 301 | interface_list[0].iface_type = NL80211_IFTYPE_AP; |
| 302 | VerifyInterfaceList(interface_list); |
| 303 | } |
| 304 | |
| 305 | TEST_F(DeviceInfoTest, ParsePhyInfoForWifiInterface) { |
| 306 | // Register a mock device. |
| 307 | scoped_refptr<MockDevice> device = new MockDevice(); |
| 308 | device->SetDeviceName(kTestDeviceName); |
| 309 | EXPECT_CALL(manager_, RegisterDevice(_)).Times(1); |
| 310 | RegisterDevice(device); |
| 311 | |
| 312 | // PHY info message. |
| 313 | shill::NewWiphyMessage message; |
Samuel Tan | 3109666 | 2015-02-04 14:44:10 -0800 | [diff] [blame^] | 314 | message.attributes()->CreateNl80211Attribute( |
| 315 | NL80211_ATTR_WIPHY_NAME, shill::NetlinkMessage::MessageContext()); |
Peter Qiu | fb39ba4 | 2014-11-21 09:09:59 -0800 | [diff] [blame] | 316 | message.attributes()->SetStringAttributeValue(NL80211_ATTR_WIPHY_NAME, |
| 317 | kTestDeviceName); |
| 318 | |
| 319 | // Receive PHY info message for an interface that have not been detected yet. |
| 320 | EXPECT_CALL(*device.get(), RegisterInterface(_)).Times(0); |
| 321 | OnWiFiInterfacePhyInfoReceived(kTestInterface0Index, message); |
| 322 | |
| 323 | // Pretend interface is detected through AddLink with interface info already |
| 324 | // received (interface type), and still missing PHY info for that interface. |
| 325 | Device::WiFiInterface interface( |
| 326 | kTestInterface0Name, "", kTestInterface0Index, NL80211_IFTYPE_AP); |
| 327 | AddInterface(interface); |
| 328 | |
| 329 | // PHY info is received for a detected interface, should register that |
| 330 | // interface to the corresponding Device. |
| 331 | interface.device_name = kTestDeviceName; |
| 332 | EXPECT_CALL(*device.get(), |
| 333 | RegisterInterface(IsInterface(interface))).Times(1); |
| 334 | OnWiFiInterfacePhyInfoReceived(kTestInterface0Index, message); |
| 335 | } |
| 336 | |
| 337 | TEST_F(DeviceInfoTest, ReceivePhyInfoBeforePhyIsEnumerated) { |
| 338 | // New interface is detected. |
| 339 | Device::WiFiInterface interface( |
| 340 | kTestInterface0Name, "", kTestInterface0Index, NL80211_IFTYPE_AP); |
| 341 | AddInterface(interface); |
Peter Qiu | 1ff67a7 | 2014-11-22 07:06:10 -0800 | [diff] [blame] | 342 | vector<Device::WiFiInterface> interface_list; |
| 343 | interface_list.push_back(interface); |
Peter Qiu | fb39ba4 | 2014-11-21 09:09:59 -0800 | [diff] [blame] | 344 | |
| 345 | // Received PHY info for the interface when the corresponding PHY is not |
Peter Qiu | 1ff67a7 | 2014-11-22 07:06:10 -0800 | [diff] [blame] | 346 | // enumerated yet, new device should be created and register to manager. |
Peter Qiu | fb39ba4 | 2014-11-21 09:09:59 -0800 | [diff] [blame] | 347 | shill::NewWiphyMessage message; |
Samuel Tan | 3109666 | 2015-02-04 14:44:10 -0800 | [diff] [blame^] | 348 | message.attributes()->CreateNl80211Attribute( |
| 349 | NL80211_ATTR_WIPHY_NAME, shill::NetlinkMessage::MessageContext()); |
Peter Qiu | fb39ba4 | 2014-11-21 09:09:59 -0800 | [diff] [blame] | 350 | message.attributes()->SetStringAttributeValue(NL80211_ATTR_WIPHY_NAME, |
| 351 | kTestDeviceName); |
Peter Qiu | 1ff67a7 | 2014-11-22 07:06:10 -0800 | [diff] [blame] | 352 | EXPECT_CALL(manager_, RegisterDevice(IsDevice(kTestDeviceName))).Times(1); |
Peter Qiu | fb39ba4 | 2014-11-21 09:09:59 -0800 | [diff] [blame] | 353 | OnWiFiInterfacePhyInfoReceived(kTestInterface0Index, message); |
Peter Qiu | 1ff67a7 | 2014-11-22 07:06:10 -0800 | [diff] [blame] | 354 | interface_list[0].device_name = kTestDeviceName; |
| 355 | VerifyInterfaceList(interface_list); |
Peter Qiu | fb39ba4 | 2014-11-21 09:09:59 -0800 | [diff] [blame] | 356 | } |
| 357 | |
| 358 | TEST_F(DeviceInfoTest, RegisterDevice) { |
| 359 | vector<scoped_refptr<Device>> device_list; |
| 360 | |
| 361 | // Register a nullptr. |
| 362 | RegisterDevice(nullptr); |
| 363 | VerifyDeviceList(device_list); |
| 364 | |
| 365 | // Register a device. |
Peter Qiu | 7e0ffcf | 2014-12-02 12:53:27 -0800 | [diff] [blame] | 366 | device_list.push_back(new Device(&manager_, kTestDeviceName)); |
Peter Qiu | fb39ba4 | 2014-11-21 09:09:59 -0800 | [diff] [blame] | 367 | EXPECT_CALL(manager_, RegisterDevice(device_list[0])); |
| 368 | RegisterDevice(device_list[0]); |
| 369 | VerifyDeviceList(device_list); |
| 370 | } |
| 371 | |
| 372 | } // namespace apmanager |