Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 13 Feb 2025 15:00:49 GMT
From:      Kajetan Staszkiewicz <ks@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 07e070ef0869 - main - pf: Add support for multiple source node types
Message-ID:  <202502131500.51DF0nbT046085@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by ks:

URL: https://cgit.FreeBSD.org/src/commit/?id=07e070ef086997590cd6d9d47908885c12947bd2

commit 07e070ef086997590cd6d9d47908885c12947bd2
Author:     Kajetan Staszkiewicz <ks@FreeBSD.org>
AuthorDate: 2025-02-07 11:40:09 +0000
Commit:     Kajetan Staszkiewicz <ks@FreeBSD.org>
CommitDate: 2025-02-13 14:59:12 +0000

    pf: Add support for multiple source node types
    
    For every state pf creates up to two source nodes: a limiting one
    struct pf_kstate -> src_node and a NAT one struct pf_kstate -> nat_src_node.
    The limiting source node is tracking information needed for limits using
    max-src-states and max-src-nodes and the NAT source node is tracking NAT
    rules only.
    
    On closer inspection some issues emerge:
    - For route-to rules the redirection decision is stored in the limiting source
      node. Thus sticky-address and source limiting can't be used separately.
    - Global source tracking, as promised in the man page, is totally absent from
      the code. Pfctl is capable of setting flags PFRULE_SRCTRACK (enable source
      tracking) and PFRULE_RULESRCTRACK (make source tracking per rule). The kernel
      code checks PFRULE_SRCTRACK but ignores PFRULE_RULESRCTRACK. That makes
      source tracking work per-rule only.
    
    This patch is based on OpenBSD approach where source nodes have a type and each
    state has an array of source node pointers indexed by source node type
    instead of just two pointers. The conditions for limiting are applied
    only to source nodes of PF_SN_LIMIT type. For global limit tracking
    source nodes are attached to the default rule.
    
    Reviewed by:            kp
    Approved by:            kp (mentor)
    Sponsored by:           InnoGames GmbH
    Differential Revision:  https://reviews.freebsd.org/D39880
