git: 3c76623ad553 - main - ipfw: add 'internal monitor' subcommand to capture rtsock messages.

From: Andrey V. Elsukov <ae_at_FreeBSD.org>
Date: Fri, 18 Apr 2025 12:34:20 UTC
The branch main has been updated by ae:

URL: https://cgit.FreeBSD.org/src/commit/?id=3c76623ad55369c3998b2b00d7322f8aeedc98fd

commit 3c76623ad55369c3998b2b00d7322f8aeedc98fd
Author:     Andrey V. Elsukov <ae@FreeBSD.org>
AuthorDate: 2025-04-18 12:22:56 +0000
Commit:     Andrey V. Elsukov <ae@FreeBSD.org>
CommitDate: 2025-04-18 12:22:56 +0000

    ipfw: add 'internal monitor' subcommand to capture rtsock messages.
    
    This command is similar to route(8) monitor subcommand. It can be
    used for debugging rules in run-time.
    Also add __pritflike() to bprintf function and fix some related bugs.
    
    Obtained from:  Yandex LLC
    Sponsored by:   Yandex LLC
---
 sbin/ipfw/ipfw.8  |  11 +++++
 sbin/ipfw/ipfw2.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 sbin/ipfw/ipfw2.h |   3 +-
 3 files changed, 148 insertions(+), 3 deletions(-)

diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8
index 719f92e96e68..ddfdc35ce651 100644
--- a/sbin/ipfw/ipfw.8
+++ b/sbin/ipfw/ipfw.8
@@ -171,6 +171,8 @@ in-kernel NAT.\&
 .Nm
 .Cm internal iflist
 .Nm
+.Cm internal monitor Op Ar filter-comment
+.Nm
 .Cm internal talist
 .Nm
 .Cm internal vlist
@@ -4332,6 +4334,15 @@ sub-options:
 Lists all interface which are currently tracked by
 .Nm
 with their in-kernel status.
+.It Cm monitor Op Ar filter-comment
+Capture messages from
+.Xr route 4
+socket, that were logged using rules with
+.Cm log Cm logdst Ar rtsock
+opcode. Optional
+.Ar filter-comment
+can be specified to show only those messages, that were logged
+by rules with specific rule comment.
 .It Cm talist
 List all table lookup algorithms currently available.
 .El
diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c
index be796808380f..8eeff72463b5 100644
--- a/sbin/ipfw/ipfw2.c
+++ b/sbin/ipfw/ipfw2.c
@@ -47,6 +47,8 @@
 
 #include <net/ethernet.h>
 #include <net/if.h>		/* only IFNAMSIZ */
+#include <net/if_dl.h>
+#include <net/route.h>
 #include <netinet/in.h>
 #include <netinet/in_systm.h>	/* only n_short, n_long */
 #include <netinet/ip.h>
