blob: 9db5e8535f94af812d1ed3fddd0c170f93dba3ba [file] [log] [blame]
Vitaly Buka58a288b2015-07-31 00:33:31 -07001// 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 "buffet/shill_client.h"
6
7#include <set>
8
9#include <base/message_loop/message_loop.h>
10#include <base/stl_util.h>
11#include <chromeos/any.h>
12#include <chromeos/dbus/service_constants.h>
13#include <chromeos/errors/error.h>
14#include <chromeos/errors/error_codes.h>
15
Vitaly Buka3ef4fff2015-07-31 01:12:07 -070016#include "buffet/ap_manager_client.h"
Vitaly Buka58a288b2015-07-31 00:33:31 -070017#include "weave/enum_to_string.h"
18
19using chromeos::Any;
20using chromeos::VariantDictionary;
21using dbus::ObjectPath;
22using org::chromium::flimflam::DeviceProxy;
23using org::chromium::flimflam::ServiceProxy;
24using std::map;
25using std::set;
26using std::string;
27using std::vector;
28
29namespace buffet {
30
31namespace {
32
33void IgnoreDetachEvent() {}
34
35bool GetStateForService(ServiceProxy* service, string* state) {
36 CHECK(service) << "|service| was nullptr in GetStateForService()";
37 VariantDictionary properties;
38 if (!service->GetProperties(&properties, nullptr)) {
39 LOG(WARNING) << "Failed to read properties from service.";
40 return false;
41 }
42 auto property_it = properties.find(shill::kStateProperty);
43 if (property_it == properties.end()) {
44 LOG(WARNING) << "No state found in service properties.";
45 return false;
46 }
47 string new_state = property_it->second.TryGet<string>();
48 if (new_state.empty()) {
49 LOG(WARNING) << "Invalid state value.";
50 return false;
51 }
52 *state = new_state;
53 return true;
54}
55
56weave::NetworkState ShillServiceStateToNetworkState(const string& state) {
57 // TODO(wiley) What does "unconfigured" mean in a world with multiple sets
58 // of WiFi credentials?
59 // TODO(wiley) Detect disabled devices, update state appropriately.
60 if ((state.compare(shill::kStateReady) == 0) ||
61 (state.compare(shill::kStatePortal) == 0) ||
62 (state.compare(shill::kStateOnline) == 0)) {
63 return weave::NetworkState::kConnected;
64 }
65 if ((state.compare(shill::kStateAssociation) == 0) ||
66 (state.compare(shill::kStateConfiguration) == 0)) {
67 return weave::NetworkState::kConnecting;
68 }
69 if ((state.compare(shill::kStateFailure) == 0) ||
70 (state.compare(shill::kStateActivationFailure) == 0)) {
71 // TODO(wiley) Get error information off the service object.
72 return weave::NetworkState::kFailure;
73 }
74 if ((state.compare(shill::kStateIdle) == 0) ||
75 (state.compare(shill::kStateOffline) == 0) ||
76 (state.compare(shill::kStateDisconnect) == 0)) {
77 return weave::NetworkState::kOffline;
78 }
79 LOG(WARNING) << "Unknown state found: '" << state << "'";
80 return weave::NetworkState::kOffline;
81}
82
83} // namespace
84
85ShillClient::ShillClient(const scoped_refptr<dbus::Bus>& bus,
86 const set<string>& device_whitelist)
87 : bus_{bus},
88 manager_proxy_{bus_, ObjectPath{"/"}},
Vitaly Buka3ef4fff2015-07-31 01:12:07 -070089 device_whitelist_{device_whitelist},
90 ap_manager_client_{new ApManagerClient(bus)} {
Vitaly Buka58a288b2015-07-31 00:33:31 -070091 manager_proxy_.RegisterPropertyChangedSignalHandler(
92 base::Bind(&ShillClient::OnManagerPropertyChange,
93 weak_factory_.GetWeakPtr()),
94 base::Bind(&ShillClient::OnManagerPropertyChangeRegistration,
95 weak_factory_.GetWeakPtr()));
96 auto owner_changed_cb = base::Bind(&ShillClient::OnShillServiceOwnerChange,
97 weak_factory_.GetWeakPtr());
98 bus_->GetObjectProxy(shill::kFlimflamServiceName, ObjectPath{"/"})
99 ->SetNameOwnerChangedCallback(owner_changed_cb);
100}
101
Vitaly Buka3ef4fff2015-07-31 01:12:07 -0700102ShillClient::~ShillClient() {}
103
Vitaly Buka58a288b2015-07-31 00:33:31 -0700104void ShillClient::Init() {
105 VLOG(2) << "ShillClient::Init();";
106 CleanupConnectingService(false);
107 devices_.clear();
108 connectivity_state_ = weave::NetworkState::kOffline;
109 VariantDictionary properties;
110 if (!manager_proxy_.GetProperties(&properties, nullptr)) {
111 LOG(ERROR) << "Unable to get properties from Manager, waiting for "
112 "Manager to come back online.";
113 return;
114 }
115 auto it = properties.find(shill::kDevicesProperty);
116 CHECK(it != properties.end()) << "shill should always publish a device list.";
117 OnManagerPropertyChange(shill::kDevicesProperty, it->second);
118}
119
120bool ShillClient::ConnectToService(const string& ssid,
121 const string& passphrase,
122 const base::Closure& on_success,
123 chromeos::ErrorPtr* error) {
124 CleanupConnectingService(false);
125 VariantDictionary service_properties;
126 service_properties[shill::kTypeProperty] = Any{string{shill::kTypeWifi}};
127 service_properties[shill::kSSIDProperty] = Any{ssid};
128 service_properties[shill::kPassphraseProperty] = Any{passphrase};
129 service_properties[shill::kSecurityProperty] = Any{
130 string{passphrase.empty() ? shill::kSecurityNone : shill::kSecurityPsk}};
131 service_properties[shill::kSaveCredentialsProperty] = Any{true};
132 service_properties[shill::kAutoConnectProperty] = Any{true};
133 ObjectPath service_path;
134 if (!manager_proxy_.ConfigureService(service_properties, &service_path,
135 error)) {
136 return false;
137 }
138 if (!manager_proxy_.RequestScan(shill::kTypeWifi, error)) {
139 return false;
140 }
141 connecting_service_.reset(new ServiceProxy{bus_, service_path});
142 on_connect_success_.Reset(on_success);
143 connecting_service_->RegisterPropertyChangedSignalHandler(
144 base::Bind(&ShillClient::OnServicePropertyChange,
145 weak_factory_.GetWeakPtr(), service_path),
146 base::Bind(&ShillClient::OnServicePropertyChangeRegistration,
147 weak_factory_.GetWeakPtr(), service_path));
148 return true;
149}
150
151weave::NetworkState ShillClient::GetConnectionState() const {
152 return connectivity_state_;
153}
154
Vitaly Buka3ef4fff2015-07-31 01:12:07 -0700155void ShillClient::EnableAccessPoint(const std::string& ssid) {
156 ap_manager_client_->Start(ssid);
157}
158
159void ShillClient::DisableAccessPoint() {
160 ap_manager_client_->Stop();
161}
162
Vitaly Buka58a288b2015-07-31 00:33:31 -0700163void ShillClient::AddOnConnectionChangedCallback(
164 const OnConnectionChangedCallback& listener) {
165 connectivity_listeners_.push_back(listener);
166}
167
168bool ShillClient::IsMonitoredDevice(DeviceProxy* device) {
169 if (device_whitelist_.empty()) {
170 return true;
171 }
172 VariantDictionary device_properties;
173 if (!device->GetProperties(&device_properties, nullptr)) {
174 LOG(ERROR) << "Devices without properties aren't whitelisted.";
175 return false;
176 }
177 auto it = device_properties.find(shill::kInterfaceProperty);
178 if (it == device_properties.end()) {
179 LOG(ERROR) << "Failed to find interface property in device properties.";
180 return false;
181 }
182 return ContainsKey(device_whitelist_, it->second.TryGet<string>());
183}
184
185void ShillClient::OnShillServiceOwnerChange(const string& old_owner,
186 const string& new_owner) {
187 VLOG(1) << "Shill service owner name changed to '" << new_owner << "'";
188 if (new_owner.empty()) {
189 CleanupConnectingService(false);
190 devices_.clear();
191 connectivity_state_ = weave::NetworkState::kOffline;
192 } else {
193 Init(); // New service owner means shill reset!
194 }
195}
196
197void ShillClient::OnManagerPropertyChangeRegistration(const string& interface,
198 const string& signal_name,
199 bool success) {
200 VLOG(3) << "Registered ManagerPropertyChange handler.";
201 CHECK(success) << "privetd requires Manager signals.";
202 VariantDictionary properties;
203 if (!manager_proxy_.GetProperties(&properties, nullptr)) {
204 LOG(ERROR) << "Unable to get properties from Manager, waiting for "
205 "Manager to come back online.";
206 return;
207 }
208 auto it = properties.find(shill::kDevicesProperty);
209 CHECK(it != properties.end()) << "Shill should always publish a device list.";
210 OnManagerPropertyChange(shill::kDevicesProperty, it->second);
211}
212
213void ShillClient::OnManagerPropertyChange(const string& property_name,
214 const Any& property_value) {
215 if (property_name != shill::kDevicesProperty) {
216 return;
217 }
218 VLOG(3) << "Manager's device list has changed.";
219 // We're going to remove every device we haven't seen in the update.
220 set<ObjectPath> device_paths_to_remove;
221 for (const auto& kv : devices_) {
222 device_paths_to_remove.insert(kv.first);
223 }
224 for (const auto& device_path : property_value.TryGet<vector<ObjectPath>>()) {
225 if (!device_path.IsValid()) {
226 LOG(ERROR) << "Ignoring invalid device path in Manager's device list.";
227 return;
228 }
229 auto it = devices_.find(device_path);
230 if (it != devices_.end()) {
231 // Found an existing proxy. Since the whitelist never changes,
232 // this still a valid device.
233 device_paths_to_remove.erase(device_path);
234 continue;
235 }
236 std::unique_ptr<DeviceProxy> device{new DeviceProxy{bus_, device_path}};
237 if (!IsMonitoredDevice(device.get())) {
238 continue;
239 }
240 device->RegisterPropertyChangedSignalHandler(
241 base::Bind(&ShillClient::OnDevicePropertyChange,
242 weak_factory_.GetWeakPtr(), device_path),
243 base::Bind(&ShillClient::OnDevicePropertyChangeRegistration,
244 weak_factory_.GetWeakPtr(), device_path));
245 VLOG(3) << "Creating device proxy at " << device_path.value();
246 devices_[device_path].device = std::move(device);
247 }
248 // Clean up devices/services related to removed devices.
249 if (!device_paths_to_remove.empty()) {
250 for (const ObjectPath& device_path : device_paths_to_remove) {
251 devices_.erase(device_path);
252 }
253 UpdateConnectivityState();
254 }
255}
256
257void ShillClient::OnDevicePropertyChangeRegistration(
258 const ObjectPath& device_path,
259 const string& interface,
260 const string& signal_name,
261 bool success) {
262 VLOG(3) << "Registered DevicePropertyChange handler.";
263 auto it = devices_.find(device_path);
264 if (it == devices_.end()) {
265 return;
266 }
267 CHECK(success) << "Failed to subscribe to Device property changes.";
268 DeviceProxy* device = it->second.device.get();
269 VariantDictionary properties;
270 if (!device->GetProperties(&properties, nullptr)) {
271 LOG(WARNING) << "Failed to get device properties?";
272 return;
273 }
274 auto prop_it = properties.find(shill::kSelectedServiceProperty);
275 if (prop_it == properties.end()) {
276 LOG(WARNING) << "Failed to get device's selected service?";
277 return;
278 }
279 OnDevicePropertyChange(device_path, shill::kSelectedServiceProperty,
280 prop_it->second);
281}
282
283void ShillClient::OnDevicePropertyChange(const ObjectPath& device_path,
284 const string& property_name,
285 const Any& property_value) {
286 // We only care about selected services anyway.
287 if (property_name != shill::kSelectedServiceProperty) {
288 return;
289 }
290 // If the device isn't our list of whitelisted devices, ignore it.
291 auto it = devices_.find(device_path);
292 if (it == devices_.end()) {
293 return;
294 }
295 DeviceState& device_state = it->second;
296 ObjectPath service_path{property_value.TryGet<ObjectPath>()};
297 if (!service_path.IsValid()) {
298 LOG(ERROR) << "Device at " << device_path.value()
299 << " selected invalid service path.";
300 return;
301 }
302 VLOG(3) << "Device at " << it->first.value() << " has selected service at "
303 << service_path.value();
304 bool removed_old_service{false};
305 if (device_state.selected_service) {
306 if (device_state.selected_service->GetObjectPath() == service_path) {
307 return; // Spurious update?
308 }
309 device_state.selected_service.reset();
310 device_state.service_state = weave::NetworkState::kOffline;
311 removed_old_service = true;
312 }
313 std::shared_ptr<ServiceProxy> new_service;
314 const bool reuse_connecting_service =
315 service_path.value() != "/" && connecting_service_ &&
316 connecting_service_->GetObjectPath() == service_path;
317 if (reuse_connecting_service) {
318 new_service = connecting_service_;
319 // When we reuse the connecting service, we need to make sure that our
320 // cached state is correct. Normally, we do this by relying reading the
321 // state when our signal handlers finish registering, but this may have
322 // happened long in the past for the connecting service.
323 string state;
324 if (GetStateForService(new_service.get(), &state)) {
325 device_state.service_state = ShillServiceStateToNetworkState(state);
326 } else {
327 LOG(WARNING) << "Failed to read properties from existing service "
328 "on selection.";
329 }
330 } else if (service_path.value() != "/") {
331 // The device has selected a new service we haven't see before.
332 new_service.reset(new ServiceProxy{bus_, service_path});
333 new_service->RegisterPropertyChangedSignalHandler(
334 base::Bind(&ShillClient::OnServicePropertyChange,
335 weak_factory_.GetWeakPtr(), service_path),
336 base::Bind(&ShillClient::OnServicePropertyChangeRegistration,
337 weak_factory_.GetWeakPtr(), service_path));
338 }
339 device_state.selected_service = new_service;
340 if (reuse_connecting_service || removed_old_service) {
341 UpdateConnectivityState();
342 }
343}
344
345void ShillClient::OnServicePropertyChangeRegistration(const ObjectPath& path,
346 const string& interface,
347 const string& signal_name,
348 bool success) {
349 VLOG(3) << "OnServicePropertyChangeRegistration(" << path.value() << ");";
350 ServiceProxy* service{nullptr};
351 if (connecting_service_ && connecting_service_->GetObjectPath() == path) {
352 // Note that the connecting service might also be a selected service.
353 service = connecting_service_.get();
354 if (!success) {
355 CleanupConnectingService(false);
356 }
357 } else {
358 for (const auto& kv : devices_) {
359 if (kv.second.selected_service &&
360 kv.second.selected_service->GetObjectPath() == path) {
361 service = kv.second.selected_service.get();
362 break;
363 }
364 }
365 }
366 if (service == nullptr || !success) {
367 return; // A failure or success for a proxy we no longer care about.
368 }
369 VariantDictionary properties;
370 if (!service->GetProperties(&properties, nullptr)) {
371 return;
372 }
373 // Give ourselves property changed signals for the initial property
374 // values.
375 auto it = properties.find(shill::kStateProperty);
376 if (it != properties.end()) {
377 OnServicePropertyChange(path, shill::kStateProperty, it->second);
378 }
379 it = properties.find(shill::kSignalStrengthProperty);
380 if (it != properties.end()) {
381 OnServicePropertyChange(path, shill::kSignalStrengthProperty, it->second);
382 }
383}
384
385void ShillClient::OnServicePropertyChange(const ObjectPath& service_path,
386 const string& property_name,
387 const Any& property_value) {
388 VLOG(3) << "ServicePropertyChange(" << service_path.value() << ", "
389 << property_name << ", ...);";
390 if (property_name == shill::kStateProperty) {
391 const string state{property_value.TryGet<string>()};
392 if (state.empty()) {
393 VLOG(3) << "Invalid service state update.";
394 return;
395 }
396 VLOG(3) << "New service state=" << state;
397 OnStateChangeForSelectedService(service_path, state);
398 OnStateChangeForConnectingService(service_path, state);
399 } else if (property_name == shill::kSignalStrengthProperty) {
400 OnStrengthChangeForConnectingService(service_path,
401 property_value.TryGet<uint8_t>());
402 }
403}
404
405void ShillClient::OnStateChangeForConnectingService(
406 const ObjectPath& service_path,
407 const string& state) {
408 if (!connecting_service_ ||
409 connecting_service_->GetObjectPath() != service_path ||
410 ShillServiceStateToNetworkState(state) !=
411 weave::NetworkState::kConnected) {
412 return;
413 }
414 connecting_service_reset_pending_ = true;
415 on_connect_success_.callback().Run();
416 CleanupConnectingService(true);
417}
418
419void ShillClient::OnStrengthChangeForConnectingService(
420 const ObjectPath& service_path,
421 uint8_t signal_strength) {
422 if (!connecting_service_ ||
423 connecting_service_->GetObjectPath() != service_path ||
424 signal_strength <= 0 || have_called_connect_) {
425 return;
426 }
427 VLOG(1) << "Connecting service has signal. Calling Connect().";
428 have_called_connect_ = true;
429 // Failures here indicate that we've already connected,
430 // or are connecting, or some other very unexciting thing.
431 // Ignore all that, and rely on state changes to detect
432 // connectivity.
433 connecting_service_->Connect(nullptr);
434}
435
436void ShillClient::OnStateChangeForSelectedService(
437 const ObjectPath& service_path,
438 const string& state) {
439 // Find the device/service pair responsible for this update
440 VLOG(3) << "State for potentially selected service " << service_path.value()
441 << " have changed to " << state;
442 for (auto& kv : devices_) {
443 if (kv.second.selected_service &&
444 kv.second.selected_service->GetObjectPath() == service_path) {
445 VLOG(3) << "Updated cached connection state for selected service.";
446 kv.second.service_state = ShillServiceStateToNetworkState(state);
447 UpdateConnectivityState();
448 return;
449 }
450 }
451}
452
453void ShillClient::UpdateConnectivityState() {
454 // Update the connectivity state of the device by picking the
455 // state of the currently most connected selected service.
456 weave::NetworkState new_connectivity_state{weave::NetworkState::kOffline};
457 for (const auto& kv : devices_) {
458 if (kv.second.service_state > new_connectivity_state) {
459 new_connectivity_state = kv.second.service_state;
460 }
461 }
462 VLOG(1) << "Connectivity changed: " << EnumToString(connectivity_state_)
463 << " -> " << EnumToString(new_connectivity_state);
464 // Notify listeners even if state changed to the same value. Listeners may
465 // want to handle this event.
466 connectivity_state_ = new_connectivity_state;
467 // We may call UpdateConnectivityState whenever we mutate a data structure
468 // such that our connectivity status could change. However, we don't want
469 // to allow people to call into ShillClient while some other operation is
470 // underway. Therefore, call our callbacks later, when we're in a good
471 // state.
472 base::MessageLoop::current()->PostTask(
473 FROM_HERE,
474 base::Bind(&ShillClient::NotifyConnectivityListeners,
475 weak_factory_.GetWeakPtr(),
476 GetConnectionState() == weave::NetworkState::kConnected));
477}
478
479void ShillClient::NotifyConnectivityListeners(bool am_online) {
480 VLOG(3) << "Notifying connectivity listeners that online=" << am_online;
481 for (const auto& listener : connectivity_listeners_) {
482 listener.Run(am_online);
483 }
484}
485
486void ShillClient::CleanupConnectingService(bool check_for_reset_pending) {
487 if (check_for_reset_pending && !connecting_service_reset_pending_) {
488 return; // Must have called connect before we got here.
489 }
490 if (connecting_service_) {
491 connecting_service_->ReleaseObjectProxy(base::Bind(&IgnoreDetachEvent));
492 connecting_service_.reset();
493 }
494 on_connect_success_.Cancel();
495 have_called_connect_ = false;
496 connecting_service_reset_pending_ = false;
497}
498
499} // namespace buffet