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