diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -216,6 +216,7 @@ uint64_t states_cur; uint64_t states_tot; uint64_t src_nodes; + uint64_t src_nodes_type[PF_SN_MAX]; uint16_t return_icmp; uint16_t return_icmp6; @@ -373,6 +374,7 @@ uint8_t set_prio[2]; uint8_t rt; char rt_ifname[IFNAMSIZ]; + uint8_t src_node_flags; }; TAILQ_HEAD(pfctl_statelist, pfctl_state); @@ -415,6 +417,7 @@ uint64_t creation; uint64_t expire; struct pfctl_threshold conn_rate; + pf_sn_types_t type; }; #define PF_DEVICE "/dev/pf" diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -1665,6 +1665,9 @@ { .type = PF_RT_NAF, .off = _OUT(r.naf), .cb = snl_attr_get_uint8 }, { .type = PF_RT_RPOOL_RT, .off = _OUT(r.route), .arg = &pool_parser, .cb = snl_attr_get_nested }, { .type = PF_RT_RCV_IFNOT, .off = _OUT(r.rcvifnot),.cb = snl_attr_get_bool }, + { .type = PF_RT_SRC_NODES_LIMIT, .off = _OUT(r.src_nodes_type[PF_SN_LIMIT]), .cb = snl_attr_get_uint64 }, + { .type = PF_RT_SRC_NODES_NAT, .off = _OUT(r.src_nodes_type[PF_SN_NAT]), .cb = snl_attr_get_uint64 }, + { .type = PF_RT_SRC_NODES_ROUTE, .off = _OUT(r.src_nodes_type[PF_SN_ROUTE]), .cb = snl_attr_get_uint64 }, }; #undef _OUT SNL_DECLARE_PARSER(getrule_parser, struct genlmsghdr, snl_f_p_empty, ap_getrule); @@ -1910,6 +1913,7 @@ { .type = PF_ST_DNRPIPE, .off = _OUT(dnrpipe), .cb = snl_attr_get_uint16 }, { .type = PF_ST_RT, .off = _OUT(rt), .cb = snl_attr_get_uint8 }, { .type = PF_ST_RT_IFNAME, .off = _OUT(rt_ifname), .cb = snl_attr_store_ifname }, + { .type = PF_ST_SRC_NODE_FLAGS, .off = _OUT(src_node_flags), .cb = snl_attr_get_uint8 }, }; #undef _IN #undef _OUT @@ -3018,6 +3022,7 @@ { .type = PF_SN_EXPIRE, .off = _OUT(expire), .cb = snl_attr_get_uint64 }, { .type = PF_SN_CONNECTION_RATE, .off = _OUT(conn_rate), .arg = &pfctl_threshold_parser, .cb = snl_attr_get_nested }, { .type = PF_SN_NAF, .off = _OUT(naf), .cb = snl_attr_get_uint8 }, + { .type = PF_SN_NODE_TYPE, .off = _OUT(type), .cb = snl_attr_get_uint8 }, }; #undef _OUT SNL_DECLARE_PARSER(srcnode_parser, struct genlmsghdr, snl_f_p_empty, ap_srcnode); diff --git a/sbin/pfctl/pf_print_state.c b/sbin/pfctl/pf_print_state.c --- a/sbin/pfctl/pf_print_state.c +++ b/sbin/pfctl/pf_print_state.c @@ -245,6 +245,7 @@ uint8_t proto; int afto = (s->key[PF_SK_STACK].af != s->key[PF_SK_WIRE].af); int idx; + const char *sn_type_names[] = PF_SN_TYPE_NAMES; #ifndef __NO_STRICT_ALIGNMENT struct pfctl_state_key aligned_key[2]; @@ -405,10 +406,14 @@ printf(", dummynet queue (%d %d)", s->dnpipe, s->dnrpipe); } - if (s->sync_flags & PFSYNC_FLAG_SRCNODE) - printf(", source-track"); - if (s->sync_flags & PFSYNC_FLAG_NATSRCNODE) - printf(", sticky-address"); + if (s->src_node_flags & PFSTATE_SRC_NODE_LIMIT) + printf(", %s", sn_type_names[PF_SN_LIMIT]); + if (s->src_node_flags & PFSTATE_SRC_NODE_LIMIT_GLOBAL) + printf(" global"); + if (s->src_node_flags & PFSTATE_SRC_NODE_NAT) + printf(", %s", sn_type_names[PF_SN_NAT]); + if (s->src_node_flags & PFSTATE_SRC_NODE_ROUTE) + printf(", %s", sn_type_names[PF_SN_ROUTE]); if (s->log) printf(", log"); if (s->log & PF_LOG_ALL) diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -1064,6 +1064,15 @@ rule->packets[1]), (unsigned long long)(rule->bytes[0] + rule->bytes[1]), (uintmax_t)rule->states_cur); + printf(" [ Source Nodes: %-6ju " + "Limit: %-6ju " + "NAT/RDR: %-6ju " + "Route: %-6ju " + "]\n", + (uintmax_t)rule->src_nodes, + (uintmax_t)rule->src_nodes_type[PF_SN_LIMIT], + (uintmax_t)rule->src_nodes_type[PF_SN_NAT], + (uintmax_t)rule->src_nodes_type[PF_SN_ROUTE]); if (!(opts & PF_OPT_DEBUG)) printf(" [ Inserted: uid %u pid %u " "State Creations: %-6ju]\n", diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c --- a/sbin/pfctl/pfctl_parser.c +++ b/sbin/pfctl/pfctl_parser.c @@ -651,6 +651,7 @@ { struct pf_addr_wrap aw; uint64_t min, sec; + const char *sn_type_names[] = PF_SN_TYPE_NAMES; memset(&aw, 0, sizeof(aw)); if (sn->af == AF_INET) @@ -699,6 +700,7 @@ printf(", filter rule %u", sn->rule); break; } + printf(", %s", sn_type_names[sn->type]); printf("\n"); } } diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -624,6 +624,21 @@ #define PF_ALGNMNT(off) (((off) % 2) == 0) +/* + * At the moment there are no rules which have both NAT and RDR actions, + * apart from af-to rules, but those don't to source tracking for address + * translation. And the r->rdr pool is used for both NAT and RDR. + * So there is no PF_SN_RDR. + */ +enum pf_sn_types { PF_SN_LIMIT, PF_SN_NAT, PF_SN_ROUTE, PF_SN_MAX }; +typedef enum pf_sn_types pf_sn_types_t; +#define PF_SN_TYPE_NAMES { \ + "limit source-track", \ + "NAT/RDR sticky-address", \ + "route sticky-address", \ + NULL \ +} + #ifdef _KERNEL struct pf_kpooladdr { @@ -822,7 +837,7 @@ counter_u64_t states_cur; counter_u64_t states_tot; - counter_u64_t src_nodes; + counter_u64_t src_nodes[PF_SN_MAX]; u_int16_t return_icmp; u_int16_t return_icmp6; @@ -904,6 +919,7 @@ sa_family_t af; sa_family_t naf; u_int8_t ruletype; + pf_sn_types_t type; struct mtx *lock; }; #endif @@ -1104,8 +1120,7 @@ struct pf_udp_mapping *udp_mapping; struct pfi_kkif *kif; struct pfi_kkif *orig_kif; /* The real kif, even if we're a floating state (i.e. if == V_pfi_all). */ - struct pf_ksrc_node *src_node; - struct pf_ksrc_node *nat_src_node; + struct pf_ksrc_node *sns[PF_SN_MAX];/* source nodes */ u_int64_t packets[2]; u_int64_t bytes[2]; u_int64_t creation; @@ -1118,9 +1133,10 @@ }; /* - * Size <= fits 11 objects per page on LP64. Try to not grow the struct beyond that. + * 6 cache lines per struct, 10 structs per page. + * Try to not grow the struct beyond that. */ -_Static_assert(sizeof(struct pf_kstate) <= 372, "pf_kstate size crosses 372 bytes"); +_Static_assert(sizeof(struct pf_kstate) <= 384, "pf_kstate size crosses 384 bytes"); #endif /* @@ -2367,7 +2383,7 @@ struct pf_srchash *); extern struct pf_ksrc_node *pf_find_src_node(struct pf_addr *, struct pf_krule *, sa_family_t, - struct pf_srchash **, bool); + struct pf_srchash **, pf_sn_types_t, bool); extern void pf_unlink_src_node(struct pf_ksrc_node *); extern u_int pf_free_src_nodes(struct pf_ksrc_node_list *); extern void pf_print_state(struct pf_kstate *); @@ -2670,7 +2686,7 @@ struct pf_addr *, struct pf_addr *, struct pfi_kkif **nkif, struct pf_addr *, struct pf_ksrc_node **, struct pf_srchash **, - struct pf_kpool *); + struct pf_kpool *, pf_sn_types_t); int pf_get_transaddr_af(struct pf_krule *, struct pf_pdesc *); u_short pf_get_translation(struct pf_pdesc *, diff --git a/sys/netpfil/pf/pf.h b/sys/netpfil/pf/pf.h --- a/sys/netpfil/pf/pf.h +++ b/sys/netpfil/pf/pf.h @@ -649,6 +649,12 @@ #define PFSTATE_SCRUBMASK (PFSTATE_NODF|PFSTATE_RANDOMID|PFSTATE_SCRUB_TCP) #define PFSTATE_SETMASK (PFSTATE_SETTOS|PFSTATE_SETPRIO) +/* pfctl_state->src_node_flags */ +#define PFSTATE_SRC_NODE_LIMIT 0x01 +#define PFSTATE_SRC_NODE_NAT 0x02 +#define PFSTATE_SRC_NODE_ROUTE 0x04 +#define PFSTATE_SRC_NODE_LIMIT_GLOBAL 0x10 + #define PFSTATE_HIWAT 100000 /* default state table size */ #define PFSTATE_ADAPT_START 60000 /* default adaptive timeout start */ #define PFSTATE_ADAPT_END 120000 /* default adaptive timeout end */ diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -389,7 +389,7 @@ static u_short pf_insert_src_node(struct pf_ksrc_node **, struct pf_srchash **, struct pf_krule *, struct pf_addr *, sa_family_t, struct pf_addr *, - struct pfi_kkif *); + struct pfi_kkif *, pf_sn_types_t); static u_int pf_purge_expired_states(u_int, int); static void pf_purge_unlinked_rules(void); static int pf_mtag_uminit(void *, int, int); @@ -835,25 +835,26 @@ static bool pf_src_connlimit(struct pf_kstate *state) { - struct pf_overload_entry *pfoe; - bool limited = false; + struct pf_overload_entry *pfoe; + struct pf_ksrc_node *src_node = state->sns[PF_SN_LIMIT]; + bool limited = false; PF_STATE_LOCK_ASSERT(state); - PF_SRC_NODE_LOCK(state->src_node); + PF_SRC_NODE_LOCK(src_node); - state->src_node->conn++; + src_node->conn++; state->src.tcp_est = 1; - pf_add_threshold(&state->src_node->conn_rate); + pf_add_threshold(&src_node->conn_rate); if (state->rule->max_src_conn && state->rule->max_src_conn < - state->src_node->conn) { + src_node->conn) { counter_u64_add(V_pf_status.lcounters[LCNT_SRCCONN], 1); limited = true; } if (state->rule->max_src_conn_rate.limit && - pf_check_threshold(&state->src_node->conn_rate)) { + pf_check_threshold(&src_node->conn_rate)) { counter_u64_add(V_pf_status.lcounters[LCNT_SRCCONNRATE], 1); limited = true; } @@ -873,7 +874,7 @@ if (pfoe == NULL) goto done; /* too bad :( */ - bcopy(&state->src_node->addr, &pfoe->addr, sizeof(pfoe->addr)); + bcopy(&src_node->addr, &pfoe->addr, sizeof(pfoe->addr)); pfoe->af = state->key[PF_SK_WIRE]->af; pfoe->rule = state->rule; pfoe->dir = state->direction; @@ -883,7 +884,7 @@ taskqueue_enqueue(taskqueue_swi, &V_pf_overloadtask); done: - PF_SRC_NODE_UNLOCK(state->src_node); + PF_SRC_NODE_UNLOCK(src_node); return (limited); } @@ -985,7 +986,7 @@ */ struct pf_ksrc_node * pf_find_src_node(struct pf_addr *src, struct pf_krule *rule, sa_family_t af, - struct pf_srchash **sh, bool returnlocked) + struct pf_srchash **sh, pf_sn_types_t sn_type, bool returnlocked) { struct pf_ksrc_node *n; @@ -994,7 +995,7 @@ *sh = &V_pf_srchash[pf_hashsrc(src, af)]; PF_HASHROW_LOCK(*sh); LIST_FOREACH(n, &(*sh)->nodes, entry) - if (n->rule == rule && n->af == af && + if (n->rule == rule && n->af == af && n->type == sn_type && ((af == AF_INET && n->addr.v4.s_addr == src->v4.s_addr) || (af == AF_INET6 && bcmp(&n->addr, src, sizeof(*src)) == 0))) break; @@ -1039,27 +1040,43 @@ } static u_short -pf_insert_src_node(struct pf_ksrc_node **sn, struct pf_srchash **sh, - struct pf_krule *rule, struct pf_addr *src, sa_family_t af, - struct pf_addr *raddr, struct pfi_kkif *rkif) +pf_insert_src_node(struct pf_ksrc_node *sns[PF_SN_MAX], + struct pf_srchash *snhs[PF_SN_MAX], struct pf_krule *rule, + struct pf_addr *src, sa_family_t af, struct pf_addr *raddr, + struct pfi_kkif *rkif, pf_sn_types_t sn_type) { u_short reason = 0; + struct pf_krule *r_track = rule; + struct pf_ksrc_node **sn = &(sns[sn_type]); + struct pf_srchash **sh = &(snhs[sn_type]); - KASSERT((rule->rule_flag & PFRULE_SRCTRACK || - rule->rdr.opts & PF_POOL_STICKYADDR), - ("%s for non-tracking rule %p", __func__, rule)); + KASSERT(sn_type != PF_SN_LIMIT || (raddr == NULL && rkif == NULL), + ("%s: raddr and rkif must be NULL for PF_SN_LIMIT", __func__)); + + KASSERT(sn_type != PF_SN_LIMIT || (rule->rule_flag & PFRULE_SRCTRACK), + ("%s: PF_SN_LIMIT only valid for rules with PFRULE_SRCTRACK", __func__)); + + /* + * XXX: There could be a KASSERT for + * sn_type == PF_SN_LIMIT || (pool->opts & PF_POOL_STICKYADDR) + * but we'd need to pass pool *only* for this KASSERT. + */ + + if ( (rule->rule_flag & PFRULE_SRCTRACK) && + !(rule->rule_flag & PFRULE_RULESRCTRACK)) + r_track = &V_pf_default_rule; /* * Request the sh to always be locked, as we might insert a new sn. */ if (*sn == NULL) - *sn = pf_find_src_node(src, rule, af, sh, true); + *sn = pf_find_src_node(src, r_track, af, sh, sn_type, true); if (*sn == NULL) { PF_HASHROW_ASSERT(*sh); - if (rule->max_src_nodes && - counter_u64_fetch(rule->src_nodes) >= rule->max_src_nodes) { + if (sn_type == PF_SN_LIMIT && rule->max_src_nodes && + counter_u64_fetch(r_track->src_nodes[sn_type]) >= rule->max_src_nodes) { counter_u64_add(V_pf_status.lcounters[LCNT_SRCNODES], 1); reason = PFRES_SRCLIMIT; goto done; @@ -1082,26 +1099,28 @@ } } - pf_init_threshold(&(*sn)->conn_rate, - rule->max_src_conn_rate.limit, - rule->max_src_conn_rate.seconds); + if (sn_type == PF_SN_LIMIT) + pf_init_threshold(&(*sn)->conn_rate, + rule->max_src_conn_rate.limit, + rule->max_src_conn_rate.seconds); MPASS((*sn)->lock == NULL); (*sn)->lock = &(*sh)->lock; (*sn)->af = af; - (*sn)->rule = rule; + (*sn)->rule = r_track; PF_ACPY(&(*sn)->addr, src, af); - PF_ACPY(&(*sn)->raddr, raddr, af); + if (raddr != NULL) + PF_ACPY(&(*sn)->raddr, raddr, af); (*sn)->rkif = rkif; LIST_INSERT_HEAD(&(*sh)->nodes, *sn, entry); (*sn)->creation = time_uptime; (*sn)->ruletype = rule->action; - if ((*sn)->rule != NULL) - counter_u64_add((*sn)->rule->src_nodes, 1); + (*sn)->type = sn_type; + counter_u64_add(r_track->src_nodes[sn_type], 1); counter_u64_add(V_pf_status.scounters[SCNT_SRC_NODE_INSERT], 1); } else { - if (rule->max_src_states && + if (sn_type == PF_SN_LIMIT && rule->max_src_states && (*sn)->states >= rule->max_src_states) { counter_u64_add(V_pf_status.lcounters[LCNT_SRCSTATES], 1); @@ -1126,7 +1145,7 @@ LIST_REMOVE(src, entry); if (src->rule) - counter_u64_add(src->rule->src_nodes, -1); + counter_u64_add(src->rule->src_nodes[src->type], -1); } u_int @@ -2647,30 +2666,24 @@ static void pf_src_tree_remove_state(struct pf_kstate *s) { - struct pf_ksrc_node *sn; uint32_t timeout; timeout = s->rule->timeout[PFTM_SRC_NODE] ? s->rule->timeout[PFTM_SRC_NODE] : V_pf_default_rule.timeout[PFTM_SRC_NODE]; - if (s->src_node != NULL) { - sn = s->src_node; - PF_SRC_NODE_LOCK(sn); - if (s->src.tcp_est) - --sn->conn; - if (--sn->states == 0) - sn->expire = time_uptime + timeout; - PF_SRC_NODE_UNLOCK(sn); - } - if (s->nat_src_node != s->src_node && s->nat_src_node != NULL) { - sn = s->nat_src_node; - PF_SRC_NODE_LOCK(sn); - if (--sn->states == 0) - sn->expire = time_uptime + timeout; - PF_SRC_NODE_UNLOCK(sn); + for (pf_sn_types_t sn_type=0; sn_typesns[sn_type] == NULL) + continue; + PF_SRC_NODE_LOCK(s->sns[sn_type]); + if (sn_type == PF_SN_LIMIT && s->src.tcp_est) + --(s->sns[sn_type]->conn); + if (--(s->sns[sn_type]->states) == 0) + s->sns[sn_type]->expire = time_uptime + timeout; + PF_SRC_NODE_UNLOCK(s->sns[sn_type]); + s->sns[sn_type] = NULL; } - s->src_node = s->nat_src_node = NULL; + } /* @@ -5895,7 +5908,7 @@ pd->act.rt = r->rt; /* Don't use REASON_SET, pf_map_addr increases the reason counters */ reason = pf_map_addr_sn(pd->af, r, pd->src, &pd->act.rt_addr, - &pd->act.rt_kif, NULL, &sn, &snh, pool); + &pd->act.rt_kif, NULL, &sn, &snh, pool, PF_SN_ROUTE); if (reason != 0) goto cleanup; } @@ -5997,14 +6010,18 @@ struct pf_udp_mapping *udp_mapping) { struct pf_kstate *s = NULL; - struct pf_ksrc_node *sn = NULL; - struct pf_srchash *snh = NULL; - struct pf_ksrc_node *nsn = NULL; - struct pf_srchash *nsnh = NULL; + struct pf_ksrc_node *sns[PF_SN_MAX] = { NULL }; + /* + * XXXKS: The hash for PF_SN_LIMIT and PF_SN_ROUTE should be the same + * but for PF_SN_NAT it is different. Don't try optimizing it, + * just store all 3 hashes. + */ + struct pf_srchash *snhs[PF_SN_MAX] = { NULL }; struct tcphdr *th = &pd->hdr.tcp; u_int16_t mss = V_tcp_mssdflt; u_short reason, sn_reason; struct pf_krule_item *ri; + struct pf_kpool *pool_route = &r->route; /* check maximums */ if (r->max_states && @@ -6013,18 +6030,26 @@ REASON_SET(&reason, PFRES_MAXSTATES); goto csfailed; } - /* src node for filter rule */ - if ((r->rule_flag & PFRULE_SRCTRACK || - r->rdr.opts & PF_POOL_STICKYADDR) && - (sn_reason = pf_insert_src_node(&sn, &snh, r, pd->src, pd->af, - &pd->act.rt_addr, pd->act.rt_kif)) != 0) { + /* src node for limits */ + if ((r->rule_flag & PFRULE_SRCTRACK) && + (sn_reason = pf_insert_src_node(sns, snhs, r, pd->src, pd->af, + NULL, NULL, PF_SN_LIMIT)) != 0) { + REASON_SET(&reason, sn_reason); + goto csfailed; + } + /* src node for route-to rule */ + if (TAILQ_EMPTY(&pool_route->list)) /* Backwards compatibility. */ + pool_route = &r->rdr; + if ((pool_route->opts & PF_POOL_STICKYADDR) && + (sn_reason = pf_insert_src_node(sns, snhs, r, pd->src, pd->af, + &pd->act.rt_addr, pd->act.rt_kif, PF_SN_ROUTE)) != 0) { REASON_SET(&reason, sn_reason); goto csfailed; } /* src node for translation rule */ if (nr != NULL && (nr->rdr.opts & PF_POOL_STICKYADDR) && - (sn_reason = pf_insert_src_node(&nsn, &nsnh, nr, &sk->addr[pd->sidx], - pd->af, &nk->addr[1], NULL)) != 0 ) { + (sn_reason = pf_insert_src_node(sns, snhs, nr, &sk->addr[pd->sidx], + pd->af, &nk->addr[1], NULL, PF_SN_NAT)) != 0 ) { REASON_SET(&reason, sn_reason); goto csfailed; } @@ -6166,13 +6191,11 @@ /* * Lock order is important: first state, then source node. */ - if (pf_src_node_exists(&sn, snh)) { - s->src_node = sn; - PF_HASHROW_UNLOCK(snh); - } - if (pf_src_node_exists(&nsn, nsnh)) { - s->nat_src_node = nsn; - PF_HASHROW_UNLOCK(nsnh); + for (pf_sn_types_t sn_type=0; sn_typesns[sn_type] = sns[sn_type]; + PF_HASHROW_UNLOCK(snhs[sn_type]); + } } if (tag > 0) @@ -6223,24 +6246,17 @@ uma_zfree(V_pf_state_key_z, sk); uma_zfree(V_pf_state_key_z, nk); - if (pf_src_node_exists(&sn, snh)) { - if (--sn->states == 0 && sn->expire == 0) { - pf_unlink_src_node(sn); - pf_free_src_node(sn); - counter_u64_add( - V_pf_status.scounters[SCNT_SRC_NODE_REMOVALS], 1); - } - PF_HASHROW_UNLOCK(snh); - } - - if (sn != nsn && pf_src_node_exists(&nsn, nsnh)) { - if (--nsn->states == 0 && nsn->expire == 0) { - pf_unlink_src_node(nsn); - pf_free_src_node(nsn); - counter_u64_add( - V_pf_status.scounters[SCNT_SRC_NODE_REMOVALS], 1); + for (pf_sn_types_t sn_type=0; sn_typestates == 0 && + sns[sn_type]->expire == 0) { + pf_unlink_src_node(sns[sn_type]); + pf_free_src_node(sns[sn_type]); + counter_u64_add( + V_pf_status.scounters[SCNT_SRC_NODE_REMOVALS], 1); + } + PF_HASHROW_UNLOCK(snhs[sn_type]); } - PF_HASHROW_UNLOCK(nsnh); } drop: @@ -6575,7 +6591,7 @@ pf_set_protostate(*state, pdst, TCPS_ESTABLISHED); if (src->state == TCPS_ESTABLISHED && - (*state)->src_node != NULL && + (*state)->sns[PF_SN_LIMIT] != NULL && pf_src_connlimit(*state)) { REASON_SET(reason, PFRES_SRCLIMIT); return (PF_DROP); @@ -6746,7 +6762,7 @@ if (dst->state == TCPS_SYN_SENT) { pf_set_protostate(*state, pdst, TCPS_ESTABLISHED); if (src->state == TCPS_ESTABLISHED && - (*state)->src_node != NULL && + (*state)->sns[PF_SN_LIMIT] != NULL && pf_src_connlimit(*state)) { REASON_SET(reason, PFRES_SRCLIMIT); return (PF_DROP); @@ -6764,7 +6780,7 @@ pf_set_protostate(*state, PF_PEER_BOTH, TCPS_ESTABLISHED); dst->state = src->state = TCPS_ESTABLISHED; - if ((*state)->src_node != NULL && + if ((*state)->sns[PF_SN_LIMIT] != NULL && pf_src_connlimit(*state)) { REASON_SET(reason, PFRES_SRCLIMIT); return (PF_DROP); @@ -6831,7 +6847,7 @@ (ntohl(th->th_seq) != (*state)->src.seqlo + 1)) { REASON_SET(reason, PFRES_SYNPROXY); return (PF_DROP); - } else if ((*state)->src_node != NULL && + } else if ((*state)->sns[PF_SN_LIMIT] != NULL && pf_src_connlimit(*state)) { REASON_SET(reason, PFRES_SRCLIMIT); return (PF_DROP); @@ -10023,17 +10039,21 @@ pf_counter_u64_add_protected(&s->nat_rule->bytes[dirndx], pd->tot_len); } - if (s->src_node != NULL) { - counter_u64_add(s->src_node->packets[dirndx], - 1); - counter_u64_add(s->src_node->bytes[dirndx], - pd->tot_len); - } - if (s->nat_src_node != NULL) { - counter_u64_add(s->nat_src_node->packets[dirndx], - 1); - counter_u64_add(s->nat_src_node->bytes[dirndx], - pd->tot_len); + /* + * Source nodes are accessed unlocked here. + * But since we are operating with stateful tracking + * and the state is locked, those SNs could not have + * been freed. + */ + for (pf_sn_types_t sn_type=0; sn_typesns[sn_type] != NULL) { + counter_u64_add( + s->sns[sn_type]->packets[dirndx], + 1); + counter_u64_add( + s->sns[sn_type]->bytes[dirndx], + pd->tot_len); + } } dirndx = (dir == s->direction) ? 0 : 1; s->packets[dirndx]++; diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c --- a/sys/netpfil/pf/pf_ioctl.c +++ b/sys/netpfil/pf/pf_ioctl.c @@ -352,7 +352,8 @@ } V_pf_default_rule.states_cur = counter_u64_alloc(M_WAITOK); V_pf_default_rule.states_tot = counter_u64_alloc(M_WAITOK); - V_pf_default_rule.src_nodes = counter_u64_alloc(M_WAITOK); + for (pf_sn_types_t sn_type = 0; sn_typestates_cur); counter_u64_free(rule->states_tot); - counter_u64_free(rule->src_nodes); + for (pf_sn_types_t sn_type=0; sn_typesrc_nodes[sn_type]); uma_zfree_pcpu(pf_timestamp_pcpu_zone, rule->timestamp); mtx_destroy(&rule->nat.mtx); @@ -2090,7 +2092,8 @@ } rule->states_cur = counter_u64_alloc(M_WAITOK); rule->states_tot = counter_u64_alloc(M_WAITOK); - rule->src_nodes = counter_u64_alloc(M_WAITOK); + for (pf_sn_types_t sn_type=0; sn_typesrc_nodes[sn_type] = counter_u64_alloc(M_WAITOK); rule->cuid = uid; rule->cpid = pid; TAILQ_INIT(&rule->rdr.list); @@ -3651,7 +3654,8 @@ } newrule->states_cur = counter_u64_alloc(M_WAITOK); newrule->states_tot = counter_u64_alloc(M_WAITOK); - newrule->src_nodes = counter_u64_alloc(M_WAITOK); + for (pf_sn_types_t sn_type=0; sn_typesrc_nodes[sn_type] = counter_u64_alloc(M_WAITOK); newrule->cuid = td->td_ucred->cr_ruid; newrule->cpid = td->td_proc ? td->td_proc->p_pid : 0; TAILQ_INIT(&newrule->nat.list); @@ -5672,9 +5676,14 @@ __func__, msg_version); } - if (st->src_node) + /* + * XXX Why do we bother pfsyncing source node information if source + * nodes are not synced? Showing users that there is source tracking + * when there is none seems useless. + */ + if (st->sns[PF_SN_LIMIT] != NULL) sp->pfs_1301.sync_flags |= PFSYNC_FLAG_SRCNODE; - if (st->nat_src_node) + if (st->sns[PF_SN_NAT] != NULL || st->sns[PF_SN_ROUTE]) sp->pfs_1301.sync_flags |= PFSYNC_FLAG_NATSRCNODE; sp->pfs_1301.id = st->id; @@ -5738,11 +5747,10 @@ /* 8 bits for the old libpfctl, 16 bits for the new libpfctl */ sp->state_flags_compat = st->state_flags; sp->state_flags = htons(st->state_flags); - if (st->src_node) + if (st->sns[PF_SN_LIMIT] != NULL) sp->sync_flags |= PFSYNC_FLAG_SRCNODE; - if (st->nat_src_node) + if (st->sns[PF_SN_NAT] != NULL || st->sns[PF_SN_ROUTE] != NULL) sp->sync_flags |= PFSYNC_FLAG_NATSRCNODE; - sp->id = st->id; sp->creatorid = st->creatorid; pf_state_peer_hton(&st->src, &sp->src); @@ -6007,10 +6015,13 @@ PF_HASHROW_LOCK(ih); LIST_FOREACH(s, &ih->states, entry) { - if (s->src_node && s->src_node->expire == 1) - s->src_node = NULL; - if (s->nat_src_node && s->nat_src_node->expire == 1) - s->nat_src_node = NULL; + for(pf_sn_types_t sn_type=0; sn_typesns[sn_type] && + s->sns[sn_type]->expire == 1) { + s->sns[sn_type] = NULL; + } + } } PF_HASHROW_UNLOCK(ih); } @@ -6834,7 +6845,8 @@ } counter_u64_free(V_pf_default_rule.states_cur); counter_u64_free(V_pf_default_rule.states_tot); - counter_u64_free(V_pf_default_rule.src_nodes); + for (pf_sn_types_t sn_type=0; sn_typeopts & PF_POOL_STICKYADDR && (rpool->opts & PF_POOL_TYPEMASK) != PF_POOL_NONE) - *sn = pf_find_src_node(&pd->nsaddr, r, pd->af, sh, false); + *sn = pf_find_src_node(&pd->nsaddr, r, + pd->af, sh, sn_type, false); if (*sn != NULL) PF_SRC_NODE_UNLOCK(*sn); return (0); @@ -276,7 +279,7 @@ } if (pf_map_addr_sn(pd->naf, r, &pd->nsaddr, naddr, NULL, &init_addr, - sn, sh, rpool)) + sn, sh, rpool, sn_type)) goto failed; if (pd->proto == IPPROTO_ICMP) { @@ -400,7 +403,7 @@ */ (*sn) = NULL; if (pf_map_addr_sn(pd->naf, r, &pd->nsaddr, naddr, NULL, - &init_addr, sn, sh, rpool)) + &init_addr, sn, sh, rpool, sn_type)) return (1); break; case PF_POOL_NONE: @@ -453,14 +456,14 @@ low = (i << ashift) | psmask; if (!pf_get_sport(pd, r, naddr, nport, low, low | highmask, sn, sh, &r->rdr, - udp_mapping)) + udp_mapping, PF_SN_NAT)) return (0); } for (i = cut - 1; i > 0; i--) { low = (i << ashift) | psmask; if (!pf_get_sport(pd, r, naddr, nport, low, low | highmask, sn, sh, &r->rdr, - udp_mapping)) + udp_mapping, PF_SN_NAT)) return (0); } return (1); @@ -642,7 +645,8 @@ u_short pf_map_addr_sn(sa_family_t af, struct pf_krule *r, struct pf_addr *saddr, struct pf_addr *naddr, struct pfi_kkif **nkif, struct pf_addr *init_addr, - struct pf_ksrc_node **sn, struct pf_srchash **sh, struct pf_kpool *rpool) + struct pf_ksrc_node **sn, struct pf_srchash **sh, struct pf_kpool *rpool, + pf_sn_types_t sn_type) { u_short reason = 0; @@ -655,7 +659,7 @@ */ if (rpool->opts & PF_POOL_STICKYADDR && (rpool->opts & PF_POOL_TYPEMASK) != PF_POOL_NONE) - *sn = pf_find_src_node(saddr, r, af, sh, false); + *sn = pf_find_src_node(saddr, r, af, sh, sn_type, false); if (*sn != NULL) { PF_SRC_NODE_LOCK_ASSERT(*sn); @@ -780,7 +784,7 @@ goto notrans; } } else if (pf_get_sport(pd, r, naddr, nportp, low, high, &sn, - &sh, &r->rdr, udp_mapping)) { + &sh, &r->rdr, udp_mapping, PF_SN_NAT)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: NAT proxy port allocation (%u-%u) failed\n", r->rdr.proxy_port[0], r->rdr.proxy_port[1])); @@ -868,7 +872,7 @@ uint16_t cut, low, high, nport; reason = pf_map_addr_sn(pd->af, r, &pd->nsaddr, naddr, NULL, - NULL, &sn, &sh, &r->rdr); + NULL, &sn, &sh, &r->rdr, PF_SN_NAT); if (reason != 0) goto notrans; if ((r->rdr.opts & PF_POOL_TYPEMASK) == PF_POOL_BITMASK) @@ -1007,7 +1011,8 @@ /* get source address and port */ if (pf_get_sport(pd, r, &nsaddr, &nport, - r->nat.proxy_port[0], r->nat.proxy_port[1], &sns, &sh, &r->nat, NULL)) { + r->nat.proxy_port[0], r->nat.proxy_port[1], &sns, &sh, &r->nat, + NULL, PF_SN_NAT)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: af-to NAT proxy port allocation (%u-%u) failed", r->nat.proxy_port[0], r->nat.proxy_port[1])); @@ -1051,7 +1056,7 @@ /* get the destination address and port */ if (! TAILQ_EMPTY(&r->rdr.list)) { if (pf_map_addr_sn(pd->naf, r, &nsaddr, &naddr, NULL, NULL, - &sns, NULL, &r->rdr)) + &sns, NULL, &r->rdr, PF_SN_NAT)) return (-1); if (r->rdr.proxy_port[0]) pd->ndport = htons(r->rdr.proxy_port[0]); diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h --- a/sys/netpfil/pf/pf_nl.h +++ b/sys/netpfil/pf/pf_nl.h @@ -131,6 +131,7 @@ PF_ST_DNRPIPE = 35, /* u16 */ PF_ST_RT = 36, /* u8 */ PF_ST_RT_IFNAME = 37, /* string */ + PF_ST_SRC_NODE_FLAGS = 38, /* u8 */ }; enum pf_addr_type_t { @@ -271,6 +272,9 @@ PF_RT_NAF = 76, /* u8 */ PF_RT_RPOOL_RT = 77, /* nested, pf_rpool_type_t */ PF_RT_RCV_IFNOT = 78, /* bool */ + PF_RT_SRC_NODES_LIMIT = 79, /* u64 */ + PF_RT_SRC_NODES_NAT = 80, /* u64 */ + PF_RT_SRC_NODES_ROUTE = 81, /* u64 */ }; enum pf_addrule_type_t { @@ -425,6 +429,7 @@ PF_SN_EXPIRE = 13, /* u64 */ PF_SN_CONNECTION_RATE = 14, /* nested, pf_threshold */ PF_SN_NAF = 15, /* u8 */ + PF_SN_NODE_TYPE = 16, /* u8 */ }; enum pf_tables_t { diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c --- a/sys/netpfil/pf/pf_nl.c +++ b/sys/netpfil/pf/pf_nl.c @@ -186,9 +186,9 @@ nlattr_add_u8(nw, PF_ST_TIMEOUT, s->timeout); nlattr_add_u16(nw, PF_ST_STATE_FLAGS, s->state_flags); uint8_t sync_flags = 0; - if (s->src_node) + if (s->sns[PF_SN_LIMIT] != NULL) sync_flags |= PFSYNC_FLAG_SRCNODE; - if (s->nat_src_node) + if (s->sns[PF_SN_NAT] != NULL || s->sns[PF_SN_ROUTE]) sync_flags |= PFSYNC_FLAG_NATSRCNODE; nlattr_add_u8(nw, PF_ST_SYNC_FLAGS, sync_flags); nlattr_add_u64(nw, PF_ST_ID, s->id); @@ -210,6 +210,17 @@ nlattr_add_u8(nw, PF_ST_RT, s->act.rt); if (s->act.rt_kif != NULL) nlattr_add_string(nw, PF_ST_RT_IFNAME, s->act.rt_kif->pfik_name); + uint8_t src_node_flags = 0; + if (s->sns[PF_SN_LIMIT] != NULL) { + src_node_flags |= PFSTATE_SRC_NODE_LIMIT; + if (s->sns[PF_SN_LIMIT]->rule == &V_pf_default_rule) + src_node_flags |= PFSTATE_SRC_NODE_LIMIT_GLOBAL; + } + if (s->sns[PF_SN_NAT] != NULL) + src_node_flags |= PFSTATE_SRC_NODE_NAT; + if (s->sns[PF_SN_ROUTE] != NULL) + src_node_flags |= PFSTATE_SRC_NODE_ROUTE; + nlattr_add_u8(nw, PF_ST_SRC_NODE_FLAGS, src_node_flags); if (!dump_state_peer(nw, PF_ST_PEER_SRC, &s->src)) goto enomem; @@ -854,6 +865,7 @@ struct genlmsghdr *ghdr_new; struct pf_kruleset *ruleset; struct pf_krule *rule; + u_int64_t src_nodes_total = 0; int rs_num; int error; @@ -985,7 +997,12 @@ nlattr_add_u64(nw, PF_RT_TIMESTAMP, pf_get_timestamp(rule)); nlattr_add_u64(nw, PF_RT_STATES_CUR, counter_u64_fetch(rule->states_cur)); nlattr_add_u64(nw, PF_RT_STATES_TOTAL, counter_u64_fetch(rule->states_tot)); - nlattr_add_u64(nw, PF_RT_SRC_NODES, counter_u64_fetch(rule->src_nodes)); + for (pf_sn_types_t sn_type=0; sn_typesrc_nodes[sn_type]); + nlattr_add_u64(nw, PF_RT_SRC_NODES, src_nodes_total); + nlattr_add_u64(nw, PF_RT_SRC_NODES_LIMIT, counter_u64_fetch(rule->src_nodes[PF_SN_LIMIT])); + nlattr_add_u64(nw, PF_RT_SRC_NODES_NAT, counter_u64_fetch(rule->src_nodes[PF_SN_NAT])); + nlattr_add_u64(nw, PF_RT_SRC_NODES_ROUTE, counter_u64_fetch(rule->src_nodes[PF_SN_ROUTE])); error = pf_kanchor_copyout(ruleset, rule, anchor_call, sizeof(anchor_call)); MPASS(error == 0); @@ -1785,6 +1802,8 @@ nlattr_add_pf_threshold(nw, PF_SN_CONNECTION_RATE, &n->conn_rate, secs); + nlattr_add_u8(nw, PF_SN_NODE_TYPE, n->type); + if (!nlmsg_end(nw)) { PF_HASHROW_UNLOCK(sh); nlmsg_abort(nw); diff --git a/sys/netpfil/pf/pf_nv.c b/sys/netpfil/pf/pf_nv.c --- a/sys/netpfil/pf/pf_nv.c +++ b/sys/netpfil/pf/pf_nv.c @@ -684,6 +684,7 @@ pf_krule_to_nvrule(struct pf_krule *rule) { nvlist_t *nvl, *tmp; + u_int64_t src_nodes_total = 0; nvl = nvlist_create(0); if (nvl == NULL) @@ -759,8 +760,9 @@ counter_u64_fetch(rule->states_cur)); nvlist_add_number(nvl, "states_tot", counter_u64_fetch(rule->states_tot)); - nvlist_add_number(nvl, "src_nodes", - counter_u64_fetch(rule->src_nodes)); + for (pf_sn_types_t sn_type=0; sn_typesrc_nodes[sn_type]); + nvlist_add_number(nvl, "src_nodes", src_nodes_total); nvlist_add_number(nvl, "return_icmp", rule->return_icmp); nvlist_add_number(nvl, "return_icmp6", rule->return_icmp6); @@ -993,9 +995,9 @@ nvlist_add_number(nvl, "creatorid", s->creatorid); nvlist_add_number(nvl, "direction", s->direction); nvlist_add_number(nvl, "state_flags", s->state_flags); - if (s->src_node) + if (s->sns[PF_SN_LIMIT] != NULL) flags |= PFSYNC_FLAG_SRCNODE; - if (s->nat_src_node) + if (s->sns[PF_SN_NAT] != NULL || s->sns[PF_SN_ROUTE]) flags |= PFSYNC_FLAG_NATSRCNODE; nvlist_add_number(nvl, "sync_flags", flags); diff --git a/tests/sys/netpfil/pf/src_track.sh b/tests/sys/netpfil/pf/src_track.sh --- a/tests/sys/netpfil/pf/src_track.sh +++ b/tests/sys/netpfil/pf/src_track.sh @@ -241,11 +241,11 @@ nodes=$(mktemp) || exit 1 jexec router pfctl -qvsS | normalize_pfctl_s > $nodes for node_regexp in \ - '2001:db8:44::1 -> :: \( states 3, connections 3, rate [0-9/\.]+s \) age [0-9:]+, 9 pkts, [0-9]+ bytes, filter rule 3$' \ - '2001:db8:44::1 -> :: \( states 3, connections 3, rate [0-9/\.]+s \) age [0-9:]+, 9 pkts, [0-9]+ bytes, filter rule 4$' \ - '2001:db8:44::2 -> :: \( states 3, connections 3, rate [0-9/\.]+s \) age [0-9:]+, 9 pkts, [0-9]+ bytes, filter rule 4$' \ + '2001:db8:44::1 -> :: \( states 3, connections 3, rate [0-9/\.]+s \) age [0-9:]+, 9 pkts, [0-9]+ bytes, filter rule 3, limit source-track$' \ + '2001:db8:44::1 -> :: \( states 3, connections 3, rate [0-9/\.]+s \) age [0-9:]+, 9 pkts, [0-9]+ bytes, filter rule 4, limit source-track$' \ + '2001:db8:44::2 -> :: \( states 3, connections 3, rate [0-9/\.]+s \) age [0-9:]+, 9 pkts, [0-9]+ bytes, filter rule 4, limit source-track$' \ ; do - grep -qE "$node_regexp" $nodes || atf_fail "Source nodes not matching expected output" + grep -qE "${node_regexp}" $nodes || atf_fail "Source node not found for '${node_regexp}'" done # Check if limit counters have been properly set. @@ -257,10 +257,157 @@ pft_cleanup } +max_src_states_global_head() +{ + atf_set descr 'Max states per source global' + atf_set require.user root +} + +max_src_states_global_body() +{ + setup_router_server_ipv6 + + # Clients will connect from another network behind the router. + # This allows for using multiple source addresses and for tester jail + # to not respond with RST packets for SYN+ACKs. + jexec router route add -6 2001:db8:44::0/64 2001:db8:42::2 + jexec server route add -6 2001:db8:44::0/64 2001:db8:43::1 + + pft_set_rules router \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ + "pass in on ${epair_tester}b inet6 proto tcp from port 4210:4219 keep state (max-src-states 3 source-track global) label rule_A" \ + "pass in on ${epair_tester}b inet6 proto tcp from port 4220:4229 keep state (max-src-states 3 source-track global) label rule_B" \ + "pass out on ${epair_server}a keep state" + + # Global source tracking creates a single source node shared between all + # rules for each connecting source IP address and counts states created + # by all rules. Each rule has its own max-src-conn value checked against + # that single source node. + + # 3 connections from host …::1 matching rule_A will be allowed. + ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4211 --fromaddr 2001:db8:44::1 + ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4212 --fromaddr 2001:db8:44::1 + ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4213 --fromaddr 2001:db8:44::1 + # The 4th connection matching rule_A from host …::1 will have its state killed. + ping_server_check_reply exit:1 --ping-type=tcp3way --send-sport=4214 --fromaddr 2001:db8:44::1 + # A connection matching rule_B from host …::1 will have its state killed too. + ping_server_check_reply exit:1 --ping-type=tcp3way --send-sport=4221 --fromaddr 2001:db8:44::1 + + nodes=$(mktemp) || exit 1 + jexec router pfctl -qvsS | normalize_pfctl_s > $nodes + cat $nodes + node_regexp='2001:db8:44::1 -> :: \( states 3, connections 3, rate [0-9/\.]+s \) age [0-9:]+, 9 pkts, [0-9]+ bytes, limit source-track' + grep -qE "$node_regexp" $nodes || atf_fail "Source nodes not matching expected output" +} + +max_src_states_global_cleanup() +{ + pft_cleanup +} + +route_to_head() +{ + atf_set descr 'Max states per source per rule with route-to' + atf_set require.user root +} + +route_to_body() +{ + setup_router_dummy_ipv6 + + # Clients will connect from another network behind the router. + # This allows for using multiple source addresses. + jexec router route add -6 2001:db8:44::0/64 2001:db8:42::2 + + # Additional gateways for route-to. + rtgw=${net_server_host_server%::*}::2:1 + jexec router ndp -s ${rtgw} 00:01:02:03:04:05 + + # This test will check for proper source node creation for: + # max-src-states -> PF_SN_LIMIT + # sticky-address -> PF_SN_NAT + # route-to -> PF_SN_ROUTE + # The test expands to all 8 combinations of those source nodes being + # present or not. + + pft_set_rules router \ + "table { ${rtgw} }" \ + "table { 2001:db8:45::1 }" \ + "rdr on ${epair_tester}b inet6 proto tcp from 2001:db8:44::10/124 to 2001:db8:45::1 -> port 4242 sticky-address" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ + "pass in quick on ${epair_tester}b route-to ( ${epair_server}a ) inet6 proto tcp from port 4211 keep state label rule_3" \ + "pass in quick on ${epair_tester}b route-to ( ${epair_server}a ) sticky-address inet6 proto tcp from port 4212 keep state label rule_4" \ + "pass in quick on ${epair_tester}b route-to ( ${epair_server}a ) inet6 proto tcp from port 4213 keep state (max-src-states 3 source-track rule) label rule_5" \ + "pass in quick on ${epair_tester}b route-to ( ${epair_server}a ) sticky-address inet6 proto tcp from port 4214 keep state (max-src-states 3 source-track rule) label rule_6" \ + "pass out quick on ${epair_server}a keep state" + + # We don't check if state limits are properly enforced, this is tested + # by other tests in this file. + # Source address will not match the NAT rule + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4211 --fromaddr 2001:db8:44::01 --to 2001:db8:45::1 + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4212 --fromaddr 2001:db8:44::02 --to 2001:db8:45::1 + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4213 --fromaddr 2001:db8:44::03 --to 2001:db8:45::1 + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4214 --fromaddr 2001:db8:44::04 --to 2001:db8:45::1 + # Source address will match the NAT rule + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4211 --fromaddr 2001:db8:44::11 --to 2001:db8:45::1 + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4212 --fromaddr 2001:db8:44::12 --to 2001:db8:45::1 + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4213 --fromaddr 2001:db8:44::13 --to 2001:db8:45::1 + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4214 --fromaddr 2001:db8:44::14 --to 2001:db8:45::1 + + states=$(mktemp) || exit 1 + jexec router pfctl -qvss | normalize_pfctl_s > $states + nodes=$(mktemp) || exit 1 + jexec router pfctl -qvvsS | normalize_pfctl_s > $nodes + + # Order of states in output is not guaranteed, find each one separately. + for state_regexp in \ + 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::1\[4211\] .* 1:0 pkts, 76:0 bytes, rule 3$' \ + 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::2\[4212\] .* 1:0 pkts, 76:0 bytes, rule 4, route sticky-address$' \ + 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::3\[4213\] .* 1:0 pkts, 76:0 bytes, rule 5, limit source-track$' \ + 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::4\[4214\] .* 1:0 pkts, 76:0 bytes, rule 6, limit source-track, route sticky-address$' \ + 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::11\[4211\] .* 1:0 pkts, 76:0 bytes, rule 3, NAT/RDR sticky-address' \ + 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::12\[4212\] .* 1:0 pkts, 76:0 bytes, rule 4, NAT/RDR sticky-address, route sticky-address' \ + 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::13\[4213\] .* 1:0 pkts, 76:0 bytes, rule 5, limit source-track, NAT/RDR sticky-address' \ + 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::14\[4214\] .* 1:0 pkts, 76:0 bytes, rule 6, limit source-track, NAT/RDR sticky-address, route sticky-address' \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done + + # Order of source nodes in output is not guaranteed, find each one separately. + for node_regexp in \ + '2001:db8:44::2 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 4, route sticky-address' \ + '2001:db8:44::3 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 5, limit source-track' \ + '2001:db8:44::4 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s ) age [0-9:]+, 1 pkts, 76 bytes, filter rule 6, route sticky-address' \ + '2001:db8:44::4 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 6, limit source-track' \ + '2001:db8:44::11 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, rdr rule 0, NAT/RDR sticky-address' \ + '2001:db8:44::12 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, rdr rule 0, NAT/RDR sticky-address' \ + '2001:db8:44::12 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 4, route sticky-address' \ + '2001:db8:44::13 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, rdr rule 0, NAT/RDR sticky-address' \ + '2001:db8:44::13 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 5, limit source-track' \ + '2001:db8:44::14 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, rdr rule 0, NAT/RDR sticky-address' \ + '2001:db8:44::14 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s ) age [0-9:]+, 1 pkts, 76 bytes, filter rule 6, route sticky-address' \ + '2001:db8:44::14 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 6, limit source-track' \ + ; do + grep -qE "${node_regexp}" $nodes || atf_fail "Source node not found for '${node_regexp}'" + done + + ! grep -q 'filter rule 3' $nodes || atf_fail "Source node found for rule 3" +} + +route_to_cleanup() +{ + pft_cleanup +} + + atf_init_test_cases() { atf_add_test_case "source_track" atf_add_test_case "kill" atf_add_test_case "max_src_conn_rule" atf_add_test_case "max_src_states_rule" + atf_add_test_case "max_src_states_global" + atf_add_test_case "route_to" }