git: da509c29089a - main - libc: add link_ntoa_r()
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Wed, 07 May 2025 09:52:41 UTC
The branch main has been updated by ivy:
URL: https://cgit.FreeBSD.org/src/commit/?id=da509c29089ab169b667ebdf82aa903987ba9c6d
commit da509c29089ab169b667ebdf82aa903987ba9c6d
Author: Lexi Winter <ivy@FreeBSD.org>
AuthorDate: 2025-05-07 09:34:08 +0000
Commit: Lexi Winter <ivy@FreeBSD.org>
CommitDate: 2025-05-07 09:52:04 +0000
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
---
lib/libc/net/Makefile.inc | 3 +-
lib/libc/net/Symbol.map | 4 ++
lib/libc/net/linkaddr.3 | 45 +++++++++++++-
lib/libc/net/linkaddr.c | 110 ++++++++++++++++++++++++++--------
lib/libc/tests/net/link_addr_test.cc | 111 +++++++++++++++++++++++++++++++++++
sys/net/if_dl.h | 1 +
6 files changed, 246 insertions(+), 28 deletions(-)
diff --git a/lib/libc/net/Makefile.inc b/lib/libc/net/Makefile.inc
index 58c650b207cf..7e9e5295c78c 100644
--- a/lib/libc/net/Makefile.inc
+++ b/lib/libc/net/Makefile.inc
@@ -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 \
diff --git a/lib/libc/net/Symbol.map b/lib/libc/net/Symbol.map
index ebc3a56b549f..25e79b4e5807 100644
--- a/lib/libc/net/Symbol.map
+++ b/lib/libc/net/Symbol.map
@@ -138,6 +138,10 @@ FBSD_1.3 {
sctp_sendv;
};
+FBSD_1.8 {
+ link_ntoa_r;
+};
+
FBSDprivate_1.0 {
_nsdispatch;
_nsyyerror; /* generated from nslexer.l */
diff --git a/lib/libc/net/linkaddr.3 b/lib/libc/net/linkaddr.3
index ee3ea3056c71..ccfbf4c8d2e1 100644
--- a/lib/libc/net/linkaddr.3
+++ b/lib/libc/net/linkaddr.3
@@ -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.
diff --git a/lib/libc/net/linkaddr.c b/lib/libc/net/linkaddr.c
index dedc910b9474..27d444666185 100644
--- a/lib/libc/net/linkaddr.c
+++ b/lib/libc/net/linkaddr.c
@@ -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;
- namelen = (sdl->sdl_nlen <= IFNAMSIZ) ? sdl->sdl_nlen : IFNAMSIZ;
+ assert(sdl);
+ assert(buflen);
+ /* obuf may be null */
+ needed = 1; /* 1 for the NUL */
out = obuf;
- rem = sizeof(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;
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);
}
diff --git a/lib/libc/tests/net/link_addr_test.cc b/lib/libc/tests/net/link_addr_test.cc
index 6b9f5a841ef7..ea8f64c6723b 100644
--- a/lib/libc/tests/net/link_addr_test.cc
+++ b/lib/libc/tests/net/link_addr_test.cc
@@ -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);
}
diff --git a/sys/net/if_dl.h b/sys/net/if_dl.h
index f8c7c2f75956..683f298f5463 100644
--- a/sys/net/if_dl.h
+++ b/sys/net/if_dl.h
@@ -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 */