[libc-commits] [libc] [libc] Read multiple interfaces per netlink message in if_nameindex (PR #213952)
Pavel Labath via libc-commits
libc-commits at lists.llvm.org
Tue Aug 4 06:49:58 PDT 2026
https://github.com/labath created https://github.com/llvm/llvm-project/pull/213952
This patch changes it to collect all interfaces in the buffer using BlockStore<InterfaceEntry, 16>. I'm storing interface names as a fixed char array (IF_NAMESIZE) in InterfaceEntry so that we're ready for the next step when we reuse the recvfrom buffer across multiple messages. Once all messages in the buffer are parsed, I allocate a single contiguous buffer containing all struct if_nameindex entries and strings.
To keep if_nameindex() readable, I've split the implementation into two helper functions: parse_netlink_messages() and build_if_nameindex_list().
>From e66e975889c55206861fcab602aeb905d30fa3bb Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Tue, 4 Aug 2026 13:23:54 +0000
Subject: [PATCH] [libc] Read multiple interfaces per netlink message in
if_nameindex
This patch changes it to collect all interfaces in the buffer using
BlockStore<InterfaceEntry, 16>. I'm storing interface names as a fixed
char array (IF_NAMESIZE) in InterfaceEntry so that we're ready for the
next step when we reuse the recvfrom buffer across multiple messages.
Once all messages in the buffer are parsed, I allocate a single
contiguous buffer containing all struct if_nameindex entries and strings.
To keep if_nameindex() readable, I've split the implementation into two
helper functions: parse_netlink_messages() and build_if_nameindex_list().
---
libc/src/net/linux/if_nameindex_impl.h | 150 +++++++++++-------
libc/test/src/net/linux/if_nameindex_test.cpp | 56 +++++++
2 files changed, 151 insertions(+), 55 deletions(-)
diff --git a/libc/src/net/linux/if_nameindex_impl.h b/libc/src/net/linux/if_nameindex_impl.h
index ae451a376ae8b..eac01efde513c 100644
--- a/libc/src/net/linux/if_nameindex_impl.h
+++ b/libc/src/net/linux/if_nameindex_impl.h
@@ -24,6 +24,7 @@
#include "src/__support/CPP/scope.h"
#include "src/__support/CPP/span.h"
#include "src/__support/alloc-checker.h"
+#include "src/__support/blockstore.h"
#include "src/__support/common.h"
#include "src/__support/error_or.h"
#include "src/string/memory_utils/inline_memcpy.h"
@@ -60,38 +61,19 @@ LIBC_INLINE ErrorOr<ssize_t> send_netlink_dump_request(int sockfd) {
/// 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)) {
+struct InterfaceEntry {
+ unsigned int index;
+ char name[IF_NAMESIZE];
+};
+
+// TODO: Use ErrorOr<void> when that's a thing.
+LIBC_INLINE ErrorOr<int>
+parse_netlink_messages(cpp::span<uint8_t> buf,
+ BlockStore<InterfaceEntry, 16> &store) {
+ size_t len = buf.size();
+ for (auto *nh = reinterpret_cast<struct nlmsghdr *>(buf.data());
+ NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) {
if (nh->nlmsg_type == NLMSG_DONE)
break;
if (nh->nlmsg_type == NLMSG_ERROR) {
@@ -118,44 +100,102 @@ LIBC_INLINE ErrorOr<struct if_nameindex *> if_nameindex() {
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);
+ // Defensive check: kernel should not be providing us with names that
+ // don't fit.
+ if (name_len >= IF_NAMESIZE)
+ name_len = IF_NAMESIZE - 1;
- 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';
+ InterfaceEntry entry;
+ entry.index = static_cast<unsigned int>(ifm->ifi_index);
+ inline_memcpy(entry.name, name_data, name_len);
+ entry.name[name_len] = '\0';
- result[1].if_index = 0;
- result[1].if_name = nullptr;
-
- return result.data();
+ if (!store.push_back(entry))
+ return Error(ENOBUFS);
+ break;
}
}
+ return 0;
+}
+
+LIBC_INLINE ErrorOr<struct if_nameindex *>
+build_if_nameindex_list(BlockStore<InterfaceEntry, 16> &store) {
+ size_t count = 0;
+ size_t strings_size = 0;
+ for (const InterfaceEntry &entry : store) {
+ ++count;
+ strings_size += internal::string_length(entry.name) + 1;
+ }
+ size_t total_size = (count + 1) * sizeof(struct if_nameindex) + strings_size;
AllocChecker ac;
- uint8_t *buffer = new (ac) uint8_t[sizeof(struct if_nameindex)];
+ uint8_t *buffer = new (ac) uint8_t[total_size];
if (!ac)
return Error(ENOBUFS);
+
cpp::span<struct if_nameindex> result(
- reinterpret_cast<struct if_nameindex *>(buffer), 1);
- result[0] = {};
+ reinterpret_cast<struct if_nameindex *>(buffer), count + 1);
+ char *str_ptr = reinterpret_cast<char *>(result.end());
+
+ size_t idx = 0;
+ for (const InterfaceEntry &entry : store) {
+ size_t name_len = internal::string_length(entry.name);
+ result[idx].if_index = entry.index;
+ result[idx].if_name = str_ptr;
+ inline_memcpy(str_ptr, entry.name, name_len + 1);
+ str_ptr += name_len + 1;
+ ++idx;
+ }
+
+ result[count].if_index = 0;
+ result[count].if_name = nullptr;
+
return result.data();
}
+} // 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: Deduplicate interfaces to handle restarts.
+ BlockStore<detail::InterfaceEntry, 16> store;
+ cpp::scope_exit destroy_store([&store]() {
+ BlockStore<detail::InterfaceEntry, 16>::destroy(&store);
+ });
+
+ if (ErrorOr<int> parse_res = detail::parse_netlink_messages(
+ {buf, static_cast<size_t>(*recv_res)}, store);
+ !parse_res.has_value())
+ return Error(parse_res.error());
+
+ return detail::build_if_nameindex_list(store);
+}
+
} // namespace net
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/net/linux/if_nameindex_test.cpp b/libc/test/src/net/linux/if_nameindex_test.cpp
index 1285f967ad0b1..c68e4781641c4 100644
--- a/libc/test/src/net/linux/if_nameindex_test.cpp
+++ b/libc/test/src/net/linux/if_nameindex_test.cpp
@@ -314,6 +314,39 @@ TEST_F(LlvmLibcIfNameIndexSocketTest, SingleInterface) {
LIBC_NAMESPACE::if_freenameindex(list);
}
+TEST_F(LlvmLibcIfNameIndexSocketTest, MultipleInterfaces) {
+ uint8_t pkt_buf[2048];
+ size_t len1 = build_ifinfomsg_packet(pkt_buf, 1, AttrName{"lo"});
+ size_t len2 = build_ifinfomsg_packet(pkt_buf + len1, 2, AttrName{"eth0"});
+ size_t len3 =
+ build_ifinfomsg_packet(pkt_buf + len1 + len2, 3, AttrName{"wlan0"});
+ size_t len4 = build_nlmsg_done_packet(pkt_buf + len1 + len2 + len3);
+
+ policy_data.recv_results.push_back(
+ span<const uint8_t>(pkt_buf, len1 + len2 + len3 + len4));
+
+ 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, 2u);
+ ASSERT_STREQ(list[1].if_name, "eth0");
+ ASSERT_EQ(list[2].if_index, 3u);
+ ASSERT_STREQ(list[2].if_name, "wlan0");
+ ASSERT_EQ(list[3].if_index, 0u);
+ ASSERT_EQ(list[3].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));
@@ -511,6 +544,29 @@ TEST_F(LlvmLibcIfNameIndexSocketTest, InterfaceNameWithoutNullTerminator) {
LIBC_NAMESPACE::if_freenameindex(list);
}
+TEST_F(LlvmLibcIfNameIndexSocketTest, InterfaceNameExceedingIfNamesize) {
+ uint8_t pkt_buf[1024];
+ size_t len1 = build_ifinfomsg_packet(
+ pkt_buf, 5,
+ AttrName{"this_interface_name_is_way_too_long_for_if_namesize"});
+ 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, 5u);
+ ASSERT_STREQ(list[0].if_name, "this_interface_");
+ 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) {
More information about the libc-commits
mailing list