| // Copyright 2015 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 "components/arc/net/arc_net_host_impl.h" |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/singleton.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "chromeos/login/login_state/login_state.h" |
| #include "chromeos/network/device_state.h" |
| #include "chromeos/network/managed_network_configuration_handler.h" |
| #include "chromeos/network/network_connection_handler.h" |
| #include "chromeos/network/network_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/onc/onc_utils.h" |
| #include "components/arc/arc_browser_context_keyed_service_factory_base.h" |
| #include "components/arc/arc_features.h" |
| #include "components/arc/arc_prefs.h" |
| #include "components/arc/session/arc_bridge_service.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/user_manager/user_manager.h" |
| #include "third_party/cros_system_api/dbus/shill/dbus-constants.h" |
| |
| namespace { |
| |
| constexpr int kGetNetworksListLimit = 100; |
| |
| chromeos::NetworkStateHandler* GetStateHandler() { |
| return chromeos::NetworkHandler::Get()->network_state_handler(); |
| } |
| |
| chromeos::ManagedNetworkConfigurationHandler* GetManagedConfigurationHandler() { |
| return chromeos::NetworkHandler::Get() |
| ->managed_network_configuration_handler(); |
| } |
| |
| chromeos::NetworkConnectionHandler* GetNetworkConnectionHandler() { |
| return chromeos::NetworkHandler::Get()->network_connection_handler(); |
| } |
| |
| bool IsDeviceOwner() { |
| // Check whether the logged-in Chrome OS user is allowed to add or remove WiFi |
| // networks. The user account state changes immediately after boot. There is a |
| // small window when this may return an incorrect state. However, after things |
| // settle down this is guranteed to reflect the correct user account state. |
| return user_manager::UserManager::Get()->GetActiveUser()->GetAccountId() == |
| user_manager::UserManager::Get()->GetOwnerAccountId(); |
| } |
| |
| std::string GetStringFromONCDictionary(const base::Value* dict, |
| const char* key, |
| bool required) { |
| DCHECK(dict->is_dict()); |
| const base::Value* string_value = |
| dict->FindKeyOfType(key, base::Value::Type::STRING); |
| if (!string_value) { |
| LOG_IF(ERROR, required) << "Required property " << key << " not found."; |
| return std::string(); |
| } |
| std::string result = string_value->GetString(); |
| LOG_IF(ERROR, required && result.empty()) |
| << "Required property " << key << " is empty."; |
| return result; |
| } |
| |
| arc::mojom::SecurityType TranslateONCWifiSecurityType( |
| const base::DictionaryValue* dict) { |
| std::string type = GetStringFromONCDictionary(dict, onc::wifi::kSecurity, |
| true /* required */); |
| if (type == onc::wifi::kWEP_PSK) |
| return arc::mojom::SecurityType::WEP_PSK; |
| if (type == onc::wifi::kWEP_8021X) |
| return arc::mojom::SecurityType::WEP_8021X; |
| if (type == onc::wifi::kWPA_PSK) |
| return arc::mojom::SecurityType::WPA_PSK; |
| if (type == onc::wifi::kWPA_EAP) |
| return arc::mojom::SecurityType::WPA_EAP; |
| return arc::mojom::SecurityType::NONE; |
| } |
| |
| arc::mojom::TetheringClientState TranslateTetheringState( |
| const std::string& tethering_state) { |
| if (tethering_state == onc::tethering_state::kTetheringConfirmedState) |
| return arc::mojom::TetheringClientState::CONFIRMED; |
| else if (tethering_state == onc::tethering_state::kTetheringNotDetectedState) |
| return arc::mojom::TetheringClientState::NOT_DETECTED; |
| else if (tethering_state == onc::tethering_state::kTetheringSuspectedState) |
| return arc::mojom::TetheringClientState::SUSPECTED; |
| NOTREACHED() << "Invalid tethering state: " << tethering_state; |
| return arc::mojom::TetheringClientState::NOT_DETECTED; |
| } |
| |
| arc::mojom::WiFiPtr TranslateONCWifi(const base::DictionaryValue* dict) { |
| arc::mojom::WiFiPtr wifi = arc::mojom::WiFi::New(); |
| |
| // Optional; defaults to 0. |
| dict->GetInteger(onc::wifi::kFrequency, &wifi->frequency); |
| |
| wifi->bssid = |
| GetStringFromONCDictionary(dict, onc::wifi::kBSSID, false /* required */); |
| wifi->hex_ssid = GetStringFromONCDictionary(dict, onc::wifi::kHexSSID, |
| true /* required */); |
| |
| // Optional; defaults to false. |
| dict->GetBoolean(onc::wifi::kHiddenSSID, &wifi->hidden_ssid); |
| |
| wifi->security = TranslateONCWifiSecurityType(dict); |
| |
| // Optional; defaults to 0. |
| dict->GetInteger(onc::wifi::kSignalStrength, &wifi->signal_strength); |
| |
| return wifi; |
| } |
| |
| // Extracts WiFi's tethering client state from a dictionary of WiFi properties. |
| arc::mojom::TetheringClientState GetWifiTetheringClientState( |
| const base::DictionaryValue* dict) { |
| std::string tethering_state; |
| dict->GetString(onc::wifi::kTetheringState, &tethering_state); |
| return TranslateTetheringState(tethering_state); |
| } |
| |
| arc::mojom::IPConfigurationPtr TranslateONCIPConfig( |
| const base::Value* ip_dict) { |
| DCHECK(ip_dict->is_dict()); |
| |
| arc::mojom::IPConfigurationPtr configuration = |
| arc::mojom::IPConfiguration::New(); |
| |
| const base::Value* ip_address = ip_dict->FindKeyOfType( |
| onc::ipconfig::kIPAddress, base::Value::Type::STRING); |
| if (ip_address && !ip_address->GetString().empty()) { |
| configuration->ip_address = ip_address->GetString(); |
| const base::Value* routing_prefix = ip_dict->FindKeyOfType( |
| onc::ipconfig::kRoutingPrefix, base::Value::Type::INTEGER); |
| if (routing_prefix) |
| configuration->routing_prefix = routing_prefix->GetInt(); |
| else |
| LOG(ERROR) << "Required property RoutingPrefix not found."; |
| configuration->gateway = GetStringFromONCDictionary( |
| ip_dict, onc::ipconfig::kGateway, true /* required */); |
| } |
| |
| const base::Value* name_servers = ip_dict->FindKeyOfType( |
| onc::ipconfig::kNameServers, base::Value::Type::LIST); |
| if (name_servers) { |
| for (const auto& entry : name_servers->GetList()) |
| configuration->name_servers.push_back(entry.GetString()); |
| } |
| |
| const base::Value* type = |
| ip_dict->FindKeyOfType(onc::ipconfig::kType, base::Value::Type::STRING); |
| configuration->type = type && type->GetString() == onc::ipconfig::kIPv6 |
| ? arc::mojom::IPAddressType::IPV6 |
| : arc::mojom::IPAddressType::IPV4; |
| |
| configuration->web_proxy_auto_discovery_url = GetStringFromONCDictionary( |
| ip_dict, onc::ipconfig::kWebProxyAutoDiscoveryUrl, false /* required */); |
| |
| return configuration; |
| } |
| |
| // Returns an IPConfiguration vector from the IPConfigs ONC property, which may |
| // include multiple IP configurations (e.g. IPv4 and IPv6). |
| std::vector<arc::mojom::IPConfigurationPtr> IPConfigurationsFromONCIPConfigs( |
| const base::Value* dict) { |
| const base::Value* ip_config_list = |
| dict->FindKey(onc::network_config::kIPConfigs); |
| if (!ip_config_list || !ip_config_list->is_list()) |
| return {}; |
| std::vector<arc::mojom::IPConfigurationPtr> result; |
| for (const auto& entry : ip_config_list->GetList()) { |
| arc::mojom::IPConfigurationPtr config = TranslateONCIPConfig(&entry); |
| if (config) |
| result.push_back(std::move(config)); |
| } |
| return result; |
| } |
| |
| // Returns an IPConfiguration vector from ONC property |property|, which will |
| // include a single IP configuration. |
| std::vector<arc::mojom::IPConfigurationPtr> IPConfigurationsFromONCProperty( |
| const base::Value* dict, |
| const char* property_key) { |
| const base::Value* ip_dict = dict->FindKey(property_key); |
| if (!ip_dict) |
| return {}; |
| arc::mojom::IPConfigurationPtr config = TranslateONCIPConfig(ip_dict); |
| if (!config) |
| return {}; |
| std::vector<arc::mojom::IPConfigurationPtr> result; |
| result.push_back(std::move(config)); |
| return result; |
| } |
| |
| arc::mojom::ConnectionStateType TranslateONCConnectionState( |
| const base::DictionaryValue* dict) { |
| std::string connection_state = GetStringFromONCDictionary( |
| dict, onc::network_config::kConnectionState, false /* required */); |
| |
| if (connection_state == onc::connection_state::kConnected) |
| return arc::mojom::ConnectionStateType::CONNECTED; |
| if (connection_state == onc::connection_state::kConnecting) |
| return arc::mojom::ConnectionStateType::CONNECTING; |
| return arc::mojom::ConnectionStateType::NOT_CONNECTED; |
| } |
| |
| // Translates a shill connection state into a mojo ConnectionStateType. |
| // This is effectively the inverse function of shill.Service::GetStateString |
| // defined in platform2/shill/service.cc, with in addition some of shill's |
| // connection states translated to the same ConnectionStateType value. |
| arc::mojom::ConnectionStateType TranslateConnectionState( |
| const std::string& state) { |
| if (state == shill::kStateReady) |
| return arc::mojom::ConnectionStateType::CONNECTED; |
| if (state == shill::kStateAssociation || |
| state == shill::kStateConfiguration) |
| return arc::mojom::ConnectionStateType::CONNECTING; |
| if ((state == shill::kStateIdle) || (state == shill::kStateFailure) || |
| (state == "")) |
| return arc::mojom::ConnectionStateType::NOT_CONNECTED; |
| if (chromeos::NetworkState::StateIsPortalled(state)) |
| return arc::mojom::ConnectionStateType::PORTAL; |
| if (state == shill::kStateOnline) |
| return arc::mojom::ConnectionStateType::ONLINE; |
| |
| // The remaining cases defined in shill dbus-constants are legacy values from |
| // Flimflam and are not expected to be encountered. These are: kStateCarrier, |
| // kStateActivationFailure, kStateDisconnect, and kStateOffline. |
| NOTREACHED() << "Unknown connection state: " << state; |
| return arc::mojom::ConnectionStateType::NOT_CONNECTED; |
| } |
| |
| void TranslateONCNetworkTypeDetails(const base::DictionaryValue* dict, |
| arc::mojom::NetworkConfiguration* mojo) { |
| std::string type = GetStringFromONCDictionary( |
| dict, onc::network_config::kType, true /* required */); |
| // This property will be updated as required by the relevant network types |
| // below. |
| mojo->tethering_client_state = arc::mojom::TetheringClientState::NOT_DETECTED; |
| if (type == onc::network_type::kCellular) { |
| mojo->type = arc::mojom::NetworkType::CELLULAR; |
| } else if (type == onc::network_type::kEthernet) { |
| mojo->type = arc::mojom::NetworkType::ETHERNET; |
| } else if (type == onc::network_type::kVPN) { |
| mojo->type = arc::mojom::NetworkType::VPN; |
| } else if (type == onc::network_type::kWiFi) { |
| mojo->type = arc::mojom::NetworkType::WIFI; |
| const base::DictionaryValue* wifi_dict = nullptr; |
| dict->GetDictionary(onc::network_config::kWiFi, &wifi_dict); |
| DCHECK(wifi_dict); |
| mojo->wifi = TranslateONCWifi(wifi_dict); |
| mojo->tethering_client_state = GetWifiTetheringClientState(wifi_dict); |
| } else { |
| NOTREACHED() << "Unknown network type: " << type; |
| } |
| } |
| |
| arc::mojom::NetworkConfigurationPtr TranslateONCConfiguration( |
| const chromeos::NetworkState* network_state, |
| const base::DictionaryValue* dict) { |
| arc::mojom::NetworkConfigurationPtr mojo = |
| arc::mojom::NetworkConfiguration::New(); |
| |
| mojo->connection_state = TranslateONCConnectionState(dict); |
| |
| mojo->guid = GetStringFromONCDictionary(dict, onc::network_config::kGUID, |
| true /* required */); |
| |
| // crbug.com/761708 - VPNs do not currently have an IPConfigs array, |
| // so in order to fetch the parameters (particularly the DNS server list), |
| // fall back to StaticIPConfig or SavedIPConfig. |
| std::vector<arc::mojom::IPConfigurationPtr> ip_configs = |
| IPConfigurationsFromONCIPConfigs(dict); |
| if (ip_configs.empty()) { |
| ip_configs = IPConfigurationsFromONCProperty( |
| dict, onc::network_config::kStaticIPConfig); |
| } |
| if (ip_configs.empty()) { |
| ip_configs = IPConfigurationsFromONCProperty( |
| dict, onc::network_config::kSavedIPConfig); |
| } |
| if (!ip_configs.empty()) |
| mojo->ip_configs = std::move(ip_configs); |
| |
| mojo->guid = GetStringFromONCDictionary(dict, onc::network_config::kGUID, |
| true /* required */); |
| mojo->mac_address = GetStringFromONCDictionary( |
| dict, onc::network_config::kMacAddress, false /* required */); |
| TranslateONCNetworkTypeDetails(dict, mojo.get()); |
| |
| if (network_state) { |
| mojo->connection_state = |
| TranslateConnectionState(network_state->connection_state()); |
| const chromeos::DeviceState* device_state = |
| GetStateHandler()->GetDeviceState(network_state->device_path()); |
| if (device_state) |
| mojo->network_interface = device_state->interface(); |
| } |
| |
| return mojo; |
| } |
| |
| // Convenience helper for returning a mojo NetworkConfiguration from |
| // a NetworkState. |
| arc::mojom::NetworkConfigurationPtr TranslateNetworkState( |
| const chromeos::NetworkState* state) { |
| return TranslateONCConfiguration( |
| state, chromeos::network_util::TranslateNetworkStateToONC(state).get()); |
| } |
| |
| const chromeos::NetworkState* GetShillBackedNetwork( |
| const chromeos::NetworkState* network) { |
| if (!network) |
| return nullptr; |
| |
| // Non-Tether networks are already backed by Shill. |
| if (!chromeos::NetworkTypePattern::Tether().MatchesType(network->type())) |
| return network; |
| |
| // Tether networks which are not connected are also not backed by Shill. |
| if (!network->IsConnectedState()) |
| return nullptr; |
| |
| // Connected Tether networks delegate to an underlying Wi-Fi network. |
| DCHECK(!network->tether_guid().empty()); |
| return GetStateHandler()->GetNetworkStateFromGuid(network->tether_guid()); |
| } |
| |
| void ForgetNetworkSuccessCallback( |
| base::OnceCallback<void(arc::mojom::NetworkResult)> callback) { |
| std::move(callback).Run(arc::mojom::NetworkResult::SUCCESS); |
| } |
| |
| void ForgetNetworkFailureCallback( |
| base::OnceCallback<void(arc::mojom::NetworkResult)> callback, |
| const std::string& error_name, |
| std::unique_ptr<base::DictionaryValue> error_data) { |
| VLOG(1) << "ForgetNetworkFailureCallback: " << error_name; |
| std::move(callback).Run(arc::mojom::NetworkResult::FAILURE); |
| } |
| |
| void StartConnectSuccessCallback( |
| base::OnceCallback<void(arc::mojom::NetworkResult)> callback) { |
| std::move(callback).Run(arc::mojom::NetworkResult::SUCCESS); |
| } |
| |
| void StartConnectFailureCallback( |
| base::OnceCallback<void(arc::mojom::NetworkResult)> callback, |
| const std::string& error_name, |
| std::unique_ptr<base::DictionaryValue> error_data) { |
| VLOG(1) << "StartConnectFailureCallback: " << error_name; |
| std::move(callback).Run(arc::mojom::NetworkResult::FAILURE); |
| } |
| |
| void StartDisconnectSuccessCallback( |
| base::OnceCallback<void(arc::mojom::NetworkResult)> callback) { |
| std::move(callback).Run(arc::mojom::NetworkResult::SUCCESS); |
| } |
| |
| void StartDisconnectFailureCallback( |
| base::OnceCallback<void(arc::mojom::NetworkResult)> callback, |
| const std::string& error_name, |
| std::unique_ptr<base::DictionaryValue> error_data) { |
| VLOG(1) << "StartDisconnectFailureCallback: " << error_name; |
| std::move(callback).Run(arc::mojom::NetworkResult::FAILURE); |
| } |
| |
| void GetDefaultNetworkSuccessCallback( |
| base::OnceCallback<void(arc::mojom::NetworkConfigurationPtr, |
| arc::mojom::NetworkConfigurationPtr)> callback, |
| const std::string& service_path, |
| const base::DictionaryValue& dictionary) { |
| // TODO(cernekee): Figure out how to query Chrome for the default physical |
| // service if a VPN is connected, rather than just reporting the |
| // default logical service in both fields. |
| const chromeos::NetworkState* network_state = |
| GetStateHandler()->GetNetworkState(service_path); |
| std::move(callback).Run( |
| TranslateONCConfiguration(network_state, &dictionary), |
| TranslateONCConfiguration(network_state, &dictionary)); |
| } |
| |
| void GetDefaultNetworkFailureCallback( |
| base::OnceCallback<void(arc::mojom::NetworkConfigurationPtr, |
| arc::mojom::NetworkConfigurationPtr)> callback, |
| const std::string& error_name, |
| std::unique_ptr<base::DictionaryValue> error_data) { |
| LOG(ERROR) << "Failed to query default logical network: " << error_name; |
| std::move(callback).Run(nullptr, nullptr); |
| } |
| |
| void DefaultNetworkFailureCallback( |
| const std::string& error_name, |
| std::unique_ptr<base::DictionaryValue> error_data) { |
| LOG(ERROR) << "Failed to query default logical network: " << error_name; |
| } |
| |
| void ArcVpnSuccessCallback() { |
| DVLOG(1) << "ArcVpnSuccessCallback"; |
| } |
| |
| void ArcVpnErrorCallback(const std::string& error_name, |
| std::unique_ptr<base::DictionaryValue> error_data) { |
| LOG(ERROR) << "ArcVpnErrorCallback: " << error_name; |
| } |
| |
| } // namespace |
| |
| namespace arc { |
| namespace { |
| |
| // Singleton factory for ArcNetHostImpl. |
| class ArcNetHostImplFactory |
| : public internal::ArcBrowserContextKeyedServiceFactoryBase< |
| ArcNetHostImpl, |
| ArcNetHostImplFactory> { |
| public: |
| // Factory name used by ArcBrowserContextKeyedServiceFactoryBase. |
| static constexpr const char* kName = "ArcNetHostImplFactory"; |
| |
| static ArcNetHostImplFactory* GetInstance() { |
| return base::Singleton<ArcNetHostImplFactory>::get(); |
| } |
| |
| private: |
| friend base::DefaultSingletonTraits<ArcNetHostImplFactory>; |
| ArcNetHostImplFactory() = default; |
| ~ArcNetHostImplFactory() override = default; |
| }; |
| |
| } // namespace |
| |
| // static |
| ArcNetHostImpl* ArcNetHostImpl::GetForBrowserContext( |
| content::BrowserContext* context) { |
| return ArcNetHostImplFactory::GetForBrowserContext(context); |
| } |
| |
| // static |
| ArcNetHostImpl* ArcNetHostImpl::GetForBrowserContextForTesting( |
| content::BrowserContext* context) { |
| return ArcNetHostImplFactory::GetForBrowserContextForTesting(context); |
| } |
| |
| ArcNetHostImpl::ArcNetHostImpl(content::BrowserContext* context, |
| ArcBridgeService* bridge_service) |
| : arc_bridge_service_(bridge_service), weak_factory_(this) { |
| arc_bridge_service_->net()->SetHost(this); |
| arc_bridge_service_->net()->AddObserver(this); |
| } |
| |
| ArcNetHostImpl::~ArcNetHostImpl() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (observing_network_state_) { |
| GetStateHandler()->RemoveObserver(this, FROM_HERE); |
| GetNetworkConnectionHandler()->RemoveObserver(this); |
| } |
| arc_bridge_service_->net()->RemoveObserver(this); |
| arc_bridge_service_->net()->SetHost(nullptr); |
| } |
| |
| void ArcNetHostImpl::SetPrefService(PrefService* pref_service) { |
| pref_service_ = pref_service; |
| } |
| |
| void ArcNetHostImpl::OnConnectionReady() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (chromeos::NetworkHandler::IsInitialized()) { |
| GetStateHandler()->AddObserver(this, FROM_HERE); |
| GetNetworkConnectionHandler()->AddObserver(this); |
| observing_network_state_ = true; |
| } |
| |
| // If the default network is an ARC VPN, that means Chrome is restarting |
| // after a crash but shill still thinks a VPN is connected. Nuke it. |
| const chromeos::NetworkState* default_network = |
| GetShillBackedNetwork(GetStateHandler()->DefaultNetwork()); |
| if (default_network && default_network->type() == shill::kTypeVPN && |
| default_network->GetVpnProviderType() == shill::kProviderArcVpn) { |
| VLOG(0) << "Disconnecting stale ARC VPN " << default_network->path(); |
| GetNetworkConnectionHandler()->DisconnectNetwork( |
| default_network->path(), base::Bind(&ArcVpnSuccessCallback), |
| base::Bind(&ArcVpnErrorCallback)); |
| } |
| } |
| |
| void ArcNetHostImpl::OnConnectionClosed() { |
| // Make sure shill doesn't leave an ARC VPN connected after Android |
| // goes down. |
| AndroidVpnStateChanged(arc::mojom::ConnectionStateType::NOT_CONNECTED); |
| |
| if (!observing_network_state_) |
| return; |
| |
| GetStateHandler()->RemoveObserver(this, FROM_HERE); |
| GetNetworkConnectionHandler()->RemoveObserver(this); |
| observing_network_state_ = false; |
| } |
| |
| void ArcNetHostImpl::GetNetworks(mojom::GetNetworksRequestType type, |
| GetNetworksCallback callback) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| chromeos::NetworkStateHandler::NetworkStateList network_states; |
| if (type == mojom::GetNetworksRequestType::ACTIVE_ONLY) { |
| // Retrieve list of currently active networks. |
| GetStateHandler()->GetActiveNetworkListByType( |
| chromeos::NetworkTypePattern::Default(), &network_states); |
| } else { |
| // Otherwise retrieve list of configured or visible WiFi networks. |
| bool configured_only = |
| type == mojom::GetNetworksRequestType::CONFIGURED_ONLY; |
| chromeos::NetworkTypePattern network_pattern = |
| chromeos::onc::NetworkTypePatternFromOncType(onc::network_type::kWiFi); |
| GetStateHandler()->GetNetworkListByType( |
| network_pattern, configured_only, !configured_only /* visible_only */, |
| kGetNetworksListLimit, &network_states); |
| } |
| |
| std::vector<mojom::NetworkConfigurationPtr> networks; |
| for (const chromeos::NetworkState* state : network_states) |
| networks.push_back(TranslateNetworkState(state)); |
| std::move(callback).Run(mojom::GetNetworksResponseType::New( |
| arc::mojom::NetworkResult::SUCCESS, std::move(networks))); |
| } |
| |
| void ArcNetHostImpl::CreateNetworkSuccessCallback( |
| base::OnceCallback<void(const std::string&)> callback, |
| const std::string& service_path, |
| const std::string& guid) { |
| VLOG(1) << "CreateNetworkSuccessCallback"; |
| |
| cached_guid_ = guid; |
| cached_service_path_ = service_path; |
| |
| std::move(callback).Run(guid); |
| } |
| |
| void ArcNetHostImpl::CreateNetworkFailureCallback( |
| base::OnceCallback<void(const std::string&)> callback, |
| const std::string& error_name, |
| std::unique_ptr<base::DictionaryValue> error_data) { |
| VLOG(1) << "CreateNetworkFailureCallback: " << error_name; |
| std::move(callback).Run(std::string()); |
| } |
| |
| void ArcNetHostImpl::CreateNetwork(mojom::WifiConfigurationPtr cfg, |
| CreateNetworkCallback callback) { |
| if (!IsDeviceOwner()) { |
| std::move(callback).Run(std::string()); |
| return; |
| } |
| |
| std::unique_ptr<base::DictionaryValue> properties(new base::DictionaryValue); |
| std::unique_ptr<base::DictionaryValue> wifi_dict(new base::DictionaryValue); |
| |
| if (!cfg->hexssid.has_value() || !cfg->details) { |
| std::move(callback).Run(std::string()); |
| return; |
| } |
| mojom::ConfiguredNetworkDetailsPtr details = |
| std::move(cfg->details->get_configured()); |
| if (!details) { |
| std::move(callback).Run(std::string()); |
| return; |
| } |
| |
| properties->SetKey(onc::network_config::kType, |
| base::Value(onc::network_config::kWiFi)); |
| wifi_dict->SetKey(onc::wifi::kHexSSID, base::Value(cfg->hexssid.value())); |
| wifi_dict->SetKey(onc::wifi::kAutoConnect, base::Value(details->autoconnect)); |
| if (cfg->security.empty()) { |
| wifi_dict->SetKey(onc::wifi::kSecurity, |
| base::Value(onc::wifi::kSecurityNone)); |
| } else { |
| wifi_dict->SetKey(onc::wifi::kSecurity, base::Value(cfg->security)); |
| if (details->passphrase.has_value()) { |
| wifi_dict->SetKey(onc::wifi::kPassphrase, |
| base::Value(details->passphrase.value())); |
| } |
| } |
| properties->SetWithoutPathExpansion(onc::network_config::kWiFi, |
| std::move(wifi_dict)); |
| |
| std::string user_id_hash = chromeos::LoginState::Get()->primary_user_hash(); |
| // TODO(crbug.com/730593): Remove AdaptCallbackForRepeating() by updating |
| // the callee interface. |
| auto repeating_callback = |
| base::AdaptCallbackForRepeating(std::move(callback)); |
| GetManagedConfigurationHandler()->CreateConfiguration( |
| user_id_hash, *properties, |
| base::Bind(&ArcNetHostImpl::CreateNetworkSuccessCallback, |
| weak_factory_.GetWeakPtr(), repeating_callback), |
| base::Bind(&ArcNetHostImpl::CreateNetworkFailureCallback, |
| weak_factory_.GetWeakPtr(), repeating_callback)); |
| } |
| |
| bool ArcNetHostImpl::GetNetworkPathFromGuid(const std::string& guid, |
| std::string* path) { |
| const chromeos::NetworkState* network = |
| GetShillBackedNetwork(GetStateHandler()->GetNetworkStateFromGuid(guid)); |
| if (network) { |
| *path = network->path(); |
| return true; |
| } |
| |
| if (cached_guid_ == guid) { |
| *path = cached_service_path_; |
| return true; |
| } |
| return false; |
| } |
| |
| void ArcNetHostImpl::ForgetNetwork(const std::string& guid, |
| ForgetNetworkCallback callback) { |
| if (!IsDeviceOwner()) { |
| std::move(callback).Run(mojom::NetworkResult::FAILURE); |
| return; |
| } |
| |
| std::string path; |
| if (!GetNetworkPathFromGuid(guid, &path)) { |
| std::move(callback).Run(mojom::NetworkResult::FAILURE); |
| return; |
| } |
| |
| cached_guid_.clear(); |
| // TODO(crbug.com/730593): Remove AdaptCallbackForRepeating() by updating |
| // the callee interface. |
| auto repeating_callback = |
| base::AdaptCallbackForRepeating(std::move(callback)); |
| GetManagedConfigurationHandler()->RemoveConfiguration( |
| path, base::Bind(&ForgetNetworkSuccessCallback, repeating_callback), |
| base::Bind(&ForgetNetworkFailureCallback, repeating_callback)); |
| } |
| |
| void ArcNetHostImpl::StartConnect(const std::string& guid, |
| StartConnectCallback callback) { |
| std::string path; |
| if (!GetNetworkPathFromGuid(guid, &path)) { |
| std::move(callback).Run(mojom::NetworkResult::FAILURE); |
| return; |
| } |
| |
| // TODO(crbug.com/730593): Remove AdaptCallbackForRepeating() by updating |
| // the callee interface. |
| auto repeating_callback = |
| base::AdaptCallbackForRepeating(std::move(callback)); |
| GetNetworkConnectionHandler()->ConnectToNetwork( |
| path, base::Bind(&StartConnectSuccessCallback, repeating_callback), |
| base::Bind(&StartConnectFailureCallback, repeating_callback), |
| false /* check_error_state */, chromeos::ConnectCallbackMode::ON_STARTED); |
| } |
| |
| void ArcNetHostImpl::StartDisconnect(const std::string& guid, |
| StartDisconnectCallback callback) { |
| std::string path; |
| if (!GetNetworkPathFromGuid(guid, &path)) { |
| std::move(callback).Run(mojom::NetworkResult::FAILURE); |
| return; |
| } |
| |
| // TODO(crbug.com/730593): Remove AdaptCallbackForRepeating() by updating |
| // the callee interface. |
| auto repeating_callback = |
| base::AdaptCallbackForRepeating(std::move(callback)); |
| GetNetworkConnectionHandler()->DisconnectNetwork( |
| path, base::Bind(&StartDisconnectSuccessCallback, repeating_callback), |
| base::Bind(&StartDisconnectFailureCallback, repeating_callback)); |
| } |
| |
| void ArcNetHostImpl::GetWifiEnabledState(GetWifiEnabledStateCallback callback) { |
| bool is_enabled = GetStateHandler()->IsTechnologyEnabled( |
| chromeos::NetworkTypePattern::WiFi()); |
| std::move(callback).Run(is_enabled); |
| } |
| |
| void ArcNetHostImpl::SetWifiEnabledState(bool is_enabled, |
| SetWifiEnabledStateCallback callback) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| chromeos::NetworkStateHandler::TechnologyState state = |
| GetStateHandler()->GetTechnologyState( |
| chromeos::NetworkTypePattern::WiFi()); |
| // WiFi can't be enabled or disabled in these states. |
| if ((state == chromeos::NetworkStateHandler::TECHNOLOGY_PROHIBITED) || |
| (state == chromeos::NetworkStateHandler::TECHNOLOGY_UNINITIALIZED) || |
| (state == chromeos::NetworkStateHandler::TECHNOLOGY_UNAVAILABLE)) { |
| VLOG(1) << "SetWifiEnabledState failed due to WiFi state: " << state; |
| std::move(callback).Run(false); |
| return; |
| } |
| GetStateHandler()->SetTechnologyEnabled( |
| chromeos::NetworkTypePattern::WiFi(), is_enabled, |
| chromeos::network_handler::ErrorCallback()); |
| std::move(callback).Run(true); |
| } |
| |
| void ArcNetHostImpl::StartScan() { |
| GetStateHandler()->RequestScan(chromeos::NetworkTypePattern::WiFi()); |
| } |
| |
| void ArcNetHostImpl::ScanCompleted(const chromeos::DeviceState* /*unused*/) { |
| auto* net_instance = |
| ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->net(), ScanCompleted); |
| if (!net_instance) |
| return; |
| |
| net_instance->ScanCompleted(); |
| } |
| |
| const chromeos::NetworkState* ArcNetHostImpl::GetDefaultNetworkFromChrome() { |
| // If an Android VPN is connected, report the underlying physical |
| // connection only. Never tell Android about its own VPN. |
| // If a Chrome OS VPN is connected, report the Chrome OS VPN as the |
| // default connection. |
| if (arc_vpn_service_path_.empty()) { |
| return GetShillBackedNetwork(GetStateHandler()->DefaultNetwork()); |
| } |
| |
| return GetShillBackedNetwork(GetStateHandler()->ConnectedNetworkByType( |
| chromeos::NetworkTypePattern::NonVirtual())); |
| } |
| |
| void ArcNetHostImpl::GetDefaultNetwork(GetDefaultNetworkCallback callback) { |
| const chromeos::NetworkState* default_network = GetDefaultNetworkFromChrome(); |
| |
| if (!default_network) { |
| VLOG(1) << "GetDefaultNetwork: no default network"; |
| std::move(callback).Run(nullptr, nullptr); |
| return; |
| } |
| VLOG(1) << "GetDefaultNetwork: default network is " |
| << default_network->path(); |
| std::string user_id_hash = chromeos::LoginState::Get()->primary_user_hash(); |
| // TODO(crbug.com/730593): Remove AdaptCallbackForRepeating() by updating |
| // the callee interface. |
| auto repeating_callback = |
| base::AdaptCallbackForRepeating(std::move(callback)); |
| GetManagedConfigurationHandler()->GetProperties( |
| user_id_hash, default_network->path(), |
| base::Bind(&GetDefaultNetworkSuccessCallback, repeating_callback), |
| base::Bind(&GetDefaultNetworkFailureCallback, repeating_callback)); |
| } |
| |
| void ArcNetHostImpl::DefaultNetworkSuccessCallback( |
| const std::string& service_path, |
| const base::DictionaryValue& dictionary) { |
| auto* net_instance = ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->net(), |
| DefaultNetworkChanged); |
| if (!net_instance) |
| return; |
| |
| const chromeos::NetworkState* network_state = |
| GetStateHandler()->GetNetworkState(service_path); |
| net_instance->DefaultNetworkChanged( |
| TranslateONCConfiguration(network_state, &dictionary), |
| TranslateONCConfiguration(network_state, &dictionary)); |
| } |
| |
| void ArcNetHostImpl::UpdateDefaultNetwork() { |
| const chromeos::NetworkState* default_network = GetDefaultNetworkFromChrome(); |
| |
| if (!default_network) { |
| VLOG(1) << "No default network"; |
| auto* net_instance = ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->net(), |
| DefaultNetworkChanged); |
| if (net_instance) |
| net_instance->DefaultNetworkChanged(nullptr, nullptr); |
| return; |
| } |
| |
| VLOG(1) << "New default network: " << default_network->path() << " (" |
| << default_network->type() << ")"; |
| std::string user_id_hash = chromeos::LoginState::Get()->primary_user_hash(); |
| GetManagedConfigurationHandler()->GetProperties( |
| user_id_hash, default_network->path(), |
| base::Bind(&ArcNetHostImpl::DefaultNetworkSuccessCallback, |
| weak_factory_.GetWeakPtr()), |
| base::Bind(&DefaultNetworkFailureCallback)); |
| } |
| |
| void ArcNetHostImpl::DefaultNetworkChanged( |
| const chromeos::NetworkState* network) { |
| UpdateDefaultNetwork(); |
| } |
| |
| void ArcNetHostImpl::DeviceListChanged() { |
| auto* net_instance = ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->net(), |
| WifiEnabledStateChanged); |
| if (!net_instance) |
| return; |
| |
| bool is_enabled = GetStateHandler()->IsTechnologyEnabled( |
| chromeos::NetworkTypePattern::WiFi()); |
| net_instance->WifiEnabledStateChanged(is_enabled); |
| } |
| |
| std::string ArcNetHostImpl::LookupArcVpnServicePath() { |
| chromeos::NetworkStateHandler::NetworkStateList state_list; |
| GetStateHandler()->GetNetworkListByType( |
| chromeos::NetworkTypePattern::VPN(), true /* configured_only */, |
| false /* visible_only */, kGetNetworksListLimit, &state_list); |
| |
| for (const chromeos::NetworkState* state : state_list) { |
| const chromeos::NetworkState* shill_backed_network = |
| GetShillBackedNetwork(state); |
| if (!shill_backed_network) |
| continue; |
| if (shill_backed_network->GetVpnProviderType() == shill::kProviderArcVpn) { |
| return shill_backed_network->path(); |
| } |
| } |
| return std::string(); |
| } |
| |
| void ArcNetHostImpl::ConnectArcVpn(const std::string& service_path, |
| const std::string& /* guid */) { |
| DVLOG(1) << "ConnectArcVpn " << service_path; |
| arc_vpn_service_path_ = service_path; |
| |
| GetNetworkConnectionHandler()->ConnectToNetwork( |
| service_path, base::Bind(&ArcVpnSuccessCallback), |
| base::Bind(&ArcVpnErrorCallback), false /* check_error_state */, |
| chromeos::ConnectCallbackMode::ON_COMPLETED); |
| } |
| |
| std::unique_ptr<base::Value> ArcNetHostImpl::TranslateStringListToValue( |
| const std::vector<std::string>& string_list) { |
| std::unique_ptr<base::Value> result = |
| std::make_unique<base::Value>(base::Value::Type::LIST); |
| for (const auto& item : string_list) { |
| result->GetList().emplace_back(item); |
| } |
| return result; |
| } |
| |
| std::unique_ptr<base::DictionaryValue> |
| ArcNetHostImpl::TranslateVpnConfigurationToOnc( |
| const mojom::AndroidVpnConfiguration& cfg) { |
| std::unique_ptr<base::DictionaryValue> top_dict = |
| std::make_unique<base::DictionaryValue>(); |
| |
| // Name, Type |
| top_dict->SetKey( |
| onc::network_config::kName, |
| base::Value(cfg.session_name.empty() ? cfg.app_label : cfg.session_name)); |
| top_dict->SetKey(onc::network_config::kType, |
| base::Value(onc::network_config::kVPN)); |
| |
| // StaticIPConfig dictionary |
| top_dict->SetKey(onc::network_config::kIPAddressConfigType, |
| base::Value(onc::network_config::kIPConfigTypeStatic)); |
| top_dict->SetKey(onc::network_config::kNameServersConfigType, |
| base::Value(onc::network_config::kIPConfigTypeStatic)); |
| |
| std::unique_ptr<base::DictionaryValue> ip_dict = |
| std::make_unique<base::DictionaryValue>(); |
| ip_dict->SetKey(onc::ipconfig::kType, base::Value(onc::ipconfig::kIPv4)); |
| ip_dict->SetKey(onc::ipconfig::kIPAddress, base::Value(cfg.ipv4_gateway)); |
| ip_dict->SetKey(onc::ipconfig::kRoutingPrefix, base::Value(32)); |
| ip_dict->SetKey(onc::ipconfig::kGateway, base::Value(cfg.ipv4_gateway)); |
| |
| ip_dict->SetWithoutPathExpansion(onc::ipconfig::kNameServers, |
| TranslateStringListToValue(cfg.nameservers)); |
| ip_dict->SetWithoutPathExpansion(onc::ipconfig::kSearchDomains, |
| TranslateStringListToValue(cfg.domains)); |
| ip_dict->SetWithoutPathExpansion( |
| onc::ipconfig::kIncludedRoutes, |
| TranslateStringListToValue(cfg.split_include)); |
| ip_dict->SetWithoutPathExpansion( |
| onc::ipconfig::kExcludedRoutes, |
| TranslateStringListToValue(cfg.split_exclude)); |
| |
| top_dict->SetWithoutPathExpansion(onc::network_config::kStaticIPConfig, |
| std::move(ip_dict)); |
| |
| // VPN dictionary |
| std::unique_ptr<base::DictionaryValue> vpn_dict = |
| std::make_unique<base::DictionaryValue>(); |
| vpn_dict->SetKey(onc::vpn::kHost, base::Value(cfg.app_name)); |
| vpn_dict->SetKey(onc::vpn::kType, base::Value(onc::vpn::kArcVpn)); |
| |
| // ARCVPN dictionary |
| std::unique_ptr<base::DictionaryValue> arcvpn_dict = |
| std::make_unique<base::DictionaryValue>(); |
| arcvpn_dict->SetKey( |
| onc::arc_vpn::kTunnelChrome, |
| base::Value(cfg.tunnel_chrome_traffic ? "true" : "false")); |
| vpn_dict->SetWithoutPathExpansion(onc::vpn::kArcVpn, std::move(arcvpn_dict)); |
| |
| top_dict->SetWithoutPathExpansion(onc::network_config::kVPN, |
| std::move(vpn_dict)); |
| |
| return top_dict; |
| } |
| |
| void ArcNetHostImpl::AndroidVpnConnected( |
| mojom::AndroidVpnConfigurationPtr cfg) { |
| std::unique_ptr<base::DictionaryValue> properties = |
| TranslateVpnConfigurationToOnc(*cfg); |
| |
| if (!base::FeatureList::IsEnabled(arc::kVpnFeature)) { |
| VLOG(1) << "AndroidVpnConnected: feature is disabled; ignoring"; |
| return; |
| } |
| |
| std::string service_path = LookupArcVpnServicePath(); |
| if (!service_path.empty()) { |
| VLOG(1) << "AndroidVpnConnected: reusing " << service_path; |
| GetManagedConfigurationHandler()->SetProperties( |
| service_path, *properties, |
| base::Bind(&ArcNetHostImpl::ConnectArcVpn, weak_factory_.GetWeakPtr(), |
| service_path, std::string()), |
| base::Bind(&ArcVpnErrorCallback)); |
| return; |
| } |
| |
| VLOG(1) << "AndroidVpnConnected: creating new ARC VPN"; |
| std::string user_id_hash = chromeos::LoginState::Get()->primary_user_hash(); |
| GetManagedConfigurationHandler()->CreateConfiguration( |
| user_id_hash, *properties, |
| base::Bind(&ArcNetHostImpl::ConnectArcVpn, weak_factory_.GetWeakPtr()), |
| base::Bind(&ArcVpnErrorCallback)); |
| } |
| |
| void ArcNetHostImpl::AndroidVpnStateChanged(mojom::ConnectionStateType state) { |
| VLOG(1) << "AndroidVpnStateChanged: state=" << state |
| << " service=" << arc_vpn_service_path_; |
| |
| if (state != arc::mojom::ConnectionStateType::NOT_CONNECTED || |
| arc_vpn_service_path_.empty()) { |
| return; |
| } |
| |
| // DisconnectNetwork() invokes DisconnectRequested() through the |
| // observer interface, so make sure it doesn't generate an unwanted |
| // mojo call to Android. |
| std::string service_path(arc_vpn_service_path_); |
| arc_vpn_service_path_.clear(); |
| |
| GetNetworkConnectionHandler()->DisconnectNetwork( |
| service_path, base::Bind(&ArcVpnSuccessCallback), |
| base::Bind(&ArcVpnErrorCallback)); |
| } |
| |
| void ArcNetHostImpl::SetAlwaysOnVpn(const std::string& vpn_package, |
| bool lockdown) { |
| // pref_service_ should be set by ArcServiceLauncher. |
| DCHECK(pref_service_); |
| pref_service_->SetString(prefs::kAlwaysOnVpnPackage, vpn_package); |
| pref_service_->SetBoolean(prefs::kAlwaysOnVpnLockdown, lockdown); |
| } |
| |
| void ArcNetHostImpl::DisconnectArcVpn() { |
| arc_vpn_service_path_.clear(); |
| |
| auto* net_instance = ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->net(), |
| DisconnectAndroidVpn); |
| if (!net_instance) { |
| LOG(ERROR) << "User requested VPN disconnection but API is unavailable"; |
| return; |
| } |
| net_instance->DisconnectAndroidVpn(); |
| } |
| |
| void ArcNetHostImpl::DisconnectRequested(const std::string& service_path) { |
| if (arc_vpn_service_path_ != service_path) { |
| return; |
| } |
| |
| // This code path is taken when a user clicks the blue Disconnect button |
| // in Chrome OS. Chrome is about to send the Disconnect call to shill, |
| // so update our local state and tell Android to disconnect the VPN. |
| VLOG(1) << "DisconnectRequested " << service_path; |
| DisconnectArcVpn(); |
| } |
| |
| void ArcNetHostImpl::NetworkConnectionStateChanged( |
| const chromeos::NetworkState* network) { |
| // DefaultNetworkChanged() won't be invoked if an ARC VPN is the default |
| // network and the underlying physical connection changed, so check for |
| // that condition here. This is invoked any time any service state |
| // changes. |
| UpdateDefaultNetwork(); |
| |
| const chromeos::NetworkState* shill_backed_network = |
| GetShillBackedNetwork(network); |
| if (!shill_backed_network) |
| return; |
| |
| if (arc_vpn_service_path_ != shill_backed_network->path() || |
| shill_backed_network->IsConnectingOrConnected()) { |
| return; |
| } |
| |
| // This code path is taken when shill disconnects the Android VPN |
| // service. This can happen if a user tries to connect to a Chrome OS |
| // VPN, and shill's VPNProvider::DisconnectAll() forcibly disconnects |
| // all other VPN services to avoid a conflict. |
| VLOG(1) << "NetworkConnectionStateChanged " << shill_backed_network->path(); |
| DisconnectArcVpn(); |
| } |
| |
| void ArcNetHostImpl::ActiveNetworksChanged( |
| const std::vector<const chromeos::NetworkState*>& active_networks) { |
| auto* net_instance = ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->net(), |
| ActiveNetworksChanged); |
| if (!net_instance) |
| return; |
| |
| std::vector<arc::mojom::NetworkConfigurationPtr> network_configurations; |
| for (const chromeos::NetworkState* state : active_networks) |
| network_configurations.push_back(TranslateNetworkState(state)); |
| net_instance->ActiveNetworksChanged(std::move(network_configurations)); |
| } |
| |
| void ArcNetHostImpl::NetworkListChanged() { |
| // This is invoked any time the list of services is reordered or changed. |
| // During the transition when a new service comes online, it will |
| // temporarily be ranked below "inferior" services. This callback |
| // informs us that shill's ordering has been updated. |
| UpdateDefaultNetwork(); |
| } |
| |
| void ArcNetHostImpl::OnShuttingDown() { |
| DCHECK(observing_network_state_); |
| GetStateHandler()->RemoveObserver(this, FROM_HERE); |
| GetNetworkConnectionHandler()->RemoveObserver(this); |
| observing_network_state_ = false; |
| } |
| |
| } // namespace arc |