Path: blob/main/lib/libc/tests/net/link_addr_test.cc
39500 views
/*1* Copyright (c) 2025 Lexi Winter2*3* SPDX-License-Identifier: ISC4*/56/*7* Tests for link_addr() and link_ntoa().8*9* link_addr converts a string representing an (optionally null) interface name10* followed by an Ethernet address into a sockaddr_dl. The expected format is11* "[ifname]:lladdr". This means if ifname is not specified, the leading colon12* is still required.13*14* link_ntoa does the inverse of link_addr, returning a string representation15* of the address.16*17* Note that the output format of link_ntoa is not valid input for link_addr18* since the leading colon may be omitted. This is by design.19*/2021#include <sys/types.h>22#include <sys/socket.h>2324#include <net/ethernet.h>25#include <net/if_dl.h>2627#include <format>28#include <iostream>29#include <ranges>30#include <span>31#include <utility>32#include <vector>3334#include <cstddef>35#include <cstdint>3637#include <atf-c++.hpp>3839using namespace std::literals;4041/*42* Define operator== and operator<< for ether_addr so we can use them in43* ATF_EXPECT_EQ expressions.44*/4546bool47operator==(ether_addr a, ether_addr b)48{49return (std::ranges::equal(a.octet, b.octet));50}5152std::ostream &53operator<<(std::ostream &s, ether_addr a)54{55for (unsigned i = 0; i < ETHER_ADDR_LEN; ++i) {56if (i > 0)57s << ":";5859s << std::format("{:02x}", static_cast<int>(a.octet[i]));60}6162return (s);63}6465/*66* Create a sockaddr_dl from a string using link_addr(), and ensure the67* returned struct looks valid.68*/69sockaddr_dl70make_linkaddr(const std::string &addr)71{72auto sdl = sockaddr_dl{};73int ret;7475sdl.sdl_len = sizeof(sdl);76ret = ::link_addr(addr.c_str(), &sdl);7778ATF_REQUIRE_EQ(0, ret);79ATF_REQUIRE_EQ(AF_LINK, static_cast<int>(sdl.sdl_family));80ATF_REQUIRE_EQ(true, sdl.sdl_len > 0);81ATF_REQUIRE_EQ(true, sdl.sdl_nlen >= 0);8283return (sdl);84}8586/*87* Return the data stored in a sockaddr_dl as a span.88*/89std::span<const char>90data(const sockaddr_dl &sdl)91{92// sdl_len is the entire structure, but we only want the length of the93// data area.94auto dlen = sdl.sdl_len - offsetof(sockaddr_dl, sdl_data);95return {&sdl.sdl_data[0], dlen};96}9798/*99* Return the interface name stored in a sockaddr_dl as a string.100*/101std::string_view102ifname(const sockaddr_dl &sdl)103{104auto name = data(sdl).subspan(0, sdl.sdl_nlen);105return {name.begin(), name.end()};106}107108/*109* Return the Ethernet address stored in a sockaddr_dl as an ether_addr.110*/111ether_addr112addr(const sockaddr_dl &sdl)113{114ether_addr ret{};115ATF_REQUIRE_EQ(ETHER_ADDR_LEN, sdl.sdl_alen);116std::ranges::copy(data(sdl).subspan(sdl.sdl_nlen, ETHER_ADDR_LEN),117&ret.octet[0]);118return (ret);119}120121/*122* Return the link address stored in a sockaddr_dl as a span of octets.123*/124std::span<const std::uint8_t>125lladdr(const sockaddr_dl &sdl)126{127auto data = reinterpret_cast<const uint8_t *>(LLADDR(&sdl));128return {data, data + sdl.sdl_alen};129}130131132/*133* Some sample addresses we use for testing. Include at least one address for134* each format we want to support.135*/136137struct test_address {138std::string input; /* value passed to link_addr */139std::string ntoa; /* expected return from link_ntoa */140ether_addr addr{}; /* expected return from link_addr */141};142143std::vector<test_address> test_addresses{144// No delimiter145{"001122334455"s, "0.11.22.33.44.55",146ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},147148// Colon delimiter149{"00:11:22:33:44:55"s, "0.11.22.33.44.55",150ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},151152// Dash delimiter153{"00-11-22-33-44-55"s, "0.11.22.33.44.55",154ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},155156// Period delimiter (link_ntoa format)157{"00.11.22.33.44.55"s, "0.11.22.33.44.55",158ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},159160// Period delimiter (Cisco format)161{"0011.2233.4455"s, "0.11.22.33.44.55",162ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},163164// An addresses without leading zeroes.165{"0:1:02:30:4:55"s, "0.1.2.30.4.55",166ether_addr{0x00, 0x01, 0x02, 0x30, 0x04, 0x55}},167168// An address with some uppercase letters.169{"AA:B:cC:Dd:e0:1f"s, "aa.b.cc.dd.e0.1f",170ether_addr{0xaa, 0x0b, 0xcc, 0xdd, 0xe0, 0x1f}},171172// Addresses composed only of letters, to make sure they're not173// confused with an interface name.174175{"aabbccddeeff"s, "aa.bb.cc.dd.ee.ff",176ether_addr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}},177178{"aa:bb:cc:dd:ee:ff"s, "aa.bb.cc.dd.ee.ff",179ether_addr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}},180181// Address with a blank octet; this is an old form of Ethernet address.182{"00:11::33:44:55"s, "0.11.0.33.44.55",183ether_addr{0x00, 0x11, 0x00, 0x33, 0x44, 0x55}},184};185186/*187* Test without an interface name.188*/189ATF_TEST_CASE_WITHOUT_HEAD(basic)190ATF_TEST_CASE_BODY(basic)191{192for (const auto &ta : test_addresses) {193// This does basic tests on the returned value.194auto sdl = make_linkaddr(":" + ta.input);195196// Check the ifname and address.197ATF_REQUIRE_EQ(""s, ifname(sdl));198ATF_REQUIRE_EQ(ETHER_ADDR_LEN, static_cast<int>(sdl.sdl_alen));199ATF_REQUIRE_EQ(ta.addr, addr(sdl));200201// Check link_ntoa returns the expected value.202auto ntoa = std::string(::link_ntoa(&sdl));203ATF_REQUIRE_EQ(ta.ntoa, ntoa);204}205206}207208/*209* Test with an interface name.210*/211ATF_TEST_CASE_WITHOUT_HEAD(ifname)212ATF_TEST_CASE_BODY(ifname)213{214for (const auto &ta : test_addresses) {215auto sdl = make_linkaddr("ix0:" + ta.input);216217ATF_REQUIRE_EQ("ix0", ifname(sdl));218ATF_REQUIRE_EQ(ETHER_ADDR_LEN, static_cast<int>(sdl.sdl_alen));219ATF_REQUIRE_EQ(ta.addr, addr(sdl));220221auto ntoa = std::string(::link_ntoa(&sdl));222ATF_REQUIRE_EQ("ix0:" + ta.ntoa, ntoa);223}224225}226227/*228* Test with some invalid addresses.229*/230ATF_TEST_CASE_WITHOUT_HEAD(invalid)231ATF_TEST_CASE_BODY(invalid)232{233auto const invalid_addresses = std::vector{234// Invalid separator235":1/2/3"s,236"ix0:1/2/3"s,237238// Multiple different separators239":1.2-3"s,240"ix0:1.2-3"s,241242// An IP address243":10.1.2.200/28"s,244"ix0:10.1.2.200/28"s,245246// Valid address followed by garbage247":1.2.3xxx"s,248":1.2.3.xxx"s,249"ix0:1.2.3xxx"s,250"ix0:1.2.3.xxx"s,251};252253for (auto const &addr : invalid_addresses) {254int ret;255256auto sdl = sockaddr_dl{};257sdl.sdl_len = sizeof(sdl);258259ret = link_addr(addr.c_str(), &sdl);260ATF_REQUIRE_EQ(-1, ret);261}262}263264/*265* Test some non-Ethernet addresses.266*/267ATF_TEST_CASE_WITHOUT_HEAD(nonether)268ATF_TEST_CASE_BODY(nonether)269{270sockaddr_dl sdl;271272/* A short address */273sdl = make_linkaddr(":1:23:cc");274ATF_REQUIRE_EQ("", ifname(sdl));275ATF_REQUIRE_EQ("1.23.cc"s, ::link_ntoa(&sdl));276ATF_REQUIRE_EQ(3, sdl.sdl_alen);277ATF_REQUIRE_EQ(true,278std::ranges::equal(std::vector{0x01u, 0x23u, 0xccu}, lladdr(sdl)));279280/* A long address */281sdl = make_linkaddr(":1:23:cc:a:b:c:d:e:f");282ATF_REQUIRE_EQ("", ifname(sdl));283ATF_REQUIRE_EQ("1.23.cc.a.b.c.d.e.f"s, ::link_ntoa(&sdl));284ATF_REQUIRE_EQ(9, sdl.sdl_alen);285ATF_REQUIRE_EQ(true, std::ranges::equal(286std::vector{0x01u, 0x23u, 0xccu, 0xau, 0xbu, 0xcu, 0xdu, 0xeu, 0xfu},287lladdr(sdl)));288}289290/*291* Test link_addr behaviour with undersized buffers.292*/293ATF_TEST_CASE_WITHOUT_HEAD(smallbuf)294ATF_TEST_CASE_BODY(smallbuf)295{296static constexpr auto garbage = std::byte{0xcc};297auto buf = std::vector<std::byte>();298sockaddr_dl *sdl;299int ret;300301/*302* Make an sdl with an sdl_data member of the appropriate size, and303* place it in buf. Ensure it's followed by a trailing byte of garbage304* so we can test that link_addr doesn't write past the end.305*/306auto mksdl = [&buf](std::size_t datalen) -> sockaddr_dl * {307auto actual_size = datalen + offsetof(sockaddr_dl, sdl_data);308309buf.resize(actual_size + 1);310std::ranges::fill(buf, garbage);311312auto *sdl = new(reinterpret_cast<sockaddr_dl *>(&buf[0]))313sockaddr_dl;314sdl->sdl_len = actual_size;315return (sdl);316};317318/* An sdl large enough to store the interface name */319sdl = mksdl(3);320ret = link_addr("ix0:1.2.3", sdl);321ATF_REQUIRE(*rbegin(buf) == garbage);322ATF_REQUIRE_EQ(-1, ret);323ATF_REQUIRE_EQ(ENOSPC, errno);324ATF_REQUIRE_EQ(3, sdl->sdl_nlen);325ATF_REQUIRE_EQ("ix0", ifname(*sdl));326ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen));327328/*329* For these tests, test both with and without an interface name, since330* that will overflow the buffer in different places.331*/332333/* An empty sdl. Nothing may grow on this cursed ground. */334335sdl = mksdl(0);336ret = link_addr("ix0:1.2.3", sdl);337ATF_REQUIRE(*rbegin(buf) == garbage);338ATF_REQUIRE_EQ(-1, ret);339ATF_REQUIRE_EQ(ENOSPC, errno);340ATF_REQUIRE_EQ(0, sdl->sdl_nlen);341ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen));342343sdl = mksdl(0);344ret = link_addr(":1.2.3", sdl);345ATF_REQUIRE(*rbegin(buf) == garbage);346ATF_REQUIRE_EQ(-1, ret);347ATF_REQUIRE_EQ(ENOSPC, errno);348ATF_REQUIRE_EQ(0, sdl->sdl_nlen);349ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen));350351/*352* An sdl large enough to store the interface name and two octets of the353* address.354*/355356sdl = mksdl(5);357ret = link_addr("ix0:1.2.3", sdl);358ATF_REQUIRE(*rbegin(buf) == garbage);359ATF_REQUIRE_EQ(-1, ret);360ATF_REQUIRE_EQ(ENOSPC, errno);361ATF_REQUIRE_EQ("ix0", ifname(*sdl));362ATF_REQUIRE(std::ranges::equal(363std::vector{ 0x01, 0x02 }, lladdr(*sdl)));364365sdl = mksdl(2);366ret = link_addr(":1.2.3", sdl);367ATF_REQUIRE(*rbegin(buf) == garbage);368ATF_REQUIRE_EQ(-1, ret);369ATF_REQUIRE_EQ(ENOSPC, errno);370ATF_REQUIRE_EQ("", ifname(*sdl));371ATF_REQUIRE(std::ranges::equal(372std::vector{ 0x01, 0x02 }, lladdr(*sdl)));373374/*375* An sdl large enough to store the entire address.376*/377378sdl = mksdl(6);379ret = link_addr("ix0:1.2.3", sdl);380ATF_REQUIRE(*rbegin(buf) == garbage);381ATF_REQUIRE_EQ(0, ret);382ATF_REQUIRE_EQ("ix0", ifname(*sdl));383ATF_REQUIRE(std::ranges::equal(384std::vector{ 0x01, 0x02, 0x03 }, lladdr(*sdl)));385386sdl = mksdl(3);387ret = link_addr(":1.2.3", sdl);388ATF_REQUIRE(*rbegin(buf) == garbage);389ATF_REQUIRE_EQ(0, ret);390ATF_REQUIRE_EQ("", ifname(*sdl));391ATF_REQUIRE(std::ranges::equal(392std::vector{ 0x01, 0x02, 0x03 }, lladdr(*sdl)));393}394395/*396* Test an extremely long address which would overflow link_ntoa's internal397* buffer. It should handle this by truncating the output.398* (Test for SA-16:37.libc / CVE-2016-6559.)399*/400ATF_TEST_CASE_WITHOUT_HEAD(overlong)401ATF_TEST_CASE_BODY(overlong)402{403auto sdl = make_linkaddr(404":01.02.03.04.05.06.07.08.09.0a.0b.0c.0d.0e.0f."405"11.12.13.14.15.16.17.18.19.1a.1b.1c.1d.1e.1f."406"22.22.23.24.25.26.27.28.29.2a.2b.2c.2d.2e.2f");407408ATF_REQUIRE_EQ(409"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,410::link_ntoa(&sdl));411}412413/*414* Test link_ntoa_r, the re-entrant version of link_ntoa().415*/416ATF_TEST_CASE_WITHOUT_HEAD(link_ntoa_r)417ATF_TEST_CASE_BODY(link_ntoa_r)418{419static constexpr char garbage = 0x41;420std::vector<char> buf;421sockaddr_dl sdl;422size_t len;423int ret;424425// Return the contents of buf as a string, using the NUL terminator to426// determine length. This is to ensure we're using the return value in427// the same way C code would, but we do a bit more verification to428// elicit a test failure rather than a SEGV if it's broken.429auto bufstr = [&buf]() -> std::string_view {430// Find the NUL.431auto end = std::ranges::find(buf, '\0');432ATF_REQUIRE(end != buf.end());433434// Intentionally chopping the NUL off.435return {begin(buf), end};436};437438// Resize the buffer and set the contents to a known garbage value, so439// we don't accidentally have a NUL in the right place when link_ntoa_r440// didn't put it there.441auto resetbuf = [&buf, &len](std::size_t size) {442len = size;443buf.resize(len);444std::ranges::fill(buf, garbage);445};446447// Test a short address with a large buffer.448sdl = make_linkaddr("ix0:1.2.3");449resetbuf(64);450ret = ::link_ntoa_r(&sdl, &buf[0], &len);451ATF_REQUIRE_EQ(0, ret);452ATF_REQUIRE_EQ(10, len);453ATF_REQUIRE_EQ("ix0:1.2.3"s, bufstr());454455// Test a buffer which is exactly the right size.456sdl = make_linkaddr("ix0:1.2.3");457resetbuf(10);458ret = ::link_ntoa_r(&sdl, &buf[0], &len);459ATF_REQUIRE_EQ(0, ret);460ATF_REQUIRE_EQ(10, len);461ATF_REQUIRE_EQ("ix0:1.2.3"sv, bufstr());462463// Test various short buffers, using a table of buffer length and the464// output we expect. All of these should produce valid but truncated465// strings, with a trailing NUL and with buflen set correctly.466467auto buftests = std::vector<std::pair<std::size_t, std::string_view>>{468{1u, ""sv},469{2u, ""sv},470{3u, ""sv},471{4u, "ix0"sv},472{5u, "ix0:"sv},473{6u, "ix0:1"sv},474{7u, "ix0:1."sv},475{8u, "ix0:1.2"sv},476{9u, "ix0:1.2."sv},477};478479for (auto const &[buflen, expected] : buftests) {480sdl = make_linkaddr("ix0:1.2.3");481resetbuf(buflen);482ret = ::link_ntoa_r(&sdl, &buf[0], &len);483ATF_REQUIRE_EQ(-1, ret);484ATF_REQUIRE_EQ(10, len);485ATF_REQUIRE_EQ(expected, bufstr());486}487488// Test a NULL buffer, which should just set buflen.489sdl = make_linkaddr("ix0:1.2.3");490len = 0;491ret = ::link_ntoa_r(&sdl, NULL, &len);492ATF_REQUIRE_EQ(-1, ret);493ATF_REQUIRE_EQ(10, len);494495// A NULL buffer with a non-zero length should also be accepted.496sdl = make_linkaddr("ix0:1.2.3");497len = 64;498ret = ::link_ntoa_r(&sdl, NULL, &len);499ATF_REQUIRE_EQ(-1, ret);500ATF_REQUIRE_EQ(10, len);501502// Test a non-NULL buffer, but with a length of zero.503sdl = make_linkaddr("ix0:1.2.3");504resetbuf(1);505len = 0;506ret = ::link_ntoa_r(&sdl, &buf[0], &len);507ATF_REQUIRE_EQ(-1, ret);508ATF_REQUIRE_EQ(10, len);509// Check we really didn't write anything.510ATF_REQUIRE_EQ(garbage, buf[0]);511512// Test a buffer which would be truncated in the middle of a two-digit513// hex octet, which should not write the truncated octet at all.514sdl = make_linkaddr("ix0:1.22.3");515resetbuf(8);516ret = ::link_ntoa_r(&sdl, &buf[0], &len);517ATF_REQUIRE_EQ(-1, ret);518ATF_REQUIRE_EQ(11, len);519ATF_REQUIRE_EQ("ix0:1."sv, bufstr());520}521522ATF_INIT_TEST_CASES(tcs)523{524ATF_ADD_TEST_CASE(tcs, basic);525ATF_ADD_TEST_CASE(tcs, ifname);526ATF_ADD_TEST_CASE(tcs, smallbuf);527ATF_ADD_TEST_CASE(tcs, invalid);528ATF_ADD_TEST_CASE(tcs, nonether);529ATF_ADD_TEST_CASE(tcs, overlong);530ATF_ADD_TEST_CASE(tcs, link_ntoa_r);531}532533534