git: 706d465dae6a - main - pf: convert kill/clear state to use netlink

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Wed, 28 Feb 2024 22:27:14 UTC
The branch main has been updated by kp:

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

commit 706d465dae6aa3e1b567299b9e80eb574b6c5abf
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2024-02-26 10:20:29 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2024-02-28 22:26:18 +0000

    pf: convert kill/clear state to use netlink
    
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D44090
---
 contrib/pf/authpf/authpf.c |   8 +--
 lib/libpfctl/libpfctl.c    | 128 ++++++++++++++++++++++++++++++---------------
 lib/libpfctl/libpfctl.h    |   4 ++
 sbin/pfctl/pfctl.c         |  12 ++---
 sys/net/pfvar.h            |   3 ++
 sys/netpfil/pf/pf_ioctl.c  |   7 +--
 sys/netpfil/pf/pf_nl.c     |  96 +++++++++++++++++++++++++++++++++-
 sys/netpfil/pf/pf_nl.h     |  19 +++++++
 8 files changed, 221 insertions(+), 56 deletions(-)

diff --git a/contrib/pf/authpf/authpf.c b/contrib/pf/authpf/authpf.c
index 9858c1c50ced..81dbcb747f5f 100644
--- a/contrib/pf/authpf/authpf.c
+++ b/contrib/pf/authpf/authpf.c
@@ -57,6 +57,7 @@ static int	change_table(int, const char *);
 static void	authpf_kill_states(void);
 
 int	dev;			/* pf device */
+struct pfctl_handle	 *pfh;
 char	anchorname[PF_ANCHOR_NAME_SIZE] = "authpf";
 char	rulesetname[MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 2];
 char	tablename[PF_TABLE_NAME_SIZE] = "authpf_users";
@@ -135,7 +136,8 @@ main(void)
 	}
 	/* open the pf device */
 	dev = open(PATH_DEVFILE, O_RDWR);
-	if (dev == -1) {
+	pfh = pfctl_open(PATH_DEVFILE);
+	if (dev == -1 || pfh == NULL) {
 		syslog(LOG_ERR, "cannot open packet filter device (%m)");
 		goto die;
 	}
@@ -906,7 +908,7 @@ authpf_kill_states(void)
 	    sizeof(kill.src.addr.v.a.addr));
 	memset(&kill.src.addr.v.a.mask, 0xff,
 	    sizeof(kill.src.addr.v.a.mask));
-	if (pfctl_kill_states(dev, &kill, NULL))
+	if (pfctl_kill_states_h(pfh, &kill, NULL))
 		syslog(LOG_ERR, "pfctl_kill_states() failed (%m)");
 
 	/* Kill all states to ipsrc */
@@ -915,7 +917,7 @@ authpf_kill_states(void)
 	    sizeof(kill.dst.addr.v.a.addr));
 	memset(&kill.dst.addr.v.a.mask, 0xff,
 	    sizeof(kill.dst.addr.v.a.mask));
-	if (pfctl_kill_states(dev, &kill, NULL))
+	if (pfctl_kill_states_h(pfh, &kill, NULL))
 		syslog(LOG_ERR, "pfctl_kill_states() failed (%m)");
 }
 
diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index 71546c4709c2..f12e45291fc1 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -1630,22 +1630,6 @@ pfctl_get_creatorids(struct pfctl_handle *h, uint32_t *creators, size_t *len)
 	return (error);
 }
 
-static void
-pfctl_nv_add_state_cmp(nvlist_t *nvl, const char *name,
-    const struct pfctl_state_cmp *cmp)
-{
-	nvlist_t	*nv;
-
-	nv = nvlist_create(0);
-
-	nvlist_add_number(nv, "id", cmp->id);
-	nvlist_add_number(nv, "creatorid", htonl(cmp->creatorid));
-	nvlist_add_number(nv, "direction", cmp->direction);
-
-	nvlist_add_nvlist(nvl, name, nv);
-	nvlist_destroy(nv);
-}
-
 static inline bool
 snl_attr_get_pfaddr(struct snl_state *ss __unused, struct nlattr *nla,
     const void *arg __unused, void *target)
