[fdio] Initial implementation of getifaddrs

Sets up the API plumbing and implement getifaddrs to the point where a
list of all addresses are returned.

Due to lack of support in the current version of the netstack,
broadcast address (missing IFF_BROADCAST flag) and destination address
are not set. They will be added when corresponding support is added in
the netstack.

Support for AF_PACKET addresses will be added separately.

Test:
fx test -d getifaddrs-netemul-test
fx test --host getifaddrs-test

Bug: 30719, 54162, 54163

Change-Id: I52910b5bbf5a4d493fd9b591e1dbb2203aa51ef9
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/398773
Commit-Queue: Jay Zhuang <jayzhuang@google.com>
Reviewed-by: Tamir Duberstein <tamird@google.com>
Reviewed-by: Adam Barth <abarth@google.com>
Reviewed-by: Chris Tam <godtamit@google.com>
Testability-Review: Tamir Duberstein <tamird@google.com>
API-Review: Adam Barth <abarth@google.com>
diff --git a/sdk/fidl/fuchsia.net.stack/stack.fidl b/sdk/fidl/fuchsia.net.stack/stack.fidl
index 49f179e..13f01fd 100644
--- a/sdk/fidl/fuchsia.net.stack/stack.fidl
+++ b/sdk/fidl/fuchsia.net.stack/stack.fidl
@@ -46,6 +46,7 @@
     3: AdministrativeStatus administrative;
 };
 
+// TODO(https://fxbug.dev/54163): use `fuchsia.net.InterfaceAddress` instead.
 struct InterfaceAddress {
     /// The IP address of the interface.
     fuchsia.net.IpAddress ip_address;
diff --git a/sdk/fidl/fuchsia.net/net.fidl b/sdk/fidl/fuchsia.net/net.fidl
index a47f8c7..9e761c1 100644
--- a/sdk/fidl/fuchsia.net/net.fidl
+++ b/sdk/fidl/fuchsia.net/net.fidl
@@ -33,11 +33,11 @@
     uint16 port;
 };
 
-/// Subnet describes an IP subnetwork, where all host IP addresses share the same most significant
-/// bits.
+// TODO(https://fxbug.dev/54163): rename Subnet to AddressWithPrefix when
+// binding support is ready, so we don't have to recursively fix all users.
+/// An IP address with its subnet prefix length.
 struct Subnet {
-    /// The Ipv4 or Ipv6 address. Only the `prefix_len` most significant bits may be set in `addr`;
-    /// all bits in the host portion of the address must be zero.
+    /// The Ipv4 or Ipv6 address.
     IpAddress addr;
 
     /// The prefix length of the netmask. E.g. for 192.168.1.0/24, the prefix
diff --git a/sdk/fidl/fuchsia.posix.socket/BUILD.gn b/sdk/fidl/fuchsia.posix.socket/BUILD.gn
index 2d10404..e94afa9e 100644
--- a/sdk/fidl/fuchsia.posix.socket/BUILD.gn
+++ b/sdk/fidl/fuchsia.posix.socket/BUILD.gn
@@ -8,5 +8,8 @@
   sdk_category = "partner"
   api = "fuchsia.posix.socket.api"
   sources = [ "socket.fidl" ]
-  public_deps = [ "//sdk/fidl/fuchsia.io" ]
+  public_deps = [
+    "//sdk/fidl/fuchsia.io",
+    "//sdk/fidl/fuchsia.net",
+  ]
 }
diff --git a/sdk/fidl/fuchsia.posix.socket/fuchsia.posix.socket.api b/sdk/fidl/fuchsia.posix.socket/fuchsia.posix.socket.api
index 7c1cec4..733fdba 100644
--- a/sdk/fidl/fuchsia.posix.socket/fuchsia.posix.socket.api
+++ b/sdk/fidl/fuchsia.posix.socket/fuchsia.posix.socket.api
@@ -1,3 +1,3 @@
 {
-  "fidl/fuchsia.posix.socket": "43a271948d74960c04d7ff1aeadd087f"
+  "fidl/fuchsia.posix.socket": "a3ac470444e23f55ad16c4d05755c8b1"
 }
