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