git: c5131afee39b - main - pf: add anchor support for ether rules

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Wed, 02 Mar 2022 16:01:01 UTC
The branch main has been updated by kp:

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

commit c5131afee39b4fa9e3889deb2ceea35a43ef35e2
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2021-10-01 17:05:50 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2022-03-02 16:00:07 +0000

    pf: add anchor support for ether rules
    
    Support anchors in ether rules.
    
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D32482
---
 lib/libpfctl/libpfctl.c     |  31 ++++-
 lib/libpfctl/libpfctl.h     |  28 +++-
 sbin/pfctl/parse.y          | 119 +++++++++++++++--
 sbin/pfctl/pf_ruleset.c     | 197 +++++++++++++++++++++++++++-
 sbin/pfctl/pfctl.c          | 254 +++++++++++++++++++++++++++++++-----
 sbin/pfctl/pfctl.h          |   9 ++
 sbin/pfctl/pfctl_parser.c   |  13 +-
 sbin/pfctl/pfctl_parser.h   |   7 +-
 sys/net/pfvar.h             |  82 ++++++++++--
 sys/netpfil/pf/pf.c         | 136 ++++++++++++++++++--
 sys/netpfil/pf/pf_ioctl.c   | 228 ++++++++++++++++++++------------
 sys/netpfil/pf/pf_nv.c      |   3 +
 sys/netpfil/pf/pf_ruleset.c | 307 +++++++++++++++++++++++++++++++++++++++++++-
 13 files changed, 1247 insertions(+), 167 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index fd7dd7a474a0..90733d421572 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -606,20 +606,34 @@ pfctl_nveth_rule_to_eth_rule(const nvlist_t *nvl, struct pfctl_eth_rule *rule)
 	rule->dnpipe = nvlist_get_number(nvl, "dnpipe");
 	rule->dnflags = nvlist_get_number(nvl, "dnflags");
 
+	rule->anchor_relative = nvlist_get_number(nvl, "anchor_relative");
+	rule->anchor_wildcard = nvlist_get_number(nvl, "anchor_wildcard");
+
 	rule->action = nvlist_get_number(nvl, "action");
 }
 
 int
