Reland "[fuchsia] Migrate to fuchsia.net.interfaces/State"

This is a reland of 022c5b0e929855e1422d8dea776d72ada41020f0.
Consumers of WebEngine have been prepared for a soft transition, and
runners_integration_tests.test-cmx is now updated.

Original change's description:
> [fuchsia] Migrate to fuchsia.net.interfaces/State
>
> Fixed: fuchsia:21155
> Change-Id: I85b388be33d4c9697ba866cf11a143493b2b3239
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2380329
> Reviewed-by: Robert Sesek <[email protected]>
> Reviewed-by: Adam Langley <[email protected]>
> Reviewed-by: Sergey Volk <[email protected]>
> Reviewed-by: Wez <[email protected]>
> Commit-Queue: Marina Ciocea <[email protected]>
> Cr-Commit-Position: refs/heads/master@{#828229}

[email protected]

Change-Id: I7c63e2ab88ce8b087411de97bc4ee2eceebc7c3a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2554857
Commit-Queue: Marina Ciocea <[email protected]>
Reviewed-by: David Dorwin <[email protected]>
Reviewed-by: Robert Sesek <[email protected]>
Reviewed-by: Wez <[email protected]>
Reviewed-by: Marina Ciocea <[email protected]>
Reviewed-by: Adam Langley <[email protected]>
Cr-Commit-Position: refs/heads/master@{#833397}
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 076568a..bc07d101 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -1458,7 +1458,8 @@
 
     if (is_fuchsia) {
       deps += [ "//third_party/fuchsia-sdk/sdk/pkg/async-loop-cpp" ]
-      public_deps += [ "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.netstack" ]
+      public_deps +=
+          [ "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.net.interfaces" ]
       sources += [
         "base/network_change_notifier_fuchsia.cc",
         "base/network_change_notifier_fuchsia.h",
@@ -1710,8 +1711,8 @@
 
     if (is_fuchsia) {
       public_deps += [
-        "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.hardware.ethernet",
-        "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.netstack",
+        "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.hardware.network",
+        "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.net.interfaces",
         "//third_party/fuchsia-sdk/sdk/pkg/sys_cpp",
       ]
     }
@@ -4630,7 +4631,7 @@
   if (is_fuchsia) {
     use_test_server = true
     deps += [
-      "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.netstack",
+      "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.net.interfaces",
       "//third_party/fuchsia-sdk/sdk/pkg/fidl_cpp",
     ]
     sources += [ "base/network_change_notifier_fuchsia_unittest.cc" ]
diff --git a/net/base/network_change_notifier.cc b/net/base/network_change_notifier.cc
index 1bf82ef..d786914 100644
--- a/net/base/network_change_notifier.cc
+++ b/net/base/network_change_notifier.cc
@@ -251,7 +251,7 @@
   return std::make_unique<NetworkChangeNotifierMac>();
 #elif defined(OS_FUCHSIA)
   return std::make_unique<NetworkChangeNotifierFuchsia>(
-      fuchsia::hardware::ethernet::Features());
+      /*require_wlan=*/false);
 #else
   NOTIMPLEMENTED();
   return NULL;
diff --git a/net/base/network_change_notifier_fuchsia.cc b/net/base/network_change_notifier_fuchsia.cc
index 75d66bc..3c10a11 100644
--- a/net/base/network_change_notifier_fuchsia.cc
+++ b/net/base/network_change_notifier_fuchsia.cc
@@ -4,63 +4,63 @@
 
 #include "net/base/network_change_notifier_fuchsia.h"
 
-#include <lib/async-loop/cpp/loop.h>
-#include <lib/sys/cpp/component_context.h>
-
 #include <algorithm>
+#include <iterator>
 #include <string>
 #include <utility>
 #include <vector>
 
 #include "base/bind.h"
 #include "base/fuchsia/fuchsia_logging.h"
-#include "base/fuchsia/process_context.h"
 #include "base/optional.h"
-#include "base/run_loop.h"
-#include "net/base/network_interfaces.h"
-#include "net/base/network_interfaces_fuchsia.h"
+#include "base/strings/stringprintf.h"
 
 namespace net {
 
-NetworkChangeNotifierFuchsia::NetworkChangeNotifierFuchsia(
-    fuchsia::hardware::ethernet::Features required_features)
-    : NetworkChangeNotifierFuchsia(base::ComponentContextForProcess()
-                                       ->svc()
-                                       ->Connect<fuchsia::netstack::Netstack>(),
-                                   required_features) {}
+NetworkChangeNotifierFuchsia::NetworkChangeNotifierFuchsia(bool require_wlan)
+    : NetworkChangeNotifierFuchsia(internal::ConnectInterfacesWatcher(),
+                                   require_wlan) {}
 
 NetworkChangeNotifierFuchsia::NetworkChangeNotifierFuchsia(
-    fidl::InterfaceHandle<fuchsia::netstack::Netstack> netstack,
-    fuchsia::hardware::ethernet::Features required_features,
+    fidl::InterfaceHandle<fuchsia::net::interfaces::Watcher> handle,
+    bool require_wlan,
     SystemDnsConfigChangeNotifier* system_dns_config_notifier)
     : NetworkChangeNotifier(NetworkChangeCalculatorParams(),
                             system_dns_config_notifier),
-      required_features_(required_features) {
-  DCHECK(netstack);
+      require_wlan_(require_wlan) {
+  DCHECK(handle);
 
-  netstack_.set_error_handler([](zx_status_t status) {
-    ZX_LOG(FATAL, status) << "Lost connection to netstack.";
-  });
+  watcher_.set_error_handler(
+      [](zx_status_t status) {
+        ZX_LOG(FATAL, status)
+            << "Lost connection to fuchsia.net.interfaces/Watcher.";
+      });
 
-  netstack_.events().OnInterfacesChanged = fit::bind_member(
-      this, &NetworkChangeNotifierFuchsia::ProcessInterfaceList);
+  fuchsia::net::interfaces::WatcherSyncPtr watcher = handle.BindSync();
+  base::Optional<internal::ExistingInterfaceProperties> interfaces =
+      internal::GetExistingInterfaces(watcher);
+  if (!interfaces) {
+    ZX_LOG(ERROR, ZX_ERR_INVALID_ARGS) << "Failed to load existing 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));
 
-  // Temporarily bind to a local dispatcher so we can synchronously wait for the
-  // synthetic event to populate the initial state.
-  async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
-  zx_status_t status = netstack_.Bind(std::move(netstack), loop.dispatcher());
-  ZX_CHECK(status == ZX_OK, status) << "Bind()";
-  on_initial_interfaces_received_ =
-      base::BindOnce(&async::Loop::Quit, base::Unretained(&loop));
-  status = loop.Run();
-  ZX_CHECK(status == ZX_ERR_CANCELED, status) << "loop.Run()";
+  UpdateConnectionType();
+  if (notify_ip_address_changed) {
+    NotifyObserversOfIPAddressChange();
+  }
 
   // Bind to the dispatcher for the thread's MessagePump.
-  //
-  // Note this must be done before |loop| is destroyed, since that would close
-  // the interface handle underlying |netstack_|.
-  status = netstack_.Bind(netstack_.Unbind());
+  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() {
@@ -75,80 +75,146 @@
   return type;
 }
 
-void NetworkChangeNotifierFuchsia::ProcessInterfaceList(
-    std::vector<fuchsia::netstack::NetInterface> interfaces) {
-  netstack_->GetRouteTable(
-      [this, interfaces = std::move(interfaces)](
-          std::vector<fuchsia::netstack::RouteTableEntry> route_table) mutable {
-        OnRouteTableReceived(std::move(interfaces), std::move(route_table));
-      });
+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::OnRouteTableReceived(
-    std::vector<fuchsia::netstack::NetInterface> interfaces,
-    std::vector<fuchsia::netstack::RouteTableEntry> route_table) {
-  // Create a set of NICs that have default routes (ie 0.0.0.0).
-  base::flat_set<uint32_t> default_route_ids;
-  for (const auto& route : route_table) {
-    if (MaskPrefixLength(
-            internal::FuchsiaIpAddressToIPAddress(route.netmask)) == 0) {
-      default_route_ids.insert(route.nicid);
-    }
+void NetworkChangeNotifierFuchsia::OnInterfaceAdded(
+    fuchsia::net::interfaces::Properties properties) {
+  base::Optional<internal::InterfaceProperties> cache_entry =
+      internal::InterfaceProperties::VerifyAndCreate(std::move(properties));
+  if (!cache_entry) {
+    OnWatcherError("OnInterfaceAdded: incomplete interface properties.");
+    return;
   }
-
-  ConnectionType connection_type = CONNECTION_NONE;
-  base::flat_set<IPAddress> addresses;
-  for (auto& interface : interfaces) {
-    // Filter out loopback and invalid connection types.
-    if ((internal::ConvertConnectionType(interface) ==
-         NetworkChangeNotifier::CONNECTION_NONE) ||
-        (interface.features &
-         fuchsia::hardware::ethernet::Features::LOOPBACK) ==
-            fuchsia::hardware::ethernet::Features::LOOPBACK) {
-      continue;
-    }
-
-    // Filter out interfaces that do not meet the |required_features_|.
-    if ((interface.features & required_features_) != required_features_) {
-      continue;
-    }
-
-    // Filter out interfaces with non-default routes.
-    if (!default_route_ids.contains(interface.id)) {
-      continue;
-    }
-
-    std::vector<NetworkInterface> flattened_interfaces =
-        internal::NetInterfaceToNetworkInterfaces(interface);
-    if (flattened_interfaces.empty()) {
-      continue;
-    }
-
-    // Add the addresses from this interface to the list of all addresses.
-    std::transform(
-        flattened_interfaces.begin(), flattened_interfaces.end(),
-        std::inserter(addresses, addresses.begin()),
-        [](const NetworkInterface& interface) { return interface.address; });
-
-    // Set the default connection to the first interface connection found.
-    if (connection_type == CONNECTION_NONE) {
-      connection_type = flattened_interfaces.front().type;
-    }
+  uint64_t id = properties.id();
+  if (interface_cache_.find(id) != interface_cache_.end()) {
+    OnWatcherError(base::StringPrintf(
+        "OnInterfaceAdded: duplicate interface ID %lu.", id));
+    return;
   }
-
-  if (addresses != cached_addresses_) {
-    std::swap(cached_addresses_, addresses);
+  const bool can_reach = CanReachExternalNetwork(*cache_entry);
+  interface_cache_.emplace(id, std::move(*cache_entry));
+  UpdateConnectionType();
+  if (can_reach) {
     NotifyObserversOfIPAddressChange();
   }
+}
 
-  if (connection_type != cached_connection_type_) {
+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();
   }
+}
 
-  if (on_initial_interfaces_received_) {
-    std::move(on_initial_interfaces_received_).Run();
+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
diff --git a/net/base/network_change_notifier_fuchsia.h b/net/base/network_change_notifier_fuchsia.h
index 99dc60c..cb5e002 100644
--- a/net/base/network_change_notifier_fuchsia.h
+++ b/net/base/network_change_notifier_fuchsia.h
@@ -5,19 +5,21 @@
 #ifndef NET_BASE_NETWORK_CHANGE_NOTIFIER_FUCHSIA_H_
 #define NET_BASE_NETWORK_CHANGE_NOTIFIER_FUCHSIA_H_
 
-#include <fuchsia/netstack/cpp/fidl.h>
+#include <fuchsia/net/interfaces/cpp/fidl.h>
 #include <lib/fidl/cpp/binding.h>
 
 #include <vector>
 
 #include "base/atomicops.h"
 #include "base/callback.h"
-#include "base/containers/flat_set.h"
+#include "base/containers/flat_map.h"
 #include "base/gtest_prod_util.h"
+#include "base/strings/string_piece.h"
 #include "base/threading/thread_checker.h"
 #include "net/base/net_export.h"
 #include "net/base/network_change_notifier.h"
 #include "net/base/network_interfaces.h"
+#include "net/base/network_interfaces_fuchsia.h"
 
 namespace net {
 
@@ -25,9 +27,8 @@
     : public NetworkChangeNotifier {
  public:
   // Registers for asynchronous notifications of changes to network interfaces.
-  // Interfaces are filtered by |required_features|.
-  explicit NetworkChangeNotifierFuchsia(
-      fuchsia::hardware::ethernet::Features required_features);
+  // Only WLAN interfaces are observed if |require_wlan| is requested.
+  explicit NetworkChangeNotifierFuchsia(bool require_wlan);
   NetworkChangeNotifierFuchsia(const NetworkChangeNotifierFuchsia&) = delete;
   NetworkChangeNotifierFuchsia& operator=(const NetworkChangeNotifierFuchsia&) =
       delete;
@@ -37,42 +38,55 @@
   ConnectionType GetCurrentConnectionType() const override;
 
  private:
+  using InterfacePropertiesMap =
+      base::flat_map<uint64_t, internal::InterfaceProperties>;
   friend class NetworkChangeNotifierFuchsiaTest;
 
-  // For testing purposes. Receives a |netstack| pointer for easy mocking.
-  // Interfaces are filtered by |required_features|.
   NetworkChangeNotifierFuchsia(
-      fidl::InterfaceHandle<fuchsia::netstack::Netstack> netstack,
-      fuchsia::hardware::ethernet::Features required_features,
+      fidl::InterfaceHandle<fuchsia::net::interfaces::Watcher> watcher,
+      bool require_wlan,
       SystemDnsConfigChangeNotifier* system_dns_config_notifier = nullptr);
 
-  // Forwards the network interface list along with the result of
-  // GetRouteTable() to OnRouteTableReceived().
-  void ProcessInterfaceList(
-      std::vector<fuchsia::netstack::NetInterface> interfaces);
+  // Processes events from the watcher for interface addition, change, or
+  // removal.
+  void OnInterfacesEvent(fuchsia::net::interfaces::Event event);
 
-  // Computes network change notification state change from the list of
-  // interfaces and routing table data, sending observer events if IP or
-  // connection type changes are detected.
-  void OnRouteTableReceived(
-      std::vector<fuchsia::netstack::NetInterface> interfaces,
-      std::vector<fuchsia::netstack::RouteTableEntry> table);
+  // Handlers for the interface change events. Listeners are notified of changes
+  // that affect them. |watcher_| is closed if an event is malformed in some
+  // way.
+  void OnInterfaceAdded(fuchsia::net::interfaces::Properties properties);
+  void OnInterfaceRemoved(uint64_t interface_id);
+  void OnInterfaceChanged(fuchsia::net::interfaces::Properties properties);
 
-  // Required features for an interface to be taken into account.
-  const fuchsia::hardware::ethernet::Features required_features_;
+  // Unbinds the watcher, reset the connection type and logs |error_message|.
+  void OnWatcherError(base::StringPiece error_message);
 
-  fuchsia::netstack::NetstackPtr netstack_;
+  // Updates the connection type from |interface_cache_| and notifies observers
+  // of changes.
+  void UpdateConnectionType();
 
-  // Used to allow the constructor to block until the initial state is received
-  // from |netstack_|.
-  base::OnceClosure on_initial_interfaces_received_;
+  // Resets the connection type to CONNECTION_UNKNOWN.
+  void ResetConnectionType();
+
+  // Returns the ConnectionType converted from |properties|' device_class.
+  // Returns CONNECTION_NONE if the interface is not publicly routable, taking
+  // into account the |requires_wlan_| setting.
+  ConnectionType GetEffectiveConnectionType(
+      const internal::InterfaceProperties& properties);
+
+  // Returns true if the effective connection type is not CONNECTION_NONE.
+  bool CanReachExternalNetwork(const internal::InterfaceProperties& properties);
+
+  // Whether only WLAN interfaces should be taken into account.
+  const bool require_wlan_;
+
+  fuchsia::net::interfaces::WatcherPtr watcher_;
 
   // The ConnectionType of the default network interface, stored as an atomic
   // 32-bit int for safe concurrent access.
   base::subtle::Atomic32 cached_connection_type_ = CONNECTION_UNKNOWN;
 
-  // Set of addresses from the previous query/update for the default interface.
-  base::flat_set<IPAddress> cached_addresses_;
+  InterfacePropertiesMap interface_cache_;
 
   THREAD_CHECKER(thread_checker_);
 };
diff --git a/net/base/network_change_notifier_fuchsia_unittest.cc b/net/base/network_change_notifier_fuchsia_unittest.cc
index cac2360..b2a9444 100644
--- a/net/base/network_change_notifier_fuchsia_unittest.cc
+++ b/net/base/network_change_notifier_fuchsia_unittest.cc
@@ -4,8 +4,7 @@
 
 #include "net/base/network_change_notifier_fuchsia.h"
 
-#include <fuchsia/hardware/ethernet/cpp/fidl.h>
-#include <fuchsia/netstack/cpp/fidl_test_base.h>
+#include <fuchsia/net/interfaces/cpp/fidl_test_base.h>
 #include <memory>
 #include <string>
 #include <utility>
@@ -32,18 +31,15 @@
 using IPv4Octets = std::array<uint8_t, 4>;
 using IPv6Octets = std::array<uint8_t, 16>;
 
-constexpr IPv4Octets kIPv4DefaultGatewayNetmask = {0, 0, 0, 0};
-constexpr IPv4Octets kIPv4DefaultGatewayAddress = {192, 168, 0, 1};
-
 constexpr IPv4Octets kDefaultIPv4Address = {192, 168, 0, 2};
-constexpr IPv4Octets kDefaultIPv4Netmask = {255, 255, 0, 0};
+constexpr uint8_t kDefaultIPv4Prefix = 16;
 constexpr IPv4Octets kSecondaryIPv4Address = {10, 0, 0, 1};
-constexpr IPv4Octets kSecondaryIPv4Netmask = {255, 0, 0, 0};
+constexpr uint8_t kSecondaryIPv4Prefix = 8;
 
-constexpr IPv6Octets kDefaultIPv6Address = {0xfe, 0x80, 0x01};
-constexpr IPv6Octets kDefaultIPv6Netmask = {0xfe, 0x80};
-constexpr IPv6Octets kSecondaryIPv6Address = {0xfe, 0x80, 0x02};
-constexpr IPv6Octets kSecondaryIPv6Netmask = {0xfe, 0x80};
+constexpr IPv6Octets kDefaultIPv6Address = {0x20, 0x01, 0x01};
+constexpr uint8_t kDefaultIPv6Prefix = 16;
+constexpr IPv6Octets kSecondaryIPv6Address = {0x20, 0x01, 0x02};
+constexpr uint8_t kSecondaryIPv6Prefix = 16;
 
 fuchsia::net::IpAddress IpAddressFrom(IPv4Octets octets) {
   fuchsia::net::IpAddress output;
@@ -57,130 +53,165 @@
   return output;
 }
 
-fuchsia::net::Subnet SubnetFrom(IPv6Octets octets, uint8_t prefix) {
+template <typename T>
+fuchsia::net::Subnet SubnetFrom(T octets, uint8_t prefix) {
   fuchsia::net::Subnet output;
   output.addr = IpAddressFrom(octets);
   output.prefix_len = prefix;
   return output;
 }
 
-fuchsia::netstack::NetInterface DefaultNetInterface() {
-  // For most tests a live interface with an IPv4 address and no |features| set
+template <typename T>
+fuchsia::net::interfaces::Address InterfaceAddressFrom(T octets,
+                                                       uint8_t prefix) {
+  fuchsia::net::interfaces::Address addr;
+  addr.set_addr(SubnetFrom(octets, prefix));
+  return addr;
+}
+
+template <typename T>
+std::vector<T> MakeSingleItemVec(T item) {
+  std::vector<T> vec;
+  vec.push_back(std::move(item));
+  return vec;
+}
+
+fuchsia::net::interfaces::Properties DefaultInterfaceProperties(
+    fuchsia::hardware::network::DeviceClass device_class =
+        fuchsia::hardware::network::DeviceClass::UNKNOWN) {
+  // For most tests a live interface with an IPv4 address and an unknown class
   // is sufficient.
-  fuchsia::netstack::NetInterface interface;
-  interface.id = kDefaultInterfaceId;
-  interface.flags = fuchsia::netstack::Flags::UP;
-  interface.features = {};
-  interface.addr = IpAddressFrom(kDefaultIPv4Address);
-  interface.netmask = IpAddressFrom(kDefaultIPv4Netmask);
-  interface.broadaddr = IpAddressFrom(kDefaultIPv4Address);
+  fuchsia::net::interfaces::Properties interface;
+  interface.set_id(kDefaultInterfaceId);
+  interface.set_online(true);
+  interface.set_has_default_ipv4_route(true);
+  interface.set_has_default_ipv6_route(true);
+  interface.set_device_class(fuchsia::net::interfaces::DeviceClass::WithDevice(
+      std::move(device_class)));
+  interface.set_addresses(MakeSingleItemVec(
+      InterfaceAddressFrom(kDefaultIPv4Address, kDefaultIPv4Prefix)));
   return interface;
 }
 
-fuchsia::netstack::NetInterface SecondaryNetInterface() {
-  // For most tests a live interface with an IPv4 address and no |features| set
+fuchsia::net::interfaces::Properties SecondaryInterfaceProperties() {
+  // For most tests a live interface with an IPv4 address and an unknown class
   // is sufficient.
-  fuchsia::netstack::NetInterface interface;
-  interface.id = kSecondaryInterfaceId;
-  interface.flags = fuchsia::netstack::Flags::UP;
-  interface.features = {};
-  interface.addr = IpAddressFrom(kSecondaryIPv4Address);
-  interface.netmask = IpAddressFrom(kSecondaryIPv4Netmask);
-  interface.broadaddr = IpAddressFrom(kSecondaryIPv4Address);
+  fuchsia::net::interfaces::Properties interface;
+  interface.set_id(kSecondaryInterfaceId);
+  interface.set_online(true);
+  interface.set_has_default_ipv4_route(false);
+  interface.set_has_default_ipv6_route(false);
+  interface.set_device_class(fuchsia::net::interfaces::DeviceClass::WithDevice(
+      fuchsia::hardware::network::DeviceClass::UNKNOWN));
+  interface.set_addresses(MakeSingleItemVec(
+      InterfaceAddressFrom(kSecondaryIPv4Address, kSecondaryIPv4Prefix)));
   return interface;
 }
 
-std::vector<fuchsia::netstack::NetInterface> CloneNetInterfaces(
-    const std::vector<fuchsia::netstack::NetInterface>& interfaces) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces_copy(
-      interfaces.size());
-  for (size_t i = 0; i < interfaces.size(); ++i) {
-    CHECK_EQ(ZX_OK, interfaces[i].Clone(&interfaces_copy[i]));
-  }
-  return interfaces_copy;
+template <typename F>
+fuchsia::net::interfaces::Event MakeChangeEvent(uint64_t interface_id, F fn) {
+  fuchsia::net::interfaces::Properties props;
+  props.set_id(interface_id);
+  fn(&props);
+  return fuchsia::net::interfaces::Event::WithChanged(std::move(props));
 }
 
-// Partial fake implementation of a Netstack.
-class FakeNetstack : public fuchsia::netstack::testing::Netstack_TestBase {
+// Partial fake implementation of a fuchsia.net.interfaces/Watcher.
+class FakeWatcher : public fuchsia::net::interfaces::testing::Watcher_TestBase {
  public:
-  FakeNetstack() = default;
-  FakeNetstack(const FakeNetstack&) = delete;
-  FakeNetstack& operator=(const FakeNetstack&) = delete;
-  ~FakeNetstack() override = default;
+  explicit FakeWatcher() : binding_(this) {
+    // Always create the watcher with an empty set of interfaces.
+    // Callers can override the initial set of events with SetInitial.
+    pending_.push(fuchsia::net::interfaces::Event::WithIdle(
+        fuchsia::net::interfaces::Empty{}));
+  }
+  FakeWatcher(const FakeWatcher&) = delete;
+  FakeWatcher& operator=(const FakeWatcher&) = delete;
+  ~FakeWatcher() override = default;
 
-  void Bind(
-      fidl::InterfaceRequest<fuchsia::netstack::Netstack> netstack_request) {
-    CHECK_EQ(ZX_OK, binding_.Bind(std::move(netstack_request)));
-    binding_.events().OnInterfacesChanged(CloneNetInterfaces(interfaces_));
+  void Bind(fidl::InterfaceRequest<fuchsia::net::interfaces::Watcher> request) {
+    CHECK_EQ(ZX_OK, binding_.Bind(std::move(request)));
   }
 
-  // Sets the interfaces reported by the fake Netstack and sends an
-  // OnInterfacesChanged() event to the client.
-  void SetInterfaces(std::vector<fuchsia::netstack::NetInterface> interfaces) {
-    interfaces_ = std::move(interfaces);
-    if (binding_.is_bound()) {
-      binding_.events().OnInterfacesChanged(CloneNetInterfaces(interfaces_));
+  void PushEvent(fuchsia::net::interfaces::Event event) {
+    if (pending_callback_) {
+      pending_callback_(std::move(event));
+      pending_callback_ = nullptr;
+    } else {
+      pending_.push(std::move(event));
     }
   }
 
+  void SetInitial(std::vector<fuchsia::net::interfaces::Properties> props) {
+    // Discard any pending events.
+    pending_ = std::queue<fuchsia::net::interfaces::Event>();
+    for (auto& prop : props) {
+      pending_.push(
+          fuchsia::net::interfaces::Event::WithExisting(std::move(prop)));
+    }
+    pending_.push(fuchsia::net::interfaces::Event::WithIdle(
+        fuchsia::net::interfaces::Empty{}));
+    // We should not have a pending callback already when setting initial state.
+    CHECK(!pending_callback_);
+  }
+
  private:
-  void GetRouteTable(GetRouteTableCallback callback) override {
-    CHECK(binding_.is_bound());
-    std::vector<fuchsia::netstack::RouteTableEntry> table(2);
-
-    table[0].nicid = kDefaultInterfaceId;
-    table[0].netmask = IpAddressFrom(kIPv4DefaultGatewayNetmask);
-    table[0].destination = IpAddressFrom(kDefaultIPv4Address);
-    table[0].gateway = IpAddressFrom(kIPv4DefaultGatewayAddress);
-
-    table[1].nicid = kSecondaryInterfaceId;
-    table[1].netmask = IpAddressFrom(kSecondaryIPv4Netmask);
-    table[1].destination = IpAddressFrom(kSecondaryIPv4Address);
-    table[1].gateway = IpAddressFrom(kSecondaryIPv4Address);
-
-    callback(std::move(table));
+  void Watch(WatchCallback callback) override {
+    ASSERT_FALSE(pending_callback_);
+    if (pending_.empty()) {
+      pending_callback_ = std::move(callback);
+    } else {
+      callback(std::move(pending_.front()));
+      pending_.pop();
+    }
   }
 
   void NotImplemented_(const std::string& name) override {
     LOG(FATAL) << "Unimplemented function called: " << name;
   }
 
-  std::vector<fuchsia::netstack::NetInterface> interfaces_;
-  fidl::Binding<fuchsia::netstack::Netstack> binding_{this};
+  std::queue<fuchsia::net::interfaces::Event> pending_;
+  fidl::Binding<fuchsia::net::interfaces::Watcher> binding_;
+  WatchCallback pending_callback_ = nullptr;
 };
 
-class FakeNetstackAsync {
+class FakeWatcherAsync {
  public:
-  FakeNetstackAsync() : thread_("Netstack Thread") {
+  explicit FakeWatcherAsync() {
     base::Thread::Options options(base::MessagePumpType::IO, 0);
     CHECK(thread_.StartWithOptions(options));
-    netstack_ = base::SequenceBound<FakeNetstack>(thread_.task_runner());
+    watcher_ = base::SequenceBound<FakeWatcher>(thread_.task_runner());
   }
-  FakeNetstackAsync(const FakeNetstackAsync&) = delete;
-  FakeNetstackAsync& operator=(const FakeNetstackAsync&) = delete;
-  ~FakeNetstackAsync() = default;
+  FakeWatcherAsync(const FakeWatcherAsync&) = delete;
+  FakeWatcherAsync& operator=(const FakeWatcherAsync&) = delete;
+  ~FakeWatcherAsync() = default;
 
-  void Bind(
-      fidl::InterfaceRequest<fuchsia::netstack::Netstack> netstack_request) {
-    netstack_.Post(FROM_HERE, &FakeNetstack::Bind, std::move(netstack_request));
+  void Bind(fidl::InterfaceRequest<fuchsia::net::interfaces::Watcher> request) {
+    watcher_.Post(FROM_HERE, &FakeWatcher::Bind, std::move(request));
   }
 
-  // Asynchronously update the state of the netstack.
-  void SetInterfaces(
-      const std::vector<fuchsia::netstack::NetInterface>& interfaces) {
-    netstack_.Post(FROM_HERE, &FakeNetstack::SetInterfaces,
-                   CloneNetInterfaces(interfaces));
+  // Asynchronously push an event to the watcher.
+  void PushEvent(fuchsia::net::interfaces::Event event) {
+    watcher_.Post(FROM_HERE, &FakeWatcher::PushEvent, std::move(event));
   }
 
-  // Ensures that any SetInterfaces() or SendOnInterfacesChanged() calls have
+  // Asynchronously push an initial set of interfaces to the watcher.
+  void SetInitial(std::vector<fuchsia::net::interfaces::Properties> props) {
+    watcher_.Post(FROM_HERE, &FakeWatcher::SetInitial, std::move(props));
+  }
+
+  // Asynchronously push an initial single intface to the watcher.
+  void SetInitial(fuchsia::net::interfaces::Properties prop) {
+    SetInitial(MakeSingleItemVec(std::move(prop)));
+  }
+
+  // Ensures that any PushEvent() or SetInitial() calls have
   // been processed.
-  void FlushNetstackThread() {
-    thread_.FlushForTesting();
-  }
+  void FlushThread() { thread_.FlushForTesting(); }
 
  private:
-  base::Thread thread_;
-  base::SequenceBound<FakeNetstack> netstack_;
+  base::Thread thread_{"Watcher Thread"};
+  base::SequenceBound<FakeWatcher> watcher_;
 };
 
 template <class T>
@@ -302,25 +333,22 @@
       const NetworkChangeNotifierFuchsiaTest&) = delete;
   ~NetworkChangeNotifierFuchsiaTest() override = default;
 
-  // Creates a NetworkChangeNotifier and spins the MessageLoop to allow it to
-  // populate from the list of interfaces which have already been added to
-  // |netstack_|. |observer_| is registered last, so that tests need only
-  // express expectations on changes they make themselves.
-  void CreateNotifier(
-      fuchsia::hardware::ethernet::Features required_features = {}) {
-    // Ensure that the Netstack internal state is up-to-date before the
+  // Creates a NetworkChangeNotifier that binds to |watcher_|.
+  // |observer_| is registered last, so that tests need only express
+  // expectations on changes they make themselves.
+  void CreateNotifier(bool requires_wlan = false) {
+    // Ensure that internal state is up-to-date before the
     // notifier queries it.
-    netstack_.FlushNetstackThread();
+    watcher_.FlushThread();
 
-    CHECK(!netstack_handle_);
-    netstack_.Bind(netstack_handle_.NewRequest());
+    CHECK(!watcher_handle_);
+    watcher_.Bind(watcher_handle_.NewRequest());
 
     // Use a noop DNS notifier.
     dns_config_notifier_ = std::make_unique<SystemDnsConfigChangeNotifier>(
         nullptr /* task_runner */, nullptr /* dns_config_service */);
     notifier_.reset(new NetworkChangeNotifierFuchsia(
-        std::move(netstack_handle_), required_features,
-        dns_config_notifier_.get()));
+        std::move(watcher_handle_), requires_wlan, dns_config_notifier_.get()));
 
     type_observer_ = std::make_unique<FakeConnectionTypeObserver>();
     ip_observer_ = std::make_unique<FakeIPAddressObserver>();
@@ -328,7 +356,7 @@
 
   void TearDown() override {
     // Spin the loops to catch any unintended notifications.
-    netstack_.FlushNetstackThread();
+    watcher_.FlushThread();
     base::RunLoop().RunUntilIdle();
   }
 
@@ -336,8 +364,8 @@
   base::test::SingleThreadTaskEnvironment task_environment_{
       base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
 
-  fidl::InterfaceHandle<fuchsia::netstack::Netstack> netstack_handle_;
-  FakeNetstackAsync netstack_;
+  fidl::InterfaceHandle<fuchsia::net::interfaces::Watcher> watcher_handle_;
+  FakeWatcherAsync watcher_;
 
   // Allows us to allocate our own NetworkChangeNotifier for unit testing.
   NetworkChangeNotifier::DisableForTest disable_for_test_;
@@ -356,19 +384,19 @@
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, NotifyNetworkChangeOnInitialIPChange) {
   // Set a live interface with an IP address and create the notifier.
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-  interfaces[0].features = fuchsia::hardware::ethernet::Features::WLAN;
-
-  netstack_.SetInterfaces(interfaces);
+  watcher_.SetInitial(DefaultInterfaceProperties(
+      fuchsia::hardware::network::DeviceClass::WLAN));
   CreateNotifier();
 
   // Add the NetworkChangeNotifier, and change the IP address. This should
-  // trigger a network change notification, since the IP address is out-of-sync.
+  // trigger a network change notification.
   FakeNetworkChangeObserver network_change_observer;
 
-  interfaces[0].addr = IpAddressFrom(kSecondaryIPv4Address);
-  netstack_.SetInterfaces(interfaces);
+  watcher_.PushEvent(MakeChangeEvent(
+      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
+        props->set_addresses(MakeSingleItemVec(
+            InterfaceAddressFrom(kSecondaryIPv4Address, kSecondaryIPv4Prefix)));
+      }));
 
   EXPECT_TRUE(network_change_observer.RunAndExpectNetworkChanges(
       {NetworkChangeNotifier::CONNECTION_NONE,
@@ -378,139 +406,137 @@
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, NoChange) {
   // Set a live interface with an IP address and create the notifier.
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-
-  netstack_.SetInterfaces(interfaces);
+  watcher_.SetInitial(DefaultInterfaceProperties());
   CreateNotifier();
   EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN,
             notifier_->GetCurrentConnectionType());
-
-  // Leave the set of interfaces unchanged, but re-send OnInterfacesChanged.
-  netstack_.SetInterfaces(interfaces);
+  // Push an event with no side-effects.
+  watcher_.PushEvent(MakeChangeEvent(kDefaultInterfaceId, [](auto*) {}));
 }
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, NoChangeV6) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-  interfaces[0].addr = IpAddressFrom(kDefaultIPv6Address);
-  interfaces[0].netmask = IpAddressFrom(kDefaultIPv6Netmask);
-
-  netstack_.SetInterfaces(interfaces);
+  auto initial = DefaultInterfaceProperties();
+  initial.set_addresses(MakeSingleItemVec(
+      InterfaceAddressFrom(kDefaultIPv6Address, kDefaultIPv6Prefix)));
+  watcher_.SetInitial(std::move(initial));
   CreateNotifier();
-
-  // Leave the set of interfaces unchanged, but re-send OnInterfacesChanged.
-  netstack_.SetInterfaces(interfaces);
+  // Push an event with no side-effects.
+  watcher_.PushEvent(MakeChangeEvent(kDefaultInterfaceId, [](auto*) {}));
 }
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, MultiInterfaceNoChange) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(2);
-  interfaces[0] = DefaultNetInterface();
-  interfaces[1] = SecondaryNetInterface();
-
-  netstack_.SetInterfaces(interfaces);
+  std::vector<fuchsia::net::interfaces::Properties> props;
+  props.push_back(DefaultInterfaceProperties());
+  props.push_back(SecondaryInterfaceProperties());
+  watcher_.SetInitial(std::move(props));
   CreateNotifier();
-
-  // Leave the set of interfaces unchanged, but re-send OnInterfacesChanged.
-  netstack_.SetInterfaces(interfaces);
+  // Push an event with no side-effects.
+  watcher_.PushEvent(MakeChangeEvent(kDefaultInterfaceId, [](auto*) {}));
 }
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, MultiV6IPNoChange) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-  interfaces[0].ipv6addrs.push_back(SubnetFrom(kDefaultIPv6Address, 2));
+  auto props = DefaultInterfaceProperties();
+  props.mutable_addresses()->push_back(
+      InterfaceAddressFrom(kDefaultIPv6Address, kDefaultIPv6Prefix));
+  props.mutable_addresses()->push_back(
+      InterfaceAddressFrom(kSecondaryIPv6Address, kSecondaryIPv6Prefix));
 
-  netstack_.SetInterfaces(interfaces);
+  watcher_.SetInitial(std::move(props));
   CreateNotifier();
 
-  // Leave the set of interfaces unchanged, but re-send OnInterfacesChanged.
-  netstack_.SetInterfaces(interfaces);
+  // Push an event with no side-effects.
+  watcher_.PushEvent(MakeChangeEvent(kDefaultInterfaceId, [](auto*) {}));
 }
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, IpChange) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-
-  netstack_.SetInterfaces(interfaces);
+  watcher_.SetInitial(DefaultInterfaceProperties());
   CreateNotifier();
   EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN,
             notifier_->GetCurrentConnectionType());
 
-  interfaces[0].addr = IpAddressFrom(kSecondaryIPv4Address);
-  netstack_.SetInterfaces(interfaces);
+  watcher_.PushEvent(MakeChangeEvent(
+      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
+        props->set_addresses(MakeSingleItemVec(
+            InterfaceAddressFrom(kSecondaryIPv4Address, kSecondaryIPv4Prefix)));
+      }));
 
   // Expect a single OnIPAddressChanged() notification.
   EXPECT_TRUE(ip_observer_->RunAndExpectCallCount(1));
 }
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, IpChangeV6) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-  interfaces[0].addr = IpAddressFrom(kDefaultIPv6Address);
-  interfaces[0].netmask = IpAddressFrom(kDefaultIPv6Netmask);
-  interfaces[0].broadaddr = IpAddressFrom(kDefaultIPv6Address);
-
-  netstack_.SetInterfaces(interfaces);
+  auto props = DefaultInterfaceProperties();
+  props.set_addresses(MakeSingleItemVec(
+      InterfaceAddressFrom(kDefaultIPv6Address, kDefaultIPv6Prefix)));
+  watcher_.SetInitial(std::move(props));
   CreateNotifier();
   EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN,
             notifier_->GetCurrentConnectionType());
 
-  interfaces[0].addr = IpAddressFrom(kSecondaryIPv6Address);
-  interfaces[0].netmask = IpAddressFrom(kSecondaryIPv6Netmask);
-  interfaces[0].broadaddr = IpAddressFrom(kSecondaryIPv6Address);
-  netstack_.SetInterfaces(interfaces);
+  watcher_.PushEvent(MakeChangeEvent(
+      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
+        props->set_addresses(MakeSingleItemVec(
+            InterfaceAddressFrom(kSecondaryIPv6Address, kSecondaryIPv6Prefix)));
+      }));
 
   // Expect a single OnIPAddressChanged() notification.
   EXPECT_TRUE(ip_observer_->RunAndExpectCallCount(1));
 }
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, MultiV6IPChanged) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-  interfaces[0].ipv6addrs.push_back(SubnetFrom(kDefaultIPv6Address, 2));
+  auto props = DefaultInterfaceProperties();
+  props.mutable_addresses()->push_back(
+      InterfaceAddressFrom(kDefaultIPv6Address, kDefaultIPv6Prefix));
 
-  netstack_.SetInterfaces(interfaces);
+  watcher_.SetInitial(std::move(props));
   CreateNotifier();
   EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN,
             notifier_->GetCurrentConnectionType());
 
-  interfaces[0].addr = IpAddressFrom(kSecondaryIPv4Address);
-  interfaces[0].netmask = IpAddressFrom(kSecondaryIPv4Netmask);
-  interfaces[0].broadaddr = IpAddressFrom(kSecondaryIPv4Address);
-  interfaces[0].ipv6addrs[0] = SubnetFrom(kSecondaryIPv6Address, 2);
-  netstack_.SetInterfaces(interfaces);
+  watcher_.PushEvent(MakeChangeEvent(
+      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
+        std::vector<fuchsia::net::interfaces::Address> addrs;
+        addrs.push_back(
+            InterfaceAddressFrom(kSecondaryIPv4Address, kSecondaryIPv4Prefix));
+        addrs.push_back(
+            InterfaceAddressFrom(kSecondaryIPv6Address, kSecondaryIPv6Prefix));
+        props->set_addresses(std::move(addrs));
+      }));
 
   // Expect a single OnIPAddressChanged() notification.
   EXPECT_TRUE(ip_observer_->RunAndExpectCallCount(1));
 }
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, Ipv6AdditionalIpChange) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-
-  netstack_.SetInterfaces(interfaces);
+  watcher_.SetInitial(DefaultInterfaceProperties());
   CreateNotifier();
   EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN,
             notifier_->GetCurrentConnectionType());
 
-  interfaces[0].ipv6addrs.push_back(SubnetFrom(kDefaultIPv6Address, 2));
-  netstack_.SetInterfaces(interfaces);
+  watcher_.PushEvent(MakeChangeEvent(
+      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
+        // Add the initial default address + a new IPv6 one. Address changes are
+        // always sent as the entire new list of addresses.
+        props->mutable_addresses()->push_back(
+            InterfaceAddressFrom(kDefaultIPv4Address, kDefaultIPv4Prefix));
+        props->mutable_addresses()->push_back(
+            InterfaceAddressFrom(kDefaultIPv6Address, kDefaultIPv6Prefix));
+      }));
 
   // Expect a single OnIPAddressChanged() notification.
   EXPECT_TRUE(ip_observer_->RunAndExpectCallCount(1));
 }
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, InterfaceDown) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-
-  netstack_.SetInterfaces(interfaces);
+  watcher_.SetInitial(DefaultInterfaceProperties());
   CreateNotifier();
   EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN,
             notifier_->GetCurrentConnectionType());
 
-  interfaces[0].flags = {};
-  netstack_.SetInterfaces(interfaces);
+  watcher_.PushEvent(MakeChangeEvent(
+      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
+        props->set_online(false);
+      }));
 
   EXPECT_TRUE(type_observer_->RunAndExpectConnectionTypes(
       {NetworkChangeNotifier::ConnectionType::CONNECTION_NONE}));
@@ -518,17 +544,17 @@
 }
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, InterfaceUp) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-  interfaces[0].flags = {};
-
-  netstack_.SetInterfaces(interfaces);
+  auto props = DefaultInterfaceProperties();
+  props.set_online(false);
+  watcher_.SetInitial(std::move(props));
   CreateNotifier();
   EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_NONE,
             notifier_->GetCurrentConnectionType());
 
