| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/network/network_connection_handler_impl.h" |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/json/json_reader.h" |
| #include "base/location.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "chromeos/dbus/shill/shill_manager_client.h" |
| #include "chromeos/dbus/shill/shill_service_client.h" |
| #include "chromeos/network/client_cert_resolver.h" |
| #include "chromeos/network/client_cert_util.h" |
| #include "chromeos/network/device_state.h" |
| #include "chromeos/network/managed_network_configuration_handler.h" |
| #include "chromeos/network/network_configuration_handler.h" |
| #include "chromeos/network/network_event_log.h" |
| #include "chromeos/network/network_handler.h" |
| #include "chromeos/network/network_profile_handler.h" |
| #include "chromeos/network/network_state.h" |
| #include "chromeos/network/network_state_handler.h" |
| #include "chromeos/network/network_type_pattern.h" |
| #include "chromeos/network/network_util.h" |
| #include "chromeos/network/prohibited_technologies_handler.h" |
| #include "chromeos/network/shill_property_util.h" |
| #include "dbus/object_path.h" |
| #include "net/cert/x509_certificate.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| bool IsAuthenticationError(const std::string& error) { |
| return (error == shill::kErrorBadWEPKey || |
| error == shill::kErrorPppAuthFailed || |
| error == shill::kErrorEapLocalTlsFailed || |
| error == shill::kErrorEapRemoteTlsFailed || |
| error == shill::kErrorEapAuthenticationFailed); |
| } |
| |
| std::string GetStringFromDictionary(const base::Value& dict, |
| const std::string& key) { |
| const std::string* s = dict.FindStringKey(key); |
| return s ? *s : std::string(); |
| } |
| |
| bool IsCertificateConfigured(const client_cert::ConfigType cert_config_type, |
| const base::Value& properties) { |
| // VPN certificate properties are read from the Provider dictionary. |
| const base::Value* provider_properties = |
| properties.FindDictKey(shill::kProviderProperty); |
| switch (cert_config_type) { |
| case client_cert::CONFIG_TYPE_NONE: |
| return true; |
| case client_cert::CONFIG_TYPE_OPENVPN: |
| // We don't know whether a pasphrase or certificates are required, so |
| // always return true here (otherwise we will never attempt to connect). |
| // TODO(stevenjb/cernekee): Fix this? |
| return true; |
| case client_cert::CONFIG_TYPE_IPSEC: { |
| if (!provider_properties) |
| return false; |
| |
| std::string client_cert_id = GetStringFromDictionary( |
| *provider_properties, shill::kL2tpIpsecClientCertIdProperty); |
| return !client_cert_id.empty(); |
| } |
| case client_cert::CONFIG_TYPE_EAP: { |
| std::string cert_id = |
| GetStringFromDictionary(properties, shill::kEapCertIdProperty); |
| std::string key_id = |
| GetStringFromDictionary(properties, shill::kEapKeyIdProperty); |
| std::string identity = |
| GetStringFromDictionary(properties, shill::kEapIdentityProperty); |
| return !cert_id.empty() && !key_id.empty() && !identity.empty(); |
| } |
| } |
| NOTREACHED(); |
| return false; |
| } |
| |
| std::string VPNCheckCredentials(const std::string& service_path, |
| const std::string& provider_type, |
| const base::Value& provider_properties) { |
| if (provider_type == shill::kProviderOpenVpn) { |
| bool passphrase_required = |
| provider_properties.FindBoolKey(shill::kPassphraseRequiredProperty) |
| .value_or(false); |
| if (passphrase_required) { |
| NET_LOG(ERROR) << "OpenVPN: Passphrase Required for: " |
| << NetworkPathId(service_path); |
| return NetworkConnectionHandler::kErrorPassphraseRequired; |
| } |
| NET_LOG(EVENT) << "OpenVPN Is Configured: " << NetworkPathId(service_path); |
| } else { |
| bool passphrase_required = |
| provider_properties.FindBoolKey(shill::kL2tpIpsecPskRequiredProperty) |
| .value_or(false); |
| if (passphrase_required) { |
| NET_LOG(ERROR) << "VPN: PSK Required for: " |
| << NetworkPathId(service_path); |
| return NetworkConnectionHandler::kErrorConfigurationRequired; |
| } |
| passphrase_required = |
| provider_properties.FindBoolKey(shill::kPassphraseRequiredProperty) |
| .value_or(false); |
| if (passphrase_required) { |
| NET_LOG(ERROR) << "VPN: Passphrase Required for: " |
| << NetworkPathId(service_path); |
| return NetworkConnectionHandler::kErrorPassphraseRequired; |
| } |
| NET_LOG(EVENT) << "VPN Is Configured: " << NetworkPathId(service_path); |
| } |
| return std::string(); |
| } |
| |
| std::string GetDefaultUserProfilePath(const NetworkState* network) { |
| if (!NetworkHandler::IsInitialized() || |
| (LoginState::IsInitialized() && |
| !LoginState::Get()->UserHasNetworkProfile()) || |
| (network && network->type() == shill::kTypeWifi && |
| !network->IsSecure())) { |
| return NetworkProfileHandler::GetSharedProfilePath(); |
| } |
| const NetworkProfile* profile = |
| NetworkHandler::Get()->network_profile_handler()->GetDefaultUserProfile(); |
| return profile ? profile->path |
| : NetworkProfileHandler::GetSharedProfilePath(); |
| } |
| |
| bool IsVpnProhibited() { |
| if (!NetworkHandler::IsInitialized()) |
| return false; |
| |
| std::vector<std::string> prohibited_technologies = |
| NetworkHandler::Get() |
| ->prohibited_technologies_handler() |
| ->GetCurrentlyProhibitedTechnologies(); |
| return base::Contains(prohibited_technologies, shill::kTypeVPN); |
| } |
| |
| } // namespace |
| |
| NetworkConnectionHandlerImpl::ConnectRequest::ConnectRequest( |
| ConnectCallbackMode mode, |
| const std::string& service_path, |
| const std::string& profile_path, |
| base::OnceClosure success_callback, |
| network_handler::ErrorCallback error) |
| : mode(mode), |
| service_path(service_path), |
| profile_path(profile_path), |
| connect_state(CONNECT_REQUESTED), |
| success_callback(std::move(success_callback)), |
| error_callback(std::move(error)) {} |
| |
| NetworkConnectionHandlerImpl::ConnectRequest::~ConnectRequest() = default; |
| |
| NetworkConnectionHandlerImpl::ConnectRequest::ConnectRequest(ConnectRequest&&) = |
| default; |
| |
| NetworkConnectionHandlerImpl::NetworkConnectionHandlerImpl() |
| : network_cert_loader_(nullptr), |
| network_state_handler_(nullptr), |
| configuration_handler_(nullptr), |
| logged_in_(false), |
| certificates_loaded_(false) {} |
| |
| NetworkConnectionHandlerImpl::~NetworkConnectionHandlerImpl() { |
| if (network_state_handler_) |
| network_state_handler_->RemoveObserver(this, FROM_HERE); |
| if (network_cert_loader_) |
| network_cert_loader_->RemoveObserver(this); |
| if (LoginState::IsInitialized()) |
| LoginState::Get()->RemoveObserver(this); |
| } |
| |
| void NetworkConnectionHandlerImpl::Init( |
| NetworkStateHandler* network_state_handler, |
| NetworkConfigurationHandler* network_configuration_handler, |
| ManagedNetworkConfigurationHandler* managed_network_configuration_handler) { |
| if (LoginState::IsInitialized()) |
| LoginState::Get()->AddObserver(this); |
| |
| if (NetworkCertLoader::IsInitialized()) { |
| network_cert_loader_ = NetworkCertLoader::Get(); |
| network_cert_loader_->AddObserver(this); |
| if (network_cert_loader_->initial_load_finished()) { |
| NET_LOG(EVENT) << "Certificates Loaded"; |
| certificates_loaded_ = true; |
| } |
| } else { |
| // TODO(tbarzic): Require a mock or stub |network_cert_loader| in tests. |
| NET_LOG(DEBUG) << "Certificate Loader not initialized"; |
| certificates_loaded_ = true; |
| } |
| |
| if (network_state_handler) { |
| network_state_handler_ = network_state_handler; |
| network_state_handler_->AddObserver(this, FROM_HERE); |
| } |
| configuration_handler_ = network_configuration_handler; |
| managed_configuration_handler_ = managed_network_configuration_handler; |
| |
| // After this point, the NetworkConnectionHandlerImpl is fully initialized |
| // (all handler references set, observers registered, ...). |
| |
| if (LoginState::IsInitialized()) |
| LoggedInStateChanged(); |
| } |
| |
| void NetworkConnectionHandlerImpl::LoggedInStateChanged() { |
| LoginState* login_state = LoginState::Get(); |
| if (logged_in_ || !login_state->IsUserLoggedIn()) |
| return; |
| |
| logged_in_ = true; |
| logged_in_time_ = base::TimeTicks::Now(); |
| } |
| |
| void NetworkConnectionHandlerImpl::OnCertificatesLoaded() { |
| certificates_loaded_ = true; |
| NET_LOG(EVENT) << "Certificates Loaded"; |
| if (queued_connect_) |
| ConnectToQueuedNetwork(); |
| } |
| |
| void NetworkConnectionHandlerImpl::ConnectToNetwork( |
| const std::string& service_path, |
| base::OnceClosure success_callback, |
| network_handler::ErrorCallback error_callback, |
| bool check_error_state, |
| ConnectCallbackMode mode) { |
| NET_LOG(USER) << "ConnectToNetworkRequested: " << NetworkPathId(service_path); |
| for (auto& observer : observers_) |
| observer.ConnectToNetworkRequested(service_path); |
| |
| // Clear any existing queued connect request. |
| if (queued_connect_) { |
| network_state_handler_->SetNetworkConnectRequested( |
| queued_connect_->service_path, false); |
| queued_connect_.reset(); |
| } |
| |
| if (HasConnectingNetwork(service_path)) { |
| NET_LOG(USER) << "Connect Request while pending: " |
| << NetworkPathId(service_path); |
| InvokeConnectErrorCallback(service_path, std::move(error_callback), |
| kErrorConnecting); |
| return; |
| } |
| |
| // Check cached network state for connected, connecting, or unactivated |
| // networks. These states will not be affected by a recent configuration. |
| // Note: NetworkState may not exist for a network that was recently |
| // configured, in which case these checks do not apply anyway. |
| const NetworkState* network = |
| network_state_handler_->GetNetworkState(service_path); |
| |
| if (network) { |
| // For existing networks, perform some immediate consistency checks. |
| const std::string connection_state = network->connection_state(); |
| if (NetworkState::StateIsConnected(connection_state)) { |
| NET_LOG(ERROR) << "Connect Request while connected: " |
| << NetworkId(network); |
| InvokeConnectErrorCallback(service_path, std::move(error_callback), |
| kErrorConnected); |
| return; |
| } |
| if (NetworkState::StateIsConnecting(connection_state)) { |
| InvokeConnectErrorCallback(service_path, std::move(error_callback), |
| kErrorConnecting); |
| return; |
| } |
| |
| if (check_error_state) { |
| const std::string& error = network->GetError(); |
| if (error == shill::kErrorBadPassphrase) { |
| InvokeConnectErrorCallback(service_path, std::move(error_callback), |
| kErrorBadPassphrase); |
| return; |
| } |
| if (IsAuthenticationError(error)) { |
| InvokeConnectErrorCallback(service_path, std::move(error_callback), |
| kErrorAuthenticationRequired); |
| return; |
| } |
| } |
| |
| if (NetworkTypePattern::Tether().MatchesType(network->type())) { |
| if (tether_delegate_) { |
| const std::string& tether_network_guid = network->guid(); |
| DCHECK(!tether_network_guid.empty()); |
| InitiateTetherNetworkConnection(tether_network_guid, |
| std::move(success_callback), |
| std::move(error_callback)); |
| } else { |
| InvokeConnectErrorCallback(service_path, std::move(error_callback), |
| kErrorTetherAttemptWithNoDelegate); |
| } |
| return; |
| } |
| |
| if (NetworkTypePattern::VPN().MatchesType(network->type()) && |
| IsVpnProhibited()) { |
| InvokeConnectErrorCallback(service_path, std::move(error_callback), |
| kErrorBlockedByPolicy); |
| return; |
| } |
| } |
| |
| // If the network does not have a profile path, specify the correct default |
| // profile here and set it once connected. Otherwise leave it empty to |
| // indicate that it does not need to be set. |
| std::string profile_path; |
| if (!network || network->profile_path().empty()) |
| profile_path = GetDefaultUserProfilePath(network); |
| |
| bool call_connect = false; |
| |
| // Connect immediately to 'connectable' networks. |
| // TODO(stevenjb): Shill needs to properly set Connectable for VPN. |
| if (network && network->connectable() && network->type() != shill::kTypeVPN) { |
| if (network->blocked_by_policy()) { |
| InvokeConnectErrorCallback(service_path, std::move(error_callback), |
| kErrorBlockedByPolicy); |
| return; |
| } |
| |
| call_connect = true; |
| } |
| |
| // All synchronous checks passed, add |service_path| to connecting list. |
| pending_requests_.emplace( |
| service_path, |
| ConnectRequest(mode, service_path, profile_path, |
| std::move(success_callback), std::move(error_callback))); |
| |
| // Indicate that a connect was requested. This will be updated by |
| // NetworkStateHandler when the connection state changes, or cleared if |
| // an error occurs before a connect is initialted. |
| network_state_handler_->SetNetworkConnectRequested(service_path, true); |
| |
| if (call_connect) { |
| CallShillConnect(service_path); |
| return; |
| } |
| |
| // Request additional properties to check. VerifyConfiguredAndConnect will |
| // use only these properties, not cached properties, to ensure that they |
| // are up to date after any recent configuration. |
| configuration_handler_->GetShillProperties( |
| service_path, |
| base::BindOnce(&NetworkConnectionHandlerImpl::VerifyConfiguredAndConnect, |
| AsWeakPtr(), check_error_state)); |
| } |
| |
| void NetworkConnectionHandlerImpl::DisconnectNetwork( |
| const std::string& service_path, |
| base::OnceClosure success_callback, |
| network_handler::ErrorCallback error_callback) { |
| NET_LOG(USER) << "DisconnectNetwork"; |
| for (auto& observer : observers_) |
| observer.DisconnectRequested(service_path); |
| |
| const NetworkState* network = |
| network_state_handler_->GetNetworkState(service_path); |
| if (!network) { |
| NET_LOG(ERROR) << "Disconnect Error: Not Found: " |
| << NetworkPathId(service_path); |
| network_handler::RunErrorCallback(std::move(error_callback), service_path, |
| kErrorNotFound, ""); |
| return; |
| } |
| const std::string connection_state = network->connection_state(); |
| if (!NetworkState::StateIsConnected(connection_state) && |
| !NetworkState::StateIsConnecting(connection_state)) { |
| NET_LOG(ERROR) << "Disconnect Error: Not Connected: " << NetworkId(network); |
| network_handler::RunErrorCallback(std::move(error_callback), service_path, |
| kErrorNotConnected, ""); |
| return; |
| } |
| if (NetworkTypePattern::Tether().MatchesType(network->type())) { |
| if (tether_delegate_) { |
| const std::string& tether_network_guid = network->guid(); |
| DCHECK(!tether_network_guid.empty()); |
| InitiateTetherNetworkDisconnection(tether_network_guid, |
| std::move(success_callback), |
| std::move(error_callback)); |
| } else { |
| InvokeConnectErrorCallback(service_path, std::move(error_callback), |
| kErrorTetherAttemptWithNoDelegate); |
| } |
| return; |
| } |
| ClearPendingRequest(service_path); |
| CallShillDisconnect(service_path, std::move(success_callback), |
| std::move(error_callback)); |
| } |
| |
| void NetworkConnectionHandlerImpl::NetworkListChanged() { |
| CheckAllPendingRequests(); |
| } |
| |
| void NetworkConnectionHandlerImpl::NetworkPropertiesUpdated( |
| const NetworkState* network) { |
| if (HasConnectingNetwork(network->path())) |
| CheckPendingRequest(network->path()); |
| } |
| |
| bool NetworkConnectionHandlerImpl::HasConnectingNetwork( |
| const std::string& service_path) { |
| return pending_requests_.count(service_path) != 0; |
| } |
| |
| NetworkConnectionHandlerImpl::ConnectRequest* |
| NetworkConnectionHandlerImpl::GetPendingRequest( |
| const std::string& service_path) { |
| std::map<std::string, ConnectRequest>::iterator iter = |
| pending_requests_.find(service_path); |
| return iter != pending_requests_.end() ? &(iter->second) : nullptr; |
| } |
| |
| // ConnectToNetwork implementation |
| |
| void NetworkConnectionHandlerImpl::VerifyConfiguredAndConnect( |
| bool check_error_state, |
| const std::string& service_path, |
| base::Optional<base::Value> properties) { |
| if (!properties) { |
| HandleConfigurationFailure(service_path, "GetShillProperties failed", |
| nullptr); |
| return; |
| } |
| NET_LOG(EVENT) << "VerifyConfiguredAndConnect: " |
| << NetworkPathId(service_path) |
| << " check_error_state: " << check_error_state; |
| |
| // If 'passphrase_required' is still true, then the 'Passphrase' property |
| // has not been set to a minimum length value. |
| bool passphrase_required = |
| properties->FindBoolKey(shill::kPassphraseRequiredProperty) |
| .value_or(false); |
| if (passphrase_required) { |
| ErrorCallbackForPendingRequest(service_path, kErrorPassphraseRequired); |
| return; |
| } |
| |
| const std::string* type = properties->FindStringKey(shill::kTypeProperty); |
| if (!type) { |
| HandleConfigurationFailure(service_path, "Properties with no type", |
| nullptr); |
| return; |
| } |
| bool connectable = |
| properties->FindBoolKey(shill::kConnectableProperty).value_or(false); |
| |
| // In case NetworkState was not available in ConnectToNetwork (e.g. it had |
| // been recently configured), we need to check Connectable again. |
| if (connectable && *type != shill::kTypeVPN) { |
| // TODO(stevenjb): Shill needs to properly set Connectable for VPN. |
| CallShillConnect(service_path); |
| return; |
| } |
| |
| // Get VPN provider type and host (required for configuration) and ensure |
| // that required VPN non-cert properties are set. |
| const base::Value* provider_properties = |
| properties->FindDictKey(shill::kProviderProperty); |
| std::string vpn_provider_type, vpn_provider_host, vpn_client_cert_id; |
| if (*type == shill::kTypeVPN) { |
| // VPN Provider values are read from the "Provider" dictionary, not the |
| // "Provider.Type", etc keys (which are used only to set the values). |
| if (provider_properties) { |
| vpn_provider_type = |
| GetStringFromDictionary(*provider_properties, shill::kTypeProperty); |
| vpn_provider_host = |
| GetStringFromDictionary(*provider_properties, shill::kHostProperty); |
| vpn_client_cert_id = GetStringFromDictionary( |
| *provider_properties, shill::kL2tpIpsecClientCertIdProperty); |
| } |
| if (vpn_provider_type.empty() || vpn_provider_host.empty()) { |
| NET_LOG(ERROR) << "VPN Provider missing for: " |
| << NetworkPathId(service_path); |
| ErrorCallbackForPendingRequest(service_path, kErrorConfigurationRequired); |
| return; |
| } |
| } |
| |
| const std::string* guid = properties->FindStringKey(shill::kGuidProperty); |
| const std::string* profile = |
| properties->FindStringKey(shill::kProfileProperty); |
| ::onc::ONCSource onc_source = ::onc::ONC_SOURCE_NONE; |
| const base::Value* policy = nullptr; |
| if (guid && profile) { |
| policy = managed_configuration_handler_->FindPolicyByGuidAndProfile( |
| *guid, *profile, &onc_source); |
| } |
| // Check if network is blocked by policy. |
| if (*type == shill::kTypeWifi && |
| onc_source != ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY && |
| onc_source != ::onc::ONCSource::ONC_SOURCE_USER_POLICY) { |
| const std::string* hex_ssid = |
| properties->FindStringKey(shill::kWifiHexSsid); |
| if (!hex_ssid) { |
| ErrorCallbackForPendingRequest(service_path, kErrorHexSsidRequired); |
| return; |
| } |
| if (network_state_handler_->OnlyManagedWifiNetworksAllowed() || |
| base::Contains(managed_configuration_handler_->GetBlockedHexSSIDs(), |
| *hex_ssid)) { |
| ErrorCallbackForPendingRequest(service_path, kErrorBlockedByPolicy); |
| return; |
| } |
| } |
| |
| client_cert::ClientCertConfig cert_config_from_policy; |
| if (policy) { |
| client_cert::OncToClientCertConfig(onc_source, |
| base::Value::AsDictionaryValue(*policy), |
| &cert_config_from_policy); |
| } |
| |
| client_cert::ConfigType client_cert_type = client_cert::CONFIG_TYPE_NONE; |
| if (*type == shill::kTypeVPN) { |
| if (vpn_provider_type == shill::kProviderOpenVpn) { |
| client_cert_type = client_cert::CONFIG_TYPE_OPENVPN; |
| } else { |
| // L2TP/IPSec only requires a certificate if one is specified in ONC |
| // or one was configured by the UI. Otherwise it is L2TP/IPSec with |
| // PSK and doesn't require a certificate. |
| // |
| // TODO(benchan): Modify shill to specify the authentication type via |
| // the kL2tpIpsecAuthenticationType property, so that Chrome doesn't need |
| // to deduce the authentication type based on the |
| // kL2tpIpsecClientCertIdProperty here (and also in VPNConfigView). |
| if (!vpn_client_cert_id.empty() || |
| cert_config_from_policy.client_cert_type != |
| ::onc::client_cert::kClientCertTypeNone) { |
| client_cert_type = client_cert::CONFIG_TYPE_IPSEC; |
| } |
| } |
| } else if (*type == shill::kTypeWifi) { |
| const std::string* security_class = |
| properties->FindStringKey(shill::kSecurityClassProperty); |
| if (security_class && *security_class == shill::kSecurity8021x) |
| client_cert_type = client_cert::CONFIG_TYPE_EAP; |
| } |
| |
| base::DictionaryValue config_properties; |
| if (client_cert_type != client_cert::CONFIG_TYPE_NONE) { |
| // Note: if we get here then a certificate *may* be required, so we want |
| // to ensure that certificates have loaded successfully before attempting |
| // to connect. |
| NET_LOG(DEBUG) << "Client cert type for: " << NetworkPathId(service_path) |
| << ": " << client_cert_type; |
| |
| // User must be logged in to connect to a network requiring a certificate. |
| if (!logged_in_ || !network_cert_loader_) { |
| NET_LOG(ERROR) << "User not logged in for: " |
| << NetworkPathId(service_path); |
| ErrorCallbackForPendingRequest(service_path, kErrorCertificateRequired); |
| return; |
| } |
| // If certificates have not been loaded yet, queue the connect request. |
| if (!certificates_loaded_) { |
| NET_LOG(EVENT) << "Certificates not loaded for: " |
| << NetworkPathId(service_path); |
| QueueConnectRequest(service_path); |
| return; |
| } |
| |
| // Check certificate properties from policy. |
| if (cert_config_from_policy.client_cert_type == |
| ::onc::client_cert::kPattern) { |
| if (!ClientCertResolver::ResolveClientCertificateSync( |
| client_cert_type, cert_config_from_policy, &config_properties)) { |
| NET_LOG(ERROR) << "Non matching certificate for: " |
| << NetworkPathId(service_path); |
| ErrorCallbackForPendingRequest(service_path, kErrorCertificateRequired); |
| return; |
| } |
| } else if (check_error_state && |
| !IsCertificateConfigured(client_cert_type, *properties)) { |
| // Network may not be configured. |
| NET_LOG(ERROR) << "Certificate not configured for: " |
| << NetworkPathId(service_path); |
| ErrorCallbackForPendingRequest(service_path, kErrorConfigurationRequired); |
| return; |
| } |
| } |
| |
| if (*type == shill::kTypeVPN) { |
| // VPN may require a username, and/or passphrase to be set. (Check after |
| // ensuring that any required certificates are configured). |
| DCHECK(provider_properties); |
| std::string error = VPNCheckCredentials(service_path, vpn_provider_type, |
| *provider_properties); |
| if (!error.empty()) { |
| ErrorCallbackForPendingRequest(service_path, error); |
| return; |
| } |
| |
| // If it's L2TP/IPsec PSK, there is no properties to configure, so proceed |
| // to connect. |
| if (client_cert_type == client_cert::CONFIG_TYPE_NONE) { |
| CallShillConnect(service_path); |
| return; |
| } |
| } |
| |
| if (!config_properties.empty()) { |
| NET_LOG(EVENT) << "Configuring Network: " << NetworkPathId(service_path); |
| configuration_handler_->SetShillProperties( |
| service_path, config_properties, |
| base::BindOnce(&NetworkConnectionHandlerImpl::CallShillConnect, |
| AsWeakPtr(), service_path), |
| base::BindOnce( |
| &NetworkConnectionHandlerImpl::HandleConfigurationFailure, |
| AsWeakPtr(), service_path)); |
| return; |
| } |
| |
| if (*type != shill::kTypeVPN && check_error_state) { |
| // For non VPNs, 'Connectable' must be false here, so fail immediately if |
| // |check_error_state| is true. (For VPNs 'Connectable' is not reliable). |
| NET_LOG(ERROR) << "Non VPN is unconfigured: " |
| << NetworkPathId(service_path); |
| ErrorCallbackForPendingRequest(service_path, kErrorConfigurationRequired); |
| return; |
| } |
| |
| // Otherwise attempt to connect to possibly gain additional error state from |
| // Shill (or in case 'Connectable' is improperly set to false). |
| CallShillConnect(service_path); |
| } |
| |
| void NetworkConnectionHandlerImpl::QueueConnectRequest( |
| const std::string& service_path) { |
| ConnectRequest* request = GetPendingRequest(service_path); |
| if (!request) { |
| NET_LOG(ERROR) << "No pending request to queue: " |
| << NetworkPathId(service_path); |
| return; |
| } |
| |
| const int kMaxCertLoadTimeSeconds = 15; |
| base::TimeDelta dtime = base::TimeTicks::Now() - logged_in_time_; |
| if (dtime > base::TimeDelta::FromSeconds(kMaxCertLoadTimeSeconds)) { |
| NET_LOG(ERROR) << "Certificate load timeout: " |
| << NetworkPathId(service_path); |
| InvokeConnectErrorCallback(service_path, std::move(request->error_callback), |
| kErrorCertLoadTimeout); |
| return; |
| } |
| |
| NET_LOG(EVENT) << "Connect Request Queued: " << NetworkPathId(service_path); |
| queued_connect_.reset(new ConnectRequest(request->mode, service_path, |
| request->profile_path, |
| std::move(request->success_callback), |
| std::move(request->error_callback))); |
| pending_requests_.erase(service_path); |
| |
| // Post a delayed task to check to see if certificates have loaded. If they |
| // haven't, and queued_connect_ has not been cleared (e.g. by a successful |
| // connect request), cancel the request and notify the user. |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&NetworkConnectionHandlerImpl::CheckCertificatesLoaded, |
| AsWeakPtr()), |
| base::TimeDelta::FromSeconds(kMaxCertLoadTimeSeconds) - dtime); |
| } |
| |
| // Called after a delay to check whether certificates loaded. If they did not |
| // and we still have a queued network connect request, show an error and clear |
| // the request. |
| void NetworkConnectionHandlerImpl::CheckCertificatesLoaded() { |
| // Certificates loaded successfully, nothing more to do here. |
| if (certificates_loaded_) |
| return; |
| |
| // If queued_connect_ has been cleared (e.g. another connect request occurred |
| // and wasn't queued), do nothing here. |
| if (!queued_connect_) |
| return; |
| |
| // Notify the user that the connect failed, clear the queued network, and |
| // clear the connect_requested flag for the NetworkState. |
| NET_LOG(ERROR) << "Certificate load timeout: " |
| << NetworkPathId(queued_connect_->service_path); |
| InvokeConnectErrorCallback(queued_connect_->service_path, |
| std::move(queued_connect_->error_callback), |
| kErrorCertLoadTimeout); |
| queued_connect_.reset(); |
| network_state_handler_->SetNetworkConnectRequested( |
| queued_connect_->service_path, false); |
| } |
| |
| void NetworkConnectionHandlerImpl::ConnectToQueuedNetwork() { |
| DCHECK(queued_connect_); |
| |
| // Make a copy of |queued_connect_| parameters, because |queued_connect_| |
| // will get reset at the beginning of |ConnectToNetwork|. |
| std::string service_path = queued_connect_->service_path; |
| base::OnceClosure success_callback = |
| std::move(queued_connect_->success_callback); |
| network_handler::ErrorCallback error_callback = |
| std::move(queued_connect_->error_callback); |
| |
| NET_LOG(EVENT) << "Connecting to Queued Network: " |
| << NetworkPathId(service_path); |
| ConnectToNetwork(service_path, std::move(success_callback), |
| std::move(error_callback), false /* check_error_state */, |
| queued_connect_->mode); |
| } |
| |
| void NetworkConnectionHandlerImpl::CallShillConnect( |
| const std::string& service_path) { |
| NET_LOG(EVENT) << "Sending Connect Request to Shill: " |
| << NetworkPathId(service_path); |
| network_state_handler_->ClearLastErrorForNetwork(service_path); |
| ShillServiceClient::Get()->Connect( |
| dbus::ObjectPath(service_path), |
| base::BindOnce(&NetworkConnectionHandlerImpl::HandleShillConnectSuccess, |
| AsWeakPtr(), service_path), |
| base::BindOnce(&NetworkConnectionHandlerImpl::HandleShillConnectFailure, |
| AsWeakPtr(), service_path)); |
| } |
| |
| void NetworkConnectionHandlerImpl::HandleConfigurationFailure( |
| const std::string& service_path, |
| const std::string& error_name, |
| std::unique_ptr<base::DictionaryValue> error_data) { |
| NET_LOG(ERROR) << "Connect configuration failure: " << error_name |
| << " for: " << NetworkPathId(service_path); |
| ConnectRequest* request = GetPendingRequest(service_path); |
| if (!request) { |
| NET_LOG(ERROR) |
| << "HandleConfigurationFailure called with no pending request: " |
| << NetworkPathId(service_path); |
| return; |
| } |
| network_handler::ErrorCallback error_callback = |
| std::move(request->error_callback); |
| ClearPendingRequest(service_path); |
| InvokeConnectErrorCallback(service_path, std::move(error_callback), |
| kErrorConfigureFailed); |
| } |
| |
| void NetworkConnectionHandlerImpl::HandleShillConnectSuccess( |
| const std::string& service_path) { |
| ConnectRequest* request = GetPendingRequest(service_path); |
| if (!request) { |
| NET_LOG(ERROR) |
| << "HandleShillConnectSuccess called with no pending request: " |
| << NetworkPathId(service_path); |
| return; |
| } |
| if (request->mode == ConnectCallbackMode::ON_STARTED) { |
| if (!request->success_callback.is_null()) |
| std::move(request->success_callback).Run(); |
| // Request started; do not invoke success or error callbacks on |
| // completion. |
| request->success_callback.Reset(); |
| request->error_callback = network_handler::ErrorCallback(); |
| } |
| request->connect_state = ConnectRequest::CONNECT_STARTED; |
| NET_LOG(EVENT) << "Connect Request Acknowledged: " |
| << NetworkPathId(service_path); |
| // Do not call success_callback here, wait for one of the following |
| // conditions: |
| // * State transitions to a non connecting state indicating success or failure |
| // * Network is no longer in the visible list, indicating failure |
| CheckPendingRequest(service_path); |
| } |
| |
| void NetworkConnectionHandlerImpl::HandleShillConnectFailure( |
| const std::string& service_path, |
| const std::string& dbus_error_name, |
| const std::string& dbus_error_message) { |
| ConnectRequest* request = GetPendingRequest(service_path); |
| if (!request) { |
| NET_LOG(ERROR) |
| << "HandleShillConnectFailure called with no pending request: " |
| << NetworkPathId(service_path); |
| return; |
| } |
| network_handler::ErrorCallback error_callback = |
| std::move(request->error_callback); |
| ClearPendingRequest(service_path); |
| |
| std::string error; |
| if (dbus_error_name == shill::kErrorResultAlreadyConnected) { |
| error = kErrorConnected; |
| } else if (dbus_error_name == shill::kErrorResultInProgress) { |
| error = kErrorConnecting; |
| } else { |
| error = kErrorConnectFailed; |
| } |
| NET_LOG(ERROR) << "Connect Failure: " << NetworkPathId(service_path) |
| << " Error: " << error << " Shill error: " << dbus_error_name; |
| InvokeConnectErrorCallback(service_path, std::move(error_callback), error); |
| } |
| |
| void NetworkConnectionHandlerImpl::CheckPendingRequest( |
| const std::string service_path) { |
| ConnectRequest* request = GetPendingRequest(service_path); |
| DCHECK(request); |
| if (request->connect_state == ConnectRequest::CONNECT_REQUESTED) |
| return; // Request has not started, ignore update |
| const NetworkState* network = |
| network_state_handler_->GetNetworkState(service_path); |
| if (!network) |
| return; // NetworkState may not be be updated yet. |
| |
| const std::string connection_state = network->connection_state(); |
| if (NetworkState::StateIsConnecting(connection_state)) { |
| request->connect_state = ConnectRequest::CONNECT_CONNECTING; |
| return; |
| } |
| if (NetworkState::StateIsConnected(connection_state)) { |
| if (!request->profile_path.empty()) { |
| // If a profile path was specified, set it on a successful connection. |
| configuration_handler_->SetNetworkProfile( |
| service_path, request->profile_path, base::DoNothing(), |
| chromeos::network_handler::ErrorCallback()); |
| } |
| InvokeConnectSuccessCallback(request->service_path, |
| std::move(request->success_callback)); |
| ClearPendingRequest(service_path); |
| return; |
| } |
| if (connection_state == shill::kStateIdle && |
| request->connect_state != ConnectRequest::CONNECT_CONNECTING) { |
| // Connection hasn't started yet, keep waiting. |
| return; |
| } |
| |
| // Network is neither connecting or connected; an error occurred. |
| std::string error_name; // 'Canceled' or 'Failed' |
| if (connection_state == shill::kStateIdle && pending_requests_.size() > 1) { |
| // Another connect request canceled this one. |
| error_name = kErrorConnectCanceled; |
| } else { |
| error_name = kErrorConnectFailed; |
| if (connection_state != shill::kStateFailure) |
| NET_LOG(ERROR) << "Unexpected State: " << connection_state |
| << " for: " << NetworkPathId(service_path); |
| } |
| |
| network_handler::ErrorCallback error_callback = |
| std::move(request->error_callback); |
| ClearPendingRequest(service_path); |
| InvokeConnectErrorCallback(service_path, std::move(error_callback), |
| error_name); |
| } |
| |
| void NetworkConnectionHandlerImpl::CheckAllPendingRequests() { |
| for (std::map<std::string, ConnectRequest>::iterator iter = |
| pending_requests_.begin(); |
| iter != pending_requests_.end(); ++iter) { |
| CheckPendingRequest(iter->first); |
| } |
| } |
| |
| void NetworkConnectionHandlerImpl::ClearPendingRequest( |
| const std::string& service_path) { |
| pending_requests_.erase(service_path); |
| network_state_handler_->SetNetworkConnectRequested(service_path, false); |
| } |
| |
| // Connect callbacks |
| |
| void NetworkConnectionHandlerImpl::ErrorCallbackForPendingRequest( |
| const std::string& service_path, |
| const std::string& error_name) { |
| ConnectRequest* request = GetPendingRequest(service_path); |
| if (!request) { |
| NET_LOG(ERROR) << "ErrorCallbackForPendingRequest with no pending request: " |
| << NetworkPathId(service_path); |
| return; |
| } |
| // Remove the entry before invoking the callback in case it triggers a retry. |
| network_handler::ErrorCallback error_callback = |
| std::move(request->error_callback); |
| ClearPendingRequest(service_path); |
| |
| InvokeConnectErrorCallback(service_path, std::move(error_callback), |
| error_name); |
| } |
| |
| // Disconnect |
| |
| void NetworkConnectionHandlerImpl::CallShillDisconnect( |
| const std::string& service_path, |
| base::OnceClosure success_callback, |
| network_handler::ErrorCallback error_callback) { |
| NET_LOG(USER) << "Disconnect Request: " << NetworkPathId(service_path); |
| ShillServiceClient::Get()->Disconnect( |
| dbus::ObjectPath(service_path), |
| base::BindOnce( |
| &NetworkConnectionHandlerImpl::HandleShillDisconnectSuccess, |
| AsWeakPtr(), service_path, std::move(success_callback)), |
| base::BindOnce(&network_handler::ShillErrorCallbackFunction, |
| kErrorDisconnectFailed, service_path, |
| std::move(error_callback))); |
| } |
| |
| void NetworkConnectionHandlerImpl::HandleShillDisconnectSuccess( |
| const std::string& service_path, |
| base::OnceClosure success_callback) { |
| NET_LOG(EVENT) << "Disconnect Request Sent for: " |
| << NetworkPathId(service_path); |
| if (!success_callback.is_null()) |
| std::move(success_callback).Run(); |
| } |
| |
| } // namespace chromeos |