git: 88cd1e17a7d8 - main - genl: add RPC parser that dumps what sys/rpc/clnt_nl.c sends

From: Gleb Smirnoff <glebius_at_FreeBSD.org>
Date: Sat, 01 Feb 2025 09:02:06 UTC
The branch main has been updated by glebius:

URL: https://cgit.FreeBSD.org/src/commit/?id=88cd1e17a7d8ba66eb5fb04441dd9264d48708b1

commit 88cd1e17a7d8ba66eb5fb04441dd9264d48708b1
Author:     Gleb Smirnoff <glebius@FreeBSD.org>
AuthorDate: 2025-02-01 01:02:04 +0000
Commit:     Gleb Smirnoff <glebius@FreeBSD.org>
CommitDate: 2025-02-01 09:00:25 +0000

    genl: add RPC parser that dumps what sys/rpc/clnt_nl.c sends
    
    Use a separate file for the RPC parser.  Potentially it may get bigger.
    Also to avoid include RPC header pollution of the genl.c.
    
    Reviewed by:            rmacklem
    Differential Revision:  https://reviews.freebsd.org/D48551
---
 usr.bin/genl/Makefile     |   1 +
 usr.bin/genl/genl.c       |  33 +++++++---
 usr.bin/genl/genl.h       |  31 +++++++++
 usr.bin/genl/parser_rpc.c | 161 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 216 insertions(+), 10 deletions(-)

diff --git a/usr.bin/genl/Makefile b/usr.bin/genl/Makefile
index 15e60300de02..8ab5919b1486 100644
--- a/usr.bin/genl/Makefile
+++ b/usr.bin/genl/Makefile
@@ -1,3 +1,4 @@
 PROG=	genl
+SRCS=	genl.c parser_rpc.c
 
 .include <bsd.prog.mk>
diff --git a/usr.bin/genl/genl.c b/usr.bin/genl/genl.c
index cb01c6bcdd26..51d2dfaa6364 100644
--- a/usr.bin/genl/genl.c
+++ b/usr.bin/genl/genl.c
@@ -42,11 +42,10 @@
 #include <netlink/netlink_snl_generic.h>
 #include <netlink/netlink_sysevent.h>
 
+#include "genl.h"
+
 static int monitor_mcast(int argc, char **argv);
 static int list_families(int argc, char **argv);
-static void parser_nlctrl_notify(struct snl_state *ss, struct nlmsghdr *hdr);
-static void parser_nlsysevent(struct snl_state *ss, struct nlmsghdr *hdr);
-static void parser_fallback(struct snl_state *ss, struct nlmsghdr *hdr);
 
 static struct commands {
 	const char *name;
@@ -57,12 +56,16 @@ static struct commands {
 	{ "list", "list", list_families },
 };
 
+static monitor_parser_t parser_nlctrl_notify;
+static monitor_parser_t parser_nlsysevent;
+
 static struct mcast_parsers {
 	const char *family;
-	void (*parser)(struct snl_state *ss, struct nlmsghdr *hdr);
+	monitor_parser_t *parser;
 } mcast_parsers [] = {
 	{ "nlctrl", parser_nlctrl_notify },
 	{ "nlsysevent", parser_nlsysevent },
+	{ "rpc", parser_rpc },
 };
 
 struct nlevent {
@@ -252,7 +255,7 @@ dump_family(struct genl_family *family)
 	dump_mcast_groups(&family->mcast_groups);
 }
 
-void
+static void
 parser_nlctrl_notify(struct snl_state *ss, struct nlmsghdr *hdr)
 {
 	struct genl_family family = {};
@@ -262,7 +265,7 @@ parser_nlctrl_notify(struct snl_state *ss, struct nlmsghdr *hdr)
 		dump_family(&family);
 }
 
-void
+static void
 parser_nlsysevent(struct snl_state *ss, struct nlmsghdr *hdr)
 {
 	struct nlevent ne = {};
@@ -276,15 +279,25 @@ parser_nlsysevent(struct snl_state *ss, struct nlmsghdr *hdr)
 	}
 }
 
