diff options
| author | Kristof Provost <kp@FreeBSD.org> | 2025-12-30 19:06:48 +0000 |
|---|---|---|
| committer | Kristof Provost <kp@FreeBSD.org> | 2026-01-14 06:44:38 +0000 |
| commit | 4616481212302b5d875cfc7a00766af017318f7f (patch) | |
| tree | 96d5023704b950ed22ce7dbe8648149cfc8be725 /sys/netpfil | |
| parent | c498eaa2f9090d7bdc6456181d8bf74869288bbb (diff) | |
Diffstat (limited to 'sys/netpfil')
| -rw-r--r-- | sys/netpfil/pf/pf.c | 647 | ||||
| -rw-r--r-- | sys/netpfil/pf/pf_ioctl.c | 922 | ||||
| -rw-r--r-- | sys/netpfil/pf/pf_nl.c | 4 | ||||
| -rw-r--r-- | sys/netpfil/pf/pf_nl.h | 2 | ||||
| -rw-r--r-- | sys/netpfil/pf/pf_table.c | 20 |
5 files changed, 1595 insertions, 0 deletions
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c index 60ca9039e9ce..88b110d744ee 100644 --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -360,6 +360,8 @@ static int pf_tcp_track_sloppy(struct pf_kstate *, struct pf_pdesc *, u_short *, struct pf_state_peer *, struct pf_state_peer *, u_int8_t, u_int8_t); +static __inline int pf_synproxy_ack(struct pf_krule *, struct pf_pdesc *, + struct pf_kstate **, struct pf_rule_actions *); static int pf_test_state(struct pf_kstate **, struct pf_pdesc *, u_short *); int pf_icmp_state_lookup(struct pf_state_key_cmp *, @@ -426,6 +428,269 @@ static __inline void pf_set_protostate(struct pf_kstate *, int, u_int8_t); int in4_cksum(struct mbuf *m, u_int8_t nxt, int off, int len); +static inline int +pf_statelim_id_cmp(const struct pf_statelim *a, const struct pf_statelim *b) +{ + if (a->pfstlim_id > b->pfstlim_id) + return (1); + if (a->pfstlim_id < b->pfstlim_id) + return (-1); + + return (0); +} + +RB_GENERATE(pf_statelim_id_tree, pf_statelim, pfstlim_id_tree, + pf_statelim_id_cmp); + +static inline int +pf_statelim_nm_cmp(const struct pf_statelim *a, const struct pf_statelim *b) +{ + return (strncmp(a->pfstlim_nm, b->pfstlim_nm, sizeof(a->pfstlim_nm))); +} + +RB_GENERATE(pf_statelim_nm_tree, pf_statelim, pfstlim_nm_tree, + pf_statelim_nm_cmp); + +VNET_DEFINE(struct pf_statelim_id_tree, pf_statelim_id_tree_active); +VNET_DEFINE(struct pf_statelim_list, pf_statelim_list_active); +VNET_DEFINE(struct pf_statelim_id_tree, pf_statelim_id_tree_inactive); +VNET_DEFINE(struct pf_statelim_nm_tree, pf_statelim_nm_tree_inactive); +VNET_DEFINE(struct pf_statelim_list, pf_statelim_list_inactive); + +static inline int +pf_sourcelim_id_cmp(const struct pf_sourcelim *a, const struct pf_sourcelim *b) +{ + if (a->pfsrlim_id > b->pfsrlim_id) + return (1); + if (a->pfsrlim_id < b->pfsrlim_id) + return (-1); + + return (0); +} + +RB_GENERATE(pf_sourcelim_id_tree, pf_sourcelim, pfsrlim_id_tree, + pf_sourcelim_id_cmp); + +static inline int +pf_sourcelim_nm_cmp(const struct pf_sourcelim *a, const struct pf_sourcelim *b) +{ + return (strncmp(a->pfsrlim_nm, b->pfsrlim_nm, sizeof(a->pfsrlim_nm))); +} + +RB_GENERATE(pf_sourcelim_nm_tree, pf_sourcelim, pfsrlim_nm_tree, + pf_sourcelim_nm_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); + if (a->pfsr_rdomain > b->pfsr_rdomain) + return (1); + if (a->pfsr_rdomain < b->pfsr_rdomain) + return (-1); + + return (pf_addr_cmp(&a->pfsr_addr, &b->pfsr_addr, a->pfsr_af)); +} + +RB_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); + if (a->pfsr_rdomain > b->pfsr_rdomain) + return (1); + if (a->pfsr_rdomain < b->pfsr_rdomain) + 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); +} + +RB_GENERATE(pf_source_ioc_tree, pf_source, pfsr_ioc_tree, pf_source_ioc_cmp); + +VNET_DEFINE(struct pf_sourcelim_id_tree, pf_sourcelim_id_tree_active); +VNET_DEFINE(struct pf_sourcelim_list, pf_sourcelim_list_active); + +VNET_DEFINE(struct pf_sourcelim_id_tree, pf_sourcelim_id_tree_inactive); +VNET_DEFINE(struct pf_sourcelim_nm_tree, pf_sourcelim_nm_tree_inactive); +VNET_DEFINE(struct pf_sourcelim_list, pf_sourcelim_list_inactive); + +static inline struct pf_statelim * +pf_statelim_find(uint32_t id) +{ + struct pf_statelim key; + + /* only the id is used in cmp, so don't have to zero all the things */ + key.pfstlim_id = id; + + return (RB_FIND(pf_statelim_id_tree, + &V_pf_statelim_id_tree_active, &key)); +} + +static inline struct pf_sourcelim * +pf_sourcelim_find(uint32_t id) +{ + struct pf_sourcelim key; + + /* only the id is used in cmp, so don't have to zero all the things */ + key.pfsrlim_id = id; + + return (RB_FIND(pf_sourcelim_id_tree, + &V_pf_sourcelim_id_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; + + TAILQ_FOREACH_SAFE(sr, &pf_source_gc, pfsr_empty_gc, nsr) { + struct pf_sourcelim *srlim = sr->pfsr_parent; + + if (time_uptime <= sr->pfsr_empty_ts + + srlim->pfsrlim_rate.seconds + 1) + continue; + + TAILQ_REMOVE(&pf_source_gc, sr, pfsr_empty_gc); + + RB_REMOVE(pf_source_tree, &srlim->pfsrlim_sources, sr); + RB_REMOVE(pf_source_ioc_tree, &srlim->pfsrlim_ioc_sources, sr); + srlim->pfsrlim_nsources--; + + free(sr, M_PF_SOURCE_LIM); + } +} + +static void +pf_source_pfr_addr(struct pfr_addr *p, const struct pf_source *sr) +{ + struct pf_sourcelim *srlim = sr->pfsr_parent; + + memset(p, 0, sizeof(*p)); + + p->pfra_af = sr->pfsr_af; + switch (sr->pfsr_af) { + case AF_INET: + p->pfra_net = srlim->pfsrlim_ipv4_prefix; + p->pfra_ip4addr = sr->pfsr_addr.v4; + break; +#ifdef INET6 + case AF_INET6: + p->pfra_net = srlim->pfsrlim_ipv6_prefix; + p->pfra_ip6addr = sr->pfsr_addr.v6; + break; +#endif /* INET6 */ + } +} + +static void +pf_source_used(struct pf_source *sr) +{ + struct pf_sourcelim *srlim = sr->pfsr_parent; + struct pfr_ktable *t; + unsigned int used; + + used = sr->pfsr_inuse++; + sr->pfsr_rate_ts += srlim->pfsrlim_rate_token; + + if (used == 0) + TAILQ_REMOVE(&pf_source_gc, sr, pfsr_empty_gc); + else if ((t = srlim->pfsrlim_overload.table) != NULL && + used >= srlim->pfsrlim_overload.hwm && !sr->pfsr_intable) { + struct pfr_addr p; + + pf_source_pfr_addr(&p, sr); + + pfr_insert_kentry(t, &p, time_second); + sr->pfsr_intable = 1; + } +} + +static void +pf_source_rele(struct pf_source *sr) +{ + struct pf_sourcelim *srlim = sr->pfsr_parent; + struct pfr_ktable *t; + unsigned int used; + + used = --sr->pfsr_inuse; + + t = srlim->pfsrlim_overload.table; + if (t != NULL && sr->pfsr_intable && + used < srlim->pfsrlim_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 = time_uptime + srlim->pfsrlim_rate.seconds; + } +} + +static inline void +pf_source_key(struct pf_sourcelim *srlim, 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; + key->pfsr_rdomain = 0; + switch (af) { + case AF_INET: + key->pfsr_addr.addr32[0] = + srlim->pfsrlim_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] = + srlim->pfsrlim_ipv6_mask.addr32[i] & + addr->addr32[i]; + } + break; +#endif + default: + unhandled_af(af); + /* NOTREACHED */ + } +} + +static inline struct pf_source * +pf_source_find(struct pf_sourcelim *srlim, struct pf_source *key) +{ + return (RB_FIND(pf_source_tree, &srlim->pfsrlim_sources, key)); +} + extern int pf_end_threads; extern struct proc *pf_purge_proc; @@ -519,6 +784,8 @@ BOUND_IFACE(struct pf_kstate *st, struct pf_pdesc *pd) MALLOC_DEFINE(M_PFHASH, "pf_hash", "pf(4) hash header structures"); MALLOC_DEFINE(M_PF_RULE_ITEM, "pf_krule_item", "pf(4) rule items"); +MALLOC_DEFINE(M_PF_STATE_LINK, "pf_state_link", "pf(4) state links"); +MALLOC_DEFINE(M_PF_SOURCE_LIM, "pf_source_lim", "pf(4) source limiter"); VNET_DEFINE(struct pf_keyhash *, pf_keyhash); VNET_DEFINE(struct pf_idhash *, pf_idhash); VNET_DEFINE(struct pf_srchash *, pf_srchash); @@ -1295,6 +1562,22 @@ pf_initialize(void) /* Unlinked, but may be referenced rules. */ TAILQ_INIT(&V_pf_unlinked_rules); + + /* State limiters */ + RB_INIT(&V_pf_statelim_id_tree_inactive); + RB_INIT(&V_pf_statelim_nm_tree_inactive); + TAILQ_INIT(&V_pf_statelim_list_inactive); + + RB_INIT(&V_pf_statelim_id_tree_active); + TAILQ_INIT(&V_pf_statelim_list_active); + + /* Source limiters */ + RB_INIT(&V_pf_sourcelim_id_tree_active); + TAILQ_INIT(&V_pf_sourcelim_list_active); + + RB_INIT(&V_pf_sourcelim_id_tree_inactive); + RB_INIT(&V_pf_sourcelim_nm_tree_inactive); + TAILQ_INIT(&V_pf_sourcelim_list_inactive); } void @@ -2680,6 +2963,7 @@ pf_purge_thread(void *unused __unused) pf_purge_expired_fragments(); pf_purge_expired_src_nodes(); pf_purge_unlinked_rules(); + pf_source_purge(); pfi_kkif_purge(); } CURVNET_RESTORE(); @@ -2712,6 +2996,7 @@ pf_unload_vnet_purge(void) pf_purge_expired_states(0, V_pf_hashmask); pf_purge_fragments(UINT_MAX); pf_purge_expired_src_nodes(); + pf_source_purge(); /* * Now all kifs & rules should be unreferenced, @@ -2817,6 +3102,7 @@ int pf_remove_state(struct pf_kstate *s) { struct pf_idhash *ih = &V_pf_idhash[PF_IDHASH(s)]; + struct pf_state_link *pfl; NET_EPOCH_ASSERT(); PF_HASHROW_ASSERT(ih); @@ -2858,6 +3144,63 @@ pf_remove_state(struct pf_kstate *s) s->key[PF_SK_STACK]->proto == IPPROTO_TCP) pf_set_protostate(s, PF_PEER_BOTH, TCPS_CLOSED); + while ((pfl = SLIST_FIRST(&s->linkage)) != NULL) { + struct pf_state_link_list *list; + unsigned int gen; + + SLIST_REMOVE_HEAD(&s->linkage, pfl_linkage); + + switch (pfl->pfl_type) { + case PF_STATE_LINK_TYPE_STATELIM: { + struct pf_statelim *stlim; + + stlim = pf_statelim_find(s->statelim); + KASSERT(stlim != NULL, + ("pf_state %p pfl %p cannot find statelim %u", s, + pfl, s->statelim)); + + gen = pf_statelim_enter(stlim); + stlim->pfstlim_inuse--; + pf_statelim_leave(stlim, gen); + + list = &stlim->pfstlim_states; + break; + } + case PF_STATE_LINK_TYPE_SOURCELIM: { + struct pf_sourcelim *srlim; + struct pf_source key, *sr; + + srlim = pf_sourcelim_find(s->sourcelim); + KASSERT(srlim != NULL, + ("pf_state %p pfl %p cannot find sourcelim %u", s, + pfl, s->sourcelim)); + + pf_source_key(srlim, &key, s->key[PF_SK_WIRE]->af, + &s->key[PF_SK_WIRE]->addr[0 /* XXX or 1? */]); + + sr = pf_source_find(srlim, &key); + KASSERT(sr != NULL, + ("pf_state %p pfl %p cannot find source in %u", s, + pfl, s->sourcelim)); + + gen = pf_sourcelim_enter(srlim); + srlim->pfsrlim_counters.inuse--; + pf_sourcelim_leave(srlim, gen); + pf_source_rele(sr); + + list = &sr->pfsr_states; + break; + } + default: + panic("%s: unexpected link type on pfl %p", __func__, + pfl); + } + + PF_STATE_LOCK_ASSERT(s); + TAILQ_REMOVE(list, pfl, pfl_link); + free(pfl, M_PF_STATE_LINK); + } + PF_HASHROW_UNLOCK(ih); pf_detach_state(s); @@ -5656,6 +5999,11 @@ pf_match_rule(struct pf_test_ctx *ctx, struct pf_kruleset *ruleset, r = TAILQ_FIRST(ruleset->rules[PF_RULESET_FILTER].active.ptr); while (r != NULL) { + struct pf_statelim *stlim = NULL; + struct pf_sourcelim *srlim = NULL; + struct pf_source *sr = NULL; + unsigned int gen; + if (ctx->pd->related_rule) { *ctx->rm = ctx->pd->related_rule; break; @@ -5757,6 +6105,124 @@ pf_match_rule(struct pf_test_ctx *ctx, struct pf_kruleset *ruleset, pf_osfp_fingerprint(pd, ctx->th), r->os_fingerprint)), TAILQ_NEXT(r, entries)); + if (r->statelim != PF_STATELIM_ID_NONE) { + stlim = pf_statelim_find(r->statelim); + + /* + * Treat a missing limiter like an exhausted limiter. + * There is no "backend" to get a resource out of + * so the rule can't create state. + */ + PF_TEST_ATTRIB(stlim == NULL, TAILQ_NEXT(r, entries)); + + /* + * An overcommitted pool means this rule + * can't create state. + */ + if (stlim->pfstlim_inuse >= stlim->pfstlim_limit) { + gen = pf_statelim_enter(stlim); + stlim->pfstlim_counters.hardlimited++; + pf_statelim_leave(stlim, gen); + r = TAILQ_NEXT(r, entries); + continue; + } + + /* + * Is access to the pool rate limited? + */ + if (stlim->pfstlim_rate.limit != 0) { + struct timespec ts; + getnanouptime(&ts); + uint64_t diff = SEC_TO_NSEC(ts.tv_sec) + + ts.tv_nsec - stlim->pfstlim_rate_ts; + + if (diff < stlim->pfstlim_rate_token) { + gen = pf_statelim_enter(stlim); + stlim->pfstlim_counters.ratelimited++; + pf_statelim_leave(stlim, gen); + r = TAILQ_NEXT(r, entries); + continue; + } + + if (diff > stlim->pfstlim_rate_bucket) { + stlim->pfstlim_rate_ts = + SEC_TO_NSEC(ts.tv_sec) + ts.tv_nsec - + stlim->pfstlim_rate_bucket; + } + } + } + + if (r->sourcelim != PF_SOURCELIM_ID_NONE) { + struct pf_source key; + + srlim = pf_sourcelim_find(r->sourcelim); + + /* + * 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(srlim == NULL, TAILQ_NEXT(r, entries)); + + pf_source_key(srlim, &key, ctx->pd->af, + ctx->pd->src); + sr = pf_source_find(srlim, &key); + if (sr != NULL) { + /* + * An overcommitted limiter means this rule + * can't create state. + */ + if (sr->pfsr_inuse >= srlim->pfsrlim_limit) { + sr->pfsr_counters.hardlimited++; + gen = pf_sourcelim_enter(srlim); + srlim->pfsrlim_counters.hardlimited++; + pf_sourcelim_leave(srlim, gen); + r = TAILQ_NEXT(r, entries); + continue; + } + + /* + * Is access to the pool rate limited? + */ + if (srlim->pfsrlim_rate.limit != 0) { + struct timespec ts; + getnanouptime(&ts); + uint64_t diff = SEC_TO_NSEC(ts.tv_sec) + + ts.tv_nsec - sr->pfsr_rate_ts; + + if (diff < srlim->pfsrlim_rate_token) { + sr->pfsr_counters.ratelimited++; + gen = pf_sourcelim_enter(srlim); + srlim->pfsrlim_counters + .ratelimited++; + pf_sourcelim_leave(srlim, gen); + r = TAILQ_NEXT(r, entries); + continue; + } + + if (diff > srlim->pfsrlim_rate_bucket) { + sr->pfsr_rate_ts = + SEC_TO_NSEC(ts.tv_sec) + ts.tv_nsec - + srlim->pfsrlim_rate_bucket; + } + } + } else { + /* + * a new source entry will (should) + * admit a state. + */ + + if (srlim->pfsrlim_nsources >= + srlim->pfsrlim_entries) { + gen = pf_sourcelim_enter(srlim); + srlim->pfsrlim_counters.addrlimited++; + pf_sourcelim_leave(srlim, gen); + r = TAILQ_NEXT(r, entries); + continue; + } + } + } + /* must be last! */ if (r->pktrate.limit) { PF_TEST_ATTRIB((pf_check_threshold(&r->pktrate)), @@ -5833,6 +6299,13 @@ pf_match_rule(struct pf_test_ctx *ctx, struct pf_kruleset *ruleset, * ruleset, where anchor belongs to. */ ctx->arsm = ctx->aruleset; + /* + * state/source pools + */ + + ctx->statelim = stlim; + ctx->sourcelim = srlim; + ctx->source = sr; } if (pd->act.log & PF_LOG_MATCHES) pf_log_matches(pd, r, ctx->a, ruleset, match_rules); @@ -6085,6 +6558,13 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm, return (action); } + 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) + goto cleanup; /* PF_SYNPROXY_DROP */ + } + nat64 = pd->af != pd->naf; if (nat64) { int ret; @@ -6157,6 +6637,10 @@ pf_create_state(struct pf_krule *r, struct pf_test_ctx *ctx, { struct pf_pdesc *pd = ctx->pd; struct pf_kstate *s = NULL; + struct pf_statelim *stlim = NULL; + struct pf_sourcelim *srlim = NULL; + struct pf_source *sr = NULL; + struct pf_state_link *pfl; struct pf_ksrc_node *sns[PF_SN_MAX] = { NULL }; /* * XXXKS: The hash for PF_SN_LIMIT and PF_SN_ROUTE should be the same @@ -6219,6 +6703,7 @@ pf_create_state(struct pf_krule *r, struct pf_test_ctx *ctx, s->nat_rule = ctx->nr; s->anchor = ctx->a; s->match_rules = *match_rules; + SLIST_INIT(&s->linkage); memcpy(&s->act, &pd->act, sizeof(struct pf_rule_actions)); if (pd->act.allow_opts) @@ -6334,6 +6819,98 @@ pf_create_state(struct pf_krule *r, struct pf_test_ctx *ctx, KASSERT((ctx->sk != NULL && ctx->nk != NULL), ("%s: nr %p sk %p, nk %p", __func__, ctx->nr, ctx->sk, ctx->nk)); + stlim = ctx->statelim; + if (stlim != NULL) { + unsigned int gen; + + pfl = malloc(sizeof(*pfl), M_PF_STATE_LINK, M_NOWAIT); + if (pfl == NULL) { + REASON_SET(&ctx->reason, PFRES_MEMORY); + goto csfailed; + } + + gen = pf_statelim_enter(stlim); + stlim->pfstlim_counters.admitted++; + stlim->pfstlim_inuse++; + pf_statelim_leave(stlim, gen); + + stlim->pfstlim_rate_ts += stlim->pfstlim_rate_token; + + s->statelim = stlim->pfstlim_id; + pfl->pfl_state = s; + pfl->pfl_type = PF_STATE_LINK_TYPE_STATELIM; + + TAILQ_INSERT_TAIL(&stlim->pfstlim_states, pfl, pfl_link); + SLIST_INSERT_HEAD(&s->linkage, pfl, pfl_linkage); + } + + srlim = ctx->sourcelim; + if (srlim != NULL) { + struct pf_source *sr = ctx->source; + unsigned int gen; + + if (sr == NULL) { + sr = malloc(sizeof(*sr), M_PF_SOURCE_LIM, M_NOWAIT | M_ZERO); + if (sr == NULL) { + gen = pf_sourcelim_enter(srlim); + srlim->pfsrlim_counters.addrnomem++; + pf_sourcelim_leave(srlim, gen); + REASON_SET(&ctx->reason, PFRES_MEMORY); + goto csfailed; + } + + sr->pfsr_parent = srlim; + pf_source_key(srlim, sr, ctx->pd->af, ctx->pd->src); + TAILQ_INIT(&sr->pfsr_states); + + if (RB_INSERT(pf_source_tree, &srlim->pfsrlim_sources, + sr) != NULL) { + panic("%s: source pool %u (%p) " + "insert collision %p?!", + __func__, srlim->pfsrlim_id, srlim, sr); + } + + if (RB_INSERT(pf_source_ioc_tree, + &srlim->pfsrlim_ioc_sources, sr) != NULL) { + panic("%s: source pool %u (%p) ioc " + "insert collision (%p)?!", + __func__, srlim->pfsrlim_id, srlim, sr); + } + + sr->pfsr_empty_ts = time_uptime; + TAILQ_INSERT_TAIL(&pf_source_gc, sr, pfsr_empty_gc); + + gen = pf_sourcelim_enter(srlim); + srlim->pfsrlim_nsources++; + srlim->pfsrlim_counters.addrallocs++; + pf_sourcelim_leave(srlim, gen); + } else { + MPASS(sr->pfsr_parent == srlim); + } + + pfl = malloc(sizeof(*pfl), M_PF_STATE_LINK, M_NOWAIT); + if (pfl == NULL) { + REASON_SET(&ctx->reason, PFRES_MEMORY); + goto csfailed; + } + + pf_source_used(sr); + + sr->pfsr_counters.admitted++; + + gen = pf_sourcelim_enter(srlim); + srlim->pfsrlim_counters.inuse++; + srlim->pfsrlim_counters.admitted++; + pf_sourcelim_leave(srlim, gen); + + s->sourcelim = srlim->pfsrlim_id; + pfl->pfl_state = s; + pfl->pfl_type = PF_STATE_LINK_TYPE_SOURCELIM; + + TAILQ_INSERT_TAIL(&sr->pfsr_states, pfl, pfl_link); + SLIST_INSERT_HEAD(&s->linkage, pfl, pfl_linkage); + } + /* Swap sk/nk for PF_OUT. */ if (pf_state_insert(BOUND_IFACE(s, pd), pd->kif, (pd->dir == PF_IN) ? ctx->sk : ctx->nk, @@ -6400,6 +6977,44 @@ csfailed: drop: if (s != NULL) { + struct pf_state_link *npfl; + + SLIST_FOREACH_SAFE(pfl, &s->linkage, pfl_linkage, npfl) { + struct pf_state_link_list *list; + unsigned int gen; + + /* who needs KASSERTS when we have NULL derefs */ + + switch (pfl->pfl_type) { + case PF_STATE_LINK_TYPE_STATELIM: + gen = pf_statelim_enter(stlim); + stlim->pfstlim_inuse--; + pf_statelim_leave(stlim, gen); + + stlim->pfstlim_rate_ts -= + stlim->pfstlim_rate_token; + list = &stlim->pfstlim_states; + break; + case PF_STATE_LINK_TYPE_SOURCELIM: + gen = pf_sourcelim_enter(srlim); + srlim->pfsrlim_counters.inuse--; + pf_sourcelim_leave(srlim, gen); + + sr->pfsr_rate_ts -= srlim->pfsrlim_rate_token; + pf_source_rele(sr); + + list = &sr->pfsr_states; + break; + default: + panic("%s: unexpected link type on pfl %p", + __func__, pfl); + } + + TAILQ_REMOVE(list, pfl, pfl_link); + PF_STATE_LOCK_ASSERT(s); + free(pfl, M_PF_STATE_LINK); + } + pf_src_tree_remove_state(s); s->timeout = PFTM_UNLINKED; pf_free_state(s); @@ -7164,6 +7779,38 @@ pf_synproxy(struct pf_pdesc *pd, struct pf_kstate *state, u_short *reason) return (PF_PASS); } +static __inline int +pf_synproxy_ack(struct pf_krule *r, struct pf_pdesc *pd, struct pf_kstate **sm, + struct pf_rule_actions *act) +{ + struct tcphdr *th = &pd->hdr.tcp; + struct pf_kstate *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; + + 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, 0, r->rtableid, NULL); + + REASON_SET(&reason, PFRES_SYNPROXY); + return (PF_SYNPROXY_DROP); +} + static int pf_test_state(struct pf_kstate **state, struct pf_pdesc *pd, u_short *reason) { diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c index ca1815984797..0bc8b30181d6 100644 --- a/sys/netpfil/pf/pf_ioctl.c +++ b/sys/netpfil/pf/pf_ioctl.c @@ -136,6 +136,12 @@ static int pf_import_kaltq(struct pfioc_altq_v1 *, struct pf_altq *, size_t); #endif /* ALTQ */ +static void pf_statelim_commit(void); +static void pf_statelim_rollback(void); +static int pf_sourcelim_check(void); +static void pf_sourcelim_commit(void); +static void pf_sourcelim_rollback(void); + VNET_DEFINE(struct pf_krule, pf_default_rule); static __inline int pf_krule_compare(struct pf_krule *, @@ -187,6 +193,7 @@ VNET_DEFINE(uma_zone_t, pf_tag_z); static MALLOC_DEFINE(M_PFALTQ, "pf_altq", "pf(4) altq configuration db"); static MALLOC_DEFINE(M_PFRULE, "pf_rule", "pf(4) rules"); MALLOC_DEFINE(M_PF, "pf", "pf(4)"); +MALLOC_DEFINE(M_PF_STATE_LIM, "pf_state_lim", "pf(4) state limiter"); #if (PF_QNAME_SIZE != PF_TAG_NAME_SIZE) #error PF_QNAME_SIZE must be equal to PF_TAG_NAME_SIZE @@ -1318,6 +1325,12 @@ pf_rollback_rules(u_int32_t ticket, int rs_num, char *anchor) rs->rules[rs_num].inactive.rcount--; } rs->rules[rs_num].inactive.open = 0; + + if (anchor[0]) + return (0); + + pf_statelim_rollback(); + pf_sourcelim_rollback(); return (0); } @@ -1437,6 +1450,7 @@ pf_commit_rules(u_int32_t ticket, int rs_num, char *anchor) struct pf_krule_global *old_tree; int error; u_int32_t old_rcount; + bool is_main_ruleset = anchor[0] == '\0'; PF_RULES_WASSERT(); @@ -1449,6 +1463,9 @@ pf_commit_rules(u_int32_t ticket, int rs_num, char *anchor) /* Calculate checksum for the main ruleset */ if (rs == &pf_main_ruleset) { + error = pf_sourcelim_check(); + if (error != 0) + return (error); error = pf_setup_pfsync_matching(rs); if (error != 0) return (error); @@ -1507,6 +1524,13 @@ pf_commit_rules(u_int32_t ticket, int rs_num, char *anchor) pf_remove_if_empty_kruleset(rs); pf_rule_tree_free(old_tree); + /* statelim/sourcelim/queue defs only in the main ruleset */ + if (! is_main_ruleset || rs_num != PF_RULESET_FILTER) + return (0); + + pf_statelim_commit(); + pf_sourcelim_commit(); + return (0); } @@ -1589,6 +1613,844 @@ pf_addr_copyout(struct pf_addr_wrap *addr) } } +static int +pf_statelim_add(const struct pfioc_statelim *ioc) +{ + struct pf_statelim *pfstlim; + int error; + size_t namelen; + + if (ioc->id < PF_STATELIM_ID_MIN || + ioc->id > PF_STATELIM_ID_MAX) + return (EINVAL); + + if (ioc->limit < PF_STATELIM_LIMIT_MIN || + ioc->limit > PF_STATELIM_LIMIT_MAX) + return (EINVAL); + + if ((ioc->rate.limit == 0) != (ioc->rate.seconds == 0)) + return (EINVAL); + + namelen = strnlen(ioc->name, sizeof(ioc->name)); + if (namelen == sizeof(ioc->name)) + return (EINVAL); + + pfstlim = malloc(sizeof(*pfstlim), M_PF_STATE_LIM, M_WAITOK | M_ZERO); + if (pfstlim == NULL) + return (ENOMEM); + + pfstlim->pfstlim_id = ioc->id; + memcpy(pfstlim->pfstlim_nm, ioc->name, namelen); + pfstlim->pfstlim_limit = ioc->limit; + pfstlim->pfstlim_rate.limit = ioc->rate.limit; + pfstlim->pfstlim_rate.seconds = ioc->rate.seconds; + + if (pfstlim->pfstlim_rate.limit) { + uint64_t bucket = SEC_TO_NSEC(pfstlim->pfstlim_rate.seconds); + struct timespec ts; + + getnanouptime(&ts); + + pfstlim->pfstlim_rate_ts = SEC_TO_NSEC(ts.tv_sec) + ts.tv_nsec - + bucket; + pfstlim->pfstlim_rate_token = bucket / + pfstlim->pfstlim_rate.limit; + pfstlim->pfstlim_rate_bucket = bucket; + } + + TAILQ_INIT(&pfstlim->pfstlim_states); + mtx_init(&pfstlim->pfstlim_lock, "pf state limit", NULL, MTX_DEF); + + PF_RULES_WLOCK(); + if (ioc->ticket != pf_main_ruleset.rules[PF_RULESET_FILTER].inactive.ticket) { + error = EBUSY; + goto unlock; + } + + if (RB_INSERT(pf_statelim_id_tree, &V_pf_statelim_id_tree_inactive, + pfstlim) != NULL) { + error = EBUSY; + goto unlock; + } + + if (RB_INSERT(pf_statelim_nm_tree, &V_pf_statelim_nm_tree_inactive, + pfstlim) != NULL) { + RB_REMOVE(pf_statelim_id_tree, &V_pf_statelim_id_tree_inactive, + pfstlim); + error = EBUSY; + goto unlock; + } + + TAILQ_INSERT_HEAD(&V_pf_statelim_list_inactive, pfstlim, pfstlim_list); + + PF_RULES_WUNLOCK(); + + return (0); + +unlock: + PF_RULES_WUNLOCK(); + + /* free: */ + free(pfstlim, M_PF_STATE_LIM); + + return (error); +} + +static void +pf_statelim_unlink(struct pf_statelim *pfstlim, + struct pf_state_link_list *garbage) +{ + struct pf_state_link *pfl; + + + /* unwire the links */ + TAILQ_FOREACH(pfl, &pfstlim->pfstlim_states, pfl_link) { + struct pf_kstate *s = pfl->pfl_state; + + /* if !rmst */ + PF_STATE_LOCK(s); + s->statelim = 0; + SLIST_REMOVE(&s->linkage, pfl, pf_state_link, pfl_linkage); + PF_STATE_UNLOCK(s); + } + + /* take the list away */ + TAILQ_CONCAT(garbage, &pfstlim->pfstlim_states, pfl_link); + pfstlim->pfstlim_inuse = 0; +} + +void +pf_statelim_commit(void) +{ + struct pf_statelim *pfstlim, *npfstlim, *opfstlim; + struct pf_statelim_list l = TAILQ_HEAD_INITIALIZER(l); + struct pf_state_link_list garbage = TAILQ_HEAD_INITIALIZER(garbage); + struct pf_state_link *pfl, *npfl; + + PF_RULES_WASSERT(); + + /* merge the new statelims into the current set */ + + /* start with an empty active list */ + TAILQ_CONCAT(&l, &V_pf_statelim_list_active, pfstlim_list); + + /* beware, the inactive bits gets messed up here */ + + /* try putting pending statelims into the active tree */ + TAILQ_FOREACH_SAFE(pfstlim, &V_pf_statelim_list_inactive, pfstlim_list, + npfstlim) { + opfstlim = RB_INSERT(pf_statelim_id_tree, + &V_pf_statelim_id_tree_active, pfstlim); + if (opfstlim != NULL) { + /* this statelim already exists, merge */ + opfstlim->pfstlim_limit = pfstlim->pfstlim_limit; + opfstlim->pfstlim_rate.limit = + pfstlim->pfstlim_rate.limit; + opfstlim->pfstlim_rate.seconds = + pfstlim->pfstlim_rate.seconds; + + opfstlim->pfstlim_rate_ts = pfstlim->pfstlim_rate_ts; + opfstlim->pfstlim_rate_token = + pfstlim->pfstlim_rate_token; + opfstlim->pfstlim_rate_bucket = + pfstlim->pfstlim_rate_bucket; + + memcpy(opfstlim->pfstlim_nm, pfstlim->pfstlim_nm, + sizeof(opfstlim->pfstlim_nm)); + + /* use the existing statelim instead */ + free(pfstlim, M_PF_STATE_LIM); + TAILQ_REMOVE(&l, opfstlim, pfstlim_list); + pfstlim = opfstlim; + } + + TAILQ_INSERT_TAIL(&V_pf_statelim_list_active, pfstlim, + pfstlim_list); + } + + /* clean up the now unused statelims from the old set */ + TAILQ_FOREACH_SAFE(pfstlim, &l, pfstlim_list, npfstlim) { + pf_statelim_unlink(pfstlim, &garbage); + + RB_REMOVE(pf_statelim_id_tree, &V_pf_statelim_id_tree_active, + pfstlim); + + free(pfstlim, M_PF_STATE_LIM); + } + + /* fix up the inactive tree */ + RB_INIT(&V_pf_statelim_id_tree_inactive); + RB_INIT(&V_pf_statelim_nm_tree_inactive); + TAILQ_INIT(&V_pf_statelim_list_inactive); + + TAILQ_FOREACH_SAFE(pfl, &garbage, pfl_link, npfl) + free(pfl, M_PF_STATE_LINK); +} + +static void +pf_sourcelim_unlink(struct pf_sourcelim *pfsrlim, + struct pf_state_link_list *garbage) +{ + extern struct pf_source_list pf_source_gc; + struct pf_source *pfsr; + struct pf_state_link *pfl; + + PF_RULES_WASSERT(); + + while ((pfsr = RB_ROOT(&pfsrlim->pfsrlim_sources)) != NULL) { + RB_REMOVE(pf_source_tree, &pfsrlim->pfsrlim_sources, pfsr); + RB_REMOVE(pf_source_ioc_tree, &pfsrlim->pfsrlim_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_states, pfl_link) { + struct pf_kstate *s = pfl->pfl_state; + + PF_STATE_LOCK(s); + /* if !rmst */ + s->sourcelim = 0; + SLIST_REMOVE(&s->linkage, pfl, pf_state_link, + pfl_linkage); + PF_STATE_UNLOCK(s); + } + + /* take the list away */ + TAILQ_CONCAT(garbage, &pfsr->pfsr_states, pfl_link); + + free(pfsr, M_PF_SOURCE_LIM); + } +} + +int +pf_sourcelim_check(void) +{ + struct pf_sourcelim *pfsrlim, *npfsrlim; + + PF_RULES_WASSERT(); + + /* check if we can merge */ + + TAILQ_FOREACH(pfsrlim, &V_pf_sourcelim_list_inactive, pfsrlim_list) { + npfsrlim = RB_FIND(pf_sourcelim_id_tree, + &V_pf_sourcelim_id_tree_active, pfsrlim); + + /* new config, no conflict */ + if (npfsrlim == NULL) + continue; + + /* nothing is tracked at the moment, no conflict */ + if (RB_EMPTY(&npfsrlim->pfsrlim_sources)) + continue; + + if (strcmp(npfsrlim->pfsrlim_overload.name, + pfsrlim->pfsrlim_overload.name) != 0) + return (EBUSY); + + /* + * we should allow the prefixlens to get shorter + * and merge pf_source entries. + */ + + if ((npfsrlim->pfsrlim_ipv4_prefix != + pfsrlim->pfsrlim_ipv4_prefix) || + (npfsrlim->pfsrlim_ipv6_prefix != + pfsrlim->pfsrlim_ipv6_prefix)) + return (EBUSY); + } + + return (0); +} + +void +pf_sourcelim_commit(void) +{ + struct pf_sourcelim *pfsrlim, *npfsrlim, *opfsrlim; + struct pf_sourcelim_list l = TAILQ_HEAD_INITIALIZER(l); + struct pf_state_link_list garbage = TAILQ_HEAD_INITIALIZER(garbage); + struct pf_state_link *pfl, *npfl; + + PF_RULES_WASSERT(); + + /* merge the new sourcelims into the current set */ + + /* start with an empty active list */ + TAILQ_CONCAT(&l, &V_pf_sourcelim_list_active, pfsrlim_list); + + /* beware, the inactive bits gets messed up here */ + + /* try putting pending sourcelims into the active tree */ + TAILQ_FOREACH_SAFE(pfsrlim, &V_pf_sourcelim_list_inactive, pfsrlim_list, + npfsrlim) { + opfsrlim = RB_INSERT(pf_sourcelim_id_tree, + &V_pf_sourcelim_id_tree_active, pfsrlim); + if (opfsrlim != NULL) { + /* this sourcelim already exists, merge */ + opfsrlim->pfsrlim_entries = pfsrlim->pfsrlim_entries; + opfsrlim->pfsrlim_limit = pfsrlim->pfsrlim_limit; + opfsrlim->pfsrlim_ipv4_prefix = + pfsrlim->pfsrlim_ipv4_prefix; + opfsrlim->pfsrlim_ipv6_prefix = + pfsrlim->pfsrlim_ipv6_prefix; + opfsrlim->pfsrlim_rate.limit = + pfsrlim->pfsrlim_rate.limit; + opfsrlim->pfsrlim_rate.seconds = + pfsrlim->pfsrlim_rate.seconds; + + opfsrlim->pfsrlim_ipv4_mask = + pfsrlim->pfsrlim_ipv4_mask; + opfsrlim->pfsrlim_ipv6_mask = + pfsrlim->pfsrlim_ipv6_mask; + + /* keep the existing pfstlim_rate_ts */ + + opfsrlim->pfsrlim_rate_token = + pfsrlim->pfsrlim_rate_token; + opfsrlim->pfsrlim_rate_bucket = + pfsrlim->pfsrlim_rate_bucket; + + if (opfsrlim->pfsrlim_overload.table != NULL) { + pfr_detach_table( + opfsrlim->pfsrlim_overload.table); + } + + strlcpy(opfsrlim->pfsrlim_overload.name, + pfsrlim->pfsrlim_overload.name, + sizeof(opfsrlim->pfsrlim_overload.name)); + opfsrlim->pfsrlim_overload.hwm = + pfsrlim->pfsrlim_overload.hwm; + opfsrlim->pfsrlim_overload.lwm = + pfsrlim->pfsrlim_overload.lwm; + opfsrlim->pfsrlim_overload.table = + pfsrlim->pfsrlim_overload.table, + + memcpy(opfsrlim->pfsrlim_nm, pfsrlim->pfsrlim_nm, + sizeof(opfsrlim->pfsrlim_nm)); + + /* use the existing sourcelim instead */ + free(pfsrlim, M_PF_SOURCE_LIM); + TAILQ_REMOVE(&l, opfsrlim, pfsrlim_list); + pfsrlim = opfsrlim; + } + + TAILQ_INSERT_TAIL(&V_pf_sourcelim_list_active, pfsrlim, + pfsrlim_list); + } + + /* clean up the now unused sourcelims from the old set */ + TAILQ_FOREACH_SAFE(pfsrlim, &l, pfsrlim_list, npfsrlim) { + pf_sourcelim_unlink(pfsrlim, &garbage); + + RB_REMOVE(pf_sourcelim_id_tree, &V_pf_sourcelim_id_tree_active, + pfsrlim); + + if (pfsrlim->pfsrlim_overload.table != NULL) + pfr_detach_table(pfsrlim->pfsrlim_overload.table); + + free(pfsrlim, M_PF_SOURCE_LIM); + } + + /* fix up the inactive tree */ + RB_INIT(&V_pf_sourcelim_id_tree_inactive); + RB_INIT(&V_pf_sourcelim_nm_tree_inactive); + TAILQ_INIT(&V_pf_sourcelim_list_inactive); + + TAILQ_FOREACH_SAFE(pfl, &garbage, pfl_link, npfl) + free(pfl, M_PF_STATE_LINK); +} + +void +pf_statelim_rollback(void) +{ + struct pf_statelim *pfstlim, *npfstlim; + + PF_RULES_WASSERT(); + + TAILQ_FOREACH_SAFE(pfstlim, &V_pf_statelim_list_inactive, pfstlim_list, + npfstlim) + free(pfstlim, M_PF_STATE_LIM); + + TAILQ_INIT(&V_pf_statelim_list_inactive); + RB_INIT(&V_pf_statelim_id_tree_inactive); + RB_INIT(&V_pf_statelim_nm_tree_inactive); +} + +static struct pf_statelim * +pf_statelim_rb_find(struct pf_statelim_id_tree *tree, struct pf_statelim *key) +{ + PF_RULES_ASSERT(); + + return (RB_FIND(pf_statelim_id_tree, tree, key)); +} + +static struct pf_statelim * +pf_statelim_rb_nfind(struct pf_statelim_id_tree *tree, struct pf_statelim *key) +{ + PF_RULES_ASSERT(); + + return (RB_NFIND(pf_statelim_id_tree, tree, key)); +} + +static int +pf_statelim_get(struct pfioc_statelim *ioc, + struct pf_statelim *(*rbt_op)(struct pf_statelim_id_tree *, + struct pf_statelim *)) +{ + struct pf_statelim key = { .pfstlim_id = ioc->id }; + struct pf_statelim *pfstlim; + int error = 0; + PF_RULES_RLOCK_TRACKER; + + PF_RULES_RLOCK(); + + pfstlim = (*rbt_op)(&V_pf_statelim_id_tree_active, &key); + if (pfstlim == NULL) { + error = ENOENT; + goto unlock; + } + + ioc->id = pfstlim->pfstlim_id; + ioc->limit = pfstlim->pfstlim_limit; + ioc->rate.limit = pfstlim->pfstlim_rate.limit; + ioc->rate.seconds = pfstlim->pfstlim_rate.seconds; + CTASSERT(sizeof(ioc->name) == sizeof(pfstlim->pfstlim_nm)); + memcpy(ioc->name, pfstlim->pfstlim_nm, sizeof(ioc->name)); + + ioc->inuse = pfstlim->pfstlim_inuse; + ioc->admitted = pfstlim->pfstlim_counters.admitted; + ioc->hardlimited = pfstlim->pfstlim_counters.hardlimited; + ioc->ratelimited = pfstlim->pfstlim_counters.ratelimited; + +unlock: + PF_RULES_RUNLOCK(); + + return (error); +} + +static int +pf_sourcelim_add(const struct pfioc_sourcelim *ioc) +{ + struct pf_sourcelim *pfsrlim; + int error; + size_t namelen, tablelen; + unsigned int prefix; + size_t i; + + if (ioc->id < PF_SOURCELIM_ID_MIN || + ioc->id > PF_SOURCELIM_ID_MAX) + return (EINVAL); + + if (ioc->entries < 1) + return (EINVAL); + + if (ioc->limit < 1) + 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); + + namelen = strnlen(ioc->name, sizeof(ioc->name)); + if (namelen == sizeof(ioc->name)) + 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); + } + + pfsrlim = malloc(sizeof(*pfsrlim), M_PF_SOURCE_LIM, M_WAITOK | M_ZERO); + if (pfsrlim == NULL) + return (ENOMEM); + + pfsrlim->pfsrlim_id = ioc->id; + pfsrlim->pfsrlim_entries = ioc->entries; + pfsrlim->pfsrlim_limit = ioc->limit; + pfsrlim->pfsrlim_ipv4_prefix = ioc->inet_prefix; + pfsrlim->pfsrlim_ipv6_prefix = ioc->inet6_prefix; + pfsrlim->pfsrlim_rate.limit = ioc->rate.limit; + pfsrlim->pfsrlim_rate.seconds = ioc->rate.seconds; + memcpy(pfsrlim->pfsrlim_overload.name, ioc->overload_tblname, tablelen); + pfsrlim->pfsrlim_overload.hwm = ioc->overload_hwm; + pfsrlim->pfsrlim_overload.lwm = ioc->overload_lwm; + memcpy(pfsrlim->pfsrlim_nm, ioc->name, namelen); + + if (pfsrlim->pfsrlim_rate.limit) { + uint64_t bucket = pfsrlim->pfsrlim_rate.seconds * 1000000000ULL; + + pfsrlim->pfsrlim_rate_token = bucket / + pfsrlim->pfsrlim_rate.limit; + pfsrlim->pfsrlim_rate_bucket = bucket; + } + + pfsrlim->pfsrlim_ipv4_mask.v4.s_addr = htonl( + 0xffffffff << (32 - pfsrlim->pfsrlim_ipv4_prefix)); + + prefix = pfsrlim->pfsrlim_ipv6_prefix; + for (i = 0; i < nitems(pfsrlim->pfsrlim_ipv6_mask.addr32); i++) { + if (prefix == 0) { + /* the memory is already zeroed */ + break; + } + if (prefix < 32) { + pfsrlim->pfsrlim_ipv6_mask.addr32[i] = htonl( + 0xffffffff << (32 - prefix)); + break; + } + + pfsrlim->pfsrlim_ipv6_mask.addr32[i] = htonl(0xffffffff); + prefix -= 32; + } + + RB_INIT(&pfsrlim->pfsrlim_sources); + mtx_init(&pfsrlim->pfsrlim_lock, "pf source limit", NULL, MTX_DEF); + + PF_RULES_WLOCK(); + if (ioc->ticket != pf_main_ruleset.rules[PF_RULESET_FILTER].inactive.ticket) { + error = EBUSY; + goto unlock; + } + + if (pfsrlim->pfsrlim_overload.name[0] != '\0') { + pfsrlim->pfsrlim_overload.table = pfr_attach_table( + &pf_main_ruleset, pfsrlim->pfsrlim_overload.name); + if (pfsrlim->pfsrlim_overload.table == NULL) { + error = EINVAL; + goto unlock; + } + } + + if (RB_INSERT(pf_sourcelim_id_tree, &V_pf_sourcelim_id_tree_inactive, + pfsrlim) != NULL) { + error = EBUSY; + goto unlock; + } + + if (RB_INSERT(pf_sourcelim_nm_tree, &V_pf_sourcelim_nm_tree_inactive, + pfsrlim) != NULL) { + RB_INSERT(pf_sourcelim_nm_tree, &V_pf_sourcelim_nm_tree_inactive, + pfsrlim); + error = EBUSY; + goto unlock; + } + + TAILQ_INSERT_HEAD(&V_pf_sourcelim_list_inactive, pfsrlim, pfsrlim_list); + + PF_RULES_WUNLOCK(); + + return (0); + +unlock: + PF_RULES_WUNLOCK(); + /* free: */ + free(pfsrlim, M_PF_SOURCE_LIM); + + return (error); +} + +void +pf_sourcelim_rollback(void) +{ + struct pf_sourcelim *pfsrlim, *npfsrlim; + + PF_RULES_WASSERT(); + + TAILQ_FOREACH_SAFE(pfsrlim, &V_pf_sourcelim_list_inactive, pfsrlim_list, + npfsrlim) { + if (pfsrlim->pfsrlim_overload.table != NULL) + pfr_detach_table(pfsrlim->pfsrlim_overload.table); + + free(pfsrlim, M_PF_SOURCE_LIM); + } + + TAILQ_INIT(&V_pf_sourcelim_list_inactive); + RB_INIT(&V_pf_sourcelim_id_tree_inactive); + RB_INIT(&V_pf_sourcelim_nm_tree_inactive); +} + +static struct pf_sourcelim * +pf_sourcelim_rb_find(struct pf_sourcelim_id_tree *tree, + struct pf_sourcelim *key) +{ + PF_RULES_ASSERT(); + return (RB_FIND(pf_sourcelim_id_tree, tree, key)); +} + +static struct pf_sourcelim * +pf_sourcelim_rb_nfind(struct pf_sourcelim_id_tree *tree, + struct pf_sourcelim *key) +{ + PF_RULES_ASSERT(); + return (RB_NFIND(pf_sourcelim_id_tree, tree, key)); +} + +static int +pf_sourcelim_get(struct pfioc_sourcelim *ioc, + struct pf_sourcelim *(*rbt_op)(struct pf_sourcelim_id_tree *, + struct pf_sourcelim *)) +{ + struct pf_sourcelim key = { .pfsrlim_id = ioc->id }; + struct pf_sourcelim *pfsrlim; + int error = 0; + PF_RULES_RLOCK_TRACKER; + + PF_RULES_RLOCK(); +#if 0 + if (ioc->ticket != pf_main_ruleset.rules.active.ticket) { + error = EBUSY; + goto unlock; + } +#endif + + pfsrlim = (*rbt_op)(&V_pf_sourcelim_id_tree_active, &key); + if (pfsrlim == NULL) { + error = ESRCH; + goto unlock; + } + + ioc->id = pfsrlim->pfsrlim_id; + ioc->entries = pfsrlim->pfsrlim_entries; + ioc->limit = pfsrlim->pfsrlim_limit; + ioc->inet_prefix = pfsrlim->pfsrlim_ipv4_prefix; + ioc->inet6_prefix = pfsrlim->pfsrlim_ipv6_prefix; + ioc->rate.limit = pfsrlim->pfsrlim_rate.limit; + ioc->rate.seconds = pfsrlim->pfsrlim_rate.seconds; + + CTASSERT(sizeof(ioc->overload_tblname) == + sizeof(pfsrlim->pfsrlim_overload.name)); + memcpy(ioc->overload_tblname, pfsrlim->pfsrlim_overload.name, + sizeof(pfsrlim->pfsrlim_overload.name)); + ioc->overload_hwm = pfsrlim->pfsrlim_overload.hwm; + ioc->overload_lwm = pfsrlim->pfsrlim_overload.lwm; + + CTASSERT(sizeof(ioc->name) == sizeof(pfsrlim->pfsrlim_nm)); + memcpy(ioc->name, pfsrlim->pfsrlim_nm, sizeof(ioc->name)); + /* XXX overload table thing */ + + ioc->nentries = pfsrlim->pfsrlim_nsources; + + ioc->inuse = pfsrlim->pfsrlim_counters.inuse; + ioc->addrallocs = pfsrlim->pfsrlim_counters.addrallocs; + ioc->addrnomem = pfsrlim->pfsrlim_counters.addrnomem; + ioc->admitted = pfsrlim->pfsrlim_counters.admitted; + ioc->addrlimited = pfsrlim->pfsrlim_counters.addrlimited; + ioc->hardlimited = pfsrlim->pfsrlim_counters.hardlimited; + ioc->ratelimited = pfsrlim->pfsrlim_counters.ratelimited; + +unlock: + PF_RULES_RUNLOCK(); + + return (error); +} + +static struct pf_source * +pf_source_rb_find(struct pf_source_ioc_tree *tree, + struct pf_source *key) +{ + PF_RULES_ASSERT(); + + return (RB_FIND(pf_source_ioc_tree, tree, key)); +} + +static struct pf_source * +pf_source_rb_nfind(struct pf_source_ioc_tree *tree, + struct pf_source *key) +{ + PF_RULES_ASSERT(); + + return (RB_NFIND(pf_source_ioc_tree, tree, key)); +} + +static int +pf_source_get(struct pfioc_source *ioc, + struct pf_source *(*rbt_op)(struct pf_source_ioc_tree *, + struct pf_source *)) +{ + struct pf_sourcelim plkey = { .pfsrlim_id = ioc->id }; + struct pfioc_source_entry e, *uentry; + struct pf_source key; + struct pf_sourcelim *pfsrlim; + struct pf_source *pfsr; + size_t used = 0, len = ioc->entrieslen; + int error = 0; + PF_RULES_RLOCK_TRACKER; + + if (ioc->entry_size != sizeof(e)) + return (EINVAL); + if (len < sizeof(e)) + return (EMSGSIZE); + + error = copyin(ioc->key, &e, sizeof(e)); + if (error != 0) + return (error); + + PF_RULES_RLOCK(); + +#if 0 + if (ioc->ticket != pf_main_ruleset.rules.active.ticket) { + error = EBUSY; + goto unlock; + } +#endif + + pfsrlim = pf_sourcelim_rb_find(&V_pf_sourcelim_id_tree_active, &plkey); + if (pfsrlim == NULL) { + error = ESRCH; + goto unlock; + } + + key.pfsr_af = e.af; + key.pfsr_rdomain = e.rdomain; + key.pfsr_addr = e.addr; + pfsr = (*rbt_op)(&pfsrlim->pfsrlim_ioc_sources, &key); + if (pfsr == NULL) { + error = ENOENT; + goto unlock; + } + + memset(&e, 0, sizeof(e)); + + uentry = ioc->entries; + for (;;) { + e.af = pfsr->pfsr_af; + e.rdomain = pfsr->pfsr_rdomain; + e.addr = pfsr->pfsr_addr; + + e.inuse = pfsr->pfsr_inuse; + e.admitted = pfsr->pfsr_counters.admitted; + e.hardlimited = pfsr->pfsr_counters.hardlimited; + e.ratelimited = pfsr->pfsr_counters.ratelimited; + + error = copyout(&e, uentry, sizeof(e)); + if (error != 0) + goto unlock; + + used += sizeof(e); + if (used == len) + break; + + pfsr = RB_NEXT(pf_source_ioc_tree, srlim->pfsrlim_ioc_sources, pfsr); + if (pfsr == NULL) + break; + + if ((len - used) < sizeof(e)) { + error = EMSGSIZE; + goto unlock; + } + + uentry++; + } + MPASS(error == 0); + + ioc->inet_prefix = pfsrlim->pfsrlim_ipv4_prefix; + ioc->inet6_prefix = pfsrlim->pfsrlim_ipv6_prefix; + ioc->limit = pfsrlim->pfsrlim_limit; + + ioc->entrieslen = used; + +unlock: + PF_RULES_RUNLOCK(); + + return (error); +} + +static int +pf_source_clr(struct pfioc_source_kill *ioc) +{ + extern struct pf_source_list pf_source_gc; + struct pf_sourcelim plkey = { + .pfsrlim_id = ioc->id, + }; + struct pf_source skey = { + .pfsr_af = ioc->af, + .pfsr_rdomain = ioc->rdomain, + .pfsr_addr = ioc->addr, + }; + struct pf_sourcelim *pfsrlim; + struct pf_source *pfsr; + struct pf_state_link *pfl, *npfl; + int error = 0; + unsigned int gen; + + if (ioc->rmstates) { + /* XXX userland wants the states removed too */ + return (EOPNOTSUPP); + } + + PF_RULES_WLOCK(); + +#if 0 + if (ioc->ticket != pf_main_ruleset.rules.active.ticket) { + error = EBUSY; + goto unlock; + } +#endif + + pfsrlim = pf_sourcelim_rb_find(&V_pf_sourcelim_id_tree_active, &plkey); + if (pfsrlim == NULL) { + error = ESRCH; + goto unlock; + } + + pfsr = pf_source_rb_find(&pfsrlim->pfsrlim_ioc_sources, &skey); + if (pfsr == NULL) { + error = ENOENT; + goto unlock; + } + + RB_REMOVE(pf_source_tree, &pfsrlim->pfsrlim_sources, pfsr); + RB_REMOVE(pf_source_ioc_tree, &pfsrlim->pfsrlim_ioc_sources, pfsr); + if (pfsr->pfsr_inuse == 0) + TAILQ_REMOVE(&pf_source_gc, pfsr, pfsr_empty_gc); + + gen = pf_sourcelim_enter(pfsrlim); + pfsrlim->pfsrlim_nsources--; + pfsrlim->pfsrlim_counters.inuse -= pfsr->pfsr_inuse; + pf_sourcelim_leave(pfsrlim, gen); + + /* unwire the links */ + TAILQ_FOREACH(pfl, &pfsr->pfsr_states, pfl_link) { + struct pf_kstate *st = pfl->pfl_state; + + /* if !rmst */ + st->sourcelim = 0; + SLIST_REMOVE(&st->linkage, pfl, pf_state_link, pfl_linkage); + } + + PF_RULES_WUNLOCK(); + + TAILQ_FOREACH_SAFE(pfl, &pfsr->pfsr_states, pfl_link, npfl) + free(pfl, M_PF_STATE_LINK); + + free(pfsr, M_PF_SOURCE_LIM); + + return (0); + +unlock: + PF_RULES_WUNLOCK(); + + return (error); +} + static void pf_src_node_copy(const struct pf_ksrc_node *in, struct pf_src_node *out) { @@ -2181,6 +3043,18 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket, if (pf_validate_range(rule->dst.port_op, rule->dst.port)) ERROUT_UNLOCKED(EINVAL); + if (rule->statelim != PF_STATELIM_ID_NONE) { + if (rule->statelim < PF_STATELIM_ID_MIN || + rule->statelim > PF_STATELIM_ID_MAX) + ERROUT_UNLOCKED(EINVAL); + } + + if (rule->sourcelim != PF_SOURCELIM_ID_NONE) { + if (rule->sourcelim < PF_SOURCELIM_ID_MIN || + rule->sourcelim > PF_SOURCELIM_ID_MAX) + ERROUT_UNLOCKED(EINVAL); + } + if (rule->ifname[0]) kif = pf_kkif_create(M_WAITOK); if (rule->rcv_ifname[0]) @@ -3002,6 +3876,12 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td case DIOCGETTIMEOUT: case DIOCCLRRULECTRS: case DIOCGETLIMIT: + case DIOCGETSTATELIM: + case DIOCGETNSTATELIM: + case DIOCGETSOURCELIM: + case DIOCGETNSOURCELIM: + case DIOCGETSOURCE: + case DIOCGETNSOURCE: case DIOCGETALTQSV0: case DIOCGETALTQSV1: case DIOCGETALTQV0: @@ -3061,6 +3941,12 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td #endif case DIOCGETTIMEOUT: case DIOCGETLIMIT: + case DIOCGETSTATELIM: + case DIOCGETNSTATELIM: + case DIOCGETSOURCELIM: + case DIOCGETNSOURCELIM: + case DIOCGETSOURCE: + case DIOCGETNSOURCE: case DIOCGETALTQSV0: case DIOCGETALTQSV1: case DIOCGETALTQV0: @@ -4349,6 +5235,42 @@ DIOCGETSTATESV2_full: break; } + case DIOCADDSTATELIM: + error = pf_statelim_add((struct pfioc_statelim *)addr); + break; + case DIOCGETSTATELIM: + error = pf_statelim_get((struct pfioc_statelim *)addr, + pf_statelim_rb_find); + break; + case DIOCGETNSTATELIM: + error = pf_statelim_get((struct pfioc_statelim *)addr, + pf_statelim_rb_nfind); + break; + + case DIOCADDSOURCELIM: + error = pf_sourcelim_add((struct pfioc_sourcelim *)addr); + break; + case DIOCGETSOURCELIM: + error = pf_sourcelim_get((struct pfioc_sourcelim *)addr, + pf_sourcelim_rb_find); + break; + case DIOCGETNSOURCELIM: + error = pf_sourcelim_get((struct pfioc_sourcelim *)addr, + pf_sourcelim_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 DIOCCLRSOURCE: + error = pf_source_clr((struct pfioc_source_kill *)addr); + break; + case DIOCCLRRULECTRS: { /* obsoleted by DIOCGETRULE with action=PF_GET_CLR_CNTR */ struct pf_kruleset *ruleset = &pf_main_ruleset; diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c index 1c8a1f95b650..6e19beec5bfe 100644 --- a/sys/netpfil/pf/pf_nl.c +++ b/sys/netpfil/pf/pf_nl.c @@ -784,6 +784,8 @@ static const struct nlattr_parser nla_p_rule[] = { { .type = PF_RT_MAX_PKT_SIZE, .off = _OUT(max_pkt_size), .cb = nlattr_get_uint16 }, { .type = PF_RT_TYPE_2, .off = _OUT(type), .cb = nlattr_get_uint16 }, { .type = PF_RT_CODE_2, .off = _OUT(code), .cb = nlattr_get_uint16 }, + { .type = PF_RT_STATE_LIMIT, .off = _OUT(statelim), .cb = nlattr_get_uint8 }, + { .type = PF_RT_SOURCE_LIMIT, .off = _OUT(sourcelim), .cb = nlattr_get_uint8 }, }; NL_DECLARE_ATTR_PARSER(rule_parser, nla_p_rule); #undef _OUT @@ -1041,6 +1043,8 @@ pf_handle_getrule(struct nlmsghdr *hdr, struct nl_pstate *npt) nlattr_add_u64(nw, PF_RT_SRC_NODES_ROUTE, counter_u64_fetch(rule->src_nodes[PF_SN_ROUTE])); nlattr_add_pf_threshold(nw, PF_RT_PKTRATE, &rule->pktrate); nlattr_add_time_t(nw, PF_RT_EXPTIME, time_second - (time_uptime - rule->exptime)); + nlattr_add_u8(nw, PF_RT_STATE_LIMIT, rule->statelim); + nlattr_add_u8(nw, PF_RT_SOURCE_LIMIT, rule->sourcelim); error = pf_kanchor_copyout(ruleset, rule, anchor_call, sizeof(anchor_call)); MPASS(error == 0); diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h index 216f3d13db32..d45766b91a30 100644 --- a/sys/netpfil/pf/pf_nl.h +++ b/sys/netpfil/pf/pf_nl.h @@ -290,6 +290,8 @@ enum pf_rule_type_t { PF_RT_TYPE_2 = 84, /* u16 */ PF_RT_CODE_2 = 85, /* u16 */ PF_RT_EXPTIME = 86, /* time_t */ + PF_RT_STATE_LIMIT = 87, /* uint8_t */ + PF_RT_SOURCE_LIMIT = 88, /* uint8_t */ }; enum pf_addrule_type_t { diff --git a/sys/netpfil/pf/pf_table.c b/sys/netpfil/pf/pf_table.c index 0e2b9fe1cac8..650334c45db3 100644 --- a/sys/netpfil/pf/pf_table.c +++ b/sys/netpfil/pf/pf_table.c @@ -882,6 +882,26 @@ pfr_insert_kentry(struct pfr_ktable *kt, struct pfr_addr *ad, time_t tzero) 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 || p->pfrke_not) + return (ESRCH); + + if (p->pfrke_mark) + return (0); + + p->pfrke_mark = 1; + SLIST_INSERT_HEAD(&workq, p, pfrke_workq); + pfr_remove_kentries(kt, &workq); + + return (0); +} + static void pfr_remove_kentries(struct pfr_ktable *kt, struct pfr_kentryworkq *workq) |
