#include <sys/types.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <net/if_dl.h>
#include <format>
#include <iostream>
#include <ranges>
#include <span>
#include <utility>
#include <vector>
#include <cstddef>
#include <cstdint>
#include <atf-c++.hpp>
using namespace std::literals;
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);
}
sockaddr_dl
make_linkaddr(const std::string &addr)
{
auto sdl = sockaddr_dl{};
int ret;
sdl.sdl_len = sizeof(sdl);
ret = ::link_addr(addr.c_str(), &sdl);
ATF_REQUIRE_EQ(0, ret);
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);
}
std::span<const char>
data(const sockaddr_dl &sdl)
{
auto dlen = sdl.sdl_len - offsetof(sockaddr_dl, sdl_data);
return {&sdl.sdl_data[0], dlen};
}
std::string_view
ifname(const sockaddr_dl &sdl)
{
auto name = data(sdl).subspan(0, sdl.sdl_nlen);
return {name.begin(), name.end()};
}
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);
}
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};
}
struct test_address {
std::string input;
std::string ntoa;
ether_addr addr{};
};
std::vector<test_address> test_addresses{
{"001122334455"s, "0.11.22.33.44.55",
ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
{"00:11:22:33:44:55"s, "0.11.22.33.44.55",
ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
{"00-11-22-33-44-55"s, "0.11.22.33.44.55",
ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
{"00.11.22.33.44.55"s, "0.11.22.33.44.55",
ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
{"0011.2233.4455"s, "0.11.22.33.44.55",
ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
{"0:1:02:30:4:55"s, "0.1.2.30.4.55",
ether_addr{0x00, 0x01, 0x02, 0x30, 0x04, 0x55}},
{"AA:B:cC:Dd:e0:1f"s, "aa.b.cc.dd.e0.1f",
ether_addr{0xaa, 0x0b, 0xcc, 0xdd, 0xe0, 0x1f}},
{"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}},
{"00:11::33:44:55"s, "0.11.0.33.44.55",
ether_addr{0x00, 0x11, 0x00, 0x33, 0x44, 0x55}},
};
ATF_TEST_CASE_WITHOUT_HEAD(basic)
ATF_TEST_CASE_BODY(basic)
{
for (const auto &ta : test_addresses) {
auto sdl = make_linkaddr(":" + ta.input);
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));
auto ntoa = std::string(::link_ntoa(&sdl));
ATF_REQUIRE_EQ(ta.ntoa, ntoa);
}
}
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);
}
}
ATF_TEST_CASE_WITHOUT_HEAD(invalid)
ATF_TEST_CASE_BODY(invalid)
{
auto const invalid_addresses = std::vector{
":1/2/3"s,
"ix0:1/2/3"s,
":1.2-3"s,
"ix0:1.2-3"s,
":10.1.2.200/28"s,
"ix0:10.1.2.200/28"s,
":1.2.3xxx"s,
":1.2.3.xxx"s,
"ix0:1.2.3xxx"s,
"ix0:1.2.3.xxx"s,
};
for (auto const &addr : invalid_addresses) {
int ret;
auto sdl = sockaddr_dl{};
sdl.sdl_len = sizeof(sdl);
ret = link_addr(addr.c_str(), &sdl);
ATF_REQUIRE_EQ(-1, ret);
}
}
ATF_TEST_CASE_WITHOUT_HEAD(nonether)
ATF_TEST_CASE_BODY(nonether)
{
sockaddr_dl sdl;
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)));
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)));
}
ATF_TEST_CASE_WITHOUT_HEAD(smallbuf)
ATF_TEST_CASE_BODY(smallbuf)
{
static constexpr auto garbage = std::byte{0xcc};
auto buf = std::vector<std::byte>();
sockaddr_dl *sdl;
int ret;
auto mksdl = [&buf](std::size_t datalen) -> sockaddr_dl * {
auto actual_size = datalen + offsetof(sockaddr_dl, sdl_data);
buf.resize(actual_size + 1);
std::ranges::fill(buf, garbage);
auto *sdl = new(reinterpret_cast<sockaddr_dl *>(&buf[0]))
sockaddr_dl;
sdl->sdl_len = actual_size;
return (sdl);
};
sdl = mksdl(3);
ret = link_addr("ix0:1.2.3", sdl);
ATF_REQUIRE(*rbegin(buf) == garbage);
ATF_REQUIRE_EQ(-1, ret);
ATF_REQUIRE_EQ(ENOSPC, errno);
ATF_REQUIRE_EQ(3, sdl->sdl_nlen);
ATF_REQUIRE_EQ("ix0", ifname(*sdl));
ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen));
sdl = mksdl(0);
ret = link_addr("ix0:1.2.3", sdl);
ATF_REQUIRE(*rbegin(buf) == garbage);
ATF_REQUIRE_EQ(-1, ret);
ATF_REQUIRE_EQ(ENOSPC, errno);
ATF_REQUIRE_EQ(0, sdl->sdl_nlen);
ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen));
sdl = mksdl(0);
ret = link_addr(":1.2.3", sdl);
ATF_REQUIRE(*rbegin(buf) == garbage);
ATF_REQUIRE_EQ(-1, ret);
ATF_REQUIRE_EQ(ENOSPC, errno);
ATF_REQUIRE_EQ(0, sdl->sdl_nlen);
ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen));
sdl = mksdl(5);
ret = link_addr("ix0:1.2.3", sdl);
ATF_REQUIRE(*rbegin(buf) == garbage);
ATF_REQUIRE_EQ(-1, ret);
ATF_REQUIRE_EQ(ENOSPC, errno);
ATF_REQUIRE_EQ("ix0", ifname(*sdl));
ATF_REQUIRE(std::ranges::equal(
std::vector{ 0x01, 0x02 }, lladdr(*sdl)));
sdl = mksdl(2);
ret = link_addr(":1.2.3", sdl);
ATF_REQUIRE(*rbegin(buf) == garbage);
ATF_REQUIRE_EQ(-1, ret);
ATF_REQUIRE_EQ(ENOSPC, errno);
ATF_REQUIRE_EQ("", ifname(*sdl));
ATF_REQUIRE(std::ranges::equal(
std::vector{ 0x01, 0x02 }, lladdr(*sdl)));
sdl = mksdl(6);
ret = link_addr("ix0:1.2.3", sdl);
ATF_REQUIRE(*rbegin(buf) == garbage);
ATF_REQUIRE_EQ(0, ret);
ATF_REQUIRE_EQ("ix0", ifname(*sdl));
ATF_REQUIRE(std::ranges::equal(
std::vector{ 0x01, 0x02, 0x03 }, lladdr(*sdl)));
sdl = mksdl(3);
ret = link_addr(":1.2.3", sdl);
ATF_REQUIRE(*rbegin(buf) == garbage);
ATF_REQUIRE_EQ(0, ret);
ATF_REQUIRE_EQ("", ifname(*sdl));
ATF_REQUIRE(std::ranges::equal(
std::vector{ 0x01, 0x02, 0x03 }, lladdr(*sdl)));
}
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_TEST_CASE_WITHOUT_HEAD(link_ntoa_r)
ATF_TEST_CASE_BODY(link_ntoa_r)
{
static constexpr char garbage = 0x41;
std::vector<char> buf;
sockaddr_dl sdl;
size_t len;
int ret;
auto bufstr = [&buf]() -> std::string_view {
auto end = std::ranges::find(buf, '\0');
ATF_REQUIRE(end != buf.end());
return {begin(buf), end};
};
auto resetbuf = [&buf, &len](std::size_t size) {
len = size;
buf.resize(len);
std::ranges::fill(buf, garbage);
};
sdl = make_linkaddr("ix0:1.2.3");
resetbuf(64);
ret = ::link_ntoa_r(&sdl, &buf[0], &len);
ATF_REQUIRE_EQ(0, ret);
ATF_REQUIRE_EQ(10, len);
ATF_REQUIRE_EQ("ix0:1.2.3"s, bufstr());
sdl = make_linkaddr("ix0:1.2.3");
resetbuf(10);
ret = ::link_ntoa_r(&sdl, &buf[0], &len);
ATF_REQUIRE_EQ(0, ret);
ATF_REQUIRE_EQ(10, len);
ATF_REQUIRE_EQ("ix0:1.2.3"sv, bufstr());
auto buftests = std::vector<std::pair<std::size_t, std::string_view>>{
{1u, ""sv},
{2u, ""sv},
{3u, ""sv},
{4u, "ix0"sv},
{5u, "ix0:"sv},
{6u, "ix0:1"sv},
{7u, "ix0:1."sv},
{8u, "ix0:1.2"sv},
{9u, "ix0:1.2."sv},
};
for (auto const &[buflen, expected] : buftests) {
sdl = make_linkaddr("ix0:1.2.3");
resetbuf(buflen);
ret = ::link_ntoa_r(&sdl, &buf[0], &len);
ATF_REQUIRE_EQ(-1, ret);
ATF_REQUIRE_EQ(10, len);
ATF_REQUIRE_EQ(expected, bufstr());
}
sdl = make_linkaddr("ix0:1.2.3");
len = 0;
ret = ::link_ntoa_r(&sdl, NULL, &len);
ATF_REQUIRE_EQ(-1, ret);
ATF_REQUIRE_EQ(10, len);
sdl = make_linkaddr("ix0:1.2.3");
len = 64;
ret = ::link_ntoa_r(&sdl, NULL, &len);
ATF_REQUIRE_EQ(-1, ret);
ATF_REQUIRE_EQ(10, len);
sdl = make_linkaddr("ix0:1.2.3");
resetbuf(1);
len = 0;
ret = ::link_ntoa_r(&sdl, &buf[0], &len);
ATF_REQUIRE_EQ(-1, ret);
ATF_REQUIRE_EQ(10, len);
ATF_REQUIRE_EQ(garbage, buf[0]);
sdl = make_linkaddr("ix0:1.22.3");
resetbuf(8);
ret = ::link_ntoa_r(&sdl, &buf[0], &len);
ATF_REQUIRE_EQ(-1, ret);
ATF_REQUIRE_EQ(11, len);
ATF_REQUIRE_EQ("ix0:1."sv, bufstr());
}
ATF_INIT_TEST_CASES(tcs)
{
ATF_ADD_TEST_CASE(tcs, basic);
ATF_ADD_TEST_CASE(tcs, ifname);
ATF_ADD_TEST_CASE(tcs, smallbuf);
ATF_ADD_TEST_CASE(tcs, invalid);
ATF_ADD_TEST_CASE(tcs, nonether);
ATF_ADD_TEST_CASE(tcs, overlong);
ATF_ADD_TEST_CASE(tcs, link_ntoa_r);
}