pf: Optionally attempt to preserve rule counter values across ruleset updates

Usually rule counters are reset to zero on every update of the ruleset.
With keepcounters set pf will attempt to find matching rules between old
and new rulesets and preserve the rule counters.

MFC after:	4 weeks
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D29780
This commit is contained in:
Kristof Provost 2021-04-15 16:12:11 +02:00
parent 8bb0f1b87b
commit 42ec75f83a
8 changed files with 126 additions and 4 deletions

View File

@ -569,3 +569,25 @@ int pfctl_get_clear_rule(int dev, u_int32_t nr, u_int32_t ticket,
return (0); 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);
}

View File

@ -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, int pfctl_add_rule(int dev, const struct pfctl_rule *r,
const char *anchor, const char *anchor_call, u_int32_t ticket, const char *anchor, const char *anchor_call, u_int32_t ticket,
u_int32_t pool_ticket); u_int32_t pool_ticket);
int pfctl_set_keepcounters(int dev, bool keep);
#endif #endif

View File

@ -461,7 +461,7 @@ int parseport(char *, struct range *r, int);
%token REASSEMBLE FRAGDROP FRAGCROP ANCHOR NATANCHOR RDRANCHOR BINATANCHOR %token REASSEMBLE FRAGDROP FRAGCROP ANCHOR NATANCHOR RDRANCHOR BINATANCHOR
%token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY FAILPOLICY %token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY FAILPOLICY
%token RANDOMID REQUIREORDER SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID %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 BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT PROBABILITY MAPEPORTSET
%token ALTQ CBQ CODEL PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME %token ALTQ CBQ CODEL PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME
%token UPPERLIMIT QUEUE PRIORITY QLIMIT HOGS BUCKETS RTABLE TARGET INTERVAL %token UPPERLIMIT QUEUE PRIORITY QLIMIT HOGS BUCKETS RTABLE TARGET INTERVAL
@ -719,6 +719,9 @@ option : SET OPTIMIZATION STRING {
} }
keep_state_defaults = $3; keep_state_defaults = $3;
} }
| SET KEEPCOUNTERS {
pf->keep_counters = true;
}
; ;
stringall : STRING { $$ = $1; } stringall : STRING { $$ = $1; }
@ -5593,6 +5596,7 @@ lookup(char *s)
{ "inet6", INET6}, { "inet6", INET6},
{ "interval", INTERVAL}, { "interval", INTERVAL},
{ "keep", KEEP}, { "keep", KEEP},
{ "keepcounters", KEEPCOUNTERS},
{ "label", LABEL}, { "label", LABEL},
{ "limit", LIMIT}, { "limit", LIMIT},
{ "linkshare", LINKSHARE}, { "linkshare", LINKSHARE},

View File

@ -1745,6 +1745,10 @@ pfctl_load_options(struct pfctl *pf)
if (pfctl_load_hostid(pf, pf->hostid)) if (pfctl_load_hostid(pf, pf->hostid))
error = 1; error = 1;
/* load keepcounters */
if (pfctl_set_keepcounters(pf->dev, pf->keep_counters))
error = 1;
return (error); return (error);
} }

View File

