git: a7191e5d7b62 - main - pf: add a way to list creator ids

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Tue, 10 Oct 2023 09:50:36 UTC
The branch main has been updated by kp:

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

commit a7191e5d7b625c35434f99f51ccbf577228b2afc
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2023-10-04 10:27:54 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2023-10-10 09:48:21 +0000

    pf: add a way to list creator ids
    
    Allow userspace to retrieve a list of distinct creator ids for the
    current states.
    
    This is used by pfSense, and used to require dumping all states to
    userspace. It's rather inefficient to export a (potentially extremely
    large) state table to obtain a handful (typically 2) of 32-bit integers.
    
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D42092
---
 lib/libpfctl/libpfctl.c        | 68 ++++++++++++++++++++++++++++++++++-
 lib/libpfctl/libpfctl.h        |  1 +
 sbin/pfctl/pfctl.c             | 21 ++++++++++-
 sbin/pfctl/pfctl.h             |  1 +
 sys/netpfil/pf/pf_nl.c         | 80 ++++++++++++++++++++++++++++++++++++++++++
 sys/netpfil/pf/pf_nl.h         |  1 +
 tests/sys/netpfil/pf/pfsync.sh |  8 +++++
 7 files changed, 178 insertions(+), 2 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index 03f80baa2178..ca29645f9c34 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -1106,6 +1106,71 @@ pfctl_set_keepcounters(int dev, bool keep)
 	return (ret);
 }
 
+struct pfctl_creator {
+	uint32_t id;
+};
+#define	_IN(_field)	offsetof(struct genlmsghdr, _field)
+#define	_OUT(_field)	offsetof(struct pfctl_creator, _field)
+static struct snl_attr_parser ap_creators[] = {
+	{ .type = PF_ST_CREATORID, .off = _OUT(id), .cb = snl_attr_get_uint32 },
+};
+static struct snl_field_parser fp_creators[] = {
+};
+#undef _IN
+#undef _OUT
+SNL_DECLARE_PARSER(creator_parser, struct genlmsghdr, fp_creators, ap_creators);
+
+static int
+pfctl_get_creators_nl(struct snl_state *ss, uint32_t *creators, size_t *len)
+{
+
+	int family_id = snl_get_genl_family(ss, PFNL_FAMILY_NAME);
+	size_t i = 0;
+
+	struct nlmsghdr *hdr;
+	struct snl_writer nw;
+
+	snl_init_writer(ss, &nw);
+	hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_GETCREATORS);
+	hdr->nlmsg_flags |= NLM_F_DUMP;
+	snl_finalize_msg(&nw);
+	uint32_t seq_id = hdr->nlmsg_seq;
+
+	snl_send_message(ss, hdr);
+
+	struct snl_errmsg_data e = {};
+	while ((hdr = snl_read_reply_multi(ss, seq_id, &e)) != NULL) {
+		struct pfctl_creator c;
+		bzero(&c, sizeof(c));
+
+		if (!snl_parse_nlmsg(ss, hdr, &creator_parser, &c))
+			continue;
+
+		creators[i] = c.id;
+		i++;
+		if (i > *len)
+			return (E2BIG);
+	}
+
+	*len = i;
+
+	return (0);
+}
+
+int
+pfctl_get_creatorids(uint32_t *creators, size_t *len)
+{
+	struct snl_state ss = {};
+	int error;
+
+	snl_init(&ss, NETLINK_GENERIC);
+	error = pfctl_get_creators_nl(&ss, creators, len);
+	snl_free(&ss);
+
+	return (error);
+
+}
+
 static void
 pfctl_nv_add_state_cmp(nvlist_t *nvl, const char *name,
     const struct pfctl_state_cmp *cmp)
