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
This commit is contained in:
committed by
Kristof Provost
parent
3e9f4fd6fc
commit
8ef0093f29
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
LIB= sysdecode
|
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" || \
|
.if ${MACHINE_CPUARCH} == "aarch64" || ${MACHINE_CPUARCH} == "amd64" || \
|
||||||
${MACHINE_CPUARCH} == "i386"
|
${MACHINE_CPUARCH} == "i386"
|
||||||
SRCS+= linux.c
|
SRCS+= linux.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);
|
||||||
|
}
|
||||||
@@ -134,6 +134,7 @@ bool sysdecode_wait4_options(FILE *_fp, int _options, int *_rem);
|
|||||||
bool sysdecode_wait6_options(FILE *_fp, int _options, int *_rem);
|
bool sysdecode_wait6_options(FILE *_fp, int _options, int *_rem);
|
||||||
const char *sysdecode_whence(int _whence);
|
const char *sysdecode_whence(int _whence);
|
||||||
bool sysdecode_shmflags(FILE *_fp, int _flags, int *_rem);
|
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__)
|
#if defined(__i386__) || defined(__amd64__) || defined(__aarch64__)
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <netinet/sctp.h>
|
#include <netinet/sctp.h>
|
||||||
|
#include <netlink/netlink.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@@ -1568,6 +1569,66 @@ user_ptr32_to_psaddr(int32_t user_pointer)
|
|||||||
return ((psaddr_t)(uintptr_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
|
* Converts a syscall argument into a string. Said string is
|
||||||
* allocated via malloc(), so needs to be free()'d. sc 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);
|
fputs("{", fp);
|
||||||
print_sockaddr(fp, trussinfo, (uintptr_t)msghdr.msg_name, msghdr.msg_namelen);
|
print_sockaddr(fp, trussinfo, (uintptr_t)msghdr.msg_name, msghdr.msg_namelen);
|
||||||
fprintf(fp, ",%d,", 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);
|
fprintf(fp, ",%d,", msghdr.msg_iovlen);
|
||||||
print_cmsgs(fp, pid, sc->type & OUT, &msghdr);
|
print_cmsgs(fp, pid, sc->type & OUT, &msghdr);
|
||||||
fprintf(fp, ",%u,", msghdr.msg_controllen);
|
fprintf(fp, ",%u,", msghdr.msg_controllen);
|
||||||
|
|||||||
Reference in New Issue
Block a user