diff --git a/sys/netinet/ip_divert.c b/sys/netinet/ip_divert.c --- a/sys/netinet/ip_divert.c +++ b/sys/netinet/ip_divert.c @@ -171,11 +171,19 @@ u_int16_t nport; struct sockaddr_in divsrc; struct m_tag *mtag; + uint16_t cookie; NET_EPOCH_ASSERT(); mtag = m_tag_locate(m, MTAG_IPFW_RULE, 0, NULL); - if (mtag == NULL) { + if (mtag != NULL) { + cookie = ((struct ipfw_rule_ref *)(mtag+1))->rulenum; + nport = htons((uint16_t) + (((struct ipfw_rule_ref *)(mtag+1))->info)); + } else if ((mtag = m_tag_locate(m, MTAG_PF_DIVERT, 0, NULL)) != NULL) { + cookie = ((struct pf_divert_mtag *)(mtag+1))->idir; + nport = htons(((struct pf_divert_mtag *)(mtag+1))->ndir); + } else { m_freem(m); return; } @@ -216,7 +224,7 @@ divsrc.sin_len = sizeof(divsrc); divsrc.sin_family = AF_INET; /* record matching rule, in host format */ - divsrc.sin_port = ((struct ipfw_rule_ref *)(mtag+1))->rulenum; + divsrc.sin_port = cookie; /* * Record receive interface address, if any. * But only for incoming packets. @@ -265,7 +273,6 @@ } /* Put packet on socket queue, if any */ - nport = htons((uint16_t)(((struct ipfw_rule_ref *)(mtag+1))->info)); SLIST_FOREACH(dcb, &V_divhash[DIVHASH(nport)], dcb_next) if (dcb->dcb_port == nport) break; @@ -304,6 +311,7 @@ const struct ip *ip; struct m_tag *mtag; struct ipfw_rule_ref *dt; + struct pf_divert_mtag *pfdt; int error, family; if (control) @@ -390,13 +398,30 @@ return (EAFNOSUPPORT); } + mtag = m_tag_locate(m, MTAG_PF_DIVERT, 0, NULL); + if (mtag == NULL) { + /* this should be normal */ + mtag = m_tag_alloc(MTAG_PF_DIVERT, 0, + sizeof(struct pf_divert_mtag), M_NOWAIT | M_ZERO); + if (mtag == NULL) { + m_freem(m); + return (ENOBUFS); + } + m_tag_prepend(m, mtag); + } + pfdt = (struct pf_divert_mtag *)(mtag+1); + if (sin) + pfdt->idir = sin->sin_port; + /* Reinject packet into the system as incoming or outgoing */ NET_EPOCH_ENTER(et); if (!sin || sin->sin_addr.s_addr == 0) { dt->info |= IPFW_IS_DIVERT | IPFW_INFO_OUT; + pfdt->ndir = PF_DIVERT_MTAG_DIR_OUT; error = div_output_outbound(family, so, m); } else { dt->info |= IPFW_IS_DIVERT | IPFW_INFO_IN; + pfdt->ndir = PF_DIVERT_MTAG_DIR_IN; error = div_output_inbound(family, so, m, sin); } NET_EPOCH_EXIT(et); diff --git a/sys/netinet/ip_var.h b/sys/netinet/ip_var.h --- a/sys/netinet/ip_var.h +++ b/sys/netinet/ip_var.h @@ -326,6 +326,16 @@ extern int (*ng_ipfw_input_p)(struct mbuf **, struct ip_fw_args *, bool); extern int (*ip_dn_ctl_ptr)(struct sockopt *); extern int (*ip_dn_io_ptr)(struct mbuf **, struct ip_fw_args *); + +/* pf specific mtag for divert(4) support */ +enum { PF_DIVERT_MTAG_DIR_IN=1, PF_DIVERT_MTAG_DIR_OUT=2 }; +struct pf_divert_mtag { + uint16_t idir; // initial pkt direction + uint16_t ndir; // a) divert(4) port upon initial diversion + // b) new direction upon pkt re-enter +}; +#define MTAG_PF_DIVERT 1262273569 + #endif /* _KERNEL */ #endif /* !_NETINET_IP_VAR_H_ */ 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 @@ -7628,7 +7628,7 @@ u_short action, reason = 0; struct mbuf *m = *m0; struct ip *h = NULL; - struct m_tag *ipfwtag; + struct m_tag *mtag; struct pf_krule *a = NULL, *r = &V_pf_default_rule, *tr, *nr; struct pf_kstate *s = NULL; struct pf_kruleset *ruleset = NULL; @@ -7718,21 +7718,26 @@ off = h->ip_hl << 2; if (__predict_false(ip_divert_ptr != NULL) && - ((ipfwtag = m_tag_locate(m, MTAG_IPFW_RULE, 0, NULL)) != NULL)) { - struct ipfw_rule_ref *rr = (struct ipfw_rule_ref *)(ipfwtag+1); - if (rr->info & IPFW_IS_DIVERT && rr->rulenum == 0) { + ((mtag = m_tag_locate(m, MTAG_PF_DIVERT, 0, NULL)) != NULL)) { + struct pf_divert_mtag *dt = (struct pf_divert_mtag *)(mtag+1); + if ((dt->idir == PF_DIVERT_MTAG_DIR_IN && dir == PF_IN) || + (dt->idir == PF_DIVERT_MTAG_DIR_OUT && dir == PF_OUT)) { if (pd.pf_mtag == NULL && ((pd.pf_mtag = pf_get_mtag(m)) == NULL)) { action = PF_DROP; goto done; } pd.pf_mtag->flags |= PF_MTAG_FLAG_PACKET_LOOPED; - m_tag_delete(m, ipfwtag); } if (pd.pf_mtag && pd.pf_mtag->flags & PF_MTAG_FLAG_FASTFWD_OURS_PRESENT) { m->m_flags |= M_FASTFWD_OURS; pd.pf_mtag->flags &= ~PF_MTAG_FLAG_FASTFWD_OURS_PRESENT; } + m_tag_delete(m, mtag); + + mtag = m_tag_locate(m, MTAG_IPFW_RULE, 0, NULL); + if (mtag != NULL) + m_tag_delete(m, mtag); } else if (pf_normalize_ip(m0, kif, &reason, &pd) != PF_PASS) { /* We do IP header normalization and packet reassembly here */ action = PF_DROP; @@ -8014,17 +8019,19 @@ if (__predict_false(ip_divert_ptr != NULL) && action == PF_PASS && r->divert.port && !PACKET_LOOPED(&pd)) { - ipfwtag = m_tag_alloc(MTAG_IPFW_RULE, 0, - sizeof(struct ipfw_rule_ref), M_NOWAIT | M_ZERO); - if (ipfwtag != NULL) { - ((struct ipfw_rule_ref *)(ipfwtag+1))->info = + mtag = m_tag_alloc(MTAG_PF_DIVERT, 0, + sizeof(struct pf_divert_mtag), M_NOWAIT | M_ZERO); + if (mtag != NULL) { + ((struct pf_divert_mtag *)(mtag+1))->ndir = ntohs(r->divert.port); - ((struct ipfw_rule_ref *)(ipfwtag+1))->rulenum = dir; + ((struct pf_divert_mtag *)(mtag+1))->idir = + (dir == PF_IN) ? PF_DIVERT_MTAG_DIR_IN : + PF_DIVERT_MTAG_DIR_OUT; if (s) PF_STATE_UNLOCK(s); - m_tag_prepend(m, ipfwtag); + m_tag_prepend(m, mtag); if (m->m_flags & M_FASTFWD_OURS) { if (pd.pf_mtag == NULL && ((pd.pf_mtag = pf_get_mtag(m)) == NULL)) { @@ -8052,6 +8059,9 @@ ("pf: failed to allocate divert tag\n")); } } + /* this flag will need revising if the pkt is forwarded */ + if (pd.pf_mtag) + pd.pf_mtag->flags &= ~PF_MTAG_FLAG_PACKET_LOOPED; if (pd.act.log) { struct pf_krule *lr; 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 @@ -2,10 +2,12 @@ PACKAGE= tests TESTSDIR= ${TESTSBASE}/sys/netpfil/pf +BINDIR= ${TESTSDIR} TESTS_SUBDIRS+= ioctl ATF_TESTS_SH+= altq \ anchor \ + divert-to \ dup \ ether \ forward \ @@ -45,6 +47,8 @@ # Tests reuse jail names and so cannot run in parallel. TEST_METADATA+= is_exclusive=true +PROGS= divapp + ${PACKAGE}FILES+= CVE-2019-5597.py \ CVE-2019-5598.py \ daytime_inetd.conf \ diff --git a/tests/sys/netpfil/pf/divapp.c b/tests/sys/netpfil/pf/divapp.c new file mode 100644 --- /dev/null +++ b/tests/sys/netpfil/pf/divapp.c @@ -0,0 +1,149 @@ +/*- + * 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 PROJECT 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 PROJECT 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. + */ + +/* Used by tests like divert-to.sh */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +struct context { + unsigned short divert_port; + bool divert_back; + + int fd; + struct sockaddr_in sin; + socklen_t sin_len; + char pkt[IP_MAXPACKET]; + ssize_t pkt_n; +}; + +static void +init(struct context *c) +{ + c->fd = socket(PF_DIVERT, SOCK_RAW, 0); + if (c->fd == -1) + errx(EX_OSERR, "init: Cannot create divert socket."); + + memset(&c->sin, 0, sizeof(c->sin)); + c->sin.sin_family = AF_INET; + c->sin.sin_port = htons(c->divert_port); + c->sin.sin_addr.s_addr = INADDR_ANY; + c->sin_len = sizeof(struct sockaddr_in); + + if (bind(c->fd, (struct sockaddr *) &c->sin, c->sin_len) != 0) + errx(EX_OSERR, "init: Cannot bind divert socket."); +} + +static ssize_t +recv_pkt(struct context *c) +{ + fd_set readfds; + struct timeval timeout; + int s; + + FD_ZERO(&readfds); + FD_SET(c->fd, &readfds); + timeout.tv_sec = 3; + timeout.tv_usec = 0; + + s = select(c->fd + 1, &readfds, 0, 0, &timeout); + if (s == -1) + errx(EX_IOERR, "recv_pkt: select() errors."); + if (s != 1) // timeout + return -1; + + c->pkt_n = recvfrom(c->fd, c->pkt, sizeof(c->pkt), 0, + (struct sockaddr *) &c->sin, &c->sin_len); + if (c->pkt_n == -1) + errx(EX_IOERR, "recv_pkt: recvfrom() errors."); + + return (c->pkt_n); +} + +static void +send_pkt(struct context *c) +{ + ssize_t n; + char errstr[32]; + + n = sendto(c->fd, c->pkt, c->pkt_n, 0, + (struct sockaddr *) &c->sin, c->sin_len); + if (n == -1) { + strerror_r(errno, errstr, sizeof(errstr)); + errx(EX_IOERR, "send_pkt: sendto() errors: %d %s.", errno, errstr); + } + if (n != c->pkt_n) + errx(EX_IOERR, "send_pkt: sendto() sent %zd of %zd bytes.", + n, c->pkt_n); +} + +int +main(int argc, char *argv[]) +{ + struct context c; + int npkt; + + if (argc < 2) + errx(EX_USAGE, + "Usage: %s [divert-back]", argv[0]); + + memset(&c, 0, sizeof(struct context)); + + c.divert_port = (unsigned short) strtol(argv[1], NULL, 10); + if (c.divert_port == 0) + errx(EX_USAGE, "divert port is not defined."); + + if (argc >= 3 && strcmp(argv[2], "divert-back") == 0) + c.divert_back = true; + + + init(&c); + + npkt = 0; + while (recv_pkt(&c) > 0) { + if (c.divert_back) + send_pkt(&c); + npkt++; + if (npkt >= 10) + break; + } + + if (npkt != 1) + errx(EXIT_FAILURE, "%d: npkt=%d.", c.divert_port, npkt); + + return EXIT_SUCCESS; +} diff --git a/tests/sys/netpfil/pf/divert-to.sh b/tests/sys/netpfil/pf/divert-to.sh new file mode 100644 --- /dev/null +++ b/tests/sys/netpfil/pf/divert-to.sh @@ -0,0 +1,413 @@ +# +# 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. + +# +# pf divert-to action test cases +# +# -----------| |-- |----| ----| |----------- +# ( ) inbound |pf_check_in| ) -> |host| -> ( ) |pf_check_out| outbound ) +# -----------| | |-- |----| ----| | |----------- +# | | +# \|/ \|/ +# |------| |------| +# |divapp| |divapp| +# |------| |------| +# +# The basic cases: +# - inbound > diverted | divapp terminated +# - inbound > diverted > inbound | host terminated +# - inbound > diverted > outbound | network terminated +# - outbound > diverted | divapp terminated +# - outbound > diverted > outbound | network terminated +# - outbound > diverted > inbound | e.g. host terminated +# +# When a packet is diverted, forwarded, and possibly diverted again: +# - inbound > diverted > inbound > forwarded +# > outbound | network terminated +# - inbound > diverted > inbound > forwarded +# > outbound > diverted > outbound | network terminated +# +# Test case naming legend: +# in - inbound +# div - diverted +# out - outbound +# fwd - forwarded +# ipfwon - with ipfw enabled, which allows all +# + +. $(atf_get_srcdir)/utils.subr + +divert_init() +{ + if ! kldstat -q -m ipdivert; then + atf_skip "This test requires ipdivert" + 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_div" "cleanup" +ipfwoff_in_div_head() +{ + atf_set descr 'Test inbound > diverted | divapp terminated' + atf_set require.user root +} +ipfwoff_in_div_body() +{ + local ipfwon + + pft_init + divert_init + test "$1" == "ipfwon" && ipfwon="yes" + test $ipfwon && ipfw_init || assert_ipfw_is_off + + epair=$(vnet_mkepair) + vnet_mkjail div ${epair}b + ifconfig ${epair}a 192.0.2.1/24 up + jexec div ifconfig ${epair}b 192.0.2.2/24 up + test $ipfwon && jexec div ipfw add 65534 allow all from any to any + + # Sanity check + atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 + + jexec div pfctl -e + pft_set_rules div \ + "pass all" \ + "pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2000" + + jexec div $(atf_get_srcdir)/divapp 2000 & + divapp_pid=$! + # Wait for the divapp to be ready + sleep 1 + + # divapp is expected to "eat" the packet + atf_check -s not-exit:0 -o ignore ping -c1 192.0.2.2 + + wait $divapp_pid +} +ipfwoff_in_div_cleanup() +{ + pft_cleanup +} + +atf_test_case "ipfwon_in_div" "cleanup" +ipfwon_in_div_head() +{ + atf_set descr 'Test inbound > diverted | divapp terminated, with ipfw enabled' + atf_set require.user root +} +ipfwon_in_div_body() +{ + ipfwoff_in_div_body "ipfwon" +} +ipfwon_in_div_cleanup() +{ + pft_cleanup +} + +atf_test_case "ipfwoff_in_div_in" "cleanup" +ipfwoff_in_div_in_head() +{ + atf_set descr 'Test inbound > diverted > inbound | host terminated' + atf_set require.user root +} +ipfwoff_in_div_in_body() +{ + local ipfwon + + pft_init + divert_init + test "$1" == "ipfwon" && ipfwon="yes" + test $ipfwon && ipfw_init || assert_ipfw_is_off + + epair=$(vnet_mkepair) + vnet_mkjail div ${epair}b + ifconfig ${epair}a 192.0.2.1/24 up + jexec div ifconfig ${epair}b 192.0.2.2/24 up + test $ipfwon && jexec div ipfw add 65534 allow all from any to any + + # Sanity check + atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 + + jexec div pfctl -e + pft_set_rules div \ + "pass all" \ + "pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2000 no state" + + jexec div $(atf_get_srcdir)/divapp 2000 divert-back & + divapp_pid=$! + # Wait for the divapp to be ready + sleep 1 + + # divapp is NOT expected to "eat" the packet + atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 + + wait $divapp_pid +} +ipfwoff_in_div_in_cleanup() +{ + pft_cleanup +} + +atf_test_case "ipfwon_in_div_in" "cleanup" +ipfwon_in_div_in_head() +{ + atf_set descr 'Test inbound > diverted > inbound | host terminated, with ipfw enabled' + atf_set require.user root +} +ipfwon_in_div_in_body() +{ + ipfwoff_in_div_in_body "ipfwon" +} +ipfwon_in_div_in_cleanup() +{ + pft_cleanup +} + +atf_test_case "ipfwoff_out_div" "cleanup" +ipfwoff_out_div_head() +{ + atf_set descr 'Test outbound > diverted | divapp terminated' + atf_set require.user root +} +ipfwoff_out_div_body() +{ + local ipfwon + + pft_init + divert_init + test "$1" == "ipfwon" && ipfwon="yes" + test $ipfwon && ipfw_init || assert_ipfw_is_off + + epair=$(vnet_mkepair) + vnet_mkjail div ${epair}b + ifconfig ${epair}a 192.0.2.1/24 up + jexec div ifconfig ${epair}b 192.0.2.2/24 up + test $ipfwon && jexec div ipfw add 65534 allow all from any to any + + # Sanity check + atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 + + jexec div pfctl -e + pft_set_rules div \ + "pass all" \ + "pass in inet proto icmp icmp-type echoreq no state" \ + "pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 2000 no state" + + jexec div $(atf_get_srcdir)/divapp 2000 & + divapp_pid=$! + # Wait for the divapp to be ready + sleep 1 + + # divapp is expected to "eat" the packet + atf_check -s not-exit:0 -o ignore ping -c1 192.0.2.2 + + wait $divapp_pid +} +ipfwoff_out_div_cleanup() +{ + pft_cleanup +} + +atf_test_case "ipfwon_out_div" "cleanup" +ipfwon_out_div_head() +{ + atf_set descr 'Test outbound > diverted | divapp terminated, with ipfw enabled' + atf_set require.user root +} +ipfwon_out_div_body() +{ + ipfwoff_out_div_body "ipfwon" +} +ipfwon_out_div_cleanup() +{ + pft_cleanup +} + +atf_test_case "ipfwoff_out_div_out" "cleanup" +ipfwoff_out_div_out_head() +{ + atf_set descr 'Test outbound > diverted > outbound | network terminated' + atf_set require.user root +} +ipfwoff_out_div_out_body() +{ + local ipfwon + + pft_init + divert_init + test "$1" == "ipfwon" && ipfwon="yes" + test $ipfwon && ipfw_init || assert_ipfw_is_off + + epair=$(vnet_mkepair) + vnet_mkjail div ${epair}b + ifconfig ${epair}a 192.0.2.1/24 up + jexec div ifconfig ${epair}b 192.0.2.2/24 up + test $ipfwon && jexec div ipfw add 65534 allow all from any to any + + # Sanity check + atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 + + jexec div pfctl -e + pft_set_rules div \ + "pass all" \ + "pass in inet proto icmp icmp-type echoreq no state" \ + "pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 2000 no state" + + jexec div $(atf_get_srcdir)/divapp 2000 divert-back & + divapp_pid=$! + # Wait for the divapp to be ready + sleep 1 + + # divapp is NOT expected to "eat" the packet + atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 + + wait $divapp_pid +} +ipfwoff_out_div_out_cleanup() +{ + pft_cleanup +} + +atf_test_case "ipfwon_out_div_out" "cleanup" +ipfwon_out_div_out_head() +{ + atf_set descr 'Test outbound > diverted > outbound | network terminated, with ipfw enabled' + atf_set require.user root +} +ipfwon_out_div_out_body() +{ + ipfwoff_out_div_out_body "ipfwon" +} +ipfwon_out_div_out_cleanup() +{ + pft_cleanup +} + +atf_test_case "ipfwoff_in_div_in_fwd_out_div_out" "cleanup" +ipfwoff_in_div_in_fwd_out_div_out_head() +{ + atf_set descr 'Test inbound > diverted > inbound > forwarded > outbound > diverted > outbound | network terminated' + atf_set require.user root +} +ipfwoff_in_div_in_fwd_out_div_out_body() +{ + local ipfwon + + pft_init + divert_init + test "$1" == "ipfwon" && ipfwon="yes" + test $ipfwon && ipfw_init || assert_ipfw_is_off + + # host router site + epair0=$(vnet_mkepair) + epair1=$(vnet_mkepair) + + vnet_mkjail router ${epair0}b ${epair1}a + ifconfig ${epair0}a 192.0.2.1/24 up + jexec router sysctl net.inet.ip.forwarding=1 + jexec router ifconfig ${epair0}b 192.0.2.2/24 up + jexec router ifconfig ${epair1}a 198.51.100.1/24 up + test $ipfwon && jexec router ipfw add 65534 allow all from any to any + + vnet_mkjail site ${epair1}b + jexec site ifconfig ${epair1}b 198.51.100.2/24 up + jexec site route add default 198.51.100.1 + test $ipfwon && jexec site ipfw add 65534 allow all from any to any + + route add -net 198.51.100.0/24 192.0.2.2 + + # Sanity check + atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 + + # Should be routed without pf + atf_check -s exit:0 -o ignore ping -c3 198.51.100.2 + + jexec router pfctl -e + pft_set_rules router \ + "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 echoreq divert-to 127.0.0.1 port 2002 no state" + + jexec router $(atf_get_srcdir)/divapp 2001 divert-back & + indivapp_pid=$! + jexec router $(atf_get_srcdir)/divapp 2002 divert-back & + outdivapp_pid=$! + # Wait for the divappS to be ready + sleep 1 + + # Both divappS are NOT expected to "eat" the packet + atf_check -s exit:0 -o ignore ping -c1 198.51.100.2 + + wait $indivapp_pid && wait $outdivapp_pid +} +ipfwoff_in_div_in_fwd_out_div_out_cleanup() +{ + pft_cleanup +} + +atf_test_case "ipfwon_in_div_in_fwd_out_div_out" "cleanup" +ipfwon_in_div_in_fwd_out_div_out_head() +{ + atf_set descr 'Test inbound > diverted > inbound > forwarded > outbound > diverted > outbound | network terminated, with ipfw enabled' + atf_set require.user root +} +ipfwon_in_div_in_fwd_out_div_out_body() +{ + ipfwoff_in_div_in_fwd_out_div_out_body "ipfwon" +} +ipfwon_in_div_in_fwd_out_div_out_cleanup() +{ + pft_cleanup +} + +atf_init_test_cases() +{ + atf_add_test_case "ipfwoff_in_div" + atf_add_test_case "ipfwoff_in_div_in" + atf_add_test_case "ipfwon_in_div" + atf_add_test_case "ipfwon_in_div_in" + + atf_add_test_case "ipfwoff_out_div" + atf_add_test_case "ipfwoff_out_div_out" + atf_add_test_case "ipfwon_out_div" + atf_add_test_case "ipfwon_out_div_out" + + atf_add_test_case "ipfwoff_in_div_in_fwd_out_div_out" + atf_add_test_case "ipfwon_in_div_in_fwd_out_div_out" +}