network_connection_handler.cc revision 558790d6acca3451cf3a6b497803a5f07d0bec58
1// Copyright (c) 2013 The Chromium 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 "chromeos/network/network_connection_handler.h"
6
7#include "base/bind.h"
8#include "base/command_line.h"
9#include "base/json/json_reader.h"
10#include "chromeos/chromeos_switches.h"
11#include "chromeos/dbus/dbus_thread_manager.h"
12#include "chromeos/dbus/shill_manager_client.h"
13#include "chromeos/dbus/shill_service_client.h"
14#include "chromeos/network/certificate_pattern_matcher.h"
15#include "chromeos/network/managed_network_configuration_handler.h"
16#include "chromeos/network/network_configuration_handler.h"
17#include "chromeos/network/network_event_log.h"
18#include "chromeos/network/network_handler_callbacks.h"
19#include "chromeos/network/network_state.h"
20#include "chromeos/network/network_state_handler.h"
21#include "chromeos/network/network_ui_data.h"
22#include "dbus/object_path.h"
23#include "net/cert/x509_certificate.h"
24#include "third_party/cros_system_api/dbus/service_constants.h"
25
26namespace chromeos {
27
28namespace {
29
30void InvokeErrorCallback(const std::string& service_path,
31                         const network_handler::ErrorCallback& error_callback,
32                         const std::string& error_name) {
33  std::string error_msg = "Connect Error: " + error_name;
34  NET_LOG_ERROR(error_msg, service_path);
35  if (error_callback.is_null())
36    return;
37  scoped_ptr<base::DictionaryValue> error_data(
38      network_handler::CreateErrorData(service_path, error_name, error_msg));
39  error_callback.Run(error_name, error_data.Pass());
40}
41
42bool NetworkConnectable(const NetworkState* network) {
43  if (network->type() == flimflam::kTypeVPN)
44    return false;  // TODO(stevenjb): Shill needs to properly set Connectable.
45  return network->connectable();
46}
47
48bool NetworkMayNeedCredentials(const NetworkState* network) {
49  if (network->type() == flimflam::kTypeWifi &&
50      (network->security() == flimflam::kSecurity8021x ||
51       network->security() == flimflam::kSecurityWep /* For dynamic WEP*/))
52    return true;
53  if (network->type() == flimflam::kTypeVPN)
54    return true;
55  return false;
56}
57
58bool NetworkRequiresActivation(const NetworkState* network) {
59  return (network->type() == flimflam::kTypeCellular &&
60          (network->activation_state() != flimflam::kActivationStateActivated ||
61           network->cellular_out_of_credits()));
62}
63
64bool VPNIsConfigured(const std::string& service_path,
65                     const base::DictionaryValue& service_properties) {
66  // VPN Provider values are read from the "Provider" dictionary, not the
67  // "Provider.Type", etc keys (which are used only to set the values).
68  const base::DictionaryValue* provider_properties;
69  if (!service_properties.GetDictionaryWithoutPathExpansion(
70          flimflam::kProviderProperty, &provider_properties)) {
71    NET_LOG_ERROR("VPN Provider Dictionary not present", service_path);
72    return false;
73  }
74  std::string provider_type;
75  if (!provider_properties->GetStringWithoutPathExpansion(
76          flimflam::kTypeProperty, &provider_type)) {
77    NET_LOG_ERROR("VPN Provider Type not present", service_path);
78    return false;
79  }
80  if (provider_type == flimflam::kProviderOpenVpn) {
81    std::string hostname;
82    provider_properties->GetStringWithoutPathExpansion(
83        flimflam::kHostProperty, &hostname);
84    if (hostname.empty()) {
85      NET_LOG_EVENT("OpenVPN: No hostname", service_path);
86      return false;
87    }
88    std::string username;
89    provider_properties->GetStringWithoutPathExpansion(
90        flimflam::kOpenVPNUserProperty, &username);
91    if (username.empty()) {
92      NET_LOG_EVENT("OpenVPN: No username", service_path);
93      return false;
94    }
95    bool passphrase_required = false;
96    provider_properties->GetBooleanWithoutPathExpansion(
97        flimflam::kPassphraseRequiredProperty, &passphrase_required);
98    std::string passphrase;
99    provider_properties->GetStringWithoutPathExpansion(
100        flimflam::kOpenVPNPasswordProperty, &passphrase);
101    if (passphrase_required && passphrase.empty()) {
102      NET_LOG_EVENT("OpenVPN: No passphrase", service_path);
103      return false;
104    }
105    NET_LOG_EVENT("OpenVPN Is Configured", service_path);
106  } else {
107    bool passphrase_required = false;
108    std::string passphrase;
109    provider_properties->GetBooleanWithoutPathExpansion(
110        flimflam::kL2tpIpsecPskRequiredProperty, &passphrase_required);
111    provider_properties->GetStringWithoutPathExpansion(
112        flimflam::kL2tpIpsecPskProperty, &passphrase);
113    if (passphrase_required && passphrase.empty())
114      return false;
115    NET_LOG_EVENT("VPN Is Configured", service_path);
116  }
117  return true;
118}
119
120}  // namespace
121
122const char NetworkConnectionHandler::kErrorNotFound[] = "not-found";
123const char NetworkConnectionHandler::kErrorConnected[] = "connected";
124const char NetworkConnectionHandler::kErrorConnecting[] = "connecting";
125const char NetworkConnectionHandler::kErrorNotConnected[] = "not-connected";
126const char NetworkConnectionHandler::kErrorPassphraseRequired[] =
127    "passphrase-required";
128const char NetworkConnectionHandler::kErrorActivationRequired[] =
129    "activation-required";
130const char NetworkConnectionHandler::kErrorCertificateRequired[] =
131    "certificate-required";
132const char NetworkConnectionHandler::kErrorConfigurationRequired[] =
133    "configuration-required";
134const char NetworkConnectionHandler::kErrorShillError[] = "shill-error";
135const char NetworkConnectionHandler::kErrorConnectFailed[] = "connect-failed";
136const char NetworkConnectionHandler::kErrorDisconnectFailed[] =
137    "disconnect-failed";
138const char NetworkConnectionHandler::kErrorMissingProviderType[] =
139    "missing-provider-type";
140const char NetworkConnectionHandler::kErrorUnknown[] = "unknown-error";
141
142struct NetworkConnectionHandler::ConnectRequest {
143  ConnectRequest(const std::string& service_path,
144                 const base::Closure& success,
145                 const network_handler::ErrorCallback& error)
146      : service_path(service_path),
147        connect_state(CONNECT_REQUESTED),
148        success_callback(success),
149        error_callback(error) {
150  }
151  enum ConnectState {
152    CONNECT_REQUESTED = 0,
153    CONNECT_STARTED = 1,
154    CONNECT_CONNECTING = 2
155  };
156  std::string service_path;
157  ConnectState connect_state;
158  base::Closure success_callback;
159  network_handler::ErrorCallback error_callback;
160};
161
162NetworkConnectionHandler::NetworkConnectionHandler()
163    : cert_loader_(NULL),
164      network_state_handler_(NULL),
165      network_configuration_handler_(NULL),
166      logged_in_(false),
167      certificates_loaded_(false) {
168}
169
170NetworkConnectionHandler::~NetworkConnectionHandler() {
171  if (network_state_handler_)
172    network_state_handler_->RemoveObserver(this, FROM_HERE);
173  if (cert_loader_)
174    cert_loader_->RemoveObserver(this);
175  if (LoginState::IsInitialized())
176    LoginState::Get()->RemoveObserver(this);
177}
178
179void NetworkConnectionHandler::Init(
180    NetworkStateHandler* network_state_handler,
181    NetworkConfigurationHandler* network_configuration_handler) {
182  if (LoginState::IsInitialized()) {
183    LoginState::Get()->AddObserver(this);
184    logged_in_ =
185        LoginState::Get()->GetLoggedInState() == LoginState::LOGGED_IN_ACTIVE;
186  }
187  if (CertLoader::IsInitialized()) {
188    cert_loader_ = CertLoader::Get();
189    cert_loader_->AddObserver(this);
190    certificates_loaded_ = cert_loader_->certificates_loaded();
191  } else {
192    // TODO(stevenjb): Require a mock or stub cert_loader in tests.
193    certificates_loaded_ = true;
194  }
195  if (network_state_handler) {
196    network_state_handler_ = network_state_handler;
197    network_state_handler_->AddObserver(this, FROM_HERE);
198  }
199  network_configuration_handler_ = network_configuration_handler;
200}
201
202void NetworkConnectionHandler::LoggedInStateChanged(
203    LoginState::LoggedInState state) {
204  NET_LOG_EVENT("NewNetworkConnectionHandler",
205                CommandLine::ForCurrentProcess()->HasSwitch(
206                    chromeos::switches::kUseNewNetworkConnectionHandler) ?
207                "enabled" : "disabled");
208  if (state == LoginState::LOGGED_IN_ACTIVE) {
209    logged_in_ = true;
210    NET_LOG_EVENT("Logged In", "");
211  }
212}
213
214void NetworkConnectionHandler::OnCertificatesLoaded(
215    const net::CertificateList& cert_list,
216    bool initial_load) {
217  certificates_loaded_ = true;
218  NET_LOG_EVENT("Certificates Loaded", "");
219  if (queued_connect_) {
220    NET_LOG_EVENT("Connecting to Queued Network",
221                  queued_connect_->service_path);
222    ConnectToNetwork(queued_connect_->service_path,
223                     queued_connect_->success_callback,
224                     queued_connect_->error_callback,
225                     true /* ignore_error_state */);
226  } else if (initial_load) {
227    // Once certificates have loaded, connect to the "best" available network.
228    network_state_handler_->ConnectToBestWifiNetwork();
229  }
230}
231
232void NetworkConnectionHandler::ConnectToNetwork(
233    const std::string& service_path,
234    const base::Closure& success_callback,
235    const network_handler::ErrorCallback& error_callback,
236    bool ignore_error_state) {
237  NET_LOG_USER("ConnectToNetwork", service_path);
238  // Clear any existing queued connect request.
239  queued_connect_.reset();
240  const NetworkState* network =
241      network_state_handler_->GetNetworkState(service_path);
242  if (!network) {
243    InvokeErrorCallback(service_path, error_callback, kErrorNotFound);
244    return;
245  }
246  if (HasConnectingNetwork(service_path)) {
247    NET_LOG_USER("Connect Request While Pending", service_path);
248    InvokeErrorCallback(service_path, error_callback, kErrorConnecting);
249    return;
250  }
251  if (network->IsConnectedState()) {
252    InvokeErrorCallback(service_path, error_callback, kErrorConnected);
253    return;
254  }
255  if (network->IsConnectingState()) {
256    InvokeErrorCallback(service_path, error_callback, kErrorConnecting);
257    return;
258  }
259  if (NetworkRequiresActivation(network)) {
260    InvokeErrorCallback(service_path, error_callback, kErrorActivationRequired);
261    return;
262  }
263  if (!ignore_error_state) {
264    if (network->passphrase_required()) {
265      InvokeErrorCallback(service_path, error_callback,
266                          kErrorPassphraseRequired);
267      return;
268    }
269    if (network->error() == flimflam::kErrorConnectFailed) {
270      InvokeErrorCallback(service_path, error_callback,
271                          kErrorPassphraseRequired);
272      return;
273    }
274    if (network->error() == flimflam::kErrorBadPassphrase) {
275      InvokeErrorCallback(service_path, error_callback,
276                          kErrorPassphraseRequired);
277      return;
278    }
279    if (network->HasAuthenticationError()) {
280      InvokeErrorCallback(service_path, error_callback,
281                          kErrorConfigurationRequired);
282      return;
283    }
284  }
285
286  // All synchronous checks passed, add |service_path| to connecting list.
287  pending_requests_.insert(std::make_pair(
288      service_path,
289      ConnectRequest(service_path, success_callback, error_callback)));
290
291  if (!NetworkConnectable(network) &&
292      NetworkMayNeedCredentials(network)) {
293    // Request additional properties to check.
294    network_configuration_handler_->GetProperties(
295        network->path(),
296        base::Bind(&NetworkConnectionHandler::VerifyConfiguredAndConnect,
297                   AsWeakPtr()),
298        base::Bind(&NetworkConnectionHandler::HandleConfigurationFailure,
299                   AsWeakPtr(), network->path()));
300    return;
301  }
302  // All checks passed, send connect request.
303  CallShillConnect(service_path);
304}
305
306void NetworkConnectionHandler::DisconnectNetwork(
307    const std::string& service_path,
308    const base::Closure& success_callback,
309    const network_handler::ErrorCallback& error_callback) {
310  NET_LOG_USER("DisconnectNetwork", service_path);
311  const NetworkState* network =
312      network_state_handler_->GetNetworkState(service_path);
313  if (!network) {
314    InvokeErrorCallback(service_path, error_callback, kErrorNotFound);
315    return;
316  }
317  if (!network->IsConnectedState()) {
318    InvokeErrorCallback(service_path, error_callback, kErrorNotConnected);
319    return;
320  }
321  CallShillDisconnect(service_path, success_callback, error_callback);
322}
323
324bool NetworkConnectionHandler::HasConnectingNetwork(
325    const std::string& service_path) {
326  return pending_requests_.count(service_path) != 0;
327}
328
329void NetworkConnectionHandler::NetworkListChanged() {
330  CheckAllPendingRequests();
331}
332
333void NetworkConnectionHandler::NetworkPropertiesUpdated(
334    const NetworkState* network) {
335  if (HasConnectingNetwork(network->path()))
336    CheckPendingRequest(network->path());
337}
338
339NetworkConnectionHandler::ConnectRequest*
340NetworkConnectionHandler::pending_request(
341    const std::string& service_path) {
342  std::map<std::string, ConnectRequest>::iterator iter =
343      pending_requests_.find(service_path);
344  return iter != pending_requests_.end() ? &(iter->second) : NULL;
345}
346
347// ConnectToNetwork implementation
348
349void NetworkConnectionHandler::VerifyConfiguredAndConnect(
350    const std::string& service_path,
351    const base::DictionaryValue& service_properties) {
352  NET_LOG_EVENT("VerifyConfiguredAndConnect", service_path);
353
354  const NetworkState* network =
355      network_state_handler_->GetNetworkState(service_path);
356  if (!network) {
357    ErrorCallbackForPendingRequest(service_path, kErrorNotFound);
358    return;
359  }
360
361  // VPN requires a host and username to be set.
362  if (network->type() == flimflam::kTypeVPN &&
363      !VPNIsConfigured(service_path, service_properties)) {
364    ErrorCallbackForPendingRequest(service_path, kErrorConfigurationRequired);
365    return;
366  }
367
368  // Check certificate properties in kUIDataProperty.
369  scoped_ptr<NetworkUIData> ui_data =
370      ManagedNetworkConfigurationHandler::GetUIData(service_properties);
371  if (ui_data && ui_data->certificate_type() == CLIENT_CERT_TYPE_PATTERN) {
372    // User must be logged in to connect to a network requiring a certificate.
373    if (!logged_in_ || !cert_loader_) {
374      ErrorCallbackForPendingRequest(service_path, kErrorCertificateRequired);
375      return;
376    }
377    // If certificates have not been loaded yet, queue the connect request.
378    if (!certificates_loaded_) {
379      ConnectRequest* request = pending_request(service_path);
380      DCHECK(request);
381      NET_LOG_EVENT("Connect Request Queued", service_path);
382      queued_connect_.reset(new ConnectRequest(
383          service_path, request->success_callback, request->error_callback));
384      pending_requests_.erase(service_path);
385      return;
386    }
387
388    // Ensure the certificate is available.
389    std::string pkcs11_id;
390    if (!CertificateIsConfigured(ui_data.get(), &pkcs11_id)) {
391      ErrorCallbackForPendingRequest(service_path, kErrorCertificateRequired);
392      return;
393    }
394
395    // The network may not be 'Connectable' because the certificate data is
396    // not set up, so configure tpm slot/pin and pkcs11_id before connecting.
397    // TODO(stevenjb): Remove this code once NetworkConfigurationHandler
398    // handles this.
399    NET_LOG_EVENT("Configuring Network", service_path);
400    const std::string& tpm_slot = cert_loader_->tpm_token_slot();
401    const std::string& tpm_pin = cert_loader_->tpm_user_pin();
402    base::DictionaryValue config_properties;
403    network->GetConfigProperties(&config_properties);
404
405    if (network->type() == flimflam::kTypeVPN) {
406      // VPN Provider values are read from the "Provider" dictionary, not the
407      // "Provider.Type", etc keys (which are used only to set the values).
408      std::string provider_type;
409      const base::DictionaryValue* provider_properties;
410      if (service_properties.GetDictionaryWithoutPathExpansion(
411              flimflam::kProviderProperty, &provider_properties)) {
412        provider_properties->GetStringWithoutPathExpansion(
413            flimflam::kTypeProperty, &provider_type);
414      }
415      if (provider_type.empty()) {
416        ErrorCallbackForPendingRequest(service_path, kErrorMissingProviderType);
417        return;
418      }
419      if (provider_type == flimflam::kProviderOpenVpn) {
420        config_properties.SetStringWithoutPathExpansion(
421            flimflam::kOpenVPNClientCertSlotProperty, tpm_slot);
422        config_properties.SetStringWithoutPathExpansion(
423            flimflam::kOpenVPNPinProperty, tpm_pin);
424        config_properties.SetStringWithoutPathExpansion(
425            flimflam::kOpenVPNClientCertIdProperty, pkcs11_id);
426      } else {
427        config_properties.SetStringWithoutPathExpansion(
428            flimflam::kL2tpIpsecClientCertSlotProperty, tpm_slot);
429        config_properties.SetStringWithoutPathExpansion(
430            flimflam::kL2tpIpsecPinProperty, tpm_pin);
431        config_properties.SetStringWithoutPathExpansion(
432            flimflam::kL2tpIpsecClientCertIdProperty, pkcs11_id);
433      }
434    } else if (network->type() == flimflam::kTypeWifi) {
435      config_properties.SetStringWithoutPathExpansion(
436          flimflam::kEapPinProperty, cert_loader_->tpm_user_pin());
437      config_properties.SetStringWithoutPathExpansion(
438          flimflam::kEapCertIdProperty, pkcs11_id);
439      config_properties.SetStringWithoutPathExpansion(
440          flimflam::kEapKeyIdProperty, pkcs11_id);
441    }
442    network_configuration_handler_->SetProperties(
443        service_path,
444        config_properties,
445        base::Bind(&NetworkConnectionHandler::CallShillConnect,
446                   AsWeakPtr(), service_path),
447        base::Bind(&NetworkConnectionHandler::HandleConfigurationFailure,
448                   AsWeakPtr(), service_path));
449   return;
450  }
451
452  // Otherwise, we need to configure the network.
453  ErrorCallbackForPendingRequest(service_path, kErrorConfigurationRequired);
454}
455
456void NetworkConnectionHandler::CallShillConnect(
457    const std::string& service_path) {
458  NET_LOG_EVENT("Sending Connect Request to Shill", service_path);
459  DBusThreadManager::Get()->GetShillServiceClient()->Connect(
460      dbus::ObjectPath(service_path),
461      base::Bind(&NetworkConnectionHandler::HandleShillConnectSuccess,
462                 AsWeakPtr(), service_path),
463      base::Bind(&NetworkConnectionHandler::HandleShillConnectFailure,
464                 AsWeakPtr(), service_path));
465}
466
467void NetworkConnectionHandler::HandleConfigurationFailure(
468    const std::string& service_path,
469    const std::string& error_name,
470    scoped_ptr<base::DictionaryValue> error_data) {
471  ConnectRequest* request = pending_request(service_path);
472  DCHECK(request);
473  network_handler::ErrorCallback error_callback = request->error_callback;
474  pending_requests_.erase(service_path);
475  if (!error_callback.is_null())
476    error_callback.Run(error_name, error_data.Pass());
477}
478
479void NetworkConnectionHandler::HandleShillConnectSuccess(
480    const std::string& service_path) {
481  ConnectRequest* request = pending_request(service_path);
482  DCHECK(request);
483  request->connect_state = ConnectRequest::CONNECT_STARTED;
484  NET_LOG_EVENT("Connect Request Acknowledged", service_path);
485  // Do not call success_callback here, wait for one of the following
486  // conditions:
487  // * State transitions to a non connecting state indicating succes or failure
488  // * Network is no longer in the visible list, indicating failure
489  CheckPendingRequest(service_path);
490}
491
492void NetworkConnectionHandler::HandleShillConnectFailure(
493    const std::string& service_path,
494    const std::string& dbus_error_name,
495    const std::string& dbus_error_message) {
496  ConnectRequest* request = pending_request(service_path);
497  DCHECK(request);
498  network_handler::ErrorCallback error_callback = request->error_callback;
499  pending_requests_.erase(service_path);
500  network_handler::ShillErrorCallbackFunction(
501      kErrorConnectFailed, service_path, error_callback,
502      dbus_error_name, dbus_error_message);
503}
504
505void NetworkConnectionHandler::CheckPendingRequest(
506    const std::string service_path) {
507  ConnectRequest* request = pending_request(service_path);
508  DCHECK(request);
509  if (request->connect_state == ConnectRequest::CONNECT_REQUESTED)
510    return;  // Request has not started, ignore update
511  const NetworkState* network =
512      network_state_handler_->GetNetworkState(service_path);
513  if (!network) {
514    ErrorCallbackForPendingRequest(service_path, kErrorNotFound);
515    return;
516  }
517  if (network->IsConnectingState()) {
518    request->connect_state = ConnectRequest::CONNECT_CONNECTING;
519    return;
520  }
521  if (network->IsConnectedState()) {
522    NET_LOG_EVENT("Connect Request Succeeded", service_path);
523    if (!request->success_callback.is_null())
524      request->success_callback.Run();
525    pending_requests_.erase(service_path);
526    return;
527  }
528  if (network->connection_state() == flimflam::kStateIdle &&
529      request->connect_state != ConnectRequest::CONNECT_CONNECTING) {
530    // Connection hasn't started yet, keep waiting.
531    return;
532  }
533
534  // Network is neither connecting or connected; an error occurred.
535  std::string error_name = kErrorConnectFailed;
536  std::string error_detail = network->error();
537  if (error_detail.empty()) {
538    if (network->connection_state() == flimflam::kStateFailure)
539      error_detail = flimflam::kUnknownString;
540    else
541      error_detail = "Unexpected State: " + network->connection_state();
542  }
543  std::string error_msg = error_name + ": " + error_detail;
544  NET_LOG_ERROR(error_msg, service_path);
545
546  network_handler::ErrorCallback error_callback = request->error_callback;
547  pending_requests_.erase(service_path);
548  if (error_callback.is_null())
549    return;
550  scoped_ptr<base::DictionaryValue> error_data(
551      network_handler::CreateErrorData(service_path, error_name, error_msg));
552  error_callback.Run(error_name, error_data.Pass());
553}
554
555void NetworkConnectionHandler::CheckAllPendingRequests() {
556  for (std::map<std::string, ConnectRequest>::iterator iter =
557           pending_requests_.begin(); iter != pending_requests_.end(); ++iter) {
558    CheckPendingRequest(iter->first);
559  }
560}
561
562bool NetworkConnectionHandler::CertificateIsConfigured(NetworkUIData* ui_data,
563                                                       std::string* pkcs11_id) {
564  if (ui_data->certificate_pattern().Empty())
565    return false;
566
567  // Find the matching certificate.
568  scoped_refptr<net::X509Certificate> matching_cert =
569      certificate_pattern::GetCertificateMatch(ui_data->certificate_pattern());
570  if (!matching_cert.get())
571    return false;
572  *pkcs11_id = cert_loader_->GetPkcs11IdForCert(*matching_cert.get());
573  return true;
574}
575
576void NetworkConnectionHandler::ErrorCallbackForPendingRequest(
577    const std::string& service_path,
578    const std::string& error_name) {
579  ConnectRequest* request = pending_request(service_path);
580  DCHECK(request);
581  // Remove the entry before invoking the callback in case it triggers a retry.
582  network_handler::ErrorCallback error_callback = request->error_callback;
583  pending_requests_.erase(service_path);
584  InvokeErrorCallback(service_path, error_callback, error_name);
585}
586
587// Disconnect
588
589void NetworkConnectionHandler::CallShillDisconnect(
590    const std::string& service_path,
591    const base::Closure& success_callback,
592    const network_handler::ErrorCallback& error_callback) {
593  NET_LOG_USER("Disconnect Request", service_path);
594  DBusThreadManager::Get()->GetShillServiceClient()->Disconnect(
595      dbus::ObjectPath(service_path),
596      base::Bind(&NetworkConnectionHandler::HandleShillDisconnectSuccess,
597                 AsWeakPtr(), service_path, success_callback),
598      base::Bind(&network_handler::ShillErrorCallbackFunction,
599                 kErrorDisconnectFailed, service_path, error_callback));
600}
601
602void NetworkConnectionHandler::HandleShillDisconnectSuccess(
603    const std::string& service_path,
604    const base::Closure& success_callback) {
605  NET_LOG_EVENT("Disconnect Request Sent", service_path);
606  if (!success_callback.is_null())
607    success_callback.Run();
608}
609
610}  // namespace chromeos
611