@ -98,6 +98,7 @@ struct pfctl {
u_int32_t debug; u_int32_t debug;
u_int32_t hostid; u_int32_t hostid;
char *ifname; char *ifname;
bool keep_counters;
u_int8_t timeout_set[PFTM_MAX]; u_int8_t timeout_set[PFTM_MAX];
u_int8_t limit_set[PF_LIMIT_MAX]; u_int8_t limit_set[PF_LIMIT_MAX];

View File

@ -28,7 +28,7 @@
.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE. .\" POSSIBILITY OF SUCH DAMAGE.
.\" .\"
.Dd December 7, 2019 .Dd April 19, 2021
.Dt PF.CONF 5 .Dt PF.CONF 5
.Os .Os
.Sh NAME .Sh NAME
@ -618,6 +618,13 @@ Generate debug messages for various errors.
.It Ar loud .It Ar loud
Generate debug messages for common conditions. Generate debug messages for common conditions.
.El .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 .El
.Sh TRAFFIC NORMALIZATION .Sh TRAFFIC NORMALIZATION
Traffic normalization is used to sanitize packet content in such Traffic normalization is used to sanitize packet content in such
@ -2888,7 +2895,8 @@ option = "set" ( [ "timeout" ( timeout | "{" timeout-list "}" ) ] |
[ "require-order" ( "yes" | "no" ) ] [ "require-order" ( "yes" | "no" ) ]
[ "fingerprints" filename ] | [ "fingerprints" filename ] |
[ "skip on" ifspec ] | [ "skip on" ifspec ] |
[ "debug" ( "none" | "urgent" | "misc" | "loud" ) ] ) [ "debug" ( "none" | "urgent" | "misc" | "loud" ) ]
[ "keepcounters" ] )
pf-rule = action [ ( "in" | "out" ) ] pf-rule = action [ ( "in" | "out" ) ]
[ "log" [ "(" logopts ")"] ] [ "quick" ] [ "log" [ "(" logopts ")"] ] [ "quick" ]

View File

@ -996,6 +996,7 @@ struct pf_kstatus {
uint32_t hostid; uint32_t hostid;
char ifname[IFNAMSIZ]; char ifname[IFNAMSIZ];
uint8_t pf_chksum[PF_MD5_DIGEST_LENGTH]; uint8_t pf_chksum[PF_MD5_DIGEST_LENGTH];
bool keep_counters;
}; };
struct pf_divert { struct pf_divert {
@ -1304,6 +1305,8 @@ struct pfioc_iface {
#define DIOCSETIFFLAG _IOWR('D', 89, struct pfioc_iface) #define DIOCSETIFFLAG _IOWR('D', 89, struct pfioc_iface)
#define DIOCCLRIFFLAG _IOWR('D', 90, struct pfioc_iface) #define DIOCCLRIFFLAG _IOWR('D', 90, struct pfioc_iface)
#define DIOCKILLSRCNODES _IOWR('D', 91, struct pfioc_src_node_kill) #define DIOCKILLSRCNODES _IOWR('D', 91, struct pfioc_src_node_kill)
#define DIOCKEEPCOUNTERS _IOWR('D', 92, struct pfioc_nv)
struct pf_ifspeed_v0 { struct pf_ifspeed_v0 {
char ifname[IFNAMSIZ]; char ifname[IFNAMSIZ];
u_int32_t baudrate; u_int32_t baudrate;

View File

@ -197,6 +197,7 @@ static void pf_clear_states(void);
static int pf_clear_tables(void); static int pf_clear_tables(void);
static void pf_clear_srcnodes(struct pf_ksrc_node *); static void pf_clear_srcnodes(struct pf_ksrc_node *);
static void pf_kill_srcnodes(struct pfioc_src_node_kill *); 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 *); 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); 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 static int
pf_commit_rules(u_int32_t ticket, int rs_num, char *anchor) pf_commit_rules(u_int32_t ticket, int rs_num, char *anchor)
{ {
struct pf_kruleset *rs; struct pf_kruleset *rs;
struct pf_krule *rule, **old_array; struct pf_krule *rule, **old_array, *tail;
struct pf_krulequeue *old_rules; struct pf_krulequeue *old_rules;
int error; int error;
u_int32_t old_rcount; 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].inactive.ptr_array;
rs->rules[rs_num].active.rcount = rs->rules[rs_num].active.rcount =
rs->rules[rs_num].inactive.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 = old_rules;
rs->rules[rs_num].inactive.ptr_array = old_array; rs->rules[rs_num].inactive.ptr_array = old_array;
rs->rules[rs_num].inactive.rcount = old_rcount; 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); pf_kill_srcnodes((struct pfioc_src_node_kill *)addr);
break; break;
case DIOCKEEPCOUNTERS:
error = pf_keepcounters((struct pfioc_nv *)addr);
break;
case DIOCSETHOSTID: { case DIOCSETHOSTID: {
u_int32_t *hostid = (u_int32_t *)addr; 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); 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!!! * XXX - Check for version missmatch!!!
*/ */