-  interfaces[0].flags = fuchsia::netstack::Flags::UP;
-  netstack_.SetInterfaces(interfaces);
+  watcher_.PushEvent(MakeChangeEvent(
+      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
+        props->set_online(true);
+      }));
 
   EXPECT_TRUE(type_observer_->RunAndExpectConnectionTypes(
       {NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN}));
@@ -536,15 +562,13 @@
 }
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, InterfaceDeleted) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-
-  netstack_.SetInterfaces(interfaces);
+  watcher_.SetInitial(DefaultInterfaceProperties());
   CreateNotifier();
   EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN,
             notifier_->GetCurrentConnectionType());
 
-  netstack_.SetInterfaces({});
+  watcher_.PushEvent(
+      fuchsia::net::interfaces::Event::WithRemoved(kDefaultInterfaceId));
 
   EXPECT_TRUE(type_observer_->RunAndExpectConnectionTypes(
       {NetworkChangeNotifier::ConnectionType::CONNECTION_NONE}));
@@ -557,11 +581,9 @@
   EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_NONE,
             notifier_->GetCurrentConnectionType());
 
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-  interfaces[0].features = fuchsia::hardware::ethernet::Features::WLAN;
-
-  netstack_.SetInterfaces(interfaces);
+  watcher_.PushEvent(
+      fuchsia::net::interfaces::Event::WithAdded(DefaultInterfaceProperties(
+          fuchsia::hardware::network::DeviceClass::WLAN)));
 
   EXPECT_TRUE(type_observer_->RunAndExpectConnectionTypes(
       {NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI}));
