[libc-commits] [libc] [libc] Read multiple interfaces per netlink message in if_nameindex (PR #213952)

via libc-commits libc-commits at lists.llvm.org
Tue Aug 4 07:29:32 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libc

Author: Pavel Labath (labath)

<details>
<summary>Changes</summary>

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().

---
Full diff: https://github.com/llvm/llvm-project/pull/213952.diff


2 Files Affected:

- (modified) libc/src/net/linux/if_nameindex_impl.h (+95-55) 
- (modified) libc/test/src/net/linux/if_nameindex_test.cpp (+56) 


``````````diff
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) {

``````````

</details>


https://github.com/llvm/llvm-project/pull/213952


More information about the libc-commits mailing list