@@ -1848,48 +1832,110 @@ pfctl_free_states(struct pfctl_states *states)
 	bzero(states, sizeof(*states));
 }
 
+struct pfctl_nl_clear_states {
+	uint32_t killed;
+};
+#define	_OUT(_field)	offsetof(struct pfctl_nl_clear_states, _field)
+static struct snl_attr_parser ap_clear_states[] = {
+	{ .type = PF_CS_KILLED, .off = _OUT(killed), .cb = snl_attr_get_uint32 },
+};
+static struct snl_field_parser fp_clear_states[] = {};
+#undef _OUT
+SNL_DECLARE_PARSER(clear_states_parser, struct genlmsghdr, fp_clear_states, ap_clear_states);
+
 static int
-_pfctl_clear_states(int dev, const struct pfctl_kill *kill,
-    unsigned int *killed, uint64_t ioctlval)
+_pfctl_clear_states_h(struct pfctl_handle *h, const struct pfctl_kill *kill,
+    unsigned int *killed, int cmd)
 {
-	nvlist_t	*nvl;
-	int		 ret;
+	struct snl_writer nw;
+	struct snl_errmsg_data e = {};
+	struct pfctl_nl_clear_states attrs = {};
+	struct nlmsghdr *hdr;
+	uint32_t seq_id;
+	int family_id;
 
-	nvl = nvlist_create(0);
+	family_id = snl_get_genl_family(&h->ss, PFNL_FAMILY_NAME);
+	if (family_id == 0)
+		return (ENOTSUP);
 
-	pfctl_nv_add_state_cmp(nvl, "cmp", &kill->cmp);
-	nvlist_add_number(nvl, "af", kill->af);
-	nvlist_add_number(nvl, "proto", kill->proto);
-	pfctl_nv_add_rule_addr(nvl, "src", &kill->src);
-	pfctl_nv_add_rule_addr(nvl, "dst", &kill->dst);
-	pfctl_nv_add_rule_addr(nvl, "rt_addr", &kill->rt_addr);
-	nvlist_add_string(nvl, "ifname", kill->ifname);
-	nvlist_add_string(nvl, "label", kill->label);
-	nvlist_add_bool(nvl, "kill_match", kill->kill_match);
-	nvlist_add_bool(nvl, "nat", kill->nat);
-
-	if ((ret = pfctl_do_ioctl(dev, ioctlval, 1024, &nvl)) != 0)
-		goto out;
+	snl_init_writer(&h->ss, &nw);
+	hdr = snl_create_genl_msg_request(&nw, family_id, cmd);
+	hdr->nlmsg_flags |= NLM_F_DUMP;
+
+	snl_add_msg_attr_u64(&nw, PF_CS_CMP_ID, kill->cmp.id);
+	snl_add_msg_attr_u32(&nw, PF_CS_CMP_CREATORID, htonl(kill->cmp.creatorid));
+	snl_add_msg_attr_u8(&nw, PF_CS_CMP_DIR, kill->cmp.direction);
+	snl_add_msg_attr_u8(&nw, PF_CS_AF, kill->af);
+	snl_add_msg_attr_u8(&nw, PF_CS_PROTO, kill->proto);
+	snl_add_msg_attr_rule_addr(&nw, PF_CS_SRC, &kill->src);
+	snl_add_msg_attr_rule_addr(&nw, PF_CS_DST, &kill->dst);
+	snl_add_msg_attr_rule_addr(&nw, PF_CS_RT_ADDR, &kill->rt_addr);
+	snl_add_msg_attr_string(&nw, PF_CS_IFNAME, kill->ifname);
+	snl_add_msg_attr_string(&nw, PF_CS_LABEL, kill->label);
+	snl_add_msg_attr_bool(&nw, PF_CS_KILL_MATCH, kill->kill_match);
+	snl_add_msg_attr_bool(&nw, PF_CS_NAT, kill->nat);
+
+	if ((hdr = snl_finalize_msg(&nw)) == NULL)
+		return (ENXIO);
+
+	seq_id = hdr->nlmsg_seq;
+
+	if (! snl_send_message(&h->ss, hdr))
+		return (ENXIO);
+
+	while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) {
+		if (! snl_parse_nlmsg(&h->ss, hdr, &clear_states_parser, &attrs))
+			continue;
+	}
 
 	if (killed)