@@ -569,56 +591,44 @@
 }
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, SecondaryInterfaceAddedNoop) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-
-  netstack_.SetInterfaces(interfaces);
+  watcher_.SetInitial(DefaultInterfaceProperties());
   CreateNotifier();
 
-  interfaces.push_back(SecondaryNetInterface());
-  netstack_.SetInterfaces(interfaces);
+  watcher_.PushEvent(fuchsia::net::interfaces::Event::WithAdded(
+      SecondaryInterfaceProperties()));
 }
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, SecondaryInterfaceDeletedNoop) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(2);
-  interfaces[0] = DefaultNetInterface();
-  interfaces[1] = SecondaryNetInterface();
+  std::vector<fuchsia::net::interfaces::Properties> interfaces;
+  interfaces.push_back(DefaultInterfaceProperties());
+  interfaces.push_back(SecondaryInterfaceProperties());
 
-  netstack_.SetInterfaces(interfaces);
+  watcher_.SetInitial(std::move(interfaces));
   CreateNotifier();
 
-  interfaces.pop_back();
-  netstack_.SetInterfaces(interfaces);
+  watcher_.PushEvent(
+      fuchsia::net::interfaces::Event::WithRemoved(kSecondaryInterfaceId));
 }
 
 TEST_F(NetworkChangeNotifierFuchsiaTest, FoundWiFi) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-  interfaces[0].features = fuchsia::hardware::ethernet::Features::WLAN;
