diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -303,6 +303,8 @@ 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_cleanup(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 @@ /* 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_cleanup(m, mtag); return (PF_PASS); } @@ -4361,7 +4363,7 @@ mtag->flags |= PF_MTAG_FLAG_DUMMYNET; ip_dn_io_ptr(m0, &dnflow); if (*m0 != NULL) - mtag->flags &= ~PF_MTAG_FLAG_DUMMYNET; + pf_dummynet_flag_cleanup(m, mtag); } else { PF_RULES_RUNLOCK(); } @@ -7794,6 +7796,21 @@ return (pf_test_eth_rule(dir, kif, m0)); } +static __inline void +pf_dummynet_flag_cleanup(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 @@ 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_cleanup(*m0, pd->pf_mtag); } } } @@ -7933,7 +7950,7 @@ /* 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_cleanup(m, pd.pf_mtag); PF_RULES_RUNLOCK(); return (PF_PASS); @@ -8494,7 +8511,7 @@ 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_cleanup(m, pd.pf_mtag); /* Dummynet re-injects packets after they've * completed their delay. We've already * processed them, so pass unconditionally. */ diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile --- a/tests/sys/netpfil/pf/Makefile +++ b/tests/sys/netpfil/pf/Makefile @@ -8,6 +8,7 @@ ATF_TESTS_SH+= altq \ anchor \ divert-to \ + divert-to_and_dnpipe \ dup \ ether \ forward \ diff --git a/tests/sys/netpfil/pf/divert-to_and_dnpipe.sh b/tests/sys/netpfil/pf/divert-to_and_dnpipe.sh new file mode 100644 --- /dev/null +++ b/tests/sys/netpfil/pf/divert-to_and_dnpipe.sh @@ -0,0 +1,175 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2023 Igor Ostapenko +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +# +# Test case naming legend: +# in - inbound +# div - diverted +# out - outbound +# dn - delayed by dummynet +# ipfwon - with ipfw enabled, which allows all +# ipfwoff - with ipfw disabled +# + +. $(atf_get_srcdir)/utils.subr + +divert_init() +{ + if ! kldstat -q -m ipdivert; then + atf_skip "This test requires ipdivert" + fi +} + +dummynet_init() +{ + if ! kldstat -q -m dummynet; then + atf_skip "This test requires dummynet" + fi +} + +ipfw_init() +{ + if ! kldstat -q -m ipfw; then + atf_skip "This test requires ipfw" + fi +} + +assert_ipfw_is_off() +{ + if kldstat -q -m ipfw; then + atf_skip "This test is for the case when ipfw is not loaded" + fi +} + +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_dn_in_div_in_out_dn_out_div_out" + atf_add_test_case "ipfwon_in_dn_in_div_in_out_dn_out_div_out" +}