-void
-parser_fallback(struct snl_state *ss __unused, struct nlmsghdr *hdr __unused)
+static void
+parser_fallback(struct snl_state *ss __unused, struct nlmsghdr *hdr)
 {
-	printf("New unknown message\n");
+	printf("Unknown message: type 0x%x, length %u\n",
+	    hdr->nlmsg_type, hdr->nlmsg_len);
 }
 
 /* Populated by monitor_mcast() and may be used by protocol parser callbacks. */
 static struct genl_family attrs;
 
+const char *
+group_name(uint32_t id)
+{
+	for (u_int i = 0; i < attrs.mcast_groups.num_groups; i++)
+		if (attrs.mcast_groups.groups[i]->id == id)
+			return (attrs.mcast_groups.groups[i]->name);
+	return ("???");
+}
+
 static int
 monitor_mcast(int argc, char **argv)
 {
@@ -294,7 +307,7 @@ monitor_mcast(int argc, char **argv)
 	struct pollfd pfd;
 	bool found = false;
 	bool all = false;
-	void (*parser)(struct snl_state *ss, struct nlmsghdr *hdr);
+	monitor_parser_t *parser;
 
 	if (argc < 1 || argc > 2) {
 		usage();
diff --git a/usr.bin/genl/genl.h b/usr.bin/genl/genl.h
new file mode 100644
index 000000000000..7c518245440c
--- /dev/null
+++ b/usr.bin/genl/genl.h
@@ -0,0 +1,31 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2025 Gleb Smirnoff <glebius@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions~
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+extern const char * group_name(uint32_t id);
+
+typedef	void monitor_parser_t(struct snl_state *, struct nlmsghdr *);
+extern monitor_parser_t parser_rpc;
diff --git a/usr.bin/genl/parser_rpc.c b/usr.bin/genl/parser_rpc.c
new file mode 100644
index 000000000000..d63d1425c811
--- /dev/null
+++ b/usr.bin/genl/parser_rpc.c
@@ -0,0 +1,161 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2025 Gleb Smirnoff <glebius@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions~
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <err.h>
+
+#include <netinet/in.h>
+
+#include <netlink/netlink.h>
+#include <netlink/netlink_generic.h>
+#include <netlink/netlink_snl.h>
+#include <netlink/netlink_snl_generic.h>
+
+#include <rpc/types.h>
+#include <rpc/xdr.h>
+#include <rpc/auth.h>
+#include <rpc/clnt.h>
+#include <rpc/rpc_msg.h>
+#include <rpc/clnt_nl.h>
+
+#include "genl.h"
+
+struct nl_request_parsed {
+	uint32_t	group;
+	struct nlattr	*data;
+};
+static const struct snl_attr_parser rpcnl_attr_parser[] = {
+#define	OUT(field)	offsetof(struct nl_request_parsed, field)
+    { .type = RPCNL_REQUEST_GROUP, .off = OUT(group),
+       .cb = snl_attr_get_uint32 },
+    { .type = RPCNL_REQUEST_BODY, .off = OUT(data), .cb = snl_attr_get_nla },
+#undef OUT
+};
+SNL_DECLARE_PARSER(request_parser, struct genlmsghdr, snl_f_p_empty,
+    rpcnl_attr_parser);
+
+void
+parser_rpc(struct snl_state *ss __unused, struct nlmsghdr *hdr)
+{
+	struct nl_request_parsed req;
+	struct genlmsghdr *ghdr = (struct genlmsghdr *)(hdr + 1);
+	XDR xdrs;
+	struct rpc_msg msg;
+	struct opaque_auth *oa;
+	int32_t *buf;
+
+	if (!snl_parse_nlmsg(NULL, hdr, &request_parser, &req))
+		errx(EXIT_FAILURE, "failed to parse RPC message");
+
+	printf("RPC %s: group %8s[0x%2x] length %4u XDR length %4u\n",
+	    ghdr->cmd == RPCNL_REQUEST ? "request" : "unknown",
+	    group_name(req.group), req.group,
+	    hdr->nlmsg_len, NLA_DATA_LEN(req.data));
+
+	xdrmem_create(&xdrs, NLA_DATA(req.data), NLA_DATA_LEN(req.data),
+	    XDR_DECODE);
+	if ((buf = XDR_INLINE(&xdrs, 8 * BYTES_PER_XDR_UNIT)) == NULL) {
+		printf("\trunt datagram\n");
+		return;
+	}
+
+	msg.rm_xid = IXDR_GET_U_INT32(buf);
+	msg.rm_direction = IXDR_GET_ENUM(buf, enum msg_type);
+	msg.rm_call.cb_rpcvers = IXDR_GET_U_INT32(buf);
+	msg.rm_call.cb_prog = IXDR_GET_U_INT32(buf);
+	msg.rm_call.cb_vers = IXDR_GET_U_INT32(buf);
+	msg.rm_call.cb_proc = IXDR_GET_U_INT32(buf);
+	printf("    %5s: xid 0x%-8x program 0x%08xv%u procedure %u\n",
+	    msg.rm_direction == CALL ? "CALL" : "REPLY", msg.rm_xid,
+            msg.rm_call.cb_prog, msg.rm_call.cb_vers, msg.rm_call.cb_proc);
+
+	oa = &msg.rm_call.cb_cred;
+	oa->oa_flavor = IXDR_GET_ENUM(buf, enum_t);
+	oa->oa_length = (u_int)IXDR_GET_U_INT32(buf);
+	if (oa->oa_length) {
+		printf("\tcb_cred auth flavor %u length %u\n",
+		    oa->oa_flavor, oa->oa_length);
+/*
+ *	Excerpt from rpc_callmsg.c, if we want to parse cb_cred better.
+		if (oa->oa_length > MAX_AUTH_BYTES) {
+			return (FALSE);
+		}
+		if (oa->oa_base == NULL) {
+			oa->oa_base = (caddr_t)
+			    mem_alloc(oa->oa_length);
+			if (oa->oa_base == NULL)
+				return (FALSE);
+		}
+		buf = XDR_INLINE(&xdrs, RNDUP(oa->oa_length));
+		if (buf == NULL) {
+			if (xdr_opaque(&xdrs, oa->oa_base,
+			    oa->oa_length) == FALSE) {
+				return (FALSE);
+			}
+		} else {
+			memmove(oa->oa_base, buf,
+			    oa->oa_length);
+		}
+*/
+	}
+	oa = &msg.rm_call.cb_verf;
+	buf = XDR_INLINE(&xdrs, 2 * BYTES_PER_XDR_UNIT);
+	if (buf == NULL) {
+		if (xdr_enum(&xdrs, &oa->oa_flavor) == FALSE ||
+		    xdr_u_int(&xdrs, &oa->oa_length) == FALSE)
+			return;
+	} else {
+		oa->oa_flavor = IXDR_GET_ENUM(buf, enum_t);
+		oa->oa_length = (u_int)IXDR_GET_U_INT32(buf);
+	}
+	if (oa->oa_length) {
+		printf("\tcb_verf auth flavor %u length %u\n",
+		    oa->oa_flavor, oa->oa_length);
+/*
+ *	Excerpt from rpc_callmsg.c, if we want to parse cb_verf better.
+		if (oa->oa_length > MAX_AUTH_BYTES) {
+			return (FALSE);
+		}
+		if (oa->oa_base == NULL) {
+			oa->oa_base = (caddr_t)
+			    mem_alloc(oa->oa_length);
+			if (oa->oa_base == NULL)
+				return (FALSE);
+		}
+		buf = XDR_INLINE(&xdrs, RNDUP(oa->oa_length));
+		if (buf == NULL) {
+			if (xdr_opaque(&xdrs, oa->oa_base,
+			    oa->oa_length) == FALSE) {
+				return (FALSE);
+			}
+		} else {
+			memmove(oa->oa_base, buf,
+			    oa->oa_length);
+		}
+*/
+	}
+}