---
 lib/libpfctl/libpfctl.c           |   5 +
 lib/libpfctl/libpfctl.h           |   3 +
 sbin/pfctl/pf_print_state.c       |  13 ++-
 sbin/pfctl/pfctl.c                |   9 ++
 sbin/pfctl/pfctl_parser.c         |   2 +
 sys/net/pfvar.h                   |  30 ++++--
 sys/netpfil/pf/pf.c               | 216 +++++++++++++++++++++-----------------
 sys/netpfil/pf/pf.h               |   6 ++
 sys/netpfil/pf/pf_ioctl.c         |  40 ++++---
 sys/netpfil/pf/pf_lb.c            |  35 +++---
 sys/netpfil/pf/pf_nl.c            |  25 ++++-
 sys/netpfil/pf/pf_nl.h            |   5 +
 sys/netpfil/pf/pf_nv.c            |  10 +-
 tests/sys/netpfil/pf/src_track.sh | 155 ++++++++++++++++++++++++++-
 14 files changed, 405 insertions(+), 149 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index fe63c91c1174..e93c79758428 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -1665,6 +1665,9 @@ static struct snl_attr_parser ap_getrule[] = {
 	{ .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 @@ static struct snl_attr_parser ap_state[] = {
 	{ .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 @@ static struct snl_attr_parser ap_srcnode[] = {
 	{ .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/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index e1af4b5e97ff..1108b0ffc693 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -216,6 +216,7 @@ struct pfctl_rule {
 	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 @@ struct pfctl_state {
 	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 @@ struct pfctl_src_node {
 	uint64_t		creation;
 	uint64_t		expire;
 	struct pfctl_threshold	conn_rate;
+	pf_sn_types_t		type;
 };
 
 #define	PF_DEVICE	"/dev/pf"
diff --git a/sbin/pfctl/pf_print_state.c b/sbin/pfctl/pf_print_state.c
index e6495dfa4ca6..1d2fa45cd9d7 100644
--- a/sbin/pfctl/pf_print_state.c
+++ b/sbin/pfctl/pf_print_state.c
@@ -245,6 +245,7 @@ print_state(struct pfctl_state *s, int opts)
 	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 @@ print_state(struct pfctl_state *s, int opts)
 				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
index 48e1d0b833c5..e05c96a252fc 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -1064,6 +1064,15 @@ pfctl_print_rule_counters(struct pfctl_rule *rule, int opts)
 			    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
index 7a6d2fc8eed5..bb458bce24fb 100644
--- a/sbin/pfctl/pfctl_parser.c
+++ b/sbin/pfctl/pfctl_parser.c
@@ -651,6 +651,7 @@ print_src_node(struct pfctl_src_node *sn, int opts)
 {
 	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 @@ print_src_node(struct pfctl_src_node *sn, int opts)
 				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
index d973fe15a5c4..076027e436dc 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -624,6 +624,21 @@ extern struct sx pf_end_lock;
 
 #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 @@ struct pf_krule {
 
 	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 @@ struct pf_ksrc_node {
 	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_kstate {
 	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 @@ struct pf_kstate {
 };
 
 /*
- * 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 @@ extern bool			 pf_src_node_exists(struct pf_ksrc_node **,
 				    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 @@ u_short			 pf_map_addr_sn(u_int8_t, struct pf_krule *,
 			    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.c b/sys/netpfil/pf/pf.c
index 378be1e72d9a..c5042a7685c2 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -389,7 +389,7 @@ static void		 pf_overload_task(void *v, int pending);
 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 @@ pf_check_threshold(struct pf_threshold *threshold)
 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 @@ pf_src_connlimit(struct pf_kstate *state)
 	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 @@ pf_src_connlimit(struct pf_kstate *state)
 	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 @@ pf_overload_task(void *v, int pending)
  */
 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 @@ pf_find_src_node(struct pf_addr *src, struct pf_krule *rule, sa_family_t af,
 	*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 @@ pf_free_src_node(struct pf_ksrc_node *sn)
 }
 
 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_insert_src_node(struct pf_ksrc_node **sn, struct pf_srchash **sh,
 			}
 		}
 
-		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 @@ pf_unlink_src_node(struct pf_ksrc_node *src)
 
 	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 @@ pf_purge_expired_src_nodes(void)
 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_type<PF_SN_MAX; sn_type++) {
+		if (s->sns[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 @@ nextrule:
 		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 @@ pf_create_state(struct pf_krule *r, struct pf_krule *nr, struct pf_krule *a,
     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 @@ pf_create_state(struct pf_krule *r, struct pf_krule *nr, struct pf_krule *a,
 		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 @@ pf_create_state(struct pf_krule *r, struct pf_krule *nr, struct pf_krule *a,
 	/*
 	 * 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_type<PF_SN_MAX; sn_type++) {
+		if (pf_src_node_exists(&sns[sn_type], snhs[sn_type])) {
+			s->sns[sn_type] = sns[sn_type];
+			PF_HASHROW_UNLOCK(snhs[sn_type]);
+		}
 	}
 
 	if (tag > 0)
@@ -6223,24 +6246,17 @@ csfailed:
 	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_type<PF_SN_MAX; sn_type++) {
+		if (pf_src_node_exists(&sns[sn_type], snhs[sn_type])) {
+			if (--sns[sn_type]->states == 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_tcp_track_full(struct pf_kstate **state, struct pf_pdesc *pd,
 				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 @@ pf_tcp_track_sloppy(struct pf_kstate **state, struct pf_pdesc *pd, u_short *reas
 		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_tcp_track_sloppy(struct pf_kstate **state, struct pf_pdesc *pd, u_short *reas
 			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 @@ pf_synproxy(struct pf_pdesc *pd, struct pf_kstate **state, u_short *reason)
 		    (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_counters_inc(int action, struct pf_pdesc *pd,
 				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_type<PF_SN_MAX; sn_type++) {
+				if (s->sns[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.h b/sys/netpfil/pf/pf.h
index 45652f174884..dfa86e7f1d6d 100644
--- a/sys/netpfil/pf/pf.h
+++ b/sys/netpfil/pf/pf.h
@@ -649,6 +649,12 @@ struct pf_rule {
 #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_ioctl.c b/sys/netpfil/pf/pf_ioctl.c
index bea2cf1a5331..6553981a1059 100644
--- a/sys/netpfil/pf/pf_ioctl.c
+++ b/sys/netpfil/pf/pf_ioctl.c
@@ -352,7 +352,8 @@ pfattach_vnet(void)
 	}
 	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_type<PF_SN_MAX; sn_type++)
+		V_pf_default_rule.src_nodes[sn_type] = counter_u64_alloc(M_WAITOK);
 
 	V_pf_default_rule.timestamp = uma_zalloc_pcpu(pf_timestamp_pcpu_zone,
 	    M_WAITOK | M_ZERO);
@@ -1854,7 +1855,8 @@ pf_krule_free(struct pf_krule *rule)
 	}
 	counter_u64_free(rule->states_cur);
 	counter_u64_free(rule->states_tot);
-	counter_u64_free(rule->src_nodes);
+	for (pf_sn_types_t sn_type=0; sn_type<PF_SN_MAX; sn_type++)
+		counter_u64_free(rule->src_nodes[sn_type]);
 	uma_zfree_pcpu(pf_timestamp_pcpu_zone, rule->timestamp);
 
 	mtx_destroy(&rule->nat.mtx);
@@ -2090,7 +2092,8 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
 	}
 	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_type<PF_SN_MAX; sn_type++)
+		rule->src_nodes[sn_type] = counter_u64_alloc(M_WAITOK);
 	rule->cuid = uid;
 	rule->cpid = pid;
 	TAILQ_INIT(&rule->rdr.list);
@@ -3651,7 +3654,8 @@ DIOCGETRULENV_error:
 			}
 			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_type<PF_SN_MAX; sn_type++)
+				newrule->src_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 @@ pfsync_state_export(union pfsync_state_union *sp, struct pf_kstate *st, int msg_
 			    __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 @@ pf_state_export(struct pf_state_export *sp, struct pf_kstate *st)
 	/* 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_kill_srcnodes(struct pfioc_src_node_kill *psnk)
 
 		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_type<PF_SN_MAX;
+			    sn_type++) {
+				if (s->sns[sn_type] &&
+				    s->sns[sn_type]->expire == 1) {
+					s->sns[sn_type] = NULL;
+				}
+			}
 		}
 		PF_HASHROW_UNLOCK(ih);
 	}
@@ -6834,7 +6845,8 @@ pf_unload_vnet(void)
 	}
 	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_type<PF_SN_MAX; sn_type++)
+		counter_u64_free(V_pf_default_rule.src_nodes[sn_type]);
 	uma_zfree_pcpu(pf_timestamp_pcpu_zone, V_pf_default_rule.timestamp);
 
 	for (int i = 0; i < PFRES_MAX; i++)
diff --git a/sys/netpfil/pf/pf_lb.c b/sys/netpfil/pf/pf_lb.c
index 23c7ad1c0a66..9c2d7b4c71b6 100644
--- a/sys/netpfil/pf/pf_lb.c
+++ b/sys/netpfil/pf/pf_lb.c
@@ -75,9 +75,11 @@ static void		 pf_hash(struct pf_addr *, struct pf_addr *,
 			    struct pf_poolhashkey *, sa_family_t);
 static struct pf_krule	*pf_match_translation(struct pf_pdesc *,
 			    int, struct pf_kanchor_stackframe *);
-static int pf_get_sport(struct pf_pdesc *, struct pf_krule *,
-    struct pf_addr *, uint16_t *, uint16_t, uint16_t, struct pf_ksrc_node **,
-    struct pf_srchash **, struct pf_kpool *, struct pf_udp_mapping **);
+static int		 pf_get_sport(struct pf_pdesc *, struct pf_krule *,
+			    struct pf_addr *, uint16_t *, uint16_t, uint16_t,
+			    struct pf_ksrc_node **, struct pf_srchash **,
+			    struct pf_kpool *, struct pf_udp_mapping **,
+			    pf_sn_types_t);
 static bool		 pf_islinklocal(const sa_family_t, const struct pf_addr *);
 
 #define mix(a,b,c) \
@@ -231,7 +233,7 @@ pf_get_sport(struct pf_pdesc *pd, struct pf_krule *r,
     struct pf_addr *naddr, uint16_t *nport, uint16_t low,
     uint16_t high, struct pf_ksrc_node **sn,
     struct pf_srchash **sh, struct pf_kpool *rpool,
-    struct pf_udp_mapping **udp_mapping)
+    struct pf_udp_mapping **udp_mapping, pf_sn_types_t sn_type)
 {
 	struct pf_state_key_cmp	key;
 	struct pf_addr		init_addr;
@@ -262,7 +264,8 @@ pf_get_sport(struct pf_pdesc *pd, struct pf_krule *r,
 				/* Try to find a src_node as per pf_map_addr(). */
 				if (*sn == NULL && rpool->opts & 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 @@ pf_get_sport(struct pf_pdesc *pd, struct pf_krule *r,
 	}
 
 	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 @@ pf_get_sport(struct pf_pdesc *pd, struct pf_krule *r,
 			 */
 			(*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 @@ pf_get_mape_sport(struct pf_pdesc *pd, struct pf_krule *r,
 		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 @@ done_pool_mtx:
 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 @@ pf_map_addr_sn(sa_family_t af, struct pf_krule *r, struct pf_addr *saddr,
 	 */
 	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 @@ pf_get_translation(struct pf_pdesc *pd, int off,
 				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 @@ pf_get_translation(struct pf_pdesc *pd, int off,
 		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 @@ pf_get_transaddr_af(struct pf_krule *r, struct pf_pdesc *pd)
 
 	/* 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 @@ pf_get_transaddr_af(struct pf_krule *r, struct pf_pdesc *pd)
 	/* 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.c b/sys/netpfil/pf/pf_nl.c
index 4cdb16d1fbba..73c39e1f7471 100644
--- a/sys/netpfil/pf/pf_nl.c
+++ b/sys/netpfil/pf/pf_nl.c
@@ -186,9 +186,9 @@ dump_state(struct nlpcb *nlp, const struct nlmsghdr *hdr, struct pf_kstate *s,
 	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 @@ dump_state(struct nlpcb *nlp, const struct nlmsghdr *hdr, struct pf_kstate *s,
 	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 @@ pf_handle_getrule(struct nlmsghdr *hdr, struct nl_pstate *npt)
 	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 @@ pf_handle_getrule(struct nlmsghdr *hdr, struct nl_pstate *npt)
 	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_type<PF_SN_MAX; sn_type++)
+		src_nodes_total += counter_u64_fetch(rule->src_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 @@ pf_handle_get_srcnodes(struct nlmsghdr *hdr, struct nl_pstate *npt)
*** 252 LINES SKIPPED ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202502131500.51DF0nbT046085>