@@ -1199,7 +1264,8 @@ static struct snl_field_parser fp_state[] = {
 SNL_DECLARE_PARSER(state_parser, struct genlmsghdr, fp_state, ap_state);
 
 static const struct snl_hdr_parser *all_parsers[] = {
-	&state_parser, &skey_parser, &speer_parser
+	&state_parser, &skey_parser, &speer_parser,
+	&creator_parser,
 };
 
 static int
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index 4906ec3ccfce..e75f93d8775e 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -413,6 +413,7 @@ int	pfctl_add_rule(int dev, const struct pfctl_rule *r,
 	    const char *anchor, const char *anchor_call, uint32_t ticket,
 	    uint32_t pool_ticket);
 int	pfctl_set_keepcounters(int dev, bool keep);
+int	pfctl_get_creatorids(uint32_t *creators, size_t *len);
 typedef int (*pfctl_get_state_fn)(struct pfctl_state *, void *);
 int pfctl_get_states_iter(pfctl_get_state_fn f, void *arg);
 int	pfctl_get_states(int dev, struct pfctl_states *states);
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index 9a75eb7d00b5..759b36d9cebe 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -233,7 +233,7 @@ static const char * const clearopt_list[] = {
 static const char * const showopt_list[] = {
 	"ether", "nat", "queue", "rules", "Anchors", "Sources", "states",
 	"info", "Interfaces", "labels", "timeouts", "memory", "Tables",
-	"osfp", "Running", "all", NULL
+	"osfp", "Running", "all", "creatorids", NULL
 };
 
 static const char * const tblcmdopt_list[] = {
@@ -1639,6 +1639,22 @@ pfctl_show_limits(int dev, int opts)
 	return (0);
 }
 
+void
+pfctl_show_creators(int opts)
+{
+	int ret;
+	uint32_t creators[16];
+	size_t count = nitems(creators);
+
+	ret = pfctl_get_creatorids(creators, &count);
+	if (ret != 0)
+		errx(ret, "Failed to retrieve creators");
+
+	printf("Creator IDs:\n");
+	for (size_t i = 0; i < count; i++)
+		printf("%08x\n", creators[i]);
+}
+
 /* callbacks for rule/nat/rdr/addr */
 int
 pfctl_add_pool(struct pfctl *pf, struct pfctl_pool *p, sa_family_t af)
@@ -3121,6 +3137,9 @@ main(int argc, char *argv[])
 		case 'I':
 			pfctl_show_ifaces(ifaceopt, opts);
 			break;
+		case 'c':
+			pfctl_show_creators(opts);
+			break;
 		}
 	}
 
diff --git a/sbin/pfctl/pfctl.h b/sbin/pfctl/pfctl.h
index b9da5e96a90e..cf198d557299 100644
--- a/sbin/pfctl/pfctl.h
+++ b/sbin/pfctl/pfctl.h
@@ -88,6 +88,7 @@ int	 pfctl_command_tables(int, char *[], char *, const char *, char *,
 int	 pfctl_show_altq(int, const char *, int, int);
 void	 warn_namespace_collision(const char *);
 int	 pfctl_show_ifaces(const char *, int);
+void	pfctl_show_creators(int);
 FILE	*pfctl_fopen(const char *, const char *);
 
 #ifdef __FreeBSD__
diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c
index cbea76e7386f..bb50b3b2b321 100644
--- a/sys/netpfil/pf/pf_nl.c
+++ b/sys/netpfil/pf/pf_nl.c
@@ -246,6 +246,30 @@ handle_getstate(struct nlpcb *nlp, struct nl_parsed_state *attrs,
 	return (dump_state(nlp, hdr, s, npt));
 }
 
+static int
+dump_creatorid(struct nlpcb *nlp, const struct nlmsghdr *hdr, uint32_t creator,
+    struct nl_pstate *npt)
+{
+	struct nl_writer *nw = npt->nw;
+
+	if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr)))
+		goto enomem;
+
+	struct genlmsghdr *ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr);
+	ghdr_new->cmd = PFNL_CMD_GETCREATORS;
+	ghdr_new->version = 0;
+	ghdr_new->reserved = 0;
+
+	nlattr_add_u32(nw, PF_ST_CREATORID, htonl(creator));
+
+	if (nlmsg_end(nw))
+		return (0);
+
+enomem:
+	nlmsg_abort(nw);
+	return (ENOMEM);
+}
+
 static int
 pf_handle_getstates(struct nlmsghdr *hdr, struct nl_pstate *npt)
 {
@@ -264,6 +288,56 @@ pf_handle_getstates(struct nlmsghdr *hdr, struct nl_pstate *npt)
 	return (error);
 }
 