-pfctl_get_eth_rules_info(int dev, struct pfctl_eth_rules_info *rules)
+pfctl_get_eth_rules_info(int dev, struct pfctl_eth_rules_info *rules,
+    const char *path)
 {
 	uint8_t buf[1024];
 	struct pfioc_nv nv;
 	nvlist_t *nvl;
+	void *packed;
+	size_t len;
 
 	bzero(rules, sizeof(*rules));
 
+	nvl = nvlist_create(0);
+	nvlist_add_string(nvl, "anchor", path);
+	packed = nvlist_pack(nvl, &len);
+	memcpy(buf, packed, len);
+	free(packed);
+	nvlist_destroy(nvl);
+
 	nv.data = buf;
-	nv.len = nv.size = sizeof(buf);
+	nv.len = len;
+	nv.size = sizeof(buf);
 
 	if (ioctl(dev, DIOCGETETHRULES, &nv) != 0)
 		return (errno);
@@ -637,7 +651,8 @@ pfctl_get_eth_rules_info(int dev, struct pfctl_eth_rules_info *rules)
 
 int
 pfctl_get_eth_rule(int dev, uint32_t nr, uint32_t ticket,
-    struct pfctl_eth_rule *rule, bool clear)
+    const char *path, struct pfctl_eth_rule *rule, bool clear,
+    char *anchor_call)
 {
 	uint8_t buf[1024];
 	struct pfioc_nv nv;
@@ -647,6 +662,7 @@ pfctl_get_eth_rule(int dev, uint32_t nr, uint32_t ticket,
 
 	nvl = nvlist_create(0);
 
+	nvlist_add_string(nvl, "anchor", path);
 	nvlist_add_number(nvl, "ticket", ticket);
 	nvlist_add_number(nvl, "nr", nr);
 	nvlist_add_bool(nvl, "clear", clear);
@@ -670,12 +686,17 @@ pfctl_get_eth_rule(int dev, uint32_t nr, uint32_t ticket,
 
 	pfctl_nveth_rule_to_eth_rule(nvl, rule);
 
+	if (anchor_call)
+		strlcpy(anchor_call, nvlist_get_string(nvl, "anchor_call"),
+		    MAXPATHLEN);
+
 	nvlist_destroy(nvl);
 	return (0);
 }
 
 int
-pfctl_add_eth_rule(int dev, const struct pfctl_eth_rule *r, uint32_t ticket)
+pfctl_add_eth_rule(int dev, const struct pfctl_eth_rule *r, const char *anchor,
+    const char *anchor_call, uint32_t ticket)
 {
 	struct pfioc_nv nv;
 	nvlist_t *nvl, *addr;
@@ -686,6 +707,8 @@ pfctl_add_eth_rule(int dev, const struct pfctl_eth_rule *r, uint32_t ticket)
 	nvl = nvlist_create(0);
 
 	nvlist_add_number(nvl, "ticket", ticket);
+	nvlist_add_string(nvl, "anchor", anchor);
+	nvlist_add_string(nvl, "anchor_call", anchor_call);
 
 	nvlist_add_number(nvl, "nr", r->nr);
 	nvlist_add_bool(nvl, "quick", r->quick);
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index 6c3dbfc5d0df..256fa49c4f25 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -37,6 +37,7 @@
 #include <netpfil/pf/pf.h>
 
 struct pfctl_anchor;
+struct pfctl_eth_anchor;
 
 struct pfctl_status_counter {
 	uint64_t	 id;
@@ -100,11 +101,28 @@ struct pfctl_eth_rule {
 	uint32_t		 dnflags;
 	uint8_t			 action;
 
+	struct pfctl_eth_anchor	*anchor;
+	uint8_t			 anchor_relative;
+	uint8_t			 anchor_wildcard;
+
 	TAILQ_ENTRY(pfctl_eth_rule)	 entries;
 };
-
 TAILQ_HEAD(pfctl_eth_rules, pfctl_eth_rule);
 
+struct pfctl_eth_ruleset {
+	struct pfctl_eth_rules	 rules;
+	struct pfctl_eth_anchor	*anchor;
+};
+
+struct pfctl_eth_anchor {
+	struct pfctl_eth_anchor		*parent;
+	char				 name[PF_ANCHOR_NAME_SIZE];
+	char				 path[MAXPATHLEN];
+	struct pfctl_eth_ruleset	 ruleset;
+	int				 refcnt;	/* anchor rules */
+	int				 match;	/* XXX: used for pfctl black magic */
+};
+
 struct pfctl_pool {
 	struct pf_palist	 list;
 	struct pf_pooladdr	*cur;
@@ -331,11 +349,13 @@ struct pfctl_syncookies {
 struct pfctl_status* pfctl_get_status(int dev);
 void	pfctl_free_status(struct pfctl_status *status);
 
-int	pfctl_get_eth_rules_info(int dev, struct pfctl_eth_rules_info *rules);
+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,
-	    struct pfctl_eth_rule *rule, bool clear);
+	    const char *path, struct pfctl_eth_rule *rule, bool clear,
+	    char *anchor_call);
 int	pfctl_add_eth_rule(int dev, const struct pfctl_eth_rule *r,
-	    uint32_t ticket);
+	    const char *anchor, const char *anchor_call, uint32_t ticket);
 int	pfctl_get_rule(int dev, uint32_t nr, uint32_t ticket,
 	    const char *anchor, uint32_t ruleset, struct pfctl_rule *rule,
 	    char *anchor_call);
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
index b856621ecf41..5f10c4ab2e17 100644
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -350,7 +350,7 @@ void		 expand_label_nr(const char *, char *, size_t,
 		    struct pfctl_rule *);
 void		 expand_eth_rule(struct pfctl_eth_rule *,
 		    struct node_if *, struct node_etherproto *,
-		    struct node_mac *, struct node_mac *);
+		    struct node_mac *, struct node_mac *, const char *);
 void		 expand_rule(struct pfctl_rule *, struct node_if *,
 		    struct node_host *, struct node_proto *, struct node_os *,
 		    struct node_host *, struct node_port *, struct node_host *,
@@ -370,6 +370,7 @@ int	 rule_label(struct pfctl_rule *, char *s[PF_RULE_MAX_LABEL_COUNT]);
 int	 rt_tableid_max(void);
 
 void	 mv_rules(struct pfctl_ruleset *, struct pfctl_ruleset *);
+void	 mv_eth_rules(struct pfctl_eth_ruleset *, struct pfctl_eth_ruleset *);
 void	 decide_address_family(struct node_host *, sa_family_t *);
 void	 remove_invalid_hosts(struct node_host **, sa_family_t *);
 int	 invalid_redirect(struct node_host *, sa_family_t);
@@ -566,6 +567,7 @@ ruleset		: /* empty */
 		| ruleset '\n'
 		| ruleset option '\n'
 		| ruleset etherrule '\n'
+		| ruleset etheranchorrule '\n'
 		| ruleset scrubrule '\n'
 		| ruleset natrule '\n'
 		| ruleset binatrule '\n'
@@ -1196,7 +1198,95 @@ etherrule	: ETHER action dir quick interface etherproto etherfromto etherfilter_
 			r.dnpipe = $8.dnpipe;
 			r.dnflags = $8.free_flags;
 
-			expand_eth_rule(&r, $5, $6, $7.src, $7.dst);
+			expand_eth_rule(&r, $5, $6, $7.src, $7.dst, "");
+		}
+		;
+
+etherpfa_anchorlist	: /* empty */
+		| etherpfa_anchorlist '\n'
+		| etherpfa_anchorlist etherrule '\n'
+		| etherpfa_anchorlist etheranchorrule '\n'
+		;
+
+etherpfa_anchor	: '{'
+		{
+			char ta[PF_ANCHOR_NAME_SIZE];
+			struct pfctl_eth_ruleset *rs;
+
+			/* steping into a brace anchor */
+			pf->asd++;
+			pf->bn++;
+
+			/* create a holding ruleset in the root */
+			snprintf(ta, PF_ANCHOR_NAME_SIZE, "_%d", pf->bn);
+			rs = pf_find_or_create_eth_ruleset(ta);
+			if (rs == NULL)
+				err(1, "etherpfa_anchor: pf_find_or_create_eth_ruleset");
+			pf->eastack[pf->asd] = rs->anchor;
+			pf->eanchor = rs->anchor;
+		} '\n' etherpfa_anchorlist '}'
+		{
+			pf->ealast = pf->eanchor;
+			pf->asd--;
+			pf->eanchor = pf->eastack[pf->asd];
+		}
+		| /* empty */
+		;
+
+etheranchorrule	: ETHER ANCHOR anchorname dir quick interface etherproto etherfromto etherpfa_anchor
+		{
+			struct pfctl_eth_rule	r;
+
+			if (check_rulestate(PFCTL_STATE_ETHER)) {
+				free($3);
+				YYERROR;
+			}
+
+			if ($3 && ($3[0] == '_' || strstr($3, "/_") != NULL)) {
+				free($3);
+				yyerror("anchor names beginning with '_' "
+				    "are reserved for internal use");
+				YYERROR;
+			}
+
+			memset(&r, 0, sizeof(r));
+			if (pf->eastack[pf->asd + 1]) {
+				/* move inline rules into relative location */
+				pfctl_eth_anchor_setup(pf, &r,
+				    &pf->eastack[pf->asd]->ruleset,
+				    $3 ? $3 : pf->ealast->name);
+				if (r.anchor == NULL)
+					err(1, "etheranchorrule: unable to "
+					    "create ruleset");
+
+				if (pf->ealast != r.anchor) {
+					if (r.anchor->match) {
+						yyerror("inline anchor '%s' "
+						    "already exists",
+						    r.anchor->name);
+						YYERROR;
+					}
+					mv_eth_rules(&pf->ealast->ruleset,
+					    &r.anchor->ruleset);
+				}
+				pf_remove_if_empty_eth_ruleset(&pf->ealast->ruleset);
+				pf->ealast = r.anchor;
+			} else {
+				if (!$3) {
+					yyerror("anchors without explicit "
+					    "rules must specify a name");
+					YYERROR;
+				}
+			}
+
+			r.direction = $4;
+			r.quick = $5.quick;
+
+			expand_eth_rule(&r, $6, $7, $8.src, $8.dst,
+			    pf->eastack[pf->asd + 1] ? pf->ealast->name : $3);
+
+			free($3);
+			pf->eastack[pf->asd + 1] = NULL;
 		}
 		;
 
@@ -5640,15 +5730,12 @@ expand_queue(struct pf_altq *a, struct node_if *interfaces,
 void
 expand_eth_rule(struct pfctl_eth_rule *r,
     struct node_if *interfaces, struct node_etherproto *protos,
-    struct node_mac *srcs, struct node_mac *dsts)
+    struct node_mac *srcs, struct node_mac *dsts, const char *anchor_call)
 {
-	struct pfctl_eth_rule *rule;
-
 	LOOP_THROUGH(struct node_if, interface, interfaces,
 	LOOP_THROUGH(struct node_etherproto, proto, protos,
 	LOOP_THROUGH(struct node_mac, src, srcs,
 	LOOP_THROUGH(struct node_mac, dst, dsts,
-		r->nr = pf->eth_nr++;
 		strlcpy(r->ifname, interface->ifname,
 		    sizeof(r->ifname));
 		r->ifnot = interface->not;
@@ -5657,12 +5744,9 @@ expand_eth_rule(struct pfctl_eth_rule *r,
 		r->src.neg = src->neg;
 		bcopy(dst->mac, r->dst.addr, ETHER_ADDR_LEN);
 		r->dst.neg = dst->neg;
+		r->nr = pf->eastack[pf->asd]->match++;
 
-		if ((rule = calloc(1, sizeof(*rule))) == NULL)
-			err(1, "calloc");
-		bcopy(r, rule, sizeof(*rule));
-
-		TAILQ_INSERT_TAIL(&pf->eth_rules, rule, entries);
+		pfctl_append_eth_rule(pf, r, anchor_call);
 	))));
 
 	FREE_LIST(struct node_if, interfaces);
@@ -6525,6 +6609,19 @@ mv_rules(struct pfctl_ruleset *src, struct pfctl_ruleset *dst)
 	}
 }
 
+void
+mv_eth_rules(struct pfctl_eth_ruleset *src, struct pfctl_eth_ruleset *dst)
+{
+	struct pfctl_eth_rule *r;
+
+	while ((r = TAILQ_FIRST(&src->rules)) != NULL) {
+		TAILQ_REMOVE(&src->rules, r, entries);
+		TAILQ_INSERT_TAIL(&dst->rules, r, entries);
+		dst->anchor->match++;
+	}
+	src->anchor->match = 0;
+}
+
 void
 decide_address_family(struct node_host *n, sa_family_t *af)
 {
diff --git a/sbin/pfctl/pf_ruleset.c b/sbin/pfctl/pf_ruleset.c
index 480e0f0c9b45..a7f31366f48c 100644
--- a/sbin/pfctl/pf_ruleset.c
+++ b/sbin/pfctl/pf_ruleset.c
@@ -65,6 +65,7 @@ __FBSDID("$FreeBSD$");
 #define rs_free(x)		 free(x)
 
 #include "pfctl.h"
+#include "pfctl_parser.h"
 
 #ifdef PFDEBUG
 #include <sys/stdarg.h>
@@ -74,7 +75,8 @@ __FBSDID("$FreeBSD$");
 #endif /* PFDEBUG */
 
 struct pfctl_anchor_global	 pf_anchors;
-struct pfctl_anchor	 pf_main_anchor;
+extern struct pfctl_anchor	 pf_main_anchor;
+extern struct pfctl_eth_anchor	 pf_eth_main_anchor;
 #undef V_pf_anchors
 #define V_pf_anchors		 pf_anchors
 #undef pf_main_ruleset
@@ -290,6 +292,148 @@ pf_remove_if_empty_ruleset(struct pfctl_ruleset *ruleset)
 		ruleset = &parent->ruleset;
 	}
 }
+
+void
+pf_remove_if_empty_eth_ruleset(struct pfctl_eth_ruleset *ruleset)
+{
+	struct pfctl_eth_anchor	*parent;
+
+	return;
+	while (ruleset != NULL) {
+		if (ruleset == &pf_eth_main_anchor.ruleset ||
+		    ruleset->anchor == NULL || ruleset->anchor->refcnt > 0)
+			return;
+		if (!TAILQ_EMPTY(&ruleset->rules))
+			return;
+		rs_free(ruleset->anchor);
+		if (parent == NULL)
+			return;
+		ruleset = &parent->ruleset;
+	}
+}
+
+void
+pf_init_eth_ruleset(struct pfctl_eth_ruleset *ruleset)
+{
+
+	memset(ruleset, 0, sizeof(*ruleset));
+	TAILQ_INIT(&ruleset->rules);
+}
+
+
+static struct pfctl_eth_anchor*
+_pf_find_eth_anchor(struct pfctl_eth_anchor *anchor, const char *path)
+{
+	struct pfctl_eth_rule	*r;
+	struct pfctl_eth_anchor	*a;
+
+	if (strcmp(path, anchor->path) == 0)
+		return (anchor);
+
+	TAILQ_FOREACH(r, &anchor->ruleset.rules, entries) {
+		if (! r->anchor)
+			continue;
+
+		/* Step into anchor */
+		a = _pf_find_eth_anchor(r->anchor, path);
+		if (a)
+			return (a);
+	}
+
+	return (NULL);
+}
+
+static struct pfctl_eth_anchor*
+pf_find_eth_anchor(const char *path)
+{
+	return (_pf_find_eth_anchor(&pf_eth_main_anchor, path));
+}
+
+static struct pfctl_eth_ruleset*
+pf_find_eth_ruleset(const char *path)
+{
+	struct pfctl_eth_anchor	*anchor;
+
+	while (*path == '/')
+		path++;
+	if (!*path)
+		return (&pf_eth_main_anchor.ruleset);
+	anchor = pf_find_eth_anchor(path);
+	if (anchor == NULL)
+		return (NULL);
+	else
+		return (&anchor->ruleset);
+}
+
+struct pfctl_eth_ruleset *
+pf_find_or_create_eth_ruleset(const char *path)
+{
+	char				*p, *q, *r;
+	struct pfctl_eth_ruleset	*ruleset;
+	struct pfctl_eth_anchor		*anchor = NULL, *parent = NULL;
+
+	if (path[0] == 0)
+		return (&pf_eth_main_anchor.ruleset);
+	while (*path == '/')
+		path++;
+	ruleset = pf_find_eth_ruleset(path);
+	if (ruleset != NULL)
+		return (ruleset);
+	p = (char *)rs_malloc(MAXPATHLEN);
+	if (p == NULL)
+		return (NULL);
+	strlcpy(p, path, MAXPATHLEN);
+	while (parent == NULL && (q = strrchr(p, '/')) != NULL) {
+		*q = 0;
+		if ((ruleset = pf_find_eth_ruleset(p)) != NULL) {
+			parent = ruleset->anchor;
+			break;
+		}
+	}
+	if (q == NULL)
+		q = p;
+	else
+		q++;
+	strlcpy(p, path, MAXPATHLEN);
+	if (!*q) {
+		rs_free(p);
+		return (NULL);
+	}
+	while ((r = strchr(q, '/')) != NULL || *q) {
+		if (r != NULL)
+			*r = 0;
+		if (!*q || strlen(q) >= PF_ANCHOR_NAME_SIZE ||
+		    (parent != NULL && strlen(parent->path) >=
+		    MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 1)) {
+			rs_free(p);
+			return (NULL);
+		}
+		anchor = (struct pfctl_eth_anchor *)rs_malloc(sizeof(*anchor));
+		if (anchor == NULL) {
+			rs_free(p);
+			return (NULL);
+		}
+		strlcpy(anchor->name, q, sizeof(anchor->name));
+		if (parent != NULL) {
+			strlcpy(anchor->path, parent->path,
+			    sizeof(anchor->path));
+			strlcat(anchor->path, "/", sizeof(anchor->path));
+		}
+		strlcat(anchor->path, anchor->name, sizeof(anchor->path));
+		if (parent != NULL)
+			anchor->parent = parent;
+		pf_init_eth_ruleset(&anchor->ruleset);
+		anchor->ruleset.anchor = anchor;
+		parent = anchor;
+		if (r != NULL)
+			q = r + 1;
+		else
+			*q = 0;
+	}
+	rs_free(p);
+	return (&anchor->ruleset);
+}
+
 int
 pfctl_anchor_setup(struct pfctl_rule *r, const struct pfctl_ruleset *s,
     const char *name)
@@ -345,3 +489,54 @@ pfctl_anchor_setup(struct pfctl_rule *r, const struct pfctl_ruleset *s,
 	r->anchor->refcnt++;
 	return (0);
 }
+
+int
+pfctl_eth_anchor_setup(struct pfctl *pf, struct pfctl_eth_rule *r,
+    const struct pfctl_eth_ruleset *s, const char *name)
+{
+	char				*p, *path;
+	struct pfctl_eth_ruleset	*ruleset;
+
+	r->anchor = NULL;
+	if (!name[0])
+		return (0);
+	path = (char *)rs_malloc(MAXPATHLEN);
+	if (path == NULL)
+		return (1);
+	if (name[0] == '/')
+		strlcpy(path, name + 1, MAXPATHLEN);
+	else {
+		/* relative path */
+		if (s->anchor == NULL || !s->anchor->path[0])
+			path[0] = 0;
+		else
+			strlcpy(path, s->anchor->path, MAXPATHLEN);
+		while (name[0] == '.' && name[1] == '.' && name[2] == '/') {
+			if (!path[0]) {
+				printf("%s: .. beyond root\n", __func__);
+				rs_free(path);
+				return (1);
+			}
+			if ((p = strrchr(path, '/')) != NULL)
+				*p = 0;
+			else
+				path[0] = 0;
+			name += 3;
+		}
+		if (path[0])
+			strlcat(path, "/", MAXPATHLEN);
+		strlcat(path, name, MAXPATHLEN);
+	}
+	if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) {
+		*p = 0;
+	}
+	ruleset = pf_find_or_create_eth_ruleset(path);
+	rs_free(path);
+	if (ruleset == NULL || ruleset->anchor == NULL) {
+		printf("%s: ruleset\n", __func__);
+		return (1);
+	}
+	r->anchor = ruleset->anchor;
+	r->anchor->refcnt++;
+	return (0);
+}
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index f825ef834ac4..bec37b0bf85f 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -98,7 +98,7 @@ int	 pfctl_get_pool(int, struct pfctl_pool *, u_int32_t, u_int32_t, int,
 	    char *);
 void	 pfctl_print_eth_rule_counters(struct pfctl_eth_rule *, int);
 void	 pfctl_print_rule_counters(struct pfctl_rule *, int);