\ No newline at end of file
diff --git a/sdk/fidl/fuchsia.posix.socket/socket.fidl b/sdk/fidl/fuchsia.posix.socket/socket.fidl
index 9d47b74..159cafb 100644
--- a/sdk/fidl/fuchsia.posix.socket/socket.fidl
+++ b/sdk/fidl/fuchsia.posix.socket/socket.fidl
@@ -5,6 +5,7 @@
 library fuchsia.posix.socket;
 
 using fuchsia.io;
+using fuchsia.net;
 using zx;
 
 /// Chosen to match `sizeof(struct sockaddr_storage)`.
@@ -87,6 +88,19 @@
     Accept(int16 flags) -> (StreamSocket s) error int32;
 };
 
+/// Holds information about an interface and its addresses.
+table InterfaceAddresses {
+    /// ID of the interface.
+    1: uint64 id;
+    /// Name of the interface.
+    2: interface_name name;
+    /// Contains the interface flags, as returned by the SIOCGIFFLAGS ioctl
+    /// operation.
+    3: uint32 flags;
+    /// All addresses currently assigned to the interface.
+    4: vector<fuchsia.net.Subnet>:MAX addresses;
+};
+
 /// Provider implements the POSIX sockets API.
 [Discoverable]
 protocol Provider {
@@ -99,4 +113,8 @@
     /// Looks up an interface by its name and returns its index. Returns `ZX_ERR_NOT_FOUND` if the
     /// specified name doesn't exist.
     InterfaceNameToIndex(interface_name name) -> (uint64 index) error zx.status;
+
+    /// Requests a list of [`fuchsia.posix.socket.InterfaceAddresses`]
+    /// describing the network interfaces on the system.
+    GetInterfaceAddresses() -> (vector<InterfaceAddresses>:MAX interfaces);
 };
diff --git a/sdk/lib/fdio/bsdsocket.cc b/sdk/lib/fdio/bsdsocket.cc
index 2540a82..3e1b3ff 100644
--- a/sdk/lib/fdio/bsdsocket.cc
+++ b/sdk/lib/fdio/bsdsocket.cc
@@ -4,8 +4,10 @@
 
 #include <fcntl.h>
 #include <fuchsia/net/llcpp/fidl.h>
+#include <ifaddrs.h>
 #include <lib/fdio/fdio.h>
 #include <lib/fdio/io.h>
+#include <net/if.h>
 #include <netdb.h>
 #include <poll.h>
 #include <sys/socket.h>
@@ -504,3 +506,79 @@
   }
   return 0;
 }
+
+// TODO(https://fxbug.dev/30719): set ifa_ifu.ifu_broadaddr and ifa_ifu.ifu_dstaddr.
+//
+// AF_PACKET addresses containing lower-level details about the interfaces are not included in the
+// result list because raw sockets are not supported on Fuchsia.
+__EXPORT
+int getifaddrs(struct ifaddrs** ifap) {
+  fsocket::Provider::SyncClient* provider = nullptr;
+  zx_status_t status = fdio_get_socket_provider(&provider);
+  if (status != ZX_OK) {
+    return ERROR(status);
+  }
+
+  auto response = provider->GetInterfaceAddresses();
+  status = response.status();
+  if (status != ZX_OK) {
+    return ERROR(status);
+  }
+
+  for (const auto& iface : response.Unwrap()->interfaces) {
+    if (!iface.has_name() || !iface.has_addresses()) {
+      continue;
+    }
+
+    const auto& if_name = iface.name();
+    for (const auto& address : iface.addresses()) {
+      auto ifs = static_cast<struct ifaddrs_storage*>(calloc(1, sizeof(struct ifaddrs_storage)));
+      if (ifs == nullptr) {
+        return -1;
+      }
+      const size_t n = std::min(if_name.size(), sizeof(ifs->name));
+      memcpy(ifs->name, if_name.data(), n);
+      ifs->name[n] = 0;
+      ifs->ifa.ifa_name = ifs->name;
+
+      const auto& addr = address.addr;
+      const uint8_t prefix_len = address.prefix_len;
+
+      switch (addr.which()) {
+        case fnet::IpAddress::Tag::kIpv4: {
+          const auto& addr_bytes = addr.ipv4().addr;
+          copy_addr(&ifs->ifa.ifa_addr, AF_INET, &ifs->addr,
+                    const_cast<uint8_t*>(addr_bytes.data()), addr_bytes.size(), iface.id());
+          gen_netmask(&ifs->ifa.ifa_netmask, AF_INET, &ifs->netmask, prefix_len);
+          break;
+        }
+        case fnet::IpAddress::Tag::kIpv6: {
+          const auto& addr_bytes = addr.ipv6().addr;
+          copy_addr(&ifs->ifa.ifa_addr, AF_INET6, &ifs->addr,
+                    const_cast<uint8_t*>(addr_bytes.data()), addr_bytes.size(), iface.id());
+          gen_netmask(&ifs->ifa.ifa_netmask, AF_INET6, &ifs->netmask, prefix_len);
+          break;
+        }
+      }
+
+      if (iface.has_flags()) {
+        ifs->ifa.ifa_flags = iface.flags();
+      }
+
+      *ifap = &ifs->ifa;
+      ifap = &ifs->ifa.ifa_next;
+    }
+  }
+
+  return 0;
+}
+
+__EXPORT
+void freeifaddrs(struct ifaddrs* ifp) {
+  struct ifaddrs* n;
+  while (ifp) {
+    n = ifp->ifa_next;
+    free(ifp);
+    ifp = n;
+  }
+}
diff --git a/sdk/lib/fdio/fdio.symbols.api b/sdk/lib/fdio/fdio.symbols.api
index 0e2a55d..d1f7851 100644
--- a/sdk/lib/fdio/fdio.symbols.api
+++ b/sdk/lib/fdio/fdio.symbols.api
@@ -75,6 +75,7 @@
 fdio_watch_directory
 fdio_zxio_create
 fdopendir
