git: 81647eb60ee3 - main - pf: implement start/stop calls via netlink

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Fri, 13 Oct 2023 09:15:39 UTC
The branch main has been updated by kp:

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

commit 81647eb60ee387b0b33ac42deacd25edace2661e
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2023-10-10 15:20:12 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2023-10-13 07:53:22 +0000

    pf: implement start/stop calls via netlink
    
    Implement equivalents to DIOCSTART and DIOCSTOP in netlink. Provide a
    libpfctl implementation and add a basic test case, mostly to verify that
    we still return the same errors as before the conversion
    
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D42145
---
 lib/libpfctl/libpfctl.c            | 28 +++++++++++++++
 lib/libpfctl/libpfctl.h            |  1 +
 sbin/pfctl/pfctl.c                 | 14 +++++---
 sys/net/pfvar.h                    |  2 ++
 sys/netpfil/pf/pf_ioctl.c          | 72 +++++++++++++++++++++++++-------------
 sys/netpfil/pf/pf_nl.c             | 24 +++++++++++++
 sys/netpfil/pf/pf_nl.h             |  2 ++
 tests/sys/netpfil/pf/pass_block.sh | 38 ++++++++++++++++++++
 8 files changed, 151 insertions(+), 30 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index ca29645f9c34..51276d8bb343 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -177,6 +177,34 @@ pf_nvuint_64_array(const nvlist_t *nvl, const char *name, size_t maxelems,
 		*nelems = elems;
 }
 
+int
+pfctl_startstop(int start)
+{
+	struct snl_state ss = {};
+	struct snl_errmsg_data e = {};
+	struct snl_writer nw;
+	struct nlmsghdr *hdr;
+	uint32_t seq_id;
+	int family_id;
+
+	snl_init(&ss, NETLINK_GENERIC);
+	family_id = snl_get_genl_family(&ss, PFNL_FAMILY_NAME);
+
+	snl_init_writer(&ss, &nw);
+	hdr = snl_create_genl_msg_request(&nw, family_id,
+	    start ? PFNL_CMD_START : PFNL_CMD_STOP);
+
+	snl_finalize_msg(&nw);
+	seq_id = hdr->nlmsg_seq;
+
+	snl_send_message(&ss, hdr);
+
+	while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) {
+	}
+
+	return (e.error);
+}
+
 static void
 _pfctl_get_status_counters(const nvlist_t *nvl,
     struct pfctl_status_counters *counters)
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index e75f93d8775e..06cd25e82c08 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -384,6 +384,7 @@ struct pfctl_syncookies {
 	uint8_t				lowwater;	/* Percent */
 };
 