-int	 pfctl_show_eth_rules(int, int, enum pfctl_show);
+int	 pfctl_show_eth_rules(int, char *, int, enum pfctl_show, char *, int);
 int	 pfctl_show_rules(int, char *, int, enum pfctl_show, char *, int);
 int	 pfctl_show_nat(int, int, char *);
 int	 pfctl_show_src_nodes(int, int);
@@ -110,15 +110,21 @@ 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_ruleset_trans(struct pfctl *, char *, struct pfctl_anchor *);
-int	 pfctl_load_eth_ruleset(struct pfctl *);
+int	 pfctl_ruleset_trans(struct pfctl *, char *, struct pfctl_anchor *, bool);
+int	 pfctl_eth_ruleset_trans(struct pfctl *, char *,
+	    struct pfctl_eth_anchor *);
+int	 pfctl_load_eth_ruleset(struct pfctl *, char *,
+	    struct pfctl_eth_ruleset *, int);
+int	 pfctl_load_eth_rule(struct pfctl *, char *, struct pfctl_eth_rule *,
+	    int);
 int	 pfctl_load_ruleset(struct pfctl *, char *,
 		struct pfctl_ruleset *, int, int);
 int	 pfctl_load_rule(struct pfctl *, char *, struct pfctl_rule *, int);
 const char	*pfctl_lookup_option(char *, const char * const *);
 
 static struct pfctl_anchor_global	 pf_anchors;
