pf: fix reply-to after rdr and dummynet

If we redirect a packet to localhost and it gets dummynet'd it may be
re-injected later (e.g. when delayed) which means it will be passed
through ip_input() again. ip_input() will then reject the packet because
it's directed to the loopback address, but did not arrive on a loopback
interface.

Fix this by having pf set the rcvif to V_iflo if we redirect to
loopback.

See also:	https://redmine.pfsense.org/issues/15363
Sponsored by:	Rubicon Communications, LLC ("Netgate")
This commit is contained in:
Kristof Provost 2024-03-27 15:47:21 +01:00
parent 5aaef5a600
commit a983cea4e9
2 changed files with 73 additions and 0 deletions

View file

@ -7954,6 +7954,18 @@ pf_dummynet_route(struct pf_pdesc *pd, struct pf_kstate *s,
sizeof(struct sockaddr_in6));
}
if (s != NULL && s->nat_rule.ptr != NULL &&
s->nat_rule.ptr->action == PF_RDR &&
((pd->af == AF_INET && IN_LOOPBACK(ntohl(pd->dst->v4.s_addr))) ||
(pd->af == AF_INET6 && IN6_IS_ADDR_LOOPBACK(&pd->dst->v6)))) {
/*
* If we're redirecting to loopback mark this packet
* as being local. Otherwise it might get dropped
* if dummynet re-injects.
*/
(*m0)->m_pkthdr.rcvif = V_loif;
}
if (pf_pdesc_to_dnflow(pd, r, s, &dnflow)) {
pd->pf_mtag->flags |= PF_MTAG_FLAG_DUMMYNET;
pd->pf_mtag->flags |= PF_MTAG_FLAG_DUMMYNETED;

View file

@ -626,6 +626,66 @@ ifbound_reply_to_v6_cleanup()
pft_cleanup
}
atf_test_case "ifbound_reply_to_rdr_dummynet" "cleanup"
ifbound_reply_to_rdr_dummynet_head()
{
atf_set descr 'Test that reply-to states bind to the expected non-default-route interface after rdr and dummynet'
atf_set require.user root
}
ifbound_reply_to_rdr_dummynet_body()
{
dummynet_init
j="route_to:ifbound_reply_to_rdr_dummynet"
epair_one=$(vnet_mkepair)
epair_two=$(vnet_mkepair)
ifconfig ${epair_one}b inet 192.0.2.2/24 up
ifconfig ${epair_two}b up
vnet_mkjail $j ${epair_one}a ${epair_two}a
jexec $j ifconfig lo0 inet 127.0.0.1/8 up
jexec $j ifconfig ${epair_one}a 192.0.2.1/24 up
jexec $j ifconfig ${epair_two}a 198.51.100.1/24 up
jexec $j route add default 198.51.100.254
jexec $j pfctl -e
jexec $j dnctl pipe 1 config delay 1
pft_set_rules $j \
"set state-policy if-bound" \
"rdr on ${epair_one}a proto icmp from any to 192.0.2.1 -> 127.0.0.1" \
"rdr on ${epair_two}a proto icmp from any to 198.51.100.1 -> 127.0.0.1" \
"match in on ${epair_one}a inet all dnpipe (1, 1)" \
"pass in on ${epair_one}a reply-to (${epair_one}a 192.0.2.2) inet from any to 127.0.0.1 keep state"
atf_check -s exit:0 -o ignore \
ping -c 3 192.0.2.1
atf_check -s exit:0 \
${common_dir}/pft_ping.py \
--to 192.0.2.1 \
--from 203.0.113.2 \
--sendif ${epair_one}b \
--replyif ${epair_one}b
# pft_ping uses the same ID every time, so this will look like more traffic in the same state
atf_check -s exit:0 \
${common_dir}/pft_ping.py \
--to 192.0.2.1 \
--from 203.0.113.2 \
--sendif ${epair_one}b \
--replyif ${epair_one}b
jexec $j pfctl -sr -vv
jexec $j pfctl -ss -vv
}
ifbound_reply_to_rdr_dummynet_cleanup()
{
pft_cleanup
}
atf_test_case "dummynet_frag" "cleanup"
dummynet_frag_head()
{
@ -740,6 +800,7 @@ atf_init_test_cases()
atf_add_test_case "ifbound_v6"
atf_add_test_case "ifbound_reply_to"
atf_add_test_case "ifbound_reply_to_v6"
atf_add_test_case "ifbound_reply_to_rdr_dummynet"
atf_add_test_case "dummynet_frag"
atf_add_test_case "dummynet_double"
}