-		*killed = nvlist_get_number(nvl, "killed");
+		*killed = attrs.killed;
+
+	return (e.error);
+}
+
+int
+pfctl_clear_states_h(struct pfctl_handle *h, const struct pfctl_kill *kill,
+    unsigned int *killed)
+{
+	return(_pfctl_clear_states_h(h, kill, killed, PFNL_CMD_CLRSTATES));
+}
+
+int
+pfctl_kill_states_h(struct pfctl_handle *h, const struct pfctl_kill *kill,
+    unsigned int *killed)
+{
+	return(_pfctl_clear_states_h(h, kill, killed, PFNL_CMD_KILLSTATES));
+}
+
+static int
+_pfctl_clear_states(int dev __unused, const struct pfctl_kill *kill,
+    unsigned int *killed, uint64_t cmd)
+{
+	struct pfctl_handle *h;
+	int ret;
+
+	h = pfctl_open(PF_DEVICE);
+	if (h == NULL)
+		return (ENODEV);
+
+	ret = _pfctl_clear_states_h(h, kill, killed, cmd);
+	pfctl_close(h);
 
-out:
-	nvlist_destroy(nvl);
 	return (ret);
 }
 
 int
-pfctl_clear_states(int dev, const struct pfctl_kill *kill,
+pfctl_clear_states(int dev __unused, const struct pfctl_kill *kill,
     unsigned int *killed)
 {
-	return (_pfctl_clear_states(dev, kill, killed, DIOCCLRSTATESNV));
+	return (_pfctl_clear_states(dev, kill, killed, PFNL_CMD_CLRSTATES));
 }
 
 int
-pfctl_kill_states(int dev, const struct pfctl_kill *kill, unsigned int *killed)
+pfctl_kill_states(int dev __unused, const struct pfctl_kill *kill, unsigned int *killed)
 {
-	return (_pfctl_clear_states(dev, kill, killed, DIOCKILLSTATESNV));
+	return (_pfctl_clear_states(dev, kill, killed, PFNL_CMD_KILLSTATES));
 }
 
 int
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index f05044a9a985..b16caf1f6480 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -446,6 +446,10 @@ int	pfctl_clear_states(int dev, const struct pfctl_kill *kill,
 	    unsigned int *killed);
 int	pfctl_kill_states(int dev, const struct pfctl_kill *kill,
 	    unsigned int *killed);
+int	pfctl_clear_states_h(struct pfctl_handle *h, const struct pfctl_kill *kill,
+	    unsigned int *killed);
+int	pfctl_kill_states_h(struct pfctl_handle *h, const struct pfctl_kill *kill,
+	    unsigned int *killed);
 int	pfctl_clear_rules(int dev, const char *anchorname);
 int	pfctl_clear_nat(int dev, const char *anchorname);
 int	pfctl_clear_eth_rules(int dev, const char *anchorname);
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index c583279750f1..f8ecded066d2 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -545,7 +545,7 @@ pfctl_clear_iface_states(int dev, const char *iface, int opts)
 	if (opts & PF_OPT_KILLMATCH)
 		kill.kill_match = true;
 
-	if (pfctl_clear_states(dev, &kill, &killed))
+	if (pfctl_clear_states_h(pfh, &kill, &killed))
 		err(1, "DIOCCLRSTATES");
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "%d states cleared\n", killed);
@@ -801,13 +801,13 @@ pfctl_net_kill_states(int dev, const char *iface, int opts)
 					errx(1, "Unknown address family %d",
 					    kill.af);
 
