| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/components/arc/net/arc_net_host_impl.h" |
| |
| #include <net/if.h> |
| |
| #include <utility> |
| |
| #include "ash/components/arc/arc_browser_context_keyed_service_factory_base.h" |
| #include "ash/components/arc/arc_features.h" |
| #include "ash/components/arc/arc_prefs.h" |
| #include "ash/components/arc/net/cert_manager.h" |
| #include "ash/components/arc/session/arc_bridge_service.h" |
| #include "base/containers/cxx20_erase.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/memory/singleton.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "chromeos/ash/components/dbus/patchpanel/patchpanel_client.h" |
| #include "chromeos/ash/components/dbus/patchpanel/patchpanel_service.pb.h" |
| #include "chromeos/ash/components/dbus/shill/shill_manager_client.h" |
| #include "chromeos/ash/components/login/login_state/login_state.h" |
| #include "chromeos/ash/components/network/client_cert_util.h" |
| #include "chromeos/ash/components/network/device_state.h" |
| #include "chromeos/ash/components/network/managed_network_configuration_handler.h" |
| #include "chromeos/ash/components/network/network_configuration_handler.h" |
| #include "chromeos/ash/components/network/network_connection_handler.h" |
| #include "chromeos/ash/components/network/network_event_log.h" |
| #include "chromeos/ash/components/network/network_handler.h" |
| #include "chromeos/ash/components/network/network_state.h" |
| #include "chromeos/ash/components/network/network_state_handler.h" |
| #include "chromeos/ash/components/network/network_type_pattern.h" |
| #include "chromeos/ash/components/network/onc/network_onc_utils.h" |
| #include "components/device_event_log/device_event_log.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/user_manager/user_manager.h" |
| #include "dbus/object_path.h" |
| #include "third_party/cros_system_api/dbus/shill/dbus-constants.h" |
| |
| namespace { |
| |
| constexpr int kGetNetworksListLimit = 100; |
| |
| std::string PackedIPAddressToString(sa_family_t family, |
| const std::string& data) { |
| if (family != AF_INET && family != AF_INET6) { |
| NET_LOG(ERROR) << "Invalid IP family " << family; |
| return ""; |
| } |
| if (family == AF_INET && data.length() != sizeof(in_addr)) { |
| NET_LOG(ERROR) << "Invalid packed IPv4 data size " << data.length() |
| << ", expected " << sizeof(in_addr); |
| return ""; |
| } |
| if (family == AF_INET6 && data.length() != sizeof(in6_addr)) { |
| NET_LOG(ERROR) << "Invalid packed IPv6 data size " << data.length() |
| << ", expected " << sizeof(in6_addr); |
| return ""; |
| } |
| |
| char buf[INET6_ADDRSTRLEN] = {0}; |
| return !inet_ntop(family, data.data(), buf, sizeof(buf)) ? "" : buf; |
| } |
| |
| ash::NetworkStateHandler* GetStateHandler() { |
| return ash::NetworkHandler::Get()->network_state_handler(); |
| } |
| |
| ash::ManagedNetworkConfigurationHandler* GetManagedConfigurationHandler() { |
| return ash::NetworkHandler::Get()->managed_network_configuration_handler(); |
| } |
| |
| ash::NetworkConnectionHandler* GetNetworkConnectionHandler() { |
| return ash::NetworkHandler::Get()->network_connection_handler(); |
| } |
| |
| ash::NetworkProfileHandler* GetNetworkProfileHandler() { |
| return ash::NetworkHandler::Get()->network_profile_handler(); |
| } |
| |
| const ash::NetworkProfile* GetNetworkProfile() { |
| return GetNetworkProfileHandler()->GetProfileForUserhash( |
| ash::LoginState::Get()->primary_user_hash()); |
| } |
| |
| std::vector<const ash::NetworkState*> GetHostActiveNetworks() { |
| std::vector<const ash::NetworkState*> active_networks; |
| GetStateHandler()->GetActiveNetworkListByType( |
| ash::NetworkTypePattern::Default(), &active_networks); |
| return active_networks; |
| } |
| |
| std::string TranslateEapMethod(arc::mojom::EapMethod method) { |
| switch (method) { |
| case arc::mojom::EapMethod::kLeap: |
| return shill::kEapMethodLEAP; |
| case arc::mojom::EapMethod::kPeap: |
| return shill::kEapMethodPEAP; |
| case arc::mojom::EapMethod::kTls: |
| return shill::kEapMethodTLS; |
| case arc::mojom::EapMethod::kTtls: |
| return shill::kEapMethodTTLS; |
| case arc::mojom::EapMethod::kNone: |
| return ""; |
| } |
| NET_LOG(ERROR) << "Unknown EAP method"; |
| return ""; |
| } |
| |
| std::string TranslateEapPhase2Method(arc::mojom::EapPhase2Method method) { |
| switch (method) { |
| case arc::mojom::EapPhase2Method::kPap: |
| return shill::kEapPhase2AuthTTLSPAP; |
| case arc::mojom::EapPhase2Method::kMschap: |
| return shill::kEapPhase2AuthTTLSMSCHAP; |
| case arc::mojom::EapPhase2Method::kMschapv2: |
| return shill::kEapPhase2AuthTTLSMSCHAPV2; |
| case arc::mojom::EapPhase2Method::kNone: |
| return ""; |
| } |
| NET_LOG(ERROR) << "Unknown EAP phase 2 method"; |
| return ""; |
| } |
| |
| std::string TranslateKeyManagement(arc::mojom::KeyManagement management) { |
| switch (management) { |
| case arc::mojom::KeyManagement::kIeee8021X: |
| return shill::kKeyManagementIEEE8021X; |
| case arc::mojom::KeyManagement::kFtEap: |
| case arc::mojom::KeyManagement::kFtPsk: |
| case arc::mojom::KeyManagement::kFtSae: |
| case arc::mojom::KeyManagement::kWpaEap: |
| case arc::mojom::KeyManagement::kWpaEapSha256: |
| case arc::mojom::KeyManagement::kWpaPsk: |
| case arc::mojom::KeyManagement::kSae: |
| // Currently these key managements are not handled. |
| NET_LOG(ERROR) << "Key management is not supported"; |
| return ""; |
| case arc::mojom::KeyManagement::kNone: |
| return ""; |
| } |
| NET_LOG(ERROR) << "Unknown key management"; |
| return ""; |
| } |
| |
| arc::mojom::SecurityType TranslateWiFiSecurity( |
| const std::string& security_class) { |
| if (security_class == shill::kSecurityClassNone) |
| return arc::mojom::SecurityType::NONE; |
| |
| if (security_class == shill::kSecurityClassWep) |
| return arc::mojom::SecurityType::WEP_PSK; |
| |
| if (security_class == shill::kSecurityClassPsk) |
| return arc::mojom::SecurityType::WPA_PSK; |
| |
| if (security_class == shill::kSecurityClass8021x) |
| return arc::mojom::SecurityType::WPA_EAP; |
| |
| NET_LOG(ERROR) << "Unknown WiFi security class " << security_class; |
| return arc::mojom::SecurityType::NONE; |
| } |
| |
| // 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 == shill::kStateDisconnect) || (state == "")) { |
| return arc::mojom::ConnectionStateType::NOT_CONNECTED; |
| } |
| if (ash::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, |
| // and kStateOffline. |
| NOTREACHED() << "Unknown connection state: " << state; |
| return arc::mojom::ConnectionStateType::NOT_CONNECTED; |
| } |
| |
| bool IsActiveNetworkState(const ash::NetworkState* network) { |
| if (!network) |
| return false; |
| |
| const std::string& state = network->connection_state(); |
| return state == shill::kStateReady || state == shill::kStateOnline || |
| state == shill::kStateAssociation || |
| state == shill::kStateConfiguration || |
| state == shill::kStateNoConnectivity || |
| state == shill::kStateRedirectFound || |
| state == shill::kStatePortalSuspected; |
| } |
| |
| arc::mojom::NetworkType TranslateNetworkType(const std::string& type) { |
| if (type == shill::kTypeWifi) |
| return arc::mojom::NetworkType::WIFI; |
| |
| if (type == shill::kTypeVPN) |
| return arc::mojom::NetworkType::VPN; |
| |
| if (type == shill::kTypeEthernet) |
| return arc::mojom::NetworkType::ETHERNET; |
| |
| if (type == shill::kTypeEthernetEap) |
| return arc::mojom::NetworkType::ETHERNET; |
| |
| if (type == shill::kTypeCellular) |
| return arc::mojom::NetworkType::CELLULAR; |
| |
| NOTREACHED() << "Unknown network type: " << type; |
| return arc::mojom::NetworkType::ETHERNET; |
| } |
| |
| // Parses a shill IPConfig dictionary and adds the relevant fields to |
| // the given |network| NetworkConfiguration object. |
| void AddIpConfiguration(arc::mojom::NetworkConfiguration* network, |
| const base::Value* shill_ipconfig) { |
| const base::Value::Dict* shill_ipconfig_dict = shill_ipconfig->GetIfDict(); |
| if (!shill_ipconfig_dict) |
| return; |
| |
| // Only set the IP address and gateway if both are defined and non empty. |
| const auto* address = |
| shill_ipconfig_dict->FindString(shill::kAddressProperty); |
| const auto* gateway = |
| shill_ipconfig_dict->FindString(shill::kGatewayProperty); |
| const int prefixlen = |
| shill_ipconfig_dict->FindInt(shill::kPrefixlenProperty).value_or(0); |
| if (address && !address->empty() && gateway && !gateway->empty()) { |
| if (prefixlen < 64) { |
| network->host_ipv4_prefix_length = prefixlen; |
| network->host_ipv4_address = *address; |
| network->host_ipv4_gateway = *gateway; |
| } else { |
| network->host_ipv6_prefix_length = prefixlen; |
| network->host_ipv6_global_addresses->push_back(*address); |
| network->host_ipv6_gateway = *gateway; |
| } |
| } |
| |
| // If the user has overridden DNS with the "Google nameservers" UI options, |
| // the kStaticIPConfigProperty object will be empty except for DNS addresses. |
| if (const auto* dns_list = |
| shill_ipconfig_dict->FindList(shill::kNameServersProperty)) { |
| for (const auto& dns_value : *dns_list) { |
| const std::string& dns = dns_value.GetString(); |
| if (dns.empty()) |
| continue; |
| |
| // When manually setting DNS, up to 4 addresses can be specified in the |
| // UI. Unspecified entries can show up as 0.0.0.0 and should be removed. |
| if (dns == "0.0.0.0") |
| continue; |
| |
| network->host_dns_addresses->push_back(dns); |
| } |
| } |
| |
| if (const auto* domains = |
| shill_ipconfig_dict->FindList(shill::kSearchDomainsProperty)) { |
| for (const auto& domain : *domains) |
| network->host_search_domains->push_back(domain.GetString()); |
| } |
| |
| const int mtu = shill_ipconfig_dict->FindInt(shill::kMtuProperty).value_or(0); |
| if (mtu > 0) |
| network->host_mtu = mtu; |
| |
| if (const auto* include_routes_list = |
| shill_ipconfig_dict->FindList(shill::kIncludedRoutesProperty)) { |
| for (const auto& include_routes_value : *include_routes_list) { |
| const std::string& include_route = include_routes_value.GetString(); |
| if (!include_route.empty()) { |
| network->include_routes->push_back(include_route); |
| } |
| } |
| } |
| |
| if (const auto* exclude_routes_list = |
| shill_ipconfig_dict->FindList(shill::kExcludedRoutesProperty)) { |
| for (const auto& exclude_routes_value : *exclude_routes_list) { |
| const std::string& exclude_route = exclude_routes_value.GetString(); |
| if (!exclude_route.empty()) { |
| network->exclude_routes->push_back(exclude_route); |
| } |
| } |
| } |
| } |
| |
| arc::mojom::NetworkConfigurationPtr TranslateNetworkProperties( |
| const ash::NetworkState* network_state, |
| const base::Value* shill_dict) { |
| auto mojo = arc::mojom::NetworkConfiguration::New(); |
| // Initialize optional array fields to avoid null guards both here and in ARC. |
| mojo->host_ipv6_global_addresses = std::vector<std::string>(); |
| mojo->host_search_domains = std::vector<std::string>(); |
| mojo->host_dns_addresses = std::vector<std::string>(); |
| mojo->include_routes = std::vector<std::string>(); |
| mojo->exclude_routes = std::vector<std::string>(); |
| mojo->connection_state = |
| TranslateConnectionState(network_state->connection_state()); |
| mojo->guid = network_state->guid(); |
| if (mojo->guid.empty()) { |
| NET_LOG(ERROR) << "Missing GUID property for network " |
| << network_state->path(); |
| } |
| mojo->type = TranslateNetworkType(network_state->type()); |
| mojo->is_metered = |
| shill_dict && |
| shill_dict->FindBoolPath(shill::kMeteredProperty).value_or(false); |
| |
| // IP configuration data is added from the properties of the underlying shill |
| // Device and shill Service attached to the Device. Device properties are |
| // preferred because Service properties cannot have both IPv4 and IPv6 |
| // configurations at the same time for dual stack networks. It is necessary to |
| // fallback on Service properties for networks without a shill Device exposed |
| // over DBus (builtin OpenVPN, builtin L2TP client, Chrome extension VPNs), |
| // particularly to obtain the DNS server list (b/155129178). |
| // A connecting or newly connected network may not immediately have any |
| // usable IP config object if IPv4 dhcp or IPv6 autoconf have not completed |
| // yet. This case is covered by requesting shill properties asynchronously |
| // when ash::NetworkStateHandlerObserver::NetworkPropertiesUpdated is |
| // called. |
| |
| // Add shill's Device properties to the given mojo NetworkConfiguration |
| // objects. This adds the network interface and current IP configurations. |
| if (const auto* device = |
| GetStateHandler()->GetDeviceState(network_state->device_path())) { |
| mojo->network_interface = device->interface(); |
| for (const auto kv : device->ip_configs()) |
| AddIpConfiguration(mojo.get(), &kv.second); |
| } |
| |
| if (shill_dict) { |
| for (const auto* property : |
| {shill::kStaticIPConfigProperty, shill::kSavedIPConfigProperty}) { |
| AddIpConfiguration(mojo.get(), shill_dict->GetDict().Find(property)); |
| } |
| } |
| |
| if (mojo->type == arc::mojom::NetworkType::WIFI) { |
| mojo->wifi = arc::mojom::WiFi::New(); |
| mojo->wifi->bssid = network_state->bssid(); |
| mojo->wifi->hex_ssid = network_state->GetHexSsid(); |
| mojo->wifi->security = |
| TranslateWiFiSecurity(network_state->security_class()); |
| mojo->wifi->frequency = network_state->frequency(); |
| mojo->wifi->signal_strength = network_state->signal_strength(); |
| mojo->wifi->rssi = network_state->rssi(); |
| if (shill_dict) { |
| mojo->wifi->hidden_ssid = |
| shill_dict->FindBoolPath(shill::kWifiHiddenSsid).value_or(false); |
| const auto* fqdn = |
| shill_dict->FindStringPath(shill::kPasspointFQDNProperty); |
| if (fqdn && !fqdn->empty()) { |
| mojo->wifi->fqdn = *fqdn; |
| } |
| } |
| } |
| |
| return mojo; |
| } |
| |
| const ash::NetworkState* GetShillBackedNetwork( |
| const ash::NetworkState* network) { |
| if (!network) |
| return nullptr; |
| |
| // Non-Tether networks are already backed by Shill. |
| const std::string type = network->type(); |
| if (type.empty() || !ash::NetworkTypePattern::Tether().MatchesType(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()); |
| } |
| |
| std::string IPv4AddressToString(uint32_t addr) { |
| char buf[INET_ADDRSTRLEN] = {0}; |
| struct in_addr ia; |
| ia.s_addr = addr; |
| return !inet_ntop(AF_INET, &ia, buf, sizeof(buf)) ? std::string() : buf; |
| } |
| |
| // Convenience helper for translating a vector of NetworkState objects to a |
| // vector of mojo NetworkConfiguration objects. |
| std::vector<arc::mojom::NetworkConfigurationPtr> TranslateNetworkStates( |
| const std::string& arc_vpn_path, |
| const ash::NetworkStateHandler::NetworkStateList& network_states, |
| const std::map<std::string, base::Value>& shill_network_properties, |
| const std::vector<patchpanel::NetworkDevice>& devices) { |
| // Move the devices vector to a map keyed by its physical interface name in |
| // order to avoid multiple loops. The map also filters non-ARC devices. |
| std::map<std::string, patchpanel::NetworkDevice> arc_devices; |
| for (const auto& d : devices) { |
| if (d.guest_type() != patchpanel::NetworkDevice::ARC && |
| d.guest_type() != patchpanel::NetworkDevice::ARCVM) { |
| continue; |
| } |
| arc_devices.emplace(d.phys_ifname(), d); |
| } |
| |
| std::vector<arc::mojom::NetworkConfigurationPtr> networks; |
| for (const ash::NetworkState* state : network_states) { |
| const std::string& network_path = state->path(); |
| // Never tell Android about its own VPN. |
| if (network_path == arc_vpn_path) |
| continue; |
| |
| // For tethered networks, the underlying WiFi networks are not part of |
| // active networks. Replace any such tethered network with its underlying |
| // backing network, because ARC cannot match its datapath with the tethered |
| // network configuration. |
| state = GetShillBackedNetwork(state); |
| if (!state) |
| continue; |
| |
| const auto it = shill_network_properties.find(network_path); |
| const auto* shill_dict = |
| (it != shill_network_properties.end()) ? &it->second : nullptr; |
| auto network = TranslateNetworkProperties(state, shill_dict); |
| network->is_default_network = state == GetStateHandler()->DefaultNetwork(); |
| network->service_name = network_path; |
| |
| // Fill in ARC properties. |
| auto arc_it = |
| arc_devices.find(network->network_interface.value_or(std::string())); |
| if (arc_it != arc_devices.end()) { |
| network->arc_network_interface = arc_it->second.guest_ifname(); |
| network->arc_ipv4_address = |
| IPv4AddressToString(arc_it->second.ipv4_addr()); |
| network->arc_ipv4_gateway = |
| IPv4AddressToString(arc_it->second.host_ipv4_addr()); |
| network->arc_ipv4_prefix_length = |
| arc_it->second.ipv4_subnet().prefix_len(); |
| // Fill in DNS proxy addresses. |
| network->dns_proxy_addresses = std::vector<std::string>(); |
| if (arc_it->second.dns_proxy_ipv4_addr().length() > 0) { |
| auto dns_proxy_ipv4_addr = PackedIPAddressToString( |
| AF_INET, arc_it->second.dns_proxy_ipv4_addr()); |
| if (!dns_proxy_ipv4_addr.empty()) { |
| network->dns_proxy_addresses->push_back(dns_proxy_ipv4_addr); |
| } |
| } |
| if (arc_it->second.dns_proxy_ipv6_addr().length() > 0) { |
| auto dns_proxy_ipv6_addr = PackedIPAddressToString( |
| AF_INET6, arc_it->second.dns_proxy_ipv6_addr()); |
| if (!dns_proxy_ipv6_addr.empty()) { |
| network->dns_proxy_addresses->push_back(dns_proxy_ipv6_addr); |
| } |
| } |
| } |
| networks.push_back(std::move(network)); |
| } |
| return networks; |
| } |
| |
| 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::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::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::move(callback).Run(arc::mojom::NetworkResult::FAILURE); |
| } |
| |
| void HostVpnSuccessCallback() {} |
| |
| void HostVpnErrorCallback(const std::string& operation, |
| const std::string& error_name) { |
| NET_LOG(ERROR) << "HostVpnErrorCallback: " << operation << ": " << error_name; |
| } |
| |
| void ArcVpnSuccessCallback() {} |
| |
| void ArcVpnErrorCallback(const std::string& operation, |
| const std::string& error_name) { |
| NET_LOG(ERROR) << "ArcVpnErrorCallback: " << operation << ": " << error_name; |
| } |
| |
| void AddPasspointCredentialsFailureCallback(const std::string& error_name, |
| const std::string& error_message) { |
| NET_LOG(ERROR) << "Failed to add passpoint credentials, error:" << error_name |
| << ", message: " << error_message; |
| } |
| |
| void RemovePasspointCredentialsFailureCallback( |
| const std::string& error_name, |
| const std::string& error_message) { |
| NET_LOG(ERROR) << "Failed to remove passpoint credentials, error:" |
| << error_name << ", message: " << error_message; |
| } |
| |
| void SetLohsEnabledSuccessCallback( |
| arc::ArcNetHostImpl::StartLohsCallback callback) { |
| std::move(callback).Run(arc::mojom::LohsStatus::kSuccess); |
| } |
| |
| void SetLohsEnabledFailureCallback( |
| arc::ArcNetHostImpl::StartLohsCallback callback, |
| const std::string& dbus_error_name, |
| const std::string& dbus_error_message) { |
| NET_LOG(ERROR) << "SetLohsEnabledFailureCallback, error: " << dbus_error_name |
| << ", message: " << dbus_error_message; |
| // TODO(b/259162524): Change this to a more specific "shill configuration" |
| // error |
| std::move(callback).Run(arc::mojom::LohsStatus::kErrorGeneric); |
| } |
| |
| void SetLohsConfigPropertySuccessCallback( |
| arc::ArcNetHostImpl::StartLohsCallback callback) { |
| auto callback_split = base::SplitOnceCallback(std::move(callback)); |
| ash::ShillManagerClient::Get()->SetLOHSEnabled( |
| true /* enabled */, |
| base::BindOnce(&SetLohsEnabledSuccessCallback, |
| std::move(callback_split.first)), |
| base::BindOnce(&SetLohsEnabledFailureCallback, |
| std::move(callback_split.second))); |
| } |
| |
| void SetLohsConfigPropertyFailureCallback( |
| arc::ArcNetHostImpl::StartLohsCallback callback, |
| const std::string& dbus_error_name, |
| const std::string& dbus_error_message) { |
| NET_LOG(ERROR) << "SetLohsConfigPropertyFailureCallback, error: " |
| << dbus_error_name << ", message: " << dbus_error_message; |
| // TODO(b/259162524): Change this to a more specific "shill configuration" |
| // error |
| std::move(callback).Run(arc::mojom::LohsStatus::kErrorGeneric); |
| } |
| |
| void StopLohsFailureCallback(const std::string& error_name, |
| const std::string& error_message) { |
| NET_LOG(ERROR) << "StopLohsFailureCallback, error:" << error_name |
| << ", message: " << error_message; |
| } |
| |
| } // 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) { |
| 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::SetCertManager(std::unique_ptr<CertManager> cert_manager) { |
| cert_manager_ = std::move(cert_manager); |
| } |
| |
| void ArcNetHostImpl::OnConnectionReady() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (ash::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 ash::NetworkState* default_network = |
| GetShillBackedNetwork(GetStateHandler()->DefaultNetwork()); |
| if (default_network && default_network->type() == shill::kTypeVPN && |
| default_network->GetVpnProviderType() == shill::kProviderArcVpn) { |
| GetNetworkConnectionHandler()->DisconnectNetwork( |
| default_network->path(), base::BindOnce(&ArcVpnSuccessCallback), |
| base::BindOnce(&ArcVpnErrorCallback, "disconnecting stale ARC VPN")); |
| } |
| |
| // Listen on network configuration changes. |
| ash::PatchPanelClient::Get()->AddObserver(this); |
| |
| SetUpFlags(); |
| } |
| |
| void ArcNetHostImpl::SetUpFlags() { |
| auto* net_instance = |
| ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->net(), SetUpFlag); |
| if (!net_instance) |
| return; |
| |
| net_instance->SetUpFlag(arc::mojom::Flag::ENABLE_ARC_HOST_VPN, |
| base::FeatureList::IsEnabled(arc::kEnableArcHostVpn)); |
| } |
| |
| 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; |
| |
| ash::PatchPanelClient::Get()->RemoveObserver(this); |
| } |
| |
| void ArcNetHostImpl::NetworkConfigurationChanged() { |
| // Get patchpanel devices and update active networks. |
| ash::PatchPanelClient::Get()->GetDevices(base::BindOnce( |
| &ArcNetHostImpl::UpdateActiveNetworks, weak_factory_.GetWeakPtr())); |
| } |
| |
| void ArcNetHostImpl::GetNetworks(mojom::GetNetworksRequestType type, |
| GetNetworksCallback callback) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (type == mojom::GetNetworksRequestType::ACTIVE_ONLY) { |
| ash::PatchPanelClient::Get()->GetDevices( |
| base::BindOnce(&ArcNetHostImpl::GetActiveNetworks, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| return; |
| } |
| |
| // Otherwise retrieve list of configured or visible WiFi networks. |
| bool configured_only = type == mojom::GetNetworksRequestType::CONFIGURED_ONLY; |
| ash::NetworkTypePattern network_pattern = |
| ash::onc::NetworkTypePatternFromOncType(onc::network_type::kWiFi); |
| |
| ash::NetworkStateHandler::NetworkStateList network_states; |
| GetStateHandler()->GetNetworkListByType( |
| network_pattern, configured_only, !configured_only /* visible_only */, |
| kGetNetworksListLimit, &network_states); |
| |
| std::vector<mojom::NetworkConfigurationPtr> networks = |
| TranslateNetworkStates(arc_vpn_service_path_, network_states, |
| shill_network_properties_, {} /* devices */); |
| std::move(callback).Run(mojom::GetNetworksResponseType::New( |
| arc::mojom::NetworkResult::SUCCESS, std::move(networks))); |
| } |
| |
| void ArcNetHostImpl::GetActiveNetworks( |
| GetNetworksCallback callback, |
| const std::vector<patchpanel::NetworkDevice>& devices) { |
| // Retrieve list of currently active networks. |
| ash::NetworkStateHandler::NetworkStateList network_states; |
| GetStateHandler()->GetActiveNetworkListByType( |
| ash::NetworkTypePattern::Default(), &network_states); |
| |
| std::vector<mojom::NetworkConfigurationPtr> networks = |
| TranslateNetworkStates(arc_vpn_service_path_, network_states, |
| shill_network_properties_, devices); |
| 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) { |
| 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) { |
| NET_LOG(ERROR) << "CreateNetworkFailureCallback: " << error_name; |
| std::move(callback).Run(std::string()); |
| } |
| |
| void ArcNetHostImpl::CreateNetwork(mojom::WifiConfigurationPtr cfg, |
| CreateNetworkCallback callback) { |
| if (!cfg->hexssid.has_value() || !cfg->details) { |
| NET_LOG(ERROR) |
| << "Cannot create WiFi network without hex ssid or WiFi properties"; |
| std::move(callback).Run(std::string()); |
| return; |
| } |
| |
| mojom::ConfiguredNetworkDetailsPtr details = |
| std::move(cfg->details->get_configured()); |
| if (!details) { |
| NET_LOG(ERROR) << "Cannot create WiFi network without WiFi properties"; |
| std::move(callback).Run(std::string()); |
| return; |
| } |
| |
| // TODO(b/195653632): Populate the shill EAP properties from the mojo |
| // WifiConfiguration object. |
| base::Value::Dict properties; |
| base::Value::Dict wifi_dict; |
| base::Value::Dict ipconfig_dict; |
| |
| properties.Set(onc::network_config::kType, onc::network_config::kWiFi); |
| // StaticIPConfig dictionary |
| wifi_dict.Set(onc::wifi::kHexSSID, cfg->hexssid.value()); |
| wifi_dict.Set(onc::wifi::kAutoConnect, details->autoconnect); |
| if (cfg->security.empty()) { |
| wifi_dict.Set(onc::wifi::kSecurity, onc::wifi::kSecurityNone); |
| } else { |
| wifi_dict.Set(onc::wifi::kSecurity, cfg->security); |
| if (details->passphrase.has_value()) { |
| wifi_dict.Set(onc::wifi::kPassphrase, details->passphrase.value()); |
| } |
| } |
| wifi_dict.Set(onc::wifi::kBSSID, cfg->bssid); |
| properties.Set(onc::network_config::kWiFi, std::move(wifi_dict)); |
| |
| // Set up static IPv4 config. |
| if (cfg->dns_servers.has_value()) { |
| ipconfig_dict.Set(onc::ipconfig::kNameServers, |
| TranslateStringListToValue(cfg->dns_servers.value())); |
| properties.Set(onc::network_config::kNameServersConfigType, |
| onc::network_config::kIPConfigTypeStatic); |
| } |
| |
| if (cfg->domains.has_value()) { |
| ipconfig_dict.Set(onc::ipconfig::kSearchDomains, |
| TranslateStringListToValue(cfg->domains.value())); |
| } |
| |
| // Static IPv4 address, static IPv4 address of the gateway and |
| // prefix length are made sure to be all valid or all empty on |
| // ARC side so we only need to check one of them. |
| if (cfg->static_ipv4_config && cfg->static_ipv4_config->ipv4_addr) { |
| ipconfig_dict.Set(onc::ipconfig::kType, onc::ipconfig::kIPv4); |
| properties.Set(onc::network_config::kIPAddressConfigType, |
| onc::network_config::kIPConfigTypeStatic); |
| ipconfig_dict.Set(onc::ipconfig::kIPAddress, |
| cfg->static_ipv4_config->ipv4_addr.value()); |
| ipconfig_dict.Set(onc::ipconfig::kGateway, |
| cfg->static_ipv4_config->gateway_ipv4_addr.value()); |
| ipconfig_dict.Set(onc::ipconfig::kRoutingPrefix, |
| cfg->static_ipv4_config->prefix_length); |
| } |
| if (cfg->http_proxy) { |
| properties.Set(onc::network_config::kProxySettings, |
| TranslateProxyConfiguration(cfg->http_proxy)); |
| } |
| |
| // Set up meteredness based on meteredOverride config from mojom. |
| if (cfg->metered_override == arc::mojom::MeteredOverride::kMetered) { |
| properties.Set(onc::network_config::kMetered, true); |
| } else if (cfg->metered_override == |
| arc::mojom::MeteredOverride::kNotmetered) { |
| properties.Set(onc::network_config::kMetered, false); |
| } |
| |
| if (!ipconfig_dict.empty()) { |
| properties.Set(onc::network_config::kStaticIPConfig, |
| std::move(ipconfig_dict)); |
| } |
| |
| std::string user_id_hash = ash::LoginState::Get()->primary_user_hash(); |
| // TODO(crbug.com/730593): Remove SplitOnceCallback() by updating |
| // the callee interface. |
| auto split_callback = base::SplitOnceCallback(std::move(callback)); |
| GetManagedConfigurationHandler()->CreateConfiguration( |
| user_id_hash, base::Value(std::move(properties)), |
| base::BindOnce(&ArcNetHostImpl::CreateNetworkSuccessCallback, |
| weak_factory_.GetWeakPtr(), |
| std::move(split_callback.first)), |
| base::BindOnce(&ArcNetHostImpl::CreateNetworkFailureCallback, |
| weak_factory_.GetWeakPtr(), |
| std::move(split_callback.second))); |
| } |
| |
| bool ArcNetHostImpl::GetNetworkPathFromGuid(const std::string& guid, |
| std::string* path) { |
| const auto* 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) { |
| std::string path; |
| if (!GetNetworkPathFromGuid(guid, &path)) { |
| NET_LOG(ERROR) << "Could not retrieve Service path from GUID " << guid; |
| std::move(callback).Run(mojom::NetworkResult::FAILURE); |
| return; |
| } |
| |
| cached_guid_.clear(); |
| // TODO(crbug.com/730593): Remove SplitOnceCallback() by updating |
| // the callee interface. |
| auto split_callback = base::SplitOnceCallback(std::move(callback)); |
| GetManagedConfigurationHandler()->RemoveConfigurationFromCurrentProfile( |
| path, |
| base::BindOnce(&ForgetNetworkSuccessCallback, |
| std::move(split_callback.first)), |
| base::BindOnce(&ForgetNetworkFailureCallback, |
| std::move(split_callback.second))); |
| } |
| |
| void ArcNetHostImpl::StartConnect(const std::string& guid, |
| StartConnectCallback callback) { |
| std::string path; |
| if (!GetNetworkPathFromGuid(guid, &path)) { |
| NET_LOG(ERROR) << "Could not retrieve Service path from GUID " << guid; |
| std::move(callback).Run(mojom::NetworkResult::FAILURE); |
| return; |
| } |
| |
| // TODO(crbug.com/730593): Remove SplitOnceCallback() by updating |
| // the callee interface. |
| auto split_callback = base::SplitOnceCallback(std::move(callback)); |
| GetNetworkConnectionHandler()->ConnectToNetwork( |
| path, |
| base::BindOnce(&StartConnectSuccessCallback, |
| std::move(split_callback.first)), |
| base::BindOnce(&StartConnectFailureCallback, |
| std::move(split_callback.second)), |
| false /* check_error_state */, ash::ConnectCallbackMode::ON_STARTED); |
| } |
| |
| void ArcNetHostImpl::StartDisconnect(const std::string& guid, |
| StartDisconnectCallback callback) { |
| std::string path; |
| if (!GetNetworkPathFromGuid(guid, &path)) { |
| NET_LOG(ERROR) << "Could not retrieve Service path from GUID " << guid; |
| std::move(callback).Run(mojom::NetworkResult::FAILURE); |
| return; |
| } |
| |
| // TODO(crbug.com/730593): Remove SplitOnceCallback() by updating |
| // the callee interface. |
| auto split_callback = base::SplitOnceCallback(std::move(callback)); |
| GetNetworkConnectionHandler()->DisconnectNetwork( |
| path, |
| base::BindOnce(&StartDisconnectSuccessCallback, |
| std::move(split_callback.first)), |
| base::BindOnce(&StartDisconnectFailureCallback, |
| std::move(split_callback.second))); |
| } |
| |
| void ArcNetHostImpl::GetWifiEnabledState(GetWifiEnabledStateCallback callback) { |
| bool is_enabled = |
| GetStateHandler()->IsTechnologyEnabled(ash::NetworkTypePattern::WiFi()); |
| std::move(callback).Run(is_enabled); |
| } |
| |
| void ArcNetHostImpl::SetWifiEnabledState(bool is_enabled, |
| SetWifiEnabledStateCallback callback) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| auto state = |
| GetStateHandler()->GetTechnologyState(ash::NetworkTypePattern::WiFi()); |
| // WiFi can't be enabled or disabled in these states. |
| if ((state == ash::NetworkStateHandler::TECHNOLOGY_PROHIBITED) || |
| (state == ash::NetworkStateHandler::TECHNOLOGY_UNINITIALIZED) || |
| (state == ash::NetworkStateHandler::TECHNOLOGY_UNAVAILABLE)) { |
| NET_LOG(ERROR) << "SetWifiEnabledState failed due to WiFi state: " << state; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| NET_LOG(USER) << __func__ << ":" << is_enabled; |
| GetStateHandler()->SetTechnologyEnabled( |
| ash::NetworkTypePattern::WiFi(), is_enabled, |
| ash::network_handler::ErrorCallback()); |
| std::move(callback).Run(true); |
| } |
| |
| void ArcNetHostImpl::StartScan() { |
| GetStateHandler()->RequestScan(ash::NetworkTypePattern::WiFi()); |
| } |
| |
| void ArcNetHostImpl::ScanCompleted(const ash::DeviceState* /*unused*/) { |
| auto* net_instance = |
| ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->net(), ScanCompleted); |
| if (!net_instance) |
| return; |
| |
| net_instance->ScanCompleted(); |
| } |
| |
| 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(ash::NetworkTypePattern::WiFi()); |
| net_instance->WifiEnabledStateChanged(is_enabled); |
| } |
| |
| std::string ArcNetHostImpl::LookupArcVpnServicePath() { |
| ash::NetworkStateHandler::NetworkStateList state_list; |
| GetStateHandler()->GetNetworkListByType( |
| ash::NetworkTypePattern::VPN(), true /* configured_only */, |
| false /* visible_only */, kGetNetworksListLimit, &state_list); |
| |
| for (const ash::NetworkState* state : state_list) { |
| const auto* 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 */) { |
| arc_vpn_service_path_ = service_path; |
| |
| GetNetworkConnectionHandler()->ConnectToNetwork( |
| service_path, base::BindOnce(&ArcVpnSuccessCallback), |
| base::BindOnce(&ArcVpnErrorCallback, "connecting ARC VPN"), |
| false /* check_error_state */, ash::ConnectCallbackMode::ON_COMPLETED); |
| } |
| |
| base::Value::List ArcNetHostImpl::TranslateStringListToValue( |
| const std::vector<std::string>& string_list) { |
| base::Value::List result; |
| for (const auto& item : string_list) |
| result.Append(item); |
| |
| return result; |
| } |
| |
| base::Value::List ArcNetHostImpl::TranslateLongListToStringValue( |
| const std::vector<uint64_t>& long_list) { |
| base::Value::List result; |
| for (const auto& item : long_list) |
| result.Append(base::NumberToString(item)); |
| |
| return result; |
| } |
| |
| base::Value::Dict ArcNetHostImpl::TranslateVpnConfigurationToOnc( |
| const mojom::AndroidVpnConfiguration& cfg) { |
| base::Value::Dict top_dict; |
| |
| // Name, Type |
| top_dict.Set(onc::network_config::kName, |
| cfg.session_name.empty() ? cfg.app_label : cfg.session_name); |
| top_dict.Set(onc::network_config::kType, onc::network_config::kVPN); |
| |
| top_dict.Set(onc::network_config::kIPAddressConfigType, |
| onc::network_config::kIPConfigTypeStatic); |
| top_dict.Set(onc::network_config::kNameServersConfigType, |
| onc::network_config::kIPConfigTypeStatic); |
| |
| base::Value::Dict ip_dict; |
| ip_dict.Set(onc::ipconfig::kType, onc::ipconfig::kIPv4); |
| ip_dict.Set(onc::ipconfig::kIPAddress, cfg.ipv4_gateway); |
| ip_dict.Set(onc::ipconfig::kRoutingPrefix, 32); |
| ip_dict.Set(onc::ipconfig::kGateway, cfg.ipv4_gateway); |
| ip_dict.Set(onc::ipconfig::kNameServers, |
| TranslateStringListToValue(cfg.nameservers)); |
| ip_dict.Set(onc::ipconfig::kSearchDomains, |
| TranslateStringListToValue(cfg.domains)); |
| ip_dict.Set(onc::ipconfig::kIncludedRoutes, |
| TranslateStringListToValue(cfg.split_include)); |
| ip_dict.Set(onc::ipconfig::kExcludedRoutes, |
| TranslateStringListToValue(cfg.split_exclude)); |
| |
| top_dict.Set(onc::network_config::kStaticIPConfig, std::move(ip_dict)); |
| |
| // VPN dictionary |
| base::Value::Dict vpn_dict; |
| vpn_dict.Set(onc::vpn::kHost, cfg.app_name); |
| vpn_dict.Set(onc::vpn::kType, onc::vpn::kArcVpn); |
| |
| // ARCVPN dictionary |
| base::Value::Dict arcvpn_dict; |
| arcvpn_dict.Set(onc::arc_vpn::kTunnelChrome, |
| cfg.tunnel_chrome_traffic ? "true" : "false"); |
| vpn_dict.Set(onc::vpn::kArcVpn, std::move(arcvpn_dict)); |
| |
| top_dict.Set(onc::network_config::kVPN, std::move(vpn_dict)); |
| if (cfg.http_proxy) { |
| top_dict.Set(onc::network_config::kProxySettings, |
| TranslateProxyConfiguration(cfg.http_proxy)); |
| } |
| return top_dict; |
| } |
| |
| void ArcNetHostImpl::AndroidVpnConnected( |
| mojom::AndroidVpnConfigurationPtr cfg) { |
| std::string service_path = LookupArcVpnServicePath(); |
| if (!service_path.empty()) { |
| GetManagedConfigurationHandler()->SetProperties( |
| service_path, base::Value(TranslateVpnConfigurationToOnc(*cfg)), |
| base::BindOnce(&ArcNetHostImpl::ConnectArcVpn, |
| weak_factory_.GetWeakPtr(), service_path, std::string()), |
| base::BindOnce(&ArcVpnErrorCallback, |
| "reconnecting ARC VPN " + service_path)); |
| return; |
| } |
| |
| std::string user_id_hash = ash::LoginState::Get()->primary_user_hash(); |
| GetManagedConfigurationHandler()->CreateConfiguration( |
| user_id_hash, base::Value(TranslateVpnConfigurationToOnc(*cfg)), |
| base::BindOnce(&ArcNetHostImpl::ConnectArcVpn, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce(&ArcVpnErrorCallback, "connecting new ARC VPN")); |
| } |
| |
| void ArcNetHostImpl::AndroidVpnStateChanged(mojom::ConnectionStateType state) { |
| 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::BindOnce(&ArcVpnSuccessCallback), |
| base::BindOnce(&ArcVpnErrorCallback, "disconnecting ARC VPN")); |
| } |
| |
| void ArcNetHostImpl::TranslateEapCredentialsToDict( |
| mojom::EapCredentialsPtr cred, |
| base::OnceCallback<void(base::Value::Dict)> callback) { |
| if (!cred) { |
| NET_LOG(ERROR) << "Empty EAP credentials"; |
| return; |
| } |
| if (!cert_manager_) { |
| NET_LOG(ERROR) << "CertManager is not initialized"; |
| return; |
| } |
| |
| if (cred->client_certificate_key.has_value() && |
| cred->client_certificate_pem.has_value() && |
| cred->client_certificate_pem.value().size() > 0) { |
| // |client_certificate_pem| contains all client certificates inside ARC's |
| // PasspointConfiguration. ARC uses only one of the certificate that match |
| // the certificate SHA-256 fingerprint. Currently, it is assumed that the |
| // first certificate is the used certificate. |
| // TODO(b/195262431): Remove the assumption by passing only the used |
| // certificate to Chrome. |
| // TODO(b/220803680): Remove imported certificates and keys when the |
| // associated passpoint profile is removed. |
| auto key = cred->client_certificate_key.value(); |
| auto pem = cred->client_certificate_pem.value()[0]; |
| cert_manager_->ImportPrivateKeyAndCert( |
| key, pem, |
| base::BindOnce(&ArcNetHostImpl::TranslateEapCredentialsToDictWithCertID, |
| weak_factory_.GetWeakPtr(), std::move(cred), |
| std::move(callback))); |
| return; |
| } |
| TranslateEapCredentialsToDictWithCertID(std::move(cred), std::move(callback), |
| /*cert_id=*/absl::nullopt, |
| /*slot_id=*/absl::nullopt); |
| } |
| |
| void ArcNetHostImpl::TranslateEapCredentialsToDictWithCertID( |
| mojom::EapCredentialsPtr cred, |
| base::OnceCallback<void(base::Value::Dict)> callback, |
| const absl::optional<std::string>& cert_id, |
| const absl::optional<int>& slot_id) { |
| if (!cred) { |
| NET_LOG(ERROR) << "Empty EAP credentials"; |
| return; |
| } |
| |
| base::Value::Dict dict; |
| dict.Set(shill::kEapMethodProperty, TranslateEapMethod(cred->method)); |
| dict.Set(shill::kEapPhase2AuthProperty, |
| TranslateEapPhase2Method(cred->phase2_method)); |
| if (cred->anonymous_identity.has_value()) { |
| dict.Set(shill::kEapAnonymousIdentityProperty, |
| cred->anonymous_identity.value()); |
| } |
| if (cred->identity.has_value()) |
| dict.Set(shill::kEapIdentityProperty, cred->identity.value()); |
| |
| if (cred->password.has_value()) |
| dict.Set(shill::kEapPasswordProperty, cred->password.value()); |
| |
| dict.Set(shill::kEapKeyMgmtProperty, |
| TranslateKeyManagement(cred->key_management)); |
| |
| if (cred->ca_certificate_pem.has_value()) { |
| dict.Set(shill::kEapCaCertPemProperty, |
| TranslateStringListToValue(cred->ca_certificate_pem.value())); |
| } |
| if (cert_id.has_value() && slot_id.has_value()) { |
| // The ID of imported user certificate and private key is the same, use one |
| // of them. |
| dict.Set( |
| shill::kEapKeyIdProperty, |
| base::StringPrintf("%i:%s", slot_id.value(), cert_id.value().c_str())); |
| dict.Set( |
| shill::kEapCertIdProperty, |
| base::StringPrintf("%i:%s", slot_id.value(), cert_id.value().c_str())); |
| dict.Set(shill::kEapPinProperty, ash::client_cert::kDefaultTPMPin); |
| } |
| |
| if (cred->subject_match.has_value()) { |
| dict.Set(shill::kEapSubjectMatchProperty, cred->subject_match.value()); |
| } |
| if (cred->subject_alternative_name_match_list.has_value()) { |
| dict.Set(shill::kEapSubjectAlternativeNameMatchProperty, |
| TranslateStringListToValue( |
| cred->subject_alternative_name_match_list.value())); |
| } |
| if (cred->domain_suffix_match_list.has_value()) { |
| dict.Set( |
| shill::kEapDomainSuffixMatchProperty, |
| TranslateStringListToValue(cred->domain_suffix_match_list.value())); |
| } |
| if (cred->tls_version_max.has_value()) { |
| dict.Set(shill::kEapTLSVersionMaxProperty, cred->tls_version_max.value()); |
| } |
| dict.Set(shill::kEapUseSystemCasProperty, cred->use_system_cas); |
| dict.Set(shill::kEapUseProactiveKeyCachingProperty, |
| cred->use_proactive_key_caching); |
| dict.Set(shill::kEapUseLoginPasswordProperty, cred->use_login_password); |
| |
| std::move(callback).Run(std::move(dict)); |
| } |
| |
| void ArcNetHostImpl::TranslatePasspointCredentialsToDict( |
| mojom::PasspointCredentialsPtr cred, |
| base::OnceCallback<void(base::Value::Dict)> callback) { |
| if (!cred) { |
| NET_LOG(ERROR) << "Empty passpoint credentials"; |
| return; |
| } |
| if (!cred->eap) { |
| NET_LOG(ERROR) << "mojom::PasspointCredentials has no EAP properties"; |
| return; |
| } |
| |
| mojom::EapCredentialsPtr eap = cred->eap.Clone(); |
| TranslateEapCredentialsToDict( |
| std::move(eap), |
| base::BindOnce( |
| &ArcNetHostImpl::TranslatePasspointCredentialsToDictWithEapTranslated, |
| weak_factory_.GetWeakPtr(), std::move(cred), std::move(callback))); |
| } |
| |
| void ArcNetHostImpl::TranslatePasspointCredentialsToDictWithEapTranslated( |
| mojom::PasspointCredentialsPtr cred, |
| base::OnceCallback<void(base::Value::Dict)> callback, |
| base::Value::Dict dict) { |
| if (!cred) { |
| NET_LOG(ERROR) << "Empty passpoint credentials"; |
| return; |
| } |
| if (dict.empty()) { |
| NET_LOG(ERROR) << "Failed to translate EapCredentials properties"; |
| return; |
| } |
| |
| dict.Set(shill::kPasspointCredentialsDomainsProperty, |
| TranslateStringListToValue(cred->domains)); |
| dict.Set(shill::kPasspointCredentialsRealmProperty, cred->realm); |
| dict.Set(shill::kPasspointCredentialsHomeOIsProperty, |
| TranslateLongListToStringValue(cred->home_ois)); |
| dict.Set(shill::kPasspointCredentialsRequiredHomeOIsProperty, |
| TranslateLongListToStringValue(cred->required_home_ois)); |
| dict.Set(shill::kPasspointCredentialsRoamingConsortiaProperty, |
| TranslateLongListToStringValue(cred->roaming_consortium_ois)); |
| dict.Set(shill::kPasspointCredentialsMeteredOverrideProperty, cred->metered); |
| dict.Set(shill::kPasspointCredentialsAndroidPackageNameProperty, |
| cred->package_name); |
| if (cred->friendly_name.has_value()) { |
| dict.Set(shill::kPasspointCredentialsFriendlyNameProperty, |
| cred->friendly_name.value()); |
| } |
| dict.Set(shill::kPasspointCredentialsExpirationTimeMillisecondsProperty, |
| base::NumberToString(cred->subscription_expiration_time_ms)); |
| |
| std::move(callback).Run(std::move(dict)); |
| } |
| |
| // Set up proxy configuration. If proxy auto discovery pac url is available, |
| // we set up proxy auto discovery pac url, otherwise we set up |
| // host, port and exclusion list. |
| base::Value::Dict ArcNetHostImpl::TranslateProxyConfiguration( |
| const arc::mojom::ArcProxyInfoPtr& http_proxy) { |
| base::Value::Dict proxy_dict; |
| if (http_proxy->is_pac_url_proxy()) { |
| proxy_dict.Set(onc::proxy::kType, onc::proxy::kPAC); |
| proxy_dict.Set(onc::proxy::kPAC, |
| http_proxy->get_pac_url_proxy()->pac_url.spec()); |
| } else { |
| base::Value::Dict manual; |
| manual.Set(onc::proxy::kHost, http_proxy->get_manual_proxy()->host); |
| manual.Set(onc::proxy::kPort, http_proxy->get_manual_proxy()->port); |
| manual.Set(onc::proxy::kExcludeDomains, |
| TranslateStringListToValue( |
| std::move(http_proxy->get_manual_proxy()->exclusion_list))); |
| proxy_dict.Set(onc::proxy::kType, onc::proxy::kManual); |
| proxy_dict.Set(onc::proxy::kManual, std::move(manual)); |
| } |
| return proxy_dict; |
| } |
| |
| void ArcNetHostImpl::AddPasspointCredentials( |
| mojom::PasspointCredentialsPtr credentials) { |
| TranslatePasspointCredentialsToDict( |
| std::move(credentials), |
| base::BindOnce(&ArcNetHostImpl::AddPasspointCredentialsWithProperties, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void ArcNetHostImpl::AddPasspointCredentialsWithProperties( |
| base::Value::Dict properties) { |
| if (properties.empty()) { |
| NET_LOG(ERROR) << "Failed to translate PasspointCredentials properties"; |
| return; |
| } |
| |
| const auto* profile = GetNetworkProfile(); |
| if (!profile || profile->path.empty()) { |
| NET_LOG(ERROR) << "Unable to get network profile path"; |
| return; |
| } |
| |
| ash::ShillManagerClient::Get()->AddPasspointCredentials( |
| dbus::ObjectPath(profile->path), base::Value(std::move(properties)), |
| base::DoNothing(), |
| base::BindOnce(&AddPasspointCredentialsFailureCallback)); |
| return; |
| } |
| |
| void ArcNetHostImpl::RemovePasspointCredentials( |
| mojom::PasspointRemovalPropertiesPtr properties) { |
| if (!properties) { |
| NET_LOG(ERROR) << "Empty passpoint removal properties"; |
| return; |
| } |
| |
| const auto* profile = GetNetworkProfile(); |
| if (!profile || profile->path.empty()) { |
| NET_LOG(ERROR) << "Unable to get network profile path"; |
| return; |
| } |
| |
| base::Value::Dict shill_properties; |
| if (properties->fqdn.has_value()) { |
| shill_properties.Set(shill::kPasspointCredentialsFQDNProperty, |
| properties->fqdn.value()); |
| } |
| if (properties->package_name.has_value()) { |
| shill_properties.Set(shill::kPasspointCredentialsAndroidPackageNameProperty, |
| properties->package_name.value()); |
| } |
| |
| ash::ShillManagerClient::Get()->RemovePasspointCredentials( |
| dbus::ObjectPath(profile->path), base::Value(std::move(shill_properties)), |
| base::DoNothing(), |
| base::BindOnce(&RemovePasspointCredentialsFailureCallback)); |
| |
| return; |
| } |
| |
| 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::DisconnectHostVpn() { |
| const ash::NetworkState* default_network = |
| GetShillBackedNetwork(GetStateHandler()->DefaultNetwork()); |
| if (default_network && default_network->type() == shill::kTypeVPN && |
| default_network->GetVpnProviderType() != shill::kProviderArcVpn) { |
| GetNetworkConnectionHandler()->DisconnectNetwork( |
| default_network->path(), base::BindOnce(&HostVpnSuccessCallback), |
| base::BindOnce(&HostVpnErrorCallback, "disconnecting host VPN")); |
| } |
| } |
| |
| void ArcNetHostImpl::DisconnectArcVpn() { |
| arc_vpn_service_path_.clear(); |
| |
| auto* net_instance = ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->net(), |
| DisconnectAndroidVpn); |
| if (!net_instance) |
| 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. |
| DisconnectArcVpn(); |
| } |
| |
| void ArcNetHostImpl::NetworkConnectionStateChanged( |
| const ash::NetworkState* network) { |
| const auto* 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. |
| DisconnectArcVpn(); |
| } |
| |
| void ArcNetHostImpl::NetworkPropertiesUpdated( |
| const ash::NetworkState* network) { |
| if (!IsActiveNetworkState(network)) |
| return; |
| |
| ash::NetworkHandler::Get() |
| ->network_configuration_handler() |
| ->GetShillProperties( |
| network->path(), |
| base::BindOnce(&ArcNetHostImpl::ReceiveShillProperties, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void ArcNetHostImpl::ReceiveShillProperties( |
| const std::string& service_path, |
| absl::optional<base::Value> shill_properties) { |
| if (!shill_properties) { |
| NET_LOG(ERROR) << "Failed to get shill Service properties for " |
| << service_path; |
| return; |
| } |
| |
| // Ignore properties received after the network has disconnected. |
| const auto* network = GetStateHandler()->GetNetworkState(service_path); |
| if (!IsActiveNetworkState(network)) |
| return; |
| |
| shill_network_properties_[service_path] = std::move(*shill_properties); |
| |
| // Get patchpanel devices and update active networks. |
| ash::PatchPanelClient::Get()->GetDevices(base::BindOnce( |
| &ArcNetHostImpl::UpdateActiveNetworks, weak_factory_.GetWeakPtr())); |
| } |
| |
| void ArcNetHostImpl::UpdateActiveNetworks( |
| const std::vector<patchpanel::NetworkDevice>& devices) { |
| auto* net_instance = ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->net(), |
| ActiveNetworksChanged); |
| if (!net_instance) |
| return; |
| |
| net_instance->ActiveNetworksChanged( |
| TranslateNetworkStates(arc_vpn_service_path_, GetHostActiveNetworks(), |
| shill_network_properties_, devices)); |
| } |
| |
| void ArcNetHostImpl::NetworkListChanged() { |
| // Forget properties of disconnected networks |
| base::EraseIf(shill_network_properties_, [](const auto& entry) { |
| return !IsActiveNetworkState( |
| GetStateHandler()->GetNetworkState(entry.first)); |
| }); |
| const auto active_networks = GetHostActiveNetworks(); |
| // If there is no active networks, send an explicit ActiveNetworksChanged |
| // event to ARC and skip updating Shill properties. |
| if (active_networks.empty()) { |
| UpdateActiveNetworks({} /* devices */); |
| return; |
| } |
| for (const auto* network : active_networks) |
| NetworkPropertiesUpdated(network); |
| } |
| |
| void ArcNetHostImpl::StartLohs(mojom::LohsConfigPtr config, |
| StartLohsCallback callback) { |
| NET_LOG(USER) << "Starting LOHS"; |
| base::Value dict(base::Value::Type::DICTIONARY); |
| |
| if (config->hexssid.empty()) { |
| NET_LOG(ERROR) << "Cannot create local only hotspot without hex ssid"; |
| std::move(callback).Run(arc::mojom::LohsStatus::kErrorGeneric); |
| return; |
| } |
| dict.GetDict().Set(shill::kTetheringConfSSIDProperty, |
| base::Value(config->hexssid)); |
| |
| if (config->band != arc::mojom::WifiBand::k2Ghz) { |
| // TODO(b/257880335): Support 5Ghz band as well |
| NET_LOG(ERROR) << "Unsupported band for LOHS: " << config->band |
| << "; can only support 2.4GHz"; |
| // TODO(b/259162524): Change this to a more specific "invalid argument" |
| // error |
| std::move(callback).Run(arc::mojom::LohsStatus::kErrorGeneric); |
| return; |
| } |
| dict.GetDict().Set(shill::kTetheringConfBandProperty, |
| base::Value(shill::kBand2GHz)); |
| |
| if (config->security_type != arc::mojom::SecurityType::WPA_PSK) { |
| NET_LOG(ERROR) << "Unsupported security for LOHS: " << config->security_type |
| << "; can only support WPA_PSK"; |
| // TODO(b/259162524): Change this to a more specific "invalid argument" |
| // error |
| std::move(callback).Run(arc::mojom::LohsStatus::kErrorGeneric); |
| return; |
| } |
| if (!config->passphrase.has_value()) { |
| NET_LOG(ERROR) << "Cannot create local only hotspot without password"; |
| // TODO(b/259162524): Change this to a more specific "invalid argument" |
| // error |
| std::move(callback).Run(arc::mojom::LohsStatus::kErrorGeneric); |
| return; |
| } |
| dict.GetDict().Set(shill::kTetheringConfSecurityProperty, |
| base::Value(shill::kSecurityWpa2)); |
| dict.GetDict().Set(shill::kTetheringConfPassphraseProperty, |
| base::Value(config->passphrase.value())); |
| |
| NET_LOG(USER) << "Set Shill Manager property: " << shill::kLOHSConfigProperty |
| << ": " << dict; |
| auto callback_split = base::SplitOnceCallback(std::move(callback)); |
| ash::ShillManagerClient::Get()->SetProperty( |
| shill::kLOHSConfigProperty, dict, |
| base::BindOnce(&SetLohsConfigPropertySuccessCallback, |
| std::move(callback_split.first)), |
| base::BindOnce(&SetLohsConfigPropertyFailureCallback, |
| std::move(callback_split.second))); |
| } |
| |
| void ArcNetHostImpl::StopLohs() { |
| NET_LOG(USER) << "Stopping LOHS"; |
| ash::ShillManagerClient::Get()->SetLOHSEnabled( |
| false /* enabled */, base::DoNothing(), |
| base::BindOnce(&StopLohsFailureCallback)); |
| } |
| |
| void ArcNetHostImpl::OnShuttingDown() { |
| DCHECK(observing_network_state_); |
| GetStateHandler()->RemoveObserver(this, FROM_HERE); |
| GetNetworkConnectionHandler()->RemoveObserver(this); |
| observing_network_state_ = false; |
| } |
| |
| } // namespace arc |