// 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_state.h" #include "base/memory/scoped_ptr.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "chromeos/network/network_profile_handler.h" #include "chromeos/network/network_type_pattern.h" #include "chromeos/network/network_util.h" #include "chromeos/network/onc/onc_utils.h" #include "chromeos/network/shill_property_util.h" #include "components/device_event_log/device_event_log.h" #include "third_party/cros_system_api/dbus/service_constants.h" namespace { const char kErrorUnknown[] = "Unknown"; bool ConvertListValueToStringVector(const base::ListValue& string_list, std::vector* result) { for (size_t i = 0; i < string_list.GetSize(); ++i) { std::string str; if (!string_list.GetString(i, &str)) return false; result->push_back(str); } return true; } bool IsCaptivePortalState(const base::DictionaryValue& properties, bool log) { std::string state; properties.GetStringWithoutPathExpansion(shill::kStateProperty, &state); if (state != shill::kStatePortal) return false; std::string portal_detection_phase, portal_detection_status; if (!properties.GetStringWithoutPathExpansion( shill::kPortalDetectionFailedPhaseProperty, &portal_detection_phase) || !properties.GetStringWithoutPathExpansion( shill::kPortalDetectionFailedStatusProperty, &portal_detection_status)) { // If Shill (or a stub) has not set PortalDetectionFailedStatus // or PortalDetectionFailedPhase, assume we are in captive portal state. return true; } // Shill reports the phase in which it determined that the device is behind a // captive portal. We only want to rely only on incorrect content being // returned and ignore other reasons. bool is_captive_portal = portal_detection_phase == shill::kPortalDetectionPhaseContent && portal_detection_status == shill::kPortalDetectionStatusFailure; if (log) { std::string name; properties.GetStringWithoutPathExpansion(shill::kNameProperty, &name); if (name.empty()) properties.GetStringWithoutPathExpansion(shill::kSSIDProperty, &name); if (!is_captive_portal) { NET_LOG(EVENT) << "State is 'portal' but not in captive portal state:" << " name=" << name << " phase=" << portal_detection_phase << " status=" << portal_detection_status; } else { NET_LOG(EVENT) << "Network is in captive portal state: " << name; } } return is_captive_portal; } } // namespace namespace chromeos { NetworkState::NetworkState(const std::string& path) : ManagedState(MANAGED_TYPE_NETWORK, path), visible_(false), prefix_length_(0), connectable_(false), is_captive_portal_(false), signal_strength_(0), cellular_out_of_credits_(false) { } NetworkState::~NetworkState() { } bool NetworkState::PropertyChanged(const std::string& key, const base::Value& value) { // Keep care that these properties are the same as in |GetProperties|. if (ManagedStatePropertyChanged(key, value)) return true; if (key == shill::kSignalStrengthProperty) { return GetIntegerValue(key, value, &signal_strength_); } else if (key == shill::kStateProperty) { return GetStringValue(key, value, &connection_state_); } else if (key == shill::kVisibleProperty) { return GetBooleanValue(key, value, &visible_); } else if (key == shill::kConnectableProperty) { return GetBooleanValue(key, value, &connectable_); } else if (key == shill::kErrorProperty) { if (!GetStringValue(key, value, &error_)) return false; if (ErrorIsValid(error_)) last_error_ = error_; else error_.clear(); return true; } else if (key == shill::kActivationTypeProperty) { return GetStringValue(key, value, &activation_type_); } else if (key == shill::kActivationStateProperty) { return GetStringValue(key, value, &activation_state_); } else if (key == shill::kRoamingStateProperty) { return GetStringValue(key, value, &roaming_); } else if (key == shill::kPaymentPortalProperty) { const base::DictionaryValue* olp; if (!value.GetAsDictionary(&olp)) return false; return olp->GetStringWithoutPathExpansion(shill::kPaymentPortalURL, &payment_url_); } else if (key == shill::kSecurityClassProperty) { return GetStringValue(key, value, &security_class_); } else if (key == shill::kEapMethodProperty) { return GetStringValue(key, value, &eap_method_); } else if (key == shill::kNetworkTechnologyProperty) { return GetStringValue(key, value, &network_technology_); } else if (key == shill::kDeviceProperty) { return GetStringValue(key, value, &device_path_); } else if (key == shill::kGuidProperty) { return GetStringValue(key, value, &guid_); } else if (key == shill::kProfileProperty) { return GetStringValue(key, value, &profile_path_); } else if (key == shill::kWifiHexSsid) { std::string ssid_hex; if (!GetStringValue(key, value, &ssid_hex)) { return false; } raw_ssid_.clear(); return base::HexStringToBytes(ssid_hex, &raw_ssid_); } else if (key == shill::kOutOfCreditsProperty) { return GetBooleanValue(key, value, &cellular_out_of_credits_); } else if (key == shill::kProxyConfigProperty) { std::string proxy_config_str; if (!value.GetAsString(&proxy_config_str)) { NET_LOG(ERROR) << "Failed to parse " << path() << "." << key; return false; } proxy_config_.Clear(); if (proxy_config_str.empty()) return true; scoped_ptr proxy_config_dict( onc::ReadDictionaryFromJson(proxy_config_str)); if (proxy_config_dict) { // Warning: The DictionaryValue returned from // ReadDictionaryFromJson/JSONParser is an optimized derived class that // doesn't allow releasing ownership of nested values. A Swap in the wrong // order leads to memory access errors. proxy_config_.MergeDictionary(proxy_config_dict.get()); } else { NET_LOG(ERROR) << "Failed to parse " << path() << "." << key; } return true; } else if (key == shill::kProviderProperty) { std::string vpn_provider_type; const base::DictionaryValue* dict; if (!value.GetAsDictionary(&dict) || !dict->GetStringWithoutPathExpansion(shill::kTypeProperty, &vpn_provider_type)) { NET_LOG(ERROR) << "Failed to parse " << path() << "." << key; return false; } if (vpn_provider_type == shill::kProviderThirdPartyVpn) { // If the network uses a third-party VPN provider, copy over the // provider's extension ID, which is held in |shill::kHostProperty|. if (!dict->GetStringWithoutPathExpansion( shill::kHostProperty, &third_party_vpn_provider_extension_id_)) { NET_LOG(ERROR) << "Failed to parse " << path() << "." << key; return false; } } else { third_party_vpn_provider_extension_id_.clear(); } vpn_provider_type_ = vpn_provider_type; return true; } return false; } bool NetworkState::InitialPropertiesReceived( const base::DictionaryValue& properties) { NET_LOG(EVENT) << "InitialPropertiesReceived: " << path() << ": " << name() << " State: " << connection_state_ << " Visible: " << visible_; if (!properties.HasKey(shill::kTypeProperty)) { NET_LOG(ERROR) << "NetworkState has no type: " << shill_property_util::GetNetworkIdFromProperties( properties); return false; } // By convention, all visible WiFi and WiMAX networks have a // SignalStrength > 0. if ((type() == shill::kTypeWifi || type() == shill::kTypeWimax) && visible() && signal_strength_ <= 0) { signal_strength_ = 1; } // Any change to connection state will trigger a complete property update, // so we update is_captive_portal_ here. is_captive_portal_ = IsCaptivePortalState(properties, true /* log */); // Ensure that the network has a valid name. return UpdateName(properties); } void NetworkState::GetStateProperties(base::DictionaryValue* dictionary) const { ManagedState::GetStateProperties(dictionary); // Properties shared by all types. dictionary->SetStringWithoutPathExpansion(shill::kGuidProperty, guid()); dictionary->SetStringWithoutPathExpansion(shill::kSecurityClassProperty, security_class()); dictionary->SetStringWithoutPathExpansion(shill::kProfileProperty, profile_path()); if (visible()) { dictionary->SetStringWithoutPathExpansion(shill::kStateProperty, connection_state()); } // VPN properties. if (NetworkTypePattern::VPN().MatchesType(type())) { // Shill sends VPN provider properties in a nested dictionary. |dictionary| // must replicate that nested structure. scoped_ptr provider_property( new base::DictionaryValue); provider_property->SetStringWithoutPathExpansion(shill::kTypeProperty, vpn_provider_type_); if (vpn_provider_type_ == shill::kProviderThirdPartyVpn) { provider_property->SetStringWithoutPathExpansion( shill::kHostProperty, third_party_vpn_provider_extension_id_); } dictionary->SetWithoutPathExpansion(shill::kProviderProperty, provider_property.release()); } // Wireless properties if (!NetworkTypePattern::Wireless().MatchesType(type())) return; if (visible()) { dictionary->SetBooleanWithoutPathExpansion(shill::kConnectableProperty, connectable()); dictionary->SetIntegerWithoutPathExpansion(shill::kSignalStrengthProperty, signal_strength()); } // Wifi properties if (NetworkTypePattern::WiFi().MatchesType(type())) { dictionary->SetStringWithoutPathExpansion(shill::kEapMethodProperty, eap_method()); } // Mobile properties if (NetworkTypePattern::Mobile().MatchesType(type())) { dictionary->SetStringWithoutPathExpansion(shill::kNetworkTechnologyProperty, network_technology()); dictionary->SetStringWithoutPathExpansion(shill::kActivationStateProperty, activation_state()); dictionary->SetStringWithoutPathExpansion(shill::kRoamingStateProperty, roaming()); dictionary->SetBooleanWithoutPathExpansion(shill::kOutOfCreditsProperty, cellular_out_of_credits()); } } void NetworkState::IPConfigPropertiesChanged( const base::DictionaryValue& properties) { for (base::DictionaryValue::Iterator iter(properties); !iter.IsAtEnd(); iter.Advance()) { std::string key = iter.key(); const base::Value& value = iter.value(); if (key == shill::kAddressProperty) { GetStringValue(key, value, &ip_address_); } else if (key == shill::kGatewayProperty) { GetStringValue(key, value, &gateway_); } else if (key == shill::kNameServersProperty) { const base::ListValue* dns_servers; if (value.GetAsList(&dns_servers)) { dns_servers_.clear(); ConvertListValueToStringVector(*dns_servers, &dns_servers_); } } else if (key == shill::kPrefixlenProperty) { GetIntegerValue(key, value, &prefix_length_); } else if (key == shill::kWebProxyAutoDiscoveryUrlProperty) { std::string url_string; if (GetStringValue(key, value, &url_string)) { if (url_string.empty()) { web_proxy_auto_discovery_url_ = GURL(); } else { GURL gurl(url_string); if (gurl.is_valid()) { web_proxy_auto_discovery_url_ = gurl; } else { NET_LOG(ERROR) << "Invalid WebProxyAutoDiscoveryUrl: " << path() << ": " << url_string; web_proxy_auto_discovery_url_ = GURL(); } } } } } } bool NetworkState::RequiresActivation() const { return (type() == shill::kTypeCellular && activation_state() != shill::kActivationStateActivated && activation_state() != shill::kActivationStateUnknown); } std::string NetworkState::connection_state() const { if (!visible()) return shill::kStateDisconnect; return connection_state_; } bool NetworkState::IsConnectedState() const { return visible() && StateIsConnected(connection_state_); } bool NetworkState::IsConnectingState() const { return visible() && StateIsConnecting(connection_state_); } bool NetworkState::IsInProfile() const { // kTypeEthernetEap is always saved. We need this check because it does // not show up in the visible list, but its properties may not be available // when it first shows up in ServiceCompleteList. See crbug.com/355117. return !profile_path_.empty() || type() == shill::kTypeEthernetEap; } bool NetworkState::IsPrivate() const { return !profile_path_.empty() && profile_path_ != NetworkProfileHandler::GetSharedProfilePath(); } std::string NetworkState::GetHexSsid() const { return base::HexEncode(vector_as_array(&raw_ssid()), raw_ssid().size()); } std::string NetworkState::GetDnsServersAsString() const { std::string result; for (size_t i = 0; i < dns_servers_.size(); ++i) { if (i != 0) result += ","; result += dns_servers_[i]; } return result; } std::string NetworkState::GetNetmask() const { return network_util::PrefixLengthToNetmask(prefix_length_); } std::string NetworkState::GetSpecifier() const { if (!update_received()) { NET_LOG(ERROR) << "GetSpecifier called before update: " << path(); return std::string(); } if (type() == shill::kTypeWifi) return name() + "_" + security_class_; if (!name().empty()) return name(); return type(); // For unnamed networks such as ethernet. } void NetworkState::SetGuid(const std::string& guid) { guid_ = guid; } bool NetworkState::UpdateName(const base::DictionaryValue& properties) { std::string updated_name = shill_property_util::GetNameFromProperties(path(), properties); if (updated_name != name()) { set_name(updated_name); return true; } return false; } std::string NetworkState::GetErrorState() const { if (ErrorIsValid(error())) return error(); return last_error(); } // static bool NetworkState::StateIsConnected(const std::string& connection_state) { return (connection_state == shill::kStateReady || connection_state == shill::kStateOnline || connection_state == shill::kStatePortal); } // static bool NetworkState::StateIsConnecting(const std::string& connection_state) { return (connection_state == shill::kStateAssociation || connection_state == shill::kStateConfiguration || connection_state == shill::kStateCarrier); } // static bool NetworkState::NetworkStateIsCaptivePortal( const base::DictionaryValue& shill_properties) { return IsCaptivePortalState(shill_properties, false /* log */); } // static bool NetworkState::ErrorIsValid(const std::string& error) { // Shill uses "Unknown" to indicate an unset or cleared error state. return !error.empty() && error != kErrorUnknown; } } // namespace chromeos