+static int
+pf_handle_getcreators(struct nlmsghdr *hdr, struct nl_pstate *npt)
+{
+	uint32_t creators[16];
+	int error = 0;
+
+	bzero(creators, sizeof(creators));
+
+	for (int i = 0; i < pf_hashmask; i++) {
+		struct pf_idhash *ih = &V_pf_idhash[i];
+		struct pf_kstate *s;
+
+		if (LIST_EMPTY(&ih->states))
+			continue;
+
+		PF_HASHROW_LOCK(ih);
+		LIST_FOREACH(s, &ih->states, entry) {
+			int j;
+			if (s->timeout == PFTM_UNLINKED)
+				continue;
+
+			for (j = 0; j < nitems(creators); j++) {
+				if (creators[j] == s->creatorid)
+					break;
+				if (creators[j] == 0) {
+					creators[j] = s->creatorid;
+					break;
+				}
+			}
+			if (j == nitems(creators))
+				printf("Warning: too many creators!\n");
+		}
+		PF_HASHROW_UNLOCK(ih);
+	}
+
+	hdr->nlmsg_flags |= NLM_F_MULTI;
+	for (int i = 0; i < nitems(creators); i++) {
+		if (creators[i] == 0)
+			break;
+		error = dump_creatorid(npt->nlp, hdr, creators[i], npt);
+	}
+
+	if (!nlmsg_end_dump(npt->nw, error, hdr)) {
+		NL_LOG(LOG_DEBUG, "Unable to finalize the dump");
+		return (ENOMEM);
+	}
+
+	return (error);
+}
+
 static const struct nlhdr_parser *all_parsers[] = { &state_parser };
 
 static int family_id;
@@ -275,6 +349,12 @@ static const struct genl_cmd pf_cmds[] = {
 		.cmd_cb = pf_handle_getstates,
 		.cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL,
 	},
+	{
+		.cmd_num = PFNL_CMD_GETCREATORS,
+		.cmd_name = "GETCREATORS",
+		.cmd_cb = pf_handle_getcreators,
+		.cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL,
+	},
 };
 
 void
diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h
index 5ef757eead21..98525641b43d 100644
--- a/sys/netpfil/pf/pf_nl.h
+++ b/sys/netpfil/pf/pf_nl.h
@@ -37,6 +37,7 @@
 enum {
 	PFNL_CMD_UNSPEC = 0,
 	PFNL_CMD_GETSTATES = 1,
+	PFNL_CMD_GETCREATORS = 2,
 	__PFNL_CMD_MAX,
 };
 #define PFNL_CMD_MAX (__PFNL_CMD_MAX -1)
diff --git a/tests/sys/netpfil/pf/pfsync.sh b/tests/sys/netpfil/pf/pfsync.sh
index 5d30f5b44888..87dfcf748d3c 100644
--- a/tests/sys/netpfil/pf/pfsync.sh
+++ b/tests/sys/netpfil/pf/pfsync.sh
@@ -78,6 +78,8 @@ common_body()
 		"set skip on ${epair_sync}b" \
 		"pass out keep state"
 
+	hostid_one=$(jexec one pfctl -si -v | awk '/Hostid:/ { gsub(/0x/, "", $2); printf($2); }')
+
 	ifconfig ${epair_one}b 198.51.100.254/24 up
 
 	ping -c 1 -S 198.51.100.254 198.51.100.1
@@ -89,6 +91,12 @@ common_body()
 	    grep 198.51.100.254 ; then
 		atf_fail "state not found on synced host"
 	fi
+
+	if ! jexec two pfctl -sc | grep ""${hostid_one}"";
+	then
+		jexec two pfctl -sc
+		atf_fail "HostID for host one not found on two"
+	fi
 }
 
 basic_cleanup()