+freeifaddrs
 fstat
 fstatat
 fstatfs
@@ -83,6 +84,7 @@
 ftruncate
 futimens
 getcwd
+getifaddrs
 getpeername
 getsockname
 getsockopt
diff --git a/src/connectivity/network/netstack/fuchsia_posix_socket.go b/src/connectivity/network/netstack/fuchsia_posix_socket.go
index 87091cd..b5182d9 100644
--- a/src/connectivity/network/netstack/fuchsia_posix_socket.go
+++ b/src/connectivity/network/netstack/fuchsia_posix_socket.go
@@ -11,6 +11,7 @@
 	"net"
 	"reflect"
 	"runtime"
+	"sort"
 	"strings"
 	"sync"
 	"sync/atomic"
@@ -19,10 +20,13 @@
 	"syscall/zx/zxsocket"
 	"syscall/zx/zxwait"
 
+	"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/fidlconv"
+	"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/link"
 	"go.fuchsia.dev/fuchsia/src/lib/component"
 	syslog "go.fuchsia.dev/fuchsia/src/lib/syslog/go"
 
 	"fidl/fuchsia/io"
+	fidlnet "fidl/fuchsia/net"
 	"fidl/fuchsia/posix/socket"
 
 	"gvisor.dev/gvisor/pkg/tcpip"
@@ -43,6 +47,7 @@
 #cgo CFLAGS: -I${SRCDIR}/../../../../zircon/third_party/ulib/musl/include
 #include <errno.h>
 #include <fcntl.h>
+#include <net/if.h>
 #include <netinet/in.h>
 */
 import "C"
@@ -1276,6 +1281,71 @@
 	return socket.ProviderInterfaceNameToIndexResultWithErr(int32(zx.ErrNotFound)), nil
 }
 
