? sbin/pfctl/- Index: sys/net/pf.c =================================================================== RCS file: /cvs/src/sys/net/pf.c,v retrieving revision 1.1124 diff -u -p -r1.1124 pf.c --- sys/net/pf.c 8 Feb 2022 18:08:33 -0000 1.1124 +++ sys/net/pf.c 5 Mar 2022 07:34:07 -0000 @@ -156,6 +156,8 @@ struct pf_test_ctx { struct pf_ruleset *arsm; struct pf_ruleset *aruleset; struct tcphdr *th; + struct pf_statepl *statepl; + struct pf_sourcepl *sourcepl; int depth; }; @@ -165,6 +167,9 @@ struct pool pf_src_tree_pl, pf_rule_pl struct pool pf_state_pl, pf_state_key_pl, pf_state_item_pl; struct pool pf_rule_item_pl, pf_sn_item_pl, pf_pktdelay_pl; +struct pool pf_statepl_pl, pf_sourcepl_pl; +struct pool pf_state_link_pl; + void pf_add_threshold(struct pf_threshold *); int pf_check_threshold(struct pf_threshold *); int pf_check_tcp_cksum(struct mbuf *, int, int, @@ -203,7 +208,7 @@ static __inline int pf_create_state(str struct pf_state_key **, struct pf_state_key **, int *, struct pf_state **, int, struct pf_rule_slist *, struct pf_rule_actions *, - struct pf_src_node *[]); + struct pf_src_node *[], struct pf_statepl *); static __inline int pf_state_key_addr_setup(struct pf_pdesc *, void *, int, struct pf_addr *, int, struct pf_addr *, int, int); @@ -213,6 +218,8 @@ int pf_tcp_track_full(struct pf_pdesc struct pf_state **, u_short *, int *, int); int pf_tcp_track_sloppy(struct pf_pdesc *, struct pf_state **, u_short *); +static __inline int pf_synproxy_ack(struct pf_rule *, struct pf_pdesc *, + struct pf_state **, struct pf_rule_actions *); static __inline int pf_synproxy(struct pf_pdesc *, struct pf_state **, u_short *); int pf_test_state(struct pf_pdesc *, struct pf_state **, @@ -309,6 +316,40 @@ static __inline void pf_cksum_cover(u_in #endif /* INET6 */ static __inline void pf_set_protostate(struct pf_state *, int, u_int8_t); +static inline int +pf_statepl_cmp(const struct pf_statepl *a, const struct pf_statepl *b) +{ + if (a->pfstpl_id > b->pfstpl_id) + return (1); + if (a->pfstpl_id < b->pfstpl_id) + return (-1); + + return (0); +} + +RBT_GENERATE(pf_statepl_tree, pf_statepl, pfstpl_tree, pf_statepl_cmp); + +struct pf_statepl_tree pf_statepl_tree_active = + RBT_INITIALIZER(pf_statepl_tree_active); +struct pf_statepl_list pf_statepl_list_active = + TAILQ_HEAD_INITIALIZER(pf_statepl_list_active); + +struct pf_statepl_tree pf_statepl_tree_inactive = + RBT_INITIALIZER(pf_statepl_tree_inactive); +struct pf_statepl_list pf_statepl_list_inactive = + TAILQ_HEAD_INITIALIZER(pf_statepl_list_inactive); + +static inline struct pf_statepl * +pf_statepl_find(uint32_t id) +{ + struct pf_statepl key; + + /* only the id is used in cmp, so don't have to zero all the things */ + key.pfstpl_id = id; + + return (RBT_FIND(pf_statepl_tree, &pf_statepl_tree_active, &key)); +} + struct pf_src_tree tree_src_tracking; struct pf_state_tree_id tree_id; @@ -1418,6 +1459,8 @@ pf_src_tree_remove_state(struct pf_state void pf_remove_state(struct pf_state *cur) { + struct pf_state_link *pfl, *npfl; + PF_ASSERT_LOCKED(); /* handle load balancing related tasks */ @@ -1445,6 +1488,35 @@ pf_remove_state(struct pf_state *cur) pfsync_delete_state(cur); #endif /* NPFSYNC > 0 */ cur->timeout = PFTM_UNLINKED; + + for (pfl = cur->linkage; pfl != NULL; pfl = npfl) { + struct pf_state_linkage *list; + + npfl = pf_state_link_next(pfl); + + switch (pf_state_link_type(pfl)) { + case PF_STATE_LINK_TYPE_STATEPL: { + struct pf_statepl *stpl; + + stpl = pf_statepl_find(cur->statepl); + KASSERTMSG(stpl != NULL, + "pf_state %p pfl %p cannot find statepl %u", + cur, pfl, cur->statepl); + + stpl->pfstpl_inuse--; + list = &stpl->pfstpl_links; + break; + } + default: + panic("%s: unexpected link type on pfl %p", + __func__, pfl); + } + + TAILQ_REMOVE(list, pfl, pfl_entry); + pool_put(&pf_state_link_pl, pfl); + } + cur->linkage = NULL; + pf_src_tree_remove_state(cur); pf_detach_state(cur); } @@ -3609,6 +3681,7 @@ pf_match_rule(struct pf_test_ctx *ctx, s r = TAILQ_FIRST(ruleset->rules.active.ptr); while (r != NULL) { + struct pf_statepl *stpl = NULL; r->evaluations++; PF_TEST_ATTRIB( (pfi_kif_match(r->kif, ctx->pd->kif) == r->ifnot), @@ -3722,6 +3795,50 @@ pf_match_rule(struct pf_test_ctx *ctx, s ctx->pd->m->m_pkthdr.pf.prio), TAILQ_NEXT(r, entries)); + if (r->statepl != PF_STATEPL_ID_NONE) { + stpl = pf_statepl_find(r->statepl); + + /* + * Treat a missing pool like an overcommitted pool. + * There is no "backend" to get a resource out of + * so the rule can't create state. + */ + PF_TEST_ATTRIB(stpl == NULL, + TAILQ_NEXT(r, entries)); + + /* + * An overcommitted pool means this rule + * can't create state. + */ + if (stpl->pfstpl_inuse >= stpl->pfstpl_limit) { + stpl->pfstpl_counters.hardlimited++; + r = TAILQ_NEXT(r, entries); + continue; + } + + if (stpl->pfstpl_rate.limit != 0) { + uint64_t ts = getnsecuptime(); + uint64_t diff = ts - stpl->pfstpl_rate_ts; + + if (diff < stpl->pfstpl_rate_token) { + stpl->pfstpl_counters.ratelimited++; + r = TAILQ_NEXT(r, entries); + continue; + } + + if (diff > stpl->pfstpl_rate_bucket) { + stpl->pfstpl_rate_ts = + ts - stpl->pfstpl_rate_bucket; + } + + stpl->pfstpl_rate_ts += + stpl->pfstpl_rate_token; + } + + /* Take the resource now */ + stpl->pfstpl_inuse++; + } + /* must be last! */ if (r->pktrate.limit) { pf_add_threshold(&r->pktrate); @@ -3762,6 +3879,8 @@ pf_match_rule(struct pf_test_ctx *ctx, s } #endif /* NPFLOG > 0 */ } else { + struct pf_statepl *ostpl; + /* * found matching r */ @@ -3778,6 +3897,15 @@ pf_match_rule(struct pf_test_ctx *ctx, s * ruleset, where anchor belongs to. */ ctx->arsm = ctx->aruleset; + + /* + * state pool + */ + ostpl = ctx->statepl; + if (ostpl != NULL) + ostpl->pfstpl_inuse--; + + ctx->statepl = stpl; } #if NPFLOG > 0 @@ -3819,6 +3947,7 @@ pf_test_rule(struct pf_pdesc *pd, struct struct pf_rule *a = NULL; struct pf_ruleset *ruleset = NULL; struct pf_state_key *skw = NULL, *sks = NULL; + struct pf_statepl *stpl; int rewrite = 0; u_int16_t virtual_type, virtual_id; int action = PF_DROP; @@ -3971,6 +4100,7 @@ pf_test_rule(struct pf_pdesc *pd, struct if (pd->virtual_proto != PF_VPROTO_FRAGMENT && !ctx.state_icmp && r->keep_state) { +#if 0 if (r->rule_flag & PFRULE_SRCTRACK && pf_insert_src_node(&ctx.sns[PF_SN_NONE], r, PF_SN_NONE, pd->af, pd->src, NULL, NULL) != 0) { @@ -3983,12 +4113,22 @@ pf_test_rule(struct pf_pdesc *pd, struct REASON_SET(&ctx.reason, PFRES_MAXSTATES); goto cleanup; } +#endif action = pf_create_state(pd, r, a, ctx.nr, &skw, &sks, - &rewrite, sm, ctx.tag, &ctx.rules, &ctx.act, ctx.sns); + &rewrite, sm, ctx.tag, &ctx.rules, &ctx.act, ctx.sns, + ctx.statepl); if (action != PF_PASS) goto cleanup; + + if (pd->proto == IPPROTO_TCP && + r->keep_state == PF_STATE_SYNPROXY && pd->dir == PF_IN) { + action = pf_synproxy_ack(r, pd, sm, &ctx.act); + if (action != PF_PASS) + return (action); + } + if (sks != skw) { struct pf_state_key *sk; @@ -4054,6 +4194,10 @@ pf_test_rule(struct pf_pdesc *pd, struct return (action); cleanup: + stpl = ctx.statepl; + if (stpl != NULL) + stpl->pfstpl_inuse--; + while ((ctx.ri = SLIST_FIRST(&ctx.rules))) { SLIST_REMOVE_HEAD(&ctx.rules, entry); pool_put(&pf_rule_item_pl, ctx.ri); @@ -4066,11 +4210,12 @@ static __inline int pf_create_state(struct pf_pdesc *pd, struct pf_rule *r, struct pf_rule *a, struct pf_rule *nr, struct pf_state_key **skw, struct pf_state_key **sks, int *rewrite, struct pf_state **sm, int tag, struct pf_rule_slist *rules, - struct pf_rule_actions *act, struct pf_src_node *sns[PF_SN_MAX]) + struct pf_rule_actions *act, struct pf_src_node *sns[PF_SN_MAX], + struct pf_statepl *stpl) { struct pf_state *s = NULL; + struct pf_state_link *pfl; struct tcphdr *th = &pd->hdr.tcp; - u_int16_t mss = tcp_mssdflt; u_short reason; u_int i; @@ -4165,8 +4310,7 @@ pf_create_state(struct pf_pdesc *pd, str s->timeout = PFTM_OTHER_FIRST_PACKET; } - s->creation = getuptime(); - s->expire = getuptime(); + s->creation = s->expire = getuptime(); if (pd->proto == IPPROTO_TCP) { if (s->state_flags & PFSTATE_SCRUB_TCP && @@ -4209,6 +4353,21 @@ pf_create_state(struct pf_pdesc *pd, str sni->sn->states++; } + if (stpl != NULL) { + pfl = pool_get(&pf_state_link_pl, PR_NOWAIT); + if (pfl == NULL) { + REASON_SET(&reason, PFRES_MEMORY); + goto csfailed; + } + + s->statepl = stpl->pfstpl_id; + pfl->pfl_state = s; + + TAILQ_INSERT_TAIL(&stpl->pfstpl_links, pfl, pfl_entry); + pf_state_link_insert(s, pfl, PF_STATE_LINK_TYPE_STATEPL); + stpl->pfstpl_counters.admitted++; + } + if (pf_state_insert(BOUND_IFACE(r, pd->kif), skw, sks, s)) { pf_detach_state(s); *sks = *skw = NULL; @@ -4228,29 +4387,33 @@ pf_create_state(struct pf_pdesc *pd, str pf_tag_ref(tag); s->tag = tag; } - if (pd->proto == IPPROTO_TCP && (th->th_flags & (TH_SYN|TH_ACK)) == - TH_SYN && r->keep_state == PF_STATE_SYNPROXY && pd->dir == PF_IN) { - int rtid = pd->rdomain; - if (act->rtableid >= 0) - rtid = act->rtableid; - pf_set_protostate(s, PF_PEER_SRC, PF_TCPS_PROXY_SRC); - s->src.seqhi = arc4random(); - /* Find mss option */ - mss = pf_get_mss(pd); - mss = pf_calc_mss(pd->src, pd->af, rtid, mss); - mss = pf_calc_mss(pd->dst, pd->af, rtid, mss); - s->src.mss = mss; - pf_send_tcp(r, pd->af, pd->dst, pd->src, th->th_dport, - th->th_sport, s->src.seqhi, ntohl(th->th_seq) + 1, - TH_SYN|TH_ACK, 0, s->src.mss, 0, 1, 0, pd->rdomain); - REASON_SET(&reason, PFRES_SYNPROXY); - return (PF_SYNPROXY_DROP); - } return (PF_PASS); csfailed: if (s) { + struct pf_state_link *npfl; + + for (pfl = s->linkage; pfl != NULL; pfl = npfl) { + struct pf_state_linkage *list; + + npfl = pf_state_link_next(pfl); + + /* who needs KASSERTS when we have NULL derefs */ + + switch (pf_state_link_type(pfl)) { + case PF_STATE_LINK_TYPE_STATEPL: + list = &stpl->pfstpl_links; + break; + default: + panic("%s: unexpected link type on pfl %p", + __func__, pfl); + } + + TAILQ_REMOVE(list, pfl, pfl_entry); + pool_put(&pf_state_link_pl, pfl); + } + pf_normalize_tcp_cleanup(s); /* safe even w/o init */ pf_src_tree_remove_state(s); pool_put(&pf_state_pl, s); @@ -4263,6 +4426,38 @@ csfailed: return (PF_DROP); } +static __inline int +pf_synproxy_ack(struct pf_rule *r, struct pf_pdesc *pd, struct pf_state **sm, + struct pf_rule_actions *act) +{ + struct tcphdr *th = &pd->hdr.tcp; + struct pf_state *s; + u_int16_t mss; + int rtid; + u_short reason; + + if ((th->th_flags & (TH_SYN|TH_ACK)) != TH_SYN) + return (PF_PASS); + + s = *sm; + rtid = (act->rtableid >= 0) ? act->rtableid : pd->rdomain; + + pf_set_protostate(s, PF_PEER_SRC, PF_TCPS_PROXY_SRC); + s->src.seqhi = arc4random(); + /* Find mss option */ + mss = pf_get_mss(pd); + mss = pf_calc_mss(pd->src, pd->af, rtid, mss); + mss = pf_calc_mss(pd->dst, pd->af, rtid, mss); + s->src.mss = mss; + + pf_send_tcp(r, pd->af, pd->dst, pd->src, th->th_dport, + th->th_sport, s->src.seqhi, ntohl(th->th_seq) + 1, + TH_SYN|TH_ACK, 0, s->src.mss, 0, 1, 0, pd->rdomain); + + REASON_SET(&reason, PFRES_SYNPROXY); + return (PF_SYNPROXY_DROP); +} + int pf_translate(struct pf_pdesc *pd, struct pf_addr *saddr, u_int16_t sport, struct pf_addr *daddr, u_int16_t dport, u_int16_t virtual_type, @@ -7636,6 +7831,7 @@ pf_state_unref(struct pf_state *s) (TAILQ_NEXT(s, entry_list) == _Q_INVALID)); KASSERT((s->key[PF_SK_WIRE] == NULL) && (s->key[PF_SK_STACK] == NULL)); + KASSERT(s->linkage == NULL); pool_put(&pf_state_pl, s); } @@ -7672,4 +7868,40 @@ pf_pktenqueue_delayed(void *arg) m_freem(pdy->m); pool_put(&pf_pktdelay_pl, pdy); +} + +void +pf_state_link_insert(struct pf_state *s, struct pf_state_link *pfl, + unsigned int type) +{ + uintptr_t link = (uintptr_t)s->linkage; + + KASSERTMSG((link & PF_STATE_LINK_TYPE_MASK) == 0, + "unexpected bits set in pf_state %p linkage %p", s, s->linkage); + + link |= type; + pfl->pfl_link = (struct pf_state_link *)link; + s->linkage = pfl; +} + +void +pf_state_link_remove(struct pf_state *s, struct pf_state_link *pfl) +{ + struct pf_state_link **linkp; + struct pf_state_link *link; + + linkp = &s->linkage; + link = *linkp; + while (link != NULL) { + if (link == pfl) { + *linkp = pfl->pfl_link; + return; + } + + linkp = &pfl->pfl_link; + link = pf_state_link_ptr(*linkp); + } + + panic("%s: unable to find pf_state_link %p in pf_state %p", + __func__, pfl, s); } Index: sys/net/pf_ioctl.c =================================================================== RCS file: /cvs/src/sys/net/pf_ioctl.c,v retrieving revision 1.373 diff -u -p -r1.373 pf_ioctl.c --- sys/net/pf_ioctl.c 16 Feb 2022 04:25:34 -0000 1.373 +++ sys/net/pf_ioctl.c 5 Mar 2022 07:34:07 -0000 @@ -86,6 +86,7 @@ #endif /* NPFSYNC > 0 */ struct pool pf_tag_pl; +extern struct pool pf_statepl_pl, pf_sourcepl_pl, pf_state_link_pl; void pfattach(int); void pf_thread_create(void *); @@ -188,6 +189,13 @@ pfattach(int num) pool_init(&pf_pktdelay_pl, sizeof(struct pf_pktdelay), 0, IPL_SOFTNET, 0, "pfpktdelay", NULL); + pool_init(&pf_statepl_pl, sizeof(struct pf_statepl), 0, + IPL_SOFTNET, 0, "pfstpl", NULL); + pool_init(&pf_sourcepl_pl, sizeof(struct pf_sourcepl), 0, + IPL_SOFTNET, 0, "pfsrcpl", NULL); + pool_init(&pf_state_link_pl, sizeof(struct pf_state_link), 0, + IPL_SOFTNET, 0, "pfslink", NULL); + hfsc_initialize(); pfr_initialize(); pfi_initialize(); @@ -924,6 +932,270 @@ pf_addr_copyout(struct pf_addr_wrap *add } int +pf_statepl_add(const struct pfioc_statepl *ioc) +{ + struct pf_statepl *pfstpl; + int error; + size_t descrlen; + + if (ioc->id < PF_STATEPL_ID_MIN || + ioc->id > PF_STATEPL_ID_MAX) + return (EINVAL); + + if (ioc->limit < PF_STATEPL_LIMIT_MIN || + ioc->limit > PF_STATEPL_LIMIT_MAX) + return (EINVAL); + + if ((ioc->rate.limit == 0) != (ioc->rate.seconds == 0)) + return (EINVAL); + + /* XXX check rate */ +// if (ioc->rate.limit != 0 || ioc->rate.seconds != 0) +// return (EOPNOTSUPP); + + descrlen = strnlen(ioc->description, sizeof(ioc->description)); + if (descrlen == sizeof(ioc->description)) + return (EINVAL); + + pfstpl = pool_get(&pf_statepl_pl, PR_WAITOK|PR_ZERO); + if (pfstpl == NULL) + return (ENOMEM); + + pfstpl->pfstpl_id = ioc->id; + pfstpl->pfstpl_limit = ioc->limit; + pfstpl->pfstpl_rate.limit = ioc->rate.limit; + pfstpl->pfstpl_rate.seconds = ioc->rate.seconds; + memcpy(pfstpl->pfstpl_description, ioc->description, descrlen); + + if (pfstpl->pfstpl_rate.limit) { + uint64_t bucket = + pfstpl->pfstpl_rate.seconds * 1000000000ULL; + + pfstpl->pfstpl_rate_ts = getnsecuptime() - bucket; + pfstpl->pfstpl_rate_token = bucket / pfstpl->pfstpl_rate.limit; + pfstpl->pfstpl_rate_bucket = bucket; + } + + TAILQ_INIT(&pfstpl->pfstpl_links); + + NET_LOCK(); + PF_LOCK(); + if (ioc->ticket != pf_main_ruleset.rules.inactive.ticket) { + error = EBUSY; + goto unlock; + } + + if (RBT_INSERT(pf_statepl_tree, + &pf_statepl_tree_inactive, pfstpl) != NULL) { + error = EBUSY; + goto unlock; + } + + TAILQ_INSERT_HEAD(&pf_statepl_list_inactive, pfstpl, pfstpl_list); + + PF_UNLOCK(); + NET_UNLOCK(); + + return (0); + +unlock: + PF_UNLOCK(); + NET_UNLOCK(); +/* free: */ + pool_put(&pf_statepl_pl, pfstpl); + + return (error); +} + +static void +pf_statepl_unlink(struct pf_statepl *pfstpl, struct pf_state_linkage *garbage) +{ + struct pf_state_link *pfl; + + PF_STATE_ENTER_WRITE(); + + /* unwire the links */ + TAILQ_FOREACH(pfl, &pfstpl->pfstpl_links, pfl_entry) { + struct pf_state *s = pfl->pfl_state; + + /* if !rmst */ + s->statepl = 0; + pf_state_link_remove(s, pfl); + } + + /* take the list away */ + TAILQ_CONCAT(garbage, &pfstpl->pfstpl_links, pfl_entry); + pfstpl->pfstpl_inuse = 0; + + PF_STATE_EXIT_WRITE(); +} + +int +pf_statepl_clr(uint32_t id, int rmst) +{ + struct pf_statepl key = { .pfstpl_id = id }; + struct pf_statepl *pfstpl; + int error = ESRCH; /* is this right? */ + struct pf_state_linkage garbage = TAILQ_HEAD_INITIALIZER(garbage); + struct pf_state_link *pfl, *npfl; + + if (rmst) + return (EOPNOTSUPP); + + NET_LOCK(); + PF_LOCK(); + pfstpl = RBT_FIND(pf_statepl_tree, &pf_statepl_tree_active, &key); + if (pfstpl != NULL) { + pf_statepl_unlink(pfstpl, &garbage); + error = 0; + } + PF_UNLOCK(); + NET_UNLOCK(); + + TAILQ_FOREACH_SAFE(pfl, &garbage, pfl_entry, npfl) + pool_put(&pf_state_link_pl, pfl); + + return (error); +} + +void +pf_statepl_commit(void) +{ + struct pf_statepl *pfstpl, *npfstpl, *opfstpl; + struct pf_statepl_list l = TAILQ_HEAD_INITIALIZER(l); + struct pf_state_linkage garbage = TAILQ_HEAD_INITIALIZER(garbage); + struct pf_state_link *pfl, *npfl; + + PF_ASSERT_LOCKED(); + NET_ASSERT_LOCKED(); + + /* merge the new statepls into the current set */ + + /* start with an empty active list */ + TAILQ_CONCAT(&l, &pf_statepl_list_active, pfstpl_list); + + /* beware, the inactive bits gets messed up here */ + + /* try putting pending statepls into the active tree */ + TAILQ_FOREACH_SAFE(pfstpl, &pf_statepl_list_inactive, + pfstpl_list, npfstpl) { + opfstpl = RBT_INSERT(pf_statepl_tree, + &pf_statepl_tree_active, pfstpl); + if (opfstpl != NULL) { + /* this statepl already exists, merge */ + opfstpl->pfstpl_limit = + pfstpl->pfstpl_limit; + opfstpl->pfstpl_rate.limit = + pfstpl->pfstpl_rate.limit; + opfstpl->pfstpl_rate.seconds = + pfstpl->pfstpl_rate.seconds; + + /* XXX tweak rate_ts? */ + opfstpl->pfstpl_rate_token = + pfstpl->pfstpl_rate_token; + opfstpl->pfstpl_rate_bucket = + pfstpl->pfstpl_rate_bucket; + + /* use the existing statepl instead */ + pool_put(&pf_statepl_pl, pfstpl); + TAILQ_REMOVE(&l, opfstpl, pfstpl_list); + pfstpl = opfstpl; + } + + TAILQ_INSERT_TAIL(&pf_statepl_list_active, + pfstpl, pfstpl_list); + } + + /* clean up the now unused statepls from the old set */ + TAILQ_FOREACH_SAFE(pfstpl, &l, pfstpl_list, npfstpl) { + pf_statepl_unlink(pfstpl, &garbage); + + RBT_REMOVE(pf_statepl_tree, + &pf_statepl_tree_active, pfstpl); + pool_put(&pf_statepl_pl, pfstpl); + } + + /* fix up the inactive tree */ + RBT_INIT(pf_statepl_tree, &pf_statepl_tree_inactive); + TAILQ_INIT(&pf_statepl_list_inactive); + + TAILQ_FOREACH_SAFE(pfl, &garbage, pfl_entry, npfl) + pool_put(&pf_state_link_pl, pfl); +} + +void +pf_statepl_rollback(void) +{ + struct pf_statepl *pfstpl, *npfstpl; + + PF_ASSERT_LOCKED(); + NET_ASSERT_LOCKED(); + + TAILQ_FOREACH_SAFE(pfstpl, &pf_statepl_list_inactive, + pfstpl_list, npfstpl) + pool_put(&pf_statepl_pl, pfstpl); + + TAILQ_INIT(&pf_statepl_list_inactive); + RBT_INIT(pf_statepl_tree, &pf_statepl_tree_inactive); +} + +static struct pf_statepl * +pf_statepl_rb_find(struct pf_statepl_tree *tree, const struct pf_statepl *key) +{ + return (RBT_FIND(pf_statepl_tree, tree, key)); +} + +static struct pf_statepl * +pf_statepl_rb_nfind(struct pf_statepl_tree *tree, const struct pf_statepl *key) +{ + return (RBT_NFIND(pf_statepl_tree, tree, key)); +} + +int +pf_statepl_get(struct pfioc_statepl *ioc, + struct pf_statepl *(*rbt_op)(struct pf_statepl_tree *, + const struct pf_statepl *)) +{ + struct pf_statepl key = { .pfstpl_id = ioc->id }; + struct pf_statepl *pfstpl; + int error = 0; + + NET_LOCK(); + PF_LOCK(); +#if 0 + if (ioc->ticket != pf_main_ruleset.rules.active.ticket) { + error = EBUSY; + goto unlock; + } +#endif + + pfstpl = (*rbt_op)(&pf_statepl_tree_active, &key); + if (pfstpl == NULL) { + error = ESRCH; + goto unlock; + } + + ioc->limit = pfstpl->pfstpl_limit; + ioc->rate.limit = pfstpl->pfstpl_rate.limit; + ioc->rate.seconds = pfstpl->pfstpl_rate.seconds; + CTASSERT(sizeof(ioc->description) == + sizeof(pfstpl->pfstpl_description)); + memcpy(ioc->description, pfstpl->pfstpl_description, + sizeof(ioc->description)); + + ioc->inuse = pfstpl->pfstpl_inuse; + ioc->admitted = pfstpl->pfstpl_counters.admitted; + ioc->hardlimited = pfstpl->pfstpl_counters.hardlimited; + ioc->ratelimited = pfstpl->pfstpl_counters.ratelimited; + +unlock: + PF_UNLOCK(); + NET_UNLOCK(); + + return (error); +} + +int pf_states_clr(struct pfioc_state_kill *psk) { struct pf_state *s, *nexts; @@ -1058,6 +1330,10 @@ pfioctl(dev_t dev, u_long cmd, caddr_t a case DIOCGETSTATES: case DIOCGETTIMEOUT: case DIOCGETLIMIT: + case DIOCGETSTATEPL: + case DIOCGETNSTATEPL: + case DIOCGETSOURCEPL: + case DIOCGETNSOURCEPL: case DIOCGETRULESETS: case DIOCGETRULESET: case DIOCGETQUEUES: @@ -1102,6 +1378,10 @@ pfioctl(dev_t dev, u_long cmd, caddr_t a case DIOCGETSTATES: case DIOCGETTIMEOUT: case DIOCGETLIMIT: + case DIOCGETSTATEPL: + case DIOCGETNSTATEPL: + case DIOCGETSOURCEPL: + case DIOCGETNSOURCEPL: case DIOCGETRULESETS: case DIOCGETRULESET: case DIOCGETQUEUES: @@ -1327,6 +1607,18 @@ pfioctl(dev_t dev, u_long cmd, caddr_t a break; } + case DIOCADDSTATEPL: + error = pf_statepl_add((struct pfioc_statepl *)addr); + break; + case DIOCGETSTATEPL: + error = pf_statepl_get((struct pfioc_statepl *)addr, + pf_statepl_rb_find); + break; + case DIOCGETNSTATEPL: + error = pf_statepl_get((struct pfioc_statepl *)addr, + pf_statepl_rb_nfind); + break; + case DIOCADDRULE: { struct pfioc_rule *pr = (struct pfioc_rule *)addr; struct pf_ruleset *ruleset; @@ -2774,6 +3066,7 @@ pfioctl(dev_t dev, u_long cmd, caddr_t a pf_default_rule.timeout[i] < old) task_add(net_tq(0), &pf_purge_task); } + pf_statepl_commit(); pfi_xcommit(); pf_trans_set_commit(); PF_UNLOCK(); @@ -3067,6 +3360,18 @@ pf_rule_copyin(struct pf_rule *from, str from->set_prio[1] > IFQ_MAXPRIO)) return (EINVAL); + if (from->statepl != PF_STATEPL_ID_NONE) { + if (from->statepl < PF_STATEPL_ID_MIN || + from->statepl > PF_STATEPL_ID_MAX) + return (EINVAL); + } + + if (from->sourcepl != PF_SOURCEPL_ID_NONE) { + if (from->sourcepl < PF_SOURCEPL_ID_MIN || + from->sourcepl > PF_SOURCEPL_ID_MAX) + return (EINVAL); + } + to->src = from->src; to->src.addr.p.tbl = NULL; to->dst = from->dst; @@ -3190,6 +3495,8 @@ pf_rule_copyin(struct pf_rule *from, str to->prio = from->prio; to->set_prio[0] = from->set_prio[0]; to->set_prio[1] = from->set_prio[1]; + to->statepl = from->statepl; + to->sourcepl = from->sourcepl; return (0); } Index: sys/net/pfvar.h =================================================================== RCS file: /cvs/src/sys/net/pfvar.h,v retrieving revision 1.505 diff -u -p -r1.505 pfvar.h --- sys/net/pfvar.h 26 Dec 2021 01:00:32 -0000 1.505 +++ sys/net/pfvar.h 5 Mar 2022 07:34:07 -0000 @@ -590,7 +590,8 @@ struct pf_rule { u_int8_t set_prio[2]; sa_family_t naf; u_int8_t rcvifnot; - u_int8_t pad[2]; + uint8_t statepl; + uint8_t sourcepl; struct { struct pf_addr addr; @@ -742,6 +743,8 @@ struct pf_state_cmp { u_int8_t pad[3]; }; +struct pf_state_link; + struct pf_state { u_int64_t id; u_int32_t creatorid; @@ -797,6 +800,9 @@ struct pf_state { pf_refcnt_t refcnt; u_int16_t delay; u_int8_t rt; + uint8_t statepl; + uint8_t sourcepl; + struct pf_state_link *linkage; }; /* @@ -1611,6 +1617,80 @@ struct pfioc_synflwats { u_int32_t lowat; }; +struct pfioc_statepl { + u_int32_t ticket; + + uint32_t id; +#define PF_STATEPL_ID_NONE 0 +#define PF_STATEPL_ID_MIN 1 +#define PF_STATEPL_ID_MAX 255 /* fits in pf_state uint8_t */ + + /* limit on the total number of states */ + unsigned int limit; +#define PF_STATEPL_LIMIT_MIN 1 +#define PF_STATEPL_LIMIT_MAX (1 << 24) /* pf is pretty scalable */ + + /* rate limit on the admission of states to the pool */ + struct { + unsigned int limit; + unsigned int seconds; + } rate; + + char description[PF_RULE_LABEL_SIZE]; + + /* kernel state for GET ioctls */ + unsigned int inuse; /* gauge */ + uint64_t admitted; /* counter */ + uint64_t hardlimited; /* counter */ + uint64_t ratelimited; /* counter */ +}; + +struct pfioc_sourcepl { + u_int32_t ticket; + + uint32_t id; +#define PF_SOURCEPL_ID_NONE 0 +#define PF_SOURCEPL_ID_MIN 1 +#define PF_SOURCEPL_ID_MAX 255 /* fits in pf_state uint8_t */ + + /* limit on the total number of addresses in the source pool */ + unsigned int limit; + + /* limit on the number of states per address entry */ + unsigned int states; + + /* rate limit on the addition of states to an address entry */ + struct { + unsigned int limit; + unsigned int seconds; + } rate; + + /* + * when the number of states on an entry exceeds hwm, add + * the address to the specified table. when the number of + * states goes below lwm, remove it from the table. + */ + char overload_tblname[PF_TABLE_NAME_SIZE]; + unsigned int overload_hwm; + unsigned int overload_lwm; + + /* + * mask addresses before they're used for entries. /64s + * everywhere for inet6 makes it easy to use too much memory. + */ + unsigned int inet_prefix; + unsigned int inet6_prefix; + + char description[PF_RULE_LABEL_SIZE]; + + /* kernel state for GET ioctls */ + unsigned int inuse; /* gauge */ + uint64_t admitted; /* counter */ + uint64_t addrlimited; /* counter */ + uint64_t hardlimited; /* counter */ + uint64_t ratelimited; /* counter */ +}; + /* * ioctl operations */ @@ -1678,6 +1758,12 @@ struct pfioc_synflwats { #define DIOCSETSYNFLWATS _IOWR('D', 97, struct pfioc_synflwats) #define DIOCSETSYNCOOKIES _IOWR('D', 98, u_int8_t) #define DIOCGETSYNFLWATS _IOWR('D', 99, struct pfioc_synflwats) +#define DIOCADDSTATEPL _IOW('D', 100, struct pfioc_statepl) +#define DIOCADDSOURCEPL _IOW('D', 100, struct pfioc_sourcepl) +#define DIOCGETSTATEPL _IOWR('D', 101, struct pfioc_statepl) +#define DIOCGETSOURCEPL _IOWR('D', 101, struct pfioc_sourcepl) +#define DIOCGETNSTATEPL _IOWR('D', 102, struct pfioc_statepl) +#define DIOCGETNSOURCEPL _IOWR('D', 102, struct pfioc_sourcepl) #ifdef _KERNEL Index: sys/net/pfvar_priv.h =================================================================== RCS file: /cvs/src/sys/net/pfvar_priv.h,v retrieving revision 1.8 diff -u -p -r1.8 pfvar_priv.h --- sys/net/pfvar_priv.h 2 Jan 2022 22:36:04 -0000 1.8 +++ sys/net/pfvar_priv.h 5 Mar 2022 07:34:07 -0000 @@ -147,6 +147,139 @@ struct pf_state_list { .pfs_rwl = RWLOCK_INITIALIZER("pfstates"), \ } +/* + * Rate limiter + */ + +/* + * PF state links + * + * This is used to augment a struct pf_state so it can be + * tracked/referenced by the state and source address "pool" things + * above. Each pool maintains a list of the states they "own", and + * these state links are what the pools use to wire a state into their + * lists. + * + * pfl_entry is used by the pools to add states to their list. + * pfl_state allows the pools to get from their list of states to + * the states themselves. + * + * pfl_link allows operations on states (well, delete) to be able + * to quickly locate the pf_state_link struct so they can be unwired + * from the pools. pf_state_links are typed so if a state belongs to + * both a state and source pool, the linkage used for each type of + * pool can be properly differentiated. The type is stored in the low + * bits of the pointer since we can guarantee the alignment. + */ + +#define PF_STATE_LINK_TYPE_BITS 4 +#define PF_STATE_LINK_ALIGN (1UL << PF_STATE_LINK_TYPE_BITS) +#define PF_STATE_LINK_TYPE_MASK (PF_STATE_LINK_ALIGN - 1) + +#define PF_STATE_LINK_TYPE_STATEPL 1 +#define PF_STATE_LINK_TYPE_SOURCEPL 2 + +struct pf_state_link { + struct pf_state_link *pfl_link + __aligned(PF_STATE_LINK_ALIGN); + struct pf_state *pfl_state; + TAILQ_ENTRY(pf_state_link) pfl_entry; +}; + +TAILQ_HEAD(pf_state_linkage, pf_state_link); + +static inline struct pf_state_link * +pf_state_link_ptr(struct pf_state_link *pfl) +{ + uintptr_t link = (uintptr_t)pfl; + link &= ~PF_STATE_LINK_TYPE_MASK; + return ((struct pf_state_link *)link); +} + +static inline struct pf_state_link * +pf_state_link_next(struct pf_state_link *pfl) +{ + return (pf_state_link_ptr(pfl->pfl_link)); +} + +static inline unsigned int +pf_state_link_type(struct pf_state_link *pfl) +{ + uintptr_t link = (uintptr_t)pfl->pfl_link; + link &= PF_STATE_LINK_TYPE_MASK; + return (link); +} + +/* augment a pf_state with a pf_state_link */ +void pf_state_link_insert(struct pf_state *, struct pf_state_link *, + unsigned int); +/* take a pf_state_link away from a pf_state */ +void pf_state_link_remove(struct pf_state *, struct pf_state_link *); + +/* + * State pools + */ + +struct pf_statepl { + RBT_ENTRY(pf_statepl) pfstpl_tree; + TAILQ_ENTRY(pf_statepl) pfstpl_list; + + uint32_t pfstpl_id; + + /* config */ + + unsigned int pfstpl_limit; + struct { + unsigned int limit; + unsigned int seconds; + } pfstpl_rate; + + char pfstpl_description[PF_RULE_LABEL_SIZE]; + + /* run state */ + + /* rate limiter */ + uint64_t pfstpl_rate_ts; + uint64_t pfstpl_rate_token; + uint64_t pfstpl_rate_bucket; + + unsigned int pfstpl_inuse; + struct pf_state_linkage pfstpl_links; + + /* counters */ + + struct { + uint64_t admitted; + uint64_t hardlimited; + uint64_t ratelimited; + } pfstpl_counters; + + struct { + time_t created; + time_t updated; + time_t cleared; + } pfstpl_timestamps; +}; + +RBT_HEAD(pf_statepl_tree, pf_statepl); +RBT_PROTOTYPE(pf_statepl_tree, pf_statepl, pfstpl_tree, cmp); + +TAILQ_HEAD(pf_statepl_list, pf_statepl); + +extern struct pf_statepl_tree pf_statepl_tree_active; +extern struct pf_statepl_list pf_statepl_list_active; + +extern struct pf_statepl_tree pf_statepl_tree_inactive; +extern struct pf_statepl_list pf_statepl_list_inactive; + +/* + * Source address pools + */ + +struct pf_sourcepl { + +}; + extern struct rwlock pf_lock; struct pf_pdesc { Index: sbin/pfctl/parse.y =================================================================== RCS file: /cvs/src/sbin/pfctl/parse.y,v retrieving revision 1.711 diff -u -p -r1.711 parse.y --- sbin/pfctl/parse.y 25 Oct 2021 14:50:29 -0000 1.711 +++ sbin/pfctl/parse.y 5 Mar 2022 07:34:07 -0000 @@ -57,6 +57,10 @@ #include "pfctl_parser.h" #include "pfctl.h" +#ifndef ISSET +#define ISSET(_v, _m) ((_v) & (_m)) +#endif + static struct pfctl *pf = NULL; static int debug = 0; static u_int16_t returnicmpdefault = @@ -146,7 +150,7 @@ enum { PF_STATE_OPT_MAX, PF_STATE_OPT_NO PF_STATE_OPT_MAX_SRC_CONN_RATE, PF_STATE_OPT_MAX_SRC_NODES, PF_STATE_OPT_OVERLOAD, PF_STATE_OPT_STATELOCK, PF_STATE_OPT_TIMEOUT, PF_STATE_OPT_SLOPPY, - PF_STATE_OPT_PFLOW }; + PF_STATE_OPT_PFLOW, PF_STATE_OPT_STATEPL, PF_STATE_OPT_SOURCEPL }; enum { PF_SRCTRACK_NONE, PF_SRCTRACK, PF_SRCTRACK_GLOBAL, PF_SRCTRACK_RULE }; @@ -171,6 +175,8 @@ struct node_state_opt { int number; u_int32_t seconds; } timeout; + uint32_t statepl; + uint32_t sourcepl; } data; struct node_state_opt *next; struct node_state_opt *tail; @@ -341,6 +347,46 @@ struct table_opts { struct node_tinithead init_nodes; } table_opts; +struct statepl_opts { + unsigned int marker; +#define STATEPL_M_LIMIT (1 << 0) +#define STATEPL_M_RATE (1 << 1) + + unsigned int limit; + struct { + unsigned int limit; + unsigned int seconds; + } rate; +}; + +static struct statepl_opts statepl_opts; + +struct sourcepl_opts { + unsigned int marker; +#define SOURCEPL_M_LIMIT (1 << 0) +#define SOURCEPL_M_STATES (1 << 1) +#define SOURCEPL_M_RATE (1 << 2) +#define SOURCEPL_M_TABLE (1 << 3) +#define SOURCEPL_M_INET_MASK (1 << 4) +#define SOURCEPL_M_INET6_MASK (1 << 4) + + unsigned int limit; + unsigned int states; + struct { + unsigned int limit; + unsigned int seconds; + } rate; + struct { + const char *name; + unsigned int above; + unsigned int below; + } table; + unsigned int inet_mask; + unsigned int inet6_mask; +}; + +static struct sourcepl_opts sourcepl_opts; + struct node_hfsc_opts hfsc_opts; struct node_state_opt *keep_state_defaults = NULL; struct pfctl_watermarks syncookie_opts; @@ -452,6 +498,8 @@ typedef struct { struct table_opts table_opts; struct pool_opts pool_opts; struct node_hfsc_opts hfsc_opts; + struct statepl_opts *statepl_opts; + struct sourcepl_opts *sourcepl_opts; struct pfctl_watermarks *watermarks; } v; int lineno; @@ -484,12 +532,13 @@ int parseport(char *, struct range *r, i %token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW MAXPKTRATE %token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE %token DIVERTTO DIVERTREPLY DIVERTPACKET NATTO AFTO RDRTO RECEIVEDON NE LE GE +%token POOL RATE SOURCE STATES ABOVE BELOW MASK %token STRING %token NUMBER %token PORTBINARY %type interface if_list if_item_not if_item %type number icmptype icmp6type uid gid -%type tos not yesno optnodf +%type tos not yesno optnodf sourcepl_opt_below %type probability %type optweight %type dir af optimizer syncookie_val @@ -530,6 +579,9 @@ int parseport(char *, struct range *r, i %type scrub_opts scrub_opt scrub_opts_l %type table_opts table_opt table_opts_l %type pool_opts pool_opt pool_opts_l +%type statepl_id sourcepl_id +%type statepl_opts +%type sourcepl_opts %type syncookie_opts %% @@ -537,6 +589,8 @@ ruleset : /* empty */ | ruleset include '\n' | ruleset '\n' | ruleset option '\n' + | ruleset statepl '\n' + | ruleset sourcepl '\n' | ruleset pfrule '\n' | ruleset anchorrule '\n' | ruleset loadrule '\n' @@ -1548,6 +1602,233 @@ bandwidth : STRING { } ; +statepl : statepl_id statepl_opts { + struct pfctl_statepl *stpl; + + if (!ISSET($2->marker, STATEPL_M_LIMIT)) { + yyerror("limit not specified"); + YYERROR; + } + + stpl = calloc(1, sizeof(*stpl)); + if (stpl == NULL) + err(1, "state pool: malloc"); + + stpl->ioc.id = $1; + stpl->ioc.limit = $2->limit; + stpl->ioc.rate.limit = $2->rate.limit; + stpl->ioc.rate.seconds = $2->rate.seconds; + + if (pfctl_add_statepl(pf, stpl) != 0) { + free(stpl); + yyerror("state pool %lld already exists", $1); + YYERROR; + } + } + ; + +statepl_id : STATE POOL NUMBER { + if ($3 < PF_STATEPL_ID_MIN || + $3 > PF_STATEPL_ID_MAX) { + yyerror("state pool %lld: " + "invalid identifier", $3); + YYERROR; + } + $$ = $3; + } + ; + +statepl_opts : /* empty */ { + yyerror("state pool missing options"); + YYERROR; + } + | { + memset(&statepl_opts, 0, sizeof(statepl_opts)); + } statepl_opts_l { + $$ = &statepl_opts; + } + ; + +statepl_opts_l : statepl_opts_l statepl_opt + | statepl_opt + ; + +statepl_opt : LIMIT NUMBER { + if (ISSET(statepl_opts.marker, STATEPL_M_LIMIT)) { + yyerror("limit cannot be respecified"); + YYERROR; + } + + if ($2 < 1) { + yyerror("invalid state pool limit"); + YYERROR; + } + + statepl_opts.limit = $2; + + statepl_opts.marker |= STATEPL_M_LIMIT; + } + | RATE NUMBER '/' NUMBER { + if (ISSET(statepl_opts.marker, STATEPL_M_RATE)) { + yyerror("rate cannot be respecified"); + YYERROR; + } + if ($2 < 1) { + yyerror("invalid rate limit %lld", $2); + YYERROR; + } + if ($4 < 1) { + yyerror("invalid rate seconds %lld", $4); + YYERROR; + } + + statepl_opts.rate.limit = $2; + statepl_opts.rate.seconds = $4; + + statepl_opts.marker |= STATEPL_M_RATE; + } + ; + +sourcepl : sourcepl_id sourcepl_opts { + if (!ISSET($2->marker, SOURCEPL_M_LIMIT)) { + yyerror("limit not specified"); + YYERROR; + } + if (!ISSET($2->marker, SOURCEPL_M_STATES)) { + yyerror("states limit not specified"); + YYERROR; + } + } + ; + +sourcepl_id : SOURCE POOL NUMBER { + if ($3 < PF_SOURCEPL_ID_MIN || + $3 > PF_SOURCEPL_ID_MAX) { + yyerror("source pool %lld: " + "invalid identifier", $3); + YYERROR; + } + $$ = $3; + } + ; + +sourcepl_opts : /* empty */ { + yyerror("source pool missing options"); + YYERROR; + } + | { + memset(&sourcepl_opts, 0, sizeof(sourcepl_opts)); + } sourcepl_opts_l { + $$ = &sourcepl_opts; + } + ; + +sourcepl_opts_l : sourcepl_opts_l sourcepl_opt + | sourcepl_opt + ; + +sourcepl_opt : LIMIT NUMBER { + if (ISSET(sourcepl_opts.marker, SOURCEPL_M_LIMIT)) { + yyerror("limit cannot be respecified"); + YYERROR; + } + + sourcepl_opts.limit = $2; + + sourcepl_opts.marker |= SOURCEPL_M_LIMIT; + } + | STATES NUMBER { + if (ISSET(sourcepl_opts.marker, SOURCEPL_M_STATES)) { + yyerror("source state limit " + "cannot be respecified"); + YYERROR; + } + + sourcepl_opts.states = $2; + + sourcepl_opts.marker |= SOURCEPL_M_STATES; + } + | RATE NUMBER '/' NUMBER { + if (ISSET(sourcepl_opts.marker, SOURCEPL_M_RATE)) { + yyerror("rate cannot be respecified"); + YYERROR; + } + + sourcepl_opts.rate.limit = $2; + sourcepl_opts.rate.seconds = $4; + + sourcepl_opts.marker |= SOURCEPL_M_RATE; + } + | TABLE '<' STRING '>' ABOVE NUMBER sourcepl_opt_below { + if (ISSET(sourcepl_opts.marker, SOURCEPL_M_TABLE)) { + yyerror("rate cannot be respecified"); + YYERROR; + } + + if ($6 < 0) { + yyerror("above limit is invalid"); + YYERROR; + } + if ($7 == 0) + $7 = $6; + else if ($7 > $6) { + yyerror("below limit higher than above limit"); + YYERROR; + } + + sourcepl_opts.table.name = $3; + sourcepl_opts.table.above = $6; + sourcepl_opts.table.below = $7; + + sourcepl_opts.marker |= SOURCEPL_M_TABLE; + } + | INET MASK NUMBER { + if (ISSET(sourcepl_opts.marker, + SOURCEPL_M_INET_MASK)) { + yyerror("inet mask cannot be respecified"); + YYERROR; + } + + if ($3 < 1 || $3 > 32) { + yyerror("inet mask length out of range"); + YYERROR; + } + + sourcepl_opts.inet_mask = $3; + + sourcepl_opts.marker |= SOURCEPL_M_INET_MASK; + } + | INET6 MASK NUMBER { + if (ISSET(sourcepl_opts.marker, + SOURCEPL_M_INET6_MASK)) { + yyerror("inet6 mask cannot be respecified"); + YYERROR; + } + + if ($3 < 1 || $3 > 128) { + yyerror("inet6 mask length out of range"); + YYERROR; + } + + sourcepl_opts.inet6_mask = $3; + + sourcepl_opts.marker |= SOURCEPL_M_INET6_MASK; + } + ; + +sourcepl_opt_below + : /* empty */ { + $$ = 0; + } + | BELOW NUMBER { + if ($2 < 1) { + yyerror("below limit is invalid"); + YYERROR; + } + $$ = $2; + } + ; + pfrule : action dir logquick interface af proto fromto filter_opts { @@ -1763,6 +2044,24 @@ pfrule : action dir logquick interface } r.rule_flag |= PFRULE_PFLOW; break; + case PF_STATE_OPT_STATEPL: + if (r.statepl != PF_STATEPL_ID_NONE) { + yyerror("state pool: " + "multiple definitions"); + YYERROR; + } + /* XXX check if the pool exists */ + r.statepl = o->data.statepl; + break; + case PF_STATE_OPT_SOURCEPL: + if (r.sourcepl != PF_SOURCEPL_ID_NONE) { + yyerror("source pool: " + "multiple definitions"); + YYERROR; + } + /* XXX check if the pool exists */ + r.sourcepl = o->data.sourcepl; + break; case PF_STATE_OPT_TIMEOUT: if (o->data.timeout.number == PFTM_ADAPTIVE_START || @@ -1778,6 +2077,7 @@ pfrule : action dir logquick interface } r.timeout[o->data.timeout.number] = o->data.timeout.seconds; + break; } o = o->next; if (!defaults) @@ -3442,6 +3742,24 @@ state_opt_item : MAXIMUM NUMBER { $$->next = NULL; $$->tail = $$; } + | statepl_id { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_STATEPL; + $$->data.statepl = $1; + $$->next = NULL; + $$->tail = $$; + } + | sourcepl_id { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_SOURCEPL; + $$->data.sourcepl = $1; + $$->next = NULL; + $$->tail = $$; + } | STRING NUMBER { int i; @@ -4932,6 +5250,7 @@ lookup(char *s) { /* this has to be sorted always */ static const struct keywords keywords[] = { + { "above", ABOVE}, { "af-to", AFTO}, { "all", ALL}, { "allow-opts", ALLOWOPTS}, @@ -4939,6 +5258,7 @@ lookup(char *s) { "antispoof", ANTISPOOF}, { "any", ANY}, { "bandwidth", BANDWIDTH}, + { "below", BELOW}, { "binat-to", BINATTO}, { "bitmask", BITMASK}, { "block", BLOCK}, @@ -4979,6 +5299,7 @@ lookup(char *s) { "load", LOAD}, { "log", LOG}, { "loginterface", LOGINTERFACE}, + { "mask", MASK}, { "match", MATCH}, { "matches", MATCHES}, { "max", MAXIMUM}, @@ -5005,6 +5326,7 @@ lookup(char *s) { "parent", PARENT}, { "pass", PASS}, { "pflow", PFLOW}, + { "pool", POOL}, { "port", PORT}, { "prio", PRIO}, { "probability", PROBABILITY}, @@ -5015,6 +5337,7 @@ lookup(char *s) { "quick", QUICK}, { "random", RANDOM}, { "random-id", RANDOMID}, + { "rate", RATE}, { "rdomain", RDOMAIN}, { "rdr-to", RDRTO}, { "reassemble", REASSEMBLE}, @@ -5034,11 +5357,13 @@ lookup(char *s) { "set", SET}, { "skip", SKIP}, { "sloppy", SLOPPY}, + { "source", SOURCE}, { "source-hash", SOURCEHASH}, { "source-track", SOURCETRACK}, { "state", STATE}, { "state-defaults", STATEDEFAULTS}, { "state-policy", STATEPOLICY}, + { "states", STATES}, { "static-port", STATICPORT}, { "sticky-address", STICKYADDRESS}, { "syncookies", SYNCOOKIES}, Index: sbin/pfctl/pfctl.c =================================================================== RCS file: /cvs/src/sbin/pfctl/pfctl.c,v retrieving revision 1.385 diff -u -p -r1.385 pfctl.c --- sbin/pfctl/pfctl.c 11 Nov 2021 12:49:53 -0000 1.385 +++ sbin/pfctl/pfctl.c 5 Mar 2022 07:34:07 -0000 @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -53,6 +54,7 @@ #include #include #include +#include #include #include "pfctl_parser.h" @@ -86,6 +88,7 @@ int pfctl_load_reassembly(struct pfctl int pfctl_load_syncookies(struct pfctl *, u_int8_t); int pfctl_set_synflwats(struct pfctl *, u_int32_t, u_int32_t); void pfctl_print_rule_counters(struct pf_rule *, int); +int pfctl_show_statepls(int, enum pfctl_show); int pfctl_show_rules(int, char *, int, enum pfctl_show, char *, int, int, long); int pfctl_show_src_nodes(int, int); @@ -101,6 +104,8 @@ void pfctl_load_queue(struct pfctl *, u int pfctl_load_queues(struct pfctl *); u_int pfctl_leafqueue_check(char *); u_int pfctl_check_qassignments(struct pf_ruleset *); +void pfctl_load_statepls(struct pfctl *); +void pfctl_load_statepl(struct pfctl *, struct pfctl_statepl *); int pfctl_load_ruleset(struct pfctl *, char *, struct pf_ruleset *, int); int pfctl_load_rule(struct pfctl *, char *, struct pf_rule *, int); const char *pfctl_lookup_option(char *, const char **); @@ -119,6 +124,9 @@ int pfctl_call_clearrules(int, int, stru int pfctl_call_cleartables(int, int, struct pfr_anchoritem *); int pfctl_call_clearanchors(int, int, struct pfr_anchoritem *); +RBT_PROTOTYPE(pfctl_statepls, pfctl_statepl, entry, pfctl_statepl_cmp); +RBT_PROTOTYPE(pfctl_sourcepls, pfctl_sourcepl, entry, pfctl_sourcepl_cmp); + const char *clearopt; char *rulesopt; const char *showopt; @@ -226,7 +234,7 @@ static const char *clearopt_list[] = { static const char *showopt_list[] = { "queue", "rules", "Anchors", "Sources", "states", "info", "Interfaces", "labels", "timeouts", "memory", "Tables", "osfp", - "all", NULL + "pools", "all", NULL }; static const char *tblcmdopt_list[] = { @@ -828,6 +836,48 @@ pfctl_print_title(char *title) } int +pfctl_show_statepls(int dev, enum pfctl_show format) +{ + struct pfioc_statepl stpl; + uint32_t id = PF_STATEPL_ID_MIN; + + if (format == PFCTL_SHOW_LABELS) { + printf("%3s %8s/%-8s %8s %8s %8s\n", "ID", "USE", "LIMIT", + "ADMIT", "HARDLIM", "RATELIM"); + } + + for (;;) { + memset(&stpl, 0, sizeof(stpl)); + stpl.id = id; + + if (ioctl(dev, DIOCGETNSTATEPL, &stpl) == -1) { + if (errno == ESRCH) { + /* we're done */ + return (0); + } + warn("DIOCGETNSTATEPL %u", stpl.id); + return (-1); + } + + switch (format) { + case PFCTL_SHOW_RULES: + print_statepl(&stpl); + break; + case PFCTL_SHOW_LABELS: + printf("%3u %8u/%-8u %8llu %8llu %8llu\n", stpl.id, + stpl.inuse, stpl.limit, + stpl.admitted, stpl.hardlimited, stpl.ratelimited); + break; + default: + errx(1, "%s: unexpected format %d", __func__, format); + /* NOTREACHED */ + } + + id = stpl.id + 1; + } +} + +int pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format, char *anchorname, int depth, int wildcard, long shownr) { @@ -836,6 +886,12 @@ pfctl_show_rules(int dev, char *path, in int len = strlen(path), ret = 0; char *npath, *p; + if (anchorname[0] == '\0') { + ret = pfctl_show_statepls(dev, format); + if (ret != 0) + goto error; + } + /* * Truncate a trailing / and * on an anchorname before searching for * the ruleset, this is syntactic sugar that doesn't actually make it @@ -1418,6 +1474,36 @@ pfctl_check_qassignments(struct pf_rules return (errs); } +void +pfctl_load_statepl(struct pfctl *pf, struct pfctl_statepl *stpl) +{ + if (pf->opts & PF_OPT_VERBOSE) + print_statepl(&stpl->ioc); + + if (pf->opts & PF_OPT_NOACTION) + return; + + if (ioctl(pf->dev, DIOCADDSTATEPL, &stpl->ioc) == -1) + err(1, "DIOCADDSTATEPL %u", stpl->ioc.id); +} + +void +pfctl_load_statepls(struct pfctl *pf) +{ + struct pfctl_statepl *stpl; + u_int32_t ticket = 0; + + if ((pf->opts & PF_OPT_NOACTION) == 0) + ticket = pfctl_get_ticket(pf->trans, PF_TRANS_RULESET, ""); + + RBT_FOREACH(stpl, pfctl_statepls, &pf->statepls) { + stpl->ioc.ticket = ticket; + pfctl_load_statepl(pf, stpl); + } + + /* XXX should this free the statepls? */ +} + int pfctl_load_ruleset(struct pfctl *pf, char *path, struct pf_ruleset *rs, int depth) @@ -1540,8 +1626,6 @@ pfctl_rules(int dev, char *filename, int int osize; char *p; - bzero(&pf, sizeof(pf)); - RB_INIT(&pf_anchors); memset(&pf_main_anchor, 0, sizeof(pf_main_anchor)); pf_init_ruleset(&pf_main_anchor.ruleset); if (trans == NULL) { @@ -1565,6 +1649,9 @@ pfctl_rules(int dev, char *filename, int pf.opts = opts; pf.optimize = optimize; + RBT_INIT(pfctl_statepls, &pf.statepls); + RBT_INIT(pfctl_sourcepls, &pf.sourcepls); + /* non-brace anchor, create without resolving the path */ if ((pf.anchor = calloc(1, sizeof(*pf.anchor))) == NULL) ERRX("pfctl_rules: calloc"); @@ -1610,12 +1697,16 @@ pfctl_rules(int dev, char *filename, int goto _error; } - if (!anchorname[0] && (pfctl_check_qassignments(&pf.anchor->ruleset) || - pfctl_load_queues(&pf))) { - if ((opts & PF_OPT_NOACTION) == 0) - ERRX("Unable to load queues into kernel"); - else - goto _error; + if (anchorname[0] == '\0') { + if (pfctl_check_qassignments(&pf.anchor->ruleset) || + pfctl_load_queues(&pf)) { + if ((opts & PF_OPT_NOACTION) == 0) + ERRX("Unable to load queues into kernel"); + else + goto _error; + } + + pfctl_load_statepls(&pf); } if (pfctl_load_ruleset(&pf, path, rs, 0)) { @@ -2750,6 +2841,8 @@ main(int argc, char *argv[]) case 'I': pfctl_show_ifaces(ifaceopt, opts); break; + case 'p': + pfctl_show_statepls(dev, PFCTL_SHOW_LABELS); } } @@ -2904,4 +2997,50 @@ pf_strerror(int errnum) default: return strerror(errnum); } +} + +static inline int +pfctl_statepl_cmp(const struct pfctl_statepl *a, + const struct pfctl_statepl *b) +{ + uint32_t ida = a->ioc.id; + uint32_t idb = b->ioc.id; + + if (ida > idb) + return (1); + if (ida < idb) + return (-1); + + return (0); +} + +RBT_GENERATE(pfctl_statepls, pfctl_statepl, entry, pfctl_statepl_cmp); + +int +pfctl_add_statepl(struct pfctl *pf, struct pfctl_statepl *stpl) +{ + return (RBT_INSERT(pfctl_statepls, &pf->statepls, stpl) != NULL); +} + +static inline int +pfctl_sourcepl_cmp(const struct pfctl_sourcepl *a, + const struct pfctl_sourcepl *b) +{ + uint32_t ida = a->ioc.id; + uint32_t idb = b->ioc.id; + + if (ida > idb) + return (1); + if (ida < idb) + return (-1); + + return (0); +} + +RBT_GENERATE(pfctl_sourcepls, pfctl_sourcepl, entry, pfctl_sourcepl_cmp); + +int +pfctl_add_sourcepl(struct pfctl *pf, struct pfctl_sourcepl *srcpl) +{ + return (RBT_INSERT(pfctl_sourcepls, &pf->sourcepls, srcpl) != NULL); } Index: sbin/pfctl/pfctl_parser.c =================================================================== RCS file: /cvs/src/sbin/pfctl/pfctl_parser.c,v retrieving revision 1.346 diff -u -p -r1.346 pfctl_parser.c --- sbin/pfctl/pfctl_parser.c 1 Feb 2021 00:31:04 -0000 1.346 +++ sbin/pfctl/pfctl_parser.c 5 Mar 2022 07:34:07 -0000 @@ -706,6 +706,18 @@ print_src_node(struct pf_src_node *sn, i } void +print_statepl(const struct pfioc_statepl *ioc) +{ + printf("state pool %u limit %u", ioc->id, ioc->limit); + if (ioc->rate.limit != 0) + printf(" rate %u/%u", ioc->rate.limit, ioc->rate.seconds); + if (ioc->description[0] != '\0') + printf(" description \"%s\"", ioc->description); + + printf("\n"); +} + +void print_rule(struct pf_rule *r, const char *anchor_call, int opts) { static const char *actiontypes[] = { "pass", "block", "scrub", @@ -924,6 +936,10 @@ print_rule(struct pf_rule *r, const char ropts = 1; if (r->rule_flag & PFRULE_PFLOW) ropts = 1; + if (r->statepl != PF_STATEPL_ID_NONE) + ropts = 1; + if (r->sourcepl != PF_SOURCEPL_ID_NONE) + ropts = 1; for (i = 0; !ropts && i < PFTM_MAX; ++i) if (r->timeout[i]) ropts = 1; @@ -1024,6 +1040,18 @@ print_rule(struct pf_rule *r, const char if (!ropts) printf(", "); printf("pflow"); + ropts = 0; + } + if (r->statepl != PF_STATEPL_ID_NONE) { + if (!ropts) + printf(", "); + printf("state pool %u", r->statepl); + ropts = 0; + } + if (r->sourcepl != PF_SOURCEPL_ID_NONE) { + if (!ropts) + printf(", "); + printf("source pool %u", r->sourcepl); ropts = 0; } for (i = 0; i < PFTM_MAX; ++i) Index: sbin/pfctl/pfctl_parser.h =================================================================== RCS file: /cvs/src/sbin/pfctl/pfctl_parser.h,v retrieving revision 1.118 diff -u -p -r1.118 pfctl_parser.h --- sbin/pfctl/pfctl_parser.h 25 Oct 2021 14:50:29 -0000 1.118 +++ sbin/pfctl/pfctl_parser.h 5 Mar 2022 07:34:07 -0000 @@ -70,6 +70,19 @@ struct pfr_buffer; /* forward definition */ +struct pfctl_statepl { + struct pfioc_statepl ioc; + RBT_ENTRY(pfctl_statepl) entry; +}; + +RBT_HEAD(pfctl_statepls, pfctl_statepl); + +struct pfctl_sourcepl { + struct pfioc_sourcepl ioc; + RBT_ENTRY(pfctl_sourcepl) entry; +}; + +RBT_HEAD(pfctl_sourcepls, pfctl_sourcepl); struct pfctl { int dev; @@ -81,11 +94,13 @@ struct pfctl { int tdirty; /* kernel dirty */ #define PFCTL_ANCHOR_STACK_DEPTH 64 struct pf_anchor *astack[PFCTL_ANCHOR_STACK_DEPTH]; - struct pfioc_queue *pqueue; struct pfr_buffer *trans; struct pf_anchor *anchor, *alast; const char *ruleset; + struct pfctl_statepls statepls; + struct pfctl_sourcepls sourcepls; + /* 'set foo' options */ u_int32_t timeout[PFTM_MAX]; u_int32_t limit[PF_LIMIT_MAX]; @@ -220,6 +235,9 @@ int add_opt_table(struct pfctl *, st void pfctl_add_rule(struct pfctl *, struct pf_rule *); +int pfctl_add_statepl(struct pfctl *, struct pfctl_statepl *); +int pfctl_add_sourcepl(struct pfctl *, struct pfctl_sourcepl *); + int pfctl_set_timeout(struct pfctl *, const char *, int, int); int pfctl_set_reassembly(struct pfctl *, int, int); int pfctl_set_syncookies(struct pfctl *, u_int8_t, @@ -241,6 +259,7 @@ struct pfctl_qsitem * pfctl_find_queue(c void print_pool(struct pf_pool *, u_int16_t, u_int16_t, sa_family_t, int, int); void print_src_node(struct pf_src_node *, int); +void print_statepl(const struct pfioc_statepl *); void print_rule(struct pf_rule *, const char *, int); void print_tabledef(const char *, int, int, struct node_tinithead *); void print_status(struct pf_status *, struct pfctl_watermarks *, int);