pf: fix dummynet + ipdivert use case

Dummynet re-injects an mbuf with MTAG_IPFW_RULE added, and the same mtag
is used by divert(4) as parameters for packet diversion.

If according to pf rule set a packet should go through dummynet first
and through ipdivert after then mentioned mtag must be removed after
dummynet not to make ipdivert think that this is its input parameters.

At the very beginning ipfw consumes this mtag what means the same
behavior with tag clearing after dummynet.

And after fabf705f4b5a pf passes parameters to ipdivert using its
personal MTAG_PF_DIVERT mtag.

PR:		274850
Reviewed by:	kp
Differential Revision:	https://reviews.freebsd.org/D42609

(cherry picked from commit fe3bb40b9e807d4010617de1ef040ba3aa623487)
This commit is contained in:
Igor Ostapenko 2023-11-17 17:04:01 +01:00 committed by Kristof Provost
parent 0784b5768e
commit f831517d86
2 changed files with 139 additions and 6 deletions

View file

@ -303,6 +303,8 @@ static int pf_state_key_attach(struct pf_state_key *,
static void pf_state_key_detach(struct pf_kstate *, int);
static int pf_state_key_ctor(void *, int, void *, int);
static u_int32_t pf_tcp_iss(struct pf_pdesc *);
static __inline void pf_dummynet_flag_remove(struct mbuf *m,
struct pf_mtag *pf_mtag);
static int pf_dummynet(struct pf_pdesc *, struct pf_kstate *,
struct pf_krule *, struct mbuf **);
static int pf_dummynet_route(struct pf_pdesc *,
@ -4140,7 +4142,7 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
/* But only once. We may see the packet multiple times (e.g.
* PFIL_IN/PFIL_OUT). */
mtag->flags &= ~PF_MTAG_FLAG_DUMMYNET;
pf_dummynet_flag_remove(m, mtag);
return (PF_PASS);
}
@ -4361,7 +4363,7 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
mtag->flags |= PF_MTAG_FLAG_DUMMYNET;
ip_dn_io_ptr(m0, &dnflow);
if (*m0 != NULL)
mtag->flags &= ~PF_MTAG_FLAG_DUMMYNET;
pf_dummynet_flag_remove(m, mtag);
} else {
PF_RULES_RUNLOCK();
}
@ -7794,6 +7796,21 @@ pf_test_eth(int dir, int pflags, struct ifnet *ifp, struct mbuf **m0,
return (pf_test_eth_rule(dir, kif, m0));
}
static __inline void
pf_dummynet_flag_remove(struct mbuf *m, struct pf_mtag *pf_mtag)
{
struct m_tag *mtag;
pf_mtag->flags &= ~PF_MTAG_FLAG_DUMMYNET;
/* dummynet adds this tag, but pf does not need it,
* and keeping it creates unexpected behavior,
* e.g. in case of divert(4) usage right after dummynet. */
mtag = m_tag_locate(m, MTAG_IPFW_RULE, 0, NULL);
if (mtag != NULL)
m_tag_delete(m, mtag);
}
static int
pf_dummynet(struct pf_pdesc *pd, struct pf_kstate *s,
struct pf_krule *r, struct mbuf **m0)
@ -7844,7 +7861,7 @@ pf_dummynet_route(struct pf_pdesc *pd, struct pf_kstate *s,
ip_dn_io_ptr(m0, &dnflow);
if (*m0 != NULL) {
pd->pf_mtag->flags &= ~PF_MTAG_FLAG_ROUTE_TO;
pd->pf_mtag->flags &= ~PF_MTAG_FLAG_DUMMYNET;
pf_dummynet_flag_remove(*m0, pd->pf_mtag);
}
}
}
@ -7933,7 +7950,7 @@ pf_test(int dir, int pflags, struct ifnet *ifp, struct mbuf **m0,
/* But only once. We may see the packet multiple times (e.g.
* PFIL_IN/PFIL_OUT). */
pd.pf_mtag->flags &= ~PF_MTAG_FLAG_DUMMYNET;
pf_dummynet_flag_remove(m, pd.pf_mtag);
PF_RULES_RUNLOCK();
return (PF_PASS);
@ -8494,7 +8511,7 @@ pf_test6(int dir, int pflags, struct ifnet *ifp, struct mbuf **m0, struct inpcb
if (ip_dn_io_ptr != NULL && pd.pf_mtag != NULL &&
pd.pf_mtag->flags & PF_MTAG_FLAG_DUMMYNET) {
pd.pf_mtag->flags &= ~PF_MTAG_FLAG_DUMMYNET;
pf_dummynet_flag_remove(m, pd.pf_mtag);
/* Dummynet re-injects packets after they've
* completed their delay. We've already
* processed them, so pass unconditionally. */

View file

@ -51,11 +51,13 @@
# > outbound > diverted > outbound | network terminated
#
# Test case naming legend:
# ipfwon - with ipfw enabled
# ipfwoff - with ipfw disabled
# in - inbound
# div - diverted
# out - outbound
# fwd - forwarded
# ipfwon - with ipfw enabled, which allows all
# dn - delayed by dummynet
#
. $(atf_get_srcdir)/utils.subr
@ -67,6 +69,13 @@ divert_init()
fi
}
dummynet_init()
{
if ! kldstat -q -m dummynet; then
atf_skip "This test requires dummynet"
fi
}
ipfw_init()
{
if ! kldstat -q -m ipfw; then
@ -396,6 +405,110 @@ ipfwon_in_div_in_fwd_out_div_out_cleanup()
pft_cleanup
}
atf_test_case "ipfwoff_in_dn_in_div_in_out_dn_out_div_out" "cleanup"
ipfwoff_in_dn_in_div_in_out_dn_out_div_out_head()
{
atf_set descr 'Test inbound > delayed+diverted > outbound > delayed+diverted > outbound | network terminated'
atf_set require.user root
}
ipfwoff_in_dn_in_div_in_out_dn_out_div_out_body()
{
local ipfwon
pft_init
divert_init
dummynet_init
test "$1" == "ipfwon" && ipfwon="yes"
test $ipfwon && ipfw_init || assert_ipfw_is_off
epair=$(vnet_mkepair)
vnet_mkjail alcatraz ${epair}b
ifconfig ${epair}a 192.0.2.1/24 up
ifconfig ${epair}a ether 02:00:00:00:00:01
jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
test $ipfwon && jexec alcatraz ipfw add 65534 allow all from any to any
# Sanity check
atf_check -s exit:0 -o ignore ping -c3 192.0.2.2
# a) ping should time out due to very narrow dummynet pipes {
jexec alcatraz dnctl pipe 1001 config bw 1Byte/s
jexec alcatraz dnctl pipe 1002 config bw 1Byte/s
jexec alcatraz pfctl -e
pft_set_rules alcatraz \
"ether pass in from 02:00:00:00:00:01 l3 all dnpipe 1001" \
"ether pass out to 02:00:00:00:00:01 l3 all dnpipe 1002 " \
"pass all" \
"pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 1001 no state" \
"pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 1002 no state"
jexec alcatraz $(atf_get_srcdir)/divapp 1001 divert-back &
indivapp_pid=$!
jexec alcatraz $(atf_get_srcdir)/divapp 1002 divert-back &
outdivapp_pid=$!
# Wait for the divappS to be ready
sleep 1
atf_check -s not-exit:0 -o ignore ping -c1 -s56 -t1 192.0.2.2
wait $indivapp_pid
atf_check_not_equal 0 $?
wait $outdivapp_pid
atf_check_not_equal 0 $?
# }
# b) ping should NOT time out due to wide enough dummynet pipes {
jexec alcatraz dnctl pipe 2001 config bw 100KByte/s
jexec alcatraz dnctl pipe 2002 config bw 100KByte/s
jexec alcatraz pfctl -e
pft_set_rules alcatraz \
"ether pass in from 02:00:00:00:00:01 l3 all dnpipe 2001" \
"ether pass out to 02:00:00:00:00:01 l3 all dnpipe 2002 " \
"pass all" \
"pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2001 no state" \
"pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 2002 no state"
jexec alcatraz $(atf_get_srcdir)/divapp 2001 divert-back &
indivapp_pid=$!
jexec alcatraz $(atf_get_srcdir)/divapp 2002 divert-back &
outdivapp_pid=$!
# Wait for the divappS to be ready
sleep 1
atf_check -s exit:0 -o ignore ping -c1 -s56 -t1 192.0.2.2
wait $indivapp_pid
atf_check_equal 0 $?
wait $outdivapp_pid
atf_check_equal 0 $?
# }
}
ipfwoff_in_dn_in_div_in_out_dn_out_div_out_cleanup()
{
pft_cleanup
}
atf_test_case "ipfwon_in_dn_in_div_in_out_dn_out_div_out" "cleanup"
ipfwon_in_dn_in_div_in_out_dn_out_div_out_head()
{
atf_set descr 'Test inbound > delayed+diverted > outbound > delayed+diverted > outbound | network terminated, with ipfw enabled'
atf_set require.user root
}
ipfwon_in_dn_in_div_in_out_dn_out_div_out_body()
{
ipfwoff_in_dn_in_div_in_out_dn_out_div_out_body "ipfwon"
}
ipfwon_in_dn_in_div_in_out_dn_out_div_out_cleanup()
{
pft_cleanup
}
atf_init_test_cases()
{
atf_add_test_case "ipfwoff_in_div"
@ -410,4 +523,7 @@ atf_init_test_cases()
atf_add_test_case "ipfwoff_in_div_in_fwd_out_div_out"
atf_add_test_case "ipfwon_in_div_in_fwd_out_div_out"
atf_add_test_case "ipfwoff_in_dn_in_div_in_out_dn_out_div_out"
atf_add_test_case "ipfwon_in_dn_in_div_in_out_dn_out_div_out"
}