blob: 1285f967ad0b1d4a0b7d6b2de7368a6105a5f913 [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
/// 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/optional.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/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::nullopt;
using LIBC_NAMESPACE::cpp::optional;
using LIBC_NAMESPACE::cpp::span;
using LIBC_NAMESPACE::cpp::string;
using LIBC_NAMESPACE::cpp::string_view;
using LIBC_NAMESPACE::cpp::tuple;
template <typename T, size_t CAPACITY>
static optional<T> pop_front(FixedVector<T, CAPACITY> &vec) {
if (vec.empty())
return nullopt;
// 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;
}
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(DATA->socket_results).value_or(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));
DATA->sendto_data += string_view(static_cast<const char *>(buf), len);
return pop_front(DATA->sendto_results).value_or(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(DATA->recv_results).value_or(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(DATA->close_results).value_or(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);
}