git: 041ce1d690f1 - main - pfctl: recursively flush rules and tables

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Wed, 09 Jul 2025 08:59:12 UTC
The branch main has been updated by kp:

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

commit 041ce1d690f1c58bd4a93ab808ded53444af2092
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2025-07-04 21:34:48 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2025-07-09 08:57:50 +0000

    pfctl: recursively flush rules and tables
    
    The recursive operation ("pfctl -a '*' ...") works for '-s' option already. This
    change enables the same thing for '-F' option, so "pfctl -a '*' -Fa" will
    flush everything from PF driver.
    
    The idea was discussed with many on tech@ in spring 2019.
    
    OK kn@
    
    Obtained from:  OpenBSD, sashan <sashan@openbsd.org>, ae711728d4
    Obtained from:  OpenBSD, sashan <sashan@openbsd.org>, 7abd52e24a
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
---
 sbin/pfctl/pfctl.8        |   9 +-
 sbin/pfctl/pfctl.c        | 289 +++++++++++++++++++++++++++++++++++++++-------
 sbin/pfctl/pfctl.h        |  12 +-
 sbin/pfctl/pfctl_osfp.c   |   2 +-
 sbin/pfctl/pfctl_parser.h |   1 +
 sbin/pfctl/pfctl_table.c  |  15 ++-
 6 files changed, 277 insertions(+), 51 deletions(-)

diff --git a/sbin/pfctl/pfctl.8 b/sbin/pfctl/pfctl.8
index 28efff896956..2566bca57a28 100644
--- a/sbin/pfctl/pfctl.8
+++ b/sbin/pfctl/pfctl.8
@@ -24,7 +24,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd July 2, 2025
+.Dd July 4, 2025
 .Dt PFCTL 8
 .Os
 .Sh NAME
@@ -186,6 +186,13 @@ as the anchor name:
 .Bd -literal -offset indent
 # pfctl -a '*' -sr
 .Ed
+.Pp
+To flush all rulesets and tables recursively, specify only
+.Sq *
+as the anchor name:
+.Bd -literal -offset indent
+# pfctl -a '*' -Fa
+.Ed
 .It Fl D Ar macro Ns = Ns Ar value
 Define
 .Ar macro
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index 8c6d6833dd3b..254bd054ae4a 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -59,6 +59,8 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <stdarg.h>
+#include <libgen.h>
 
 #include "pfctl_parser.h"
 #include "pfctl.h"
@@ -72,7 +74,7 @@ void	 pfctl_check_skip_ifaces(char *);
 void	 pfctl_adjust_skip_ifaces(struct pfctl *);
 void	 pfctl_clear_interface_flags(int, int);
 void	 pfctl_flush_eth_rules(int, int, char *);
-void	 pfctl_flush_rules(int, int, char *);
+int	 pfctl_flush_rules(int, int, char *);
 void	 pfctl_flush_nat(int, int, char *);
 int	 pfctl_clear_altq(int, int);
 void	 pfctl_clear_src_nodes(int, int);
