git: 9bb06778f822 - main - pf: support listing ethernet anchors

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Wed, 30 Mar 2022 09:17:22 UTC
The branch main has been updated by kp:

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

commit 9bb06778f822ad6b47d2a825d47e284ca8dd29a1
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2022-03-29 12:15:10 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2022-03-30 08:28:19 +0000

    pf: support listing ethernet anchors
    
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
---
 lib/libpfctl/libpfctl.c       |  75 +++++++++++++++++
 lib/libpfctl/libpfctl.h       |  14 ++++
 sbin/pfctl/pfctl.c            |  40 ++++++++++
 sys/net/pfvar.h               |   2 +
 sys/netpfil/pf/pf_ioctl.c     | 182 ++++++++++++++++++++++++++++++++++++++++++
 tests/sys/netpfil/pf/ether.sh |   2 +
 6 files changed, 315 insertions(+)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index 8f064594260b..1e1a90594210 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -622,6 +622,81 @@ pfctl_nveth_rule_to_eth_rule(const nvlist_t *nvl, struct pfctl_eth_rule *rule)
 	rule->action = nvlist_get_number(nvl, "action");
 }
 
+int
+pfctl_get_eth_rulesets_info(int dev, struct pfctl_eth_rulesets_info *ri,
+    const char *path)
+{
+	uint8_t buf[1024];
+	struct pfioc_nv nv;
+	nvlist_t *nvl;
+	void *packed;
+	size_t len;
+
+	bzero(ri, sizeof(*ri));
+
+	nvl = nvlist_create(0);
+	nvlist_add_string(nvl, "path", path);
+	packed = nvlist_pack(nvl, &len);
+	memcpy(buf, packed, len);
+	free(packed);
+	nvlist_destroy(nvl);
+
+	nv.data = buf;
+	nv.len = len;
+	nv.size = sizeof(buf);
+
+	if (ioctl(dev, DIOCGETETHRULESETS, &nv) != 0)
+		return (errno);
+
+	nvl = nvlist_unpack(buf, nv.len, 0);
+	if (nvl == NULL)
+		return (EIO);
+
+	ri->nr = nvlist_get_number(nvl, "nr");
+
+	nvlist_destroy(nvl);
+	return (0);
+}
+
+int
+pfctl_get_eth_ruleset(int dev, const char *path, int nr,
+    struct pfctl_eth_ruleset_info *ri)
+{
+	uint8_t buf[1024];
+	struct pfioc_nv nv;
+	nvlist_t *nvl;
+	void *packed;
+	size_t len;
+
+	bzero(ri, sizeof(*ri));
+
+	nvl = nvlist_create(0);
+	nvlist_add_string(nvl, "path", path);
+	nvlist_add_number(nvl, "nr", nr);
+	packed = nvlist_pack(nvl, &len);
+	memcpy(buf, packed, len);
+	free(packed);
+	nvlist_destroy(nvl);
+
+	nv.data = buf;
+	nv.len = len;
+	nv.size = sizeof(buf);
+
+	if (ioctl(dev, DIOCGETETHRULESET, &nv) != 0)
+		return (errno);
+
+	nvl = nvlist_unpack(buf, nv.len, 0);
+	if (nvl == NULL)
+		return (EIO);
+
+	ri->nr = nvlist_get_number(nvl, "nr");
+	strlcpy(ri->path, nvlist_get_string(nvl, "path"), MAXPATHLEN);
+	strlcpy(ri->name, nvlist_get_string(nvl, "name"),
+	    PF_ANCHOR_NAME_SIZE);
+
+	return (0);
+}
+
 int
 pfctl_get_eth_rules_info(int dev, struct pfctl_eth_rules_info *rules,
     const char *path)
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index b7f703b64def..92a1ea9b7cef 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -66,6 +66,10 @@ struct pfctl_status {
 	uint64_t	bcounters[2][2];
 };
 
