[libc-commits] [libc] b2cca30 - [libc] Add if_nameindex and if_freenameindex as an experimental entrypoint (#208438)
via libc-commits
libc-commits at lists.llvm.org
Mon Aug 3 03:31:30 PDT 2026
Author: Pavel Labath
Date: 2026-08-03T12:31:25+02:00
New Revision: b2cca30e126db355314677852f2b6139be1c2f3b
URL: https://github.com/llvm/llvm-project/commit/b2cca30e126db355314677852f2b6139be1c2f3b
DIFF: https://github.com/llvm/llvm-project/commit/b2cca30e126db355314677852f2b6139be1c2f3b.diff
LOG: [libc] Add if_nameindex and if_freenameindex as an experimental entrypoint (#208438)
This is a fairly complex function, so this patch implements bare minimum
needed to demonstrate the overall direction. The functionality is
implemented by communicating with the linux kernel over a netlink
socket, and the complexity comes from parsing the messages. In this
patch, parsing stops at the first interface, and we don't handle
multiple messages or deduplication resulting from restarted dumps.
Some notable implementation choices are:
- using a single allocation (including both the interface vector and the
strings it points to). This makes deallocation fast, reduces heap
fragmentation, and doesn't make the final code much more complex since
we still need auxiliary data structures for deduplication.
- using linux kernel headers for all the netlink structures. I think
this is fine as we're not exposing that to the user.
- while this information is available via /sys, that would mean we
require /sys to be mounted, and it wouldn't make the code much simpler.
This also matches other libc implementations.
Testing is a completely separate story, and the main source of
complexity of this patch. To make the code testable without requiring
root or live interfaces, the core function
(`net::if_nameindex<Policy>()`) is templated on a syscall policy class
(`NetworkSyscallPolicy`). The production entrypoint uses
`DefaultNetworkSyscallPolicy` forwarding directly to syscall wrappers
(`socket`, `sendto`, `recvfrom`, `close`), while the unit tests use
`FakeNetworkSyscallPolicy` to feed crafted netlink buffers from memory
and verify `sendto` request formation.
The fake policy is set up such that one can program the mock to return
certain values, and validate arguments. It does not resemble the
googletest functionality (mocks) very closely, but I've tried to put it
in a form where it is possible to morph it into that -- if that's the
direction we desire to go.
While doing this, I ran into the limitations of various container
classes (mainly the inability to handle non-trivial types). To avoid
growing the scope of this even further, I worked around those
limitations locally, and left TODOs which I intend to resolve in
follow-ups. The only thing I couldn't avoid is the addition of
cpp::expected default constructor (which is actually a consequence of
how FixedVector constructs elements). However, this is a simple addition
and it is consistent with the STL class it is emulating.
The patch also add the [AP]F_NETLINK constants needed for creating
netlink sockets.
Added:
libc/hdr/types/struct_if_nameindex.h
libc/include/llvm-libc-types/struct_if_nameindex.h
libc/src/__support/OSUtil/linux/network_syscall_policy.h
libc/src/net/if_freenameindex.h
libc/src/net/if_nameindex.h
libc/src/net/linux/if_freenameindex.cpp
libc/src/net/linux/if_nameindex.cpp
libc/src/net/linux/if_nameindex_impl.h
libc/test/src/net/linux/if_nameindex_test.cpp
Modified:
libc/config/linux/aarch64/entrypoints.txt
libc/config/linux/riscv/entrypoints.txt
libc/config/linux/x86_64/entrypoints.txt
libc/hdr/types/CMakeLists.txt
libc/include/CMakeLists.txt
libc/include/llvm-libc-macros/linux/sys-socket-macros.h
libc/include/llvm-libc-types/CMakeLists.txt
libc/include/net/if.yaml
libc/src/__support/CPP/expected.h
libc/src/__support/OSUtil/linux/CMakeLists.txt
libc/src/net/CMakeLists.txt
libc/src/net/linux/CMakeLists.txt
libc/test/src/net/linux/CMakeLists.txt
Removed:
################################################################################
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 1ccb6cf202cf7..b5b50f3216205 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1331,6 +1331,10 @@ endif()
if(LLVM_LIBC_ENABLE_EXPERIMENTAL_ENTRYPOINTS)
if(LLVM_LIBC_FULL_BUILD)
list(APPEND TARGET_LIBC_ENTRYPOINTS
+ # net/if.h entrypoints
+ libc.src.net.if_freenameindex
+ libc.src.net.if_nameindex
+
# regex.h entrypoints
libc.src.regex.regcomp
libc.src.regex.regexec
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 5083417b453be..2d67b0d98698d 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1559,6 +1559,10 @@ if(LLVM_LIBC_ENABLE_EXPERIMENTAL_ENTRYPOINTS)
if(LLVM_LIBC_FULL_BUILD)
list(APPEND TARGET_LIBC_ENTRYPOINTS
+ # net/if.h entrypoints
+ libc.src.net.if_freenameindex
+ libc.src.net.if_nameindex
+
# regex.h entrypoints
libc.src.regex.regcomp
libc.src.regex.regexec
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index bf809e268f585..255197d2a5dd8 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1568,6 +1568,10 @@ if(LLVM_LIBC_ENABLE_EXPERIMENTAL_ENTRYPOINTS)
if(LLVM_LIBC_FULL_BUILD)
list(APPEND TARGET_LIBC_ENTRYPOINTS
+ # net/if.h entrypoints
+ libc.src.net.if_freenameindex
+ libc.src.net.if_nameindex
+
# regex.h entrypoints
libc.src.regex.regcomp
libc.src.regex.regexec
diff --git a/libc/hdr/types/CMakeLists.txt b/libc/hdr/types/CMakeLists.txt
index 8e4b2175cb7cd..23dbd98d52203 100644
--- a/libc/hdr/types/CMakeLists.txt
+++ b/libc/hdr/types/CMakeLists.txt
@@ -599,6 +599,15 @@ add_proxy_header_library(
libc.include.net_if
)
+add_proxy_header_library(
+ struct_if_nameindex
+ HDRS
+ struct_if_nameindex.h
+ FULL_BUILD_DEPENDS
+ libc.include.llvm-libc-types.struct_if_nameindex
+ libc.include.net_if
+)
+
add_proxy_header_library(
struct_sockaddr
HDRS
diff --git a/libc/hdr/types/struct_if_nameindex.h b/libc/hdr/types/struct_if_nameindex.h
new file mode 100644
index 0000000000000..569e33a7c53ed
--- /dev/null
+++ b/libc/hdr/types/struct_if_nameindex.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Proxy for struct if_nameindex.
+///
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_LIBC_HDR_TYPES_STRUCT_IF_NAMEINDEX_H
+#define LLVM_LIBC_HDR_TYPES_STRUCT_IF_NAMEINDEX_H
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-types/struct_if_nameindex.h"
+
+#else
+
+#include <net/if.h>
+
+#endif // LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_TYPES_STRUCT_IF_NAMEINDEX_H
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index 196490dcfdcc1..add1411a9f95c 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -955,6 +955,7 @@ add_header_macro(
DEPENDS
.llvm_libc_common_h
.llvm-libc-macros.net_if_macros
+ .llvm-libc-types.struct_if_nameindex
.llvm-libc-types.struct_ifreq
)
diff --git a/libc/include/llvm-libc-macros/linux/sys-socket-macros.h b/libc/include/llvm-libc-macros/linux/sys-socket-macros.h
index 5478521be02b6..2c7d55c114328 100644
--- a/libc/include/llvm-libc-macros/linux/sys-socket-macros.h
+++ b/libc/include/llvm-libc-macros/linux/sys-socket-macros.h
@@ -12,11 +12,12 @@
// IEEE Std 1003.1-2017 - basedefs/sys_socket.h.html
// Macro values come from the Linux syscall interface.
-#define AF_UNSPEC 0 // Unspecified
-#define AF_UNIX 1 // Unix domain sockets
-#define AF_LOCAL 1 // POSIX name for AF_UNIX
-#define AF_INET 2 // Internet IPv4 Protocol
-#define AF_INET6 10 // IP version 6
+#define AF_UNSPEC 0 // Unspecified
+#define AF_UNIX 1 // Unix domain sockets
+#define AF_LOCAL 1 // POSIX name for AF_UNIX
+#define AF_INET 2 // Internet IPv4 Protocol
+#define AF_INET6 10 // IP version 6
+#define AF_NETLINK 16 // Netlink sockets
// 4.2BSD protocol families
#define PF_UNSPEC AF_UNSPEC
@@ -24,6 +25,7 @@
#define PF_LOCAL AF_LOCAL
#define PF_INET AF_INET
#define PF_INET6 AF_INET6
+#define PF_NETLINK AF_NETLINK
#define SOCK_STREAM 1
#define SOCK_DGRAM 2
diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt
index 3286ebe54d9a2..e17d8ec5794fa 100644
--- a/libc/include/llvm-libc-types/CMakeLists.txt
+++ b/libc/include/llvm-libc-types/CMakeLists.txt
@@ -262,6 +262,7 @@ add_header(
)
add_header(struct_sockaddr_un HDR struct_sockaddr_un.h DEPENDS .sa_family_t)
add_header(struct_ifreq HDR struct_ifreq.h DEPENDS libc.include.llvm-libc-macros.net_if_macros .struct_sockaddr)
+add_header(struct_if_nameindex HDR struct_if_nameindex.h)
add_header(struct_iovec HDR struct_iovec.h DEPENDS .size_t)
add_header(struct_linger HDR struct_linger.h)
add_header(struct_ucred HDR struct_ucred.h DEPENDS .pid_t .uid_t .gid_t)
diff --git a/libc/include/llvm-libc-types/struct_if_nameindex.h b/libc/include/llvm-libc-types/struct_if_nameindex.h
new file mode 100644
index 0000000000000..12bd79f7b6106
--- /dev/null
+++ b/libc/include/llvm-libc-types/struct_if_nameindex.h
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Definition of struct if_nameindex.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_TYPES_STRUCT_IF_NAMEINDEX_H
+#define LLVM_LIBC_TYPES_STRUCT_IF_NAMEINDEX_H
+
+/// Structure storing a network interface index and its corresponding name.
+struct if_nameindex {
+ unsigned int if_index;
+ char *if_name;
+};
+
+#endif // LLVM_LIBC_TYPES_STRUCT_IF_NAMEINDEX_H
diff --git a/libc/include/net/if.yaml b/libc/include/net/if.yaml
index 51034d84e4428..3b6d807e2fceb 100644
--- a/libc/include/net/if.yaml
+++ b/libc/include/net/if.yaml
@@ -6,9 +6,16 @@ macros:
macro_header: net-if-macros.h
types:
- type_name: struct_ifreq
+ - type_name: struct_if_nameindex
enums: []
objects: []
functions:
+ - name: if_freenameindex
+ standards:
+ - posix
+ return_type: void
+ arguments:
+ - type: struct if_nameindex *
- name: if_indextoname
standards:
- posix
@@ -16,6 +23,11 @@ functions:
arguments:
- type: unsigned int
- type: char *
+ - name: if_nameindex
+ standards:
+ - posix
+ return_type: struct if_nameindex *
+ arguments: []
- name: if_nametoindex
standards:
- posix
diff --git a/libc/src/__support/CPP/expected.h b/libc/src/__support/CPP/expected.h
index d416422ba2b50..4cdad71a444b3 100644
--- a/libc/src/__support/CPP/expected.h
+++ b/libc/src/__support/CPP/expected.h
@@ -40,6 +40,7 @@ template <class T, class E> class expected {
bool is_expected;
public:
+ LIBC_INLINE constexpr expected() : exp(), is_expected(true) {}
LIBC_INLINE constexpr expected(T exp) : exp(exp), is_expected(true) {}
LIBC_INLINE constexpr expected(unexpected<E> unexp)
: unexp(unexp.error()), is_expected(false) {}
diff --git a/libc/src/__support/OSUtil/linux/CMakeLists.txt b/libc/src/__support/OSUtil/linux/CMakeLists.txt
index 87d0fb862fad1..93f3d6d5190aa 100644
--- a/libc/src/__support/OSUtil/linux/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/CMakeLists.txt
@@ -40,6 +40,22 @@ add_header_library(
libc.src.__support.threads.callonce
)
+add_header_library(
+ network_syscall_policy
+ HDRS
+ network_syscall_policy.h
+ DEPENDS
+ libc.hdr.types.socklen_t
+ libc.hdr.types.ssize_t
+ libc.hdr.types.struct_sockaddr
+ libc.src.__support.OSUtil.linux.syscall_wrappers.close
+ libc.src.__support.OSUtil.linux.syscall_wrappers.recvfrom
+ libc.src.__support.OSUtil.linux.syscall_wrappers.sendto
+ libc.src.__support.OSUtil.linux.syscall_wrappers.socket
+ libc.src.__support.common
+ libc.src.__support.error_or
+)
+
add_header_library(
path
HDRS
diff --git a/libc/src/__support/OSUtil/linux/network_syscall_policy.h b/libc/src/__support/OSUtil/linux/network_syscall_policy.h
new file mode 100644
index 0000000000000..f02111800db3a
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/network_syscall_policy.h
@@ -0,0 +1,58 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Policy class wrapper for network-related system calls.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_NETWORK_SYSCALL_POLICY_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_NETWORK_SYSCALL_POLICY_H
+
+#include "hdr/types/socklen_t.h"
+#include "hdr/types/ssize_t.h"
+#include "hdr/types/struct_sockaddr.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/recvfrom.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/sendto.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/socket.h"
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace net {
+
+struct DefaultNetworkSyscallPolicy {
+ LIBC_INLINE static ErrorOr<int> socket(int domain, int type, int protocol) {
+ return linux_syscalls::socket(domain, type, protocol);
+ }
+
+ LIBC_INLINE static ErrorOr<ssize_t> sendto(int fd, const void *buf,
+ size_t len, int flags,
+ const struct sockaddr *dest_addr,
+ socklen_t addrlen) {
+ return linux_syscalls::sendto(fd, buf, len, flags, dest_addr, addrlen);
+ }
+
+ LIBC_INLINE static ErrorOr<ssize_t> recvfrom(int fd, void *buf, size_t len,
+ int flags,
+ struct sockaddr *src_addr,
+ socklen_t *addrlen) {
+ return linux_syscalls::recvfrom(fd, buf, len, flags, src_addr, addrlen);
+ }
+
+ LIBC_INLINE static ErrorOr<int> close(int fd) {
+ return linux_syscalls::close(fd);
+ }
+};
+
+} // namespace net
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_NETWORK_SYSCALL_POLICY_H
diff --git a/libc/src/net/CMakeLists.txt b/libc/src/net/CMakeLists.txt
index 2173a111cff4f..2860ba2be02cb 100644
--- a/libc/src/net/CMakeLists.txt
+++ b/libc/src/net/CMakeLists.txt
@@ -15,3 +15,17 @@ add_entrypoint_object(
DEPENDS
.${LIBC_TARGET_OS}.if_nametoindex
)
+
+add_entrypoint_object(
+ if_nameindex
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.if_nameindex
+)
+
+add_entrypoint_object(
+ if_freenameindex
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.if_freenameindex
+)
diff --git a/libc/src/net/if_freenameindex.h b/libc/src/net/if_freenameindex.h
new file mode 100644
index 0000000000000..52aea1eb55ba4
--- /dev/null
+++ b/libc/src/net/if_freenameindex.h
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header for if_freenameindex.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_NET_IF_FREENAMEINDEX_H
+#define LLVM_LIBC_SRC_NET_IF_FREENAMEINDEX_H
+
+#include "hdr/types/struct_if_nameindex.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+/// Frees the memory allocated by if_nameindex().
+///
+/// \param ptr Pointer to the array of if_nameindex structures returned by
+/// if_nameindex(). If ptr is nullptr, this function does nothing.
+void if_freenameindex(struct if_nameindex *ptr);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_NET_IF_FREENAMEINDEX_H
diff --git a/libc/src/net/if_nameindex.h b/libc/src/net/if_nameindex.h
new file mode 100644
index 0000000000000..966cb440b5408
--- /dev/null
+++ b/libc/src/net/if_nameindex.h
@@ -0,0 +1,32 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header for if_nameindex.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_NET_IF_NAMEINDEX_H
+#define LLVM_LIBC_SRC_NET_IF_NAMEINDEX_H
+
+#include "hdr/types/struct_if_nameindex.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+/// Returns an array of if_nameindex structures containing the interface index
+/// and name of all network interfaces on the system.
+///
+/// The array is terminated by an element with if_index == 0 and if_name ==
+/// nullptr. On failure, returns nullptr and sets errno. The returned pointer
+/// must be freed by passing it to if_freenameindex().
+struct if_nameindex *if_nameindex();
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_NET_IF_NAMEINDEX_H
diff --git a/libc/src/net/linux/CMakeLists.txt b/libc/src/net/linux/CMakeLists.txt
index 9c82636d1c54c..1e9c0143f2e94 100644
--- a/libc/src/net/linux/CMakeLists.txt
+++ b/libc/src/net/linux/CMakeLists.txt
@@ -42,3 +42,52 @@ add_entrypoint_object(
libc.src.errno.errno
libc.src.string.memory_utils.inline_memcpy
)
+
+add_header_library(
+ if_nameindex_impl
+ HDRS
+ if_nameindex_impl.h
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.net_if_macros
+ libc.hdr.sys_socket_macros
+ libc.hdr.types.socklen_t
+ libc.hdr.types.ssize_t
+ libc.hdr.types.struct_if_nameindex
+ libc.src.__support.CPP.new
+ libc.src.__support.CPP.scope
+ libc.src.__support.CPP.span
+ libc.src.__support.alloc_checker
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.string.memory_utils.inline_memcpy
+ libc.src.string.string_utils
+)
+
+add_entrypoint_object(
+ if_nameindex
+ SRCS
+ if_nameindex.cpp
+ HDRS
+ ../if_nameindex.h
+ DEPENDS
+ .if_nameindex_impl
+ libc.hdr.types.struct_if_nameindex
+ libc.src.__support.OSUtil.linux.network_syscall_policy
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.errno.errno
+)
+
+add_entrypoint_object(
+ if_freenameindex
+ SRCS
+ if_freenameindex.cpp
+ HDRS
+ ../if_freenameindex.h
+ DEPENDS
+ libc.hdr.stdint_proxy
+ libc.hdr.types.struct_if_nameindex
+ libc.src.__support.CPP.new
+ libc.src.__support.common
+)
diff --git a/libc/src/net/linux/if_freenameindex.cpp b/libc/src/net/linux/if_freenameindex.cpp
new file mode 100644
index 0000000000000..229d679797e26
--- /dev/null
+++ b/libc/src/net/linux/if_freenameindex.cpp
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Linux implementation of if_freenameindex.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/net/if_freenameindex.h"
+#include "hdr/stdint_proxy.h"
+#include "hdr/types/struct_if_nameindex.h"
+#include "src/__support/CPP/new.h"
+#include "src/__support/common.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(void, if_freenameindex, (struct if_nameindex * ptr)) {
+ // ptr was allocated in net::if_nameindex() as a single contiguous uint8_t[]
+ // buffer holding both the if_nameindex struct array and the interface name
+ // strings.
+ delete[] reinterpret_cast<uint8_t *>(ptr);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/net/linux/if_nameindex.cpp b/libc/src/net/linux/if_nameindex.cpp
new file mode 100644
index 0000000000000..81965c7318dd7
--- /dev/null
+++ b/libc/src/net/linux/if_nameindex.cpp
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Linux implementation of if_nameindex.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/net/if_nameindex.h"
+#include "hdr/types/struct_if_nameindex.h"
+#include "src/__support/OSUtil/linux/network_syscall_policy.h"
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/libc_errno.h"
+#include "src/net/linux/if_nameindex_impl.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(struct if_nameindex *, if_nameindex, ()) {
+ ErrorOr<struct if_nameindex *> result =
+ net::if_nameindex<net::DefaultNetworkSyscallPolicy>();
+ if (!result.has_value()) {
+ libc_errno = result.error();
+ return nullptr;
+ }
+ return *result;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/net/linux/if_nameindex_impl.h b/libc/src/net/linux/if_nameindex_impl.h
new file mode 100644
index 0000000000000..ae451a376ae8b
--- /dev/null
+++ b/libc/src/net/linux/if_nameindex_impl.h
@@ -0,0 +1,162 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Internal templatized implementation of if_nameindex for Linux.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_NET_LINUX_IF_NAMEINDEX_IMPL_H
+#define LLVM_LIBC_SRC_NET_LINUX_IF_NAMEINDEX_IMPL_H
+
+#include "hdr/errno_macros.h"
+#include "hdr/net_if_macros.h"
+#include "hdr/sys_socket_macros.h"
+#include "hdr/types/socklen_t.h"
+#include "hdr/types/ssize_t.h"
+#include "hdr/types/struct_if_nameindex.h"
+#include "src/__support/CPP/new.h"
+#include "src/__support/CPP/scope.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/alloc-checker.h"
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/string/memory_utils/inline_memcpy.h"
+#include "src/string/string_utils.h"
+
+#include <linux/if_link.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+namespace LIBC_NAMESPACE_DECL {
+namespace net {
+
+namespace detail {
+/// Sends a RTM_GETLINK dump request packet on the given socket. The kernel will
+/// respond with one or more messages containing the list of network interfaces
+/// and their attributes.
+template <typename Policy>
+LIBC_INLINE ErrorOr<ssize_t> send_netlink_dump_request(int sockfd) {
+ // The message consists of a standard netlink header followed by the
+ // RTM_GETLINK payload.
+ struct {
+ struct nlmsghdr nlh;
+ struct ifinfomsg ifm;
+ } req = {};
+ static_assert(sizeof(req) >= NLMSG_LENGTH(sizeof(req.ifm)));
+ req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifm));
+ req.nlh.nlmsg_type = RTM_GETLINK;
+ req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ req.ifm.ifi_family = AF_UNSPEC;
+
+ return Policy::sendto(sockfd, &req, req.nlh.nlmsg_len, 0, nullptr, 0);
+}
+
+/// A reasonable buffer size for netlink messages (see NLMSG_GOODSIZE in the
+/// kernel).
+constexpr size_t NLMSG_BUFFER_SIZE = 8192;
+} // namespace detail
+
+template <typename Policy>
+LIBC_INLINE ErrorOr<struct if_nameindex *> if_nameindex() {
+ ErrorOr<int> fd_or_err =
+ Policy::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
+ if (!fd_or_err.has_value())
+ return Error(fd_or_err.error());
+ int fd = *fd_or_err;
+ cpp::scope_exit close_fd([fd]() { Policy::close(fd); });
+
+ ErrorOr<ssize_t> send_res = detail::send_netlink_dump_request<Policy>(fd);
+ if (!send_res.has_value())
+ return Error(send_res.error());
+
+ // TODO: Figure out if we need to dynamically allocate a buffer.
+ alignas(struct nlmsghdr) uint8_t buf[detail::NLMSG_BUFFER_SIZE];
+ ErrorOr<ssize_t> recv_res =
+ Policy::recvfrom(fd, buf, sizeof(buf), 0, nullptr, nullptr);
+ if (!recv_res.has_value())
+ return Error(recv_res.error());
+
+ close_fd.release();
+ if (ErrorOr<int> close_res = Policy::close(fd); !close_res.has_value())
+ return Error(close_res.error());
+
+ // TODO: Read more than one message.
+ // TODO: Read more than one interface per message.
+ // TODO: Deduplicate interfaces to handle restarts.
+ auto len = static_cast<size_t>(*recv_res);
+ for (auto *nh = reinterpret_cast<struct nlmsghdr *>(buf); NLMSG_OK(nh, len);
+ nh = NLMSG_NEXT(nh, len)) {
+ if (nh->nlmsg_type == NLMSG_DONE)
+ break;
+ if (nh->nlmsg_type == NLMSG_ERROR) {
+ if (nh->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr)))
+ return Error(EINVAL);
+ auto *err = reinterpret_cast<struct nlmsgerr *>(NLMSG_DATA(nh));
+ if (err->error == 0) {
+ // Zero means an ACK, which we shouldn't get because we didn't ask for
+ // it...
+ continue;
+ }
+ return Error(-err->error);
+ }
+ if (nh->nlmsg_type != RTM_NEWLINK)
+ continue;
+ if (nh->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg)))
+ continue;
+
+ auto *ifm = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
+ size_t attrlen = nh->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg));
+ struct rtattr *rta = IFLA_RTA(ifm);
+ for (; RTA_OK(rta, attrlen); rta = RTA_NEXT(rta, attrlen)) {
+ if (rta->rta_type != IFLA_IFNAME)
+ continue;
+
+ size_t rta_payload_len = RTA_PAYLOAD(rta);
+ auto index = static_cast<unsigned int>(ifm->ifi_index);
+ const char *name_data = reinterpret_cast<const char *>(RTA_DATA(rta));
+ size_t name_len = internal::strnlen(name_data, rta_payload_len);
+
+ size_t total_size = 2 * sizeof(struct if_nameindex) + name_len + 1;
+ AllocChecker ac;
+ uint8_t *buffer = new (ac) uint8_t[total_size];
+ if (!ac)
+ return Error(ENOBUFS);
+
+ cpp::span<uint8_t> buffer_span(buffer, total_size);
+ cpp::span<struct if_nameindex> result(
+ reinterpret_cast<struct if_nameindex *>(buffer_span.data()), 2);
+ cpp::span<char> string_span(reinterpret_cast<char *>(result.end()),
+ reinterpret_cast<char *>(buffer_span.end()));
+
+ result[0].if_index = index;
+ result[0].if_name = string_span.data();
+ inline_memcpy(string_span.data(), name_data, name_len);
+ string_span[name_len] = '\0';
+
+ result[1].if_index = 0;
+ result[1].if_name = nullptr;
+
+ return result.data();
+ }
+ }
+
+ AllocChecker ac;
+ uint8_t *buffer = new (ac) uint8_t[sizeof(struct if_nameindex)];
+ if (!ac)
+ return Error(ENOBUFS);
+ cpp::span<struct if_nameindex> result(
+ reinterpret_cast<struct if_nameindex *>(buffer), 1);
+ result[0] = {};
+ return result.data();
+}
+
+} // namespace net
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_NET_LINUX_IF_NAMEINDEX_IMPL_H
diff --git a/libc/test/src/net/linux/CMakeLists.txt b/libc/test/src/net/linux/CMakeLists.txt
index c9119a954ad9c..08f288e2256f6 100644
--- a/libc/test/src/net/linux/CMakeLists.txt
+++ b/libc/test/src/net/linux/CMakeLists.txt
@@ -25,3 +25,30 @@ add_libc_unittest(
libc.src.net.if_nametoindex
libc.test.UnitTest.ErrnoCheckingTest
)
+
+add_libc_unittest(
+ if_nameindex_test
+ SUITE
+ libc_net_unittests
+ SRCS
+ if_nameindex_test.cpp
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.net_if_macros
+ libc.hdr.types.socklen_t
+ libc.hdr.types.ssize_t
+ libc.hdr.types.struct_if_nameindex
+ libc.src.__support.CPP.span
+ libc.src.__support.CPP.string
+ libc.src.__support.CPP.string_view
+ libc.src.__support.CPP.tuple
+ libc.src.__support.CPP.type_traits
+ libc.src.__support.error_or
+ libc.src.__support.fixedvector
+ libc.src.net.if_freenameindex
+ libc.src.net.if_nameindex
+ libc.src.net.linux.if_nameindex_impl
+ libc.src.string.memory_utils.inline_memcpy
+ libc.src.string.memory_utils.inline_memset
+ libc.test.UnitTest.ErrnoCheckingTest
+)
diff --git a/libc/test/src/net/linux/if_nameindex_test.cpp b/libc/test/src/net/linux/if_nameindex_test.cpp
new file mode 100644
index 0000000000000..ef5022b6420dc
--- /dev/null
+++ b/libc/test/src/net/linux/if_nameindex_test.cpp
@@ -0,0 +1,545 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unittests for if_nameindex and if_freenameindex.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/net_if_macros.h"
+#include "hdr/types/socklen_t.h"
+#include "hdr/types/ssize_t.h"
+#include "hdr/types/struct_if_nameindex.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/CPP/string.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/CPP/tuple.h"
+#include "src/__support/CPP/type_traits/type_identity.h"
+#include "src/__support/error_or.h"
+#include "src/__support/fixedvector.h"
+#include "src/net/if_freenameindex.h"
+#include "src/net/if_nameindex.h"
+#include "src/net/linux/if_nameindex_impl.h"
+#include "src/string/memory_utils/inline_memcpy.h"
+#include "src/string/memory_utils/inline_memset.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/Test.h"
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+using LIBC_NAMESPACE::Error;
+using LIBC_NAMESPACE::ErrorOr;
+using LIBC_NAMESPACE::FixedVector;
+using LIBC_NAMESPACE::cpp::get;
+using LIBC_NAMESPACE::cpp::span;
+using LIBC_NAMESPACE::cpp::string;
+using LIBC_NAMESPACE::cpp::string_view;
+using LIBC_NAMESPACE::cpp::tuple;
+
+// TODO: Add optional::value_or, then return optional<T>.
+template <typename T, size_t CAPACITY>
+static T
+pop_front_or(FixedVector<T, CAPACITY> &vec,
+ typename LIBC_NAMESPACE::cpp::type_identity<T>::type default_val) {
+ if (vec.empty())
+ return default_val;
+ // TODO: Add front() and erase() to FixedVector, then clean this up.
+ T first = vec[0];
+ for (size_t i = 1; i < vec.size(); ++i)
+ vec[i - 1] = vec[i];
+ vec.pop_back();
+ return first;
+}
+
+// TODO: Add string::operator+=(string_view), then remove this helper.
+static void append_bytes(string &str, const void *data, size_t len) {
+ size_t old_size = str.size();
+ str.resize(old_size + len);
+ LIBC_NAMESPACE::inline_memcpy(str.data() + old_size, data, len);
+}
+
+namespace {
+
+struct FakeNetworkSyscallPolicyData {
+ FixedVector<tuple<int, int, int>, 10> socket_calls;
+ FixedVector<ErrorOr<int>, 10> socket_results;
+ FixedVector<tuple<int, size_t, int>, 10> sendto_calls;
+ FixedVector<ErrorOr<ssize_t>, 10> sendto_results;
+ // TODO: Move this into sendto_calls when FixedVector and tuple support
+ // non-trivial types.
+ string sendto_data;
+ FixedVector<int, 10> close_calls;
+ FixedVector<ErrorOr<int>, 10> close_results;
+ FixedVector<tuple<int, size_t, int>, 10> recv_calls;
+ // TODO: Make this ErrorOr<string> when ErrorOr and FixedVector support
+ // non-trivial types.
+ FixedVector<ErrorOr<span<const uint8_t>>, 10> recv_results;
+};
+
+template <FakeNetworkSyscallPolicyData *DATA> struct FakeNetworkSyscallPolicy {
+ static ErrorOr<int> socket(int domain, int type, int protocol) {
+ DATA->socket_calls.push_back(tuple<int, int, int>(domain, type, protocol));
+ return pop_front_or(DATA->socket_results, Error(ENFILE));
+ }
+
+ static ErrorOr<ssize_t> sendto(int fd, const void *buf, size_t len, int flags,
+ const struct sockaddr *, socklen_t) {
+ DATA->sendto_calls.push_back(tuple<int, size_t, int>(fd, len, flags));
+ append_bytes(DATA->sendto_data, buf, len);
+ return pop_front_or(DATA->sendto_results, static_cast<ssize_t>(len));
+ }
+
+ static ErrorOr<ssize_t> recvfrom(int fd, void *buf, size_t len, int flags,
+ struct sockaddr *, socklen_t *) {
+ DATA->recv_calls.push_back(tuple<int, size_t, int>(fd, len, flags));
+ ErrorOr<span<const uint8_t>> chunk =
+ pop_front_or(DATA->recv_results, span<const uint8_t>());
+ if (!chunk.has_value())
+ return Error(chunk.error());
+ if (chunk->size() > len)
+ return Error(EINVAL);
+ LIBC_NAMESPACE::inline_memcpy(buf, chunk->data(), chunk->size());
+ return static_cast<ssize_t>(chunk->size());
+ }
+
+ static ErrorOr<int> close(int fd) {
+ DATA->close_calls.push_back(fd);
+ return pop_front_or(DATA->close_results, 0);
+ }
+};
+
+struct LlvmLibcIfNameIndexTest
+ : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {
+ static constexpr int FAKE_SOCKET = 47;
+ static FakeNetworkSyscallPolicyData policy_data;
+ using Policy = FakeNetworkSyscallPolicy<&policy_data>;
+
+ void SetUp() override {
+ ErrnoCheckingTest::SetUp();
+ policy_data = {};
+ }
+ void validate_dump_request() {
+ ASSERT_EQ(policy_data.sendto_calls.size(), size_t(1));
+ ASSERT_EQ(get<0>(policy_data.sendto_calls[0]), FAKE_SOCKET);
+ ASSERT_EQ(get<2>(policy_data.sendto_calls[0]), 0);
+ ASSERT_EQ(policy_data.sendto_data.size(),
+ static_cast<size_t>(NLMSG_LENGTH(sizeof(ifinfomsg))));
+ auto *nlh =
+ reinterpret_cast<struct nlmsghdr *>(policy_data.sendto_data.data());
+ ASSERT_EQ(nlh->nlmsg_len,
+ static_cast<uint32_t>(NLMSG_LENGTH(sizeof(ifinfomsg))));
+ ASSERT_EQ(nlh->nlmsg_type, static_cast<uint16_t>(RTM_GETLINK));
+ ASSERT_EQ(nlh->nlmsg_flags,
+ static_cast<uint16_t>(NLM_F_REQUEST | NLM_F_DUMP));
+ auto *ifm = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(nlh));
+ ASSERT_EQ(ifm->ifi_family, static_cast<unsigned char>(AF_UNSPEC));
+ }
+};
+
+struct LlvmLibcIfNameIndexSocketTest : public LlvmLibcIfNameIndexTest {
+ void SetUp() override {
+ LlvmLibcIfNameIndexTest::SetUp();
+ policy_data.socket_results.push_back(FAKE_SOCKET);
+ }
+
+ void TearDown() override {
+ ASSERT_EQ(policy_data.socket_calls.size(), size_t(1));
+ ASSERT_EQ(get<0>(policy_data.socket_calls[0]), AF_NETLINK);
+ ASSERT_EQ(get<1>(policy_data.socket_calls[0]), SOCK_RAW | SOCK_CLOEXEC);
+ ASSERT_EQ(get<2>(policy_data.socket_calls[0]), NETLINK_ROUTE);
+
+ ASSERT_EQ(policy_data.close_calls.size(), size_t(1));
+ ASSERT_EQ(policy_data.close_calls[0], FAKE_SOCKET);
+ ASSERT_TRUE(policy_data.sendto_results.empty());
+ ASSERT_TRUE(policy_data.recv_results.empty());
+ LlvmLibcIfNameIndexTest::TearDown();
+ }
+};
+
+/// A helper struct used to construct netlink messages. Name is the attribute
+/// we're most interested in.
+struct AttrName {
+ string_view name;
+ bool null_terminate = true;
+ size_t payload_len() const { return name.size() + (null_terminate ? 1 : 0); }
+ void write(uint8_t *dest) const {
+ LIBC_NAMESPACE::inline_memcpy(dest, name.data(), name.size());
+ if (null_terminate)
+ dest[name.size()] = '\0';
+ }
+ static constexpr uint16_t TYPE = IFLA_IFNAME;
+};
+
+/// A helper struct used to construct netlink messages with integer attributes.
+/// The attributes themselves are not important. We're just testing that we can
+/// skip over them correctly.
+template <uint16_t ATTR_TYPE> struct AttrInt {
+ unsigned int value;
+ size_t payload_len() const { return sizeof(unsigned int); }
+ void write(uint8_t *dest) const {
+ LIBC_NAMESPACE::inline_memcpy(dest, &value, sizeof(unsigned int));
+ }
+ static constexpr uint16_t TYPE = ATTR_TYPE;
+};
+using AttrMtu = AttrInt<IFLA_MTU>;
+using AttrTxqlen = AttrInt<IFLA_TXQLEN>;
+
+} // namespace
+
+FakeNetworkSyscallPolicyData LlvmLibcIfNameIndexTest::policy_data;
+
+template <typename... Attrs>
+static size_t build_ifinfomsg_packet(uint8_t *buf, unsigned int index,
+ const Attrs &...attrs) {
+ size_t total_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)) +
+ (0 + ... + RTA_ALIGN(RTA_LENGTH(attrs.payload_len())));
+
+ LIBC_NAMESPACE::inline_memset(buf, 0, total_len);
+
+ struct nlmsghdr *nh = reinterpret_cast<struct nlmsghdr *>(buf);
+ nh->nlmsg_len = static_cast<uint32_t>(total_len);
+ nh->nlmsg_type = RTM_NEWLINK;
+
+ struct ifinfomsg *ifm = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
+ ifm->ifi_family = AF_UNSPEC;
+ ifm->ifi_index = static_cast<int>(index);
+
+ uint8_t *attr_ptr = reinterpret_cast<uint8_t *>(IFLA_RTA(ifm));
+
+ auto write_attr = [&attr_ptr](const auto &attr) {
+ size_t rta_len = RTA_LENGTH(attr.payload_len());
+ struct rtattr *rta = reinterpret_cast<struct rtattr *>(attr_ptr);
+ rta->rta_len = static_cast<unsigned short>(rta_len);
+ rta->rta_type = attr.TYPE;
+ attr.write(reinterpret_cast<uint8_t *>(RTA_DATA(rta)));
+ attr_ptr += RTA_ALIGN(rta_len);
+ };
+
+ if constexpr (sizeof...(Attrs) > 0)
+ (..., write_attr(attrs));
+
+ return total_len;
+}
+
+static size_t build_nlmsg_done_packet(uint8_t *buf) {
+ size_t total_len = NLMSG_LENGTH(sizeof(int));
+ LIBC_NAMESPACE::inline_memset(buf, 0, total_len);
+ struct nlmsghdr *nh = reinterpret_cast<struct nlmsghdr *>(buf);
+ nh->nlmsg_len = static_cast<uint32_t>(total_len);
+ nh->nlmsg_type = NLMSG_DONE;
+ return total_len;
+}
+
+static size_t build_nlmsg_error_packet(uint8_t *buf, int errcode) {
+ size_t total_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+ LIBC_NAMESPACE::inline_memset(buf, 0, total_len);
+ struct nlmsghdr *nh = reinterpret_cast<struct nlmsghdr *>(buf);
+ nh->nlmsg_len = static_cast<uint32_t>(total_len);
+ nh->nlmsg_type = NLMSG_ERROR;
+ struct nlmsgerr *err = reinterpret_cast<struct nlmsgerr *>(NLMSG_DATA(nh));
+ err->error = errcode;
+ return total_len;
+}
+
+static size_t build_generic_nlmsg_packet(uint8_t *buf, uint16_t type) {
+ size_t total_len = NLMSG_LENGTH(0);
+ LIBC_NAMESPACE::inline_memset(buf, 0, total_len);
+ struct nlmsghdr *nh = reinterpret_cast<struct nlmsghdr *>(buf);
+ nh->nlmsg_len = static_cast<uint32_t>(total_len);
+ nh->nlmsg_type = type;
+ return total_len;
+}
+
+TEST_F(LlvmLibcIfNameIndexTest, SocketCreationFailure) {
+ policy_data.socket_results.push_back(Error(EAFNOSUPPORT));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_FALSE(res.has_value());
+ ASSERT_EQ(res.error(), EAFNOSUPPORT);
+ ASSERT_EQ(policy_data.socket_calls.size(), size_t(1));
+ ASSERT_EQ(get<0>(policy_data.socket_calls[0]), AF_NETLINK);
+ ASSERT_EQ(get<1>(policy_data.socket_calls[0]), SOCK_RAW | SOCK_CLOEXEC);
+ ASSERT_EQ(get<2>(policy_data.socket_calls[0]), NETLINK_ROUTE);
+ ASSERT_EQ(policy_data.close_calls.size(), size_t(0));
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, SendFailure) {
+ policy_data.sendto_results.push_back(Error(ENETDOWN));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_FALSE(res.has_value());
+ ASSERT_EQ(res.error(), ENETDOWN);
+ validate_dump_request();
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, ZeroInterfaces) {
+ uint8_t pkt_buf[128];
+ size_t len = build_nlmsg_done_packet(pkt_buf);
+
+ policy_data.recv_results.push_back(span<const uint8_t>(pkt_buf, len));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 0u);
+ ASSERT_EQ(list[0].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, SingleInterface) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_ifinfomsg_packet(pkt_buf, 1, AttrName{"lo"});
+ size_t len2 = build_nlmsg_done_packet(pkt_buf + len1);
+
+ policy_data.recv_results.push_back(span<const uint8_t>(pkt_buf, len1 + len2));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 1u);
+ ASSERT_STREQ(list[0].if_name, "lo");
+ ASSERT_EQ(list[1].if_index, 0u);
+ ASSERT_EQ(list[1].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+
+ ASSERT_EQ(policy_data.recv_calls.size(), size_t(1));
+ ASSERT_EQ(get<0>(policy_data.recv_calls[0]), FAKE_SOCKET);
+
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, RecvFailure) {
+ policy_data.recv_results.push_back(Error(ETIMEDOUT));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_FALSE(res.has_value());
+ ASSERT_EQ(res.error(), ETIMEDOUT);
+ validate_dump_request();
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, NetlinkErrorPacket) {
+ uint8_t pkt_buf[128];
+ size_t len = build_nlmsg_error_packet(pkt_buf, -EINVAL);
+
+ policy_data.recv_results.push_back(span<const uint8_t>(pkt_buf, len));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_FALSE(res.has_value());
+ ASSERT_EQ(res.error(), EINVAL);
+ validate_dump_request();
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, TruncatedNetlinkErrorPacket) {
+ uint8_t pkt_buf[128];
+ size_t len = build_generic_nlmsg_packet(pkt_buf, NLMSG_ERROR);
+
+ policy_data.recv_results.push_back(span<const uint8_t>(pkt_buf, len));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_FALSE(res.has_value());
+ ASSERT_EQ(res.error(), EINVAL);
+ validate_dump_request();
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, TruncatedIfInfoMsgPacket) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_generic_nlmsg_packet(pkt_buf, RTM_NEWLINK);
+ size_t len2 = build_ifinfomsg_packet(pkt_buf + len1, 2, AttrName{"eth0"});
+ size_t len3 = build_nlmsg_done_packet(pkt_buf + len1 + len2);
+
+ policy_data.recv_results.push_back(
+ span<const uint8_t>(pkt_buf, len1 + len2 + len3));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 2u);
+ ASSERT_STREQ(list[0].if_name, "eth0");
+ ASSERT_EQ(list[1].if_index, 0u);
+ ASSERT_EQ(list[1].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, TruncatedNetlinkHeaderPacket) {
+ uint8_t pkt_buf[4] = {1, 2, 3, 4};
+ policy_data.recv_results.push_back(
+ span<const uint8_t>(pkt_buf, sizeof(pkt_buf)));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+ ASSERT_EQ(list[0].if_index, 0u);
+ ASSERT_EQ(list[0].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, CloseFailure) {
+ uint8_t pkt_buf[128];
+ size_t len = build_nlmsg_done_packet(pkt_buf);
+
+ policy_data.recv_results.push_back(span<const uint8_t>(pkt_buf, len));
+ policy_data.close_results.push_back(Error(EIO));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_FALSE(res.has_value());
+ ASSERT_EQ(res.error(), EIO);
+ validate_dump_request();
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, NetlinkAckPacket) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_nlmsg_error_packet(pkt_buf, 0);
+ size_t len2 = build_ifinfomsg_packet(pkt_buf + len1, 1, AttrName{"lo"});
+ size_t len3 = build_nlmsg_done_packet(pkt_buf + len1 + len2);
+
+ policy_data.recv_results.push_back(
+ span<const uint8_t>(pkt_buf, len1 + len2 + len3));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 1u);
+ ASSERT_STREQ(list[0].if_name, "lo");
+ ASSERT_EQ(list[1].if_index, 0u);
+ ASSERT_EQ(list[1].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, IgnoredNetlinkMessageTypes) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_generic_nlmsg_packet(pkt_buf, NLMSG_NOOP);
+ size_t len2 = build_ifinfomsg_packet(pkt_buf + len1, 2, AttrName{"eth0"});
+ size_t len3 = build_nlmsg_done_packet(pkt_buf + len1 + len2);
+
+ policy_data.recv_results.push_back(
+ span<const uint8_t>(pkt_buf, len1 + len2 + len3));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 2u);
+ ASSERT_STREQ(list[0].if_name, "eth0");
+ ASSERT_EQ(list[1].if_index, 0u);
+ ASSERT_EQ(list[1].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, InterfaceWithMultipleAttributes) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_ifinfomsg_packet(pkt_buf, 3, AttrMtu{1500},
+ AttrName{"wlan0"}, AttrTxqlen{1000});
+ size_t len2 = build_nlmsg_done_packet(pkt_buf + len1);
+
+ policy_data.recv_results.push_back(span<const uint8_t>(pkt_buf, len1 + len2));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 3u);
+ ASSERT_STREQ(list[0].if_name, "wlan0");
+ ASSERT_EQ(list[1].if_index, 0u);
+ ASSERT_EQ(list[1].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, InterfaceMissingNameAttribute) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_ifinfomsg_packet(pkt_buf, 1, AttrMtu{1500});
+ size_t len2 = build_ifinfomsg_packet(pkt_buf + len1, 2, AttrName{"eth0"});
+ size_t len3 = build_nlmsg_done_packet(pkt_buf + len1 + len2);
+
+ policy_data.recv_results.push_back(
+ span<const uint8_t>(pkt_buf, len1 + len2 + len3));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 2u);
+ ASSERT_STREQ(list[0].if_name, "eth0");
+ ASSERT_EQ(list[1].if_index, 0u);
+ ASSERT_EQ(list[1].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexSocketTest, InterfaceNameWithoutNullTerminator) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_ifinfomsg_packet(pkt_buf, 4, AttrName{"docker0", false});
+ size_t len2 = build_nlmsg_done_packet(pkt_buf + len1);
+
+ policy_data.recv_results.push_back(span<const uint8_t>(pkt_buf, len1 + len2));
+
+ auto res = LIBC_NAMESPACE::net::if_nameindex<Policy>();
+ ASSERT_TRUE(res.has_value());
+ struct if_nameindex *list = res.value();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+
+ ASSERT_EQ(list[0].if_index, 4u);
+ ASSERT_STREQ(list[0].if_name, "docker0");
+ ASSERT_EQ(list[1].if_index, 0u);
+ ASSERT_EQ(list[1].if_name, static_cast<char *>(nullptr));
+
+ validate_dump_request();
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+using LlvmLibcIfNameIndexLiveTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcIfNameIndexLiveTest, LiveOSIntegration) {
+ struct if_nameindex *list = LIBC_NAMESPACE::if_nameindex();
+ ASSERT_NE(list, static_cast<struct if_nameindex *>(nullptr));
+ ASSERT_ERRNO_SUCCESS();
+
+ bool found_valid = false;
+ for (struct if_nameindex *cur = list;
+ cur->if_index != 0 || cur->if_name != nullptr; ++cur) {
+ ASSERT_GT(cur->if_index, 0u);
+ ASSERT_NE(cur->if_name, static_cast<char *>(nullptr));
+ if (LIBC_NAMESPACE::cpp::string_view(cur->if_name) == "lo" ||
+ cur->if_index > 0)
+ found_valid = true;
+ }
+ ASSERT_TRUE(found_valid);
+
+ LIBC_NAMESPACE::if_freenameindex(list);
+}
+
+TEST_F(LlvmLibcIfNameIndexLiveTest, FreeNullptr) {
+ LIBC_NAMESPACE::if_freenameindex(nullptr);
+}
More information about the libc-commits
mailing list