Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 03 Apr 2026 06:54:52 +0000
From:      Kristof Provost <kp@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Cc:        Ishan Agrawal <iagrawal9990@gmail.com>
Subject:   git: 8ef0093f297a - main - truss: add support for decoding Netlink messages
Message-ID:  <69cf643c.2356e.2d0a0374@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=8ef0093f297af7c917037f058af9813105e67662

commit 8ef0093f297af7c917037f058af9813105e67662
Author:     Ishan Agrawal <iagrawal9990@gmail.com>
AuthorDate: 2026-01-06 12:05:59 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2026-04-03 06:52:29 +0000

    truss: add support for decoding Netlink messages
    
    Netlink usage is growing in FreeBSD. This patch adds support to
    `truss(1)` to decode Netlink headers in sendmsg/recvmsg calls, making
    debugging network configuration tools significantly easier.
    
    Changes:
    
    libsysdecode: Add `sysdecode_netlink()` to parse struct `nlmsghdr`.
    truss: Detect `AF_NETLINK` sockets and decode the message payload.
    
    Reviewed by:    kp
    Signed-off-by:  Ishan Agrawal <iagrawal9990@gmail.com>
    Github PR:      https://github.com/freebsd/freebsd-src/pull/1950
---
 lib/libsysdecode/Makefile    |  2 +-
 lib/libsysdecode/netlink.c   | 94 ++++++++++++++++++++++++++++++++++++++++++++
 lib/libsysdecode/sysdecode.h |  1 +
 usr.bin/truss/syscalls.c     | 67 ++++++++++++++++++++++++++++++-
 4 files changed, 162 insertions(+), 2 deletions(-)

diff --git a/lib/libsysdecode/Makefile b/lib/libsysdecode/Makefile
index 60422d3fc9ef..85c76bcffd8e 100644
--- a/lib/libsysdecode/Makefile
+++ b/lib/libsysdecode/Makefile
@@ -2,7 +2,7 @@
 
 LIB=	sysdecode
 
-SRCS=	errno.c flags.c ioctl.c signal.c syscallnames.c utrace.c support.c
+SRCS=	errno.c flags.c ioctl.c netlink.c signal.c syscallnames.c utrace.c support.c
 .if ${MACHINE_CPUARCH} == "aarch64" || ${MACHINE_CPUARCH} == "amd64" || \
 	${MACHINE_CPUARCH} == "i386"
 SRCS+= linux.c
diff --git a/lib/libsysdecode/netlink.c b/lib/libsysdecode/netlink.c
new file mode 100644
index 000000000000..a28a0a061134
--- /dev/null
+++ b/lib/libsysdecode/netlink.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2026 Ishan Agrawal
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/param.h>
+#include <netlink/netlink.h>
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "sysdecode.h"
+
+/*
+ * Decodes a buffer as a Netlink message stream.
+ *
+ * Returns true if the data was successfully decoded as Netlink.
+ * Returns false if the data is malformed, allowing the caller
+ * to fallback to a standard hex/string dump.
+ */
+bool
+sysdecode_netlink(FILE *fp, const void *buf, size_t len)
+{
+	const struct nlmsghdr *nl = buf;
+	size_t remaining = len;
+	bool first = true;
+
+	/* Basic sanity check: Buffer must be at least one header size. */
+	if (remaining < sizeof(struct nlmsghdr))
+		return (false);
+
+	/* * Protocol Sanity Check:
+	 * The first message length must be valid (>= header) and fit
+	 * inside the provided buffer snapshot.
+	 */
+	if (nl->nlmsg_len < sizeof(struct nlmsghdr) || nl->nlmsg_len > remaining)
+		return (false);
+
+	fprintf(fp, "netlink{");
+
+	while (remaining >= sizeof(struct nlmsghdr)) {
+		if (!first)
+			fprintf(fp, ",");
+
+		/* Safety check for current message. */
+		if (nl->nlmsg_len < sizeof(struct nlmsghdr) ||
+		    nl->nlmsg_len > remaining) {
+			fprintf(fp, "<truncated>");
+			break;
+		}
+
+		fprintf(fp, "len=%u,type=", nl->nlmsg_len);
+
+		/* Decode Standard Message Types. */
+		switch (nl->nlmsg_type) {
+		case NLMSG_NOOP:
+			fprintf(fp, "NLMSG_NOOP");
+			break;
+		case NLMSG_ERROR:
+			fprintf(fp, "NLMSG_ERROR");
+			break;
+		case NLMSG_DONE:
+			fprintf(fp, "NLMSG_DONE");
+			break;
+		case NLMSG_OVERRUN:
+			fprintf(fp, "NLMSG_OVERRUN");
+			break;
+		default:
+			fprintf(fp, "%u", nl->nlmsg_type);
+			break;
+		}
+
+		fprintf(fp, ",flags=");
+		/* TODO: decode flags symbolically using sysdecode_mask. */
+		fprintf(fp, "0x%x", nl->nlmsg_flags);
+
+		fprintf(fp, ",seq=%u,pid=%u", nl->nlmsg_seq, nl->nlmsg_pid);
+
+		/* Handle Alignment (Netlink messages are 4-byte aligned). */
+		size_t aligned_len = NLMSG_ALIGN(nl->nlmsg_len);
+		if (aligned_len > remaining)
+			remaining = 0;
+		else
+			remaining -= aligned_len;
+
+		nl = (const struct nlmsghdr *)(const void *)((const char *)nl + aligned_len);
+		first = false;
+	}
+
+	fprintf(fp, "}");
+	return (true);
+}
diff --git a/lib/libsysdecode/sysdecode.h b/lib/libsysdecode/sysdecode.h
index c95d7f71379b..dad9d447478c 100644
--- a/lib/libsysdecode/sysdecode.h
+++ b/lib/libsysdecode/sysdecode.h
@@ -134,6 +134,7 @@ bool	sysdecode_wait4_options(FILE *_fp, int _options, int *_rem);
 bool	sysdecode_wait6_options(FILE *_fp, int _options, int *_rem);
 const char *sysdecode_whence(int _whence);
 bool	sysdecode_shmflags(FILE *_fp, int _flags, int *_rem);
