aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKristof Provost <kp@FreeBSD.org>2025-09-04 12:49:00 +0000
committerKristof Provost <kp@FreeBSD.org>2025-09-24 11:44:54 +0000
commit7ec06143964a949ebf6885ac120fdf839ad29eab (patch)
tree961d5348f63c79bb91d62231f920e2fe27b1a36c
parentdc0cf0648c8d28ab4914c798a4cff8256ae94ee5 (diff)
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")
-rw-r--r--sys/netpfil/pf/pf.c39
-rw-r--r--tests/sys/netpfil/pf/nat.sh45
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"
}