-
-  netstack_.SetInterfaces(interfaces);
+  watcher_.SetInitial(DefaultInterfaceProperties(
+      fuchsia::hardware::network::DeviceClass::WLAN));
   CreateNotifier();
   EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI,
             notifier_->GetCurrentConnectionType());
 }
 
-TEST_F(NetworkChangeNotifierFuchsiaTest, FindsInterfaceWithRequiredFeature) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-  interfaces[0].features = fuchsia::hardware::ethernet::Features::WLAN;
-
-  netstack_.SetInterfaces(interfaces);
-  CreateNotifier(fuchsia::hardware::ethernet::Features::WLAN);
+TEST_F(NetworkChangeNotifierFuchsiaTest, FindsInterfaceWithRequiredWlan) {
+  watcher_.SetInitial(DefaultInterfaceProperties(
+      fuchsia::hardware::network::DeviceClass::WLAN));
+  CreateNotifier(/*require_wlan=*/true);
   EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI,
             notifier_->GetCurrentConnectionType());
 }
 
-TEST_F(NetworkChangeNotifierFuchsiaTest, IgnoresInterfaceWithMissingFeature) {
-  std::vector<fuchsia::netstack::NetInterface> interfaces(1);
-  interfaces[0] = DefaultNetInterface();
-
-  netstack_.SetInterfaces(interfaces);
-  CreateNotifier(fuchsia::hardware::ethernet::Features::WLAN);
+TEST_F(NetworkChangeNotifierFuchsiaTest, IgnoresNonWlanInterface) {
+  watcher_.SetInitial(DefaultInterfaceProperties());
+  CreateNotifier(/*require_wlan=*/true);
   EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_NONE,
             notifier_->GetCurrentConnectionType());
 }
