libc tests: add tests for link_addr(3) and link_ntoa(3)

for now, since link_addr() has no way to indicate an error, these are
only positive tests which check the outcome of valid inputs.

Reviewed by:	ngie, des, adrian
Approved by:	des (mentor)
Differential Revision:	https://reviews.freebsd.org/D50062
This commit is contained in:
Lexi Winter 2025-05-05 18:25:03 +01:00
parent 3ab34225af
commit 757e973fb2
2 changed files with 272 additions and 0 deletions

View file

@ -3,6 +3,9 @@ PACKAGE= tests
ATF_TESTS_C+= ether_test
ATF_TESTS_C+= eui64_aton_test
ATF_TESTS_C+= eui64_ntoa_test
ATF_TESTS_CXX+= link_addr_test
CXXSTD.link_addr_test= c++20
CFLAGS+= -I${.CURDIR}

View file

@ -0,0 +1,269 @@
/*
* Copyright (c) 2025 Lexi Winter
*
* SPDX-License-Identifier: ISC
*/
/*
* Tests for link_addr() and link_ntoa().
*
* link_addr converts a string representing an (optionally null) interface name
* followed by an Ethernet address into a sockaddr_dl. The expected format is
* "[ifname]:lladdr". This means if ifname is not specified, the leading colon
* is still required.
*
* link_ntoa does the inverse of link_addr, returning a string representation
* of the address.
*
* Note that the output format of link_ntoa is not valid input for link_addr
* since the leading colon may be omitted. This is by design.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stddef.h>
#include <net/ethernet.h>
#include <net/if_dl.h>
#include <vector>
#include <span>
#include <format>
#include <utility>
#include <ranges>
#include <cstdint>
#include <atf-c++.hpp>
using namespace std::literals;
/*
* Define operator== and operator<< for ether_addr so we can use them in
* ATF_EXPECT_EQ expressions.
*/
bool
operator==(ether_addr a, ether_addr b)
{
return (std::ranges::equal(a.octet, b.octet));
}
std::ostream &
operator<<(std::ostream &s, ether_addr a)
{
for (unsigned i = 0; i < ETHER_ADDR_LEN; ++i) {
if (i > 0)
s << ":";
s << std::format("{:02x}", static_cast<int>(a.octet[i]));
}
return (s);
}
/*
* Create a sockaddr_dl from a string using link_addr(), and ensure the
* returned struct looks valid.
*/
sockaddr_dl
make_linkaddr(const std::string &addr)
{
auto sdl = sockaddr_dl{};
sdl.sdl_len = sizeof(sdl);
::link_addr(addr.c_str(), &sdl);
ATF_REQUIRE_EQ(AF_LINK, static_cast<int>(sdl.sdl_family));
ATF_REQUIRE_EQ(true, sdl.sdl_len > 0);
ATF_REQUIRE_EQ(true, sdl.sdl_nlen >= 0);
return (sdl);
}
/*
* Return the data stored in a sockaddr_dl as a span.
*/
std::span<const char>
data(const sockaddr_dl &sdl)
{
// sdl_len is the entire structure, but we only want the length of the
// data area.
auto dlen = sdl.sdl_len - offsetof(sockaddr_dl, sdl_data);
return {&sdl.sdl_data[0], dlen};
}
/*
* Return the interface name stored in a sockaddr_dl as a string.
*/
std::string_view
ifname(const sockaddr_dl &sdl)
{
auto name = data(sdl).subspan(0, sdl.sdl_nlen);
return {name.begin(), name.end()};
}
/*
* Return the Ethernet address stored in a sockaddr_dl as an ether_addr.
*/
ether_addr
addr(const sockaddr_dl &sdl)
{
ether_addr ret{};
ATF_REQUIRE_EQ(ETHER_ADDR_LEN, sdl.sdl_alen);
std::ranges::copy(data(sdl).subspan(sdl.sdl_nlen, ETHER_ADDR_LEN),
&ret.octet[0]);
return (ret);
}
/*
* Return the link address stored in a sockaddr_dl as a span of octets.
*/
std::span<const std::uint8_t>
lladdr(const sockaddr_dl &sdl)
{
auto data = reinterpret_cast<const uint8_t *>(LLADDR(&sdl));
return {data, data + sdl.sdl_alen};
}
/*
* Some sample addresses we use for testing. Include at least one address for
* each format we want to support.
*/
struct test_address {
std::string input; /* value passed to link_addr */
std::string ntoa; /* expected return from link_ntoa */
ether_addr addr{}; /* expected return from link_addr */
};
std::vector<test_address> test_addresses{
// No delimiter
{"001122334455"s, "0.11.22.33.44.55",
ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
// Colon delimiter
{"00:11:22:33:44:55"s, "0.11.22.33.44.55",
ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
// Dash delimiter
{"00-11-22-33-44-55"s, "0.11.22.33.44.55",
ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
// Period delimiter (link_ntoa format)
{"00.11.22.33.44.55"s, "0.11.22.33.44.55",
ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
// Period delimiter (Cisco format)
{"0011.2233.4455"s, "0.11.22.33.44.55",
ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
// An addresses without leading zeroes.
{"0:1:02:30:4:55"s, "0.1.2.30.4.55",
ether_addr{0x00, 0x01, 0x02, 0x30, 0x04, 0x55}},
// An address with some uppercase letters.
{"AA:B:cC:Dd:e0:1f"s, "aa.b.cc.dd.e0.1f",
ether_addr{0xaa, 0x0b, 0xcc, 0xdd, 0xe0, 0x1f}},
// Addresses composed only of letters, to make sure they're not
// confused with an interface name.
{"aabbccddeeff"s, "aa.bb.cc.dd.ee.ff",
ether_addr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}},
{"aa:bb:cc:dd:ee:ff"s, "aa.bb.cc.dd.ee.ff",
ether_addr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}},
};
/*
* Test without an interface name.
*/
ATF_TEST_CASE_WITHOUT_HEAD(basic)
ATF_TEST_CASE_BODY(basic)
{
for (const auto &ta : test_addresses) {
// This does basic tests on the returned value.
auto sdl = make_linkaddr(":" + ta.input);
// Check the ifname and address.
ATF_REQUIRE_EQ(""s, ifname(sdl));
ATF_REQUIRE_EQ(ETHER_ADDR_LEN, static_cast<int>(sdl.sdl_alen));
ATF_REQUIRE_EQ(ta.addr, addr(sdl));
// Check link_ntoa returns the expected value.
auto ntoa = std::string(::link_ntoa(&sdl));
ATF_REQUIRE_EQ(ta.ntoa, ntoa);
}
}
/*
* Test with an interface name.
*/
ATF_TEST_CASE_WITHOUT_HEAD(ifname)
ATF_TEST_CASE_BODY(ifname)
{
for (const auto &ta : test_addresses) {
auto sdl = make_linkaddr("ix0:" + ta.input);
ATF_REQUIRE_EQ("ix0", ifname(sdl));
ATF_REQUIRE_EQ(ETHER_ADDR_LEN, static_cast<int>(sdl.sdl_alen));
ATF_REQUIRE_EQ(ta.addr, addr(sdl));
auto ntoa = std::string(::link_ntoa(&sdl));
ATF_REQUIRE_EQ("ix0:" + ta.ntoa, ntoa);
}
}
/*
* Test some non-Ethernet addresses.
*/
ATF_TEST_CASE_WITHOUT_HEAD(nonether)
ATF_TEST_CASE_BODY(nonether)
{
sockaddr_dl sdl;
/* A short address */
sdl = make_linkaddr(":1:23:cc");
ATF_REQUIRE_EQ("", ifname(sdl));
ATF_REQUIRE_EQ("1.23.cc"s, ::link_ntoa(&sdl));
ATF_REQUIRE_EQ(3, sdl.sdl_alen);
ATF_REQUIRE_EQ(true,
std::ranges::equal(std::vector{0x01u, 0x23u, 0xccu}, lladdr(sdl)));
/* A long address */
sdl = make_linkaddr(":1:23:cc:a:b:c:d:e:f");
ATF_REQUIRE_EQ("", ifname(sdl));
ATF_REQUIRE_EQ("1.23.cc.a.b.c.d.e.f"s, ::link_ntoa(&sdl));
ATF_REQUIRE_EQ(9, sdl.sdl_alen);
ATF_REQUIRE_EQ(true, std::ranges::equal(
std::vector{0x01u, 0x23u, 0xccu, 0xau, 0xbu, 0xcu, 0xdu, 0xeu, 0xfu},
lladdr(sdl)));
}
/*
* Test an extremely long address which would overflow link_ntoa's internal
* buffer. It should handle this by truncating the output.
* (Test for SA-16:37.libc / CVE-2016-6559.)
*/
ATF_TEST_CASE_WITHOUT_HEAD(overlong)
ATF_TEST_CASE_BODY(overlong)
{
auto sdl = make_linkaddr(
":01.02.03.04.05.06.07.08.09.0a.0b.0c.0d.0e.0f."
"11.12.13.14.15.16.17.18.19.1a.1b.1c.1d.1e.1f."
"22.22.23.24.25.26.27.28.29.2a.2b.2c.2d.2e.2f");
ATF_REQUIRE_EQ(
"1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.11.12.13.14.15.16.17.18.19.1a.1b."s,
::link_ntoa(&sdl));
}
ATF_INIT_TEST_CASES(tcs)
{
ATF_ADD_TEST_CASE(tcs, basic);
ATF_ADD_TEST_CASE(tcs, ifname);
ATF_ADD_TEST_CASE(tcs, nonether);
ATF_ADD_TEST_CASE(tcs, overlong);
}