Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 20 May 2025 21:52:58 GMT
From:      Lexi Winter <ivy@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org
Subject:   git: 02a07676c382 - stable/14 - libc: add link_ntoa_r()
Message-ID:  <202505202152.54KLqwmw039740@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch stable/14 has been updated by ivy:

URL: https://cgit.FreeBSD.org/src/commit/?id=02a07676c382836917b711d2b523b49b9a879183

commit 02a07676c382836917b711d2b523b49b9a879183
Author:     Lexi Winter <ivy@FreeBSD.org>
AuthorDate: 2025-05-07 09:34:08 +0000
Commit:     Lexi Winter <ivy@FreeBSD.org>
CommitDate: 2025-05-20 21:49:52 +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
    
    (cherry picked from commit da509c29089ab169b667ebdf82aa903987ba9c6d)
---
 lib/libc/net/Makefile.inc            |   7 ++-
 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, 249 insertions(+), 29 deletions(-)

diff --git a/lib/libc/net/Makefile.inc b/lib/libc/net/Makefile.inc
index ea5ddf875151..18ca2c3b8df1 100644
--- a/lib/libc/net/Makefile.inc
+++ b/lib/libc/net/Makefile.inc
@@ -108,8 +108,11 @@ MLINKS+=inet6_opt_init.3 inet6_opt_append.3 \
 	inet6_rthdr_space.3 inet6_rthdr_lasthop.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 
-MLINKS+=rcmd.3 iruserok.3 rcmd.3 iruserok_sa.3 \
+MLINKS+=linkaddr.3 link_addr.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 \
 	rcmd.3 rresvport.3 rcmd.3 rresvport_af.3 \
 	rcmd.3 ruserok.3
diff --git a/lib/libc/net/Symbol.map b/lib/libc/net/Symbol.map
index 4831868c0e55..784f83a90bb9 100644
--- a/lib/libc/net/Symbol.map
+++ b/lib/libc/net/Symbol.map
@@ -149,6 +149,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 6c2fb6ccfdc3..c91a50c9b5b9 100644
--- a/lib/libc/net/linkaddr.3
+++ b/lib/libc/net/linkaddr.3
@@ -30,12 +30,13 @@
 .\"
 .\"     From: @(#)linkaddr.3	8.1 (Berkeley) 7/28/93
 .\"
-.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
@@ -47,12 +48,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
@@ -62,9 +66,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
@@ -96,6 +125,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
@@ -111,6 +148,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 5cff503331db..47ec378d5fd8 100644
--- a/lib/libc/net/linkaddr.c
+++ b/lib/libc/net/linkaddr.c
@@ -34,8 +34,12 @@ static char sccsid[] = "@(#)linkaddr.c	8.1 (Berkeley) 6/4/93";
 #endif /* LIBC_SCCS and not lint */
 #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*/
@@ -116,53 +120,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 31579efdeda4..80a5f452f024 100644
--- a/sys/net/if_dl.h
+++ b/sys/net/if_dl.h
@@ -87,6 +87,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 */



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202505202152.54KLqwmw039740>