Hello,

below is a complete change I'd like to commit. Diff below enables pfctl to
recursively flush objects (tables, rules, anchors) from PF.  The same change
has been discussed last spring [1]. But the discussion died and all effort
dropped down on the floor. I'd like to restart the effort now.

The idea is to enable "pfctl -a '*' -F[art]" flush all objects recursively.
Change below does not update manpage yet. I'd like to fine tune manpage changes
in follow up commit. The idea is to make current implementation of "pfctl -a
'*' -sr" more generic. My change turns that to generic function, which can walk
tree of anchors and apply desired operation (show/flush) on every node found.

I'm going to change partial diffs just for reference, to make review bit
easier.  However I'd like to commit a big diff in one go, because partial
diffs were never tested.

Also kn@ was testing that change back in 2019 and found some glitches [2].
IMO the issues pointed out by kn@ are present in code already, diff below
just uncovers them. We can investigate/fix them once the change below will
be in.

I'll send partial diffs as a reply to this email to keep things organized.

thanks and
regards
sashan

[1] https://marc.info/?l=openbsd-tech&m=155545759616085&w=2

[2] https://marc.info/?l=openbsd-tech&m=155769978704563&w=2

--------8<---------------8<---------------8<------------------8<--------
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index d1a309d919f..4a6bdc588a4 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -51,8 +51,9 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-
 #include <syslog.h>
+#include <stdarg.h>
+#include <libgen.h>
 
 #include "pfctl_parser.h"
 #include "pfctl.h"
@@ -63,7 +64,7 @@ int    pfctl_disable(int, int);
 void    pfctl_clear_queues(struct pf_qihead *);
 void    pfctl_clear_stats(int, const char *, int);
 void    pfctl_clear_interface_flags(int, int);
-void    pfctl_clear_rules(int, int, char *);
+int     pfctl_clear_rules(int, int, char *);
 void    pfctl_clear_src_nodes(int, int);
 void    pfctl_clear_states(int, const char *, int);
 struct addrinfo *
@@ -106,6 +107,17 @@ const char *pfctl_lookup_option(char *, const char **);
 void   pfctl_state_store(int, const char *);
 void   pfctl_state_load(int, const char *);
 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 *, void *,
+    int(*)(int, struct pfioc_ruleset *, void *));
+struct pfr_anchors *
+       pfctl_get_anchors(int, const char *, int);
+int    pfctl_recurse(int, int, const 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 *);
 
 const char     *clearopt;
 char           *rulesopt;
@@ -125,6 +137,7 @@ char                *state_kill[2];
 int             dev = -1;
 int             first_title = 1;
 int             labels = 0;
