pf: fix pf divert-to loop

Resolved conflict between ipfw and pf if both are used and pf wants to
do divert(4) by having separate mtags for pf and ipfw.

Also fix the incorrect 'rulenum' check, which caused the reported loop.

While here add a few test cases to ensure that divert-to works as
expected, even if ipfw is loaded.

divert(4)
PR:		272770
MFC after:	3 weeks
Reviewed by:	kp
Differential Revision:	https://reviews.freebsd.org/D42142
This commit is contained in:
Igor Ostapenko 2023-10-19 12:12:15 +02:00 committed by Kristof Provost
parent e44751530e
commit fabf705f4b
6 changed files with 625 additions and 14 deletions

View file

@ -171,11 +171,19 @@ divert_packet(struct mbuf *m, bool incoming)
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 @@ divert_packet(struct mbuf *m, bool incoming)
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 @@ divert_packet(struct mbuf *m, bool incoming)
}
/* 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 @@ div_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam,
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 @@ div_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam,
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);

View file

@ -326,6 +326,16 @@ extern void (*ip_divert_ptr)(struct mbuf *m, bool incoming);
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_ */

View file

@ -7628,7 +7628,7 @@ pf_test(int dir, int pflags, struct ifnet *ifp, struct mbuf **m0,
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 @@ pf_test(int dir, int pflags, struct ifnet *ifp, struct mbuf **m0,
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 @@ pf_test(int dir, int pflags, struct ifnet *ifp, struct mbuf **m0,
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_test(int dir, int pflags, struct ifnet *ifp, struct mbuf **m0,
("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;

View file

@ -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 @@ ATF_TESTS_PYTEST+= sctp.py
# 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 \

View file

@ -0,0 +1,149 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2023 Igor Ostapenko <pm@igoro.pro>
*
* 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 <errno.h>
#include <stdlib.h>
#include <stdbool.h>
#include <err.h>
#include <sysexits.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
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-port> [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;
}

View file

@ -0,0 +1,413 @@
#
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2023 Igor Ostapenko <pm@igoro.pro>
#
# 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 <a--epair0--b> router <a--epair1--b> 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"
}