@@ -2009,7 +2011,7 @@ print_logdst(struct buf_pr *bp, uint16_t arg1)
 {
 	char const *comma = "";
 
-	bprintf(bp, " logdst ", arg1);
+	bprintf(bp, " logdst ");
 	if (arg1 & IPFW_LOG_SYSLOG) {
 		bprintf(bp, "%ssyslog", comma);
 		comma = ",";
@@ -2179,7 +2181,7 @@ print_action_instruction(struct buf_pr *bp, const struct format_opts *fo,
 		if (cmd->len == F_INSN_SIZE(ipfw_insn))
 			bprintf(bp, " %u", cmd->arg1);
 		else
-			bprintf(bp, " %ubytes",
+			bprintf(bp, " %lubytes",
 			    cmd->len * sizeof(uint32_t));
 		break;
 	case O_SETDSCP:
@@ -5956,6 +5958,7 @@ static struct _s_x intcmds[] = {
       { "iflist",	TOK_IFLIST },
       { "olist",	TOK_OLIST },
       { "vlist",	TOK_VLIST },
+      { "monitor",	TOK_MONITOR },
       { NULL, 0 }
 };
 
@@ -6030,6 +6033,132 @@ ipfw_list_objects(int ac __unused, char *av[] __unused)
 	free(olh);
 }
 
+static void
+bprint_sa(struct buf_pr *bp, const struct sockaddr *sa)
+{
+	struct sockaddr_storage ss;
+	char buf[INET6_ADDRSTRLEN];
+
+	memset(&ss, 0, sizeof(ss));
+	if (sa->sa_len == 0)
+		ss.ss_family = sa->sa_family;
+	else
+		memcpy(&ss, sa, sa->sa_len);
+
+	/* set ss_len in case it was shortened */
+	switch (sa->sa_family) {
+	case AF_INET:
+		ss.ss_len = sizeof(struct sockaddr_in);
+		break;
+	default:
+		ss.ss_len = sizeof(struct sockaddr_in6);
+	}
+	if (getnameinfo((const struct sockaddr *)&ss, ss.ss_len,
+	    buf, sizeof(buf), NULL, 0, NI_NUMERICHOST) != 0) {
+		bprintf(bp, "bad-addr");
+		return;
+	}
+	bprintf(bp, "%s", buf);
+}
+
+static void
+ipfw_rtsock_monitor(const char *filter)
+{
+	char msg[2048], buf[32];
+	struct timespec tp;
+	struct tm tm;
+	struct buf_pr bp;
+	struct rt_msghdr *hdr;
+	struct sockaddr *sa;
+	struct sockaddr_dl *sdl;
+	ipfwlog_rtsock_hdr_v2 *loghdr;
+	ssize_t msglen;
+	int rtsock;
+
+	rtsock = socket(PF_ROUTE, SOCK_RAW, AF_IPFWLOG);
+	if (rtsock < 0)
+		err(EX_UNAVAILABLE, "socket(AF_IPFWLOG)");
+	bp_alloc(&bp, 4096);
+	for (;;) {
+		msglen = read(rtsock, msg, sizeof(msg));
+		if (msglen < 0) {
+			warn("read()");
+			continue;
+		}
+		if (sizeof(*hdr) - msglen < 0)
+			continue;
+
+		hdr = (struct rt_msghdr *)msg;
+		if (hdr->rtm_version != RTM_VERSION ||
+		    hdr->rtm_type != RTM_IPFWLOG ||
+		    (hdr->rtm_addrs & (1 << RTAX_DST)) == 0 ||
+		    (hdr->rtm_addrs & (1 << RTAX_GATEWAY)) == 0 ||
+		    (hdr->rtm_addrs & (1 << RTAX_NETMASK)) == 0)
+			continue;
+
+		msglen -= sizeof(*hdr);
+		sdl = (struct sockaddr_dl *)(hdr + 1);
+		if (msglen - sizeof(*sdl) < 0 || msglen - SA_SIZE(sdl) < 0 ||
+		    sdl->sdl_family != AF_IPFWLOG ||
+		    sdl->sdl_type != 2 /* version */ ||
+		    sdl->sdl_alen != sizeof(*loghdr))
+			continue;
+
+		msglen -= SA_SIZE(sdl);
+		loghdr = (ipfwlog_rtsock_hdr_v2 *)sdl->sdl_data;
+		/* filter by rule comment. MAX_COMMENT_LEN = 80 */
+		if (filter != NULL &&
+		    strncmp(filter, loghdr->comment, 80) != 0)
+			continue;
+
+		sa = (struct sockaddr *)((char *)sdl + SA_SIZE(sdl));
+		if (msglen - SA_SIZE(sa) < 0)
+			continue;
+
+		msglen -= SA_SIZE(sa);
+		bp_flush(&bp);
+
+		clock_gettime(CLOCK_REALTIME, &tp);
+		localtime_r(&tp.tv_sec, &tm);
+		strftime(buf, sizeof(buf), "%T", &tm);
+		bprintf(&bp, "%s.%03ld AF %s", buf, tp.tv_nsec / 1000000,
+		    sa->sa_family == AF_INET ? "IPv4" : "IPv6");
+
+		bprintf(&bp, " %s >",
+		    ether_ntoa((const struct ether_addr *)loghdr->ether_shost));
+		bprintf(&bp, " %s, ",
+		    ether_ntoa((const struct ether_addr *)loghdr->ether_dhost));
+		bprint_sa(&bp, sa);
+
+		sa = (struct sockaddr *)((char *)sa + SA_SIZE(sa));
+		if (msglen - SA_SIZE(sa) < 0)
+			continue;
+
+		msglen -= SA_SIZE(sa);
+		bprintf(&bp, " > ");
+		bprint_sa(&bp, sa);
+		bprintf(&bp, ", set %u, rulenum %u, targ 0x%08x, "
+		    "%scmd[op %d, len %d, arg1 0x%04x], mark 0x%08x",
+		    sdl->sdl_index, loghdr->rulenum, loghdr->tablearg,
+		    (loghdr->cmd.len & F_NOT) ? "!": "",
+		    loghdr->cmd.opcode, F_LEN(&loghdr->cmd),
+		    loghdr->cmd.arg1, loghdr->mark);
+
+		sa = (struct sockaddr *)((char *)sa + SA_SIZE(sa));
+		if ((hdr->rtm_addrs & (1 << RTAX_GENMASK)) != 0 &&
+		    msglen - SA_SIZE(sa) >= 0) {
+			msglen -= SA_SIZE(sa);
+			bprintf(&bp, ", nh ");
+			bprint_sa(&bp, sa);
+		}
+		if (sdl->sdl_nlen > 0)
+			bprintf(&bp, " // %s", loghdr->comment);
+		printf("%s\n", bp.buf);
+	}
+	bp_free(&bp);
+	close(rtsock);
+}
+
 void
 ipfw_internal_handler(int ac, char *av[])
 {
@@ -6054,6 +6183,10 @@ ipfw_internal_handler(int ac, char *av[])
 	case TOK_VLIST:
 		ipfw_list_values(ac, av);
 		break;
+	case TOK_MONITOR:
+		av++;
+		ipfw_rtsock_monitor(*av);
+		break;
 	}
 }
 
diff --git a/sbin/ipfw/ipfw2.h b/sbin/ipfw/ipfw2.h
index 788e9fe365f3..f19ea59c1bb4 100644
--- a/sbin/ipfw/ipfw2.h
+++ b/sbin/ipfw/ipfw2.h
@@ -275,6 +275,7 @@ enum tokens {
 	TOK_UNLOCK,
 	TOK_VLIST,
 	TOK_OLIST,
+	TOK_MONITOR,
 	TOK_MISSING,
 	TOK_ORFLUSH,
 
@@ -347,7 +348,7 @@ struct buf_pr {
 int pr_u64(struct buf_pr *bp, void *pd, int width);
 int bp_alloc(struct buf_pr *b, size_t size);
 void bp_free(struct buf_pr *b);
-int bprintf(struct buf_pr *b, const char *format, ...);
+int bprintf(struct buf_pr *b, const char *format, ...) __printflike(2, 3);
 
 
 /* memory allocation support */