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 };

Reply via email to