@@ -124,6 +126,17 @@ int	 pfctl_load_ruleset(struct pfctl *, char *,
 int	 pfctl_load_rule(struct pfctl *, char *, struct pfctl_rule *, int);
 const char	*pfctl_lookup_option(char *, const char * const *);
 void	 pfctl_reset(int, int);
+int	 pfctl_walk_show(int, struct pfioc_ruleset *, void *);
+int	 pfctl_walk_get(int, struct pfioc_ruleset *, void *);
+int	 pfctl_walk_anchors(int, int, const char *,
+	    int(*)(int, struct pfioc_ruleset *, void *), void *);
+struct pfr_anchors *
+	 pfctl_get_anchors(int, char *, int);
+int	 pfctl_recurse(int, int, char *,
+	    int(*)(int, int, struct pfr_anchoritem *));
+int	 pfctl_call_clearrules(int, int, struct pfr_anchoritem *);
+int	 pfctl_call_cleartables(int, int, struct pfr_anchoritem *);
+int	 pfctl_call_clearanchors(int, int, struct pfr_anchoritem *);
 
 static struct pfctl_anchor_global	 pf_anchors;
 struct pfctl_anchor	 pf_main_anchor;
@@ -151,6 +164,7 @@ int			 dev = -1;
 struct pfctl_handle	*pfh = NULL;
 static int		 first_title = 1;
 static int		 labels = 0;
+static int		 exit_val = 0;
 
 #define INDENT(d, o)	do {						\
 				if (o) {				\
@@ -269,6 +283,40 @@ usage(void)
 	exit(1);
 }
 
+void
+pfctl_err(int opts, int eval, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+
+	if ((opts & PF_OPT_IGNFAIL) == 0)
+		verr(eval, fmt, ap);
+	else
+		vwarn(fmt, ap);
+
+	va_end(ap);
+
+	exit_val = eval;
+}
+
+void
+pfctl_errx(int opts, int eval, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+
+	if ((opts & PF_OPT_IGNFAIL) == 0)
+		verrx(eval, fmt, ap);
+	else
+		vwarnx(fmt, ap);
+
+	va_end(ap);
+
+	exit_val = eval;
+}
+
 /*
  * Cache protocol number to name translations.
  *
@@ -361,7 +409,7 @@ pfctl_clear_stats(struct pfctl_handle *h, int opts)
 {
 	int ret;
 	if ((ret = pfctl_clear_status(h)) != 0)
-		errc(1, ret, "DIOCCLRSTATUS");
+		pfctl_err(opts, 1, "DIOCCLRSTATUS");
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "pf: statistics cleared\n");
 }
@@ -469,16 +517,19 @@ pfctl_flush_eth_rules(int dev, int opts, char *anchorname)
 		fprintf(stderr, "Ethernet rules cleared\n");
 }
 
-void
+int
 pfctl_flush_rules(int dev, int opts, char *anchorname)
 {
 	int ret;
 
 	ret = pfctl_clear_rules(dev, anchorname);
-	if (ret != 0)
-		err(1, "pfctl_clear_rules");
-	if ((opts & PF_OPT_QUIET) == 0)
+	if (ret != 0) {
+		pfctl_err(opts, 1, "%s", __func__);
+		return (1);
+	} else if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "rules cleared\n");
+
+	return (0);
 }
 
 void
@@ -515,7 +566,7 @@ void
 pfctl_clear_src_nodes(int dev, int opts)
 {
 	if (ioctl(dev, DIOCCLRSRCNODES))
-		err(1, "DIOCCLRSRCNODES");
+		pfctl_err(opts, 1, "DIOCCLRSRCNODES");
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "source tracking entries cleared\n");
 }
@@ -530,13 +581,13 @@ pfctl_clear_iface_states(int dev, const char *iface, int opts)
 	memset(&kill, 0, sizeof(kill));
 	if (iface != NULL && strlcpy(kill.ifname, iface,
 	    sizeof(kill.ifname)) >= sizeof(kill.ifname))
-		errx(1, "invalid interface: %s", iface);
+		pfctl_errx(opts, 1, "invalid interface: %s", iface);
 
 	if (opts & PF_OPT_KILLMATCH)
 		kill.kill_match = true;
 
 	if ((ret = pfctl_clear_states_h(pfh, &kill, &killed)) != 0)
-		errc(1, ret, "DIOCCLRSTATES");
+		pfctl_err(opts, 1, "DIOCCLRSTATUS");
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "%d states cleared\n", killed);
 }
@@ -687,7 +738,7 @@ pfctl_net_kill_states(int dev, const char *iface, int opts)
 	memset(&last_dst, 0xff, sizeof(last_dst));
 	if (iface != NULL && strlcpy(kill.ifname, iface,
 	    sizeof(kill.ifname)) >= sizeof(kill.ifname))
-		errx(1, "invalid interface: %s", iface);
+		pfctl_errx(opts, 1, "invalid interface: %s", iface);
 
 	if (state_killers == 2 && (strcmp(state_kill[0], "nat") == 0)) {
 		kill.nat = true;
@@ -740,13 +791,13 @@ pfctl_net_kill_states(int dev, const char *iface, int opts)
 				    resp[1]->ai_addr);
 
 				if ((ret = pfctl_kill_states_h(pfh, &kill, &newkilled)) != 0)
-					errc(1, ret, "DIOCKILLSTATES");
+					pfctl_errx(opts, 1, "DIOCKILLSTATES");
 				killed += newkilled;
 			}
 			freeaddrinfo(res[1]);
 		} else {
 			if ((ret = pfctl_kill_states_h(pfh, &kill, &newkilled)) != 0)
-				errc(1, ret, "DIOCKILLSTATES");
+				pfctl_errx(opts, 1, "DIOCKILLSTATES");
 			killed += newkilled;
 		}
 	}
@@ -778,7 +829,7 @@ pfctl_gateway_kill_states(int dev, const char *iface, int opts)
 	memset(&last_src, 0xff, sizeof(last_src));
 	if (iface != NULL && strlcpy(kill.ifname, iface,
 	    sizeof(kill.ifname)) >= sizeof(kill.ifname))
-		errx(1, "invalid interface: %s", iface);
+		pfctl_errx(opts, 1, "invalid interface: %s", iface);
 
 	if (opts & PF_OPT_KILLMATCH)
 		kill.kill_match = true;
@@ -799,7 +850,7 @@ pfctl_gateway_kill_states(int dev, const char *iface, int opts)
 		copy_satopfaddr(&kill.rt_addr.addr.v.a.addr,
 		    resp->ai_addr);
 		if (pfctl_kill_states_h(pfh, &kill, &newkilled))
-			err(1, "DIOCKILLSTATES");
+			pfctl_errx(opts, 1, "DIOCKILLSTATES");
 		killed += newkilled;
 	}
 
@@ -823,7 +874,7 @@ pfctl_label_kill_states(int dev, const char *iface, int opts)
 	memset(&kill, 0, sizeof(kill));
 	if (iface != NULL && strlcpy(kill.ifname, iface,
 	    sizeof(kill.ifname)) >= sizeof(kill.ifname))
-		errx(1, "invalid interface: %s", iface);
+		pfctl_errx(opts, 1, "invalid interface: %s", iface);
 
 	if (opts & PF_OPT_KILLMATCH)
 		kill.kill_match = true;
@@ -833,7 +884,7 @@ pfctl_label_kill_states(int dev, const char *iface, int opts)
 		errx(1, "label too long: %s", state_kill[1]);
 
 	if ((ret = pfctl_kill_states_h(pfh, &kill, &killed)) != 0)
-		errc(1, ret, "DIOCKILLSTATES");
+		pfctl_errx(opts, 1, "DIOCKILLSTATES");
 
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "killed %d states\n", killed);
@@ -871,7 +922,7 @@ pfctl_id_kill_states(int dev, const char *iface, int opts)
 	}
 
 	if ((ret = pfctl_kill_states_h(pfh, &kill, &killed)) != 0)
-		errc(1, ret, "DIOCKILLSTATES");
+		pfctl_errx(opts, 1, "DIOCKILLSTATES");
 
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "killed %d states\n", killed);
@@ -895,7 +946,7 @@ pfctl_key_kill_states(int dev, const char *iface, int opts)
 	if (iface != NULL &&
 	    strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >=
 	    sizeof(kill.ifname))
-		errx(1, "invalid interface: %s", iface);
+		pfctl_errx(opts, 1, "invalid interface: %s", iface);
 
 	s = strdup(state_kill[1]);
 	if (!s)
@@ -931,7 +982,7 @@ pfctl_key_kill_states(int dev, const char *iface, int opts)
 		errx(1, "invalid host: %s", tokens[didx]);
 
 	if ((ret = pfctl_kill_states_h(pfh, &kill, &killed)) != 0)
-		errc(1, ret, "DIOCKILLSTATES");
+		pfctl_errx(opts, 1, "DIOCKILLSTATES");
 
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "killed %d states\n", killed);
@@ -2805,7 +2856,7 @@ pfctl_set_interface_flags(struct pfctl *pf, char *ifname, int flags, int how)
 	if ((pf->opts & PF_OPT_NOACTION) == 0) {
 		if (how == 0) {
 			if (ioctl(pf->dev, DIOCCLRIFFLAG, &pi))
-				err(1, "DIOCCLRIFFLAG");
+				pfctl_err(pf->opts, 1, "DIOCCLRIFFLAG");
 		} else {
 			if (ioctl(pf->dev, DIOCSETIFFLAG, &pi))
 				err(1, "DIOCSETIFFLAG");
@@ -2864,17 +2915,56 @@ pfctl_test_altqsupport(int dev, int opts)
 }
 
 int
-pfctl_show_anchors(int dev, int opts, char *anchorname)
+pfctl_walk_show(int opts, struct pfioc_ruleset *pr, void *warg)
+{
+	if (pr->path[0]) {
+		if (pr->path[0] != '_' || (opts & PF_OPT_VERBOSE))
+			printf("  %s/%s\n", pr->path, pr->name);
+	} else if (pr->name[0] != '_' || (opts & PF_OPT_VERBOSE))
+		printf("  %s\n", pr->name);
+
+	return (0);
+}
+
+int
+pfctl_walk_get(int opts, struct pfioc_ruleset *pr, void *warg)
+{
+	struct pfr_anchoritem *pfra;
+	struct pfr_anchors *anchors;
+	int e;
+
+	anchors = (struct pfr_anchors *)warg;
+
+	pfra = malloc(sizeof(*pfra));
+	if (pfra == NULL)
+		err(1, "%s", __func__);
+
+	if (pr->path[0])
+		e = asprintf(&pfra->pfra_anchorname, "%s/%s", pr->path,
+		    pr->name);
+	else
+		e = asprintf(&pfra->pfra_anchorname, "%s", pr->name);
+
+	if (e == -1)
+		err(1, "%s", __func__);
+
+	SLIST_INSERT_HEAD(anchors, pfra, pfra_sle);
+
+	return (0);
+}
+
+int
+pfctl_walk_anchors(int dev, int opts, const char *anchor,
+    int(walkf)(int, struct pfioc_ruleset *, void *), void *warg)
 {
 	struct pfioc_ruleset	 pr;
 	u_int32_t		 mnr, nr;
 	int			 ret;
 
 	memset(&pr, 0, sizeof(pr));
-	if ((ret = pfctl_get_rulesets(pfh, anchorname, &mnr)) != 0) {
+	if ((ret = pfctl_get_rulesets(pfh, anchor, &mnr)) != 0) {
 		if (ret == EINVAL)
-			fprintf(stderr, "Anchor '%s' not found.\n",
-			    anchorname);
+			fprintf(stderr, "Anchor '%s' not found.\n", anchor);
 		else
 			errc(1, ret, "DIOCGETRULESETS");
 		return (-1);
@@ -2882,24 +2972,122 @@ pfctl_show_anchors(int dev, int opts, char *anchorname)
 	for (nr = 0; nr < mnr; ++nr) {
 		char sub[MAXPATHLEN];
 
-		if ((ret = pfctl_get_ruleset(pfh, anchorname, nr, &pr)) != 0)
+		if ((ret = pfctl_get_ruleset(pfh, anchor, nr, &pr)) != 0)
 			errc(1, ret, "DIOCGETRULESET");
 		if (!strcmp(pr.name, PF_RESERVED_ANCHOR))
 			continue;
 		sub[0] = '\0';
-		if (pr.path[0]) {
-			strlcat(sub, pr.path, sizeof(sub));
-			strlcat(sub, "/", sizeof(sub));
-		}
-		strlcat(sub, pr.name, sizeof(sub));
-		if (sub[0] != '_' || (opts & PF_OPT_VERBOSE))
-			printf("  %s\n", sub);
-		if ((opts & PF_OPT_VERBOSE) && pfctl_show_anchors(dev, opts, sub))
+		if (walkf(opts, &pr, warg))
+			return (-1);
+
+		if (pr.path[0])
+			snprintf(sub, sizeof(sub), "%s/%s", pr.path, pr.name);
+		else
+			snprintf(sub, sizeof(sub), "%s", pr.name);
+		if (pfctl_walk_anchors(dev, opts, sub, walkf, warg))
 			return (-1);
 	}
 	return (0);
 }
 
+int
+pfctl_show_anchors(int dev, int opts, char *anchor)
+{
+	return (
+	    pfctl_walk_anchors(dev, opts, anchor, pfctl_walk_show, NULL));
+}
+
+struct pfr_anchors *
+pfctl_get_anchors(int dev, char *anchor, int opts)
+{
+	struct pfioc_ruleset pr;
+	static struct pfr_anchors anchors;
+	char *n;
+
+	SLIST_INIT(&anchors);
+
+	memset(&pr, 0, sizeof(pr));
+	if (*anchor != '\0') {
+		n = dirname(anchor);
+		if (n[0] != '.' && n[1] != '\0')
+			strlcpy(pr.path, n, sizeof(pr.path));
+		n = basename(anchor);
+		if (n != NULL)
+			strlcpy(pr.name, n, sizeof(pr.name));
+	}
+
+	/* insert a root anchor first. */
+	pfctl_walk_get(opts, &pr, &anchors);
+
+	if (pfctl_walk_anchors(dev, opts, anchor, pfctl_walk_get, &anchors))
+		errx(1, "%s failed to retrieve list of anchors, can't continue",
+		    __func__);
+
+	return (&anchors);
+}
+
+int
+pfctl_call_cleartables(int dev, int opts, struct pfr_anchoritem *pfra)
+{
+	/*
+	 * PF_OPT_QUIET makes pfctl_clear_tables() to stop printing number of
+	 * tables cleared for given anchor.
+	 */
+	opts |= PF_OPT_QUIET;
+	return ((pfctl_do_clear_tables(pfra->pfra_anchorname, opts) == -1) ?
+	    1 : 0);
+}
+
+int
+pfctl_call_clearrules(int dev, int opts, struct pfr_anchoritem *pfra)
+{
+	/*
+	 * PF_OPT_QUIET makes pfctl_clear_rules() to stop printing a 'rules
+	 * cleared' message for every anchor it deletes.
+	 */
+	opts |= PF_OPT_QUIET;
+	return (pfctl_flush_rules(dev, opts, pfra->pfra_anchorname));
+}
+
+int
+pfctl_call_clearanchors(int dev, int opts, struct pfr_anchoritem *pfra)
+{
+	int rv = 0;
+
+	rv |= pfctl_call_cleartables(dev, opts, pfra);
+	rv |= pfctl_call_clearrules(dev, opts, pfra);
+
+	return (rv);
+}
+
+int
+pfctl_recurse(int dev, int opts, char *anchorname,
+    int(*walkf)(int, int, struct pfr_anchoritem *))
+{
+	int			 rv = 0;
+	struct pfr_anchors	*anchors;
+	struct pfr_anchoritem	*pfra, *pfra_save;
+
+	anchors = pfctl_get_anchors(dev, anchorname, opts);
+	/*
+	 * While traversing the list, pfctl_clear_*() must always return
+	 * so that failures on one anchor do not prevent clearing others.
+	 */
+	opts |= PF_OPT_IGNFAIL;
+	printf("Removing:\n");
+	SLIST_FOREACH_SAFE(pfra, anchors, pfra_sle, pfra_save) {
+		printf("  %s\n",
+		    (*pfra->pfra_anchorname == '\0') ? "<root>" :
+						       pfra->pfra_anchorname);
+		rv |= walkf(dev, opts, pfra);
+		SLIST_REMOVE(anchors, pfra, pfr_anchoritem, pfra_sle);
+		free(pfra->pfra_anchorname);
+		free(pfra);
+	}
+
+	return (rv);
+}
+
 int
 pfctl_show_eth_anchors(int dev, int opts, char *anchorname)
 {
@@ -2990,7 +3178,6 @@ pfctl_reset(int dev, int opts)
 int
 main(int argc, char *argv[])
 {
-	int	 error = 0;
 	int	 ch;
 	int	 mode = O_RDONLY;
 	int	 opts = 0;
@@ -3218,7 +3405,7 @@ main(int argc, char *argv[])
 
 	if (opts & PF_OPT_DISABLE)
 		if (pfctl_disable(dev, opts))
-			error = 1;
+			exit_val = 1;
 
 	if ((path = calloc(1, MAXPATHLEN)) == NULL)
 		errx(1, "%s: calloc", __func__);
@@ -3259,7 +3446,7 @@ main(int argc, char *argv[])
 			pfctl_show_status(dev, opts);
 			break;
 		case 'R':
-			error = pfctl_show_running(dev);
+			exit_val = pfctl_show_running(dev);
 			break;
 		case 't':
 			pfctl_show_timeouts(dev, opts);
@@ -3321,7 +3508,11 @@ main(int argc, char *argv[])
 			pfctl_flush_eth_rules(dev, opts, anchorname);
 			break;
 		case 'r':
-			pfctl_flush_rules(dev, opts, anchorname);
+			if (opts & PF_OPT_RECURSE)
+				pfctl_recurse(dev, opts, anchorname,
+				    pfctl_call_clearrules);
+			else
+				pfctl_flush_rules(dev, opts, anchorname);
 			break;
 		case 'n':
 			pfctl_flush_nat(dev, opts, anchorname);
@@ -3347,7 +3538,13 @@ main(int argc, char *argv[])
 			pfctl_flush_eth_rules(dev, opts, anchorname);
 			pfctl_flush_rules(dev, opts, anchorname);
 			pfctl_flush_nat(dev, opts, anchorname);
-			pfctl_do_clear_tables(anchorname, opts);
+			if (opts & PF_OPT_RECURSE)
+				pfctl_recurse(dev, opts, anchorname,
+				    pfctl_call_clearanchors);
+			else {
+				pfctl_do_clear_tables(anchorname, opts);
+				pfctl_flush_rules(dev, opts, anchorname);
+			}
 			if (!*anchorname) {
 				pfctl_clear_altq(dev, opts);
 				pfctl_clear_iface_states(dev, ifaceopt, opts);
@@ -3361,7 +3558,11 @@ main(int argc, char *argv[])
 			pfctl_clear_fingerprints(dev, opts);
 			break;
 		case 'T':
-			pfctl_do_clear_tables(anchorname, opts);
+			if ((opts & PF_OPT_RECURSE) == 0)
+				pfctl_do_clear_tables(anchorname, opts);
+			else
+				pfctl_recurse(dev, opts, anchorname,
+				    pfctl_call_cleartables);
 			break;
 		case 'R':
 			pfctl_reset(dev, opts);
@@ -3385,7 +3586,7 @@ main(int argc, char *argv[])
 		pfctl_kill_src_nodes(dev, opts);
 
 	if (tblcmdopt != NULL) {
-		error = pfctl_table(argc, argv, tableopt,
+		exit_val = pfctl_table(argc, argv, tableopt,
 		    tblcmdopt, rulesopt, anchorname, opts);
 		rulesopt = NULL;
 	}
@@ -3411,17 +3612,17 @@ main(int argc, char *argv[])
 	if (rulesopt != NULL && !(opts & PF_OPT_MERGE) &&
 	    !anchorname[0] && (loadopt & PFCTL_FLAG_OPTION))
 		if (pfctl_file_fingerprints(dev, opts, PF_OSFP_FILE))
-			error = 1;
+			exit_val = 1;
 
 	if (rulesopt != NULL) {
 		if (pfctl_rules(dev, rulesopt, opts, optimize,
 		    anchorname, NULL))
-			error = 1;
+			exit_val = 1;
 	}
 
 	if (opts & PF_OPT_ENABLE)
 		if (pfctl_enable(dev, opts))
-			error = 1;
+			exit_val = 1;
 
 	if (debugopt != NULL) {
 		switch (*debugopt) {
@@ -3440,5 +3641,5 @@ main(int argc, char *argv[])
 		}
 	}
 
-	exit(error);
+	exit(exit_val);
 }
diff --git a/sbin/pfctl/pfctl.h b/sbin/pfctl/pfctl.h
index d8196c129187..5b5b3d3e5fff 100644
--- a/sbin/pfctl/pfctl.h
+++ b/sbin/pfctl/pfctl.h
@@ -55,6 +55,13 @@ struct pfr_buffer {
 	    (var) != NULL;				\
 	    (var) = pfr_buf_next((buf), (var)))
 
+struct pfr_anchoritem {
+	SLIST_ENTRY(pfr_anchoritem)	 pfra_sle;
+	char				*pfra_anchorname;
+};
+
+SLIST_HEAD(pfr_anchors, pfr_anchoritem);
+
 int	 pfr_get_fd(void);
 int	 pfr_add_table(struct pfr_table *, int *, int);
 int	 pfr_del_table(struct pfr_table *, int *, int);
@@ -81,7 +88,7 @@ int	 pfi_get_ifaces(const char *, struct pfi_kif *, int *);
 int	 pfi_clr_istats(const char *, int *, int);
 
 void	 pfctl_print_title(char *);
-void	 pfctl_do_clear_tables(const char *, int);
+int	 pfctl_do_clear_tables(const char *, int);
 void	 pfctl_show_tables(const char *, int);
 int	 pfctl_table(int, char *[], char *, const char *, char *,
 	    const char *, int);
@@ -150,4 +157,7 @@ void		 expand_label(char *, size_t, struct pfctl_rule *);
 
 const char *pfctl_proto2name(int);
 
+void	 pfctl_err(int, int, const char *, ...);
+void	 pfctl_errx(int, int, const char *, ...);
+
 #endif /* _PFCTL_H_ */
diff --git a/sbin/pfctl/pfctl_osfp.c b/sbin/pfctl/pfctl_osfp.c
index 3a94c2e8c81b..5770c8343a46 100644
--- a/sbin/pfctl/pfctl_osfp.c
+++ b/sbin/pfctl/pfctl_osfp.c
@@ -264,7 +264,7 @@ void
 pfctl_clear_fingerprints(int dev, int opts)
 {
 	if (ioctl(dev, DIOCOSFPFLUSH))
-		err(1, "DIOCOSFPFLUSH");
+		pfctl_err(opts, 1, "DIOCOSFPFLUSH");
 }
 
 /* flush pfctl's view of the fingerprints */
diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h
index b91d37c791ae..778105c2b96d 100644
--- a/sbin/pfctl/pfctl_parser.h
+++ b/sbin/pfctl/pfctl_parser.h
@@ -55,6 +55,7 @@
 #define PF_OPT_RECURSE		0x04000
 #define PF_OPT_KILLMATCH	0x08000
 #define PF_OPT_NODNS		0x10000
+#define PF_OPT_IGNFAIL		0x20000
 
 #define PF_NAT_PROXY_PORT_LOW	50001
 #define PF_NAT_PROXY_PORT_HIGH	65535
diff --git a/sbin/pfctl/pfctl_table.c b/sbin/pfctl/pfctl_table.c
index 0842b042df41..0b52f88eafbb 100644
--- a/sbin/pfctl/pfctl_table.c
+++ b/sbin/pfctl/pfctl_table.c
@@ -79,7 +79,8 @@ static const char	*istats_text[2][2][2] = {
 		if ((!(opts & PF_OPT_NOACTION) ||	\
 		    (opts & PF_OPT_DUMMYACTION)) &&	\
 		    (fct)) {				\
-			radix_perror();			\
+		if ((opts & PF_OPT_RECURSE) == 0)	\
+				radix_perror();		\
 			goto _error;			\
 		}					\
 	} while (0)
@@ -103,11 +104,17 @@ static const char	*istats_text[2][2][2] = {
 		table.pfrt_flags &= ~PFR_TFLAG_PERSIST;			\
 	} while(0)
 
-void
+int
 pfctl_do_clear_tables(const char *anchor, int opts)
 {
-	if (pfctl_table(0, NULL, NULL, "-F", NULL, anchor, opts))
-		exit(1);
+	int	rv;
+
+	if ((rv = pfctl_table(0, NULL, NULL, "-F", NULL, anchor, opts)) == -1) {
+		if ((opts & PF_OPT_IGNFAIL) == 0)
+			exit(1);
+	}
+
+	return (rv);
 }
 
 void