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.
5 #include "chromeos/network/network_connection_handler.h"
8 #include "base/command_line.h"
9 #include "base/json/json_reader.h"
10 #include "base/location.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "chromeos/chromeos_switches.h"
13 #include "chromeos/dbus/dbus_thread_manager.h"
14 #include "chromeos/dbus/shill_manager_client.h"
15 #include "chromeos/dbus/shill_service_client.h"
16 #include "chromeos/network/client_cert_util.h"
17 #include "chromeos/network/network_configuration_handler.h"
18 #include "chromeos/network/network_event_log.h"
19 #include "chromeos/network/network_handler_callbacks.h"
20 #include "chromeos/network/network_profile_handler.h"
21 #include "chromeos/network/network_state.h"
22 #include "chromeos/network/network_state_handler.h"
23 #include "chromeos/network/network_ui_data.h"
24 #include "chromeos/network/shill_property_util.h"
25 #include "dbus/object_path.h"
26 #include "net/cert/x509_certificate.h"
27 #include "third_party/cros_system_api/dbus/service_constants.h"
33 void InvokeErrorCallback(const std::string& service_path,
34 const network_handler::ErrorCallback& error_callback,
35 const std::string& error_name) {
36 std::string error_msg = "Connect Error: " + error_name;
37 NET_LOG_ERROR(error_msg, service_path);
38 network_handler::RunErrorCallback(
39 error_callback, service_path, error_name, error_msg);
42 bool IsAuthenticationError(const std::string& error) {
43 return (error == shill::kErrorBadWEPKey ||
44 error == shill::kErrorPppAuthFailed ||
45 error == shill::kErrorEapLocalTlsFailed ||
46 error == shill::kErrorEapRemoteTlsFailed ||
47 error == shill::kErrorEapAuthenticationFailed);
50 bool VPNRequiresCredentials(const std::string& service_path,
51 const std::string& provider_type,
52 const base::DictionaryValue& provider_properties) {
53 if (provider_type == shill::kProviderOpenVpn) {
55 provider_properties.GetStringWithoutPathExpansion(
56 shill::kOpenVPNUserProperty, &username);
57 if (username.empty()) {
58 NET_LOG_EVENT("OpenVPN: No username", service_path);
61 bool passphrase_required = false;
62 provider_properties.GetBooleanWithoutPathExpansion(
63 shill::kPassphraseRequiredProperty, &passphrase_required);
64 if (passphrase_required) {
65 NET_LOG_EVENT("OpenVPN: Passphrase Required", service_path);
68 NET_LOG_EVENT("OpenVPN Is Configured", service_path);
70 bool passphrase_required = false;
71 std::string passphrase;
72 provider_properties.GetBooleanWithoutPathExpansion(
73 shill::kL2tpIpsecPskRequiredProperty, &passphrase_required);
74 if (passphrase_required) {
75 NET_LOG_EVENT("VPN: PSK Required", service_path);
78 NET_LOG_EVENT("VPN Is Configured", service_path);
83 std::string GetDefaultUserProfilePath(const NetworkState* network) {
84 if (!NetworkHandler::IsInitialized() ||
85 !LoginState::Get()->IsUserAuthenticated() ||
86 (network && network->type() == shill::kTypeWifi &&
87 network->security() == shill::kSecurityNone)) {
88 return NetworkProfileHandler::kSharedProfilePath;
90 const NetworkProfile* profile =
91 NetworkHandler::Get()->network_profile_handler()->GetDefaultUserProfile();
92 return profile ? profile->path : NetworkProfileHandler::kSharedProfilePath;
97 const char NetworkConnectionHandler::kErrorNotFound[] = "not-found";
98 const char NetworkConnectionHandler::kErrorConnected[] = "connected";
99 const char NetworkConnectionHandler::kErrorConnecting[] = "connecting";
100 const char NetworkConnectionHandler::kErrorNotConnected[] = "not-connected";
101 const char NetworkConnectionHandler::kErrorPassphraseRequired[] =
102 "passphrase-required";
103 const char NetworkConnectionHandler::kErrorActivationRequired[] =
104 "activation-required";
105 const char NetworkConnectionHandler::kErrorCertificateRequired[] =
106 "certificate-required";
107 const char NetworkConnectionHandler::kErrorConfigurationRequired[] =
108 "configuration-required";
109 const char NetworkConnectionHandler::kErrorAuthenticationRequired[] =
110 "authentication-required";
111 const char NetworkConnectionHandler::kErrorShillError[] = "shill-error";
112 const char NetworkConnectionHandler::kErrorConfigureFailed[] =
114 const char NetworkConnectionHandler::kErrorConnectCanceled[] =
117 struct NetworkConnectionHandler::ConnectRequest {
118 ConnectRequest(const std::string& service_path,
119 const std::string& profile_path,
120 const base::Closure& success,
121 const network_handler::ErrorCallback& error)
122 : service_path(service_path),
123 profile_path(profile_path),
124 connect_state(CONNECT_REQUESTED),
125 success_callback(success),
126 error_callback(error) {
129 CONNECT_REQUESTED = 0,
131 CONNECT_CONNECTING = 2
133 std::string service_path;
134 std::string profile_path;
135 ConnectState connect_state;
136 base::Closure success_callback;
137 network_handler::ErrorCallback error_callback;
140 NetworkConnectionHandler::NetworkConnectionHandler()
141 : cert_loader_(NULL),
142 network_state_handler_(NULL),
143 network_configuration_handler_(NULL),
145 certificates_loaded_(false) {
148 NetworkConnectionHandler::~NetworkConnectionHandler() {
149 if (network_state_handler_)
150 network_state_handler_->RemoveObserver(this, FROM_HERE);
152 cert_loader_->RemoveObserver(this);
153 if (LoginState::IsInitialized())
154 LoginState::Get()->RemoveObserver(this);
157 void NetworkConnectionHandler::Init(
158 NetworkStateHandler* network_state_handler,
159 NetworkConfigurationHandler* network_configuration_handler) {
160 if (LoginState::IsInitialized()) {
161 LoginState::Get()->AddObserver(this);
162 logged_in_ = LoginState::Get()->IsUserLoggedIn();
164 if (CertLoader::IsInitialized()) {
165 cert_loader_ = CertLoader::Get();
166 cert_loader_->AddObserver(this);
167 certificates_loaded_ = cert_loader_->certificates_loaded();
169 // TODO(stevenjb): Require a mock or stub cert_loader in tests.
170 certificates_loaded_ = true;
172 if (network_state_handler) {
173 network_state_handler_ = network_state_handler;
174 network_state_handler_->AddObserver(this, FROM_HERE);
176 network_configuration_handler_ = network_configuration_handler;
179 void NetworkConnectionHandler::LoggedInStateChanged() {
180 if (LoginState::Get()->IsUserLoggedIn()) {
182 NET_LOG_EVENT("Logged In", "");
186 void NetworkConnectionHandler::OnCertificatesLoaded(
187 const net::CertificateList& cert_list,
189 certificates_loaded_ = true;
190 NET_LOG_EVENT("Certificates Loaded", "");
191 if (queued_connect_) {
192 NET_LOG_EVENT("Connecting to Queued Network",
193 queued_connect_->service_path);
195 // Make a copy of |queued_connect_| parameters, because |queued_connect_|
196 // will get reset at the beginning of |ConnectToNetwork|.
197 std::string service_path = queued_connect_->service_path;
198 base::Closure success_callback = queued_connect_->success_callback;
199 network_handler::ErrorCallback error_callback =
200 queued_connect_->error_callback;
202 ConnectToNetwork(service_path, success_callback, error_callback,
203 false /* check_error_state */);
204 } else if (initial_load) {
205 // Once certificates have loaded, connect to the "best" available network.
206 network_state_handler_->ConnectToBestWifiNetwork();
210 void NetworkConnectionHandler::ConnectToNetwork(
211 const std::string& service_path,
212 const base::Closure& success_callback,
213 const network_handler::ErrorCallback& error_callback,
214 bool check_error_state) {
215 NET_LOG_USER("ConnectToNetwork", service_path);
216 // Clear any existing queued connect request.
217 queued_connect_.reset();
218 if (HasConnectingNetwork(service_path)) {
219 NET_LOG_USER("Connect Request While Pending", service_path);
220 InvokeErrorCallback(service_path, error_callback, kErrorConnecting);
224 // Check cached network state for connected, connecting, or unactivated
225 // networks. These states will not be affected by a recent configuration.
226 // Note: NetworkState may not exist for a network that was recently
227 // configured, in which case these checks do not apply anyway.
228 const NetworkState* network =
229 network_state_handler_->GetNetworkState(service_path);
232 // For existing networks, perform some immediate consistency checks.
233 if (network->IsConnectedState()) {
234 InvokeErrorCallback(service_path, error_callback, kErrorConnected);
237 if (network->IsConnectingState()) {
238 InvokeErrorCallback(service_path, error_callback, kErrorConnecting);
241 if (network->RequiresActivation()) {
242 InvokeErrorCallback(service_path, error_callback,
243 kErrorActivationRequired);
247 if (check_error_state) {
248 const std::string& error = network->error();
249 if (error == shill::kErrorBadPassphrase) {
250 InvokeErrorCallback(service_path, error_callback, error);
253 if (IsAuthenticationError(error)) {
255 service_path, error_callback, kErrorAuthenticationRequired);
261 // If the network does not have a profile path, specify the correct default
262 // profile here and set it once connected. Otherwise leave it empty to
263 // indicate that it does not need to be set.
264 std::string profile_path;
265 if (!network || network->profile_path().empty())
266 profile_path = GetDefaultUserProfilePath(network);
268 // All synchronous checks passed, add |service_path| to connecting list.
269 pending_requests_.insert(std::make_pair(
271 ConnectRequest(service_path, profile_path,
272 success_callback, error_callback)));
274 // Connect immediately to 'connectable' networks.
275 // TODO(stevenjb): Shill needs to properly set Connectable for VPN.
276 if (network && network->connectable() && network->type() != shill::kTypeVPN) {
277 CallShillConnect(service_path);
281 // Request additional properties to check. VerifyConfiguredAndConnect will
282 // use only these properties, not cached properties, to ensure that they
283 // are up to date after any recent configuration.
284 network_configuration_handler_->GetProperties(
286 base::Bind(&NetworkConnectionHandler::VerifyConfiguredAndConnect,
287 AsWeakPtr(), check_error_state),
288 base::Bind(&NetworkConnectionHandler::HandleConfigurationFailure,
289 AsWeakPtr(), service_path));
292 void NetworkConnectionHandler::DisconnectNetwork(
293 const std::string& service_path,
294 const base::Closure& success_callback,
295 const network_handler::ErrorCallback& error_callback) {
296 NET_LOG_USER("DisconnectNetwork", service_path);
297 const NetworkState* network =
298 network_state_handler_->GetNetworkState(service_path);
300 InvokeErrorCallback(service_path, error_callback, kErrorNotFound);
303 if (!network->IsConnectedState()) {
304 InvokeErrorCallback(service_path, error_callback, kErrorNotConnected);
307 CallShillDisconnect(service_path, success_callback, error_callback);
310 bool NetworkConnectionHandler::HasConnectingNetwork(
311 const std::string& service_path) {
312 return pending_requests_.count(service_path) != 0;
315 bool NetworkConnectionHandler::HasPendingConnectRequest() {
316 return pending_requests_.size() > 0;
319 void NetworkConnectionHandler::NetworkListChanged() {
320 CheckAllPendingRequests();
323 void NetworkConnectionHandler::NetworkPropertiesUpdated(
324 const NetworkState* network) {
325 if (HasConnectingNetwork(network->path()))
326 CheckPendingRequest(network->path());
329 NetworkConnectionHandler::ConnectRequest*
330 NetworkConnectionHandler::GetPendingRequest(const std::string& service_path) {
331 std::map<std::string, ConnectRequest>::iterator iter =
332 pending_requests_.find(service_path);
333 return iter != pending_requests_.end() ? &(iter->second) : NULL;
336 // ConnectToNetwork implementation
338 void NetworkConnectionHandler::VerifyConfiguredAndConnect(
339 bool check_error_state,
340 const std::string& service_path,
341 const base::DictionaryValue& service_properties) {
342 NET_LOG_EVENT("VerifyConfiguredAndConnect", service_path);
344 // If 'passphrase_required' is still true, then the 'Passphrase' property
345 // has not been set to a minimum length value.
346 bool passphrase_required = false;
347 service_properties.GetBooleanWithoutPathExpansion(
348 shill::kPassphraseRequiredProperty, &passphrase_required);
349 if (passphrase_required) {
350 ErrorCallbackForPendingRequest(service_path, kErrorPassphraseRequired);
354 std::string type, security;
355 service_properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type);
356 service_properties.GetStringWithoutPathExpansion(
357 shill::kSecurityProperty, &security);
358 bool connectable = false;
359 service_properties.GetBooleanWithoutPathExpansion(
360 shill::kConnectableProperty, &connectable);
362 // In case NetworkState was not available in ConnectToNetwork (e.g. it had
363 // been recently configured), we need to check Connectable again.
364 if (connectable && type != shill::kTypeVPN) {
365 // TODO(stevenjb): Shill needs to properly set Connectable for VPN.
366 CallShillConnect(service_path);
370 // Get VPN provider type and host (required for configuration) and ensure
371 // that required VPN non-cert properties are set.
372 const base::DictionaryValue* provider_properties = NULL;
373 std::string vpn_provider_type, vpn_provider_host;
374 if (type == shill::kTypeVPN) {
375 // VPN Provider values are read from the "Provider" dictionary, not the
376 // "Provider.Type", etc keys (which are used only to set the values).
377 if (service_properties.GetDictionaryWithoutPathExpansion(
378 shill::kProviderProperty, &provider_properties)) {
379 provider_properties->GetStringWithoutPathExpansion(
380 shill::kTypeProperty, &vpn_provider_type);
381 provider_properties->GetStringWithoutPathExpansion(
382 shill::kHostProperty, &vpn_provider_host);
384 if (vpn_provider_type.empty() || vpn_provider_host.empty()) {
385 ErrorCallbackForPendingRequest(service_path, kErrorConfigurationRequired);
390 client_cert::ConfigType client_cert_type = client_cert::CONFIG_TYPE_NONE;
391 if (type == shill::kTypeVPN) {
392 if (vpn_provider_type == shill::kProviderOpenVpn)
393 client_cert_type = client_cert::CONFIG_TYPE_OPENVPN;
395 client_cert_type = client_cert::CONFIG_TYPE_IPSEC;
396 } else if (type == shill::kTypeWifi && security == shill::kSecurity8021x) {
397 client_cert_type = client_cert::CONFIG_TYPE_EAP;
400 base::DictionaryValue config_properties;
401 if (client_cert_type != client_cert::CONFIG_TYPE_NONE) {
402 // If the client certificate must be configured, this will be set to a
404 std::string pkcs11_id;
406 // Check certificate properties in kUIDataProperty if configured.
407 // Note: Wifi/VPNConfigView set these properties explicitly, in which case
408 // only the TPM must be configured.
409 scoped_ptr<NetworkUIData> ui_data =
410 shill_property_util::GetUIDataFromProperties(service_properties);
411 if (ui_data && ui_data->certificate_type() == CLIENT_CERT_TYPE_PATTERN) {
412 // User must be logged in to connect to a network requiring a certificate.
413 if (!logged_in_ || !cert_loader_) {
414 ErrorCallbackForPendingRequest(service_path, kErrorCertificateRequired);
418 // If certificates have not been loaded yet, queue the connect request.
419 if (!certificates_loaded_) {
420 ConnectRequest* request = GetPendingRequest(service_path);
422 NET_LOG_ERROR("No pending request to queue", service_path);
425 NET_LOG_EVENT("Connect Request Queued", service_path);
426 queued_connect_.reset(new ConnectRequest(
427 service_path, request->profile_path,
428 request->success_callback, request->error_callback));
429 pending_requests_.erase(service_path);
433 pkcs11_id = CertificateIsConfigured(ui_data.get());
434 // Ensure the certificate is available and configured.
435 if (!cert_loader_->IsHardwareBacked() || pkcs11_id.empty()) {
436 ErrorCallbackForPendingRequest(service_path, kErrorCertificateRequired);
439 } else if (check_error_state &&
440 !client_cert::IsCertificateConfigured(client_cert_type,
441 service_properties)) {
442 // Network may not be configured.
443 ErrorCallbackForPendingRequest(service_path, kErrorConfigurationRequired);
447 // The network may not be 'Connectable' because the TPM properties are not
448 // set up, so configure tpm slot/pin before connecting.
449 if (cert_loader_ && cert_loader_->IsHardwareBacked()) {
450 // Pass NULL if pkcs11_id is empty, so that it doesn't clear any
451 // previously configured client cert.
452 client_cert::SetShillProperties(
454 base::IntToString(cert_loader_->tpm_token_slot_id()),
455 cert_loader_->tpm_user_pin(),
456 pkcs11_id.empty() ? NULL : &pkcs11_id,
461 if (type == shill::kTypeVPN) {
462 // VPN may require a username, and/or passphrase to be set. (Check after
463 // ensuring that any required certificates are configured).
464 DCHECK(provider_properties);
465 if (VPNRequiresCredentials(
466 service_path, vpn_provider_type, *provider_properties)) {
467 NET_LOG_USER("VPN Requires Credentials", service_path);
468 ErrorCallbackForPendingRequest(service_path, kErrorConfigurationRequired);
473 if (!config_properties.empty()) {
474 NET_LOG_EVENT("Configuring Network", service_path);
475 network_configuration_handler_->SetProperties(
478 base::Bind(&NetworkConnectionHandler::CallShillConnect,
481 base::Bind(&NetworkConnectionHandler::HandleConfigurationFailure,
487 // Otherwise, we probably still need to configure the network since
488 // 'Connectable' is false. If |check_error_state| is true, signal an
489 // error, otherwise attempt to connect to possibly gain additional error
490 // state from Shill (or in case 'Connectable' is improperly unset).
491 if (check_error_state)
492 ErrorCallbackForPendingRequest(service_path, kErrorConfigurationRequired);
494 CallShillConnect(service_path);
497 void NetworkConnectionHandler::CallShillConnect(
498 const std::string& service_path) {
499 NET_LOG_EVENT("Sending Connect Request to Shill", service_path);
500 DBusThreadManager::Get()->GetShillServiceClient()->Connect(
501 dbus::ObjectPath(service_path),
502 base::Bind(&NetworkConnectionHandler::HandleShillConnectSuccess,
503 AsWeakPtr(), service_path),
504 base::Bind(&NetworkConnectionHandler::HandleShillConnectFailure,
505 AsWeakPtr(), service_path));
508 void NetworkConnectionHandler::HandleConfigurationFailure(
509 const std::string& service_path,
510 const std::string& error_name,
511 scoped_ptr<base::DictionaryValue> error_data) {
512 ConnectRequest* request = GetPendingRequest(service_path);
514 NET_LOG_ERROR("HandleConfigurationFailure called with no pending request.",
518 network_handler::ErrorCallback error_callback = request->error_callback;
519 pending_requests_.erase(service_path);
520 if (!error_callback.is_null())
521 error_callback.Run(kErrorConfigureFailed, error_data.Pass());
524 void NetworkConnectionHandler::HandleShillConnectSuccess(
525 const std::string& service_path) {
526 ConnectRequest* request = GetPendingRequest(service_path);
528 NET_LOG_ERROR("HandleShillConnectSuccess called with no pending request.",
532 request->connect_state = ConnectRequest::CONNECT_STARTED;
533 NET_LOG_EVENT("Connect Request Acknowledged", service_path);
534 // Do not call success_callback here, wait for one of the following
536 // * State transitions to a non connecting state indicating succes or failure
537 // * Network is no longer in the visible list, indicating failure
538 CheckPendingRequest(service_path);
541 void NetworkConnectionHandler::HandleShillConnectFailure(
542 const std::string& service_path,
543 const std::string& dbus_error_name,
544 const std::string& dbus_error_message) {
545 ConnectRequest* request = GetPendingRequest(service_path);
547 NET_LOG_ERROR("HandleShillConnectFailure called with no pending request.",
551 network_handler::ErrorCallback error_callback = request->error_callback;
552 pending_requests_.erase(service_path);
553 network_handler::ShillErrorCallbackFunction(
554 shill::kErrorConnectFailed, service_path, error_callback,
555 dbus_error_name, dbus_error_message);
558 void NetworkConnectionHandler::CheckPendingRequest(
559 const std::string service_path) {
560 ConnectRequest* request = GetPendingRequest(service_path);
562 if (request->connect_state == ConnectRequest::CONNECT_REQUESTED)
563 return; // Request has not started, ignore update
564 const NetworkState* network =
565 network_state_handler_->GetNetworkState(service_path);
567 return; // NetworkState may not be be updated yet.
569 if (network->IsConnectingState()) {
570 request->connect_state = ConnectRequest::CONNECT_CONNECTING;
573 if (network->IsConnectedState()) {
574 NET_LOG_EVENT("Connect Request Succeeded", service_path);
575 if (!request->profile_path.empty()) {
576 // If a profile path was specified, set it on a successful connection.
577 network_configuration_handler_->SetNetworkProfile(
578 service_path, request->profile_path,
579 base::Bind(&base::DoNothing),
580 chromeos::network_handler::ErrorCallback());
582 if (!request->success_callback.is_null())
583 request->success_callback.Run();
584 pending_requests_.erase(service_path);
587 if (network->connection_state() == shill::kStateIdle &&
588 request->connect_state != ConnectRequest::CONNECT_CONNECTING) {
589 // Connection hasn't started yet, keep waiting.
593 // Network is neither connecting or connected; an error occurred.
594 std::string error_name; // 'Canceled' or 'Failed'
595 // If network->error() is empty here, we will look it up later, but we
596 // need to preserve it in case Shill clears it before then. crbug.com/302020.
597 std::string shill_error = network->error();
598 if (network->connection_state() == shill::kStateIdle &&
599 pending_requests_.size() > 1) {
600 // Another connect request canceled this one.
601 error_name = kErrorConnectCanceled;
603 error_name = shill::kErrorConnectFailed;
604 if (network->connection_state() != shill::kStateFailure) {
605 NET_LOG_ERROR("Unexpected State: " + network->connection_state(),
609 std::string error_msg = error_name;
610 if (!shill_error.empty())
611 error_msg += ": " + shill_error;
612 NET_LOG_ERROR(error_msg, service_path);
614 network_handler::ErrorCallback error_callback = request->error_callback;
615 pending_requests_.erase(service_path);
616 if (error_callback.is_null())
618 network_handler::RunErrorCallback(
619 error_callback, service_path, error_name, shill_error);
622 void NetworkConnectionHandler::CheckAllPendingRequests() {
623 for (std::map<std::string, ConnectRequest>::iterator iter =
624 pending_requests_.begin(); iter != pending_requests_.end(); ++iter) {
625 CheckPendingRequest(iter->first);
629 std::string NetworkConnectionHandler::CertificateIsConfigured(
630 NetworkUIData* ui_data) {
631 if (ui_data->certificate_pattern().Empty())
632 return std::string();
633 // Find the matching certificate.
634 scoped_refptr<net::X509Certificate> matching_cert =
635 client_cert::GetCertificateMatch(ui_data->certificate_pattern());
636 if (!matching_cert.get())
637 return std::string();
638 return CertLoader::GetPkcs11IdForCert(*matching_cert.get());
641 void NetworkConnectionHandler::ErrorCallbackForPendingRequest(
642 const std::string& service_path,
643 const std::string& error_name) {
644 ConnectRequest* request = GetPendingRequest(service_path);
646 NET_LOG_ERROR("ErrorCallbackForPendingRequest with no pending request.",
650 // Remove the entry before invoking the callback in case it triggers a retry.
651 network_handler::ErrorCallback error_callback = request->error_callback;
652 pending_requests_.erase(service_path);
653 InvokeErrorCallback(service_path, error_callback, error_name);
658 void NetworkConnectionHandler::CallShillDisconnect(
659 const std::string& service_path,
660 const base::Closure& success_callback,
661 const network_handler::ErrorCallback& error_callback) {
662 NET_LOG_USER("Disconnect Request", service_path);
663 DBusThreadManager::Get()->GetShillServiceClient()->Disconnect(
664 dbus::ObjectPath(service_path),
665 base::Bind(&NetworkConnectionHandler::HandleShillDisconnectSuccess,
666 AsWeakPtr(), service_path, success_callback),
667 base::Bind(&network_handler::ShillErrorCallbackFunction,
668 kErrorShillError, service_path, error_callback));
671 void NetworkConnectionHandler::HandleShillDisconnectSuccess(
672 const std::string& service_path,
673 const base::Closure& success_callback) {
674 NET_LOG_EVENT("Disconnect Request Sent", service_path);
675 if (!success_callback.is_null())
676 success_callback.Run();
679 } // namespace chromeos