git: 7ec06143964a - main - pf: pass pre-NAT addresses to dummynet
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Wed, 24 Sep 2025 11:45:20 UTC
The branch main has been updated by kp:
URL: https://cgit.FreeBSD.org/src/commit/?id=7ec06143964a949ebf6885ac120fdf839ad29eab
commit 7ec06143964a949ebf6885ac120fdf839ad29eab
Author: Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2025-09-04 12:49:00 +0000
Commit: Kristof Provost <kp@FreeBSD.org>
CommitDate: 2025-09-24 11:44:54 +0000
pf: pass pre-NAT addresses to dummynet
When we're NAT-ing give dummynet (via its struct ip_fw_args) the pre-NAT source
and destination addresses.
That's what we used to do, but that got unintentionally changed during the nat64
work.
The pre-NAT addresses make somewhat more sense, in that it enables limiting
based on specific LAN clients.
See also: https://redmine.pfsense.org/issues/15770
Sponsored by: Rubicon Communications, LLC ("Netgate")
---
sys/netpfil/pf/pf.c | 39 +++++++++++++++++++++++++++++++++------
tests/sys/netpfil/pf/nat.sh | 45 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 78 insertions(+), 6 deletions(-)
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index a57905df0c69..be00aff1f5cb 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -9760,6 +9760,7 @@ pf_pdesc_to_dnflow(const struct pf_pdesc *pd, const struct pf_krule *r,
const struct pf_kstate *s, struct ip_fw_args *dnflow)
{
int dndir = r->direction;
+ sa_family_t af = pd->naf;
if (s && dndir == PF_INOUT) {
dndir = s->direction;
@@ -9800,20 +9801,46 @@ pf_pdesc_to_dnflow(const struct pf_pdesc *pd, const struct pf_krule *r,
dnflow->f_id.proto = pd->proto;
dnflow->f_id.extra = dnflow->rule.info;
- switch (pd->naf) {
+ if (s)
+ af = s->key[PF_SK_STACK]->af;
+
+ switch (af) {
case AF_INET:
dnflow->f_id.addr_type = 4;
- dnflow->f_id.src_ip = ntohl(pd->src->v4.s_addr);
- dnflow->f_id.dst_ip = ntohl(pd->dst->v4.s_addr);
+ if (s) {
+ dnflow->f_id.src_ip = htonl(
+ s->key[PF_SK_STACK]->addr[pd->sidx].v4.s_addr);
+ dnflow->f_id.dst_ip = htonl(
+ s->key[PF_SK_STACK]->addr[pd->didx].v4.s_addr);
+ } else {
+ dnflow->f_id.src_ip = ntohl(pd->src->v4.s_addr);
+ dnflow->f_id.dst_ip = ntohl(pd->dst->v4.s_addr);
+ }
break;
case AF_INET6:
- dnflow->flags |= IPFW_ARGS_IP6;
dnflow->f_id.addr_type = 6;
- dnflow->f_id.src_ip6 = pd->src->v6;
- dnflow->f_id.dst_ip6 = pd->dst->v6;
+
+ if (s) {
+ dnflow->f_id.src_ip6 =
+ s->key[PF_SK_STACK]->addr[pd->sidx].v6;
+ dnflow->f_id.dst_ip6 =
+ s->key[PF_SK_STACK]->addr[pd->didx].v6;
+ } else {
+ dnflow->f_id.src_ip6 = pd->src->v6;
+ dnflow->f_id.dst_ip6 = pd->dst->v6;
+ }
break;
}
+ /*
+ * Separate this out, because while we pass the pre-NAT addresses to
+ * dummynet we want the post-nat address family in case of nat64.
+ * Dummynet may call ip_output/ip6_output itself, and we need it to
+ * call the correct one.
+ */
+ if (pd->naf == AF_INET6)
+ dnflow->flags |= IPFW_ARGS_IP6;
+
return (true);
}
diff --git a/tests/sys/netpfil/pf/nat.sh b/tests/sys/netpfil/pf/nat.sh
index 5ea1dd6d8b2f..170d813d57fe 100644
--- a/tests/sys/netpfil/pf/nat.sh
+++ b/tests/sys/netpfil/pf/nat.sh
@@ -810,6 +810,50 @@ empty_pool_cleanup()
pft_cleanup
}
+atf_test_case "dummynet_mask" "cleanup"
+dummynet_mask_head()
+{
+ atf_set descr 'Verify that dummynet uses the pre-nat address for masking'
+ atf_set require.user root
+}
+
+dummynet_mask_body()
+{
+ dummynet_init
+
+ epair_srv=$(vnet_mkepair)
+ epair_cl=$(vnet_mkepair)
+
+ ifconfig ${epair_cl}b 192.0.2.2/24 up
+ route add default 192.0.2.1
+
+ vnet_mkjail srv ${epair_srv}a
+ jexec srv ifconfig ${epair_srv}a 198.51.100.2/24 up
+
+ vnet_mkjail gw ${epair_srv}b ${epair_cl}a
+ jexec gw ifconfig ${epair_srv}b 198.51.100.1/24 up
+ jexec gw ifconfig ${epair_cl}a 192.0.2.1/24 up
+ jexec gw sysctl net.inet.ip.forwarding=1
+
+ jexec gw dnctl pipe 1 config delay 100 mask src-ip 0xffffff00
+ jexec gw pfctl -e
+ pft_set_rules gw \
+ "nat pass on ${epair_srv}b inet from 192.0.2.0/24 to any -> (${epair_srv}b)" \
+ "pass out dnpipe 1"
+
+ atf_check -s exit:0 -o ignore \
+ ping -c 3 198.51.100.2
+
+ # Now check that dummynet looked at the correct address
+ atf_check -s exit:0 -o match:"ip.*192.0.2.0/0" \
+ jexec gw dnctl pipe show
+}
+
+dummynet_mask_cleanup()
+{
+ pft_cleanup
+}
+
atf_init_test_cases()
{
atf_add_test_case "exhaust"
@@ -828,4 +872,5 @@ atf_init_test_cases()
atf_add_test_case "binat_compat"
atf_add_test_case "binat_match"
atf_add_test_case "empty_pool"
+ atf_add_test_case "dummynet_mask"
}