// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/sync_wifi/local_network_collector_impl.h"
#include "base/barrier_closure.h"
#include "base/uuid.h"
#include "chromeos/ash/components/dbus/shill/shill_service_client.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_metadata_store.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/sync_wifi/network_eligibility_checker.h"
#include "chromeos/ash/components/sync_wifi/network_identifier.h"
#include "chromeos/ash/components/sync_wifi/network_type_conversions.h"
#include "chromeos/services/network_config/public/mojom/network_types.mojom-shared.h"
#include "components/device_event_log/device_event_log.h"
#include "components/sync/protocol/wifi_configuration_specifics.pb.h"
#include "dbus/object_path.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
namespace ash::network_config {
namespace mojom = ::chromeos::network_config::mojom;
}
namespace ash::sync_wifi {
namespace {
dbus::ObjectPath GetServicePathForGuid(const std::string& guid) {
const NetworkState* state =
NetworkHandler::Get()->network_state_handler()->GetNetworkStateFromGuid(
guid);
if (!state) {
return dbus::ObjectPath();
}
return dbus::ObjectPath(state->path());
}
bool IsAutoconnectUnspecified(
const sync_pb::WifiConfigurationSpecifics& proto) {
return !proto.has_automatically_connect() ||
proto.automatically_connect() ==
sync_pb::
WifiConfigurationSpecifics_AutomaticallyConnectOption_AUTOMATICALLY_CONNECT_UNSPECIFIED;
}
} // namespace
LocalNetworkCollectorImpl::LocalNetworkCollectorImpl(
network_config::mojom::CrosNetworkConfig* cros_network_config,
SyncedNetworkMetricsLogger* metrics_recorder)
: cros_network_config_(cros_network_config),
metrics_recorder_(metrics_recorder) {
cros_network_config_->AddObserver(
cros_network_config_observer_receiver_.BindNewPipeAndPassRemote());
// Load the current list of networks.
OnNetworkStateListChanged();
}
LocalNetworkCollectorImpl::~LocalNetworkCollectorImpl() = default;
void LocalNetworkCollectorImpl::GetAllSyncableNetworks(
ProtoListCallback callback) {
if (!is_mojo_networks_loaded_) {
after_networks_are_loaded_callback_queue_.push(
base::BindOnce(&LocalNetworkCollectorImpl::GetAllSyncableNetworks,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
return;
}
std::string request_guid = InitializeRequest();
request_guid_to_list_callback_[request_guid] = std::move(callback);
int count = 0;
for (const network_config::mojom::NetworkStatePropertiesPtr& network :
mojo_networks_) {
if (!IsEligible(network)) {
continue;
}
request_guid_to_in_flight_networks_[request_guid].insert(
NetworkIdentifier::FromMojoNetwork(network));
StartGetNetworkDetails(network.get(), request_guid);
count++;
}
if (!count) {
OnRequestFinished(request_guid);
}
}
void LocalNetworkCollectorImpl::RecordZeroNetworksEligibleForSync() {
if (has_logged_zero_eligible_networks_metric_) {
return;
}
if (!is_mojo_networks_loaded_) {
after_networks_are_loaded_callback_queue_.push(base::BindOnce(
&LocalNetworkCollectorImpl::RecordZeroNetworksEligibleForSync,
weak_ptr_factory_.GetWeakPtr()));
return;
}
base::flat_set<NetworkEligibilityStatus>
network_eligible_for_sync_status_codes;
NetworkEligibilityStatus network_eligible_for_sync_status;
for (const network_config::mojom::NetworkStatePropertiesPtr& network :
mojo_networks_) {
if (!network ||
network->type != network_config::mojom::NetworkType::kWiFi) {
continue;
}
network_eligible_for_sync_status = GetNetworkEligibilityStatus(
network->guid, network->connectable,
network->type_state->get_wifi()->hidden_ssid,
network->type_state->get_wifi()->security, network->source,
/*log_result=*/false);
network_eligible_for_sync_status_codes.insert(
network_eligible_for_sync_status);
}
metrics_recorder_->RecordZeroNetworksEligibleForSync(
network_eligible_for_sync_status_codes);
has_logged_zero_eligible_networks_metric_ = true;
}
void LocalNetworkCollectorImpl::GetSyncableNetwork(const std::string& guid,
ProtoCallback callback) {
const network_config::mojom::NetworkStateProperties* network = nullptr;
for (const network_config::mojom::NetworkStatePropertiesPtr& n :
mojo_networks_) {
if (n->guid == guid) {
if (IsEligible(n)) {
network = n.get();
}
break;
}
}
if (!network) {
std::move(callback).Run(std::nullopt);
return;
}
std::string request_guid = InitializeRequest();
request_guid_to_single_callback_[request_guid] = std::move(callback);
StartGetNetworkDetails(network, request_guid);
}
std::optional<NetworkIdentifier>
LocalNetworkCollectorImpl::GetNetworkIdentifierFromGuid(
const std::string& guid) {
for (const network_config::mojom::NetworkStatePropertiesPtr& network :
mojo_networks_) {
if (network->guid == guid) {
return NetworkIdentifier::FromMojoNetwork(network);
}
}
return std::nullopt;
}
void LocalNetworkCollectorImpl::SetNetworkMetadataStore(
base::WeakPtr<NetworkMetadataStore> network_metadata_store) {
network_metadata_store_ = network_metadata_store;
}
std::string LocalNetworkCollectorImpl::InitializeRequest() {
std::string request_guid = base::Uuid::GenerateRandomV4().AsLowercaseString();
request_guid_to_complete_protos_[request_guid] =
std::vector<sync_pb::WifiConfigurationSpecifics>();
request_guid_to_in_flight_networks_[request_guid] =
base::flat_set<NetworkIdentifier>();
return request_guid;
}
bool LocalNetworkCollectorImpl::IsEligible(
const network_config::mojom::NetworkStatePropertiesPtr& network) {
if (!network || network->type != network_config::mojom::NetworkType::kWiFi) {
return false;
}
const network_config::mojom::WiFiStatePropertiesPtr& wifi_properties =
network->type_state->get_wifi();
return IsEligibleForSync(network->guid, network->connectable,
wifi_properties->hidden_ssid,
wifi_properties->security, network->source,
/*log_result=*/true);
}
void LocalNetworkCollectorImpl::StartGetNetworkDetails(
const network_config::mojom::NetworkStateProperties* network,
const std::string& request_guid) {
sync_pb::WifiConfigurationSpecifics proto;
proto.set_hex_ssid(network->type_state->get_wifi()->hex_ssid);
proto.set_security_type(
SecurityTypeProtoFromMojo(network->type_state->get_wifi()->security));
base::TimeDelta timestamp =
network_metadata_store_->GetLastConnectedTimestamp(network->guid);
proto.set_last_connected_timestamp(timestamp.InMilliseconds());
cros_network_config_->GetManagedProperties(
network->guid,
base::BindOnce(&LocalNetworkCollectorImpl::OnGetManagedPropertiesResult,
weak_ptr_factory_.GetWeakPtr(), proto, request_guid));
}
void LocalNetworkCollectorImpl::OnGetManagedPropertiesResult(
sync_pb::WifiConfigurationSpecifics proto,
const std::string& request_guid,
network_config::mojom::ManagedPropertiesPtr properties) {
if (!properties) {
NET_LOG(ERROR) << "GetManagedProperties failed.";
OnNetworkFinished(NetworkIdentifier::FromProto(proto), request_guid);
return;
}
proto.set_automatically_connect(AutomaticallyConnectProtoFromMojo(
properties->type_properties->get_wifi()->auto_connect));
proto.set_is_preferred(IsPreferredProtoFromMojo(properties->priority));
// TODO(crbug/1128692): Restore support for the metered property when mojo
// networks track the "Automatic" state.
bool is_proxy_modified =
network_metadata_store_->GetIsFieldExternallyModified(
properties->guid, shill::kProxyConfigProperty);
sync_pb::WifiConfigurationSpecifics_ProxyConfiguration proxy_config =
ProxyConfigurationProtoFromMojo(properties->proxy_settings,
/*is_unspecified=*/is_proxy_modified);
proto.mutable_proxy_configuration()->CopyFrom(proxy_config);
bool is_dns_externally_modified =
network_metadata_store_->GetIsFieldExternallyModified(
properties->guid, shill::kNameServersProperty);
if (properties->static_ip_config &&
properties->static_ip_config->name_servers &&
(properties->source == network_config::mojom::OncSource::kUser ||
!is_dns_externally_modified)) {
proto.set_dns_option(
sync_pb::WifiConfigurationSpecifics_DnsOption_DNS_OPTION_CUSTOM);
for (const std::string& nameserver :
properties->static_ip_config->name_servers->active_value) {
proto.add_custom_dns(nameserver);
}
} else if (properties->source == network_config::mojom::OncSource::kDevice &&
is_dns_externally_modified) {
proto.set_dns_option(
sync_pb::WifiConfigurationSpecifics_DnsOption_DNS_OPTION_UNSPECIFIED);
} else {
proto.set_dns_option(
sync_pb::WifiConfigurationSpecifics_DnsOption_DNS_OPTION_DEFAULT_DHCP);
}
ShillServiceClient::Get()->GetWiFiPassphrase(
GetServicePathForGuid(properties->guid),
base::BindOnce(&LocalNetworkCollectorImpl::OnGetWiFiPassphraseResult,
weak_ptr_factory_.GetWeakPtr(), proto, request_guid),
base::BindOnce(&LocalNetworkCollectorImpl::OnGetWiFiPassphraseError,
weak_ptr_factory_.GetWeakPtr(),
NetworkIdentifier::FromProto(proto), request_guid));
}
void LocalNetworkCollectorImpl::OnGetWiFiPassphraseResult(
sync_pb::WifiConfigurationSpecifics proto,
const std::string& request_guid,
const std::string& passphrase) {
proto.set_passphrase(passphrase);
NetworkIdentifier id = NetworkIdentifier::FromProto(proto);
request_guid_to_complete_protos_[request_guid].push_back(std::move(proto));
OnNetworkFinished(id, request_guid);
}
void LocalNetworkCollectorImpl::OnGetWiFiPassphraseError(
const NetworkIdentifier& id,
const std::string& request_guid,
const std::string& error_name,
const std::string& error_message) {
NET_LOG(ERROR) << "Failed to get passphrase for " << id.SerializeToString()
<< " Error: " << error_name << " Details: " << error_message;
OnNetworkFinished(id, request_guid);
}
void LocalNetworkCollectorImpl::OnNetworkFinished(
const NetworkIdentifier& id,
const std::string& request_guid) {
request_guid_to_in_flight_networks_[request_guid].erase(id);
if (request_guid_to_in_flight_networks_[request_guid].empty()) {
OnRequestFinished(request_guid);
}
}
void LocalNetworkCollectorImpl::OnRequestFinished(
const std::string& request_guid) {
DCHECK(request_guid_to_in_flight_networks_[request_guid].empty());
if (request_guid_to_single_callback_[request_guid]) {
std::vector<sync_pb::WifiConfigurationSpecifics>& list =
request_guid_to_complete_protos_[request_guid];
DCHECK(list.size() <= 1);
std::optional<sync_pb::WifiConfigurationSpecifics> result;
if (list.size() == 1) {
result = list[0];
}
std::move(request_guid_to_single_callback_[request_guid]).Run(result);
request_guid_to_single_callback_.erase(request_guid);
} else {
ProtoListCallback callback =
std::move(request_guid_to_list_callback_[request_guid]);
DCHECK(callback);
std::move(callback).Run(request_guid_to_complete_protos_[request_guid]);
request_guid_to_list_callback_.erase(request_guid);
}
request_guid_to_complete_protos_.erase(request_guid);
request_guid_to_in_flight_networks_.erase(request_guid);
}
void LocalNetworkCollectorImpl::OnNetworkStateListChanged() {
if (!NetworkHandler::Get()
->network_state_handler()
->IsProfileNetworksLoaded()) {
is_mojo_networks_loaded_ = false;
return;
}
cros_network_config_->GetNetworkStateList(
network_config::mojom::NetworkFilter::New(
network_config::mojom::FilterType::kConfigured,
network_config::mojom::NetworkType::kWiFi,
/*limit=*/0),
base::BindOnce(&LocalNetworkCollectorImpl::OnGetNetworkList,
weak_ptr_factory_.GetWeakPtr()));
}
void LocalNetworkCollectorImpl::OnGetNetworkList(
std::vector<network_config::mojom::NetworkStatePropertiesPtr> networks) {
mojo_networks_ = std::move(networks);
is_mojo_networks_loaded_ = true;
while (!after_networks_are_loaded_callback_queue_.empty()) {
std::move(after_networks_are_loaded_callback_queue_.front()).Run();
after_networks_are_loaded_callback_queue_.pop();
}
}
void LocalNetworkCollectorImpl::ExecuteAfterNetworksLoaded(
base::OnceClosure callback) {
if (is_mojo_networks_loaded_) {
std::move(callback).Run();
return;
}
after_networks_are_loaded_callback_queue_.push(std::move(callback));
}
void LocalNetworkCollectorImpl::FixAutoconnect(
std::vector<sync_pb::WifiConfigurationSpecifics> protos,
base::OnceClosure callback) {
std::vector<std::string> guids_to_fix;
for (const sync_pb::WifiConfigurationSpecifics& proto : protos) {
// b/180854680 only affected networks with autoconnect unset/unspecified.
if (!IsAutoconnectUnspecified(proto)) {
continue;
}
network_config::mojom::NetworkStatePropertiesPtr network =
GetNetworkFromProto(proto);
if (!network) {
// A synced network that is shared could have been removed by another user
continue;
}
guids_to_fix.push_back(network->guid);
}
if (guids_to_fix.empty()) {
std::move(callback).Run();
return;
}
fix_autoconnect_callback_ =
base::BarrierClosure(guids_to_fix.size(), std::move(callback));
for (const std::string& guid : guids_to_fix) {
cros_network_config_->GetManagedProperties(
guid,
base::BindOnce(&LocalNetworkCollectorImpl::EnableAutoconnectIfDisabled,
weak_ptr_factory_.GetWeakPtr()));
}
}
void LocalNetworkCollectorImpl::OnFixAutoconnectComplete(
bool success,
const std::string& error) {
if (!fix_autoconnect_callback_.is_null()) {
fix_autoconnect_callback_.Run();
return;
}
NOTREACHED_IN_MIGRATION();
}
network_config::mojom::NetworkStatePropertiesPtr
LocalNetworkCollectorImpl::GetNetworkFromProto(
const sync_pb::WifiConfigurationSpecifics& proto) {
auto id = NetworkIdentifier::FromProto(proto);
network_config::mojom::NetworkStatePropertiesPtr network;
for (const network_config::mojom::NetworkStatePropertiesPtr& n :
mojo_networks_) {
if (id == NetworkIdentifier::FromMojoNetwork(n)) {
return n->Clone();
}
}
return nullptr;
}
void LocalNetworkCollectorImpl::EnableAutoconnectIfDisabled(
network_config::mojom::ManagedPropertiesPtr properties) {
if (!properties ||
properties->type != network_config::mojom::NetworkType::kWiFi) {
OnFixAutoconnectComplete(/*success=*/true, /*error=*/std::string());
return;
}
if (properties->type_properties->get_wifi()->auto_connect &&
properties->type_properties->get_wifi()->auto_connect->active_value) {
OnFixAutoconnectComplete(/*success=*/true, /*error=*/std::string());
return;
}
NET_LOG(EVENT) << "Fixing autoconnect for "
<< NetworkGuidId(properties->guid);
auto config = network_config::mojom::ConfigProperties::New();
config->type_config =
network_config::mojom::NetworkTypeConfigProperties::NewWifi(
network_config::mojom::WiFiConfigProperties::New());
config->auto_connect = network_config::mojom::AutoConnectConfig::New(true);
cros_network_config_->SetProperties(
properties->guid, std::move(config),
base::BindOnce(&LocalNetworkCollectorImpl::OnFixAutoconnectComplete,
weak_ptr_factory_.GetWeakPtr()));
}
} // namespace ash::sync_wifi