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