diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c index 8d81248e0229..86117268a9d0 100644 --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -569,3 +569,25 @@ int pfctl_get_clear_rule(int dev, u_int32_t nr, u_int32_t ticket, return (0); } + +int +pfctl_set_keepcounters(int dev, bool keep) +{ + struct pfioc_nv nv; + nvlist_t *nvl; + int ret; + + nvl = nvlist_create(0); + + nvlist_add_bool(nvl, "keep_counters", keep); + + nv.data = nvlist_pack(nvl, &nv.len); + nv.size = nv.len; + + nvlist_destroy(nvl); + + ret = ioctl(dev, DIOCKEEPCOUNTERS, &nv); + + free(nv.data); + return (ret); +} diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h index 32d0184f42ed..2c498189a6e4 100644 --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -188,5 +188,6 @@ int pfctl_get_clear_rule(int dev, u_int32_t nr, u_int32_t ticket, int pfctl_add_rule(int dev, const struct pfctl_rule *r, const char *anchor, const char *anchor_call, u_int32_t ticket, u_int32_t pool_ticket); +int pfctl_set_keepcounters(int dev, bool keep); #endif diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y index 9eac41fbf66f..e0314241eec3 100644 --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -461,7 +461,7 @@ int parseport(char *, struct range *r, int); %token REASSEMBLE FRAGDROP FRAGCROP ANCHOR NATANCHOR RDRANCHOR BINATANCHOR %token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY FAILPOLICY %token RANDOMID REQUIREORDER SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID -%token ANTISPOOF FOR INCLUDE +%token ANTISPOOF FOR INCLUDE KEEPCOUNTERS %token BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT PROBABILITY MAPEPORTSET %token ALTQ CBQ CODEL PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME %token UPPERLIMIT QUEUE PRIORITY QLIMIT HOGS BUCKETS RTABLE TARGET INTERVAL @@ -719,6 +719,9 @@ option : SET OPTIMIZATION STRING { } keep_state_defaults = $3; } + | SET KEEPCOUNTERS { + pf->keep_counters = true; + } ; stringall : STRING { $$ = $1; } @@ -5593,6 +5596,7 @@ lookup(char *s) { "inet6", INET6}, { "interval", INTERVAL}, { "keep", KEEP}, + { "keepcounters", KEEPCOUNTERS}, { "label", LABEL}, { "limit", LIMIT}, { "linkshare", LINKSHARE}, diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c index 9f6b3d2e36ea..82af047e7571 100644 --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -1745,6 +1745,10 @@ pfctl_load_options(struct pfctl *pf) if (pfctl_load_hostid(pf, pf->hostid)) error = 1; + /* load keepcounters */ + if (pfctl_set_keepcounters(pf->dev, pf->keep_counters)) + error = 1; + return (error); } diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h index 43d8488dcab8..0c66d5dda97a 100644 --- a/sbin/pfctl/pfctl_parser.h +++ b/sbin/pfctl/pfctl_parser.h @@ -98,6 +98,7 @@ struct pfctl { u_int32_t debug; u_int32_t hostid; char *ifname; + bool keep_counters; u_int8_t timeout_set[PFTM_MAX]; u_int8_t limit_set[PF_LIMIT_MAX]; diff --git a/share/man/man5/pf.conf.5 b/share/man/man5/pf.conf.5 index 52a7331c7cb2..00fbd4421e7f 100644 --- a/share/man/man5/pf.conf.5 +++ b/share/man/man5/pf.conf.5 @@ -28,7 +28,7 @@ .\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd December 7, 2019 +.Dd April 19, 2021 .Dt PF.CONF 5 .Os .Sh NAME @@ -618,6 +618,13 @@ Generate debug messages for various errors. .It Ar loud Generate debug messages for common conditions. .El +.It Ar set keepcounters +Preserve rule counters across rule updates. +Usually rule counters are reset to zero on every update of the ruleset. +With +.Ar keepcounters +set pf will attempt to find matching rules between old and new rulesets +and preserve the rule counters. .El .Sh TRAFFIC NORMALIZATION Traffic normalization is used to sanitize packet content in such @@ -2888,7 +2895,8 @@ option = "set" ( [ "timeout" ( timeout | "{" timeout-list "}" ) ] | [ "require-order" ( "yes" | "no" ) ] [ "fingerprints" filename ] | [ "skip on" ifspec ] | - [ "debug" ( "none" | "urgent" | "misc" | "loud" ) ] ) + [ "debug" ( "none" | "urgent" | "misc" | "loud" ) ] + [ "keepcounters" ] ) pf-rule = action [ ( "in" | "out" ) ] [ "log" [ "(" logopts ")"] ] [ "quick" ] diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h index efb9a382511e..3a79a281f916 100644 --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -996,6 +996,7 @@ struct pf_kstatus { uint32_t hostid; char ifname[IFNAMSIZ]; uint8_t pf_chksum[PF_MD5_DIGEST_LENGTH]; + bool keep_counters; }; struct pf_divert { @@ -1304,6 +1305,8 @@ struct pfioc_iface { #define DIOCSETIFFLAG _IOWR('D', 89, struct pfioc_iface) #define DIOCCLRIFFLAG _IOWR('D', 90, struct pfioc_iface) #define DIOCKILLSRCNODES _IOWR('D', 91, struct pfioc_src_node_kill) +#define DIOCKEEPCOUNTERS _IOWR('D', 92, struct pfioc_nv) + struct pf_ifspeed_v0 { char ifname[IFNAMSIZ]; u_int32_t baudrate; diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c index e7965b7dcecd..917501163264 100644 --- a/sys/netpfil/pf/pf_ioctl.c +++ b/sys/netpfil/pf/pf_ioctl.c @@ -197,6 +197,7 @@ static void pf_clear_states(void); static int pf_clear_tables(void); static void pf_clear_srcnodes(struct pf_ksrc_node *); static void pf_kill_srcnodes(struct pfioc_src_node_kill *); +static int pf_keepcounters(struct pfioc_nv *); static void pf_tbladdr_copyout(struct pf_addr_wrap *); /* @@ -1022,11 +1023,27 @@ pf_hash_rule(MD5_CTX *ctx, struct pf_krule *rule) PF_MD5_UPD(rule, tos); } +static bool +pf_krule_compare(struct pf_krule *a, struct pf_krule *b) +{ + MD5_CTX ctx[2]; + u_int8_t digest[2][PF_MD5_DIGEST_LENGTH]; + + MD5Init(&ctx[0]); + MD5Init(&ctx[1]); + pf_hash_rule(&ctx[0], a); + pf_hash_rule(&ctx[1], b); + MD5Final(digest[0], &ctx[0]); + MD5Final(digest[1], &ctx[1]); + + return (memcmp(digest[0], digest[1], PF_MD5_DIGEST_LENGTH) == 0); +} + static int pf_commit_rules(u_int32_t ticket, int rs_num, char *anchor) { struct pf_kruleset *rs; - struct pf_krule *rule, **old_array; + struct pf_krule *rule, **old_array, *tail; struct pf_krulequeue *old_rules; int error; u_int32_t old_rcount; @@ -1058,6 +1075,29 @@ pf_commit_rules(u_int32_t ticket, int rs_num, char *anchor) rs->rules[rs_num].inactive.ptr_array; rs->rules[rs_num].active.rcount = rs->rules[rs_num].inactive.rcount; + + /* Attempt to preserve counter information. */ + if (V_pf_status.keep_counters) { + TAILQ_FOREACH(rule, rs->rules[rs_num].active.ptr, + entries) { + tail = TAILQ_FIRST(old_rules); + while ((tail != NULL) && ! pf_krule_compare(tail, rule)) + tail = TAILQ_NEXT(tail, entries); + if (tail != NULL) { + counter_u64_add(rule->evaluations, + counter_u64_fetch(tail->evaluations)); + counter_u64_add(rule->packets[0], + counter_u64_fetch(tail->packets[0])); + counter_u64_add(rule->packets[1], + counter_u64_fetch(tail->packets[1])); + counter_u64_add(rule->bytes[0], + counter_u64_fetch(tail->bytes[0])); + counter_u64_add(rule->bytes[1], + counter_u64_fetch(tail->bytes[1])); + } + } + } + rs->rules[rs_num].inactive.ptr = old_rules; rs->rules[rs_num].inactive.ptr_array = old_array; rs->rules[rs_num].inactive.rcount = old_rcount; @@ -4948,6 +4988,10 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td pf_kill_srcnodes((struct pfioc_src_node_kill *)addr); break; + case DIOCKEEPCOUNTERS: + error = pf_keepcounters((struct pfioc_nv *)addr); + break; + case DIOCSETHOSTID: { u_int32_t *hostid = (u_int32_t *)addr; @@ -5227,6 +5271,41 @@ pf_kill_srcnodes(struct pfioc_src_node_kill *psnk) psnk->psnk_killed = pf_free_src_nodes(&kill); } +static int +pf_keepcounters(struct pfioc_nv *nv) +{ + nvlist_t *nvl = NULL; + void *nvlpacked = NULL; + int error = 0; + +#define ERROUT(x) do { error = (x); goto on_error; } while (0) + + if (nv->len > pf_ioctl_maxcount) + ERROUT(ENOMEM); + + nvlpacked = malloc(nv->len, M_TEMP, 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_bool(nvl, "keep_counters")) + ERROUT(EBADMSG); + + V_pf_status.keep_counters = nvlist_get_bool(nvl, "keep_counters"); + +on_error: + nvlist_destroy(nvl); + free(nvlpacked, M_TEMP); + return (error); +} + /* * XXX - Check for version missmatch!!! */