| // 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 <fuchsia/net/interfaces/cpp/fidl.h> |
| #include <lib/sys/cpp/component_context.h> |
| |
| #include <algorithm> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "base/fuchsia/process_context.h" |
| #include "base/logging.h" |
| #include "base/process/process.h" |
| #include "base/threading/thread_checker.h" |
| #include "base/types/expected.h" |
| #include "net/base/fuchsia/network_interface_cache.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace net { |
| |
| NetworkChangeNotifierFuchsia::NetworkChangeNotifierFuchsia(bool require_wlan) |
| : NetworkChangeNotifierFuchsia(internal::ConnectInterfacesWatcher(), |
| require_wlan, |
| /*system_dns_config_notifier=*/nullptr) {} |
| |
| NetworkChangeNotifierFuchsia::NetworkChangeNotifierFuchsia( |
| fuchsia::net::interfaces::WatcherHandle watcher_handle, |
| bool require_wlan, |
| SystemDnsConfigChangeNotifier* system_dns_config_notifier) |
| : NetworkChangeNotifier(NetworkChangeCalculatorParams(), |
| system_dns_config_notifier), |
| cache_(require_wlan) { |
| DCHECK(watcher_handle); |
| |
| std::vector<fuchsia::net::interfaces::Properties> interfaces; |
| auto handle_or_status = internal::ReadExistingNetworkInterfacesFromNewWatcher( |
| std::move(watcher_handle), interfaces); |
| if (!handle_or_status.has_value()) { |
| ZX_LOG(ERROR, handle_or_status.error()) << "ReadExistingNetworkInterfaces"; |
| base::Process::TerminateCurrentProcessImmediately(1); |
| } |
| |
| HandleCacheStatus(cache_.AddInterfaces(std::move(interfaces))); |
| |
| watcher_.set_error_handler(base::LogFidlErrorAndExitProcess( |
| FROM_HERE, "fuchsia.net.interfaces.Watcher")); |
| zx_status_t bind_status = watcher_.Bind(std::move(handle_or_status.value())); |
| ZX_CHECK(bind_status == ZX_OK, bind_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 { |
| return cache_.GetConnectionType(); |
| } |
| |
| const internal::NetworkInterfaceCache* |
| NetworkChangeNotifierFuchsia::GetNetworkInterfaceCacheInternal() const { |
| return &cache_; |
| } |
| |
| void NetworkChangeNotifierFuchsia::OnInterfacesEvent( |
| fuchsia::net::interfaces::Event event) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| // 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: |
| HandleCacheStatus(cache_.AddInterface(std::move(event.added()))); |
| break; |
| case fuchsia::net::interfaces::Event::kRemoved: |
| HandleCacheStatus(cache_.RemoveInterface(event.removed())); |
| break; |
| case fuchsia::net::interfaces::Event::kChanged: |
| HandleCacheStatus(cache_.ChangeInterface(std::move(event.changed()))); |
| break; |
| default: |
| LOG(ERROR) << "Unexpected event: " << event.Which(); |
| watcher_.Unbind(); |
| cache_.SetError(); |
| break; |
| } |
| } |
| |
| void NetworkChangeNotifierFuchsia::HandleCacheStatus( |
| absl::optional<internal::NetworkInterfaceCache::ChangeBits> change_bits) { |
| if (!change_bits.has_value()) { |
| watcher_.Unbind(); |
| return; |
| } |
| |
| if (change_bits.value() & |
| internal::NetworkInterfaceCache::kIpAddressChanged) { |
| NotifyObserversOfIPAddressChange(); |
| } |
| if (change_bits.value() & |
| internal::NetworkInterfaceCache::kConnectionTypeChanged) { |
| NotifyObserversOfConnectionTypeChange(); |
| } |
| } |
| |
| namespace internal { |
| |
| fuchsia::net::interfaces::WatcherHandle ConnectInterfacesWatcher() { |
| fuchsia::net::interfaces::StateSyncPtr state; |
| zx_status_t status = |
| base::ComponentContextForProcess()->svc()->Connect(state.NewRequest()); |
| ZX_CHECK(status == ZX_OK, status) << "Connect()"; |
| |
| // GetWatcher() is a feed-forward API, so failures will be observed via |
| // peer-closed events on the returned `watcher`. |
| fuchsia::net::interfaces::WatcherHandle watcher; |
| status = state->GetWatcher(/*options=*/{}, watcher.NewRequest()); |
| |
| return watcher; |
| } |
| |
| base::expected<fuchsia::net::interfaces::WatcherHandle, zx_status_t> |
| ReadExistingNetworkInterfacesFromNewWatcher( |
| fuchsia::net::interfaces::WatcherHandle watcher_handle, |
| std::vector<fuchsia::net::interfaces::Properties>& interfaces) { |
| DCHECK(watcher_handle); |
| |
| fuchsia::net::interfaces::WatcherSyncPtr watcher = watcher_handle.BindSync(); |
| |
| // fuchsia.net.interfaces.Watcher implements a hanging-get pattern, accepting |
| // a single Watch() call and returning an event when something changes. |
| // When a Watcher is first created, it emits a series of events describing |
| // existing interfaces, terminated by an "idle" event, before entering the |
| // normal hanging-get flow. |
| while (true) { |
| fuchsia::net::interfaces::Event event; |
| if (auto watch_status = watcher->Watch(&event); watch_status != ZX_OK) { |
| ZX_LOG(ERROR, watch_status) << "Watch() failed"; |
| return base::unexpected(watch_status); |
| } |
| |
| switch (event.Which()) { |
| case fuchsia::net::interfaces::Event::Tag::kExisting: |
| interfaces.push_back(std::move(event.existing())); |
| break; |
| case fuchsia::net::interfaces::Event::Tag::kIdle: |
| // Idle means we've listed all the existing interfaces. We can stop |
| // fetching events. |
| return base::ok(watcher.Unbind()); |
| default: |
| LOG(ERROR) << "Unexpected event " << event.Which(); |
| return base::unexpected(ZX_ERR_BAD_STATE); |
| } |
| } |
| } |
| |
| } // namespace internal |
| } // namespace net |