+struct pfctl_eth_rulesets_info {
+	uint32_t	nr;
+};
+
 struct pfctl_eth_rules_info {
 	uint32_t	nr;
 	uint32_t	ticket;
@@ -111,6 +115,12 @@ struct pfctl_eth_rule {
 };
 TAILQ_HEAD(pfctl_eth_rules, pfctl_eth_rule);
 
+struct pfctl_eth_ruleset_info {
+	uint32_t	nr;
+	char		name[PF_ANCHOR_NAME_SIZE];
+	char		path[MAXPATHLEN];
+};
+
 struct pfctl_eth_ruleset {
 	struct pfctl_eth_rules	 rules;
 	struct pfctl_eth_anchor	*anchor;
@@ -356,6 +366,10 @@ struct pfctl_syncookies {
 struct pfctl_status* pfctl_get_status(int dev);
 void	pfctl_free_status(struct pfctl_status *status);
 
+int	pfctl_get_eth_rulesets_info(int dev,
+	    struct pfctl_eth_rulesets_info *ri, const char *path);
+int	pfctl_get_eth_ruleset(int dev, const char *path, int nr,
+	    struct pfctl_eth_ruleset_info *ri);
 int	pfctl_get_eth_rules_info(int dev, struct pfctl_eth_rules_info *rules,
 	    const char *path);
 int	pfctl_get_eth_rule(int dev, uint32_t nr, uint32_t ticket,
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index 88a96bd303a0..67358a325f77 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -111,6 +111,7 @@ int	 pfctl_show_limits(int, int);
 void	 pfctl_debug(int, u_int32_t, int);
 int	 pfctl_test_altqsupport(int, int);
 int	 pfctl_show_anchors(int, int, char *);
+int	 pfctl_show_eth_anchors(int, int, char *);
 int	 pfctl_ruleset_trans(struct pfctl *, char *, struct pfctl_anchor *, bool);
 int	 pfctl_eth_ruleset_trans(struct pfctl *, char *,
 	    struct pfctl_eth_anchor *);
@@ -2604,6 +2605,44 @@ pfctl_show_anchors(int dev, int opts, char *anchorname)
 	return (0);
 }
 
+int
+pfctl_show_eth_anchors(int dev, int opts, char *anchorname)
+{
+	struct pfctl_eth_rulesets_info ri;
+	struct pfctl_eth_ruleset_info rs;
+	int ret;
+
+	if ((ret = pfctl_get_eth_rulesets_info(dev, &ri, anchorname)) != 0) {
+		if (ret == ENOENT)
+			fprintf(stderr, "Anchor '%s' not found.\n",
+			    anchorname);
+		else
+			err(1, "DIOCGETETHRULESETS");
+		return (-1);
+	}
+
+	for (int nr = 0; nr < ri.nr; nr++) {
+		char sub[MAXPATHLEN];
+
+		if (pfctl_get_eth_ruleset(dev, anchorname, nr, &rs) != 0)
+			err(1, "DIOCGETETHRULESET");
+
+		if (!strcmp(rs.name, PF_RESERVED_ANCHOR))
+			continue;
+		sub[0] = 0;
+		if (rs.path[0]) {
+			strlcat(sub, rs.path, sizeof(sub));
+			strlcat(sub, "/", sizeof(sub));
+		}
+		strlcat(sub, rs.name, sizeof(sub));
+		if (sub[0] != '_' || (opts & PF_OPT_VERBOSE))
+			printf("  %s\n", sub);
+		if ((opts & PF_OPT_VERBOSE) && pfctl_show_eth_anchors(dev, opts, sub))
+			return (-1);
+	}
+	return (0);
+}
+
 const char *
 pfctl_lookup_option(char *cmd, const char * const *list)
 {
@@ -2830,6 +2869,7 @@ main(int argc, char *argv[])
 		switch (*showopt) {
 		case 'A':
 			pfctl_show_anchors(dev, opts, anchorname);
+			pfctl_show_eth_anchors(dev, opts, anchorname);
 			break;
 		case 'r':
 			pfctl_load_fingerprints(dev, opts);
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index ccc81ea137b9..db6b5c22f07f 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -1865,6 +1865,8 @@ struct pfioc_iface {
 #define	DIOCADDETHRULE		_IOWR('D', 97, struct pfioc_nv)
 #define	DIOCGETETHRULE		_IOWR('D', 98, struct pfioc_nv)
 #define	DIOCGETETHRULES		_IOWR('D', 99, struct pfioc_nv)
+#define	DIOCGETETHRULESETS	_IOWR('D', 100, struct pfioc_nv)
+#define	DIOCGETETHRULESET	_IOWR('D', 101, struct pfioc_nv)
 
 struct pf_ifspeed_v0 {
 	char			ifname[IFNAMSIZ];
diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c
index 6012825ead9b..3cb5552d20c5 100644
--- a/sys/netpfil/pf/pf_ioctl.c
+++ b/sys/netpfil/pf/pf_ioctl.c
@@ -2463,6 +2463,8 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td
 		case DIOCCLRIFFLAG:
 		case DIOCGETETHRULES:
 		case DIOCGETETHRULE:
+		case DIOCGETETHRULESETS:
+		case DIOCGETETHRULESET:
 			break;
 		case DIOCRCLRTABLES:
 		case DIOCRADDTABLES:
@@ -2512,6 +2514,8 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td
 		case DIOCGETRULENV:
 		case DIOCGETETHRULES:
 		case DIOCGETETHRULE:
+		case DIOCGETETHRULESETS:
+		case DIOCGETETHRULESET:
 			break;
 		case DIOCRCLRTABLES:
 		case DIOCRADDTABLES:
@@ -2864,6 +2868,184 @@ DIOCADDETHRULE_error:
 		break;
 	}
 
+	case DIOCGETETHRULESETS: {
+		struct epoch_tracker	 et;
+		struct pfioc_nv		*nv = (struct pfioc_nv *)addr;
+		nvlist_t		*nvl = NULL;
+		void			*nvlpacked = NULL;
+		struct pf_keth_ruleset	*ruleset;
+		struct pf_keth_anchor	*anchor;
+		int			 nr = 0;
+
+#define ERROUT(x)	do { error = (x); goto DIOCGETETHRULESETS_error; } while (0)
+
+		if (nv->len > pf_ioctl_maxcount)
+			ERROUT(ENOMEM);
+
+		nvlpacked = malloc(nv->len, M_NVLIST, M_WAITOK);
+		if (nvlpacked == NULL)
+			ERROUT(ENOMEM);
+
+		error = copyin(nv->data, nvlpacked, nv->len);
+		if (error)
+			ERROUT(error);
+
+		nvl = nvlist_unpack(nvlpacked, nv->len, 0);
+		if (nvl == NULL)
+			ERROUT(EBADMSG);
+		if (! nvlist_exists_string(nvl, "path"))
+			ERROUT(EBADMSG);
+
+		NET_EPOCH_ENTER(et);
+
+		if ((ruleset = pf_find_keth_ruleset(
+		    nvlist_get_string(nvl, "path"))) == NULL) {
+			NET_EPOCH_EXIT(et);
+			ERROUT(ENOENT);
+		}
+
+		if (ruleset->anchor == NULL) {
+			RB_FOREACH(anchor, pf_keth_anchor_global, &V_pf_keth_anchors)
+				if (anchor->parent == NULL)
+					nr++;
+		} else {
+			RB_FOREACH(anchor, pf_keth_anchor_node,
+			    &ruleset->anchor->children)
+				nr++;
+		}
+
+		NET_EPOCH_EXIT(et);
+
+		nvlist_destroy(nvl);
+		nvl = NULL;
+		free(nvlpacked, M_NVLIST);
+		nvlpacked = NULL;
+
+		nvl = nvlist_create(0);
+		if (nvl == NULL)
+			ERROUT(ENOMEM);
+
+		nvlist_add_number(nvl, "nr", nr);
+
+		nvlpacked = nvlist_pack(nvl, &nv->len);
+		if (nvlpacked == NULL)
+			ERROUT(ENOMEM);
+
+		if (nv->size == 0)
+			ERROUT(0);
+		else if (nv->size < nv->len)
+			ERROUT(ENOSPC);
+
+		error = copyout(nvlpacked, nv->data, nv->len);
+
+#undef ERROUT
+DIOCGETETHRULESETS_error:
+		free(nvlpacked, M_NVLIST);
+		nvlist_destroy(nvl);
+		break;
+	}
+
+	case DIOCGETETHRULESET: {
+		struct epoch_tracker	 et;
+		struct pfioc_nv		*nv = (struct pfioc_nv *)addr;
+		nvlist_t		*nvl = NULL;
+		void			*nvlpacked = NULL;
+		struct pf_keth_ruleset	*ruleset;
+		struct pf_keth_anchor	*anchor;
+		int			 nr = 0, req_nr = 0;
+		bool			 found = false;
+
+#define ERROUT(x)	do { error = (x); goto DIOCGETETHRULESET_error; } while (0)
+
+		if (nv->len > pf_ioctl_maxcount)
+			ERROUT(ENOMEM);
+
+		nvlpacked = malloc(nv->len, M_NVLIST, M_WAITOK);
+		if (nvlpacked == NULL)
+			ERROUT(ENOMEM);
+
+		error = copyin(nv->data, nvlpacked, nv->len);
+		if (error)
+			ERROUT(error);
+
+		nvl = nvlist_unpack(nvlpacked, nv->len, 0);
+		if (nvl == NULL)
+			ERROUT(EBADMSG);
+		if (! nvlist_exists_string(nvl, "path"))
+			ERROUT(EBADMSG);
+		if (! nvlist_exists_number(nvl, "nr"))
+			ERROUT(EBADMSG);
+
+		req_nr = nvlist_get_number(nvl, "nr");
+
+		NET_EPOCH_ENTER(et);
+
+		if ((ruleset = pf_find_keth_ruleset(
+		    nvlist_get_string(nvl, "path"))) == NULL) {
+			NET_EPOCH_EXIT(et);
+			ERROUT(ENOENT);
+		}
+
+		nvlist_destroy(nvl);
+		nvl = NULL;
+		free(nvlpacked, M_NVLIST);
+		nvlpacked = NULL;
+
+		nvl = nvlist_create(0);
+		if (nvl == NULL) {
+			NET_EPOCH_EXIT(et);
+			ERROUT(ENOMEM);
+		}
+
+		if (ruleset->anchor == NULL) {
+			RB_FOREACH(anchor, pf_keth_anchor_global,
+			    &V_pf_keth_anchors) {
+				if (anchor->parent == NULL && nr++ == req_nr) {
+					found = true;
+					break;
+				}
+			}
+		} else {
+			RB_FOREACH(anchor, pf_keth_anchor_node,
+			     &ruleset->anchor->children) {
+				if (nr++ == req_nr) {
+					found = true;
+					break;
+				}
+			}
+		}
+
+		NET_EPOCH_EXIT(et);
+		if (found) {
+			nvlist_add_number(nvl, "nr", nr);
+			nvlist_add_string(nvl, "name", anchor->name);
+			if (ruleset->anchor)
+				nvlist_add_string(nvl, "path",
+				    ruleset->anchor->path);
+			else
+				nvlist_add_string(nvl, "path", "");
+		} else {
+			ERROUT(EBUSY);
+		}
+
+		nvlpacked = nvlist_pack(nvl, &nv->len);
+		if (nvlpacked == NULL)
+			ERROUT(ENOMEM);
+
+		if (nv->size == 0)
+			ERROUT(0);
+		else if (nv->size < nv->len)
+			ERROUT(ENOSPC);
+
+		error = copyout(nvlpacked, nv->data, nv->len);
+
+#undef ERROUT
+DIOCGETETHRULESET_error:
+		free(nvlpacked, M_NVLIST);
+		nvlist_destroy(nvl);
+		break;
+	}
+
 	case DIOCADDRULENV: {
 		struct pfioc_nv	*nv = (struct pfioc_nv *)addr;
 		nvlist_t	*nvl = NULL;
diff --git a/tests/sys/netpfil/pf/ether.sh b/tests/sys/netpfil/pf/ether.sh
index 6e81f07ca26e..7a7f91844148 100644
--- a/tests/sys/netpfil/pf/ether.sh
+++ b/tests/sys/netpfil/pf/ether.sh
@@ -490,6 +490,8 @@ anchor_body()
 		"}" \
 		"ether pass in from ${epair_a_mac}"
 	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
+
+	atf_check -s exit:0 -o match:'baz' jexec alcatraz pfctl -sA
 }
 
 anchor_cleanup()