+func (sp *providerImpl) GetInterfaceAddresses(fidl.Context) ([]socket.InterfaceAddresses, error) {
+	nicInfos := sp.ns.stack.NICInfo()
+
+	resultInfos := make([]socket.InterfaceAddresses, 0, len(nicInfos))
+	for id, info := range nicInfos {
+		// Ensure deterministic API response.
+		sort.Slice(info.ProtocolAddresses, func(i, j int) bool {
+			x, y := info.ProtocolAddresses[i], info.ProtocolAddresses[j]
+			if x.Protocol != y.Protocol {
+				return x.Protocol < y.Protocol
+			}
+			ax, ay := x.AddressWithPrefix, y.AddressWithPrefix
+			if ax.Address != ay.Address {
+				return ax.Address < ay.Address
+			}
+			return ax.PrefixLen < ay.PrefixLen
+		})
+
+		addrs := make([]fidlnet.Subnet, 0, len(info.ProtocolAddresses))
+		for _, a := range info.ProtocolAddresses {
+			if a.Protocol != ipv4.ProtocolNumber && a.Protocol != ipv6.ProtocolNumber {
+				continue
+			}
+			addrs = append(addrs, fidlnet.Subnet{
+				Addr:      fidlconv.ToNetIpAddress(a.AddressWithPrefix.Address),
+				PrefixLen: uint8(a.AddressWithPrefix.PrefixLen),
+			})
+		}
+
+		var resultInfo socket.InterfaceAddresses
+		resultInfo.SetId(uint64(id))
+		resultInfo.SetName(info.Name)
+		resultInfo.SetAddresses(addrs)
+
+		// gVisor assumes interfaces are always up, which is not the case on Fuchsia,
+		// so overwrite it with Fuchsia's interface state.
+		ifs := info.Context.(*ifState)
+		var bits uint32
+		flags := info.Flags
+		if flags.Running {
+			bits |= C.IFF_RUNNING
+		}
+		if flags.Promiscuous {
+			bits |= C.IFF_PROMISC
+		}
+		if flags.Loopback {
+			bits |= C.IFF_LOOPBACK
+		}
+		ifs.mu.Lock()
+		if ifs.mu.state == link.StateStarted {
+			bits |= C.IFF_UP
+		}
+		ifs.mu.Unlock()
+		resultInfo.SetFlags(bits)
+
+		resultInfos = append(resultInfos, resultInfo)
+	}
+
+	// Ensure deterministic API response.
+	sort.Slice(resultInfos, func(i, j int) bool {
+		return resultInfos[i].Id < resultInfos[j].Id
+	})
+	return resultInfos, nil
+}
+
 func tcpipErrorToCode(err *tcpip.Error) int32 {
 	if err != tcpip.ErrConnectStarted {
 		if pc, file, line, ok := runtime.Caller(1); ok {
diff --git a/src/connectivity/network/netstack3/src/bindings/socket/mod.rs b/src/connectivity/network/netstack3/src/bindings/socket/mod.rs
index cb5f8da..1569ee0 100644
--- a/src/connectivity/network/netstack3/src/bindings/socket/mod.rs
+++ b/src/connectivity/network/netstack3/src/bindings/socket/mod.rs
@@ -143,6 +143,10 @@
             psocket::ProviderRequest::Socket2 { domain, type_, protocol: _, responder } => {
                 responder_send!(responder, &mut self.socket(domain, type_));
             }
+            psocket::ProviderRequest::GetInterfaceAddresses { responder } => {
+                // TODO(https://fxbug.dev/54162): implement this method.
+                responder_send!(responder, &mut std::iter::empty());
+            }
         }
     }
 
diff --git a/src/connectivity/network/tests/BUILD.gn b/src/connectivity/network/tests/BUILD.gn
index ec17a69..6925969 100644
--- a/src/connectivity/network/tests/BUILD.gn
+++ b/src/connectivity/network/tests/BUILD.gn
@@ -20,6 +20,7 @@
     ":netstack_external_network_test_client($host_toolchain)",
     ":netstack_if_nameindex_test($host_toolchain)",
     "benchmarks",
+    "getifaddrs:tests",
     "integration:tests",
   ]
 }
