Index: sys/net/pf.c =================================================================== RCS file: /cvs/src/sys/net/pf.c,v retrieving revision 1.1125 diff -u -p -r1.1125 pf.c --- sys/net/pf.c 5 Mar 2022 10:43:32 -0000 1.1125 +++ sys/net/pf.c 8 Mar 2022 10:59:26 -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_source *source; 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, pf_source_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,8 @@ 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 *, struct pf_source *); static __inline int pf_state_key_addr_setup(struct pf_pdesc *, void *, int, struct pf_addr *, int, struct pf_addr *, int, int); @@ -213,6 +219,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 +317,246 @@ 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 int +pf_sourcepl_cmp(const struct pf_sourcepl *a, const struct pf_sourcepl *b) +{ + if (a->pfsrpl_id > b->pfsrpl_id) + return (1); + if (a->pfsrpl_id < b->pfsrpl_id) + return (-1); + + return (0); +} + +RBT_GENERATE(pf_sourcepl_tree, pf_sourcepl, pfsrpl_tree, pf_sourcepl_cmp); + +static inline int +pf_source_cmp(const struct pf_source *a, const struct pf_source *b) +{ + if (a->pfsr_af > b->pfsr_af) + return (1); + if (a->pfsr_af < b->pfsr_af) + return (-1); + + return (pf_addr_compare(&a->pfsr_addr, &b->pfsr_addr, a->pfsr_af)); +} + +RBT_GENERATE(pf_source_tree, pf_source, pfsr_tree, pf_source_cmp); + +static inline int +pf_source_ioc_cmp(const struct pf_source *a, const struct pf_source *b) +{ + size_t i; + + if (a->pfsr_af > b->pfsr_af) + return (1); + if (a->pfsr_af < b->pfsr_af) + return (-1); + + for (i = 0; i < nitems(a->pfsr_addr.addr32); i++) { + uint32_t wa = ntohl(a->pfsr_addr.addr32[i]); + uint32_t wb = ntohl(b->pfsr_addr.addr32[i]); + + if (wa > wb) + return (1); + if (wa < wb) + return (-1); + } + + return (0); +} + +RBT_GENERATE(pf_source_ioc_tree, pf_source, pfsr_ioc_tree, pf_source_ioc_cmp); + +struct pf_sourcepl_tree pf_sourcepl_tree_active = + RBT_INITIALIZER(pf_sourcepl_tree_active); +struct pf_sourcepl_list pf_sourcepl_list_active = + TAILQ_HEAD_INITIALIZER(pf_sourcepl_list_active); + +struct pf_sourcepl_tree pf_sourcepl_tree_inactive = + RBT_INITIALIZER(pf_sourcepl_tree_inactive); +struct pf_sourcepl_list pf_sourcepl_list_inactive = + TAILQ_HEAD_INITIALIZER(pf_sourcepl_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)); +} + +static inline struct pf_sourcepl * +pf_sourcepl_find(uint32_t id) +{ + struct pf_sourcepl key; + + /* only the id is used in cmp, so don't have to zero all the things */ + key.pfsrpl_id = id; + + return (RBT_FIND(pf_sourcepl_tree, &pf_sourcepl_tree_active, &key)); +} + +struct pf_source_list pf_source_gc = TAILQ_HEAD_INITIALIZER(pf_source_gc); + +static void +pf_source_purge(void) +{ + struct pf_source *sr, *nsr; + time_t now = getuptime(); + + TAILQ_FOREACH_SAFE(sr, &pf_source_gc, pfsr_empty_gc, nsr) { + struct pf_sourcepl *srpl; + + if (now <= sr->pfsr_empty_ts + 1) + continue; + + TAILQ_REMOVE(&pf_source_gc, sr, pfsr_empty_gc); + + srpl = sr->pfsr_parent; + RBT_REMOVE(pf_source_tree, &srpl->pfsrpl_sources, sr); + RBT_REMOVE(pf_source_ioc_tree, &srpl->pfsrpl_ioc_sources, sr); + srpl->pfsrpl_nsources--; + + pool_put(&pf_source_pl, sr); + } +} + +static void +pf_source_pfr_addr(struct pfr_addr *p, const struct pf_source *sr) +{ + struct pf_sourcepl *srpl = sr->pfsr_parent; + + memset(p, 0, sizeof(*p)); + + p->pfra_af = sr->pfsr_af; + switch (sr->pfsr_af) { + case AF_INET: + p->pfra_net = srpl->pfsrpl_ipv4_prefix; + p->pfra_ip4addr = sr->pfsr_addr.v4; + break; +#ifdef INET6 + case AF_INET6: + p->pfra_net = srpl->pfsrpl_ipv6_prefix; + p->pfra_ip6addr = sr->pfsr_addr.v6; + break; +#endif /* INET6 */ + } +} + +static void +pf_source_used(struct pf_source *sr) +{ + struct pf_sourcepl *srpl = sr->pfsr_parent; + struct pfr_ktable *t; + unsigned int used; + + used = sr->pfsr_inuse++; + sr->pfsr_rate_ts += srpl->pfsrpl_rate_token; + + if (used == 0) + TAILQ_REMOVE(&pf_source_gc, sr, pfsr_empty_gc); + else if ((t = srpl->pfsrpl_overload.table) != NULL && + used >= srpl->pfsrpl_overload.hwm && !sr->pfsr_intable) { + struct pfr_addr p; + + pf_source_pfr_addr(&p, sr); + + pfr_insert_kentry(t, &p, gettime()); + sr->pfsr_intable = 1; + } +} + +static void +pf_source_rele(struct pf_source *sr) +{ + struct pf_sourcepl *srpl = sr->pfsr_parent; + struct pfr_ktable *t; + unsigned int used; + + used = --sr->pfsr_inuse; + + t = srpl->pfsrpl_overload.table; + if (t != NULL && sr->pfsr_intable && + used < srpl->pfsrpl_overload.lwm) { + struct pfr_addr p; + + pf_source_pfr_addr(&p, sr); + + pfr_remove_kentry(t, &p); + sr->pfsr_intable = 0; + } + + if (used == 0) { + TAILQ_INSERT_TAIL(&pf_source_gc, sr, pfsr_empty_gc); + sr->pfsr_empty_ts = getuptime() + srpl->pfsrpl_rate.seconds; + } +} + +static void +pf_source_key(struct pf_sourcepl *srpl, struct pf_source *key, + sa_family_t af, const struct pf_addr *addr) +{ + size_t i; + + /* only af+addr is used for lookup. */ + key->pfsr_af = af; + switch (af) { + case AF_INET: + key->pfsr_addr.addr32[0] = + srpl->pfsrpl_ipv4_mask.v4.s_addr & + addr->v4.s_addr; + + for (i = 1; i < nitems(key->pfsr_addr.addr32); i++) + key->pfsr_addr.addr32[i] = htonl(0); + break; +#ifdef INET6 + case AF_INET6: + for (i = 0; i < nitems(key->pfsr_addr.addr32); i++) { + key->pfsr_addr.addr32[i] = + srpl->pfsrpl_ipv6_mask.addr32[i] & + addr->addr32[i]; + } + break; +#endif + default: + unhandled_af(af); + /* NOTREACHED */ + } +} + +static inline struct pf_source * +pf_source_find(struct pf_sourcepl *srpl, const struct pf_source *key) +{ + return (RBT_FIND(pf_source_tree, &srpl->pfsrpl_sources, key)); +} + struct pf_src_tree tree_src_tracking; struct pf_state_tree_id tree_id; @@ -323,7 +571,8 @@ SLIST_HEAD(pf_rule_gcl, pf_rule) pf_rule SLIST_HEAD_INITIALIZER(pf_rule_gcl); __inline int -pf_addr_compare(struct pf_addr *a, struct pf_addr *b, sa_family_t af) +pf_addr_compare(const struct pf_addr *a, const struct pf_addr *b, + sa_family_t af) { switch (af) { case AF_INET: @@ -1309,6 +1558,8 @@ pf_purge(void *xnloops) if (++(*nloops) >= pf_default_rule.timeout[PFTM_INTERVAL]) { pf_purge_expired_src_nodes(); pf_purge_expired_rules(); + + pf_source_purge(); } PF_UNLOCK(); @@ -1418,6 +1669,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 */ @@ -1436,6 +1689,58 @@ pf_remove_state(struct pf_state *cur) if (cur->key[PF_SK_STACK]->proto == IPPROTO_TCP) pf_set_protostate(cur, PF_PEER_BOTH, TCPS_CLOSED); + 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; + } + case PF_STATE_LINK_TYPE_SOURCEPL: { + struct pf_sourcepl *srpl; + struct pf_source key, *sr; + + srpl = pf_sourcepl_find(cur->sourcepl); + KASSERTMSG(srpl != NULL, + "pf_state %p pfl %p cannot find sourcepl %u", + cur, pfl, cur->sourcepl); + + pf_source_key(srpl, &key, + cur->key[PF_SK_WIRE]->af, + &cur->key[PF_SK_WIRE]->addr[1]); + + sr = pf_source_find(srpl, &key); + KASSERTMSG(sr != NULL, + "pf_state %p pfl %p cannot find source in %u", + cur, pfl, cur->sourcepl); + + srpl->pfsrpl_counters.inuse--; + pf_source_rele(sr); + + list = &sr->pfsr_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; + RB_REMOVE(pf_state_tree_id, &tree_id, cur); #if NPFLOW > 0 if (cur->state_flags & PFSTATE_PFLOW) @@ -1445,6 +1750,7 @@ pf_remove_state(struct pf_state *cur) pfsync_delete_state(cur); #endif /* NPFSYNC > 0 */ cur->timeout = PFTM_UNLINKED; + pf_src_tree_remove_state(cur); pf_detach_state(cur); } @@ -3609,6 +3915,8 @@ pf_match_rule(struct pf_test_ctx *ctx, s r = TAILQ_FIRST(ruleset->rules.active.ptr); while (r != NULL) { + struct pf_statepl *stpl = NULL; + struct pf_source *sr = NULL; r->evaluations++; PF_TEST_ATTRIB( (pfi_kif_match(r->kif, ctx->pd->kif) == r->ifnot), @@ -3722,6 +4030,146 @@ 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; + } + + /* + * Is access to the pool rate limited? + */ + 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; + */ + } + + /* + * stpl->pfstpl_inuse++; + */ + } + + if (r->sourcepl != PF_SOURCEPL_ID_NONE) { + struct pf_source key; + struct pf_sourcepl *srpl = + pf_sourcepl_find(r->sourcepl); + + /* + * 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(srpl == NULL, + TAILQ_NEXT(r, entries)); + + pf_source_key(srpl, &key, ctx->pd->af, ctx->pd->src); + + sr = pf_source_find(srpl, &key); + if (sr == NULL) { + if (srpl->pfsrpl_nsources >= + srpl->pfsrpl_limit) { + srpl->pfsrpl_counters.addrlimited++; + r = TAILQ_NEXT(r, entries); + continue; + } + + sr = pool_get(&pf_source_pl, + PR_NOWAIT|PR_ZERO); + if (sr == NULL) { + srpl->pfsrpl_counters.addrnomem++; + REASON_SET(&ctx->reason, PFRES_MEMORY); + ctx->test_status = PF_TEST_FAIL; + break; + } + + sr->pfsr_parent = srpl; + sr->pfsr_af = key.pfsr_af; + sr->pfsr_addr = key.pfsr_addr; + TAILQ_INIT(&sr->pfsr_links); + + if (RBT_INSERT(pf_source_tree, + &srpl->pfsrpl_sources, sr) != NULL) { + panic("%s: insert collision?!", + __func__); + } + + if (RBT_INSERT(pf_source_ioc_tree, + &srpl->pfsrpl_ioc_sources, sr) != NULL) { + panic("%s: insert collision?!", + __func__); + } + + sr->pfsr_empty_ts = getuptime(); + TAILQ_INSERT_TAIL(&pf_source_gc, sr, + pfsr_empty_gc); + + srpl->pfsrpl_nsources++; + srpl->pfsrpl_counters.addrallocs++; + } + + /* + * An overcommitted pool means this rule + * can't create state. + */ + if (sr->pfsr_inuse >= srpl->pfsrpl_states) { + sr->pfsr_counters.hardlimited++; + srpl->pfsrpl_counters.hardlimited++; + r = TAILQ_NEXT(r, entries); + continue; + } + + /* + * Is access to the pool rate limited? + */ + if (srpl->pfsrpl_rate.limit != 0) { + uint64_t ts = getnsecuptime(); + uint64_t diff = ts - sr->pfsr_rate_ts; + + if (diff < srpl->pfsrpl_rate_token) { + sr->pfsr_counters.ratelimited++; + srpl->pfsrpl_counters.ratelimited++; + r = TAILQ_NEXT(r, entries); + continue; + } + + if (diff > srpl->pfsrpl_rate_bucket) { + sr->pfsr_rate_ts = + ts - srpl->pfsrpl_rate_bucket; + } + } + } + /* must be last! */ if (r->pktrate.limit) { pf_add_threshold(&r->pktrate); @@ -3778,6 +4226,12 @@ pf_match_rule(struct pf_test_ctx *ctx, s * ruleset, where anchor belongs to. */ ctx->arsm = ctx->aruleset; + + /* + * state/source pools + */ + ctx->statepl = stpl; + ctx->source = sr; } #if NPFLOG > 0 @@ -3971,6 +4425,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 +4438,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, ctx.source); 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; @@ -4066,11 +4531,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_source *sr) { 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 +4631,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 +4674,46 @@ 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; + } + + stpl->pfstpl_inuse++; + stpl->pfstpl_rate_ts += stpl->pfstpl_rate_token; + stpl->pfstpl_counters.admitted++; + + 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); + } + + if (sr != NULL) { + struct pf_sourcepl *srpl = sr->pfsr_parent; + + pfl = pool_get(&pf_state_link_pl, PR_NOWAIT); + if (pfl == NULL) { + REASON_SET(&reason, PFRES_MEMORY); + goto csfailed; + } + + pf_source_used(sr); + + sr->pfsr_counters.admitted++; + srpl->pfsrpl_counters.inuse++; + srpl->pfsrpl_counters.admitted++; + + s->sourcepl = srpl->pfsrpl_id; + pfl->pfl_state = s; + + TAILQ_INSERT_TAIL(&sr->pfsr_links, pfl, pfl_entry); + pf_state_link_insert(s, pfl, PF_STATE_LINK_TYPE_SOURCEPL); + } + if (pf_state_insert(BOUND_IFACE(r, pd->kif), skw, sks, s)) { pf_detach_state(s); *sks = *skw = NULL; @@ -4228,29 +4733,48 @@ 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: + stpl->pfstpl_inuse--; + stpl->pfstpl_rate_ts -= + stpl->pfstpl_rate_token; + list = &stpl->pfstpl_links; + break; + case PF_STATE_LINK_TYPE_SOURCEPL: { + struct pf_sourcepl *srpl = sr->pfsr_parent; + + pf_source_rele(sr); + sr->pfsr_rate_ts -= + srpl->pfsrpl_rate_token; + + srpl->pfsrpl_counters.inuse--; + + list = &sr->pfsr_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 +4787,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 +8192,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 +8229,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 8 Mar 2022 10:59:26 -0000 @@ -86,6 +86,8 @@ #endif /* NPFSYNC > 0 */ struct pool pf_tag_pl; +extern struct pool pf_statepl_pl, pf_sourcepl_pl, pf_source_pl; +extern struct pool pf_state_link_pl; void pfattach(int); void pf_thread_create(void *); @@ -188,6 +190,15 @@ 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_source_pl, sizeof(struct pf_source), 0, + IPL_SOFTNET, 0, "pfsrc", 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 +935,759 @@ 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 */ + + 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; + + opfstpl->pfstpl_rate_ts = + pfstpl->pfstpl_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); +} + +static void +pf_sourcepl_unlink(struct pf_sourcepl *pfsrpl, + struct pf_state_linkage *garbage) +{ + extern struct pf_source_list pf_source_gc; + struct pf_source *pfsr; + struct pf_state_link *pfl; + + PF_STATE_ENTER_WRITE(); + + while ((pfsr = RBT_ROOT(pf_source_tree, + &pfsrpl->pfsrpl_sources)) != NULL) { + RBT_REMOVE(pf_source_tree, + &pfsrpl->pfsrpl_sources, pfsr); + RBT_REMOVE(pf_source_ioc_tree, + &pfsrpl->pfsrpl_ioc_sources, pfsr); + if (pfsr->pfsr_inuse == 0) + TAILQ_REMOVE(&pf_source_gc, pfsr, pfsr_empty_gc); + + /* unwire the links */ + TAILQ_FOREACH(pfl, &pfsr->pfsr_links, pfl_entry) { + struct pf_state *s = pfl->pfl_state; + + /* if !rmst */ + s->sourcepl = 0; + pf_state_link_remove(s, pfl); + } + + /* take the list away */ + TAILQ_CONCAT(garbage, &pfsr->pfsr_links, pfl_entry); + + pool_put(&pf_source_pl, pfsr); + } + + PF_STATE_EXIT_WRITE(); +} + +int +pf_sourcepl_check(void) +{ + struct pf_sourcepl *pfsrpl, *npfsrpl; + + PF_ASSERT_LOCKED(); + NET_ASSERT_LOCKED(); + + /* check if we can merge */ + + TAILQ_FOREACH(pfsrpl, &pf_sourcepl_list_inactive, pfsrpl_list) { + npfsrpl = RBT_FIND(pf_sourcepl_tree, + &pf_sourcepl_tree_active, pfsrpl); + + /* new config, no conflict */ + if (npfsrpl == NULL) + continue; + + /* nothing is tracked at the moment, no conflict */ + if (RBT_EMPTY(pf_source_tree, &npfsrpl->pfsrpl_sources)) + continue; + + if (strcmp(npfsrpl->pfsrpl_overload.name, + pfsrpl->pfsrpl_overload.name) != 0) + return (EBUSY); + + /* + * we should allow the prefixlens to get shorter + * and merge pf_source entries. + */ + + if ((npfsrpl->pfsrpl_ipv4_prefix != + pfsrpl->pfsrpl_ipv4_prefix) || + (npfsrpl->pfsrpl_ipv6_prefix != + pfsrpl->pfsrpl_ipv6_prefix)) + return (EBUSY); + } + + return (0); +} + +void +pf_sourcepl_commit(void) +{ + struct pf_sourcepl *pfsrpl, *npfsrpl, *opfsrpl; + struct pf_sourcepl_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 sourcepls into the current set */ + + /* start with an empty active list */ + TAILQ_CONCAT(&l, &pf_sourcepl_list_active, pfsrpl_list); + + /* beware, the inactive bits gets messed up here */ + + /* try putting pending sourcepls into the active tree */ + TAILQ_FOREACH_SAFE(pfsrpl, &pf_sourcepl_list_inactive, + pfsrpl_list, npfsrpl) { + opfsrpl = RBT_INSERT(pf_sourcepl_tree, + &pf_sourcepl_tree_active, pfsrpl); + if (opfsrpl != NULL) { + /* this sourcepl already exists, merge */ + opfsrpl->pfsrpl_limit = + pfsrpl->pfsrpl_limit; + opfsrpl->pfsrpl_states = + pfsrpl->pfsrpl_states; + opfsrpl->pfsrpl_ipv4_prefix = + pfsrpl->pfsrpl_ipv4_prefix; + opfsrpl->pfsrpl_ipv6_prefix = + pfsrpl->pfsrpl_ipv6_prefix; + opfsrpl->pfsrpl_rate.limit = + pfsrpl->pfsrpl_rate.limit; + opfsrpl->pfsrpl_rate.seconds = + pfsrpl->pfsrpl_rate.seconds; + + opfsrpl->pfsrpl_ipv4_mask = + pfsrpl->pfsrpl_ipv4_mask; + opfsrpl->pfsrpl_ipv6_mask = + pfsrpl->pfsrpl_ipv6_mask; + +#if 0 + opfstpl->pfstpl_rate_ts = + pfstpl->pfstpl_rate_ts; +#endif + opfsrpl->pfsrpl_rate_token = + pfsrpl->pfsrpl_rate_token; + opfsrpl->pfsrpl_rate_bucket = + pfsrpl->pfsrpl_rate_bucket; + + if (opfsrpl->pfsrpl_overload.table != NULL) { + pfr_detach_table( + opfsrpl->pfsrpl_overload.table); + } + + strlcpy(opfsrpl->pfsrpl_overload.name, + pfsrpl->pfsrpl_overload.name, + sizeof(opfsrpl->pfsrpl_overload.name)); + opfsrpl->pfsrpl_overload.hwm = + pfsrpl->pfsrpl_overload.hwm; + opfsrpl->pfsrpl_overload.lwm = + pfsrpl->pfsrpl_overload.lwm; + opfsrpl->pfsrpl_overload.table = + pfsrpl->pfsrpl_overload.table, + + strlcpy(opfsrpl->pfsrpl_description, + pfsrpl->pfsrpl_description, + sizeof(opfsrpl->pfsrpl_description)); + + /* use the existing sourcepl instead */ + pool_put(&pf_sourcepl_pl, pfsrpl); + TAILQ_REMOVE(&l, opfsrpl, pfsrpl_list); + pfsrpl = opfsrpl; + } + + TAILQ_INSERT_TAIL(&pf_sourcepl_list_active, + pfsrpl, pfsrpl_list); + } + + /* clean up the now unused sourcepls from the old set */ + TAILQ_FOREACH_SAFE(pfsrpl, &l, pfsrpl_list, npfsrpl) { + pf_sourcepl_unlink(pfsrpl, &garbage); + + RBT_REMOVE(pf_sourcepl_tree, + &pf_sourcepl_tree_active, pfsrpl); + + if (pfsrpl->pfsrpl_overload.table != NULL) + pfr_detach_table(pfsrpl->pfsrpl_overload.table); + pool_put(&pf_sourcepl_pl, pfsrpl); + } + + /* fix up the inactive tree */ + RBT_INIT(pf_sourcepl_tree, &pf_sourcepl_tree_inactive); + TAILQ_INIT(&pf_sourcepl_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->id = pfstpl->pfstpl_id; + 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_sourcepl_add(const struct pfioc_sourcepl *ioc) +{ + struct pf_sourcepl *pfsrpl; + int error; + size_t descrlen, tablelen; + uint64_t product; + unsigned int prefix; + size_t i; + + if (ioc->id < PF_SOURCEPL_ID_MIN || + ioc->id > PF_SOURCEPL_ID_MAX) + return (EINVAL); + + if (ioc->limit < 1) + return (EINVAL); + + if (ioc->states < 1) + return (EINVAL); + + /* XXX does this make sense? */ + product = ioc->limit * ioc->states; + if (product > (1 << 24)) + return (EINVAL); + + if ((ioc->rate.limit == 0) != (ioc->rate.seconds == 0)) + return (EINVAL); + + if (ioc->inet_prefix > 32) + return (EINVAL); + if (ioc->inet6_prefix > 128) + return (EINVAL); + + /* XXX check rate */ + + descrlen = strnlen(ioc->description, sizeof(ioc->description)); + if (descrlen == sizeof(ioc->description)) + return (EINVAL); + + tablelen = strnlen(ioc->overload_tblname, + sizeof(ioc->overload_tblname)); + if (tablelen == sizeof(ioc->overload_tblname)) + return (EINVAL); + if (tablelen != 0) { + if (ioc->overload_hwm == 0) + return (EINVAL); + + /* + * this is stupid, but not harmful? + * + * if (ioc->states < ioc->overload_hwm) + * return (EINVAL); + */ + + if (ioc->overload_hwm < ioc->overload_lwm) + return (EINVAL); + } + + pfsrpl = pool_get(&pf_sourcepl_pl, PR_WAITOK|PR_ZERO); + if (pfsrpl == NULL) + return (ENOMEM); + + pfsrpl->pfsrpl_id = ioc->id; + pfsrpl->pfsrpl_limit = ioc->limit; + pfsrpl->pfsrpl_states = ioc->states; + pfsrpl->pfsrpl_ipv4_prefix = ioc->inet_prefix; + pfsrpl->pfsrpl_ipv6_prefix = ioc->inet6_prefix; + pfsrpl->pfsrpl_rate.limit = ioc->rate.limit; + pfsrpl->pfsrpl_rate.seconds = ioc->rate.seconds; + memcpy(pfsrpl->pfsrpl_overload.name, ioc->overload_tblname, tablelen); + pfsrpl->pfsrpl_overload.hwm = ioc->overload_hwm; + pfsrpl->pfsrpl_overload.lwm = ioc->overload_lwm; + memcpy(pfsrpl->pfsrpl_description, ioc->description, descrlen); + + if (pfsrpl->pfsrpl_rate.limit) { + uint64_t bucket = pfsrpl->pfsrpl_rate.seconds * 1000000000ULL; + + pfsrpl->pfsrpl_rate_token = bucket / pfsrpl->pfsrpl_rate.limit; + pfsrpl->pfsrpl_rate_bucket = bucket; + } + + pfsrpl->pfsrpl_ipv4_mask.v4.s_addr = + htonl(0xffffffff << (32 - pfsrpl->pfsrpl_ipv4_prefix)); + + /* XXX this could be bogus. looks impressive though. */ + prefix = 128 - pfsrpl->pfsrpl_ipv6_prefix; + for (i = 0; i < nitems(pfsrpl->pfsrpl_ipv6_mask.addr32); i++) { + if (prefix == 0) { + /* the memory is already zeroed */ + break; + } + if (prefix < 32) { + pfsrpl->pfsrpl_ipv6_mask.addr32[i] = + htonl(0xffffffff << prefix); + break; + } + + pfsrpl->pfsrpl_ipv6_mask.addr32[i] = htonl(0xffffffff); + prefix -= 32; + } + + RBT_INIT(pf_source_tree, &pfsrpl->pfsrpl_sources); + + NET_LOCK(); + PF_LOCK(); + if (ioc->ticket != pf_main_ruleset.rules.inactive.ticket) { + error = EBUSY; + goto unlock; + } + + if (pfsrpl->pfsrpl_overload.name[0] != '\0') { + pfsrpl->pfsrpl_overload.table = pfr_attach_table( + &pf_main_ruleset, + pfsrpl->pfsrpl_overload.name, 0); + if (pfsrpl->pfsrpl_overload.table == NULL) { + error = EINVAL; + goto unlock; + } + } + + if (RBT_INSERT(pf_sourcepl_tree, + &pf_sourcepl_tree_inactive, pfsrpl) != NULL) { + error = EBUSY; + goto unlock; + } + + TAILQ_INSERT_HEAD(&pf_sourcepl_list_inactive, pfsrpl, pfsrpl_list); + + PF_UNLOCK(); + NET_UNLOCK(); + + return (0); + +unlock: + PF_UNLOCK(); + NET_UNLOCK(); +/* free: */ + pool_put(&pf_sourcepl_pl, pfsrpl); + + return (error); +} + +void +pf_sourcepl_rollback(void) +{ + struct pf_sourcepl *pfsrpl, *npfsrpl; + + PF_ASSERT_LOCKED(); + NET_ASSERT_LOCKED(); + + TAILQ_FOREACH_SAFE(pfsrpl, &pf_sourcepl_list_inactive, + pfsrpl_list, npfsrpl) { + if (pfsrpl->pfsrpl_overload.table != NULL) + pfr_detach_table(pfsrpl->pfsrpl_overload.table); + + pool_put(&pf_sourcepl_pl, pfsrpl); + } + + TAILQ_INIT(&pf_sourcepl_list_inactive); + RBT_INIT(pf_sourcepl_tree, &pf_sourcepl_tree_inactive); +} + +static struct pf_sourcepl * +pf_sourcepl_rb_find(struct pf_sourcepl_tree *tree, + const struct pf_sourcepl *key) +{ + return (RBT_FIND(pf_sourcepl_tree, tree, key)); +} + +static struct pf_sourcepl * +pf_sourcepl_rb_nfind(struct pf_sourcepl_tree *tree, + const struct pf_sourcepl *key) +{ + return (RBT_NFIND(pf_sourcepl_tree, tree, key)); +} + +int +pf_sourcepl_get(struct pfioc_sourcepl *ioc, + struct pf_sourcepl *(*rbt_op)(struct pf_sourcepl_tree *, + const struct pf_sourcepl *)) +{ + struct pf_sourcepl key = { .pfsrpl_id = ioc->id }; + struct pf_sourcepl *pfsrpl; + int error = 0; + + NET_LOCK(); + PF_LOCK(); +#if 0 + if (ioc->ticket != pf_main_ruleset.rules.active.ticket) { + error = EBUSY; + goto unlock; + } +#endif + + pfsrpl = (*rbt_op)(&pf_sourcepl_tree_active, &key); + if (pfsrpl == NULL) { + error = ESRCH; + goto unlock; + } + + ioc->id = pfsrpl->pfsrpl_id; + ioc->limit = pfsrpl->pfsrpl_limit; + ioc->states = pfsrpl->pfsrpl_states; + ioc->inet_prefix = pfsrpl->pfsrpl_ipv4_prefix; + ioc->inet6_prefix = pfsrpl->pfsrpl_ipv6_prefix; + ioc->rate.limit = pfsrpl->pfsrpl_rate.limit; + ioc->rate.seconds = pfsrpl->pfsrpl_rate.seconds; + + CTASSERT(sizeof(ioc->overload_tblname) == + sizeof(pfsrpl->pfsrpl_overload.name)); + memcpy(ioc->overload_tblname, pfsrpl->pfsrpl_overload.name, + sizeof(pfsrpl->pfsrpl_overload.name)); + ioc->overload_hwm = pfsrpl->pfsrpl_overload.hwm; + ioc->overload_lwm = pfsrpl->pfsrpl_overload.lwm; + + CTASSERT(sizeof(ioc->description) == + sizeof(pfsrpl->pfsrpl_description)); + memcpy(ioc->description, pfsrpl->pfsrpl_description, + sizeof(ioc->description)); + + /* XXX overload table thing */ + + ioc->nsources = pfsrpl->pfsrpl_nsources; + + ioc->inuse = pfsrpl->pfsrpl_counters.inuse; + ioc->addrallocs = pfsrpl->pfsrpl_counters.addrallocs; + ioc->addrnomem = pfsrpl->pfsrpl_counters.addrnomem; + ioc->admitted = pfsrpl->pfsrpl_counters.admitted; + ioc->addrlimited = pfsrpl->pfsrpl_counters.addrlimited; + ioc->hardlimited = pfsrpl->pfsrpl_counters.hardlimited; + ioc->ratelimited = pfsrpl->pfsrpl_counters.ratelimited; + +unlock: + PF_UNLOCK(); + NET_UNLOCK(); + + return (error); +} + +static struct pf_source * +pf_source_rb_find(struct pf_source_ioc_tree *tree, + const struct pf_source *key) +{ + return (RBT_FIND(pf_source_ioc_tree, tree, key)); +} + +static struct pf_source * +pf_source_rb_nfind(struct pf_source_ioc_tree *tree, + const struct pf_source *key) +{ + return (RBT_NFIND(pf_source_ioc_tree, tree, key)); +} + +int +pf_source_get(struct pfioc_source *ioc, + struct pf_source *(*rbt_op)(struct pf_source_ioc_tree *, + const struct pf_source *)) +{ + struct pf_sourcepl plkey = { .pfsrpl_id = ioc->id }; + struct pf_source key = { + .pfsr_af = ioc->af, + .pfsr_addr = ioc->addr, + }; + struct pf_sourcepl *pfsrpl; + struct pf_source *pfsr; + int error = 0; + + NET_LOCK(); + PF_LOCK(); +#if 0 + if (ioc->ticket != pf_main_ruleset.rules.active.ticket) { + error = EBUSY; + goto unlock; + } +#endif + + pfsrpl = pf_sourcepl_rb_find(&pf_sourcepl_tree_active, &plkey); + if (pfsrpl == NULL) { + error = ESRCH; + goto unlock; + } + + pfsr = (*rbt_op)(&pfsrpl->pfsrpl_ioc_sources, &key); + if (pfsr == NULL) { + error = ESRCH; + goto unlock; + } + + ioc->id = pfsrpl->pfsrpl_id; + ioc->limit = pfsrpl->pfsrpl_states; + ioc->rate.limit = pfsrpl->pfsrpl_rate.limit; + ioc->rate.seconds = pfsrpl->pfsrpl_rate.seconds; + + ioc->af = pfsr->pfsr_af; + ioc->addr = pfsr->pfsr_addr; + ioc->prefix = (pfsr->pfsr_af == AF_INET) ? + pfsrpl->pfsrpl_ipv4_prefix : pfsrpl->pfsrpl_ipv6_prefix; + + ioc->inuse = pfsr->pfsr_inuse; + ioc->admitted = pfsr->pfsr_counters.admitted; + ioc->hardlimited = pfsr->pfsr_counters.hardlimited; + ioc->ratelimited = pfsr->pfsr_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 +1822,12 @@ 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 DIOCGETSOURCE: + case DIOCGETNSOURCE: case DIOCGETRULESETS: case DIOCGETRULESET: case DIOCGETQUEUES: @@ -1102,6 +1872,12 @@ 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 DIOCGETSOURCE: + case DIOCGETNSOURCE: case DIOCGETRULESETS: case DIOCGETRULESET: case DIOCGETQUEUES: @@ -1327,6 +2103,39 @@ 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 DIOCADDSOURCEPL: + error = pf_sourcepl_add((struct pfioc_sourcepl *)addr); + break; + case DIOCGETSOURCEPL: + error = pf_sourcepl_get((struct pfioc_sourcepl *)addr, + pf_sourcepl_rb_find); + break; + case DIOCGETNSOURCEPL: + error = pf_sourcepl_get((struct pfioc_sourcepl *)addr, + pf_sourcepl_rb_nfind); + break; + + case DIOCGETSOURCE: + error = pf_source_get((struct pfioc_source *)addr, + pf_source_rb_find); + break; + case DIOCGETNSOURCE: + error = pf_source_get((struct pfioc_source *)addr, + pf_source_rb_nfind); + break; + case DIOCADDRULE: { struct pfioc_rule *pr = (struct pfioc_rule *)addr; struct pf_ruleset *ruleset; @@ -2673,6 +3482,14 @@ pfioctl(dev_t dev, u_long cmd, caddr_t a error = EBUSY; goto fail; } + error = pf_sourcepl_check(); + if (error != 0) { + PF_UNLOCK(); + NET_UNLOCK(); + free(table, M_TEMP, sizeof(*table)); + free(ioe, M_TEMP, sizeof(*ioe)); + goto fail; + } break; default: PF_UNLOCK(); @@ -2774,6 +3591,8 @@ 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(); + pf_sourcepl_commit(); pfi_xcommit(); pf_trans_set_commit(); PF_UNLOCK(); @@ -3067,6 +3886,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 +4021,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/pf_table.c =================================================================== RCS file: /cvs/src/sys/net/pf_table.c,v retrieving revision 1.139 diff -u -p -r1.139 pf_table.c --- sys/net/pf_table.c 22 Nov 2021 12:56:04 -0000 1.139 +++ sys/net/pf_table.c 8 Mar 2022 10:59:26 -0000 @@ -1156,6 +1156,26 @@ pfr_insert_kentry(struct pfr_ktable *kt, return (0); } +int +pfr_remove_kentry(struct pfr_ktable *kt, struct pfr_addr *ad) +{ + struct pfr_kentryworkq workq = SLIST_HEAD_INITIALIZER(workq); + struct pfr_kentry *p; + + p = pfr_lookup_addr(kt, ad, 1); + if (p == NULL || ISSET(p->pfrke_flags, PFRKE_FLAG_NOT)) + return (ESRCH); + + if (ISSET(p->pfrke_flags, PFRKE_FLAG_MARK)) + return (0); + + SET(p->pfrke_flags, PFRKE_FLAG_MARK); + SLIST_INSERT_HEAD(&workq, p, pfrke_workq); + pfr_remove_kentries(kt, &workq); + + return (0); +} + void pfr_remove_kentries(struct pfr_ktable *kt, struct pfr_kentryworkq *workq) 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 8 Mar 2022 10:59:26 -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,107 @@ 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 nsources; /* gauge */ + unsigned int inuse; /* gauge */ + + uint64_t addrallocs; /* counter */ + uint64_t addrnomem; /* counter */ + uint64_t admitted; /* counter */ + uint64_t addrlimited; /* counter */ + uint64_t hardlimited; /* counter */ + uint64_t ratelimited; /* counter */ +}; + +struct pfioc_source { + uint32_t id; + sa_family_t af; + struct pf_addr addr; + + /* copied from the parent source pool */ + + unsigned int prefix; + unsigned int limit; + + struct { + unsigned int limit; + unsigned int seconds; + } rate; + + /* stats */ + + unsigned int inuse; /* gauge */ + uint64_t admitted; /* counter */ + uint64_t hardlimited; /* counter */ + uint64_t ratelimited; /* counter */ +}; + /* * ioctl operations */ @@ -1678,6 +1785,14 @@ 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 DIOCGETSOURCE _IOWR('D', 101, struct pfioc_source) +#define DIOCGETNSTATEPL _IOWR('D', 102, struct pfioc_statepl) +#define DIOCGETNSOURCEPL _IOWR('D', 102, struct pfioc_sourcepl) +#define DIOCGETNSOURCE _IOWR('D', 102, struct pfioc_source) #ifdef _KERNEL @@ -1835,6 +1950,7 @@ int pfr_clr_tstats(struct pfr_table *, i int pfr_set_tflags(struct pfr_table *, int, int, int, int *, int *, int); int pfr_clr_addrs(struct pfr_table *, int *, int); int pfr_insert_kentry(struct pfr_ktable *, struct pfr_addr *, time_t); +int pfr_remove_kentry(struct pfr_ktable *, struct pfr_addr *); int pfr_add_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_del_addrs(struct pfr_table *, struct pfr_addr *, int, int *, @@ -1888,7 +2004,7 @@ void pf_tag2tagname(u_int16_t, char *) void pf_tag_ref(u_int16_t); void pf_tag_unref(u_int16_t); void pf_tag_packet(struct mbuf *, int, int); -int pf_addr_compare(struct pf_addr *, struct pf_addr *, +int pf_addr_compare(const struct pf_addr *, const struct pf_addr *, sa_family_t); const struct pfq_ops 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 8 Mar 2022 10:59:26 -0000 @@ -147,6 +147,241 @@ 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; + +struct pf_source { + RBT_ENTRY(pf_source) pfsr_tree; + RBT_ENTRY(pf_source) pfsr_ioc_tree; + struct pf_sourcepl *pfsr_parent; + + sa_family_t pfsr_af; + struct pf_addr pfsr_addr; + + /* run state */ + + unsigned int pfsr_inuse; + unsigned int pfsr_intable; + struct pf_state_linkage pfsr_links; + time_t pfsr_empty_ts; + TAILQ_ENTRY(pf_source) pfsr_empty_gc; + + /* rate limiter */ + uint64_t pfsr_rate_ts; + + struct { + uint64_t admitted; + uint64_t hardlimited; + uint64_t ratelimited; + } pfsr_counters; +}; + +RBT_HEAD(pf_source_tree, pf_source); +RBT_PROTOTYPE(pf_source_tree, pf_source, pfsr_tree, cmp); + +RBT_HEAD(pf_source_ioc_tree, pf_source); +RBT_PROTOTYPE(pf_source_ioc_tree, pf_source, pfsr_ioc_tree, cmp); + +TAILQ_HEAD(pf_source_list, pf_source); + +struct pf_sourcepl { + RBT_ENTRY(pf_sourcepl) pfsrpl_tree; + TAILQ_ENTRY(pf_sourcepl) pfsrpl_list; + + uint32_t pfsrpl_id; + unsigned int pfsrpl_disabled; + + /* config */ + + unsigned int pfsrpl_limit; /* nsources limit */ + unsigned int pfsrpl_states; + unsigned int pfsrpl_ipv4_prefix; + unsigned int pfsrpl_ipv6_prefix; + + struct { + unsigned int limit; + unsigned int seconds; + } pfsrpl_rate; + + struct { + char name[PF_TABLE_NAME_SIZE]; + unsigned int hwm; + unsigned int lwm; + struct pfr_ktable *table; + } pfsrpl_overload; + + char pfsrpl_description[PF_RULE_LABEL_SIZE]; + + /* run state */ + struct pf_addr pfsrpl_ipv4_mask; + struct pf_addr pfsrpl_ipv6_mask; + + uint64_t pfsrpl_rate_token; + uint64_t pfsrpl_rate_bucket; + + /* number of pf_sources */ + unsigned int pfsrpl_nsources; + struct pf_source_tree pfsrpl_sources; + struct pf_source_ioc_tree pfsrpl_ioc_sources; + + struct { + /* number of times pf_source was allocated */ + uint64_t addrallocs; + /* state was rejected because the address limit was hit */ + uint64_t addrlimited; + /* no memory to create address thing */ + uint64_t addrnomem; + + /* sum of pf_source inuse gauges */ + uint64_t inuse; + /* sum of pf_source admitted counters */ + uint64_t admitted; + /* sum of pf_source hardlimited counters */ + uint64_t hardlimited; + /* sum of pf_source ratelimited counters */ + uint64_t ratelimited; + } pfsrpl_counters; +}; + +RBT_HEAD(pf_sourcepl_tree, pf_sourcepl); +RBT_PROTOTYPE(pf_sourcepl_tree, pf_sourcepl, pfsrpl_tree, cmp); + +TAILQ_HEAD(pf_sourcepl_list, pf_sourcepl); + +extern struct pf_sourcepl_tree pf_sourcepl_tree_active; +extern struct pf_sourcepl_list pf_sourcepl_list_active; + +extern struct pf_sourcepl_tree pf_sourcepl_tree_inactive; +extern struct pf_sourcepl_list pf_sourcepl_list_inactive; + 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 8 Mar 2022 10:59:26 -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 << 5) + + unsigned int limit; + unsigned int states; + struct { + unsigned int limit; + unsigned int seconds; + } rate; + struct { + 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,280 @@ 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 < PF_STATEPL_LIMIT_MIN || + $2 > PF_STATEPL_LIMIT_MAX) { + 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 { + struct pfctl_sourcepl *srpl; + + 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; + } + + srpl = calloc(1, sizeof(*srpl)); + if (srpl == NULL) + err(1, "source pool: malloc"); + + srpl->ioc.id = $1; + srpl->ioc.limit = $2->limit; + srpl->ioc.states = $2->states; + srpl->ioc.rate.limit = $2->rate.limit; + srpl->ioc.rate.seconds = $2->rate.seconds; + + if (ISSET($2->marker, SOURCEPL_M_TABLE)) { + if (strlcpy(srpl->ioc.overload_tblname, + $2->table.name, + sizeof(srpl->ioc.overload_tblname)) >= + sizeof(srpl->ioc.overload_tblname)) { + abort(); + } + srpl->ioc.overload_hwm = $2->table.above; + srpl->ioc.overload_lwm = $2->table.below; + + free($2->table.name); + } + + srpl->ioc.inet_prefix = $2->inet_mask; + srpl->ioc.inet6_prefix = $2->inet6_mask; + + if (pfctl_add_sourcepl(pf, srpl) != 0) { + free(srpl); + yyerror("source pool %lld already exists", $1); + 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.inet_mask = 32; + sourcepl_opts.inet6_mask = 128; + } 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 { + size_t stringlen; + + if (ISSET(sourcepl_opts.marker, SOURCEPL_M_TABLE)) { + yyerror("rate cannot be respecified"); + YYERROR; + } + + stringlen = strlen($3); + if (stringlen == 0 || + stringlen >= PF_TABLE_NAME_SIZE) { + free($3); + yyerror("invalid table name"); + YYERROR; + } + + if ($6 < 0) { + free($3); + yyerror("above limit is invalid"); + YYERROR; + } + if ($7 > $6) { + free($3); + 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 +2091,22 @@ 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; + } + r.statepl = o->data.statepl; + break; + case PF_STATE_OPT_SOURCEPL: + if (r.sourcepl != PF_SOURCEPL_ID_NONE) { + yyerror("source pool: " + "multiple definitions"); + YYERROR; + } + r.sourcepl = o->data.sourcepl; + break; case PF_STATE_OPT_TIMEOUT: if (o->data.timeout.number == PFTM_ADAPTIVE_START || @@ -1778,6 +2122,7 @@ pfrule : action dir logquick interface } r.timeout[o->data.timeout.number] = o->data.timeout.seconds; + break; } o = o->next; if (!defaults) @@ -3442,6 +3787,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 +5295,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 +5303,7 @@ lookup(char *s) { "antispoof", ANTISPOOF}, { "any", ANY}, { "bandwidth", BANDWIDTH}, + { "below", BELOW}, { "binat-to", BINATTO}, { "bitmask", BITMASK}, { "block", BLOCK}, @@ -4979,6 +5344,7 @@ lookup(char *s) { "load", LOAD}, { "log", LOG}, { "loginterface", LOGINTERFACE}, + { "mask", MASK}, { "match", MATCH}, { "matches", MATCHES}, { "max", MAXIMUM}, @@ -5005,6 +5371,7 @@ lookup(char *s) { "parent", PARENT}, { "pass", PASS}, { "pflow", PFLOW}, + { "pool", POOL}, { "port", PORT}, { "prio", PRIO}, { "probability", PROBABILITY}, @@ -5015,6 +5382,7 @@ lookup(char *s) { "quick", QUICK}, { "random", RANDOM}, { "random-id", RANDOMID}, + { "rate", RATE}, { "rdomain", RDOMAIN}, { "rdr-to", RDRTO}, { "reassemble", REASSEMBLE}, @@ -5034,11 +5402,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 8 Mar 2022 10:59:26 -0000 @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -53,11 +54,16 @@ #include #include #include +#include #include #include "pfctl_parser.h" #include "pfctl.h" +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + void usage(void); int pfctl_enable(int, int); int pfctl_disable(int, int); @@ -86,6 +92,8 @@ 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_sourcepls(int, enum pfctl_show, int); int pfctl_show_rules(int, char *, int, enum pfctl_show, char *, int, int, long); int pfctl_show_src_nodes(int, int); @@ -101,6 +109,10 @@ 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 *); +void pfctl_load_sourcepls(struct pfctl *); +void pfctl_load_sourcepl(struct pfctl *, struct pfctl_sourcepl *); 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 +131,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 +241,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", "Pools", "all", NULL }; static const char *tblcmdopt_list[] = { @@ -828,6 +843,164 @@ 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 %5s/%-5s %8s %8s %8s\n", "ID", + "USE", "LIMIT", "RATE", "SECS", + "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 ", stpl.id, + stpl.inuse, stpl.limit); + if (stpl.rate.limit != 0) { + printf("%5u/%-5u ", + stpl.rate.limit, stpl.rate.seconds); + } else + printf("%5s/%-5s ", "nil", "nil"); + printf("%8llu %8llu %8llu\n", + stpl.admitted, stpl.hardlimited, stpl.ratelimited); + break; + default: + errx(1, "%s: unexpected format %d", __func__, format); + /* NOTREACHED */ + } + + id = stpl.id + 1; + } +} + +static inline int +pf_addr_inc(struct pf_addr *addr) +{ + int i; + uint32_t val, inc; + + for (i = 3; i >= 0; i--) { + val = ntohl(addr->addr32[i]); + inc = val + 1; + addr->addr32[i] = htonl(inc); + if (inc > val) + return (0); + } + + return (1); +} + +static int +pfctl_show_sources(int dev, const struct pfioc_sourcepl *srpl, + enum pfctl_show format, int opts) +{ + struct pfioc_source sr = { .id = srpl->id }; + /* start from af 0 address 0 */ + + if (format != PFCTL_SHOW_LABELS) + errx(1, "%s format is not PFCTL_SHOW_LABELS", __func__); + + for (;;) { + if (ioctl(dev, DIOCGETNSOURCE, &sr) == -1) { + if (errno == ESRCH) { + /* we're done */ + return (0); + } + warn("DIOCGETNSOURCE %u", sr.id); + return (-1); + } + + //printf("af %u ", sr.af); + print_addr_str(sr.af, &sr.addr); + printf("/%u ", sr.prefix); + printf("inuse %u/%u ", sr.inuse, sr.limit); + printf("admit %llu hardlim %llu ratelim %llu\n", + sr.admitted, sr.hardlimited, sr.ratelimited); + + sr.af += pf_addr_inc(&sr.addr); + } + + return (0); +} + +int +pfctl_show_sourcepls(int dev, enum pfctl_show format, int opts) +{ + struct pfioc_sourcepl srpl; + uint32_t id = PF_SOURCEPL_ID_MIN; + + if (format == PFCTL_SHOW_LABELS) { + printf("%3s %8s/%-8s %5s %5s/%-5s %8s %8s %8s %8s\n", "ID", + "USE", "ADDRS", "LIMIT", "RATE", "SECS", + "ADMIT", "ADDRLIM", "HARDLIM", "RATELIM"); + } + + for (;;) { + memset(&srpl, 0, sizeof(srpl)); + srpl.id = id; + + if (ioctl(dev, DIOCGETNSOURCEPL, &srpl) == -1) { + if (errno == ESRCH) { + /* we're done */ + return (0); + } + warn("DIOCGETNSOURCEPL %u", srpl.id); + return (-1); + } + + switch (format) { + case PFCTL_SHOW_RULES: + print_sourcepl(&srpl); + break; + + case PFCTL_SHOW_LABELS: + printf("%3u %8u/%-8u %5u ", srpl.id, + srpl.nsources, srpl.limit, srpl.states); + if (srpl.rate.limit != 0) { + printf("%5u/%-5u ", + srpl.rate.limit, srpl.rate.seconds); + } else + printf("%5s/%-5s ", "nil", "nil"); + printf("%8llu %8llu %8llu %8llu\n", + srpl.admitted, srpl.addrlimited, + srpl.hardlimited, srpl.ratelimited); + + if (opts & PF_OPT_VERBOSE) + if (pfctl_show_sources(dev, &srpl, + format, opts) != 0) + return (-1); + break; + + default: + errx(1, "%s: unexpected format %d", __func__, format); + /* NOTREACHED */ + } + + id = srpl.id + 1; + } + + return (0); +} + +int pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format, char *anchorname, int depth, int wildcard, long shownr) { @@ -836,6 +1009,15 @@ 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; + ret = pfctl_show_sourcepls(dev, format, opts); + 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 +1600,66 @@ 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? */ +} + +void +pfctl_load_sourcepl(struct pfctl *pf, struct pfctl_sourcepl *srpl) +{ + if (pf->opts & PF_OPT_VERBOSE) + print_sourcepl(&srpl->ioc); + + if (pf->opts & PF_OPT_NOACTION) + return; + + if (ioctl(pf->dev, DIOCADDSOURCEPL, &srpl->ioc) == -1) + err(1, "DIOCADDSOURCEPL %u", srpl->ioc.id); +} + +void +pfctl_load_sourcepls(struct pfctl *pf) +{ + struct pfctl_sourcepl *srpl; + u_int32_t ticket = 0; + + if ((pf->opts & PF_OPT_NOACTION) == 0) + ticket = pfctl_get_ticket(pf->trans, PF_TRANS_RULESET, ""); + + RBT_FOREACH(srpl, pfctl_sourcepls, &pf->sourcepls) { + srpl->ioc.ticket = ticket; + pfctl_load_sourcepl(pf, srpl); + } + + /* XXX should this free the statepls? */ +} + int pfctl_load_ruleset(struct pfctl *pf, char *path, struct pf_ruleset *rs, int depth) @@ -1540,8 +1782,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 +1805,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 +1853,17 @@ 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); + pfctl_load_sourcepls(&pf); } if (pfctl_load_ruleset(&pf, path, rs, 0)) { @@ -2750,6 +2998,11 @@ main(int argc, char *argv[]) case 'I': pfctl_show_ifaces(ifaceopt, opts); break; + case 'p': + pfctl_show_statepls(dev, PFCTL_SHOW_LABELS); + break; + case 'P': + pfctl_show_sourcepls(dev, PFCTL_SHOW_LABELS, opts); } } @@ -2904,4 +3157,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 8 Mar 2022 10:59:26 -0000 @@ -706,6 +706,41 @@ 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_sourcepl(const struct pfioc_sourcepl *ioc) +{ + printf("source pool %u limit %u states %u", ioc->id, + ioc->limit, ioc->states); + if (ioc->rate.limit != 0) + printf(" rate %u/%u", ioc->rate.limit, ioc->rate.seconds); + if (ioc->overload_tblname[0] != '\0') { + printf(" table <%s> above %u", + ioc->overload_tblname, ioc->overload_hwm); + if (ioc->overload_lwm) + printf(" below %u", ioc->overload_lwm); + } + if (ioc->inet_prefix < 32) + printf(" inet mask %u", ioc->inet_prefix); + if (ioc->inet6_prefix < 128) + printf(" inet6 mask %u", ioc->inet6_prefix); + 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 +959,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 +1063,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 8 Mar 2022 10:59:26 -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,8 @@ 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_sourcepl(const struct pfioc_sourcepl *); 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);