mirror of
https://github.com/opnsense/src.git
synced 2026-06-09 00:32:25 -04:00
libc: add link_ntoa_r()
this is a re-entrant version of link_ntoa. use an in-out parameter for the buffer size, so the user requires at most two calls to determine the needed size. reimplement link_ntoa using link_ntoa_r with a static buffer. Reviewed by: des Approved by: des (mentor) Differential Revision: https://reviews.freebsd.org/D50202
This commit is contained in:
parent
6f2b1b56ac
commit
da509c2908
6 changed files with 247 additions and 29 deletions
|
|
@ -141,7 +141,8 @@ MLINKS+=inet6_opt_init.3 inet6_opt_append.3 \
|
|||
inet6_rthdr_space.3 inet6_rthdr_reverse.3 \
|
||||
inet6_rthdr_space.3 inet6_rthdr_segments.3
|
||||
MLINKS+=linkaddr.3 link_addr.3 \
|
||||
linkaddr.3 link_ntoa.3
|
||||
linkaddr.3 link_ntoa.3 \
|
||||
linkaddr.3 link_ntoa_r.3
|
||||
MLINKS+=rcmd.3 iruserok.3 \
|
||||
rcmd.3 iruserok_sa.3 \
|
||||
rcmd.3 rcmd_af.3 \
|
||||
|
|
|
|||
|
|
@ -138,6 +138,10 @@ FBSD_1.3 {
|
|||
sctp_sendv;
|
||||
};
|
||||
|
||||
FBSD_1.8 {
|
||||
link_ntoa_r;
|
||||
};
|
||||
|
||||
FBSDprivate_1.0 {
|
||||
_nsdispatch;
|
||||
_nsyyerror; /* generated from nslexer.l */
|
||||
|
|
|
|||
|
|
@ -28,12 +28,13 @@
|
|||
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
.\" SUCH DAMAGE.
|
||||
.\"
|
||||
.Dd February 28, 2007
|
||||
.Dd May 7, 2025
|
||||
.Dt LINK_ADDR 3
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm link_addr ,
|
||||
.Nm link_ntoa
|
||||
.Nm link_ntoa ,
|
||||
.Nm link_ntoa_r
|
||||
.Nd elementary address specification routines for link level access
|
||||
.Sh LIBRARY
|
||||
.Lb libc
|
||||
|
|
@ -45,12 +46,15 @@
|
|||
.Fn link_addr "const char *addr" "struct sockaddr_dl *sdl"
|
||||
.Ft char *
|
||||
.Fn link_ntoa "const struct sockaddr_dl *sdl"
|
||||
.Ft int
|
||||
.Fn link_ntoa_r "const struct sockaddr_dl *sdl" "char *obuf" "size_t *buflen"
|
||||
.Sh DESCRIPTION
|
||||
The routine
|
||||
.Fn link_addr
|
||||
interprets character strings representing
|
||||
link-level addresses, returning binary information suitable
|
||||
for use in system calls.
|
||||
.Pp
|
||||
The routine
|
||||
.Fn link_ntoa
|
||||
takes
|
||||
|
|
@ -60,9 +64,34 @@ address and returns an
|
|||
string representing some of the information present,
|
||||
including the link level address itself, and the interface name
|
||||
or number, if present.
|
||||
The returned string is stored in a static buffer.
|
||||
This facility is experimental and is
|
||||
still subject to change.
|
||||
.Pp
|
||||
The routine
|
||||
.Fn link_ntoa_r
|
||||
behaves like
|
||||
.Fn link_ntoa ,
|
||||
except the string is placed in the provided buffer instead of a static
|
||||
buffer.
|
||||
The caller should initialize
|
||||
.Fa buflen
|
||||
to the number of bytes available in
|
||||
.Fa obuf .
|
||||
On return,
|
||||
.Fa buflen
|
||||
is set to the actual number of bytes required for the output buffer,
|
||||
including the NUL terminator.
|
||||
If
|
||||
.Fa obuf
|
||||
is NULL, then
|
||||
.Fa buflen
|
||||
is set as described, but nothing is written.
|
||||
This may be used to determine the required length of the buffer before
|
||||
calling
|
||||
.Fn link_ntoa_r
|
||||
a second time.
|
||||
.Pp
|
||||
For
|
||||
.Fn link_addr ,
|
||||
the string
|
||||
|
|
@ -94,6 +123,14 @@ The
|
|||
.Fn link_ntoa
|
||||
function
|
||||
always returns a null terminated string.
|
||||
.Pp
|
||||
The
|
||||
.Fn link_ntoa_r
|
||||
function returns 0 on success, or -1 if the provided buffer was not
|
||||
large enough; in the latter case, the contents of the buffer are
|
||||
indeterminate, but a trailing NUL will always be written if the buffer
|
||||
was at least one byte in size.
|
||||
.Pp
|
||||
The
|
||||
.Fn link_addr
|
||||
function
|
||||
|
|
@ -109,6 +146,10 @@ and
|
|||
.Fn link_ntoa
|
||||
functions appeared in
|
||||
.Bx 4.3 Reno .
|
||||
The
|
||||
.Fn link_ntoa_r
|
||||
function appeared in
|
||||
.Fx 15.0 .
|
||||
.Sh BUGS
|
||||
The returned values for link_ntoa
|
||||
reside in a static memory area.
|
||||
|
|
|
|||
|
|
@ -31,8 +31,12 @@
|
|||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <net/if_dl.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* States*/
|
||||
|
|
@ -113,53 +117,109 @@ link_addr(const char *addr, struct sockaddr_dl *sdl)
|
|||
return;
|
||||
}
|
||||
|
||||
static const char hexlist[] = "0123456789abcdef";
|
||||
|
||||
char *
|
||||
link_ntoa(const struct sockaddr_dl *sdl)
|
||||
{
|
||||
static char obuf[64];
|
||||
size_t buflen;
|
||||
_Static_assert(sizeof(obuf) >= IFNAMSIZ + 20, "obuf is too small");
|
||||
|
||||
/*
|
||||
* Ignoring the return value of link_ntoa_r() is safe here because it
|
||||
* always writes the terminating NUL. This preserves the traditional
|
||||
* behaviour of link_ntoa().
|
||||
*/
|
||||
buflen = sizeof(obuf);
|
||||
(void)link_ntoa_r(sdl, obuf, &buflen);
|
||||
return obuf;
|
||||
}
|
||||
|
||||
int
|
||||
link_ntoa_r(const struct sockaddr_dl *sdl, char *obuf, size_t *buflen)
|
||||
{
|
||||
static const char hexlist[] = "0123456789abcdef";
|
||||
char *out;
|
||||
const u_char *in, *inlim;
|
||||
int namelen, i, rem;
|
||||
size_t needed;
|
||||
|
||||
assert(sdl);
|
||||
assert(buflen);
|
||||
/* obuf may be null */
|
||||
|
||||
needed = 1; /* 1 for the NUL */
|
||||
out = obuf;
|
||||
if (obuf)
|
||||
rem = *buflen;
|
||||
else
|
||||
rem = 0;
|
||||
|
||||
/*
|
||||
* Check if at least n bytes are available in the output buffer, plus 1 for the
|
||||
* trailing NUL. If not, set rem = 0 so we stop writing.
|
||||
* Either way, increment needed by the amount we would have written.
|
||||
*/
|
||||
#define CHECK(n) do { \
|
||||
if ((SIZE_MAX - (n)) >= needed) \
|
||||
needed += (n); \
|
||||
if (rem >= ((n) + 1)) \
|
||||
rem -= (n); \
|
||||
else \
|
||||
rem = 0; \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Write the char c to the output buffer, unless the buffer is full.
|
||||
* Note that if obuf is NULL, rem is always zero.
|
||||
*/
|
||||
#define OUT(c) do { \
|
||||
if (rem > 0) \
|
||||
*out++ = (c); \
|
||||
} while (0)
|
||||
|
||||
namelen = (sdl->sdl_nlen <= IFNAMSIZ) ? sdl->sdl_nlen : IFNAMSIZ;
|
||||
|
||||
out = obuf;
|
||||
rem = sizeof(obuf);
|
||||
if (namelen > 0) {
|
||||
bcopy(sdl->sdl_data, out, namelen);
|
||||
out += namelen;
|
||||
rem -= namelen;
|
||||
CHECK(namelen);
|
||||
if (rem > 0) {
|
||||
bcopy(sdl->sdl_data, out, namelen);
|
||||
out += namelen;
|
||||
}
|
||||
|
||||
if (sdl->sdl_alen > 0) {
|
||||
*out++ = ':';
|
||||
rem--;
|
||||
CHECK(1);
|
||||
OUT(':');
|
||||
}
|
||||
}
|
||||
|
||||
in = (const u_char *)sdl->sdl_data + sdl->sdl_nlen;
|
||||
in = (const u_char *)LLADDR(sdl);
|
||||
inlim = in + sdl->sdl_alen;
|
||||
|
||||
while (in < inlim && rem > 1) {
|
||||
if (in != (const u_char *)sdl->sdl_data + sdl->sdl_nlen) {
|
||||
*out++ = '.';
|
||||
rem--;
|
||||
while (in < inlim) {
|
||||
if (in != (const u_char *)LLADDR(sdl)) {
|
||||
CHECK(1);
|
||||
OUT('.');
|
||||
}
|
||||
i = *in++;
|
||||
if (i > 0xf) {
|
||||
if (rem < 3)
|
||||
break;
|
||||
*out++ = hexlist[i >> 4];
|
||||
*out++ = hexlist[i & 0xf];
|
||||
rem -= 2;
|
||||
CHECK(2);
|
||||
OUT(hexlist[i >> 4]);
|
||||
OUT(hexlist[i & 0xf]);
|
||||
} else {
|
||||
if (rem < 2)
|
||||
break;
|
||||
*out++ = hexlist[i];
|
||||
rem--;
|
||||
CHECK(1);
|
||||
OUT(hexlist[i]);
|
||||
}
|
||||
}
|
||||
*out = 0;
|
||||
return (obuf);
|
||||
|
||||
#undef CHECK
|
||||
#undef OUT
|
||||
|
||||
/*
|
||||
* We always leave enough room for the NUL if possible, but the user
|
||||
* might have passed a NULL or zero-length buffer.
|
||||
*/
|
||||
if (out && *buflen)
|
||||
*out = '\0';
|
||||
|
||||
*buflen = needed;
|
||||
return ((rem > 0) ? 0 : -1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include <net/if_dl.h>
|
||||
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <ranges>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
|
|
@ -262,10 +263,120 @@ ATF_TEST_CASE_BODY(overlong)
|
|||
::link_ntoa(&sdl));
|
||||
}
|
||||
|
||||
/*
|
||||
* Test link_ntoa_r, the re-entrant version of link_ntoa().
|
||||
*/
|
||||
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;
|
||||
|
||||
// Return the contents of buf as a string, using the NUL terminator to
|
||||
// determine length. This is to ensure we're using the return value in
|
||||
// the same way C code would, but we do a bit more verification to
|
||||
// elicit a test failure rather than a SEGV if it's broken.
|
||||
auto bufstr = [&buf]() -> std::string_view {
|
||||
// Find the NUL.
|
||||
auto end = std::ranges::find(buf, '\0');
|
||||
ATF_REQUIRE(end != buf.end());
|
||||
|
||||
// Intentionally chopping the NUL off.
|
||||
return {begin(buf), end};
|
||||
};
|
||||
|
||||
// Resize the buffer and set the contents to a known garbage value, so
|
||||
// we don't accidentally have a NUL in the right place when link_ntoa_r
|
||||
// didn't put it there.
|
||||
auto resetbuf = [&buf, &len](std::size_t size) {
|
||||
len = size;
|
||||
buf.resize(len);
|
||||
std::ranges::fill(buf, garbage);
|
||||
};
|
||||
|
||||
// Test a short address with a large buffer.
|
||||
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());
|
||||
|
||||
// Test a buffer which is exactly the right size.
|
||||
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());
|
||||
|
||||
// Test various short buffers, using a table of buffer length and the
|
||||
// output we expect. All of these should produce valid but truncated
|
||||
// strings, with a trailing NUL and with buflen set correctly.
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
// Test a NULL buffer, which should just set buflen.
|
||||
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);
|
||||
|
||||
// A NULL buffer with a non-zero length should also be accepted.
|
||||
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);
|
||||
|
||||
// Test a non-NULL buffer, but with a length of zero.
|
||||
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);
|
||||
// Check we really didn't write anything.
|
||||
ATF_REQUIRE_EQ(garbage, buf[0]);
|
||||
|
||||
// Test a buffer which would be truncated in the middle of a two-digit
|
||||
// hex octet, which should not write the truncated octet at all.
|
||||
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, nonether);
|
||||
ATF_ADD_TEST_CASE(tcs, overlong);
|
||||
ATF_ADD_TEST_CASE(tcs, link_ntoa_r);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ struct sockaddr_dl *link_init_sdl(struct ifnet *, struct sockaddr *, u_char);
|
|||
__BEGIN_DECLS
|
||||
void link_addr(const char *, struct sockaddr_dl *);
|
||||
char *link_ntoa(const struct sockaddr_dl *);
|
||||
int link_ntoa_r(const struct sockaddr_dl *, char *, size_t *);
|
||||
__END_DECLS
|
||||
|
||||
#endif /* !_KERNEL */
|
||||
|
|
|
|||
Loading…
Reference in a new issue