// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chromeos/network/network_configuration_handler.h" #include "base/bind.h" #include "base/format_macros.h" #include "base/guid.h" #include "base/json/json_writer.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "base/values.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/shill_manager_client.h" #include "chromeos/dbus/shill_profile_client.h" #include "chromeos/dbus/shill_service_client.h" #include "chromeos/network/network_device_handler.h" #include "chromeos/network/network_state.h" #include "chromeos/network/network_state_handler.h" #include "chromeos/network/shill_property_util.h" #include "components/device_event_log/device_event_log.h" #include "dbus/object_path.h" #include "third_party/cros_system_api/dbus/service_constants.h" namespace chromeos { namespace { // Strip surrounding "" from keys (if present). std::string StripQuotations(const std::string& in_str) { size_t len = in_str.length(); if (len >= 2 && in_str[0] == '"' && in_str[len - 1] == '"') return in_str.substr(1, len - 2); return in_str; } void InvokeErrorCallback(const std::string& service_path, const network_handler::ErrorCallback& error_callback, const std::string& error_name) { std::string error_msg = "Config Error: " + error_name; NET_LOG(ERROR) << error_msg << ": " << service_path; network_handler::RunErrorCallback(error_callback, service_path, error_name, error_msg); } void SetNetworkProfileErrorCallback( const std::string& service_path, const std::string& profile_path, const network_handler::ErrorCallback& error_callback, const std::string& dbus_error_name, const std::string& dbus_error_message) { network_handler::ShillErrorCallbackFunction( "Config.SetNetworkProfile Failed: " + profile_path, service_path, error_callback, dbus_error_name, dbus_error_message); } void LogConfigProperties(const std::string& desc, const std::string& path, const base::DictionaryValue& properties) { for (base::DictionaryValue::Iterator iter(properties); !iter.IsAtEnd(); iter.Advance()) { std::string v = "******"; if (shill_property_util::IsLoggableShillProperty(iter.key())) base::JSONWriter::Write(iter.value(), &v); NET_LOG(USER) << desc << ": " << path + "." + iter.key() + "=" + v; } } } // namespace // Helper class to request from Shill the profile entries associated with a // Service and delete the service from each profile. Triggers either // |callback| on success or |error_callback| on failure, and calls // |handler|->ProfileEntryDeleterCompleted() on completion to delete itself. class NetworkConfigurationHandler::ProfileEntryDeleter : public base::SupportsWeakPtr<ProfileEntryDeleter> { public: ProfileEntryDeleter(NetworkConfigurationHandler* handler, const std::string& service_path, const std::string& guid, NetworkConfigurationObserver::Source source, const base::Closure& callback, const network_handler::ErrorCallback& error_callback) : owner_(handler), service_path_(service_path), guid_(guid), source_(source), callback_(callback), error_callback_(error_callback) {} void Run() { DBusThreadManager::Get() ->GetShillServiceClient() ->GetLoadableProfileEntries( dbus::ObjectPath(service_path_), base::Bind(&ProfileEntryDeleter::GetProfileEntriesToDeleteCallback, AsWeakPtr())); } private: void GetProfileEntriesToDeleteCallback( DBusMethodCallStatus call_status, const base::DictionaryValue& profile_entries) { if (call_status != DBUS_METHOD_CALL_SUCCESS) { InvokeErrorCallback(service_path_, error_callback_, "GetLoadableProfileEntriesFailed"); // ProfileEntryDeleterCompleted will delete this. owner_->ProfileEntryDeleterCompleted(service_path_, guid_, source_, false /* failed */); return; } for (base::DictionaryValue::Iterator iter(profile_entries); !iter.IsAtEnd(); iter.Advance()) { std::string profile_path = StripQuotations(iter.key()); std::string entry_path; iter.value().GetAsString(&entry_path); if (profile_path.empty() || entry_path.empty()) { NET_LOG(ERROR) << "Failed to parse Profile Entry: " << profile_path << ": " << entry_path; continue; } if (profile_delete_entries_.count(profile_path) != 0) { NET_LOG(ERROR) << "Multiple Profile Entries: " << profile_path << ": " << entry_path; continue; } NET_LOG(DEBUG) << "Delete Profile Entry: " << profile_path << ": " << entry_path; profile_delete_entries_[profile_path] = entry_path; DBusThreadManager::Get()->GetShillProfileClient()->DeleteEntry( dbus::ObjectPath(profile_path), entry_path, base::Bind(&ProfileEntryDeleter::ProfileEntryDeletedCallback, AsWeakPtr(), profile_path, entry_path), base::Bind(&ProfileEntryDeleter::ShillErrorCallback, AsWeakPtr(), profile_path, entry_path)); } } void ProfileEntryDeletedCallback(const std::string& profile_path, const std::string& entry) { NET_LOG(DEBUG) << "Profile Entry Deleted: " << profile_path << ": " << entry; profile_delete_entries_.erase(profile_path); if (!profile_delete_entries_.empty()) return; // Run the callback if this is the last pending deletion. if (!callback_.is_null()) callback_.Run(); // ProfileEntryDeleterCompleted will delete this. owner_->ProfileEntryDeleterCompleted(service_path_, guid_, source_, true /* success */); } void ShillErrorCallback(const std::string& profile_path, const std::string& entry, const std::string& dbus_error_name, const std::string& dbus_error_message) { // Any Shill Error triggers a failure / error. network_handler::ShillErrorCallbackFunction( "GetLoadableProfileEntries Failed", profile_path, error_callback_, dbus_error_name, dbus_error_message); // Delete this even if there are pending deletions; any callbacks will // safely become no-ops (by invalidating the WeakPtrs). owner_->ProfileEntryDeleterCompleted(service_path_, guid_, source_, false /* failed */); } NetworkConfigurationHandler* owner_; // Unowned std::string service_path_; std::string guid_; NetworkConfigurationObserver::Source source_; base::Closure callback_; network_handler::ErrorCallback error_callback_; // Map of pending profile entry deletions, indexed by profile path. std::map<std::string, std::string> profile_delete_entries_; DISALLOW_COPY_AND_ASSIGN(ProfileEntryDeleter); }; // NetworkConfigurationHandler void NetworkConfigurationHandler::AddObserver( NetworkConfigurationObserver* observer) { observers_.AddObserver(observer); } void NetworkConfigurationHandler::RemoveObserver( NetworkConfigurationObserver* observer) { observers_.RemoveObserver(observer); } void NetworkConfigurationHandler::GetShillProperties( const std::string& service_path, const network_handler::DictionaryResultCallback& callback, const network_handler::ErrorCallback& error_callback) { NET_LOG(USER) << "GetShillProperties: " << service_path; DBusThreadManager::Get()->GetShillServiceClient()->GetProperties( dbus::ObjectPath(service_path), base::Bind(&NetworkConfigurationHandler::GetPropertiesCallback, AsWeakPtr(), callback, error_callback, service_path)); } void NetworkConfigurationHandler::SetShillProperties( const std::string& service_path, const base::DictionaryValue& shill_properties, NetworkConfigurationObserver::Source source, const base::Closure& callback, const network_handler::ErrorCallback& error_callback) { if (shill_properties.empty()) { if (!callback.is_null()) callback.Run(); return; } NET_LOG(USER) << "SetShillProperties: " << service_path; scoped_ptr<base::DictionaryValue> properties_to_set( shill_properties.DeepCopy()); // Make sure that the GUID is saved to Shill when setting properties. std::string guid; properties_to_set->GetStringWithoutPathExpansion(shill::kGuidProperty, &guid); if (guid.empty()) { const NetworkState* network_state = network_state_handler_->GetNetworkState(service_path); guid = network_state ? network_state->guid() : base::GenerateGUID(); properties_to_set->SetStringWithoutPathExpansion(shill::kGuidProperty, guid); } LogConfigProperties("SetProperty", service_path, *properties_to_set); scoped_ptr<base::DictionaryValue> properties_copy( properties_to_set->DeepCopy()); DBusThreadManager::Get()->GetShillServiceClient()->SetProperties( dbus::ObjectPath(service_path), *properties_to_set, base::Bind(&NetworkConfigurationHandler::SetPropertiesSuccessCallback, AsWeakPtr(), service_path, base::Passed(&properties_copy), source, callback), base::Bind(&NetworkConfigurationHandler::SetPropertiesErrorCallback, AsWeakPtr(), service_path, error_callback)); // If we set the StaticIPConfig property, request an IP config refresh // after calling SetProperties. if (properties_to_set->HasKey(shill::kStaticIPConfigProperty)) RequestRefreshIPConfigs(service_path); } void NetworkConfigurationHandler::ClearShillProperties( const std::string& service_path, const std::vector<std::string>& names, const base::Closure& callback, const network_handler::ErrorCallback& error_callback) { if (names.empty()) { if (!callback.is_null()) callback.Run(); return; } NET_LOG(USER) << "ClearShillProperties: " << service_path; for (std::vector<std::string>::const_iterator iter = names.begin(); iter != names.end(); ++iter) { NET_LOG(DEBUG) << "ClearProperty: " << service_path << "." << *iter; } DBusThreadManager::Get()->GetShillServiceClient()->ClearProperties( dbus::ObjectPath(service_path), names, base::Bind(&NetworkConfigurationHandler::ClearPropertiesSuccessCallback, AsWeakPtr(), service_path, names, callback), base::Bind(&NetworkConfigurationHandler::ClearPropertiesErrorCallback, AsWeakPtr(), service_path, error_callback)); } void NetworkConfigurationHandler::CreateShillConfiguration( const base::DictionaryValue& shill_properties, NetworkConfigurationObserver::Source source, const network_handler::StringResultCallback& callback, const network_handler::ErrorCallback& error_callback) { ShillManagerClient* manager = DBusThreadManager::Get()->GetShillManagerClient(); std::string type; shill_properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type); DCHECK(!type.empty()); std::string network_id = shill_property_util::GetNetworkIdFromProperties(shill_properties); if (NetworkTypePattern::Ethernet().MatchesType(type)) { InvokeErrorCallback(network_id, error_callback, "ConfigureServiceForProfile: Invalid type: " + type); return; } scoped_ptr<base::DictionaryValue> properties_to_set( shill_properties.DeepCopy()); NET_LOG(USER) << "CreateShillConfiguration: " << type << ": " << network_id; std::string profile_path; properties_to_set->GetStringWithoutPathExpansion(shill::kProfileProperty, &profile_path); DCHECK(!profile_path.empty()); // Make sure that the GUID is saved to Shill when configuring networks. std::string guid; properties_to_set->GetStringWithoutPathExpansion(shill::kGuidProperty, &guid); if (guid.empty()) { guid = base::GenerateGUID(); properties_to_set->SetStringWithoutPathExpansion( ::onc::network_config::kGUID, guid); } LogConfigProperties("Configure", type, *properties_to_set); scoped_ptr<base::DictionaryValue> properties_copy( properties_to_set->DeepCopy()); manager->ConfigureServiceForProfile( dbus::ObjectPath(profile_path), *properties_to_set, base::Bind(&NetworkConfigurationHandler::RunCreateNetworkCallback, AsWeakPtr(), profile_path, source, base::Passed(&properties_copy), callback), base::Bind(&network_handler::ShillErrorCallbackFunction, "Config.CreateConfiguration Failed", "", error_callback)); } void NetworkConfigurationHandler::RemoveConfiguration( const std::string& service_path, NetworkConfigurationObserver::Source source, const base::Closure& callback, const network_handler::ErrorCallback& error_callback) { // Service.Remove is not reliable. Instead, request the profile entries // for the service and remove each entry. if (ContainsKey(profile_entry_deleters_, service_path)) { InvokeErrorCallback(service_path, error_callback, "RemoveConfigurationInProgress"); return; } std::string guid; const NetworkState* network_state = network_state_handler_->GetNetworkState(service_path); if (network_state) guid = network_state->guid(); NET_LOG(USER) << "Remove Configuration: " << service_path; ProfileEntryDeleter* deleter = new ProfileEntryDeleter( this, service_path, guid, source, callback, error_callback); profile_entry_deleters_[service_path] = deleter; deleter->Run(); } void NetworkConfigurationHandler::SetNetworkProfile( const std::string& service_path, const std::string& profile_path, NetworkConfigurationObserver::Source source, const base::Closure& callback, const network_handler::ErrorCallback& error_callback) { NET_LOG(USER) << "SetNetworkProfile: " << service_path << ": " << profile_path; base::StringValue profile_path_value(profile_path); DBusThreadManager::Get()->GetShillServiceClient()->SetProperty( dbus::ObjectPath(service_path), shill::kProfileProperty, profile_path_value, base::Bind(&NetworkConfigurationHandler::SetNetworkProfileCompleted, AsWeakPtr(), service_path, profile_path, source, callback), base::Bind(&SetNetworkProfileErrorCallback, service_path, profile_path, error_callback)); } // NetworkConfigurationHandler Private methods NetworkConfigurationHandler::NetworkConfigurationHandler() : network_state_handler_(NULL) { } NetworkConfigurationHandler::~NetworkConfigurationHandler() { STLDeleteContainerPairSecondPointers(profile_entry_deleters_.begin(), profile_entry_deleters_.end()); } void NetworkConfigurationHandler::Init( NetworkStateHandler* network_state_handler, NetworkDeviceHandler* network_device_handler) { network_state_handler_ = network_state_handler; network_device_handler_ = network_device_handler; } void NetworkConfigurationHandler::RunCreateNetworkCallback( const std::string& profile_path, NetworkConfigurationObserver::Source source, scoped_ptr<base::DictionaryValue> configure_properties, const network_handler::StringResultCallback& callback, const dbus::ObjectPath& service_path) { if (!callback.is_null()) callback.Run(service_path.value()); FOR_EACH_OBSERVER(NetworkConfigurationObserver, observers_, OnConfigurationCreated(service_path.value(), profile_path, *configure_properties, source)); // This may also get called when CreateConfiguration is used to update an // existing configuration, so request a service update just in case. // TODO(pneubeck): Separate 'Create' and 'Update' calls and only trigger // this on an update. network_state_handler_->RequestUpdateForNetwork(service_path.value()); } void NetworkConfigurationHandler::ProfileEntryDeleterCompleted( const std::string& service_path, const std::string& guid, NetworkConfigurationObserver::Source source, bool success) { if (success) { FOR_EACH_OBSERVER(NetworkConfigurationObserver, observers_, OnConfigurationRemoved(service_path, guid, source)); } std::map<std::string, ProfileEntryDeleter*>::iterator iter = profile_entry_deleters_.find(service_path); DCHECK(iter != profile_entry_deleters_.end()); delete iter->second; profile_entry_deleters_.erase(iter); } void NetworkConfigurationHandler::SetNetworkProfileCompleted( const std::string& service_path, const std::string& profile_path, NetworkConfigurationObserver::Source source, const base::Closure& callback) { if (!callback.is_null()) callback.Run(); FOR_EACH_OBSERVER( NetworkConfigurationObserver, observers_, OnConfigurationProfileChanged(service_path, profile_path, source)); } void NetworkConfigurationHandler::GetPropertiesCallback( const network_handler::DictionaryResultCallback& callback, const network_handler::ErrorCallback& error_callback, const std::string& service_path, DBusMethodCallStatus call_status, const base::DictionaryValue& properties) { if (call_status != DBUS_METHOD_CALL_SUCCESS) { // Because network services are added and removed frequently, we will see // failures regularly, so don't log these. network_handler::RunErrorCallback(error_callback, service_path, network_handler::kDBusFailedError, network_handler::kDBusFailedErrorMessage); return; } if (callback.is_null()) return; // Get the correct name from WifiHex if necessary. scoped_ptr<base::DictionaryValue> properties_copy(properties.DeepCopy()); std::string name = shill_property_util::GetNameFromProperties(service_path, properties); if (!name.empty()) properties_copy->SetStringWithoutPathExpansion(shill::kNameProperty, name); // Get the GUID property from NetworkState if it is not set in Shill. std::string guid; properties.GetStringWithoutPathExpansion(::onc::network_config::kGUID, &guid); if (guid.empty()) { const NetworkState* network_state = network_state_handler_->GetNetworkState(service_path); if (network_state) { properties_copy->SetStringWithoutPathExpansion( ::onc::network_config::kGUID, network_state->guid()); } } callback.Run(service_path, *properties_copy.get()); } void NetworkConfigurationHandler::SetPropertiesSuccessCallback( const std::string& service_path, scoped_ptr<base::DictionaryValue> set_properties, NetworkConfigurationObserver::Source source, const base::Closure& callback) { if (!callback.is_null()) callback.Run(); const NetworkState* network_state = network_state_handler_->GetNetworkState(service_path); if (!network_state) return; // Network no longer exists, do not notify or request update. FOR_EACH_OBSERVER(NetworkConfigurationObserver, observers_, OnPropertiesSet(service_path, network_state->guid(), *set_properties, source)); network_state_handler_->RequestUpdateForNetwork(service_path); } void NetworkConfigurationHandler::SetPropertiesErrorCallback( const std::string& service_path, const network_handler::ErrorCallback& error_callback, const std::string& dbus_error_name, const std::string& dbus_error_message) { network_handler::ShillErrorCallbackFunction( "Config.SetProperties Failed", service_path, error_callback, dbus_error_name, dbus_error_message); // Some properties may have changed so request an update regardless. network_state_handler_->RequestUpdateForNetwork(service_path); } void NetworkConfigurationHandler::ClearPropertiesSuccessCallback( const std::string& service_path, const std::vector<std::string>& names, const base::Closure& callback, const base::ListValue& result) { const std::string kClearPropertiesFailedError("Error.ClearPropertiesFailed"); DCHECK(names.size() == result.GetSize()) << "Incorrect result size from ClearProperties."; for (size_t i = 0; i < result.GetSize(); ++i) { bool success = false; result.GetBoolean(i, &success); if (!success) { // If a property was cleared that has never been set, the clear will fail. // We do not track which properties have been set, so just log the error. NET_LOG(ERROR) << "ClearProperties Failed: " << service_path << ": " << names[i]; } } if (!callback.is_null()) callback.Run(); network_state_handler_->RequestUpdateForNetwork(service_path); } void NetworkConfigurationHandler::ClearPropertiesErrorCallback( const std::string& service_path, const network_handler::ErrorCallback& error_callback, const std::string& dbus_error_name, const std::string& dbus_error_message) { network_handler::ShillErrorCallbackFunction( "Config.ClearProperties Failed", service_path, error_callback, dbus_error_name, dbus_error_message); // Some properties may have changed so request an update regardless. network_state_handler_->RequestUpdateForNetwork(service_path); } void NetworkConfigurationHandler::RequestRefreshIPConfigs( const std::string& service_path) { if (!network_device_handler_) return; const NetworkState* network_state = network_state_handler_->GetNetworkState(service_path); if (!network_state || network_state->device_path().empty()) return; network_device_handler_->RequestRefreshIPConfigs( network_state->device_path(), base::Bind(&base::DoNothing), network_handler::ErrorCallback()); } // static NetworkConfigurationHandler* NetworkConfigurationHandler::InitializeForTest( NetworkStateHandler* network_state_handler, NetworkDeviceHandler* network_device_handler) { NetworkConfigurationHandler* handler = new NetworkConfigurationHandler(); handler->Init(network_state_handler, network_device_handler); return handler; } } // namespace chromeos