-				if (pfctl_kill_states(dev, &kill, &newkilled))
+				if (pfctl_kill_states_h(pfh, &kill, &newkilled))
 					err(1, "DIOCKILLSTATES");
 				killed += newkilled;
 			}
 			freeaddrinfo(res[1]);
 		} else {
-			if (pfctl_kill_states(dev, &kill, &newkilled))
+			if (pfctl_kill_states_h(pfh, &kill, &newkilled))
 				err(1, "DIOCKILLSTATES");
 			killed += newkilled;
 		}
@@ -873,7 +873,7 @@ pfctl_gateway_kill_states(int dev, const char *iface, int opts)
 		else
 			errx(1, "Unknown address family %d", kill.af);
 
-		if (pfctl_kill_states(dev, &kill, &newkilled))
+		if (pfctl_kill_states_h(pfh, &kill, &newkilled))
 			err(1, "DIOCKILLSTATES");
 		killed += newkilled;
 	}
@@ -907,7 +907,7 @@ pfctl_label_kill_states(int dev, const char *iface, int opts)
 	    sizeof(kill.label))
 		errx(1, "label too long: %s", state_kill[1]);
 
-	if (pfctl_kill_states(dev, &kill, &killed))
+	if (pfctl_kill_states_h(pfh, &kill, &killed))
 		err(1, "DIOCKILLSTATES");
 
 	if ((opts & PF_OPT_QUIET) == 0)
@@ -946,7 +946,7 @@ pfctl_id_kill_states(int dev, const char *iface, int opts)
 		usage();
 	}
 
-	if (pfctl_kill_states(dev, &kill, &killed))
+	if (pfctl_kill_states_h(pfh, &kill, &killed))
 		err(1, "DIOCKILLSTATES");
 
 	if ((opts & PF_OPT_QUIET) == 0)
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index f3a808f8ad26..88fb99ead84e 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -2208,6 +2208,9 @@ extern int			 pf_state_insert(struct pfi_kkif *,
 				    struct pf_kstate *);
 extern struct pf_kstate		*pf_alloc_state(int);
 extern void			 pf_free_state(struct pf_kstate *);
+extern void			 pf_killstates(struct pf_kstate_kill *,
+				    unsigned int *);
+extern unsigned int		 pf_clear_states(const struct pf_kstate_kill *);
 
 static __inline void
 pf_ref_state(struct pf_kstate *s)
diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c
index d83933cd293f..1b22d49a6255 100644
--- a/sys/netpfil/pf/pf_ioctl.c
+++ b/sys/netpfil/pf/pf_ioctl.c
@@ -227,9 +227,6 @@ struct cdev *pf_dev;
  * XXX - These are new and need to be checked when moveing to a new version
  */
 static void		 pf_clear_all_states(void);
-static unsigned int	 pf_clear_states(const struct pf_kstate_kill *);
-static void		 pf_killstates(struct pf_kstate_kill *,
-			    unsigned int *);
 static int		 pf_killstates_row(struct pf_kstate_kill *,
 			    struct pf_idhash *);
 static int		 pf_killstates_nv(struct pfioc_nv *);
@@ -5944,7 +5941,7 @@ on_error:
 	return (error);
 }
 
-static unsigned int
+unsigned int
 pf_clear_states(const struct pf_kstate_kill *kill)
 {
 	struct pf_state_key_cmp	 match_key;
@@ -6013,7 +6010,7 @@ relock_DIOCCLRSTATES:
 	return (killed);
 }
 