diff --git a/net/base/network_interfaces_fuchsia.cc b/net/base/network_interfaces_fuchsia.cc
index 47aa4dc..e04c349 100644
--- a/net/base/network_interfaces_fuchsia.cc
+++ b/net/base/network_interfaces_fuchsia.cc
@@ -4,9 +4,6 @@
 
 #include "net/base/network_interfaces_fuchsia.h"
 
-#include <fuchsia/hardware/ethernet/cpp/fidl.h>
-#include <fuchsia/net/cpp/fidl.h>
-#include <fuchsia/netstack/cpp/fidl.h>
 #include <lib/sys/cpp/component_context.h>
 
 #include <string>
@@ -16,124 +13,222 @@
 #include "base/fuchsia/fuchsia_logging.h"
 #include "base/fuchsia/process_context.h"
 #include "base/strings/stringprintf.h"
-#include "net/base/ip_endpoint.h"
 #include "net/base/network_interfaces.h"
 
 namespace net {
 namespace internal {
 namespace {
 
-using ConnectionType = NetworkChangeNotifier::ConnectionType;
-
-// Converts a Netstack NetInterface |interface| to a Chrome NetworkInterface.
-// NetInterfaces may be bound to multiple IPv6 addresses. |address_index| is
-// used to specify which address to use for the conversion.
-//   address_index = 0: Uses NetInterface::addr, NetInterface::netmask.
-//   address_index >= 1: Uses NetInterface::ipv6addrs[], with the array index
-//                       offset by one.
-NetworkInterface NetworkInterfaceFromAddress(
-    const fuchsia::netstack::NetInterface& interface,
-    size_t address_index) {
-  // TODO(crbug.com/1131220): attributes field is used to return address state
-  // for IPv6 addresses. Currently Netstack doesn't provide this information.
-  const int attributes = 0;
-
-  IPAddress address;
-  uint8_t prefix_length;
-  if (address_index == 0) {
-    address = FuchsiaIpAddressToIPAddress(interface.addr);
-    prefix_length =
-        MaskPrefixLength(FuchsiaIpAddressToIPAddress(interface.netmask));
-  } else {
-    CHECK_LE(address_index, interface.ipv6addrs.size());
-    address = FuchsiaIpAddressToIPAddress(
-        interface.ipv6addrs.at(address_index - 1).addr);
-    prefix_length = interface.ipv6addrs.at(address_index - 1).prefix_len;
+IPAddress FuchsiaIpAddressToIPAddress(const fuchsia::net::IpAddress& address) {
+  switch (address.Which()) {
+    case fuchsia::net::IpAddress::kIpv4:
+      return IPAddress(address.ipv4().addr.data(), address.ipv4().addr.size());
+    case fuchsia::net::IpAddress::kIpv6:
+      return IPAddress(address.ipv6().addr.data(), address.ipv6().addr.size());
+    default:
+      return IPAddress();
   }
-
-  return NetworkInterface(interface.name, interface.name, interface.id,
-                          ConvertConnectionType(interface), address,
-                          prefix_length, attributes);
 }
 
 }  // namespace
 
+// static
+base::Optional<InterfaceProperties> InterfaceProperties::VerifyAndCreate(
+    fuchsia::net::interfaces::Properties properties) {
+  if (!internal::VerifyCompleteInterfaceProperties(properties))
+    return base::nullopt;
+  return base::make_optional(InterfaceProperties(std::move(properties)));
+}
+
+InterfaceProperties::InterfaceProperties(
+    fuchsia::net::interfaces::Properties properties)
+    : properties_(std::move(properties)) {}
+
+InterfaceProperties::InterfaceProperties(InterfaceProperties&& interface) =
+    default;
+
+InterfaceProperties& InterfaceProperties::operator=(
+    InterfaceProperties&& interface) = default;
+
+InterfaceProperties::~InterfaceProperties() = default;
+
+bool InterfaceProperties::Update(
+    fuchsia::net::interfaces::Properties properties) {
+  if (!properties.has_id() || properties_.id() != properties.id()) {
+    LOG(WARNING) << "Update failed: invalid properties.";
+    return false;
+  }
+
+  if (properties.has_addresses()) {
+    for (const auto& fidl_address : properties.addresses()) {
+      if (!fidl_address.has_addr()) {
+        LOG(WARNING) << "Update failed: invalid properties.";
+        return false;
+      }
+    }
+    properties_.set_addresses(std::move(*properties.mutable_addresses()));
+  }
+
+  if (properties.has_online())
+    properties_.set_online(properties.online());
+  if (properties.has_has_default_ipv4_route())
+    properties_.set_has_default_ipv4_route(properties.has_default_ipv4_route());
+  if (properties.has_has_default_ipv6_route())
+    properties_.set_has_default_ipv6_route(properties.has_default_ipv6_route());
+
+  return true;
+}
+
+void InterfaceProperties::AppendNetworkInterfaces(
+    NetworkInterfaceList* interfaces) const {
+  uint32_t address_counter = 1;
+  for (const auto& fidl_address : properties_.addresses()) {
+    IPAddress address = FuchsiaIpAddressToIPAddress(fidl_address.addr().addr);
+    if (address.empty()) {
+      LOG(WARNING) << "Unknown fuchsia.net/IpAddress variant "
+                   << fidl_address.addr().addr.Which();
+      continue;
+    }
+
+    // TODO(crbug.com/1131220): Set correct attributes once available in
+    // fuchsia::net::interfaces::Properties.
+    const int kAttributes = 0;
+    std::string interface_name = base::StringPrintf(
+        "%s-%d", properties_.name().c_str(), address_counter);
+    address_counter++;
+    interfaces->emplace_back(
+        interface_name, interface_name, properties_.id(),
+        internal::ConvertConnectionType(properties_.device_class()),
+        std::move(address), fidl_address.addr().prefix_len, kAttributes);
+  }
+}
+
+bool InterfaceProperties::IsPubliclyRoutable() const {
+  if (!properties_.online())
+    return false;
+
+  for (const auto& fidl_address : properties_.addresses()) {
+    const IPAddress address =
+        FuchsiaIpAddressToIPAddress(fidl_address.addr().addr);
+    if ((address.IsIPv4() && properties_.has_default_ipv4_route() &&
+         !address.IsLinkLocal()) ||
+        (address.IsIPv6() && properties_.has_default_ipv6_route() &&
+         address.IsPubliclyRoutable())) {
+      return true;
+    }
+  }
+  return false;
+}
+
 NetworkChangeNotifier::ConnectionType ConvertConnectionType(
-    const fuchsia::netstack::NetInterface& iface) {
-  if ((iface.flags & fuchsia::netstack::Flags::UP) !=
-      fuchsia::netstack::Flags::UP) {
-    return NetworkChangeNotifier::CONNECTION_NONE;
-  } else if ((iface.features & fuchsia::hardware::ethernet::Features::WLAN) ==
-             fuchsia::hardware::ethernet::Features::WLAN) {
-    return NetworkChangeNotifier::CONNECTION_WIFI;
+    const fuchsia::net::interfaces::DeviceClass& device_class) {
+  switch (device_class.Which()) {
+    case fuchsia::net::interfaces::DeviceClass::kLoopback:
+      return NetworkChangeNotifier::CONNECTION_NONE;
+    case fuchsia::net::interfaces::DeviceClass::kDevice:
+      switch (device_class.device()) {
+        case fuchsia::hardware::network::DeviceClass::WLAN:
+          return NetworkChangeNotifier::CONNECTION_WIFI;
+        case fuchsia::hardware::network::DeviceClass::ETHERNET:
+          return NetworkChangeNotifier::CONNECTION_ETHERNET;
+        default:
+          return NetworkChangeNotifier::CONNECTION_UNKNOWN;
+      }
+    default:
+      LOG(WARNING) << "Received unknown fuchsia.net.interfaces/DeviceClass "
+                   << device_class.Which();
+      return NetworkChangeNotifier::CONNECTION_UNKNOWN;
   }
-  return NetworkChangeNotifier::CONNECTION_UNKNOWN;
 }
 
-IPAddress FuchsiaIpAddressToIPAddress(const fuchsia::net::IpAddress& addr) {
-  if (addr.is_ipv4()) {
-    return IPAddress(addr.ipv4().addr.data(), addr.ipv4().addr.size());
-  }
-  if (addr.is_ipv6()) {
-    return IPAddress(addr.ipv6().addr.data(), addr.ipv6().addr.size());
-  }
-  return IPAddress();
+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()";
+
+  fuchsia::net::interfaces::WatcherHandle watcher;
+  state->GetWatcher({} /*options*/, watcher.NewRequest());
+  return watcher;
 }
 
-std::vector<NetworkInterface> NetInterfaceToNetworkInterfaces(
-    const fuchsia::netstack::NetInterface& iface_in) {
-  std::vector<NetworkInterface> output;
-
-  // If the interface is not currently up then there are no addresses to return.
-  if (internal::ConvertConnectionType(iface_in) ==
-      NetworkChangeNotifier::CONNECTION_NONE) {
-    return output;
+bool VerifyCompleteInterfaceProperties(
+    const fuchsia::net::interfaces::Properties& properties) {
+  if (!properties.has_id())
+    return false;
+  if (!properties.has_addresses())
+    return false;
+  for (const auto& fidl_address : properties.addresses()) {
+    if (!fidl_address.has_addr())
+      return false;
   }
+  if (!properties.has_online())
+    return false;
+  if (!properties.has_device_class())
+    return false;
+  if (!properties.has_has_default_ipv4_route())
+    return false;
+  if (!properties.has_has_default_ipv6_route())
+    return false;
+  return true;
+}
 
-  // It is possible for the interface not to have an IPv4 address.
-  NetworkInterface ipv4_interface = NetworkInterfaceFromAddress(iface_in, 0);
-  if (!ipv4_interface.address.IsZero())
-    output.push_back(std::move(ipv4_interface));
+base::Optional<ExistingInterfaceProperties> GetExistingInterfaces(
+    const fuchsia::net::interfaces::WatcherSyncPtr& watcher) {
+  ExistingInterfaceProperties existing_interfaces;
+  for (;;) {
+    fuchsia::net::interfaces::Event event;
+    zx_status_t status = watcher->Watch(&event);
+    if (status != ZX_OK)
+      return base::nullopt;
 
-  // Append interface entries for all additional IPv6 addresses.
-  for (size_t i = 0; i < iface_in.ipv6addrs.size(); ++i) {
-    output.push_back(NetworkInterfaceFromAddress(iface_in, i + 1));
+    switch (event.Which()) {
+      case fuchsia::net::interfaces::Event::Tag::kExisting: {
+        base::Optional<InterfaceProperties> interface =
+            InterfaceProperties::VerifyAndCreate(std::move(event.existing()));
+        if (!interface) {
+          return base::nullopt;
+        }
+        uint64_t id = interface->id();
+        existing_interfaces.emplace_back(id, std::move(*interface));
+        break;
+      }
+      case fuchsia::net::interfaces::Event::Tag::kIdle:
+        // Idle means we've listed all the existing interfaces. We can stop
+        // fetching events.
+        return existing_interfaces;
+      default:
+        return base::nullopt;
+    }
   }
-
-  return output;
 }
 
 }  // namespace internal
 
 bool GetNetworkList(NetworkInterfaceList* networks, int policy) {
   DCHECK(networks);
-
-  fuchsia::netstack::NetstackSyncPtr netstack;
-  base::ComponentContextForProcess()->svc()->Connect(netstack.NewRequest());
+  fuchsia::net::interfaces::WatcherHandle handle =
+      internal::ConnectInterfacesWatcher();
+  fuchsia::net::interfaces::WatcherSyncPtr watcher = handle.BindSync();
 
   // TODO(crbug.com/1131238): Use NetworkChangeNotifier's cached interface
   // list.
-  std::vector<fuchsia::netstack::NetInterface> interfaces;
-  zx_status_t status = netstack->GetInterfaces(&interfaces);
-  if (status != ZX_OK) {
-    ZX_LOG(ERROR, status) << "fuchsia::netstack::GetInterfaces()";
+  base::Optional<internal::ExistingInterfaceProperties> existing_interfaces =
+      internal::GetExistingInterfaces(watcher);
+  if (!existing_interfaces)
     return false;
-  }
-
-  for (auto& interface : interfaces) {
-    if ((internal::ConvertConnectionType(interface) ==
-         NetworkChangeNotifier::CONNECTION_NONE) ||
-        (interface.features &
-         fuchsia::hardware::ethernet::Features::LOOPBACK) ==
-            fuchsia::hardware::ethernet::Features::LOOPBACK) {
+  handle = watcher.Unbind();
+  for (const auto& interface_entry : *existing_interfaces) {
+    if (!interface_entry.second.online()) {
+      // GetNetworkList() only returns online interfaces.
       continue;
     }
-
-    auto converted = internal::NetInterfaceToNetworkInterfaces(interface);
-    std::move(converted.begin(), converted.end(),
-              std::back_inserter(*networks));
+    if (interface_entry.second.device_class().is_loopback()) {
+      // GetNetworkList() returns all interfaces except loopback.
+      continue;
+    }
+    interface_entry.second.AppendNetworkInterfaces(networks);
   }
-
   return true;
 }
 
diff --git a/net/base/network_interfaces_fuchsia.h b/net/base/network_interfaces_fuchsia.h
index b9dcf9c..38ced90 100644
--- a/net/base/network_interfaces_fuchsia.h
+++ b/net/base/network_interfaces_fuchsia.h
@@ -5,41 +5,81 @@
 #ifndef NET_BASE_NETWORK_INTERFACES_FUCHSIA_H_
 #define NET_BASE_NETWORK_INTERFACES_FUCHSIA_H_
 
+#include <fuchsia/net/interfaces/cpp/fidl.h>
+
 #include <vector>
 
 #include "net/base/network_change_notifier.h"
-
-namespace fuchsia {
-namespace net {
-class IpAddress;
-}
-namespace netstack {
-class NetInterface;
-}  // namespace netstack
-}  // namespace fuchsia
+#include "net/base/network_interfaces.h"
 
 namespace net {
-
-class IPAddress;
-struct NetworkInterface;
-
 namespace internal {
 
+// Move-only wrapper for fuchsia::net::interface::Properties that guarantees
+// that its inner |properties_| are valid and complete properties as reported by
+// |VerifyCompleteInterfaceProperties|.
+class InterfaceProperties final {
+ public:
+  // Creates an |InterfaceProperties| if |properties| are valid complete
+  // properties as reported by |VerifyCompleteInterfaceProperties|.
+  static base::Optional<InterfaceProperties> VerifyAndCreate(
+      fuchsia::net::interfaces::Properties properties);
+  InterfaceProperties(InterfaceProperties&& interface);
+  InterfaceProperties& operator=(InterfaceProperties&& interface);
+  ~InterfaceProperties();
+
+  // Updates this instance with the values set in |properties|.
+  // Fields not set in |properties| retain their previous values.
+  // Returns false if the |properties| has a missing or mismatched |id| field,
+  // or if any field set in |properties| has an invalid value (e.g. addresses of
+  // unknown types).
+  bool Update(fuchsia::net::interfaces::Properties properties);
+
+  // Appends the NetworkInterfaces for this interface to |interfaces|.
+  void AppendNetworkInterfaces(NetworkInterfaceList* interfaces) const;
+
+  // Returns true if the interface is online and it has either an IPv4 default
+  // route and a non-link-local address, or an IPv6 default route and a global
+  // address.
+  bool IsPubliclyRoutable() const;
+
+  bool HasAddresses() const { return !properties_.addresses().empty(); }
+  uint64_t id() const { return properties_.id(); }
+  bool online() const { return properties_.online(); }
+  const fuchsia::net::interfaces::DeviceClass& device_class() const {
+    return properties_.device_class();
+  }
+
+ private:
+  explicit InterfaceProperties(fuchsia::net::interfaces::Properties properties);
+
+  fuchsia::net::interfaces::Properties properties_;
+};
+
+using ExistingInterfaceProperties =
+    std::vector<std::pair<uint64_t, InterfaceProperties>>;
+
 // Returns the //net ConnectionType for the supplied netstack interface
-// description. Returns ConnectionType::CONNECTION_NONE if the interface is not
-// "up".
+// description. Returns CONNECTION_NONE for loopback interfaces.
 NetworkChangeNotifier::ConnectionType ConvertConnectionType(
-    const fuchsia::netstack::NetInterface& iface);
+    const fuchsia::net::interfaces::DeviceClass& device_class);
 
-// Converts a Fuchsia Netstack NetInterface object to NetworkInterface objects.
-// Interfaces with more than one IPv6 address will yield multiple
-// NetworkInterface objects, with friendly names to distinguish the different
-// IPs (e.g. "wlan" with three IPv6 IPs yields wlan-0, wlan-1, wlan-2).
-std::vector<NetworkInterface> NetInterfaceToNetworkInterfaces(
-    const fuchsia::netstack::NetInterface& iface_in);
+// Connects to the service via the process' ComponentContext, and connects the
+// Watcher to the service.
+fuchsia::net::interfaces::WatcherHandle ConnectInterfacesWatcher();
 
-// Converts a Fuchsia IPv4/IPv6 address to a Chromium IPAddress.
-IPAddress FuchsiaIpAddressToIPAddress(const fuchsia::net::IpAddress& addr);
+// Validates that |properties| contains all the required fields, returning
+// |true| if so.
+bool VerifyCompleteInterfaceProperties(
+    const fuchsia::net::interfaces::Properties& properties);
+
+// Consumes events describing existing interfaces from |watcher| and
+// returns a vector of interface Id & properties pairs.  |watcher| must
+// be a newly-connected Watcher channel.
+// Returns base::nullopt if any protocol error is encountered, e.g.
+// |watcher| supplies an invalid event, or disconnects.
+base::Optional<internal::ExistingInterfaceProperties> GetExistingInterfaces(
+    const fuchsia::net::interfaces::WatcherSyncPtr& watcher);
 
 }  // namespace internal
 }  // namespace net