Hi, Before I send a diff for pfctl to disable "once" on "match" rules, I've decided to try and see how much work is it to make it actually work. Turns out that I need to extend pf_rule_item by 3 pointers to track the match rule ruleset, anchor rule and the ruleset it belongs to.
Here's what this means in practice. Consider a ruleset: block drop all match out log proto tcp to port 22 once anchor "foo" all { match out log proto tcp to port 22 once anchor "bar" all { match out log proto tcp to port 22 once pass out quick proto tcp to port 22 once } } Once we send a packet to port 22 the ruleset collapses to just: block drop all Thoughts? diff --git sys/net/pf.c sys/net/pf.c index 9f0e2d6..5679a40 100644 --- sys/net/pf.c +++ sys/net/pf.c @@ -3279,15 +3279,16 @@ pf_test_rule(struct pf_pdesc *pd, struct pf_rule **rm, struct pf_state **sm, PR_NOWAIT)) == NULL) { REASON_SET(&reason, PFRES_MEMORY); goto cleanup; } ri->r = r; + ri->ar = a; + ri->rs = ruleset; + ri->ars = aruleset; /* order is irrelevant */ SLIST_INSERT_HEAD(&rules, ri, entry); pf_rule_to_actions(r, &act); - if (r->rule_flag & PFRULE_AFTO) - pd->naf = r->naf; if (pf_get_transaddr(r, pd, sns, &nr) == -1) { REASON_SET(&reason, PFRES_TRANSLATE); goto cleanup; } if (r->log || act.log & PF_LOG_MATCHES) { @@ -3428,10 +3429,12 @@ pf_test_rule(struct pf_pdesc *pd, struct pf_rule **rm, struct pf_state **sm, virtual_type, icmp_dir); } } else { while ((ri = SLIST_FIRST(&rules))) { SLIST_REMOVE_HEAD(&rules, entry); + if (ri->r->rule_flag & PFRULE_ONCE) + pf_purge_rule(ri->rs, ri->r, ri->ars, ri->ar); pool_put(&pf_rule_item_pl, ri); } } /* copy back packet headers if needed */ @@ -3454,10 +3457,23 @@ pf_test_rule(struct pf_pdesc *pd, struct pf_rule **rm, struct pf_state **sm, } #endif if (r->rule_flag & PFRULE_ONCE) pf_purge_rule(ruleset, r, aruleset, a); + if (*sm) { + SLIST_FOREACH(ri, &(*sm)->match_rules, entry) { + if (ri->r->rule_flag & PFRULE_ONCE) + /* + * We can be sure that pf_purge_rule won't + * pool_put the rule because when *sm != NULL + * STATE_INC_COUNTERS has increased states_cur. + * pf_rule_item's and rules will be g/c'ed by + * pf_free_state. + */ + pf_purge_rule(ri->rs, ri->r, ri->ars, ri->ar); + } + } #if INET && INET6 if (rewrite && skw->af != sks->af) return (PF_AFRT); #endif /* INET && INET6 */ diff --git sys/net/pfvar.h sys/net/pfvar.h index a0d94f7..49af7b4 100644 --- sys/net/pfvar.h +++ sys/net/pfvar.h @@ -691,10 +691,13 @@ struct pf_threshold { }; struct pf_rule_item { SLIST_ENTRY(pf_rule_item) entry; struct pf_rule *r; + struct pf_rule *ar; + struct pf_ruleset *rs; + struct pf_ruleset *ars; }; SLIST_HEAD(pf_rule_slist, pf_rule_item); enum pf_sn_types { PF_SN_NONE, PF_SN_NAT, PF_SN_RDR, PF_SN_ROUTE, PF_SN_MAX };