blob: fc7a165ef9c9633f27538e38c4a508c655025f0f [file] [log] [blame]
// Copyright 2018 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/network_change_notifier_fuchsia.h"
#include <algorithm>
#include <iterator>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/strings/stringprintf.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace net {
NetworkChangeNotifierFuchsia::NetworkChangeNotifierFuchsia(bool require_wlan)
: NetworkChangeNotifierFuchsia(internal::ConnectInterfacesWatcher(),
require_wlan) {}
NetworkChangeNotifierFuchsia::NetworkChangeNotifierFuchsia(
fidl::InterfaceHandle<fuchsia::net::interfaces::Watcher> handle,
bool require_wlan,
SystemDnsConfigChangeNotifier* system_dns_config_notifier)
: NetworkChangeNotifier(NetworkChangeCalculatorParams(),
system_dns_config_notifier),
require_wlan_(require_wlan) {
DCHECK(handle);
watcher_.set_error_handler(base::LogFidlErrorAndExitProcess(
FROM_HERE, "fuchsia.net.interfaces.Watcher"));
fuchsia::net::interfaces::WatcherSyncPtr watcher = handle.BindSync();
absl::optional<internal::ExistingInterfaceProperties> interfaces =
internal::GetExistingInterfaces(watcher);
if (!interfaces)
return;
handle = watcher.Unbind();
bool notify_ip_address_changed = false;
for (const auto& interface_entry : *interfaces) {
notify_ip_address_changed |=
CanReachExternalNetwork(interface_entry.second);
}
interface_cache_ = InterfacePropertiesMap(std::move(*interfaces));
UpdateConnectionType();
if (notify_ip_address_changed) {
NotifyObserversOfIPAddressChange();
}
// Bind to the dispatcher for the thread's MessagePump.
zx_status_t status = watcher_.Bind(std::move(handle));
ZX_CHECK(status == ZX_OK, status) << "Bind()";
watcher_->Watch(
fit::bind_member(this, &NetworkChangeNotifierFuchsia::OnInterfacesEvent));
}
NetworkChangeNotifierFuchsia::~NetworkChangeNotifierFuchsia() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
ClearGlobalPointer();
}
NetworkChangeNotifier::ConnectionType
NetworkChangeNotifierFuchsia::GetCurrentConnectionType() const {
ConnectionType type = static_cast<ConnectionType>(
base::subtle::Acquire_Load(&cached_connection_type_));
return type;
}
void NetworkChangeNotifierFuchsia::OnInterfacesEvent(
fuchsia::net::interfaces::Event event) {
// Immediately trigger the next watch, which will happen asynchronously. If
// event processing encounters an error it'll close the watcher channel which
// will cancel any pending callbacks.
watcher_->Watch(
fit::bind_member(this, &NetworkChangeNotifierFuchsia::OnInterfacesEvent));
switch (event.Which()) {
case fuchsia::net::interfaces::Event::kAdded:
OnInterfaceAdded(std::move(event.added()));
break;
case fuchsia::net::interfaces::Event::kRemoved:
OnInterfaceRemoved(event.removed());
break;
case fuchsia::net::interfaces::Event::kChanged:
OnInterfaceChanged(std::move(event.changed()));
break;
case fuchsia::net::interfaces::Event::kExisting:
case fuchsia::net::interfaces::Event::kIdle:
OnWatcherError(base::StringPrintf(
"OnInterfaceEvent: unexpected event %lu.", event.Which()));
break;
case fuchsia::net::interfaces::Event::Invalid:
LOG(WARNING)
<< "Invalid event received from fuchsia.net.interfaces/Watcher";
break;
}
}
void NetworkChangeNotifierFuchsia::OnInterfaceAdded(
fuchsia::net::interfaces::Properties properties) {
uint64_t id = properties.id();
absl::optional<internal::InterfaceProperties> cache_entry =
internal::InterfaceProperties::VerifyAndCreate(std::move(properties));
if (!cache_entry) {
OnWatcherError("OnInterfaceAdded: incomplete interface properties.");
return;
}
if (interface_cache_.find(id) != interface_cache_.end()) {
OnWatcherError(base::StringPrintf(
"OnInterfaceAdded: duplicate interface ID %lu.", id));
return;
}
const bool can_reach = CanReachExternalNetwork(*cache_entry);
interface_cache_.emplace(id, std::move(*cache_entry));
UpdateConnectionType();
if (can_reach) {
NotifyObserversOfIPAddressChange();
}
}
void NetworkChangeNotifierFuchsia::OnInterfaceRemoved(uint64_t interface_id) {
InterfacePropertiesMap::iterator cache_entry =
interface_cache_.find(interface_id);
if (cache_entry == interface_cache_.end()) {
OnWatcherError(base::StringPrintf(
"OnInterfaceRemoved: unknown interface ID %lu.", interface_id));
return;
}
const bool can_reach = CanReachExternalNetwork(cache_entry->second);
interface_cache_.erase(cache_entry);
UpdateConnectionType();
if (can_reach) {
NotifyObserversOfIPAddressChange();
}
}
void NetworkChangeNotifierFuchsia::OnInterfaceChanged(
fuchsia::net::interfaces::Properties properties) {
if (!properties.has_id()) {
OnWatcherError("OnInterfaceChanged: no interface ID.");
return;
}
const uint64_t id = properties.id();
InterfacePropertiesMap::iterator cache_entry = interface_cache_.find(id);
if (cache_entry == interface_cache_.end()) {
OnWatcherError(base::StringPrintf(
"OnInterfaceChanged: unknown interface ID %lu.", id));
return;
}
const bool old_can_reach = CanReachExternalNetwork(cache_entry->second);
const bool has_addresses = properties.has_addresses();
if (!cache_entry->second.Update(std::move(properties))) {
OnWatcherError("OnInterfaceChanged: update failed.");
return;
}
UpdateConnectionType();
const bool can_reach = CanReachExternalNetwork(cache_entry->second);
if (has_addresses || old_can_reach != can_reach) {
NotifyObserversOfIPAddressChange();
}
}
void NetworkChangeNotifierFuchsia::OnWatcherError(
base::StringPiece error_message) {
LOG(ERROR) << error_message;
watcher_.Unbind();
ResetConnectionType();
}
void NetworkChangeNotifierFuchsia::UpdateConnectionType() {
ConnectionType connection_type = ConnectionType::CONNECTION_NONE;
for (const auto& interface : interface_cache_) {
if (CanReachExternalNetwork(interface.second)) {
connection_type = GetEffectiveConnectionType(interface.second);
break;
}
}
if (connection_type != GetCurrentConnectionType()) {
base::subtle::Release_Store(&cached_connection_type_, connection_type);
NotifyObserversOfConnectionTypeChange();
}
}
void NetworkChangeNotifierFuchsia::ResetConnectionType() {
base::subtle::Release_Store(&cached_connection_type_,
ConnectionType::CONNECTION_UNKNOWN);
}
NetworkChangeNotifier::ConnectionType
NetworkChangeNotifierFuchsia::GetEffectiveConnectionType(
const internal::InterfaceProperties& properties) {
if (!properties.IsPubliclyRoutable())
return NetworkChangeNotifier::CONNECTION_NONE;
NetworkChangeNotifier::ConnectionType connection_type =
internal::ConvertConnectionType(properties.device_class());
if (require_wlan_ &&
connection_type != NetworkChangeNotifier::CONNECTION_WIFI) {
return NetworkChangeNotifier::CONNECTION_NONE;
}
return connection_type;
}
bool NetworkChangeNotifierFuchsia::CanReachExternalNetwork(
const internal::InterfaceProperties& properties) {
return GetEffectiveConnectionType(properties) !=
NetworkChangeNotifier::CONNECTION_NONE;
}
} // namespace net