+int             exit_val = 0;
 
 #define INDENT(d, o)   do {                                            \
                                if (o) {                                \
@@ -234,7 +247,6 @@ static const char *optiopt_list[] = {
 struct pf_qihead qspecs = TAILQ_HEAD_INITIALIZER(qspecs);
 struct pf_qihead rootqs = TAILQ_HEAD_INITIALIZER(rootqs);
 
-
 __dead void
 usage(void)
 {
@@ -251,6 +263,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;
+}
+
 int
 pfctl_enable(int dev, int opts)
 {
@@ -289,10 +335,10 @@ pfctl_clear_stats(int dev, const char *iface, int opts)
        memset(&pi, 0, sizeof(pi));
        if (iface != NULL && strlcpy(pi.pfiio_name, iface,
            sizeof(pi.pfiio_name)) >= sizeof(pi.pfiio_name))
-               errx(1, "invalid interface: %s", iface);
+               pfctl_errx(opts, 1, "invalid interface: %s", iface);
 
        if (ioctl(dev, DIOCCLRSTATUS, &pi) == -1)
-               err(1, "DIOCCLRSTATUS");
+               pfctl_err(opts, 1, "DIOCCLRSTATUS");
        if ((opts & PF_OPT_QUIET) == 0) {
                fprintf(stderr, "pf: statistics cleared");
                if (iface != NULL)
@@ -311,32 +357,35 @@ pfctl_clear_interface_flags(int dev, int opts)
                pi.pfiio_flags = PFI_IFLAG_SKIP;
 
                if (ioctl(dev, DIOCCLRIFFLAG, &pi) == -1)
-                       err(1, "DIOCCLRIFFLAG");
+                       pfctl_err(opts, 1, "DIOCCLRIFFLAG");
                if ((opts & PF_OPT_QUIET) == 0)
                        fprintf(stderr, "pf: interface flags reset\n");
        }
 }
 
-void
+int
 pfctl_clear_rules(int dev, int opts, char *anchorname)
 {
-       struct pfr_buffer t;
+       struct pfr_buffer       t;
 
        memset(&t, 0, sizeof(t));
        t.pfrb_type = PFRB_TRANS;
        if (pfctl_add_trans(&t, PF_TRANS_RULESET, anchorname) ||
            pfctl_trans(dev, &t, DIOCXBEGIN, 0) ||
-           pfctl_trans(dev, &t, DIOCXCOMMIT, 0))
-               err(1, "pfctl_clear_rules");
-       if ((opts & PF_OPT_QUIET) == 0)
+           pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) {
+               pfctl_err(opts, 1, "%s", __func__);
+               return (1);
+       } else if ((opts & PF_OPT_QUIET) == 0)
                fprintf(stderr, "rules cleared\n");
+
+       return (0);
 }
 
 void
 pfctl_clear_src_nodes(int dev, int opts)
 {
        if (ioctl(dev, DIOCCLRSRCNODES) == -1)
-               err(1, "DIOCCLRSRCNODES");
+               pfctl_err(opts, 1, "DIOCCLRSRCNODES");
        if ((opts & PF_OPT_QUIET) == 0)
                fprintf(stderr, "source tracking entries cleared\n");
 }
@@ -349,10 +398,10 @@ pfctl_clear_states(int dev, const char *iface, int opts)
        memset(&psk, 0, sizeof(psk));
        if (iface != NULL && strlcpy(psk.psk_ifname, iface,
            sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname))
-               errx(1, "invalid interface: %s", iface);
+               pfctl_errx(opts, 1, "invalid interface: %s", iface);
 
        if (ioctl(dev, DIOCCLRSTATES, &psk) == -1)
-               err(1, "DIOCCLRSTATES");
+               pfctl_err(opts, 1, "DIOCCLRSTATES");
        if ((opts & PF_OPT_QUIET) == 0)
                fprintf(stderr, "%d states cleared\n", psk.psk_killed);
 }
@@ -2109,13 +2158,56 @@ pfctl_debug(int dev, u_int32_t level, 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;
+       unsigned int len;
+       struct pfr_anchors      *anchors;
+
+       anchors = (struct pfr_anchors *) warg;
+
+       pfra = malloc(sizeof(*pfra));
+       if (pfra == NULL)
+               err(1, "%s", __func__);
+
+       len = strlen(pr->path) + 1;
+       len += strlen(pr->name) + 1;
+       pfra->pfra_anchorname = malloc(len);
+       if (pfra->pfra_anchorname == NULL)
+               err(1, "%s", __func__);
+
+       if (pr->path[0])
+               snprintf(pfra->pfra_anchorname, len, "%s/%s",
+                   pr->path, pr->name);
+       else
+               snprintf(pfra->pfra_anchorname, len, "%s", pr->name);
+
+       SLIST_INSERT_HEAD(anchors, pfra, pfra_sle);
+
+       return (0);
+}
+
+int
+pfctl_walk_anchors(int dev, int opts, const char *anchorname, void *warg,
+    int(walkf)(int, struct pfioc_ruleset *, void *))
 {
        struct pfioc_ruleset     pr;
        u_int32_t                mnr, nr;
 
        memset(&pr, 0, sizeof(pr));
-       memcpy(pr.path, anchorname, sizeof(pr.path));
+       strlcpy(pr.path, anchorname, sizeof(pr.path));
        if (ioctl(dev, DIOCGETRULESETS, &pr) == -1) {
                if (errno == EINVAL)
                        fprintf(stderr, "Anchor '%s' not found.\n",
@@ -2134,19 +2226,114 @@ pfctl_show_anchors(int dev, int opts, char *anchorname)
                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 (opts & PF_OPT_VERBOSE) {
+                       char    sub[PATH_MAX];
+
+                       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, warg, walkf))
+                               return (-1);
+               }
        }
        return (0);
 }
 