-static void
+void
 pf_killstates(struct pf_kstate_kill *kill, unsigned int *killed)
 {
 	struct pf_kstate	*s;
diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c
index 120ce88f8720..e4558c156aef 100644
--- a/sys/netpfil/pf/pf_nl.c
+++ b/sys/netpfil/pf/pf_nl.c
@@ -782,6 +782,7 @@ static const struct nlattr_parser nla_p_getrules[] = {
 };
 static const struct nlfield_parser nlf_p_getrules[] = {
 };
+#undef _IN
 #undef _OUT
 NL_DECLARE_PARSER(getrules_parser, struct genlmsghdr, nlf_p_getrules, nla_p_getrules);
 
@@ -842,6 +843,8 @@ static const struct nlattr_parser nla_p_getrule[] = {
 };
 static const struct nlfield_parser nlf_p_getrule[] = {
 };
+#undef _IN
+#undef _OUT
 NL_DECLARE_PARSER(getrule_parser, struct genlmsghdr, nlf_p_getrule, nla_p_getrule);
 
 static int
@@ -1000,10 +1003,87 @@ out:
 	return (error);
 }
 
+#define	_IN(_field)	offsetof(struct genlmsghdr, _field)
+#define	_OUT(_field)	offsetof(struct pf_kstate_kill, _field)
+static const struct nlattr_parser nla_p_clear_states[] = {
+	{ .type = PF_CS_CMP_ID, .off = _OUT(psk_pfcmp.id), .cb = nlattr_get_uint64 },
+	{ .type = PF_CS_CMP_CREATORID, .off = _OUT(psk_pfcmp.creatorid), .cb = nlattr_get_uint32 },
+	{ .type = PF_CS_CMP_DIR, .off = _OUT(psk_pfcmp.direction), .cb = nlattr_get_uint8 },
+	{ .type = PF_CS_AF, .off = _OUT(psk_af), .cb = nlattr_get_uint8 },
+	{ .type = PF_CS_PROTO, .off = _OUT(psk_proto), .cb = nlattr_get_uint8 },
+	{ .type = PF_CS_SRC, .off = _OUT(psk_src), .arg = &rule_addr_parser, .cb = nlattr_get_nested },
+	{ .type = PF_CS_DST, .off = _OUT(psk_dst), .arg = &rule_addr_parser, .cb = nlattr_get_nested },
+	{ .type = PF_CS_RT_ADDR, .off = _OUT(psk_rt_addr), .arg = &rule_addr_parser, .cb = nlattr_get_nested },
+	{ .type = PF_CS_IFNAME, .off = _OUT(psk_ifname), .arg = (void *)IFNAMSIZ, .cb = nlattr_get_chara },
+	{ .type = PF_CS_LABEL, .off = _OUT(psk_label), .arg = (void *)PF_RULE_LABEL_SIZE, .cb = nlattr_get_chara },
+	{ .type = PF_CS_KILL_MATCH, .off = _OUT(psk_kill_match), .cb = nlattr_get_bool },
+	{ .type = PF_CS_NAT, .off = _OUT(psk_nat), .cb = nlattr_get_bool },
+};
+static const struct nlfield_parser nlf_p_clear_states[] = {};
+#undef _IN
+#undef _OUT
+NL_DECLARE_PARSER(clear_states_parser, struct genlmsghdr, nlf_p_clear_states, nla_p_clear_states);
+
+static int
+pf_handle_killclear_states(struct nlmsghdr *hdr, struct nl_pstate *npt, int cmd)
+{
+	struct pf_kstate_kill		 kill = {};
+	struct epoch_tracker		 et;
+	struct nl_writer		*nw = npt->nw;
+	struct genlmsghdr		*ghdr_new;
+	int				 error;
+	unsigned int			 killed = 0;
+
+	error = nl_parse_nlmsg(hdr, &clear_states_parser, npt, &kill);
+	if (error != 0)
+		return (error);
+
+	if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr)))
+		return (ENOMEM);
+
+	ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr);
+	ghdr_new->cmd = cmd;
+	ghdr_new->version = 0;
+	ghdr_new->reserved = 0;
+
+	NET_EPOCH_ENTER(et);
+	if (cmd == PFNL_CMD_KILLSTATES)
+		pf_killstates(&kill, &killed);
+	else
+		killed = pf_clear_states(&kill);
+	NET_EPOCH_EXIT(et);
+
+	nlattr_add_u32(nw, PF_CS_KILLED, killed);
+
+	if (! nlmsg_end(nw)) {
+		error = ENOMEM;
+		goto out;
+	}
+
+	return (0);
+
+out:
+	nlmsg_abort(nw);
+	return (error);
+}
+
+static int
+pf_handle_clear_states(struct nlmsghdr *hdr, struct nl_pstate *npt)
+{
+	return (pf_handle_killclear_states(hdr, npt, PFNL_CMD_CLRSTATES));
+}
+
+static int
+pf_handle_kill_states(struct nlmsghdr *hdr, struct nl_pstate *npt)
+{
+	return (pf_handle_killclear_states(hdr, npt, PFNL_CMD_KILLSTATES));
+}
+
 static const struct nlhdr_parser *all_parsers[] = {
 	&state_parser,
 	&addrule_parser,
-	&getrules_parser
+	&getrules_parser,
+	&clear_states_parser,
 };
 
 static int family_id;