+int	pfctl_startstop(int start);
 struct pfctl_status* pfctl_get_status(int dev);
 uint64_t pfctl_status_counter(struct pfctl_status *status, int id);
 uint64_t pfctl_status_fcounter(struct pfctl_status *status, int id);
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index 759b36d9cebe..56b1d28c6fd6 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -310,10 +310,12 @@ pfctl_proto2name(int proto)
 int
 pfctl_enable(int dev, int opts)
 {
-	if (ioctl(dev, DIOCSTART)) {
-		if (errno == EEXIST)
+	int ret;
+
+	if ((ret = pfctl_startstop(1)) != 0) {
+		if (ret == EEXIST)
 			errx(1, "pf already enabled");
-		else if (errno == ESRCH)
+		else if (ret == ESRCH)
 			errx(1, "pfil registeration failed");
 		else
 			err(1, "DIOCSTART");
@@ -331,8 +333,10 @@ pfctl_enable(int dev, int opts)
 int
 pfctl_disable(int dev, int opts)
 {
-	if (ioctl(dev, DIOCSTOP)) {
-		if (errno == ENOENT)
+	int ret;
+
+	if ((ret = pfctl_startstop(0)) != 0) {
+		if (ret == ENOENT)
 			errx(1, "pf not enabled");
 		else
 			err(1, "DIOCSTOP");
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index f5ea9bf71f0c..59579a0849d9 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -2166,6 +2166,8 @@ VNET_DECLARE(struct pf_krule *, pf_rulemarker);
 #define V_pf_rulemarker     VNET(pf_rulemarker)
 #endif
 
+int				 pf_start(void);
+int				 pf_stop(void);
 void				 pf_initialize(void);
 void				 pf_mtag_initialize(void);
 void				 pf_mtag_cleanup(void);
diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c
index 5a441c9723e3..38c09303a543 100644
--- a/sys/netpfil/pf/pf_ioctl.c
+++ b/sys/netpfil/pf/pf_ioctl.c
@@ -2337,6 +2337,49 @@ relock_DIOCKILLSTATES:
 	return (killed);
 }
 
+int
+pf_start(void)
+{
+	int error = 0;
+
+	sx_xlock(&V_pf_ioctl_lock);
+	if (V_pf_status.running)
+		error = EEXIST;
+	else {
+		hook_pf();
+		if (! TAILQ_EMPTY(V_pf_keth->active.rules))
+			hook_pf_eth();
+		V_pf_status.running = 1;
+		V_pf_status.since = time_second;
+		new_unrhdr64(&V_pf_stateid, time_second);
+
+		DPFPRINTF(PF_DEBUG_MISC, ("pf: started\n"));
+	}
+	sx_xunlock(&V_pf_ioctl_lock);
+
+	return (error);
+}
+
+int
+pf_stop(void)
+{
+	int error = 0;
+
+	sx_xlock(&V_pf_ioctl_lock);
+	if (!V_pf_status.running)
+		error = ENOENT;
+	else {
+		V_pf_status.running = 0;
+		dehook_pf();
+		dehook_pf_eth();
+		V_pf_status.since = time_second;
+		DPFPRINTF(PF_DEBUG_MISC, ("pf: stopped\n"));
+	}
+	sx_xunlock(&V_pf_ioctl_lock);
+
+	return (error);
+}
+
 static int
 pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td)
 {
@@ -2479,34 +2522,15 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td
 	CURVNET_SET(TD_TO_VNET(td));
 
 	switch (cmd) {
+#ifdef COMPAT_FREEBSD14
 	case DIOCSTART:
-		sx_xlock(&V_pf_ioctl_lock);
-		if (V_pf_status.running)
-			error = EEXIST;
-		else {
-			hook_pf();
-			if (! TAILQ_EMPTY(V_pf_keth->active.rules))
-				hook_pf_eth();
-			V_pf_status.running = 1;
-			V_pf_status.since = time_second;
-			new_unrhdr64(&V_pf_stateid, time_second);
-
-			DPFPRINTF(PF_DEBUG_MISC, ("pf: started\n"));
-		}
+		error = pf_start();
 		break;
 
 	case DIOCSTOP:
-		sx_xlock(&V_pf_ioctl_lock);
-		if (!V_pf_status.running)
-			error = ENOENT;
-		else {
-			V_pf_status.running = 0;
-			dehook_pf();
-			dehook_pf_eth();
-			V_pf_status.since = time_second;
-			DPFPRINTF(PF_DEBUG_MISC, ("pf: stopped\n"));
-		}
+		error = pf_stop();
 		break;
+#endif
 
 	case DIOCGETETHRULES: {
 		struct pfioc_nv		*nv = (struct pfioc_nv *)addr;
@@ -5416,8 +5440,6 @@ DIOCCHANGEADDR_error:
 		break;
 	}
 fail:
-	if (sx_xlocked(&V_pf_ioctl_lock))
-		sx_xunlock(&V_pf_ioctl_lock);
 	CURVNET_RESTORE();
 
 #undef ERROUT_IOCTL
diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c
index 459a5dc6507e..e079edcc166d 100644
--- a/sys/netpfil/pf/pf_nl.c
+++ b/sys/netpfil/pf/pf_nl.c
@@ -336,6 +336,18 @@ pf_handle_getcreators(struct nlmsghdr *hdr, struct nl_pstate *npt)
 	return (error);
 }
 
+static int
+pf_handle_start(struct nlmsghdr *hdr __unused, struct nl_pstate *npt __unused)
+{
+	return (pf_start());
+}
+
+static int
+pf_handle_stop(struct nlmsghdr *hdr __unused, struct nl_pstate *npt __unused)
+{
+	return (pf_stop());
+}
+
 static const struct nlhdr_parser *all_parsers[] = { &state_parser };
 
 static int family_id;
@@ -353,6 +365,18 @@ static const struct genl_cmd pf_cmds[] = {
 		.cmd_cb = pf_handle_getcreators,
 		.cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL,
 	},
+	{
+		.cmd_num = PFNL_CMD_START,
+		.cmd_name = "START",
+		.cmd_cb = pf_handle_start,
+		.cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL,
+	},
+	{
+		.cmd_num = PFNL_CMD_STOP,
+		.cmd_name = "STOP",
+		.cmd_cb = pf_handle_stop,
+		.cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL,
+	},
 };
 
 void
diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h
index 98525641b43d..3c8c6d3b8ed4 100644
--- a/sys/netpfil/pf/pf_nl.h
+++ b/sys/netpfil/pf/pf_nl.h
@@ -38,6 +38,8 @@ enum {
 	PFNL_CMD_UNSPEC = 0,
 	PFNL_CMD_GETSTATES = 1,
 	PFNL_CMD_GETCREATORS = 2,
+	PFNL_CMD_START = 3,
+	PFNL_CMD_STOP = 4,
 	__PFNL_CMD_MAX,
 };
 #define PFNL_CMD_MAX (__PFNL_CMD_MAX -1)
diff --git a/tests/sys/netpfil/pf/pass_block.sh b/tests/sys/netpfil/pf/pass_block.sh
index 792b73b4a0a5..faf5c2de4de2 100644
--- a/tests/sys/netpfil/pf/pass_block.sh
+++ b/tests/sys/netpfil/pf/pass_block.sh
@@ -28,6 +28,43 @@
 
 common_dir=$(atf_get_srcdir)/../common
 
+atf_test_case "enable_disable" "cleanup"
+enable_disable_head()
+{
+	atf_set descr 'Test enable/disable'
+	atf_set require.user root
+}
+
+enable_disable_body()
+{
+	pft_init
+
+	j="pass_block:enable_disable"
+
+	vnet_mkjail ${j}
+
+	# Disable when disabled fails
+	atf_check -s exit:1 -e ignore \
+	    jexec ${j} pfctl -d
+
+	# Enable succeeds
+	atf_check -s exit:0 -e ignore \
+	    jexec ${j} pfctl -e
+
+	# Enable when enabled fails
+	atf_check -s exit:1 -e ignore \
+	    jexec ${j} pfctl -e
+
+	# Disable succeeds
+	atf_check -s exit:0 -e ignore \
+	    jexec ${j} pfctl -d
+}
+
+enable_disable_cleanup()
+{
+	pft_cleanup
+}
+
 atf_test_case "v4" "cleanup"
 v4_head()
 {
@@ -257,6 +294,7 @@ urpf_cleanup()
 
 atf_init_test_cases()
 {
+	atf_add_test_case "enable_disable"
 	atf_add_test_case "v4"
 	atf_add_test_case "v6"
 	atf_add_test_case "noalias"