git: ad6562ec858f - main - pf: Don't pfsync states with unrecoverable routing information
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 05 Dec 2024 22:09:33 UTC
The branch main has been updated by ks:
URL: https://cgit.FreeBSD.org/src/commit/?id=ad6562ec858fffbed83560ed3f79375a44be2705
commit ad6562ec858fffbed83560ed3f79375a44be2705
Author: Kajetan Staszkiewicz <ks@FreeBSD.org>
AuthorDate: 2024-11-29 22:20:14 +0000
Commit: Kajetan Staszkiewicz <ks@FreeBSD.org>
CommitDate: 2024-12-05 22:03:12 +0000
pf: Don't pfsync states with unrecoverable routing information
States created by route-to rules can't be trusted when received with
pfsync version 1301 as they lack the rt and rt_kif information. They
are imported, though, and pf_route() function attempts to recover
the missing information for every forwarded packet.
Move the recovery operation to pfsync_state_import() so that it's
performed only once and if it's impossible don't import the state.
Add an additional check for cases when recovery might produce wrong
results.
Reviewed by: kp
Approved by: kp (mentor)
Sponsored by: InnoGames GmbH
Differential Revision: https://reviews.freebsd.org/D47906
---
sys/netpfil/pf/if_pfsync.c | 90 ++++++++++++---
sys/netpfil/pf/pf.c | 78 ++++---------
tests/sys/netpfil/pf/pfsync.sh | 256 ++++++++++++++++++++++++++++++++++++-----
3 files changed, 323 insertions(+), 101 deletions(-)
diff --git a/sys/netpfil/pf/if_pfsync.c b/sys/netpfil/pf/if_pfsync.c
index 8dd3d875dc0b..5923675ff144 100644
--- a/sys/netpfil/pf/if_pfsync.c
+++ b/sys/netpfil/pf/if_pfsync.c
@@ -110,6 +110,8 @@
#include <netpfil/pf/pfsync_nv.h>
+#define DPFPRINTF(n, x) if (V_pf_status.debug >= (n)) printf x
+
struct pfsync_bucket;
struct pfsync_softc;
@@ -521,10 +523,13 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
#endif
struct pfsync_state_key *kw, *ks;
struct pf_kstate *st = NULL;
- struct pf_state_key *skw = NULL, *sks = NULL;
- struct pf_krule *r = NULL;
- struct pfi_kkif *kif;
- int error;
+ struct pf_state_key *skw = NULL, *sks = NULL;
+ struct pf_krule *r = NULL;
+ struct pfi_kkif *kif;
+ struct pfi_kkif *rt_kif = NULL;
+ struct pf_kpooladdr *rpool_first;
+ int error;
+ uint8_t rt = 0;
PF_RULES_RASSERT();
@@ -556,6 +561,67 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
else
r = &V_pf_default_rule;
+ /*
+ * Check routing interface early on. Do it before allocating memory etc.
+ * because there is a high chance there will be a lot more such states.
+ */
+ switch (msg_version) {
+ case PFSYNC_MSG_VERSION_1301:
+ /*
+ * On FreeBSD <= 13 the routing interface and routing operation
+ * are not sent over pfsync. If the ruleset is identical,
+ * though, we might be able to recover the routing information
+ * from the local ruleset.
+ */
+ if (r != &V_pf_default_rule) {
+ /*
+ * The ruleset is identical, try to recover. If the rule
+ * has a redirection pool with a single interface, there
+ * is a chance that this interface is identical as on
+ * the pfsync peer. If there's more than one interface,
+ * give up, as we can't be sure that we will pick the
+ * same one as the pfsync peer did.
+ */
+ rpool_first = TAILQ_FIRST(&(r->rpool.list));
+ if ((rpool_first == NULL) ||
+ (TAILQ_NEXT(rpool_first, entries) != NULL)) {
+ DPFPRINTF(PF_DEBUG_MISC,
+ ("%s: can't recover routing information "
+ "because of empty or bad redirection pool\n",
+ __func__));
+ return ((flags & PFSYNC_SI_IOCTL) ? EINVAL : 0);
+ }
+ rt = r->rt;
+ rt_kif = rpool_first->kif;
+ } else if (!PF_AZERO(&sp->pfs_1301.rt_addr, sp->pfs_1301.af)) {
+ /*
+ * Ruleset different, routing *supposedly* requested,
+ * give up on recovering.
+ */
+ DPFPRINTF(PF_DEBUG_MISC,
+ ("%s: can't recover routing information "
+ "because of different ruleset\n", __func__));
+ return ((flags & PFSYNC_SI_IOCTL) ? EINVAL : 0);
+ }
+ break;
+ case PFSYNC_MSG_VERSION_1400:
+ /*
+ * On FreeBSD 14 and above we're not taking any chances.
+ * We use the information synced to us.
+ */
+ if (sp->pfs_1400.rt) {
+ rt_kif = pfi_kkif_find(sp->pfs_1400.rt_ifname);
+ if (rt_kif == NULL) {
+ DPFPRINTF(PF_DEBUG_MISC,
+ ("%s: unknown route interface: %s\n",
+ __func__, sp->pfs_1400.rt_ifname));
+ return ((flags & PFSYNC_SI_IOCTL) ? EINVAL : 0);
+ }
+ rt = sp->pfs_1400.rt;
+ }
+ break;
+ }
+
if ((r->max_states &&
counter_u64_fetch(r->states_cur) >= r->max_states))
goto cleanup;
@@ -629,6 +695,9 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
st->act.log = sp->pfs_1301.log;
st->timeout = sp->pfs_1301.timeout;
+ st->act.rt = rt;
+ st->act.rt_kif = rt_kif;
+
switch (msg_version) {
case PFSYNC_MSG_VERSION_1301:
st->state_flags = sp->pfs_1301.state_flags;
@@ -680,17 +749,6 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
st->act.max_mss = ntohs(sp->pfs_1400.max_mss);
st->act.set_prio[0] = sp->pfs_1400.set_prio[0];
st->act.set_prio[1] = sp->pfs_1400.set_prio[1];
- st->act.rt = sp->pfs_1400.rt;
- if (st->act.rt && (st->act.rt_kif = pfi_kkif_find(sp->pfs_1400.rt_ifname)) == NULL) {
- if (V_pf_status.debug >= PF_DEBUG_MISC)
- printf("%s: unknown route interface: %s\n",
- __func__, sp->pfs_1400.rt_ifname);
- if (flags & PFSYNC_SI_IOCTL)
- error = EINVAL;
- else
- error = 0;
- goto cleanup_keys;
- }
break;
default:
panic("%s: Unsupported pfsync_msg_version %d",
@@ -737,7 +795,7 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
cleanup:
error = ENOMEM;
-cleanup_keys:
+
if (skw == sks)
sks = NULL;
uma_zfree(V_pf_state_key_z, skw);
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 8d9d595dc1ec..a0c9a92c6b84 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -7690,14 +7690,14 @@ pf_route(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
struct mbuf *m0, *m1, *md;
struct sockaddr_in dst;
struct ip *ip;
- struct ifnet *ifp = NULL;
- struct pf_addr naddr;
+ struct ifnet *ifp;
int error = 0;
uint16_t ip_len, ip_off;
uint16_t tmp;
int r_dir;
- KASSERT(m && *m && r && oifp, ("%s: invalid parameters", __func__));
+ KASSERT(m && *m && r && oifp && pd->act.rt_kif,
+ ("%s: invalid parameters", __func__));
SDT_PROBE4(pf, ip, route_to, entry, *m, pd, s, oifp);
@@ -7720,13 +7720,15 @@ pf_route(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
goto bad_locked;
}
+ if ((ifp = pd->act.rt_kif->pfik_ifp) == NULL) {
+ m0 = *m;
+ *m = NULL;
+ SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
+ goto bad_locked;
+ }
+
if (pd->act.rt == PF_DUPTO) {
if ((pd->pf_mtag->flags & PF_MTAG_FLAG_DUPLICATED)) {
- ifp = pd->act.rt_kif ? pd->act.rt_kif->pfik_ifp : NULL;
- /* If pfsync'd from FreeBSD < 14 */
- if (ifp == NULL && r->rpool.cur != NULL)
- ifp = r->rpool.cur->kif ?
- r->rpool.cur->kif->pfik_ifp : NULL;
if (s != NULL) {
PF_STATE_UNLOCK(s);
}
@@ -7764,36 +7766,18 @@ pf_route(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
dst.sin_len = sizeof(dst);
dst.sin_addr = ip->ip_dst;
dst.sin_addr.s_addr = pd->act.rt_addr.v4.s_addr;
- ifp = pd->act.rt_kif ? pd->act.rt_kif->pfik_ifp : NULL;
-
- bzero(&naddr, sizeof(naddr));
if (s != NULL){
- struct pfi_kkif *kif;
-
- kif = pd->act.rt_kif;
- /* If pfsync'd from FreeBSD < 14 */
- if (ifp == NULL && r->rpool.cur != NULL) {
- ifp = r->rpool.cur->kif ?
- r->rpool.cur->kif->pfik_ifp : NULL;
- kif = r->rpool.cur->kif;
- }
- if (ifp != NULL && kif != NULL &&
- r->rule_flag & PFRULE_IFBOUND &&
+ if (r->rule_flag & PFRULE_IFBOUND &&
pd->act.rt == PF_REPLYTO &&
s->kif == V_pfi_all) {
- s->kif = kif;
+ s->kif = pd->act.rt_kif;
s->orig_kif = oifp->if_pf_kif;
}
PF_STATE_UNLOCK(s);
}
- if (ifp == NULL) {
- SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
- goto bad;
- }
-
if (pd->dir == PF_IN) {
if (pf_test(AF_INET, PF_OUT, PFIL_FWD, ifp, &m0, inp,
&pd->act) != PF_PASS) {
@@ -7943,10 +7927,10 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
struct sockaddr_in6 dst;
struct ip6_hdr *ip6;
struct ifnet *ifp = NULL;
- struct pf_addr naddr;
int r_dir;
- KASSERT(m && *m && r && oifp, ("%s: invalid parameters", __func__));
+ KASSERT(m && *m && r && oifp && pd->act.rt_kif,
+ ("%s: invalid parameters", __func__));
SDT_PROBE4(pf, ip6, route_to, entry, *m, pd, s, oifp);
@@ -7969,13 +7953,15 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
goto bad_locked;
}
+ if ((ifp = pd->act.rt_kif->pfik_ifp) == NULL) {
+ m0 = *m;
+ *m = NULL;
+ SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
+ goto bad_locked;
+ }
+
if (pd->act.rt == PF_DUPTO) {
if ((pd->pf_mtag->flags & PF_MTAG_FLAG_DUPLICATED)) {
- ifp = pd->act.rt_kif ? pd->act.rt_kif->pfik_ifp : NULL;
- /* If pfsync'd from FreeBSD < 14 */
- if (ifp == NULL && r->rpool.cur != NULL)
- ifp = r->rpool.cur->kif ?
- r->rpool.cur->kif->pfik_ifp : NULL;
if (s != NULL) {
PF_STATE_UNLOCK(s);
}
@@ -8013,35 +7999,17 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
dst.sin6_len = sizeof(dst);
dst.sin6_addr = ip6->ip6_dst;
PF_ACPY((struct pf_addr *)&dst.sin6_addr, &pd->act.rt_addr, AF_INET6);
- bzero(&naddr, sizeof(naddr));
- ifp = pd->act.rt_kif ? pd->act.rt_kif->pfik_ifp : NULL;
if (s != NULL) {
- struct pfi_kkif *kif;
-
- kif = pd->act.rt_kif;
- /* If pfsync'd from FreeBSD < 14 */
- if (ifp == NULL && r->rpool.cur != NULL) {
- ifp = r->rpool.cur->kif ?
- r->rpool.cur->kif->pfik_ifp : NULL;
- kif = r->rpool.cur->kif;
- }
- if (ifp != NULL && kif != NULL &&
- r->rule_flag & PFRULE_IFBOUND &&
+ if (r->rule_flag & PFRULE_IFBOUND &&
pd->act.rt == PF_REPLYTO &&
s->kif == V_pfi_all) {
- s->kif = kif;
+ s->kif = pd->act.rt_kif;
s->orig_kif = oifp->if_pf_kif;
}
-
PF_STATE_UNLOCK(s);
}
- if (ifp == NULL) {
- SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
- goto bad;
- }
-
if (pd->dir == PF_IN) {
if (pf_test(AF_INET6, PF_OUT, PFIL_FWD | PF_PFIL_NOREFRAGMENT,
ifp, &m0, inp, &pd->act) != PF_PASS) {
diff --git a/tests/sys/netpfil/pf/pfsync.sh b/tests/sys/netpfil/pf/pfsync.sh
index b97b06c5f9a2..0f3505460b50 100644
--- a/tests/sys/netpfil/pf/pfsync.sh
+++ b/tests/sys/netpfil/pf/pfsync.sh
@@ -835,16 +835,11 @@ basic_ipv6_cleanup()
pfsynct_cleanup
}
-atf_test_case "route_to" "cleanup"
-route_to_head()
+route_to_common_head()
{
- atf_set descr 'Test route-to with default rule'
- atf_set require.user root
- atf_set require.progs scapy
-}
+ pfsync_version=$1
+ shift
-route_to_body()
-{
pfsynct_init
epair_sync=$(vnet_mkepair)
@@ -866,40 +861,111 @@ route_to_body()
jexec one ifconfig pfsync0 \
syncdev ${epair_sync}a \
maxupd 1 \
+ version $pfsync_version \
up
jexec two ifconfig ${epair_sync}b 192.0.2.2/24 up
jexec two ifconfig ${epair_two}a 198.51.100.2/24 up
jexec two ifconfig ${epair_out_two}a 203.0.113.2/24 up
- #jexec two ifconfig ${epair_out_two}a name outif
+ jexec two ifconfig ${epair_out_two}a name outif
jexec two sysctl net.inet.ip.forwarding=1
jexec two arp -s 203.0.113.254 00:01:02:03:04:05
jexec two ifconfig pfsync0 \
syncdev ${epair_sync}b \
maxupd 1 \
+ version $pfsync_version \
up
- # Enable pf!
+ ifconfig ${epair_one}b 198.51.100.254/24 up
+ ifconfig ${epair_two}b 198.51.100.253/24 up
+ route add -net 203.0.113.0/24 198.51.100.1
+ ifconfig ${epair_two}b up
+ ifconfig ${epair_out_one}b up
+ ifconfig ${epair_out_two}b up
+}
+
+route_to_common_tail()
+{
+ atf_check -s exit:0 env PYTHONPATH=${common_dir} \
+ ${common_dir}/pft_ping.py \
+ --sendif ${epair_one}b \
+ --fromaddr 198.51.100.254 \
+ --to 203.0.113.254 \
+ --recvif ${epair_out_one}b
+
+ # Allow time for sync
+ sleep 2
+
+ states_one=$(mktemp)
+ states_two=$(mktemp)
+ jexec one pfctl -qvvss | normalize_pfctl_s > $states_one
+ jexec two pfctl -qvvss | normalize_pfctl_s > $states_two
+}
+
+atf_test_case "route_to_1301_body" "cleanup"
+route_to_1301_head()
+{
+ atf_set descr 'Test route-to with pfsync version 13.1'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+route_to_1301_body()
+{
+ route_to_common_head 1301
+
jexec one pfctl -e
pft_set_rules one \
"set skip on ${epair_sync}a" \
"pass out route-to (outif 203.0.113.254)"
+
jexec two pfctl -e
+ pft_set_rules two \
+ "set skip on ${epair_sync}b" \
+ "pass out route-to (outif 203.0.113.254)"
+
+ route_to_common_tail
- # Make sure we have different rulesets so the synced state is associated with
- # V_pf_default_rule
+ # Sanity check
+ grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*, rule 0 .* route-to: 203.0.113.254@outif origif: outif' $states_one ||
+ atf_fail "State missing on router one"
+
+ # With identical ruleset the routing information is recovered from the matching rule.
+ grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*, rule 0 .* route-to: 203.0.113.254@outif' $states_two ||
+ atf_fail "State missing on router two"
+
+ true
+}
+
+route_to_1301_cleanup()
+{
+ pfsynct_cleanup
+}
+
+atf_test_case "route_to_1301_bad_ruleset" "cleanup"
+route_to_1301_bad_ruleset_head()
+{
+ atf_set descr 'Test route-to with pfsync version 13.1 and incompatible ruleset'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+route_to_1301_bad_ruleset_body()
+{
+ route_to_common_head 1301
+
+ jexec one pfctl -e
+ pft_set_rules one \
+ "set skip on ${epair_sync}a" \
+ "pass out route-to (outif 203.0.113.254)"
+
+ jexec two pfctl -e
pft_set_rules two \
+ "set debug loud" \
"set skip on ${epair_sync}b" \
"pass out route-to (outif 203.0.113.254)" \
"pass out proto tcp"
- ifconfig ${epair_one}b 198.51.100.254/24 up
- ifconfig ${epair_two}b 198.51.100.253/24 up
- route add -net 203.0.113.0/24 198.51.100.1
- ifconfig ${epair_two}b up
- ifconfig ${epair_out_one}b up
- ifconfig ${epair_out_two}b up
-
atf_check -s exit:0 env PYTHONPATH=${common_dir} \
${common_dir}/pft_ping.py \
--sendif ${epair_one}b \
@@ -907,25 +973,151 @@ route_to_body()
--to 203.0.113.254 \
--recvif ${epair_out_one}b
- # Allow time for sync
- ifconfig ${epair_one}b inet 198.51.100.254 -alias
- route del -net 203.0.113.0/24 198.51.100.1
- route add -net 203.0.113.0/24 198.51.100.2
+ route_to_common_tail
- sleep 2
+ # Sanity check
+ grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*, rule 0 .* route-to: 203.0.113.254@outif origif: outif' $states_one ||
+ atf_fail "State missing on router one"
- # Now try to trigger the state on the other pfsync member
- env PYTHONPATH=${common_dir} \
+ # Different ruleset on each router means the routing information recovery
+ # from rule is impossible. The state is not synced.
+ grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*' $states_two &&
+ atf_fail "State present on router two"
+
+ true
+}
+
+route_to_1301_bad_ruleset_cleanup()
+{
+ pfsynct_cleanup
+}
+
+atf_test_case "route_to_1301_bad_rpool" "cleanup"
+route_to_1301_bad_rpool_head()
+{
+ atf_set descr 'Test route-to with pfsync version 13.1 and different interface'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+route_to_1301_bad_rpool_body()
+{
+ route_to_common_head 1301
+
+ jexec one pfctl -e
+ pft_set_rules one \
+ "set skip on ${epair_sync}a" \
+ "pass out route-to { (outif 203.0.113.254) (outif 203.0.113.254) }"
+
+ jexec two pfctl -e
+ pft_set_rules two \
+ "set skip on ${epair_sync}b" \
+ "pass out route-to { (outif 203.0.113.254) (outif 203.0.113.254) }"
+
+ atf_check -s exit:0 env PYTHONPATH=${common_dir} \
${common_dir}/pft_ping.py \
- --sendif ${epair_two}b \
+ --sendif ${epair_one}b \
--fromaddr 198.51.100.254 \
--to 203.0.113.254 \
- --recvif ${epair_out_two}b
+ --recvif ${epair_out_one}b
+
+ route_to_common_tail
+
+ # Sanity check
+ grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*, rule 0 .* route-to: 203.0.113.254@outif origif: outif' $states_one ||
+ atf_fail "State missing on router one"
+
+ # The ruleset is identical but since the redirection pool contains multiple interfaces
+ # pfsync will not attempt to recover the routing information from the rule.
+ grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*' $states_two &&
+ atf_fail "State present on router two"
+
+ true
+}
+
+route_to_1301_bad_rpool_cleanup()
+{
+ pfsynct_cleanup
+}
+
+atf_test_case "route_to_1400_bad_ruleset" "cleanup"
+route_to_1400_bad_ruleset_head()
+{
+ atf_set descr 'Test route-to with pfsync version 14.0'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+route_to_1400_bad_ruleset_body()
+{
+ route_to_common_head 1400
+
+ jexec one pfctl -e
+ pft_set_rules one \
+ "set skip on ${epair_sync}a" \
+ "pass out route-to (outif 203.0.113.254)"
+
+ jexec two pfctl -e
+ pft_set_rules two \
+ "set skip on ${epair_sync}b"
+
+ route_to_common_tail
+
+ # Sanity check
+ grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*, rule 0 .* route-to: 203.0.113.254@outif origif: outif' $states_one ||
+ atf_fail "State missing on router one"
+
+ # Even with a different ruleset FreeBSD 14 syncs the state just fine.
+ # There's no recovery involved, the pfsync packet contains the routing information.
+ grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .* route-to: 203.0.113.254@outif' $states_two ||
+ atf_fail "State missing on router two"
+
+ true
+}
+
+route_to_1400_bad_ruleset_cleanup()
+{
+ pfsynct_cleanup
+}
+
+atf_test_case "route_to_1400_bad_ifname" "cleanup"
+route_to_1400_bad_ifname_head()
+{
+ atf_set descr 'Test route-to with pfsync version 14.0'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+route_to_1400_bad_ifname_body()
+{
+ route_to_common_head 1400
+
+ jexec one pfctl -e
+ pft_set_rules one \
+ "set skip on ${epair_sync}a" \
+ "pass out route-to (outif 203.0.113.254)"
+
+ jexec two pfctl -e
+ jexec two ifconfig outif name outif_new
+ pft_set_rules two \
+ "set skip on ${epair_sync}b" \
+ "pass out route-to (outif_new 203.0.113.254)"
+
+ route_to_common_tail
+
+ # Sanity check
+ grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*, rule 0 .* route-to: 203.0.113.254@outif origif: outif' $states_one ||
+ atf_fail "State missing on router one"
+
+ # Since FreeBSD 14 never attempts recovery of missing routing information
+ # a state synced to a router with a different interface name is dropped.
+ grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*' $states_two &&
+ atf_fail "State present on router two"
true
}
-route_to_cleanup()
+route_to_1400_bad_ifname_cleanup()
{
pfsynct_cleanup
}
@@ -942,5 +1134,9 @@ atf_init_test_cases()
atf_add_test_case "timeout"
atf_add_test_case "basic_ipv6_unicast"
atf_add_test_case "basic_ipv6"
- atf_add_test_case "route_to"
+ atf_add_test_case "route_to_1301"
+ atf_add_test_case "route_to_1301_bad_ruleset"
+ atf_add_test_case "route_to_1301_bad_rpool"
+ atf_add_test_case "route_to_1400_bad_ruleset"
+ atf_add_test_case "route_to_1400_bad_ifname"
}