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