diff --git a/src/connectivity/network/tests/getifaddrs/BUILD.gn b/src/connectivity/network/tests/getifaddrs/BUILD.gn
new file mode 100644
index 0000000..f2bbf34
--- /dev/null
+++ b/src/connectivity/network/tests/getifaddrs/BUILD.gn
@@ -0,0 +1,41 @@
+# Copyright 2020 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/test.gni")
+import("//src/sys/build/components.gni")
+
+test("getifaddrs-test") {
+  if (is_linux || is_fuchsia) {
+    sources = [ "getifaddrs_test.cc" ]
+  }
+  deps = [ "//src/lib/fxl/test:gtest_main" ]
+}
+
+fuchsia_component("getifaddrs-test-component") {
+  testonly = true
+  manifest = "meta/getifaddrs-test.cmx"
+  deps = [ ":getifaddrs-test" ]
+}
+
+fuchsia_component("getifaddrs-netemul-test") {
+  testonly = true
+  manifest = "meta/getifaddrs-netemul-test.cmx"
+  deps = [ ":getifaddrs-test-component" ]
+}
+
+fuchsia_test_package("getifaddrs-tests") {
+  test_components = [ ":getifaddrs-netemul-test" ]
+}
+
+group("tests") {
+  testonly = true
+
+  deps = [
+    ":getifaddrs-test($host_toolchain)",
+    ":getifaddrs-tests",
+
+    # TODO(brunodalbo): move this to component level when netemul can handle it.
+    "//src/connectivity/network/testing/netemul",
+  ]
+}
diff --git a/src/connectivity/network/tests/getifaddrs/getifaddrs_test.cc b/src/connectivity/network/tests/getifaddrs/getifaddrs_test.cc
new file mode 100644
index 0000000..115acd5
--- /dev/null
+++ b/src/connectivity/network/tests/getifaddrs/getifaddrs_test.cc
@@ -0,0 +1,136 @@
+// Copyright 2020 The Fuchsia 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 <arpa/inet.h>
+#include <errno.h>
+#include <ifaddrs.h>
+// <net/if.h> doesn't contain the full list of interface flags on Linux.
+#if defined(__linux__)
+#include <linux/if.h>
+#else
+#include <net/if.h>
+#endif
+
+#include <algorithm>
+#include <string>
+#include <tuple>
+#include <unordered_set>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace {
+
+uint8_t count_prefix(const uint8_t* mask, size_t len) {
+  uint8_t l = 0;
+  for (size_t i = 0; i < len; i++) {
+    auto m = mask[i];
+    for (size_t j = 0; j < sizeof(mask); j++) {
+      if (m) {
+        l++;
+        m <<= 1;
+      } else {
+        return l;
+      }
+    }
+  }
+  return l;
+}
+
+TEST(GetIfAddrsTest, GetIfAddrsTest) {
+  const uint32_t unsupported_flags = IFF_BROADCAST | IFF_DEBUG | IFF_POINTOPOINT | IFF_NOTRAILERS |
+                                     IFF_NOARP | IFF_ALLMULTI | IFF_MASTER | IFF_SLAVE |
+                                     IFF_MULTICAST | IFF_PORTSEL | IFF_AUTOMEDIA | IFF_DYNAMIC |
+                                     IFF_LOWER_UP | IFF_DORMANT | IFF_ECHO;
+
+  // Fields of this tuple are: interface_name, address, prefix_length, scope_id, flags.
+  using InterfaceAddress = std::tuple<std::string, std::string, uint8_t, uint32_t, uint32_t>;
+
+  std::vector<InterfaceAddress> want_ifaddrs{
+      std::make_tuple("lo", "127.0.0.1", 8, 0, IFF_LOOPBACK | IFF_UP | IFF_RUNNING),
+      std::make_tuple("lo", "::1", 128, 0, IFF_LOOPBACK | IFF_UP | IFF_RUNNING),
+  };
+  const size_t lo_addrs_count = want_ifaddrs.size();
+
+#if defined(__Fuchsia__)
+  want_ifaddrs.push_back(std::make_tuple("ep1", "192.168.0.1", 20, 0, IFF_UP | IFF_RUNNING));
+  want_ifaddrs.push_back(std::make_tuple("ep2", "192.168.0.2", 15, 0, IFF_UP | IFF_RUNNING));
+  want_ifaddrs.push_back(std::make_tuple("ep3", "fe80::1", 64, 4, IFF_UP | IFF_RUNNING));
+  want_ifaddrs.push_back(std::make_tuple("ep4", "1234::5:6:7:8", 120, 0, IFF_UP | IFF_RUNNING));
+#endif
+
+  struct ifaddrs* ifaddr;
+  ASSERT_EQ(getifaddrs(&ifaddr), 0) << strerror(errno);
+
+  std::vector<InterfaceAddress> got_ifaddrs;
+  // Expect one auto-generated link-local address per non-loopback interface.
+  const size_t want_unknown_link_local_addrs = want_ifaddrs.size() - lo_addrs_count;
+  size_t got_unknown_link_local_addrs = 0;
+
+  for (auto it = ifaddr; it != nullptr; it = it->ifa_next) {
+    const auto if_name = std::string(it->ifa_name);
+#if defined(__linux__)
+    // Only loopback is consistent on host environments.
+    if (if_name != "lo") {
+      continue;
+    }
+#endif
+
+    switch (it->ifa_addr->sa_family) {
+      case AF_INET: {
+        struct sockaddr_in* addr_in = reinterpret_cast<sockaddr_in*>(it->ifa_addr);
+        char sin_addr_buf[INET_ADDRSTRLEN];
+        const char* sin_addr =
+            inet_ntop(AF_INET, &addr_in->sin_addr, sin_addr_buf, INET_ADDRSTRLEN);
+
+        const sockaddr_in* netmask = reinterpret_cast<sockaddr_in*>(it->ifa_netmask);
+        const uint8_t prefix_len =
+            count_prefix(reinterpret_cast<const uint8_t*>(&netmask->sin_addr.s_addr), 4);
+
+        got_ifaddrs.push_back(std::make_tuple(if_name, std::string(sin_addr), prefix_len, 0,
+                                              it->ifa_flags & ~unsupported_flags));
+        break;
+      }
+      case AF_INET6: {
+        struct sockaddr_in6* addr_in6 = reinterpret_cast<sockaddr_in6*>(it->ifa_addr);
+        char sin6_addr_buf[INET6_ADDRSTRLEN];
+        const char* sin6_addr =
+            inet_ntop(AF_INET6, &(addr_in6->sin6_addr), sin6_addr_buf, INET6_ADDRSTRLEN);
+
+        const std::string sin6_addr_str = std::string(sin6_addr);
+
+        const bool is_known_addr = std::any_of(want_ifaddrs.begin(), want_ifaddrs.end(),
+                                               [sin6_addr_str](const InterfaceAddress& ifaddr) {
+                                                 return std::get<1>(ifaddr) == sin6_addr_str;
+                                               });
+
+        // Skip and count auto-generated link-local addresses.
+        if (IN6_IS_ADDR_LINKLOCAL(addr_in6->sin6_addr.s6_addr) && !is_known_addr) {
+          got_unknown_link_local_addrs++;
+          continue;
+        }
+
+        const sockaddr_in6* netmask = reinterpret_cast<sockaddr_in6*>(it->ifa_netmask);
+        const uint8_t prefix_len = count_prefix(netmask->sin6_addr.s6_addr, 16);
+
+        got_ifaddrs.push_back(std::make_tuple(if_name, sin6_addr_str, prefix_len,
+                                              addr_in6->sin6_scope_id,
+                                              it->ifa_flags & ~unsupported_flags));
+        break;
+      }
+      case AF_PACKET:
+        // Ignore AF_PACKET addresses because raw sockets are not supported on Fuchsia.
+        continue;
+      default:
+        GTEST_FAIL() << "unexpected address family " << it->ifa_addr->sa_family;
+    }
+  }
+
+  EXPECT_EQ(got_ifaddrs, want_ifaddrs);
+  EXPECT_EQ(got_unknown_link_local_addrs, want_unknown_link_local_addrs);
+
+  freeifaddrs(ifaddr);
+}
+
+}  // namespace
diff --git a/src/connectivity/network/tests/getifaddrs/meta/getifaddrs-netemul-test.cmx b/src/connectivity/network/tests/getifaddrs/meta/getifaddrs-netemul-test.cmx
new file mode 100644
index 0000000..a742b2f
--- /dev/null
+++ b/src/connectivity/network/tests/getifaddrs/meta/getifaddrs-netemul-test.cmx
@@ -0,0 +1,78 @@
+{
+    "facets": {
+        "fuchsia.netemul": {
+            "environment": {
+                "services": {
+                    "fuchsia.net.stack.Stack": "fuchsia-pkg://fuchsia.com/netstack#meta/netstack_debug.cmx",
+                    "fuchsia.netstack.Netstack": "fuchsia-pkg://fuchsia.com/netstack#meta/netstack_debug.cmx",
+                    "fuchsia.posix.socket.Provider": "fuchsia-pkg://fuchsia.com/netstack#meta/netstack_debug.cmx"
+                },
+                "setup": [
+                    {
+                        "arguments": [
+                            "-e",
+                            "ep1",
+                            "-i",
+                            "192.168.0.1/20"
+                        ],
+                        "url": "fuchsia-pkg://fuchsia.com/netemul_sandbox#meta/helper_netstack_cfg.cmx"
+                    },
+                    {
+                        "arguments": [
+                            "-e",
+                            "ep2",
+                            "-i",
+                            "192.168.0.2/15"
+                        ],
+                        "url": "fuchsia-pkg://fuchsia.com/netemul_sandbox#meta/helper_netstack_cfg.cmx"
+                    },
+                    {
+                        "arguments": [
+                            "-e",
+                            "ep3",
+                            "-i",
+                            "fe80::1/64"
+                        ],
+                        "url": "fuchsia-pkg://fuchsia.com/netemul_sandbox#meta/helper_netstack_cfg.cmx"
+                    },
+                    {
+                        "arguments": [
+                            "-e",
+                            "ep4",
+                            "-i",
+                            "1234::5:6:7:8/120"
+                        ],
+                        "url": "fuchsia-pkg://fuchsia.com/netemul_sandbox#meta/helper_netstack_cfg.cmx"
+                    }
+                ],
+                "test": [
+                    "fuchsia-pkg://fuchsia.com/getifaddrs-tests#meta/getifaddrs-test-component.cmx"
+                ]
+            },
+            "networks": [
+                {
+                    "endpoints": [
+                        {
+                            "name": "ep1"
+                        },
+                        {
+                            "name": "ep2"
+                        },
+                        {
+                            "name": "ep3"
+                        },
+                        {
+                            "name": "ep4"
+                        }
+                    ],
+                    "name": "net"
+                }
+            ],
+            "timeout": 120
+        }
+    },
+    "program": {
+        "binary": "test/getifaddrs-netemul-test"
+    },
+    "runner": "fuchsia-pkg://fuchsia.com/netemul_runner#meta/netemul_runner.cmx"
+}
diff --git a/src/connectivity/network/tests/getifaddrs/meta/getifaddrs-test.cmx b/src/connectivity/network/tests/getifaddrs/meta/getifaddrs-test.cmx
new file mode 100644
index 0000000..2c1aba7
--- /dev/null
+++ b/src/connectivity/network/tests/getifaddrs/meta/getifaddrs-test.cmx
@@ -0,0 +1,11 @@
+{
+    "program": {
+        "binary": "test/getifaddrs-test"
+    },
+    "sandbox": {
+        "services": [
+            "fuchsia.net.stack.Stack",
+            "fuchsia.posix.socket.Provider"
+        ]
+    }
+}
diff --git a/zircon/third_party/ulib/musl/src/network/getifaddrs.c b/zircon/third_party/ulib/musl/src/network/getifaddrs.c
index 7ca9a6e..70bf1a5 100644
--- a/zircon/third_party/ulib/musl/src/network/getifaddrs.c
+++ b/zircon/third_party/ulib/musl/src/network/getifaddrs.c
@@ -2,15 +2,6 @@
 
 #include "getifaddrs.h"
 
