git: 646798b67831 - main - pf: Make nat-to and rdr-to work properly both on in and out rules

From: Kajetan Staszkiewicz <ks_at_FreeBSD.org>
Date: Thu, 30 Oct 2025 17:44:39 UTC
The branch main has been updated by ks:

URL: https://cgit.FreeBSD.org/src/commit/?id=646798b6783184fb194a2d97667e05895e00c358

commit 646798b6783184fb194a2d97667e05895e00c358
Author:     Kajetan Staszkiewicz <ks@FreeBSD.org>
AuthorDate: 2025-10-01 13:51:46 +0000
Commit:     Kajetan Staszkiewicz <ks@FreeBSD.org>
CommitDate: 2025-10-30 17:32:21 +0000

    pf: Make nat-to and rdr-to work properly both on in and out rules
    
    New-style address translation is done by nat-to and rdr-to actions on
    normal match and pass rules. Those rules, when used without address
    translation, can be specified without direction. But that allows users
    to specify pre-routing nat and post-routing rdr. This case is not
    handled properly and causes pre-routing nat to modify destination
    address, as if it was a rdr rule, and post-routing rdr to modify source
    address, as if it was a nat rule.
    
    Ensure that nat-to action modifies source address and rdr-to destination
    address no matter in which direction the rule is applied. The man page
    for pf.conf already specifies that nat-to and rdr-to rules should be
    limited to respective directions.
    
    PR:             288577
    Reviewed by:    kp
    MFC after:      3 days
    Sponsored by:   InnoGames GmbH
    Differential Revision:  https://reviews.freebsd.org/D53216
---
 sys/netpfil/pf/pf_lb.c      | 16 +++++++++++++--
 tests/sys/netpfil/pf/nat.sh | 47 +++++++++++++++++++++++++++++++++++++++------
 2 files changed, 55 insertions(+), 8 deletions(-)

diff --git a/sys/netpfil/pf/pf_lb.c b/sys/netpfil/pf/pf_lb.c
index 5d85e16f18e3..4b1d74e0e61f 100644
--- a/sys/netpfil/pf/pf_lb.c
+++ b/sys/netpfil/pf/pf_lb.c
@@ -974,6 +974,7 @@ pf_get_transaddr(struct pf_test_ctx *ctx, struct pf_krule *r,
 {
 	struct pf_pdesc	*pd = ctx->pd;
 	struct pf_addr	*naddr;
+	int		 idx;
 	uint16_t	*nportp;
 	uint16_t	 low, high;
 	u_short		 reason;
@@ -988,8 +989,19 @@ pf_get_transaddr(struct pf_test_ctx *ctx, struct pf_krule *r,
 			return (PFRES_MEMORY);
 	}
 
-	naddr = &ctx->nk->addr[1];
-	nportp = &ctx->nk->port[1];
+	switch (nat_action) {
+	case PF_NAT:
+		idx = pd->sidx;
+		break;
+	case PF_BINAT:
+		idx = 1;
+		break;
+	case PF_RDR:
+		idx = pd->didx;
+		break;
+	}
+	naddr = &ctx->nk->addr[idx];
+	nportp = &ctx->nk->port[idx];
 
 	switch (nat_action) {
 	case PF_NAT:
diff --git a/tests/sys/netpfil/pf/nat.sh b/tests/sys/netpfil/pf/nat.sh
index 1ef87cee3598..0824671fa0f1 100644
--- a/tests/sys/netpfil/pf/nat.sh
+++ b/tests/sys/netpfil/pf/nat.sh
@@ -477,15 +477,49 @@ no_addrs_random_cleanup()
 	pft_cleanup
 }
 
-atf_test_case "nat_pass" "cleanup"
-nat_pass_head()
+atf_test_case "nat_pass_in" "cleanup"
+nat_pass_in_head()
 {
-	atf_set descr 'IPv4 NAT on pass rule'
+	atf_set descr 'IPv4 NAT on inbound pass rule'
 	atf_set require.user root
 	atf_set require.progs scapy
 }
 
-nat_pass_body()
+nat_pass_in_body()
+{
+	setup_router_server_ipv4
+	# Delete the route back to make sure that the traffic has been NAT-ed
+	jexec server route del -net ${net_tester} ${net_server_host_router}
+	# Provide routing back to the NAT address
+	jexec server route add 203.0.113.0/24 ${net_server_host_router}
+	jexec router route add 203.0.113.0/24 -iface ${epair_tester}b
+
+	pft_set_rules router \
+		"block" \
+		"pass in  on ${epair_tester}b inet proto tcp nat-to 203.0.113.0 keep state" \
+		"pass out on ${epair_server}a inet proto tcp keep state"
+
+	ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4201
+
+	jexec router pfctl -qvvsr
+	jexec router pfctl -qvvss
+	jexec router ifconfig
+	jexec router netstat -rn
+}
+
+nat_pass_in_cleanup()
+{
+	pft_cleanup
+}
+
+nat_pass_out_head()
+{
+	atf_set descr 'IPv4 NAT on outbound pass rule'
+	atf_set require.user root
+	atf_set require.progs scapy
+}
+
+nat_pass_out_body()
 {
 	setup_router_server_ipv4
 	# Delete the route back to make sure that the traffic has been NAT-ed
@@ -504,7 +538,7 @@ nat_pass_body()
 	jexec router netstat -rn
 }
 
-nat_pass_cleanup()
+nat_pass_out_cleanup()
 {
 	pft_cleanup
 }
@@ -874,7 +908,8 @@ atf_init_test_cases()
 	atf_add_test_case "no_addrs_random"
 	atf_add_test_case "map_e_compat"
 	atf_add_test_case "map_e_pass"
-	atf_add_test_case "nat_pass"
+	atf_add_test_case "nat_pass_in"
+	atf_add_test_case "nat_pass_out"
 	atf_add_test_case "nat_match"
 	atf_add_test_case "binat_compat"
 	atf_add_test_case "binat_match"