-static struct pfctl_anchor	 pf_main_anchor;
+struct pfctl_anchor	 pf_main_anchor;
+struct pfctl_eth_anchor	 pf_eth_main_anchor;
 static struct pfr_buffer skip_b;
 
 static const char	*clearopt;
@@ -1052,31 +1058,66 @@ pfctl_print_title(char *title)
 }
 
 int
-pfctl_show_eth_rules(int dev, int opts, enum pfctl_show format)
+pfctl_show_eth_rules(int dev, char *path, int opts, enum pfctl_show format,
+    char *anchorname, int depth)
 {
+	char anchor_call[MAXPATHLEN];
 	struct pfctl_eth_rules_info info;
 	struct pfctl_eth_rule rule;
 	int dotitle = opts & PF_OPT_SHOWALL;
+	int len = strlen(path);
+	int brace;
+	char *p;
+
+	if (path[0])
+		snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname);
+	else
+		snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname);
 
-	if (pfctl_get_eth_rules_info(dev, &info)) {
+	if (pfctl_get_eth_rules_info(dev, &info, path)) {
 		warn("DIOCGETETHRULES");
 		return (-1);
 	}
 	for (int nr = 0; nr < info.nr; nr++) {
-		if (pfctl_get_eth_rule(dev, nr, info.ticket, &rule,
-		    opts & PF_OPT_CLRRULECTRS) != 0) {
+		brace = 0;
+		INDENT(depth, !(opts & PF_OPT_VERBOSE));
+		if (pfctl_get_eth_rule(dev, nr, info.ticket, path, &rule,
+		    opts & PF_OPT_CLRRULECTRS, anchor_call) != 0) {
 			warn("DIOCGETETHRULE");
 			return (-1);
 		}
+		if (anchor_call[0] &&
+		   ((((p = strrchr(anchor_call, '_')) != NULL) &&
+		   (p == anchor_call ||
+		   *(--p) == '/')) || (opts & PF_OPT_RECURSE))) {
+			brace++;
+			if ((p = strrchr(anchor_call, '/')) !=
+			    NULL)
+				p++;
+			else
+				p = &anchor_call[0];
+		} else
+			p = &anchor_call[0];
 		if (dotitle) {
 			pfctl_print_title("ETH RULES:");
 			dotitle = 0;
 		}
-		print_eth_rule(&rule, opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG));
-		printf("\n");
+		print_eth_rule(&rule, anchor_call,
+		    opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG));
+		if (brace)
+			printf(" {\n");
+		else
+			printf("\n");
 		pfctl_print_eth_rule_counters(&rule, opts);
+		if (brace) {
+			pfctl_show_eth_rules(dev, path, opts, format,
+			    p, depth + 1);
+			INDENT(depth, !(opts & PF_OPT_VERBOSE));
+			printf("}\n");
+		}
 	}
 