-void freeifaddrs(struct ifaddrs* ifp) {
-  struct ifaddrs* n;
-  while (ifp) {
-    n = ifp->ifa_next;
-    free(ifp);
-    ifp = n;
-  }
-}
-
 static int netlink_msg_to_ifaddr(void* pctx, struct nlmsghdr* h) {
   struct ifaddrs_ctx* ctx = pctx;
   struct ifaddrs_storage *ifs, *ifs0 = NULL;
@@ -122,15 +113,3 @@
   }
   return 0;
 }
-
-int getifaddrs(struct ifaddrs** ifap) {
-  struct ifaddrs_ctx _ctx, *ctx = &_ctx;
-  int r;
-  memset(ctx, 0, sizeof *ctx);
-  r = __rtnetlink_enumerate(AF_UNSPEC, AF_UNSPEC, netlink_msg_to_ifaddr, ctx);
-  if (r == 0)
-    *ifap = &ctx->first->ifa;
-  else
-    freeifaddrs(&ctx->first->ifa);
-  return r;
-}
diff --git a/zircon/third_party/ulib/musl/stubs/socketstubs.c b/zircon/third_party/ulib/musl/stubs/socketstubs.c
index 5ca3cf2..429c835 100644
--- a/zircon/third_party/ulib/musl/stubs/socketstubs.c
+++ b/zircon/third_party/ulib/musl/stubs/socketstubs.c
@@ -1,5 +1,6 @@
 #define _GNU_SOURCE
 #include <errno.h>
+#include <ifaddrs.h>
 #include <netdb.h>
 #include <sys/socket.h>
 #include <sys/types.h>
@@ -118,3 +119,12 @@
   return -1;
 }
 weak_alias(stub_sockatmark, sockatmark);
+
+static int stub_getifaddrs(struct ifaddrs** ifap) {
+  errno = ENOSYS;
+  return -1;
+}
+weak_alias(stub_getifaddrs, getifaddrs);
+
+static void stub_freeifaddrs(struct ifaddrs* ifp) {}
+weak_alias(stub_freeifaddrs, freeifaddrs);