+int
+pfctl_show_anchors(int dev, int opts, char *anchorname)
+{
+       return (
+           pfctl_walk_anchors(dev, opts, anchorname, NULL, pfctl_walk_show));
+}
+
+struct pfr_anchors *
+pfctl_get_anchors(int dev, const char *anchorname, int opts)
+{
+       struct pfioc_ruleset    pr;
+       static struct pfr_anchors anchors;
+       char *n;
+
+       SLIST_INIT(&anchors);
+
+       memset(&pr, 0, sizeof(pr));
+       if (*anchorname != '\0') {
+               n = dirname(anchorname);
+               if (n[0] != '.' && n[1] != '\0')
+                       strlcpy(pr.path, n, sizeof(pr.path));
+               n = basename(anchorname);
+               if (n != NULL)
+                       strlcpy(pr.name, n, sizeof(pr.name));
+       }
+
+       /* insert a root anchor first. */
+       pfctl_walk_get(opts, &pr, &anchors);
+
+       opts |= PF_OPT_VERBOSE;
+       if (pfctl_walk_anchors(dev, opts, anchorname, &anchors, pfctl_walk_get))
+               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)
+{
+       return ((pfctl_clear_tables(pfra->pfra_anchorname, opts) == -1) ?
+           1 : 0);
+}
+
+int
+pfctl_call_clearrules(int dev, int opts, struct pfr_anchoritem *pfra)
+{
+       return (pfctl_clear_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_clear_rules(dev, opts, pfra->pfra_anchorname);
+
+       return (rv);
+}
+
+int
+pfctl_recurse(int dev, int opts, const 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);
+       /*
+        * don't let pfctl_clear_*() function to fail with exit
+        */
+       opts |= PF_OPT_IGNFAIL | PF_OPT_QUIET;
+       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);
+}
+
 const char *
 pfctl_lookup_option(char *cmd, const char **list)
 {
@@ -2276,7 +2463,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;
@@ -2497,7 +2683,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, PATH_MAX)) == NULL)
                errx(1, "%s: calloc", __func__);
