1 // Copyright 2015 The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "buffet/shill_client.h" 16 17 #include <set> 18 19 #include <base/message_loop/message_loop.h> 20 #include <base/stl_util.h> 21 #include <brillo/any.h> 22 #include <brillo/errors/error.h> 23 #include <brillo/variant_dictionary.h> 24 #include <dbus/shill/dbus-constants.h> 25 #include <weave/enum_to_string.h> 26 27 #include "buffet/ap_manager_client.h" 28 #include "buffet/socket_stream.h" 29 #include "buffet/weave_error_conversion.h" 30 31 using brillo::Any; 32 using brillo::VariantDictionary; 33 using dbus::ObjectPath; 34 using org::chromium::flimflam::DeviceProxy; 35 using org::chromium::flimflam::ServiceProxy; 36 using std::map; 37 using std::set; 38 using std::string; 39 using std::vector; 40 using weave::EnumToString; 41 using weave::provider::Network; 42 43 namespace buffet { 44 45 namespace { 46 47 void IgnoreDetachEvent() {} 48 49 bool GetStateForService(ServiceProxy* service, string* state) { 50 CHECK(service) << "|service| was nullptr in GetStateForService()"; 51 VariantDictionary properties; 52 if (!service->GetProperties(&properties, nullptr)) { 53 LOG(WARNING) << "Failed to read properties from service."; 54 return false; 55 } 56 auto property_it = properties.find(shill::kStateProperty); 57 if (property_it == properties.end()) { 58 LOG(WARNING) << "No state found in service properties."; 59 return false; 60 } 61 string new_state = property_it->second.TryGet<string>(); 62 if (new_state.empty()) { 63 LOG(WARNING) << "Invalid state value."; 64 return false; 65 } 66 *state = new_state; 67 return true; 68 } 69 70 Network::State ShillServiceStateToNetworkState(const string& state) { 71 // TODO(wiley) What does "unconfigured" mean in a world with multiple sets 72 // of WiFi credentials? 73 // TODO(wiley) Detect disabled devices, update state appropriately. 74 if ((state.compare(shill::kStateReady) == 0) || 75 (state.compare(shill::kStatePortal) == 0) || 76 (state.compare(shill::kStateOnline) == 0)) { 77 return Network::State::kOnline; 78 } 79 if ((state.compare(shill::kStateAssociation) == 0) || 80 (state.compare(shill::kStateConfiguration) == 0)) { 81 return Network::State::kConnecting; 82 } 83 if ((state.compare(shill::kStateFailure) == 0) || 84 (state.compare(shill::kStateActivationFailure) == 0)) { 85 // TODO(wiley) Get error information off the service object. 86 return Network::State::kError; 87 } 88 if ((state.compare(shill::kStateIdle) == 0) || 89 (state.compare(shill::kStateOffline) == 0) || 90 (state.compare(shill::kStateDisconnect) == 0)) { 91 return Network::State::kOffline; 92 } 93 LOG(WARNING) << "Unknown state found: '" << state << "'"; 94 return Network::State::kOffline; 95 } 96 97 } // namespace 98 99 ShillClient::ShillClient(const scoped_refptr<dbus::Bus>& bus, 100 const set<string>& device_whitelist, 101 bool disable_xmpp) 102 : bus_{bus}, 103 manager_proxy_{bus_}, 104 device_whitelist_{device_whitelist}, 105 disable_xmpp_{disable_xmpp}, 106 ap_manager_client_{new ApManagerClient(bus)} { 107 manager_proxy_.RegisterPropertyChangedSignalHandler( 108 base::Bind(&ShillClient::OnManagerPropertyChange, 109 weak_factory_.GetWeakPtr()), 110 base::Bind(&ShillClient::OnManagerPropertyChangeRegistration, 111 weak_factory_.GetWeakPtr())); 112 auto owner_changed_cb = base::Bind(&ShillClient::OnShillServiceOwnerChange, 113 weak_factory_.GetWeakPtr()); 114 bus_->GetObjectProxy(shill::kFlimflamServiceName, ObjectPath{"/"}) 115 ->SetNameOwnerChangedCallback(owner_changed_cb); 116 117 Init(); 118 } 119 120 ShillClient::~ShillClient() {} 121 122 void ShillClient::Init() { 123 VLOG(2) << "ShillClient::Init();"; 124 CleanupConnectingService(); 125 devices_.clear(); 126 connectivity_state_ = Network::State::kOffline; 127 VariantDictionary properties; 128 if (!manager_proxy_.GetProperties(&properties, nullptr)) { 129 LOG(ERROR) << "Unable to get properties from Manager, waiting for " 130 "Manager to come back online."; 131 return; 132 } 133 auto it = properties.find(shill::kDevicesProperty); 134 CHECK(it != properties.end()) << "shill should always publish a device list."; 135 OnManagerPropertyChange(shill::kDevicesProperty, it->second); 136 } 137 138 void ShillClient::Connect(const string& ssid, 139 const string& passphrase, 140 const weave::DoneCallback& callback) { 141 LOG(INFO) << "Connecting to WiFi network: " << ssid; 142 if (connecting_service_) { 143 weave::ErrorPtr error; 144 weave::Error::AddTo(&error, FROM_HERE, "busy", 145 "Already connecting to WiFi network"); 146 base::MessageLoop::current()->PostTask( 147 FROM_HERE, base::Bind(callback, base::Passed(&error))); 148 return; 149 } 150 CleanupConnectingService(); 151 VariantDictionary service_properties; 152 service_properties[shill::kTypeProperty] = Any{string{shill::kTypeWifi}}; 153 service_properties[shill::kSSIDProperty] = Any{ssid}; 154 if (passphrase.empty()) { 155 service_properties[shill::kSecurityProperty] = Any{shill::kSecurityNone}; 156 } else { 157 service_properties[shill::kPassphraseProperty] = Any{passphrase}; 158 service_properties[shill::kSecurityProperty] = Any{shill::kSecurityPsk}; 159 } 160 service_properties[shill::kSaveCredentialsProperty] = Any{true}; 161 service_properties[shill::kAutoConnectProperty] = Any{true}; 162 ObjectPath service_path; 163 brillo::ErrorPtr brillo_error; 164 if (!manager_proxy_.ConfigureService(service_properties, &service_path, 165 &brillo_error) || 166 !manager_proxy_.RequestScan(shill::kTypeWifi, &brillo_error)) { 167 weave::ErrorPtr weave_error; 168 ConvertError(*brillo_error, &weave_error); 169 base::MessageLoop::current()->PostTask( 170 FROM_HERE, base::Bind(callback, base::Passed(&weave_error))); 171 return; 172 } 173 connecting_service_.reset(new ServiceProxy{bus_, service_path}); 174 connecting_service_->Connect(nullptr); 175 connect_done_callback_ = callback; 176 connecting_service_->RegisterPropertyChangedSignalHandler( 177 base::Bind(&ShillClient::OnServicePropertyChange, 178 weak_factory_.GetWeakPtr(), service_path), 179 base::Bind(&ShillClient::OnServicePropertyChangeRegistration, 180 weak_factory_.GetWeakPtr(), service_path)); 181 base::MessageLoop::current()->PostDelayedTask( 182 FROM_HERE, base::Bind(&ShillClient::ConnectToServiceError, 183 weak_factory_.GetWeakPtr(), connecting_service_), 184 base::TimeDelta::FromMinutes(1)); 185 } 186 187 void ShillClient::ConnectToServiceError( 188 std::shared_ptr<org::chromium::flimflam::ServiceProxy> connecting_service) { 189 if (connecting_service != connecting_service_ || 190 connect_done_callback_.is_null()) { 191 return; 192 } 193 std::string error = have_called_connect_ ? connecting_service_error_ 194 : shill::kErrorOutOfRange; 195 if (error.empty()) 196 error = shill::kErrorInternal; 197 OnErrorChangeForConnectingService(error); 198 } 199 200 Network::State ShillClient::GetConnectionState() const { 201 return connectivity_state_; 202 } 203 204 void ShillClient::StartAccessPoint(const std::string& ssid) { 205 LOG(INFO) << "Starting Soft AP: " << ssid; 206 ap_manager_client_->Start(ssid); 207 } 208 209 void ShillClient::StopAccessPoint() { 210 LOG(INFO) << "Stopping Soft AP"; 211 ap_manager_client_->Stop(); 212 } 213 214 void ShillClient::AddConnectionChangedCallback( 215 const ConnectionChangedCallback& listener) { 216 connectivity_listeners_.push_back(listener); 217 } 218 219 bool ShillClient::IsMonitoredDevice(DeviceProxy* device) { 220 if (device_whitelist_.empty()) { 221 return true; 222 } 223 VariantDictionary device_properties; 224 if (!device->GetProperties(&device_properties, nullptr)) { 225 LOG(ERROR) << "Devices without properties aren't whitelisted."; 226 return false; 227 } 228 auto it = device_properties.find(shill::kInterfaceProperty); 229 if (it == device_properties.end()) { 230 LOG(ERROR) << "Failed to find interface property in device properties."; 231 return false; 232 } 233 return ContainsKey(device_whitelist_, it->second.TryGet<string>()); 234 } 235 236 void ShillClient::OnShillServiceOwnerChange(const string& old_owner, 237 const string& new_owner) { 238 VLOG(1) << "Shill service owner name changed to '" << new_owner << "'"; 239 if (new_owner.empty()) { 240 CleanupConnectingService(); 241 devices_.clear(); 242 connectivity_state_ = Network::State::kOffline; 243 } else { 244 Init(); // New service owner means shill reset! 245 } 246 } 247 248 void ShillClient::OnManagerPropertyChangeRegistration(const string& interface, 249 const string& signal_name, 250 bool success) { 251 VLOG(3) << "Registered ManagerPropertyChange handler."; 252 CHECK(success) << "privetd requires Manager signals."; 253 VariantDictionary properties; 254 if (!manager_proxy_.GetProperties(&properties, nullptr)) { 255 LOG(ERROR) << "Unable to get properties from Manager, waiting for " 256 "Manager to come back online."; 257 return; 258 } 259 auto it = properties.find(shill::kDevicesProperty); 260 CHECK(it != properties.end()) << "Shill should always publish a device list."; 261 OnManagerPropertyChange(shill::kDevicesProperty, it->second); 262 } 263 264 void ShillClient::OnManagerPropertyChange(const string& property_name, 265 const Any& property_value) { 266 if (property_name != shill::kDevicesProperty) { 267 return; 268 } 269 bool update_connectivity = false; 270 VLOG(3) << "Manager's device list has changed."; 271 // We're going to remove every device we haven't seen in the update. 272 set<ObjectPath> device_paths_to_remove; 273 for (const auto& kv : devices_) { 274 device_paths_to_remove.insert(kv.first); 275 } 276 for (const auto& device_path : property_value.TryGet<vector<ObjectPath>>()) { 277 if (!device_path.IsValid()) { 278 LOG(ERROR) << "Ignoring invalid device path in Manager's device list."; 279 return; 280 } 281 auto it = devices_.find(device_path); 282 if (it != devices_.end()) { 283 // Found an existing proxy. Since the whitelist never changes, 284 // this still a valid device. 285 device_paths_to_remove.erase(device_path); 286 continue; 287 } 288 std::unique_ptr<DeviceProxy> device{new DeviceProxy{bus_, device_path}}; 289 if (!IsMonitoredDevice(device.get())) { 290 continue; 291 } 292 VLOG(3) << "Creating device proxy at " << device_path.value(); 293 devices_[device_path].device = std::move(device); 294 update_connectivity = true; 295 devices_[device_path].device->RegisterPropertyChangedSignalHandler( 296 base::Bind(&ShillClient::OnDevicePropertyChange, 297 weak_factory_.GetWeakPtr(), device_path), 298 base::Bind(&ShillClient::OnDevicePropertyChangeRegistration, 299 weak_factory_.GetWeakPtr(), device_path)); 300 } 301 // Clean up devices/services related to removed devices. 302 for (const ObjectPath& device_path : device_paths_to_remove) { 303 devices_.erase(device_path); 304 update_connectivity = true; 305 } 306 307 if (update_connectivity) 308 UpdateConnectivityState(); 309 } 310 311 void ShillClient::OnDevicePropertyChangeRegistration( 312 const ObjectPath& device_path, 313 const string& interface, 314 const string& signal_name, 315 bool success) { 316 VLOG(3) << "Registered DevicePropertyChange handler."; 317 auto it = devices_.find(device_path); 318 if (it == devices_.end()) { 319 return; 320 } 321 CHECK(success) << "Failed to subscribe to Device property changes."; 322 DeviceProxy* device = it->second.device.get(); 323 VariantDictionary properties; 324 if (!device->GetProperties(&properties, nullptr)) { 325 LOG(WARNING) << "Failed to get device properties?"; 326 return; 327 } 328 auto prop_it = properties.find(shill::kSelectedServiceProperty); 329 if (prop_it == properties.end()) { 330 LOG(WARNING) << "Failed to get device's selected service?"; 331 return; 332 } 333 OnDevicePropertyChange(device_path, shill::kSelectedServiceProperty, 334 prop_it->second); 335 } 336 337 void ShillClient::OnDevicePropertyChange(const ObjectPath& device_path, 338 const string& property_name, 339 const Any& property_value) { 340 // We only care about selected services anyway. 341 if (property_name != shill::kSelectedServiceProperty) { 342 return; 343 } 344 // If the device isn't our list of whitelisted devices, ignore it. 345 auto it = devices_.find(device_path); 346 if (it == devices_.end()) { 347 return; 348 } 349 DeviceState& device_state = it->second; 350 ObjectPath service_path{property_value.TryGet<ObjectPath>()}; 351 if (!service_path.IsValid()) { 352 LOG(ERROR) << "Device at " << device_path.value() 353 << " selected invalid service path."; 354 return; 355 } 356 VLOG(3) << "Device at " << it->first.value() << " has selected service at " 357 << service_path.value(); 358 bool removed_old_service{false}; 359 if (device_state.selected_service) { 360 if (device_state.selected_service->GetObjectPath() == service_path) { 361 return; // Spurious update? 362 } 363 device_state.selected_service.reset(); 364 device_state.service_state = Network::State::kOffline; 365 removed_old_service = true; 366 } 367 const bool reuse_connecting_service = 368 service_path.value() != "/" && connecting_service_ && 369 connecting_service_->GetObjectPath() == service_path; 370 if (reuse_connecting_service) { 371 device_state.selected_service = connecting_service_; 372 // When we reuse the connecting service, we need to make sure that our 373 // cached state is correct. Normally, we do this by relying reading the 374 // state when our signal handlers finish registering, but this may have 375 // happened long in the past for the connecting service. 376 string state; 377 if (GetStateForService(connecting_service_.get(), &state)) { 378 device_state.service_state = ShillServiceStateToNetworkState(state); 379 } else { 380 LOG(WARNING) << "Failed to read properties from existing service " 381 "on selection."; 382 } 383 } else if (service_path.value() != "/") { 384 // The device has selected a new service we haven't see before. 385 device_state.selected_service = 386 std::make_shared<ServiceProxy>(bus_, service_path); 387 device_state.selected_service->RegisterPropertyChangedSignalHandler( 388 base::Bind(&ShillClient::OnServicePropertyChange, 389 weak_factory_.GetWeakPtr(), service_path), 390 base::Bind(&ShillClient::OnServicePropertyChangeRegistration, 391 weak_factory_.GetWeakPtr(), service_path)); 392 } 393 394 if (reuse_connecting_service || removed_old_service) { 395 UpdateConnectivityState(); 396 } 397 } 398 399 void ShillClient::OnServicePropertyChangeRegistration(const ObjectPath& path, 400 const string& interface, 401 const string& signal_name, 402 bool success) { 403 VLOG(3) << "OnServicePropertyChangeRegistration(" << path.value() << ");"; 404 ServiceProxy* service{nullptr}; 405 if (connecting_service_ && connecting_service_->GetObjectPath() == path) { 406 // Note that the connecting service might also be a selected service. 407 service = connecting_service_.get(); 408 if (!success) 409 CleanupConnectingService(); 410 } else { 411 for (const auto& kv : devices_) { 412 if (kv.second.selected_service && 413 kv.second.selected_service->GetObjectPath() == path) { 414 service = kv.second.selected_service.get(); 415 break; 416 } 417 } 418 } 419 if (service == nullptr || !success) { 420 return; // A failure or success for a proxy we no longer care about. 421 } 422 VariantDictionary properties; 423 if (!service->GetProperties(&properties, nullptr)) { 424 return; 425 } 426 // Give ourselves property changed signals for the initial property 427 // values. 428 for (auto name : {shill::kStateProperty, shill::kSignalStrengthProperty, 429 shill::kErrorProperty}) { 430 auto it = properties.find(name); 431 if (it != properties.end()) 432 OnServicePropertyChange(path, name, it->second); 433 } 434 } 435 436 void ShillClient::OnServicePropertyChange(const ObjectPath& service_path, 437 const string& property_name, 438 const Any& property_value) { 439 VLOG(3) << "ServicePropertyChange(" << service_path.value() << ", " 440 << property_name << ", ...);"; 441 442 bool is_connecting_service = 443 connecting_service_ && 444 connecting_service_->GetObjectPath() == service_path; 445 if (property_name == shill::kStateProperty) { 446 const string state{property_value.TryGet<string>()}; 447 if (state.empty()) { 448 VLOG(3) << "Invalid service state update."; 449 return; 450 } 451 VLOG(3) << "New service state=" << state; 452 OnStateChangeForSelectedService(service_path, state); 453 if (is_connecting_service) 454 OnStateChangeForConnectingService(state); 455 } else if (property_name == shill::kSignalStrengthProperty) { 456 VLOG(3) << "Signal strength=" << property_value.TryGet<uint8_t>(); 457 if (is_connecting_service) 458 OnStrengthChangeForConnectingService(property_value.TryGet<uint8_t>()); 459 } else if (property_name == shill::kErrorProperty) { 460 VLOG(3) << "Error=" << property_value.TryGet<std::string>(); 461 if (is_connecting_service) 462 connecting_service_error_ = property_value.TryGet<std::string>(); 463 } 464 } 465 466 void ShillClient::OnStateChangeForConnectingService(const string& state) { 467 switch (ShillServiceStateToNetworkState(state)) { 468 case Network::State::kOnline: { 469 auto callback = connect_done_callback_; 470 connect_done_callback_.Reset(); 471 CleanupConnectingService(); 472 473 if (!callback.is_null()) 474 callback.Run(nullptr); 475 break; 476 } 477 case Network::State::kError: { 478 ConnectToServiceError(connecting_service_); 479 break; 480 } 481 case Network::State::kOffline: 482 case Network::State::kConnecting: 483 break; 484 } 485 } 486 487 void ShillClient::OnErrorChangeForConnectingService(const std::string& error) { 488 if (error.empty()) 489 return; 490 491 auto callback = connect_done_callback_; 492 CleanupConnectingService(); 493 494 weave::ErrorPtr weave_error; 495 weave::Error::AddTo(&weave_error, FROM_HERE, error, 496 "Failed to connect to WiFi network"); 497 498 if (!callback.is_null()) 499 callback.Run(std::move(weave_error)); 500 } 501 502 void ShillClient::OnStrengthChangeForConnectingService( 503 uint8_t signal_strength) { 504 if (signal_strength == 0 || have_called_connect_) { 505 return; 506 } 507 VLOG(1) << "Connecting service has signal. Calling Connect()."; 508 have_called_connect_ = true; 509 // Failures here indicate that we've already connected, 510 // or are connecting, or some other very unexciting thing. 511 // Ignore all that, and rely on state changes to detect 512 // connectivity. 513 connecting_service_->Connect(nullptr); 514 } 515 516 void ShillClient::OnStateChangeForSelectedService( 517 const ObjectPath& service_path, 518 const string& state) { 519 // Find the device/service pair responsible for this update 520 VLOG(3) << "State for potentially selected service " << service_path.value() 521 << " have changed to " << state; 522 for (auto& kv : devices_) { 523 if (kv.second.selected_service && 524 kv.second.selected_service->GetObjectPath() == service_path) { 525 VLOG(3) << "Updated cached connection state for selected service."; 526 kv.second.service_state = ShillServiceStateToNetworkState(state); 527 UpdateConnectivityState(); 528 return; 529 } 530 } 531 } 532 533 void ShillClient::UpdateConnectivityState() { 534 // Update the connectivity state of the device by picking the 535 // state of the currently most connected selected service. 536 Network::State new_connectivity_state{Network::State::kOffline}; 537 for (const auto& kv : devices_) { 538 if (kv.second.service_state > new_connectivity_state) { 539 new_connectivity_state = kv.second.service_state; 540 } 541 } 542 VLOG(1) << "Connectivity changed: " << EnumToString(connectivity_state_) 543 << " -> " << EnumToString(new_connectivity_state); 544 // Notify listeners even if state changed to the same value. Listeners may 545 // want to handle this event. 546 connectivity_state_ = new_connectivity_state; 547 // We may call UpdateConnectivityState whenever we mutate a data structure 548 // such that our connectivity status could change. However, we don't want 549 // to allow people to call into ShillClient while some other operation is 550 // underway. Therefore, call our callbacks later, when we're in a good 551 // state. 552 base::MessageLoop::current()->PostTask( 553 FROM_HERE, base::Bind(&ShillClient::NotifyConnectivityListeners, 554 weak_factory_.GetWeakPtr(), 555 GetConnectionState() == Network::State::kOnline)); 556 } 557 558 void ShillClient::NotifyConnectivityListeners(bool am_online) { 559 VLOG(3) << "Notifying connectivity listeners that online=" << am_online; 560 for (const auto& listener : connectivity_listeners_) 561 listener.Run(); 562 } 563 564 void ShillClient::CleanupConnectingService() { 565 if (connecting_service_) { 566 connecting_service_->ReleaseObjectProxy(base::Bind(&IgnoreDetachEvent)); 567 connecting_service_.reset(); 568 } 569 connect_done_callback_.Reset(); 570 have_called_connect_ = false; 571 } 572 573 void ShillClient::OpenSslSocket(const std::string& host, 574 uint16_t port, 575 const OpenSslSocketCallback& callback) { 576 if (disable_xmpp_) 577 return; 578 std::unique_ptr<weave::Stream> raw_stream{ 579 SocketStream::ConnectBlocking(host, port)}; 580 if (!raw_stream) { 581 brillo::ErrorPtr error; 582 brillo::errors::system::AddSystemError(&error, FROM_HERE, errno); 583 weave::ErrorPtr weave_error; 584 ConvertError(*error.get(), &weave_error); 585 base::MessageLoop::current()->PostTask( 586 FROM_HERE, base::Bind(callback, nullptr, base::Passed(&weave_error))); 587 return; 588 } 589 590 SocketStream::TlsConnect(std::move(raw_stream), host, callback); 591 } 592 593 } // namespace buffet 594