@@ -1058,6 +1138,20 @@ static const struct genl_cmd pf_cmds[] = {
 		.cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL,
 		.cmd_priv = PRIV_NETINET_PF,
 	},
+	{
+		.cmd_num = PFNL_CMD_CLRSTATES,
+		.cmd_name = "CLRSTATES",
+		.cmd_cb = pf_handle_clear_states,
+		.cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL,
+		.cmd_priv = PRIV_NETINET_PF,
+	},
+	{
+		.cmd_num = PFNL_CMD_KILLSTATES,
+		.cmd_name = "KILLSTATES",
+		.cmd_cb = pf_handle_kill_states,
+		.cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL,
+		.cmd_priv = PRIV_NETINET_PF,
+	},
 };
 
 void
diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h
index 51df8b7aece9..0ae4a9283072 100644
--- a/sys/netpfil/pf/pf_nl.h
+++ b/sys/netpfil/pf/pf_nl.h
@@ -43,6 +43,8 @@ enum {
 	PFNL_CMD_ADDRULE = 5,
 	PFNL_CMD_GETRULES = 6,
 	PFNL_CMD_GETRULE = 7,
+	PFNL_CMD_CLRSTATES = 8,
+	PFNL_CMD_KILLSTATES = 9,
 	__PFNL_CMD_MAX,
 };
 #define PFNL_CMD_MAX (__PFNL_CMD_MAX -1)
@@ -262,6 +264,23 @@ enum pf_getrules_type_t {
 	PF_GR_CLEAR		= 5, /* u8 */
 };
 
+enum pf_clear_states_type_t {
+	PF_CS_UNSPEC,
+	PF_CS_CMP_ID		= 1, /* u64 */
+	PF_CS_CMP_CREATORID	= 2, /* u32 */
+	PF_CS_CMP_DIR		= 3, /* u8 */
+	PF_CS_AF		= 4, /* u8 */
+	PF_CS_PROTO		= 5, /* u8 */
+	PF_CS_SRC		= 6, /* nested, pf_addr_wrap */
+	PF_CS_DST		= 7, /* nested, pf_addr_wrap */
+	PF_CS_RT_ADDR		= 8, /* nested, pf_addr_wrap */
+	PF_CS_IFNAME		= 9, /* string */
+	PF_CS_LABEL		= 10, /* string */
+	PF_CS_KILL_MATCH	= 11, /* bool */
+	PF_CS_NAT		= 12, /* bool */
+	PF_CS_KILLED		= 13, /* u32 */
+};
+
 #ifdef _KERNEL
 
 void	pf_nl_register(void);