| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/base/fuchsia/network_interface_cache.h" |
| |
| #include <fuchsia/net/interfaces/cpp/fidl.h> |
| |
| #include <utility> |
| |
| #include "base/containers/flat_map.h" |
| #include "base/logging.h" |
| #include "base/sequence_checker.h" |
| #include "base/synchronization/lock.h" |
| #include "net/base/network_change_notifier.h" |
| #include "net/base/network_interfaces.h" |
| #include "net/base/network_interfaces_fuchsia.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace net::internal { |
| namespace { |
| |
| // Returns a ConnectionType derived from the supplied InterfaceProperties: |
| // - CONNECTION_NONE if the interface is not publicly routable. |
| // - Otherwise, returns a type derived from the interface's device_class. |
| NetworkChangeNotifier::ConnectionType GetEffectiveConnectionType( |
| const InterfaceProperties& properties, |
| bool require_wlan) { |
| if (!properties.IsPubliclyRoutable()) { |
| return NetworkChangeNotifier::CONNECTION_NONE; |
| } |
| |
| NetworkChangeNotifier::ConnectionType connection_type = |
| ConvertConnectionType(properties.device_class()); |
| if (require_wlan && |
| connection_type != NetworkChangeNotifier::CONNECTION_WIFI) { |
| return NetworkChangeNotifier::CONNECTION_NONE; |
| } |
| return connection_type; |
| } |
| |
| bool CanReachExternalNetwork(const InterfaceProperties& interface, |
| bool require_wlan) { |
| return GetEffectiveConnectionType(interface, require_wlan) != |
| NetworkChangeNotifier::CONNECTION_NONE; |
| } |
| |
| } // namespace |
| |
| NetworkInterfaceCache::NetworkInterfaceCache(bool require_wlan) |
| : require_wlan_(require_wlan) {} |
| |
| NetworkInterfaceCache::~NetworkInterfaceCache() = default; |
| |
| absl::optional<NetworkInterfaceCache::ChangeBits> |
| NetworkInterfaceCache::AddInterfaces( |
| std::vector<fuchsia::net::interfaces::Properties> interfaces) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| base::AutoLock auto_lock(lock_); |
| |
| ChangeBits combined_changes = kNoChange; |
| for (auto& interface : interfaces) { |
| auto change_bits = AddInterfaceWhileLocked(std::move(interface)); |
| if (!change_bits.has_value()) { |
| return absl::nullopt; |
| } |
| combined_changes |= change_bits.value(); |
| } |
| return combined_changes; |
| } |
| |
| absl::optional<NetworkInterfaceCache::ChangeBits> |
| NetworkInterfaceCache::AddInterface( |
| fuchsia::net::interfaces::Properties properties) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| base::AutoLock auto_lock(lock_); |
| |
| return AddInterfaceWhileLocked(std::move(properties)); |
| } |
| |
| absl::optional<NetworkInterfaceCache::ChangeBits> |
| NetworkInterfaceCache::AddInterfaceWhileLocked( |
| fuchsia::net::interfaces::Properties properties) |
| EXCLUSIVE_LOCKS_REQUIRED(lock_) VALID_CONTEXT_REQUIRED(sequence_checker_) { |
| if (error_state_) { |
| return absl::nullopt; |
| } |
| |
| auto interface = InterfaceProperties::VerifyAndCreate(std::move(properties)); |
| if (!interface) { |
| LOG(ERROR) << "Incomplete interface properties."; |
| SetErrorWhileLocked(); |
| return absl::nullopt; |
| } |
| |
| if (interfaces_.find(interface->id()) != interfaces_.end()) { |
| LOG(ERROR) << "Unexpected duplicate interface ID " << interface->id(); |
| SetErrorWhileLocked(); |
| return absl::nullopt; |
| } |
| |
| ChangeBits change_bits = kNoChange; |
| if (CanReachExternalNetwork(*interface, require_wlan_)) { |
| change_bits |= kIpAddressChanged; |
| } |
| interfaces_.emplace(interface->id(), std::move(*interface)); |
| if (UpdateConnectionTypeWhileLocked()) { |
| change_bits |= kConnectionTypeChanged; |
| } |
| return change_bits; |
| } |
| |
| absl::optional<NetworkInterfaceCache::ChangeBits> |
| NetworkInterfaceCache::ChangeInterface( |
| fuchsia::net::interfaces::Properties properties) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| base::AutoLock auto_lock(lock_); |
| if (error_state_) { |
| return absl::nullopt; |
| } |
| |
| auto cache_entry = interfaces_.find(properties.id()); |
| if (cache_entry == interfaces_.end()) { |
| LOG(ERROR) << "Unknown interface ID " << properties.id(); |
| SetErrorWhileLocked(); |
| return absl::nullopt; |
| } |
| |
| const bool old_can_reach = |
| CanReachExternalNetwork(cache_entry->second, require_wlan_); |
| const bool has_addresses = properties.has_addresses(); |
| |
| if (!cache_entry->second.Update(std::move(properties))) { |
| LOG(ERROR) << "Update failed"; |
| SetErrorWhileLocked(); |
| return absl::nullopt; |
| } |
| |
| const bool new_can_reach = |
| CanReachExternalNetwork(cache_entry->second, require_wlan_); |
| |
| ChangeBits change_bits = kNoChange; |
| if (has_addresses || old_can_reach != new_can_reach) { |
| change_bits |= kIpAddressChanged; |
| } |
| if (UpdateConnectionTypeWhileLocked()) { |
| change_bits |= kConnectionTypeChanged; |
| } |
| return change_bits; |
| } |
| |
| absl::optional<NetworkInterfaceCache::ChangeBits> |
| NetworkInterfaceCache::RemoveInterface( |
| InterfaceProperties::InterfaceId interface_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| base::AutoLock auto_lock(lock_); |
| if (error_state_) { |
| return absl::nullopt; |
| } |
| |
| auto cache_entry = interfaces_.find(interface_id); |
| if (cache_entry == interfaces_.end()) { |
| LOG(ERROR) << "Unknown interface ID " << interface_id; |
| SetErrorWhileLocked(); |
| return absl::nullopt; |
| } |
| |
| ChangeBits change_bits = kNoChange; |
| if (CanReachExternalNetwork(cache_entry->second, require_wlan_)) { |
| change_bits |= kIpAddressChanged; |
| } |
| interfaces_.erase(cache_entry); |
| if (UpdateConnectionTypeWhileLocked()) { |
| change_bits |= kConnectionTypeChanged; |
| } |
| return change_bits; |
| } |
| |
| bool NetworkInterfaceCache::GetOnlineInterfaces( |
| NetworkInterfaceList* networks) const { |
| DCHECK(networks); |
| |
| base::AutoLock auto_lock(lock_); |
| if (error_state_) { |
| return false; |
| } |
| |
| for (const auto& [_, interface] : interfaces_) { |
| if (!interface.online()) { |
| continue; |
| } |
| if (interface.device_class().is_loopback()) { |
| continue; |
| } |
| interface.AppendNetworkInterfaces(networks); |
| } |
| return true; |
| } |
| |
| NetworkChangeNotifier::ConnectionType NetworkInterfaceCache::GetConnectionType() |
| const { |
| base::AutoLock auto_lock(lock_); |
| if (error_state_) { |
| return NetworkChangeNotifier::CONNECTION_UNKNOWN; |
| } |
| |
| return connection_type_; |
| } |
| |
| void NetworkInterfaceCache::SetError() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| base::AutoLock auto_lock(lock_); |
| SetErrorWhileLocked(); |
| } |
| |
| bool NetworkInterfaceCache::UpdateConnectionTypeWhileLocked() |
| EXCLUSIVE_LOCKS_REQUIRED(lock_) VALID_CONTEXT_REQUIRED(sequence_checker_) { |
| NetworkChangeNotifier::ConnectionType connection_type = |
| NetworkChangeNotifier::ConnectionType::CONNECTION_NONE; |
| for (const auto& [_, interface] : interfaces_) { |
| connection_type = GetEffectiveConnectionType(interface, require_wlan_); |
| if (connection_type != NetworkChangeNotifier::CONNECTION_NONE) { |
| break; |
| } |
| } |
| if (connection_type != connection_type_) { |
| connection_type_ = connection_type; |
| return true; |
| } |
| return false; |
| } |
| |
| void NetworkInterfaceCache::SetErrorWhileLocked() |
| EXCLUSIVE_LOCKS_REQUIRED(lock_) VALID_CONTEXT_REQUIRED(sequence_checker_) { |
| error_state_ = true; |
| interfaces_.clear(); |
| interfaces_.shrink_to_fit(); |
| } |
| |
| } // namespace net::internal |