@@ -2574,7 +2760,11 @@ main(int argc, char *argv[])
        if (clearopt != NULL) {
                switch (*clearopt) {
                case 'r':
-                       pfctl_clear_rules(dev, opts, anchorname);
+                       if (opts & PF_OPT_RECURSE)
+                               pfctl_recurse(dev, opts, anchorname,
+                                   pfctl_call_clearrules);
+                       else
+                               pfctl_clear_rules(dev, opts, anchorname);
                        break;
                case 's':
                        pfctl_clear_states(dev, ifaceopt, opts);
@@ -2591,8 +2781,14 @@ main(int argc, char *argv[])
                                usage();
                                /* NOTREACHED */
                        }
-                       pfctl_clear_tables(anchorname, opts);
-                       pfctl_clear_rules(dev, opts, anchorname);
+                       if (opts & PF_OPT_RECURSE)
+                               pfctl_recurse(dev, opts, anchorname,
+                                   pfctl_call_clearanchors);
+                       else {
+                               pfctl_clear_tables(anchorname, opts);
+                               pfctl_clear_rules(dev, opts, anchorname);
+                       }
+
                        if (!*anchorname) {
                                pfctl_clear_states(dev, ifaceopt, opts);
                                pfctl_clear_src_nodes(dev, opts);
@@ -2605,7 +2801,11 @@ main(int argc, char *argv[])
                        pfctl_clear_fingerprints(dev, opts);
                        break;
                case 'T':
-                       pfctl_clear_tables(anchorname, opts);
+                       if ((opts & PF_OPT_RECURSE) == 0)
+                               pfctl_clear_tables(anchorname, opts);
+                       else
+                               pfctl_recurse(dev, opts, anchorname,
+                                   pfctl_call_cleartables);
                        break;
                case 'R':
                        pfctl_reset(dev, opts);
@@ -2627,7 +2827,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;
        }
@@ -2649,18 +2849,18 @@ main(int argc, char *argv[])
        if (rulesopt != NULL && !anchorname[0]) {
                pfctl_clear_interface_flags(dev, opts | PF_OPT_QUIET);
                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) {
                if ((level = string_to_loglevel((char *)debugopt)) < 0) {
@@ -2688,5 +2888,5 @@ main(int argc, char *argv[])
        if (lfile != NULL)
                pfctl_state_load(dev, lfile);
 
-       exit(error);
+       exit(exit_val);
 }
diff --git a/sbin/pfctl/pfctl.h b/sbin/pfctl/pfctl.h
index 7981cf66fdb..6cbd456a21b 100644
--- a/sbin/pfctl/pfctl.h
+++ b/sbin/pfctl/pfctl.h
@@ -48,6 +48,14 @@ 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_clr_tables(struct pfr_table *, int *, int);
 int     pfr_add_tables(struct pfr_table *, int, int *, int);
@@ -75,7 +83,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_clear_tables(const char *, int);
+int     pfctl_clear_tables(const char *, int);
 void    pfctl_show_tables(const char *, int);
 int     pfctl_table(int, char *[], char *, const char *, char *,
            const char *, int);
@@ -97,4 +105,7 @@ int   pfctl_trans(int, struct pfr_buffer *, u_long, int);
 
 int     pfctl_show_queues(int, const char *, int, 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 79abfd1a7ab..8c0ea24171b 100644
--- a/sbin/pfctl/pfctl_osfp.c
+++ b/sbin/pfctl/pfctl_osfp.c
@@ -260,7 +260,7 @@ void
 pfctl_clear_fingerprints(int dev, int opts)
 {
        if (ioctl(dev, DIOCOSFPFLUSH) == -1)
-               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 be0ccee1897..993b1fe1c77 100644
--- a/sbin/pfctl/pfctl_parser.h
+++ b/sbin/pfctl/pfctl_parser.h
@@ -36,21 +36,22 @@
 
 #define PF_OSFP_FILE           "/etc/pf.os"
 
-#define PF_OPT_DISABLE         0x0001
-#define PF_OPT_ENABLE          0x0002
-#define PF_OPT_VERBOSE         0x0004
-#define PF_OPT_NOACTION                0x0008
-#define PF_OPT_QUIET           0x0010
-#define PF_OPT_CLRRULECTRS     0x0020
-#define PF_OPT_USEDNS          0x0040
-#define PF_OPT_VERBOSE2                0x0080
-#define PF_OPT_DUMMYACTION     0x0100
-#define PF_OPT_DEBUG           0x0200
-#define PF_OPT_SHOWALL         0x0400
-#define PF_OPT_OPTIMIZE                0x0800
-#define PF_OPT_NODNS           0x1000
-#define PF_OPT_RECURSE         0x4000
-#define PF_OPT_PORTNAMES       0x8000
+#define PF_OPT_DISABLE         0x00001
+#define PF_OPT_ENABLE          0x00002
+#define PF_OPT_VERBOSE         0x00004
+#define PF_OPT_NOACTION                0x00008
+#define PF_OPT_QUIET           0x00010
+#define PF_OPT_CLRRULECTRS     0x00020
+#define PF_OPT_USEDNS          0x00040
+#define PF_OPT_VERBOSE2                0x00080
+#define PF_OPT_DUMMYACTION     0x00100
+#define PF_OPT_DEBUG           0x00200
+#define PF_OPT_SHOWALL         0x00400
+#define PF_OPT_OPTIMIZE                0x00800
+#define PF_OPT_NODNS           0x01000
+#define PF_OPT_RECURSE         0x04000
+#define PF_OPT_PORTNAMES       0x08000
+#define PF_OPT_IGNFAIL         0x10000
 
 #define PF_TH_ALL              0xFF
 
diff --git a/sbin/pfctl/pfctl_table.c b/sbin/pfctl/pfctl_table.c
index 9507418644e..6ff9d8c49b0 100644
--- a/sbin/pfctl/pfctl_table.c
+++ b/sbin/pfctl/pfctl_table.c
@@ -77,7 +77,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)
@@ -101,11 +102,17 @@ static const char *istats_text[2][2][2] = {
                table.pfrt_flags &= ~PFR_TFLAG_PERSIST;                 \
        } while(0)
 
-void
+int
 pfctl_clear_tables(const char *anchor, int opts)
 {
-       if (pfctl_table(0, NULL, NULL, "-F", NULL, anchor, opts) == -1)
-               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

Reply via email to