+	path[len] = '\0';
 	return (0);
 }
 
@@ -1508,15 +1549,70 @@ pfctl_append_rule(struct pfctl *pf, struct pfctl_rule *r,
 }
 
 int
-pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_anchor *a)
+pfctl_append_eth_rule(struct pfctl *pf, struct pfctl_eth_rule *r,
+    const char *anchor_call)
+{
+	struct pfctl_eth_rule		*rule;
+	struct pfctl_eth_ruleset	*rs;
+	char 				*p;
+
+	rs = &pf->eanchor->ruleset;
+
+	if (anchor_call[0] && r->anchor == NULL) {
+		/*
+		 * Don't make non-brace anchors part of the main anchor pool.
+		 */
+		if ((r->anchor = calloc(1, sizeof(*r->anchor))) == NULL)
+			err(1, "pfctl_append_rule: calloc");
+
+		pf_init_eth_ruleset(&r->anchor->ruleset);
+		r->anchor->ruleset.anchor = r->anchor;
+		if (strlcpy(r->anchor->path, anchor_call,
+		    sizeof(rule->anchor->path)) >= sizeof(rule->anchor->path))
+			errx(1, "pfctl_append_rule: strlcpy");
+		if ((p = strrchr(anchor_call, '/')) != NULL) {
+			if (!strlen(p))
+				err(1, "pfctl_append_eth_rule: bad anchor name %s",
+				    anchor_call);
+		} else
+			p = (char *)anchor_call;
+		if (strlcpy(r->anchor->name, p,
+		    sizeof(rule->anchor->name)) >= sizeof(rule->anchor->name))
+			errx(1, "pfctl_append_eth_rule: strlcpy");
+	}
+
+	if ((rule = calloc(1, sizeof(*rule))) == NULL)
+		err(1, "calloc");
+	bcopy(r, rule, sizeof(*rule));
+
+	TAILQ_INSERT_TAIL(&rs->rules, rule, entries);
+	return (0);
+}
+
+int
+pfctl_eth_ruleset_trans(struct pfctl *pf, char *path,
+    struct pfctl_eth_anchor *a)
 {
 	int osize = pf->trans->pfrb_size;
 
 	if ((pf->loadopt & PFCTL_FLAG_ETH) != 0) {
-		if (! path[0]) {
-			if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path))
-				return (1);
-		}
+		if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path))
+			return (1);
+	}
+	if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize))
+		return (5);
+
+	return (0);
+}
+
+int
+pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_anchor *a, bool do_eth)
+{
+	int osize = pf->trans->pfrb_size;
+
+	if ((pf->loadopt & PFCTL_FLAG_ETH) != 0 && do_eth) {
+		if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path))
+			return (1);
 	}
 	if ((pf->loadopt & PFCTL_FLAG_NAT) != 0) {
 		if (pfctl_add_trans(pf->trans, PF_RULESET_NAT, path) ||
@@ -1544,22 +1640,92 @@ pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_anchor *a)
 }
 
 int