+bool	sysdecode_netlink(FILE *_fp, const void *_buf, size_t _len);
 
 #if defined(__i386__) || defined(__amd64__) || defined(__aarch64__)
 
diff --git a/usr.bin/truss/syscalls.c b/usr.bin/truss/syscalls.c
index 7b299bd2e1ff..abb9d18d6783 100644
--- a/usr.bin/truss/syscalls.c
+++ b/usr.bin/truss/syscalls.c
@@ -58,6 +58,7 @@
 #include <sys/wait.h>
 #include <netinet/in.h>
 #include <netinet/sctp.h>
+#include <netlink/netlink.h>
 #include <arpa/inet.h>
 
 #include <assert.h>
@@ -1568,6 +1569,66 @@ user_ptr32_to_psaddr(int32_t user_pointer)
 	return ((psaddr_t)(uintptr_t)user_pointer);
 }
 
+#define NETLINK_MAX_DECODE 4096
+
+/*
+ * Reads the first IOV and attempts to print it as Netlink using libsysdecode.
+ * Returns true if successful, false if fallback to standard print is needed.
+ */
+static bool
+print_netlink(FILE *fp, struct trussinfo *trussinfo, struct msghdr *msg)
+{
+	struct sockaddr_storage ss;
+	struct iovec iov;
+	struct ptrace_io_desc piod;
+	char *buf;
+	pid_t pid = trussinfo->curthread->proc->pid;
+	bool success = false;
+
+	/* Only decode AF_NETLINK sockets. */
+	if (msg->msg_name == NULL || msg->msg_namelen < offsetof(struct sockaddr, sa_data)
+		|| msg->msg_iovlen == 0 || msg->msg_iov == NULL)
+		return (false);
+
+	if (get_struct(pid, (uintptr_t)msg->msg_name, &ss,
+	    MIN(sizeof(ss), msg->msg_namelen)) == -1)
+		return (false);
+
+	if (ss.ss_family != AF_NETLINK)
+		return (false);
+
+	if (get_struct(pid, (uintptr_t)msg->msg_iov, &iov, sizeof(iov)) == -1)
+		return (false);
+
+	/* Cap read size to avoid unbounded allocations. */
+	size_t read_len = MIN(iov.iov_len, NETLINK_MAX_DECODE);
+	if (read_len == 0)
+		return (false);
+
+	buf = malloc(read_len);
+	if (buf == NULL)
+		return (false);
+
+	/* Snapshot User Memory using PTRACE. */
+	piod.piod_op = PIOD_READ_D;
+	piod.piod_offs = iov.iov_base;
+	piod.piod_addr = buf;
+	piod.piod_len = read_len;
+
+	if (ptrace(PT_IO, pid, (caddr_t)&piod, 0) == -1) {
+		free(buf);
+		return (false);
+	}
+
+	/* Delegate Decoding to libsysdecode. */
+	if (sysdecode_netlink(fp, buf, read_len)) {
+		success = true;
+	}
+	free(buf);
+
+	return (success);
+}
+
 /*
  * Converts a syscall argument into a string.  Said string is
  * allocated via malloc(), so needs to be free()'d.  sc is
@@ -2706,7 +2767,11 @@ print_arg(struct syscall_arg *sc, syscallarg_t *args, syscallarg_t *retval,
 		fputs("{", fp);
 		print_sockaddr(fp, trussinfo, (uintptr_t)msghdr.msg_name, msghdr.msg_namelen);
 		fprintf(fp, ",%d,", msghdr.msg_namelen);
-		print_iovec(fp, trussinfo, (uintptr_t)msghdr.msg_iov, msghdr.msg_iovlen);
+		/* Attempt Netlink decode; fallback to standard iovec if it fails. */
+		if (!print_netlink(fp, trussinfo, &msghdr)) {
+			print_iovec(fp, trussinfo, (uintptr_t)msghdr.msg_iov,
+			    msghdr.msg_iovlen);
+		}
 		fprintf(fp, ",%d,", msghdr.msg_iovlen);
 		print_cmsgs(fp, pid, sc->type & OUT, &msghdr);
 		fprintf(fp, ",%u,", msghdr.msg_controllen);


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?69cf643c.2356e.2d0a0374>