shill_client.cc revision 685b2aeed879903b86448428dec1e88c9b6741b1
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 31using brillo::Any; 32using brillo::VariantDictionary; 33using dbus::ObjectPath; 34using org::chromium::flimflam::DeviceProxy; 35using org::chromium::flimflam::ServiceProxy; 36using std::map; 37using std::set; 38using std::string; 39using std::vector; 40using weave::EnumToString; 41using weave::provider::Network; 42 43namespace buffet { 44 45namespace { 46 47const char kErrorDomain[] = "buffet"; 48 49void IgnoreDetachEvent() {} 50 51bool GetStateForService(ServiceProxy* service, string* state) { 52 CHECK(service) << "|service| was nullptr in GetStateForService()"; 53 VariantDictionary properties; 54 if (!service->GetProperties(&properties, nullptr)) { 55 LOG(WARNING) << "Failed to read properties from service."; 56 return false; 57 } 58 auto property_it = properties.find(shill::kStateProperty); 59 if (property_it == properties.end()) { 60 LOG(WARNING) << "No state found in service properties."; 61 return false; 62 } 63 string new_state = property_it->second.TryGet<string>(); 64 if (new_state.empty()) { 65 LOG(WARNING) << "Invalid state value."; 66 return false; 67 } 68 *state = new_state; 69 return true; 70} 71 72Network::State ShillServiceStateToNetworkState(const string& state) { 73 // TODO(wiley) What does "unconfigured" mean in a world with multiple sets 74 // of WiFi credentials? 75 // TODO(wiley) Detect disabled devices, update state appropriately. 76 if ((state.compare(shill::kStateReady) == 0) || 77 (state.compare(shill::kStatePortal) == 0) || 78 (state.compare(shill::kStateOnline) == 0)) { 79 return Network::State::kOnline; 80 } 81 if ((state.compare(shill::kStateAssociation) == 0) || 82 (state.compare(shill::kStateConfiguration) == 0)) { 83 return Network::State::kConnecting; 84 } 85 if ((state.compare(shill::kStateFailure) == 0) || 86 (state.compare(shill::kStateActivationFailure) == 0)) { 87 // TODO(wiley) Get error information off the service object. 88 return Network::State::kError; 89 } 90 if ((state.compare(shill::kStateIdle) == 0) || 91 (state.compare(shill::kStateOffline) == 0) || 92 (state.compare(shill::kStateDisconnect) == 0)) { 93 return Network::State::kOffline; 94 } 95 LOG(WARNING) << "Unknown state found: '" << state << "'"; 96 return Network::State::kOffline; 97} 98 99} // namespace 100 101ShillClient::ShillClient(const scoped_refptr<dbus::Bus>& bus, 102 const set<string>& device_whitelist, 103 bool disable_xmpp) 104 : bus_{bus}, 105 manager_proxy_{bus_}, 106 device_whitelist_{device_whitelist}, 107 disable_xmpp_{disable_xmpp}, 108 ap_manager_client_{new ApManagerClient(bus)} { 109 manager_proxy_.RegisterPropertyChangedSignalHandler( 110 base::Bind(&ShillClient::OnManagerPropertyChange, 111 weak_factory_.GetWeakPtr()), 112 base::Bind(&ShillClient::OnManagerPropertyChangeRegistration, 113 weak_factory_.GetWeakPtr())); 114 auto owner_changed_cb = base::Bind(&ShillClient::OnShillServiceOwnerChange, 115 weak_factory_.GetWeakPtr()); 116 bus_->GetObjectProxy(shill::kFlimflamServiceName, ObjectPath{"/"}) 117 ->SetNameOwnerChangedCallback(owner_changed_cb); 118} 119 120ShillClient::~ShillClient() {} 121 122void 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 138void 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, kErrorDomain, "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 187void 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 200Network::State ShillClient::GetConnectionState() const { 201 return connectivity_state_; 202} 203 204void ShillClient::StartAccessPoint(const std::string& ssid) { 205 LOG(INFO) << "Starting Soft AP: " << ssid; 206 ap_manager_client_->Start(ssid); 207} 208 209void ShillClient::StopAccessPoint() { 210 LOG(INFO) << "Stopping Soft AP"; 211 ap_manager_client_->Stop(); 212} 213 214void ShillClient::AddConnectionChangedCallback( 215 const ConnectionChangedCallback& listener) { 216 connectivity_listeners_.push_back(listener); 217} 218 219bool 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 236void 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 248void 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 264void ShillClient::OnManagerPropertyChange(const string& property_name, 265 const Any& property_value) { 266 if (property_name != shill::kDevicesProperty) { 267 return; 268 } 269 VLOG(3) << "Manager's device list has changed."; 270 // We're going to remove every device we haven't seen in the update. 271 set<ObjectPath> device_paths_to_remove; 272 for (const auto& kv : devices_) { 273 device_paths_to_remove.insert(kv.first); 274 } 275 for (const auto& device_path : property_value.TryGet<vector<ObjectPath>>()) { 276 if (!device_path.IsValid()) { 277 LOG(ERROR) << "Ignoring invalid device path in Manager's device list."; 278 return; 279 } 280 auto it = devices_.find(device_path); 281 if (it != devices_.end()) { 282 // Found an existing proxy. Since the whitelist never changes, 283 // this still a valid device. 284 device_paths_to_remove.erase(device_path); 285 continue; 286 } 287 std::unique_ptr<DeviceProxy> device{new DeviceProxy{bus_, device_path}}; 288 if (!IsMonitoredDevice(device.get())) { 289 continue; 290 } 291 device->RegisterPropertyChangedSignalHandler( 292 base::Bind(&ShillClient::OnDevicePropertyChange, 293 weak_factory_.GetWeakPtr(), device_path), 294 base::Bind(&ShillClient::OnDevicePropertyChangeRegistration, 295 weak_factory_.GetWeakPtr(), device_path)); 296 VLOG(3) << "Creating device proxy at " << device_path.value(); 297 devices_[device_path].device = std::move(device); 298 } 299 // Clean up devices/services related to removed devices. 300 if (!device_paths_to_remove.empty()) { 301 for (const ObjectPath& device_path : device_paths_to_remove) { 302 devices_.erase(device_path); 303 } 304 UpdateConnectivityState(); 305 } 306} 307 308void ShillClient::OnDevicePropertyChangeRegistration( 309 const ObjectPath& device_path, 310 const string& interface, 311 const string& signal_name, 312 bool success) { 313 VLOG(3) << "Registered DevicePropertyChange handler."; 314 auto it = devices_.find(device_path); 315 if (it == devices_.end()) { 316 return; 317 } 318 CHECK(success) << "Failed to subscribe to Device property changes."; 319 DeviceProxy* device = it->second.device.get(); 320 VariantDictionary properties; 321 if (!device->GetProperties(&properties, nullptr)) { 322 LOG(WARNING) << "Failed to get device properties?"; 323 return; 324 } 325 auto prop_it = properties.find(shill::kSelectedServiceProperty); 326 if (prop_it == properties.end()) { 327 LOG(WARNING) << "Failed to get device's selected service?"; 328 return; 329 } 330 OnDevicePropertyChange(device_path, shill::kSelectedServiceProperty, 331 prop_it->second); 332} 333 334void ShillClient::OnDevicePropertyChange(const ObjectPath& device_path, 335 const string& property_name, 336 const Any& property_value) { 337 // We only care about selected services anyway. 338 if (property_name != shill::kSelectedServiceProperty) { 339 return; 340 } 341 // If the device isn't our list of whitelisted devices, ignore it. 342 auto it = devices_.find(device_path); 343 if (it == devices_.end()) { 344 return; 345 } 346 DeviceState& device_state = it->second; 347 ObjectPath service_path{property_value.TryGet<ObjectPath>()}; 348 if (!service_path.IsValid()) { 349 LOG(ERROR) << "Device at " << device_path.value() 350 << " selected invalid service path."; 351 return; 352 } 353 VLOG(3) << "Device at " << it->first.value() << " has selected service at " 354 << service_path.value(); 355 bool removed_old_service{false}; 356 if (device_state.selected_service) { 357 if (device_state.selected_service->GetObjectPath() == service_path) { 358 return; // Spurious update? 359 } 360 device_state.selected_service.reset(); 361 device_state.service_state = Network::State::kOffline; 362 removed_old_service = true; 363 } 364 std::shared_ptr<ServiceProxy> new_service; 365 const bool reuse_connecting_service = 366 service_path.value() != "/" && connecting_service_ && 367 connecting_service_->GetObjectPath() == service_path; 368 if (reuse_connecting_service) { 369 new_service = connecting_service_; 370 // When we reuse the connecting service, we need to make sure that our 371 // cached state is correct. Normally, we do this by relying reading the 372 // state when our signal handlers finish registering, but this may have 373 // happened long in the past for the connecting service. 374 string state; 375 if (GetStateForService(new_service.get(), &state)) { 376 device_state.service_state = ShillServiceStateToNetworkState(state); 377 } else { 378 LOG(WARNING) << "Failed to read properties from existing service " 379 "on selection."; 380 } 381 } else if (service_path.value() != "/") { 382 // The device has selected a new service we haven't see before. 383 new_service.reset(new ServiceProxy{bus_, service_path}); 384 new_service->RegisterPropertyChangedSignalHandler( 385 base::Bind(&ShillClient::OnServicePropertyChange, 386 weak_factory_.GetWeakPtr(), service_path), 387 base::Bind(&ShillClient::OnServicePropertyChangeRegistration, 388 weak_factory_.GetWeakPtr(), service_path)); 389 } 390 device_state.selected_service = new_service; 391 if (reuse_connecting_service || removed_old_service) { 392 UpdateConnectivityState(); 393 } 394} 395 396void ShillClient::OnServicePropertyChangeRegistration(const ObjectPath& path, 397 const string& interface, 398 const string& signal_name, 399 bool success) { 400 VLOG(3) << "OnServicePropertyChangeRegistration(" << path.value() << ");"; 401 ServiceProxy* service{nullptr}; 402 if (connecting_service_ && connecting_service_->GetObjectPath() == path) { 403 // Note that the connecting service might also be a selected service. 404 service = connecting_service_.get(); 405 if (!success) 406 CleanupConnectingService(); 407 } else { 408 for (const auto& kv : devices_) { 409 if (kv.second.selected_service && 410 kv.second.selected_service->GetObjectPath() == path) { 411 service = kv.second.selected_service.get(); 412 break; 413 } 414 } 415 } 416 if (service == nullptr || !success) { 417 return; // A failure or success for a proxy we no longer care about. 418 } 419 VariantDictionary properties; 420 if (!service->GetProperties(&properties, nullptr)) { 421 return; 422 } 423 // Give ourselves property changed signals for the initial property 424 // values. 425 for (auto name : {shill::kStateProperty, shill::kSignalStrengthProperty, 426 shill::kErrorProperty}) { 427 auto it = properties.find(name); 428 if (it != properties.end()) 429 OnServicePropertyChange(path, name, it->second); 430 } 431} 432 433void ShillClient::OnServicePropertyChange(const ObjectPath& service_path, 434 const string& property_name, 435 const Any& property_value) { 436 VLOG(3) << "ServicePropertyChange(" << service_path.value() << ", " 437 << property_name << ", ...);"; 438 439 bool is_connecting_service = 440 connecting_service_ && 441 connecting_service_->GetObjectPath() == service_path; 442 if (property_name == shill::kStateProperty) { 443 const string state{property_value.TryGet<string>()}; 444 if (state.empty()) { 445 VLOG(3) << "Invalid service state update."; 446 return; 447 } 448 VLOG(3) << "New service state=" << state; 449 OnStateChangeForSelectedService(service_path, state); 450 if (is_connecting_service) 451 OnStateChangeForConnectingService(state); 452 } else if (property_name == shill::kSignalStrengthProperty) { 453 VLOG(3) << "Signal strength=" << property_value.TryGet<uint8_t>(); 454 if (is_connecting_service) 455 OnStrengthChangeForConnectingService(property_value.TryGet<uint8_t>()); 456 } else if (property_name == shill::kErrorProperty) { 457 VLOG(3) << "Error=" << property_value.TryGet<std::string>(); 458 if (is_connecting_service) 459 connecting_service_error_ = property_value.TryGet<std::string>(); 460 } 461} 462 463void ShillClient::OnStateChangeForConnectingService(const string& state) { 464 switch (ShillServiceStateToNetworkState(state)) { 465 case Network::State::kOnline: { 466 auto callback = connect_done_callback_; 467 connect_done_callback_.Reset(); 468 CleanupConnectingService(); 469 470 if (!callback.is_null()) 471 callback.Run(nullptr); 472 break; 473 } 474 case Network::State::kError: { 475 ConnectToServiceError(connecting_service_); 476 break; 477 } 478 case Network::State::kOffline: 479 case Network::State::kConnecting: 480 break; 481 } 482} 483 484void ShillClient::OnErrorChangeForConnectingService(const std::string& error) { 485 if (error.empty()) 486 return; 487 488 auto callback = connect_done_callback_; 489 CleanupConnectingService(); 490 491 weave::ErrorPtr weave_error; 492 weave::Error::AddTo(&weave_error, FROM_HERE, kErrorDomain, error, 493 "Failed to connect to WiFi network"); 494 495 if (!callback.is_null()) 496 callback.Run(std::move(weave_error)); 497} 498 499void ShillClient::OnStrengthChangeForConnectingService( 500 uint8_t signal_strength) { 501 if (signal_strength == 0 || have_called_connect_) { 502 return; 503 } 504 VLOG(1) << "Connecting service has signal. Calling Connect()."; 505 have_called_connect_ = true; 506 // Failures here indicate that we've already connected, 507 // or are connecting, or some other very unexciting thing. 508 // Ignore all that, and rely on state changes to detect 509 // connectivity. 510 connecting_service_->Connect(nullptr); 511} 512 513void ShillClient::OnStateChangeForSelectedService( 514 const ObjectPath& service_path, 515 const string& state) { 516 // Find the device/service pair responsible for this update 517 VLOG(3) << "State for potentially selected service " << service_path.value() 518 << " have changed to " << state; 519 for (auto& kv : devices_) { 520 if (kv.second.selected_service && 521 kv.second.selected_service->GetObjectPath() == service_path) { 522 VLOG(3) << "Updated cached connection state for selected service."; 523 kv.second.service_state = ShillServiceStateToNetworkState(state); 524 UpdateConnectivityState(); 525 return; 526 } 527 } 528} 529 530void ShillClient::UpdateConnectivityState() { 531 // Update the connectivity state of the device by picking the 532 // state of the currently most connected selected service. 533 Network::State new_connectivity_state{Network::State::kOffline}; 534 for (const auto& kv : devices_) { 535 if (kv.second.service_state > new_connectivity_state) { 536 new_connectivity_state = kv.second.service_state; 537 } 538 } 539 VLOG(1) << "Connectivity changed: " << EnumToString(connectivity_state_) 540 << " -> " << EnumToString(new_connectivity_state); 541 // Notify listeners even if state changed to the same value. Listeners may 542 // want to handle this event. 543 connectivity_state_ = new_connectivity_state; 544 // We may call UpdateConnectivityState whenever we mutate a data structure 545 // such that our connectivity status could change. However, we don't want 546 // to allow people to call into ShillClient while some other operation is 547 // underway. Therefore, call our callbacks later, when we're in a good 548 // state. 549 base::MessageLoop::current()->PostTask( 550 FROM_HERE, base::Bind(&ShillClient::NotifyConnectivityListeners, 551 weak_factory_.GetWeakPtr(), 552 GetConnectionState() == Network::State::kOnline)); 553} 554 555void ShillClient::NotifyConnectivityListeners(bool am_online) { 556 VLOG(3) << "Notifying connectivity listeners that online=" << am_online; 557 for (const auto& listener : connectivity_listeners_) 558 listener.Run(); 559} 560 561void ShillClient::CleanupConnectingService() { 562 if (connecting_service_) { 563 connecting_service_->ReleaseObjectProxy(base::Bind(&IgnoreDetachEvent)); 564 connecting_service_.reset(); 565 } 566 connect_done_callback_.Reset(); 567 have_called_connect_ = false; 568} 569 570void ShillClient::OpenSslSocket(const std::string& host, 571 uint16_t port, 572 const OpenSslSocketCallback& callback) { 573 if (disable_xmpp_) 574 return; 575 std::unique_ptr<weave::Stream> raw_stream{ 576 SocketStream::ConnectBlocking(host, port)}; 577 if (!raw_stream) { 578 brillo::ErrorPtr error; 579 brillo::errors::system::AddSystemError(&error, FROM_HERE, errno); 580 weave::ErrorPtr weave_error; 581 ConvertError(*error.get(), &weave_error); 582 base::MessageLoop::current()->PostTask( 583 FROM_HERE, base::Bind(callback, nullptr, base::Passed(&weave_error))); 584 return; 585 } 586 587 SocketStream::TlsConnect(std::move(raw_stream), host, callback); 588} 589 590} // namespace buffet 591