-pfctl_load_eth_ruleset(struct pfctl *pf)
+pfctl_load_eth_ruleset(struct pfctl *pf, char *path,
+    struct pfctl_eth_ruleset *rs, int depth)
 {
 	struct pfctl_eth_rule	*r;
-	int	error;
+	int	error, len = strlen(path);
+	int	brace = 0;
 
-	while ((r = TAILQ_FIRST(&pf->eth_rules)) != NULL) {
-		TAILQ_REMOVE(&pf->eth_rules, r, entries);
+	pf->eanchor = rs->anchor;
+	if (path[0])
+		snprintf(&path[len], MAXPATHLEN - len, "/%s", pf->eanchor->name);
+	else
+		snprintf(&path[len], MAXPATHLEN - len, "%s", pf->eanchor->name);
 
-		if ((pf->opts & PF_OPT_NOACTION) == 0) {
-			error = pfctl_add_eth_rule(pf->dev, r, pf->eth_ticket);
-			if (error)
+	if (depth) {
+		if (TAILQ_FIRST(&rs->rules) != NULL) {
+			brace++;
+			if (pf->opts & PF_OPT_VERBOSE)
+				printf(" {\n");
+			if ((pf->opts & PF_OPT_NOACTION) == 0 &&
+			    (error = pfctl_eth_ruleset_trans(pf,
+			    path, rs->anchor))) {
+				printf("pfctl_load_eth_rulesets: "
+				    "pfctl_eth_ruleset_trans %d\n", error);
+				goto error;
+			}
+		} else if (pf->opts & PF_OPT_VERBOSE)
+			printf("\n");
+	}
+
+	while ((r = TAILQ_FIRST(&rs->rules)) != NULL) {
+		TAILQ_REMOVE(&rs->rules, r, entries);
+
+		error = pfctl_load_eth_rule(pf, path, r, depth);
+		if (error)
+			return (error);
+
+		if (r->anchor) {
+			if ((error = pfctl_load_eth_ruleset(pf, path,
+			    &r->anchor->ruleset, depth + 1)))
 				return (error);
 		}
-
 		free(r);
 	}
+	if (brace && pf->opts & PF_OPT_VERBOSE) {
+		INDENT(depth - 1, (pf->opts & PF_OPT_VERBOSE));
+		printf("}\n");
+	}
+	path[len] = '\0';
+
+	return (0);
+error:
+	path[len] = '\0';
+	return (error);
+}
+
+int
+pfctl_load_eth_rule(struct pfctl *pf, char *path, struct pfctl_eth_rule *r,
+    int depth)
+{
+	char			*name;
+	char			anchor[PF_ANCHOR_NAME_SIZE];
+	int			len = strlen(path);
+
+	if (strlcpy(anchor, path, sizeof(anchor)) >= sizeof(anchor))
+		errx(1, "pfctl_load_eth_rule: strlcpy");
+
+	if (r->anchor) {
+		if (r->anchor->match) {
+			if (path[0])
+				snprintf(&path[len], MAXPATHLEN - len,
+				    "/%s", r->anchor->name);
+			else
+				snprintf(&path[len], MAXPATHLEN - len,
+				    "%s", r->anchor->name);
+			name = r->anchor->name;
+		} else
+			name = r->anchor->path;
+	} else
+		name = "";
+
+	if ((pf->opts & PF_OPT_NOACTION) == 0)
+		if (pfctl_add_eth_rule(pf->dev, r, anchor, name,
+		    pf->eth_ticket))
+			err(1, "DIOCADDETHRULENV");
+
+	path[len] = '\0';
 
 	return (0);
 }
@@ -1586,7 +1752,7 @@ pfctl_load_ruleset(struct pfctl *pf, char *path, struct pfctl_ruleset *rs,
 				printf(" {\n");
 			if ((pf->opts & PF_OPT_NOACTION) == 0 &&
 			    (error = pfctl_ruleset_trans(pf,
-			    path, rs->anchor))) {
+			    path, rs->anchor, false))) {
 				printf("pfctl_load_rulesets: "
 				    "pfctl_ruleset_trans %d\n", error);
 				goto error;
@@ -1711,6 +1877,7 @@ pfctl_rules(int dev, char *filename, int opts, int optimize,
 	struct pfioc_altq	 pa;
 	struct pfctl		 pf;
 	struct pfctl_ruleset	*rs;
+	struct pfctl_eth_ruleset	*ethrs;
 	struct pfr_table	 trs;
 	char			*path;
 	int			 osize;
@@ -1719,6 +1886,11 @@ pfctl_rules(int dev, char *filename, int opts, int optimize,
 	memset(&pf_main_anchor, 0, sizeof(pf_main_anchor));
 	pf_init_ruleset(&pf_main_anchor.ruleset);
 	pf_main_anchor.ruleset.anchor = &pf_main_anchor;
+
+	memset(&pf_eth_main_anchor, 0, sizeof(pf_eth_main_anchor));
+	pf_init_eth_ruleset(&pf_eth_main_anchor.ruleset);
+	pf_eth_main_anchor.ruleset.anchor = &pf_eth_main_anchor;
+
 	if (trans == NULL) {
 		bzero(&buf, sizeof(buf));
 		buf.pfrb_type = PFRB_TRANS;
@@ -1742,7 +1914,6 @@ pfctl_rules(int dev, char *filename, int opts, int optimize,
 	pf.opts = opts;
 	pf.optimize = optimize;
 	pf.loadopt = loadopt;
-	TAILQ_INIT(&pf.eth_rules);
 
 	/* non-brace anchor, create without resolving the path */
 	if ((pf.anchor = calloc(1, sizeof(*pf.anchor))) == NULL)
@@ -1752,10 +1923,10 @@ pfctl_rules(int dev, char *filename, int opts, int optimize,
 	rs->anchor = pf.anchor;
 	if (strlcpy(pf.anchor->path, anchorname,
 	    sizeof(pf.anchor->path)) >= sizeof(pf.anchor->path))
-		errx(1, "pfctl_add_rule: strlcpy");
+		errx(1, "pfctl_rules: strlcpy");
 	if (strlcpy(pf.anchor->name, anchorname,
 	    sizeof(pf.anchor->name)) >= sizeof(pf.anchor->name))
-		errx(1, "pfctl_add_rule: strlcpy");
+		errx(1, "pfctl_rules: strlcpy");
 
 
 	pf.astack[0] = pf.anchor;
@@ -1766,13 +1937,29 @@ pfctl_rules(int dev, char *filename, int opts, int optimize,
 	pf.trans = t;
 	pfctl_init_options(&pf);
 
+	/* Set up ethernet anchor */
+	if ((pf.eanchor = calloc(1, sizeof(*pf.eanchor))) == NULL)
+		ERRX("pfctl_rules: calloc");
+
+	if (strlcpy(pf.eanchor->path, anchorname,
+	    sizeof(pf.eanchor->path)) >= sizeof(pf.eanchor->path))
+		errx(1, "pfctl_rules: strlcpy");
+	if (strlcpy(pf.eanchor->name, anchorname,
+	    sizeof(pf.eanchor->name)) >= sizeof(pf.eanchor->name))
+		errx(1, "pfctl_rules: strlcpy");
+
+	ethrs = &pf.eanchor->ruleset;
+	pf_init_eth_ruleset(ethrs);
+	ethrs->anchor = pf.eanchor;
+	pf.eastack[0] = pf.eanchor;
+
 	if ((opts & PF_OPT_NOACTION) == 0) {
 		/*
 		 * XXX For the time being we need to open transactions for
 		 * the main ruleset before parsing, because tables are still
 		 * loaded at parse time.
 		 */
-		if (pfctl_ruleset_trans(&pf, anchorname, pf.anchor))
+		if (pfctl_ruleset_trans(&pf, anchorname, pf.anchor, true))
 			ERRX("pfctl_rules");
 		if (pf.loadopt & PFCTL_FLAG_ETH)
 			pf.eth_ticket = pfctl_get_ticket(t, PF_RULESET_ETH, anchorname);
@@ -1797,7 +1984,7 @@ pfctl_rules(int dev, char *filename, int opts, int optimize,
 	if ((pf.loadopt & PFCTL_FLAG_FILTER &&
 	    (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_SCRUB, 0))) ||
 	    (pf.loadopt & PFCTL_FLAG_ETH &&
-	    (pfctl_load_eth_ruleset(&pf))) ||
+	    (pfctl_load_eth_ruleset(&pf, path, ethrs, 0))) ||
 	    (pf.loadopt & PFCTL_FLAG_NAT &&
 	    (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_NAT, 0) ||
 	    pfctl_load_ruleset(&pf, path, rs, PF_RULESET_RDR, 0) ||
@@ -2572,7 +2759,7 @@ main(int argc, char *argv[])
 		    sizeof(anchorname)) >= sizeof(anchorname))
 			errx(1, "anchor name '%s' too long",
 			    anchoropt);
-		loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE;
+		loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE|PFCTL_FLAG_ETH;
 	}
 
 	if ((opts & PF_OPT_NOACTION) == 0) {
@@ -2640,13 +2827,13 @@ main(int argc, char *argv[])
 			pfctl_show_limits(dev, opts);
 			break;
 		case 'e':
-			pfctl_show_eth_rules(dev, opts, 0);
+			pfctl_show_eth_rules(dev, path, opts, 0, anchorname, 0);
 			break;
 		case 'a':
 			opts |= PF_OPT_SHOWALL;
 			pfctl_load_fingerprints(dev, opts);
 
-			pfctl_show_eth_rules(dev, opts, 0);
+			pfctl_show_eth_rules(dev, path, opts, 0, anchorname, 0);
 
 			pfctl_show_nat(dev, opts, anchorname);
 			pfctl_show_rules(dev, path, opts, 0, anchorname, 0);
@@ -2674,7 +2861,8 @@ main(int argc, char *argv[])
 	}
*** 1272 LINES SKIPPED ***