blob: 2e4ce089a567260cf89879ab9976989d6a1f42a4 [file]
//===----------------------------------------------------------------------===//
//
// 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/blockstore.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;
struct InterfaceEntry {
unsigned int index;
uint8_t name_length; // Length of next field.
char name[IF_NAMESIZE - 1]; // No null terminator.
static_assert(IF_NAMESIZE - 1 < (1u << 8));
};
LIBC_INLINE ErrorOr<void>
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) {
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);
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;
InterfaceEntry entry;
entry.index = static_cast<unsigned int>(ifm->ifi_index);
entry.name_length = static_cast<uint8_t>(name_len);
inline_memcpy(entry.name, name_data, name_len);
if (!store.push_back(entry))
return Error(ENOBUFS);
break;
}
}
return {};
}
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 += entry.name_length + 1;
}
size_t total_size = (count + 1) * sizeof(struct if_nameindex) + strings_size;
AllocChecker ac;
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), count + 1);
char *str_ptr = reinterpret_cast<char *>(result.end());
size_t idx = 0;
for (const InterfaceEntry &entry : store) {
result[idx].if_index = entry.index;
result[idx].if_name = str_ptr;
inline_memcpy(str_ptr, entry.name, entry.name_length);
str_ptr[entry.name_length] = '\0';
str_ptr += entry.name_length + 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<void> 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
#endif // LLVM_LIBC_SRC_NET_LINUX_IF_NAMEINDEX_IMPL_H