aboutsummaryrefslogtreecommitdiff
path: root/tests/sys/net
diff options
context:
space:
mode:
Diffstat (limited to 'tests/sys/net')
-rw-r--r--tests/sys/net/Makefile47
-rw-r--r--tests/sys/net/bpf/Makefile15
-rw-r--r--tests/sys/net/bpf/bpf.sh67
-rw-r--r--tests/sys/net/bpf/bpf_multi_read.c76
-rw-r--r--tests/sys/net/dhclient_pcp.conf1
-rwxr-xr-xtests/sys/net/if_bridge_test.sh1481
-rwxr-xr-xtests/sys/net/if_clone_test.sh574
-rw-r--r--tests/sys/net/if_epair.c75
-rw-r--r--tests/sys/net/if_epair_test.sh65
-rw-r--r--tests/sys/net/if_gif.sh365
-rwxr-xr-xtests/sys/net/if_lagg_test.sh463
-rw-r--r--tests/sys/net/if_ovpn/Makefile30
-rw-r--r--tests/sys/net/if_ovpn/ca.crt33
-rw-r--r--tests/sys/net/if_ovpn/ca.key51
-rw-r--r--tests/sys/net/if_ovpn/ccd/Makefile8
-rw-r--r--tests/sys/net/if_ovpn/ccd/Test-Client22
-rw-r--r--tests/sys/net/if_ovpn/client.crt123
-rw-r--r--tests/sys/net/if_ovpn/client.key51
-rw-r--r--tests/sys/net/if_ovpn/client2.crt32
-rw-r--r--tests/sys/net/if_ovpn/client2.key51
-rw-r--r--tests/sys/net/if_ovpn/dh.pem8
-rw-r--r--tests/sys/net/if_ovpn/if_ovpn.sh1504
-rw-r--r--tests/sys/net/if_ovpn/if_ovpn_c.c132
-rw-r--r--tests/sys/net/if_ovpn/server.crt123
-rw-r--r--tests/sys/net/if_ovpn/server.key51
-rw-r--r--tests/sys/net/if_ovpn/user.pass2
-rw-r--r--tests/sys/net/if_ovpn/utils.subr92
-rw-r--r--tests/sys/net/if_stf.sh203
-rwxr-xr-xtests/sys/net/if_tun_test.sh85
-rwxr-xr-xtests/sys/net/if_vlan.sh373
-rw-r--r--tests/sys/net/if_wg.sh633
-rw-r--r--tests/sys/net/pcp.py74
-rw-r--r--tests/sys/net/randsleep.c61
-rw-r--r--tests/sys/net/routing/Makefile21
-rwxr-xr-xtests/sys/net/routing/generic_cleanup.sh35
-rw-r--r--tests/sys/net/routing/params.h36
-rw-r--r--tests/sys/net/routing/rtsock_common.h882
-rw-r--r--tests/sys/net/routing/rtsock_config.h175
-rw-r--r--tests/sys/net/routing/rtsock_print.h412
-rwxr-xr-xtests/sys/net/routing/test_routing_l3.py120
-rw-r--r--tests/sys/net/routing/test_rtsock_l3.c1421
-rw-r--r--tests/sys/net/routing/test_rtsock_lladdr.c416
-rwxr-xr-xtests/sys/net/routing/test_rtsock_multipath.py271
-rw-r--r--tests/sys/net/routing/test_rtsock_ops.c53
-rw-r--r--tests/sys/net/stp.py114
-rw-r--r--tests/sys/net/transient_tuntap.c54
46 files changed, 10961 insertions, 0 deletions
diff --git a/tests/sys/net/Makefile b/tests/sys/net/Makefile
new file mode 100644
index 000000000000..e390c6e8059d
--- /dev/null
+++ b/tests/sys/net/Makefile
@@ -0,0 +1,47 @@
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/net
+BINDIR= ${TESTSDIR}
+
+ATF_TESTS_C+= if_epair
+ATF_TESTS_SH+= if_epair_test
+ATF_TESTS_SH+= if_bridge_test
+TEST_METADATA.if_bridge_test+= execenv="jail"
+TEST_METADATA.if_bridge_test+= execenv_jail_params="vnet allow.raw_sockets"
+ATF_TESTS_SH+= if_clone_test
+ATF_TESTS_SH+= if_gif
+ATF_TESTS_SH+= if_lagg_test
+ATF_TESTS_SH+= if_stf
+ATF_TESTS_SH+= if_tun_test
+ATF_TESTS_SH+= if_vlan
+ATF_TESTS_SH+= if_wg
+
+TESTS_SUBDIRS+= bpf
+TESTS_SUBDIRS+= if_ovpn
+TESTS_SUBDIRS+= routing
+
+# The netmap bridge application is used by if_wg tests.
+.PATH: ${SRCTOP}/tools/tools/netmap
+PROGS+= bridge
+LIBADD.bridge+= netmap
+
+# The tests are written to be run in parallel, but doing so leads to random
+# panics. I think it's because the kernel's list of interfaces isn't properly
+# locked.
+TEST_METADATA+= is_exclusive=true
+
+${PACKAGE}FILES+= \
+ dhclient_pcp.conf \
+ pcp.py \
+ stp.py
+
+${PACKAGE}FILESMODE_pcp.py= 0555
+${PACKAGE}FILESMODE_stp.py= 0555
+
+MAN=
+PROGS+= randsleep
+PROGS+= transient_tuntap
+
+CFLAGS+= -I${.CURDIR:H:H}
+
+.include <bsd.test.mk>
diff --git a/tests/sys/net/bpf/Makefile b/tests/sys/net/bpf/Makefile
new file mode 100644
index 000000000000..9c8a25b15d16
--- /dev/null
+++ b/tests/sys/net/bpf/Makefile
@@ -0,0 +1,15 @@
+.include <src.opts.mk>
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/net/bpf
+BINDIR= ${TESTSDIR}
+
+LIBADD+= nv
+
+PROGS= bpf_multi_read
+LIBADD.bpf_multi_read+= pcap
+
+ATF_TESTS_SH= bpf
+
+.include <bsd.test.mk>
diff --git a/tests/sys/net/bpf/bpf.sh b/tests/sys/net/bpf/bpf.sh
new file mode 100644
index 000000000000..2830c4862de9
--- /dev/null
+++ b/tests/sys/net/bpf/bpf.sh
@@ -0,0 +1,67 @@
+##
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2025 Rubicon Communications, LLC ("Netgate")
+#
+# 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.
+
+. $(atf_get_srcdir)/../../common/vnet.subr
+
+atf_test_case "multi_read" "cleanup"
+multi_read_head()
+{
+ atf_set descr 'Test multiple readers on /dev/bpf'
+ atf_set require.user root
+}
+
+multi_read_body()
+{
+ vnet_init
+
+ epair=$(vnet_mkepair)
+ ifconfig ${epair}a inet 192.0.2.1/24 up
+
+ vnet_mkjail alcatraz ${epair}b
+ jexec alcatraz ifconfig ${epair}b inet 192.0.2.2/24 up
+
+ atf_check -s exit:0 -o ignore \
+ ping -c 1 192.0.2.2
+
+ # Start a multi-thread (or multi-process) read on bpf
+ $(atf_get_srcdir)/bpf_multi_read ${epair}a &
+
+ # Generate traffic
+ ping -f 192.0.2.2 >/dev/null 2>&1 &
+
+ # Now let this run for 10 seconds
+ sleep 10
+}
+
+multi_read_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "multi_read"
+}
diff --git a/tests/sys/net/bpf/bpf_multi_read.c b/tests/sys/net/bpf/bpf_multi_read.c
new file mode 100644
index 000000000000..3a8edd76d623
--- /dev/null
+++ b/tests/sys/net/bpf/bpf_multi_read.c
@@ -0,0 +1,76 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Rubicon Communications, LLC (Netgate)
+ *
+ * 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.
+ *
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <pcap.h>
+#include <unistd.h>
+
+static void
+callback(u_char *arg __unused, const struct pcap_pkthdr *hdr __unused,
+ const unsigned char *bytes __unused)
+{
+}
+
+int
+main(int argc, const char **argv)
+{
+ pcap_t *pcap;
+ const char *interface;
+ char errbuf[PCAP_ERRBUF_SIZE] = { 0 };
+ int ret;
+
+ if (argc != 2)
+ err(1, "Usage: %s <interface>\n", argv[0]);
+
+ interface = argv[1];
+
+ pcap = pcap_create(interface, errbuf);
+ if (! pcap)
+ perror("Failed to pcap interface");
+
+ ret = pcap_set_snaplen(pcap, 86);
+ if (ret != 0)
+ perror("Failed to set snaplen");
+
+ ret = pcap_set_timeout(pcap, 100);
+ if (ret != 0)
+ perror("Failed to set timeout");
+
+ ret = pcap_activate(pcap);
+ if (ret != 0)
+ perror("Failed to activate");
+
+ /* So we have two readers on one /dev/bpf fd */
+ fork();
+
+ printf("Interface open\n");
+ pcap_loop(pcap, 0, callback, NULL);
+
+ return (0);
+}
diff --git a/tests/sys/net/dhclient_pcp.conf b/tests/sys/net/dhclient_pcp.conf
new file mode 100644
index 000000000000..fbd86e5bd0f8
--- /dev/null
+++ b/tests/sys/net/dhclient_pcp.conf
@@ -0,0 +1 @@
+vlan-pcp 6;
diff --git a/tests/sys/net/if_bridge_test.sh b/tests/sys/net/if_bridge_test.sh
new file mode 100755
index 000000000000..b3405fd978c8
--- /dev/null
+++ b/tests/sys/net/if_bridge_test.sh
@@ -0,0 +1,1481 @@
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2020 The FreeBSD Foundation
+#
+# This software was developed by Kristof Provost under sponsorship
+# from the FreeBSD Foundation.
+#
+# 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.
+
+. $(atf_get_srcdir)/../common/vnet.subr
+
+atf_test_case "bridge_transmit_ipv4_unicast" "cleanup"
+bridge_transmit_ipv4_unicast_head()
+{
+ atf_set descr 'bridge_transmit_ipv4_unicast bridging test'
+ atf_set require.user root
+}
+
+bridge_transmit_ipv4_unicast_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epair_alcatraz=$(vnet_mkepair)
+ epair_singsing=$(vnet_mkepair)
+
+ vnet_mkjail alcatraz ${epair_alcatraz}b
+ vnet_mkjail singsing ${epair_singsing}b
+
+ jexec alcatraz ifconfig ${epair_alcatraz}b 192.0.2.1/24 up
+ jexec singsing ifconfig ${epair_singsing}b 192.0.2.2/24 up
+
+ bridge=$(vnet_mkbridge)
+
+ ifconfig ${bridge} up
+ ifconfig ${epair_alcatraz}a up
+ ifconfig ${epair_singsing}a up
+ ifconfig ${bridge} addm ${epair_alcatraz}a
+ ifconfig ${bridge} addm ${epair_singsing}a
+
+ atf_check -s exit:0 -o ignore jexec alcatraz ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:0 -o ignore jexec singsing ping -c 3 -t 1 192.0.2.1
+}
+
+bridge_transmit_ipv4_unicast_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "stp" "cleanup"
+stp_head()
+{
+ atf_set descr 'Spanning tree test'
+ atf_set require.user root
+}
+
+stp_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epair_one=$(vnet_mkepair)
+ epair_two=$(vnet_mkepair)
+ bridge_a=$(vnet_mkbridge)
+ bridge_b=$(vnet_mkbridge)
+
+ vnet_mkjail a ${bridge_a} ${epair_one}a ${epair_two}a
+ vnet_mkjail b ${bridge_b} ${epair_one}b ${epair_two}b
+
+ jexec a ifconfig ${epair_one}a up
+ jexec a ifconfig ${epair_two}a up
+ jexec a ifconfig ${bridge_a} addm ${epair_one}a
+ jexec a ifconfig ${bridge_a} addm ${epair_two}a
+
+ jexec b ifconfig ${epair_one}b up
+ jexec b ifconfig ${epair_two}b up
+ jexec b ifconfig ${bridge_b} addm ${epair_one}b
+ jexec b ifconfig ${bridge_b} addm ${epair_two}b
+
+ jexec a ifconfig ${bridge_a} 192.0.2.1/24
+
+ # Enable spanning tree
+ jexec a ifconfig ${bridge_a} stp ${epair_one}a
+ jexec a ifconfig ${bridge_a} stp ${epair_two}a
+ jexec b ifconfig ${bridge_b} stp ${epair_one}b
+ jexec b ifconfig ${bridge_b} stp ${epair_two}b
+
+ jexec b ifconfig ${bridge_b} up
+ jexec a ifconfig ${bridge_a} up
+
+ # Give STP time to do its thing
+ sleep 5
+
+ a_discard=$(jexec a ifconfig ${bridge_a} | grep discarding)
+ b_discard=$(jexec b ifconfig ${bridge_b} | grep discarding)
+
+ if [ -z "${a_discard}" ] && [ -z "${b_discard}" ]
+ then
+ atf_fail "STP failed to detect bridging loop"
+ fi
+
+ # We must also have at least some forwarding interfaces
+ a_forwarding=$(jexec a ifconfig ${bridge_a} | grep forwarding)
+ b_forwarding=$(jexec b ifconfig ${bridge_b} | grep forwarding)
+
+ if [ -z "${a_forwarding}" ] && [ -z "${b_forwarding}" ]
+ then
+ atf_fail "STP failed to detect bridging loop"
+ fi
+}
+
+stp_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "stp_vlan" "cleanup"
+stp_vlan_head()
+{
+ atf_set descr 'Spanning tree on VLAN test'
+ atf_set require.user root
+}
+
+stp_vlan_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epair_one=$(vnet_mkepair)
+ epair_two=$(vnet_mkepair)
+ bridge_a=$(vnet_mkbridge)
+ bridge_b=$(vnet_mkbridge)
+
+ vnet_mkjail a ${bridge_a} ${epair_one}a ${epair_two}a
+ vnet_mkjail b ${bridge_b} ${epair_one}b ${epair_two}b
+
+ jexec a ifconfig ${epair_one}a up
+ jexec a ifconfig ${epair_two}a up
+ vlan_a_one=$(jexec a ifconfig vlan create vlandev ${epair_one}a vlan 42)
+ vlan_a_two=$(jexec a ifconfig vlan create vlandev ${epair_two}a vlan 42)
+ jexec a ifconfig ${vlan_a_one} up
+ jexec a ifconfig ${vlan_a_two} up
+ jexec a ifconfig ${bridge_a} addm ${vlan_a_one}
+ jexec a ifconfig ${bridge_a} addm ${vlan_a_two}
+
+ jexec b ifconfig ${epair_one}b up
+ jexec b ifconfig ${epair_two}b up
+ vlan_b_one=$(jexec b ifconfig vlan create vlandev ${epair_one}b vlan 42)
+ vlan_b_two=$(jexec b ifconfig vlan create vlandev ${epair_two}b vlan 42)
+ jexec b ifconfig ${vlan_b_one} up
+ jexec b ifconfig ${vlan_b_two} up
+ jexec b ifconfig ${bridge_b} addm ${vlan_b_one}
+ jexec b ifconfig ${bridge_b} addm ${vlan_b_two}
+
+ jexec a ifconfig ${bridge_a} 192.0.2.1/24
+
+ # Enable spanning tree
+ jexec a ifconfig ${bridge_a} stp ${vlan_a_one}
+ jexec a ifconfig ${bridge_a} stp ${vlan_a_two}
+ jexec b ifconfig ${bridge_b} stp ${vlan_b_one}
+ jexec b ifconfig ${bridge_b} stp ${vlan_b_two}
+
+ jexec b ifconfig ${bridge_b} up
+ jexec a ifconfig ${bridge_a} up
+
+ # Give STP time to do its thing
+ sleep 5
+
+ a_discard=$(jexec a ifconfig ${bridge_a} | grep discarding)
+ b_discard=$(jexec b ifconfig ${bridge_b} | grep discarding)
+
+ if [ -z "${a_discard}" ] && [ -z "${b_discard}" ]
+ then
+ atf_fail "STP failed to detect bridging loop"
+ fi
+
+ # We must also have at least some forwarding interfaces
+ a_forwarding=$(jexec a ifconfig ${bridge_a} | grep forwarding)
+ b_forwarding=$(jexec b ifconfig ${bridge_b} | grep forwarding)
+
+ if [ -z "${a_forwarding}" ] && [ -z "${b_forwarding}" ]
+ then
+ atf_fail "STP failed to detect bridging loop"
+ fi
+}
+
+stp_vlan_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "static" "cleanup"
+static_head()
+{
+ atf_set descr 'Bridge static address test'
+ atf_set require.user root
+}
+
+static_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epair=$(vnet_mkepair)
+ bridge=$(vnet_mkbridge)
+
+ vnet_mkjail one ${bridge} ${epair}a
+
+ ifconfig ${epair}b up
+
+ jexec one ifconfig ${bridge} up
+ jexec one ifconfig ${epair}a up
+ jexec one ifconfig ${bridge} addm ${epair}a
+
+ # Wrong interface
+ atf_check -s exit:1 -o ignore -e ignore \
+ jexec one ifconfig ${bridge} static ${epair}b 00:01:02:03:04:05
+
+ # Bad address format
+ atf_check -s exit:1 -o ignore -e ignore \
+ jexec one ifconfig ${bridge} static ${epair}a 00:01:02:03:04
+
+ # Correct add
+ atf_check -s exit:0 -o ignore \
+ jexec one ifconfig ${bridge} static ${epair}a 00:01:02:03:04:05
+
+ # List addresses
+ atf_check -s exit:0 \
+ -o match:"00:01:02:03:04:05 Vlan0 ${epair}a 0 flags=1<STATIC>" \
+ jexec one ifconfig ${bridge} addr
+
+ # Delete with bad address format
+ atf_check -s exit:1 -o ignore -e ignore \
+ jexec one ifconfig ${bridge} deladdr 00:01:02:03:04
+
+ # Delete with unlisted address
+ atf_check -s exit:1 -o ignore -e ignore \
+ jexec one ifconfig ${bridge} deladdr 00:01:02:03:04:06
+
+ # Correct delete
+ atf_check -s exit:0 -o ignore \
+ jexec one ifconfig ${bridge} deladdr 00:01:02:03:04:05
+}
+
+static_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "vstatic" "cleanup"
+vstatic_head()
+{
+ atf_set descr 'Bridge VLAN static address test'
+ atf_set require.user root
+}
+
+vstatic_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epair=$(vnet_mkepair)
+ bridge=$(vnet_mkbridge)
+
+ vnet_mkjail one ${bridge} ${epair}a
+
+ ifconfig ${epair}b up
+
+ jexec one ifconfig ${bridge} up
+ jexec one ifconfig ${epair}a up
+ jexec one ifconfig ${bridge} addm ${epair}a
+
+ # Wrong interface
+ atf_check -s exit:1 -o ignore -e ignore jexec one \
+ ifconfig ${bridge} static ${epair}b 00:01:02:03:04:05 vlan 10
+
+ # Bad address format
+ atf_check -s exit:1 -o ignore -e ignore jexec one \
+ ifconfig ${bridge} static ${epair}a 00:01:02:03:04 vlan 10
+
+ # Invalid VLAN ID
+ atf_check -s exit:1 -o ignore -e ignore jexec one \
+ ifconfig ${bridge} static ${epair}a 00:01:02:03:04:05 vlan 5000
+
+ # Correct add
+ atf_check -s exit:0 -o ignore jexec one \
+ ifconfig ${bridge} static ${epair}a 00:01:02:03:04:05 vlan 10
+
+ # List addresses
+ atf_check -s exit:0 \
+ -o match:"00:01:02:03:04:05 Vlan10 ${epair}a 0 flags=1<STATIC>" \
+ jexec one ifconfig ${bridge} addr
+
+ # Delete with bad address format
+ atf_check -s exit:1 -o ignore -e ignore jexec one \
+ ifconfig ${bridge} deladdr 00:01:02:03:04 vlan 10
+
+ # Delete with unlisted address
+ atf_check -s exit:1 -o ignore -e ignore jexec one \
+ ifconfig ${bridge} deladdr 00:01:02:03:04:06 vlan 10
+
+ # Delete with wrong vlan id
+ atf_check -s exit:1 -o ignore -e ignore jexec one \
+ ifconfig ${bridge} deladdr 00:01:02:03:04:05 vlan 20
+
+ # Correct delete
+ atf_check -s exit:0 -o ignore jexec one \
+ ifconfig ${bridge} deladdr 00:01:02:03:04:05 vlan 10
+}
+
+vstatic_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "span" "cleanup"
+span_head()
+{
+ atf_set descr 'Bridge span test'
+ atf_set require.user root
+ atf_set require.progs python3 scapy
+}
+
+span_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epair=$(vnet_mkepair)
+ epair_span=$(vnet_mkepair)
+ bridge=$(vnet_mkbridge)
+
+ vnet_mkjail one ${bridge} ${epair}a ${epair_span}a
+
+ ifconfig ${epair}b up
+ ifconfig ${epair_span}b up
+
+ jexec one ifconfig ${bridge} up
+ jexec one ifconfig ${epair}a up
+ jexec one ifconfig ${epair_span}a up
+ jexec one ifconfig ${bridge} addm ${epair}a
+
+ jexec one ifconfig ${bridge} span ${epair_span}a
+ jexec one ifconfig ${bridge} 192.0.2.1/24
+
+ # Send some traffic through the span
+ jexec one ping -c 1 -t 1 192.0.2.2
+
+ # Check that we see the traffic on the span interface
+ atf_check -s exit:0 \
+ $(atf_get_srcdir)/../netpfil/common/pft_ping.py \
+ --sendif ${epair}b \
+ --to 192.0.2.2 \
+ --recvif ${epair_span}b
+
+ jexec one ifconfig ${bridge} -span ${epair_span}a
+
+ # And no more traffic after we remove the span
+ atf_check -s exit:1 \
+ $(atf_get_srcdir)/../netpfil/common/pft_ping.py \
+ --sendif ${epair}b \
+ --to 192.0.2.2 \
+ --recvif ${epair_span}b
+}
+
+span_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "delete_with_members" "cleanup"
+delete_with_members_head()
+{
+ atf_set descr 'Delete a bridge which still has member interfaces'
+ atf_set require.user root
+}
+
+delete_with_members_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ bridge=$(vnet_mkbridge)
+ epair=$(vnet_mkepair)
+
+ ifconfig ${bridge} 192.0.2.1/24 up
+ ifconfig ${epair}a up
+ ifconfig ${bridge} addm ${epair}a
+
+ ifconfig ${bridge} destroy
+}
+
+delete_with_members_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "mac_conflict" "cleanup"
+mac_conflict_head()
+{
+ atf_set descr 'Ensure that bridges in different jails get different mac addresses'
+ atf_set require.user root
+}
+
+mac_conflict_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epair=$(vnet_mkepair)
+
+ # Ensure the bridge module is loaded so jails can use it.
+ tmpbridge=$(vnet_mkbridge)
+
+ vnet_mkjail bridge_mac_conflict_one ${epair}a
+ vnet_mkjail bridge_mac_conflict_two ${epair}b
+
+ jexec bridge_mac_conflict_one ifconfig bridge create
+ jexec bridge_mac_conflict_one ifconfig bridge0 192.0.2.1/24 up \
+ addm ${epair}a
+ jexec bridge_mac_conflict_one ifconfig ${epair}a up
+
+ jexec bridge_mac_conflict_two ifconfig bridge create
+ jexec bridge_mac_conflict_two ifconfig bridge0 192.0.2.2/24 up \
+ addm ${epair}b
+ jexec bridge_mac_conflict_two ifconfig ${epair}b up
+
+ atf_check -s exit:0 -o ignore \
+ jexec bridge_mac_conflict_one ping -c 3 192.0.2.2
+}
+
+mac_conflict_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "inherit_mac" "cleanup"
+inherit_mac_head()
+{
+ atf_set descr 'Bridge inherit_mac test, #216510'
+ atf_set require.user root
+}
+
+inherit_mac_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ bridge=$(vnet_mkbridge)
+ epair=$(vnet_mkepair)
+ vnet_mkjail one ${bridge} ${epair}a
+
+ jexec one sysctl net.link.bridge.inherit_mac=1
+
+ # Attempt to provoke the panic described in #216510
+ jexec one ifconfig ${bridge} 192.0.0.1/24 up
+ jexec one ifconfig ${bridge} addm ${epair}a
+}
+
+inherit_mac_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "stp_validation" "cleanup"
+stp_validation_head()
+{
+ atf_set descr 'Check STP validation'
+ atf_set require.user root
+ atf_set require.progs python3 scapy
+}
+
+stp_validation_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epair_one=$(vnet_mkepair)
+ epair_two=$(vnet_mkepair)
+ bridge=$(vnet_mkbridge)
+
+ ifconfig ${bridge} up
+ ifconfig ${bridge} addm ${epair_one}a addm ${epair_two}a
+ ifconfig ${bridge} stp ${epair_one}a stp ${epair_two}a
+
+ ifconfig ${epair_one}a up
+ ifconfig ${epair_one}b up
+ ifconfig ${epair_two}a up
+ ifconfig ${epair_two}b up
+
+ # Wait until the interfaces are no longer discarding
+ while ifconfig ${bridge} | grep 'state discarding' >/dev/null
+ do
+ sleep 1
+ done
+
+ # Now inject invalid STP BPDUs on epair_one and see if they're repeated
+ # on epair_two
+ atf_check -s exit:0 \
+ $(atf_get_srcdir)/stp.py \
+ --sendif ${epair_one}b \
+ --recvif ${epair_two}b
+}
+
+stp_validation_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "gif" "cleanup"
+gif_head()
+{
+ atf_set descr 'gif as a bridge member'
+ atf_set require.user root
+}
+
+gif_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epair=$(vnet_mkepair)
+
+ vnet_mkjail one ${epair}a
+ vnet_mkjail two ${epair}b
+
+ jexec one sysctl net.link.gif.max_nesting=2
+ jexec two sysctl net.link.gif.max_nesting=2
+
+ jexec one ifconfig ${epair}a 192.0.2.1/24 up
+ jexec two ifconfig ${epair}b 192.0.2.2/24 up
+
+ # Tunnel
+ gif_one=$(jexec one ifconfig gif create)
+ gif_two=$(jexec two ifconfig gif create)
+
+ jexec one ifconfig ${gif_one} tunnel 192.0.2.1 192.0.2.2
+ jexec one ifconfig ${gif_one} up
+ jexec two ifconfig ${gif_two} tunnel 192.0.2.2 192.0.2.1
+ jexec two ifconfig ${gif_two} up
+
+ bridge_one=$(jexec one ifconfig bridge create)
+ bridge_two=$(jexec two ifconfig bridge create)
+ jexec one ifconfig ${bridge_one} 198.51.100.1/24 up
+ jexec one ifconfig ${bridge_one} addm ${gif_one}
+ jexec two ifconfig ${bridge_two} 198.51.100.2/24 up
+ jexec two ifconfig ${bridge_two} addm ${gif_two}
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 192.0.2.2
+
+ # Test tunnel
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 198.51.100.2
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 -s 1200 198.51.100.2
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 -s 2000 198.51.100.2
+
+ # Higher MTU on the tunnel than on the underlying interface
+ jexec one ifconfig ${epair}a mtu 1000
+ jexec two ifconfig ${epair}b mtu 1000
+
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 -s 1200 198.51.100.2
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 -s 2000 198.51.100.2
+
+ # Assigning IP addresses on the gif tunneling interfaces
+ jexec one sysctl net.link.bridge.member_ifaddrs=1
+ atf_check -s exit:0 -o ignore \
+ jexec one ifconfig ${gif_one} 192.168.0.224/24 192.168.169.254
+ atf_check -s exit:0 -o ignore \
+ jexec one ifconfig ${gif_one} inet6 no_dad 2001:db8::1/64
+ jexec one ifconfig ${bridge_one} deletem ${gif_one}
+ atf_check -s exit:0 -o ignore \
+ jexec one ifconfig ${bridge_one} addm ${gif_one}
+
+ jexec two sysctl net.link.bridge.member_ifaddrs=0
+ atf_check -s exit:0 -o ignore \
+ jexec two ifconfig ${gif_two} 192.168.169.254/24 192.168.0.224
+ atf_check -s exit:0 -o ignore \
+ jexec two ifconfig ${gif_two} inet6 no_dad 2001:db8::2/64
+ jexec two ifconfig ${bridge_two} deletem ${gif_two}
+ atf_check -s exit:0 -o ignore \
+ jexec two ifconfig ${bridge_two} addm ${gif_two}
+}
+
+gif_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "mtu" "cleanup"
+mtu_head()
+{
+ atf_set descr 'Bridge MTU changes'
+ atf_set require.user root
+}
+
+get_mtu()
+{
+ intf=$1
+
+ ifconfig ${intf} | awk '$5 == "mtu" { print $6 }'
+}
+
+check_mtu()
+{
+ intf=$1
+ expected=$2
+
+ mtu=$(get_mtu $intf)
+ if [ "$mtu" -ne "$expected" ];
+ then
+ atf_fail "Expected MTU of $expected on $intf but found $mtu"
+ fi
+}
+
+mtu_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epair=$(vnet_mkepair)
+ gif=$(ifconfig gif create)
+ echo ${gif} >> created_interfaces.lst
+ bridge=$(vnet_mkbridge)
+
+ atf_check -s exit:0 \
+ ifconfig ${bridge} addm ${epair}a
+
+ ifconfig ${gif} mtu 1500
+ atf_check -s exit:0 \
+ ifconfig ${bridge} addm ${gif}
+
+ # Changing MTU changes it for all member interfaces
+ atf_check -s exit:0 \
+ ifconfig ${bridge} mtu 2000
+
+ check_mtu ${bridge} 2000
+ check_mtu ${gif} 2000
+ check_mtu ${epair}a 2000
+
+ # Rejected MTUs mean none of the MTUs change
+ atf_check -s exit:1 -e ignore \
+ ifconfig ${bridge} mtu 9000
+
+ check_mtu ${bridge} 2000
+ check_mtu ${gif} 2000
+ check_mtu ${epair}a 2000
+
+ # We're not allowed to change the MTU of a member interface
+ atf_check -s exit:1 -e ignore \
+ ifconfig ${epair}a mtu 1900
+ check_mtu ${epair}a 2000
+
+ # Test adding an interface with a different MTU
+ new_epair=$(vnet_mkepair)
+ check_mtu ${new_epair}a 1500
+ atf_check -s exit:0 -e ignore \
+ ifconfig ${bridge} addm ${new_epair}a
+
+ check_mtu ${bridge} 2000
+ check_mtu ${gif} 2000
+ check_mtu ${epair}a 2000
+ check_mtu ${new_epair}a 2000
+}
+
+mtu_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "vlan" "cleanup"
+vlan_head()
+{
+ atf_set descr 'Ensure the bridge takes vlan ID into account, PR#270559'
+ atf_set require.user root
+}
+
+vlan_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ vid=1
+
+ epaira=$(vnet_mkepair)
+ epairb=$(vnet_mkepair)
+
+ br=$(vnet_mkbridge)
+
+ vnet_mkjail one ${epaira}b
+ vnet_mkjail two ${epairb}b
+
+ ifconfig ${br} up
+ ifconfig ${epaira}a up
+ ifconfig ${epairb}a up
+ ifconfig ${br} addm ${epaira}a addm ${epairb}a
+
+ jexec one ifconfig ${epaira}b up
+ jexec one ifconfig ${epaira}b.${vid} create
+
+ jexec two ifconfig ${epairb}b up
+ jexec two ifconfig ${epairb}b.${vid} create
+
+ # Create a MAC address conflict between an untagged and tagged interface
+ jexec two ifconfig ${epairb}b.${vid} ether 02:05:6e:06:28:1a
+ jexec one ifconfig ${epaira}b ether 02:05:6e:06:28:1a
+ jexec one ifconfig ${epaira}b.${vid} ether 02:05:6e:06:28:1b
+
+ # Add ip address, will also populate $br's fowarding table, by ARP announcement
+ jexec one ifconfig ${epaira}b.${vid} 192.0.2.1/24 up
+ jexec two ifconfig ${epairb}b.${vid} 192.0.2.2/24 up
+
+ sleep 0.5
+
+ ifconfig ${br}
+ jexec one ifconfig
+ jexec two ifconfig
+ ifconfig ${br} addr
+
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 -t 1 192.0.2.2
+
+ # This will trigger a mac flap (by ARP announcement)
+ jexec one ifconfig ${epaira}b 192.0.2.1/24 up
+
+ sleep 0.5
+
+ ifconfig ${br} addr
+
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 -t 1 192.0.2.2
+}
+
+vlan_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "many_bridge_members" "cleanup"
+many_bridge_members_head()
+{
+ atf_set descr 'many_bridge_members ifconfig test'
+ atf_set require.user root
+}
+
+many_bridge_members_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ bridge=$(vnet_mkbridge)
+ ifcount=256
+ for _ in $(seq 1 $ifcount); do
+ epair=$(vnet_mkepair)
+ ifconfig "${bridge}" addm "${epair}"a
+ done
+
+ atf_check -s exit:0 -o inline:"$ifcount\n" \
+ sh -c "ifconfig ${bridge} | grep member: | wc -l | xargs"
+}
+
+many_bridge_members_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "member_ifaddrs_enabled" "cleanup"
+member_ifaddrs_enabled_head()
+{
+ atf_set descr 'bridge with member_ifaddrs=1'
+ atf_set require.user root
+}
+
+member_ifaddrs_enabled_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ ep=$(vnet_mkepair)
+ ifconfig ${ep}a inet 192.0.2.1/24 up
+
+ vnet_mkjail one ${ep}b
+ jexec one sysctl net.link.bridge.member_ifaddrs=1
+ jexec one ifconfig ${ep}b inet 192.0.2.2/24 up
+ jexec one ifconfig bridge0 create addm ${ep}b
+
+ atf_check -s exit:0 -o ignore ping -c3 -t1 192.0.2.2
+}
+
+member_ifaddrs_enabled_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "member_ifaddrs_disabled" "cleanup"
+member_ifaddrs_disabled_head()
+{
+ atf_set descr 'bridge with member_ifaddrs=0'
+ atf_set require.user root
+}
+
+member_ifaddrs_disabled_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ vnet_mkjail one
+ jexec one sysctl net.link.bridge.member_ifaddrs=0
+
+ bridge=$(jexec one ifconfig bridge create)
+
+ # adding an interface with an IPv4 address
+ ep=$(jexec one ifconfig epair create)
+ jexec one ifconfig ${ep} 192.0.2.1/32
+ atf_check -s exit:1 -e ignore jexec one ifconfig ${bridge} addm ${ep}
+
+ # adding an interface with an IPv6 address
+ ep=$(jexec one ifconfig epair create)
+ jexec one ifconfig ${ep} inet6 2001:db8::1/128
+ atf_check -s exit:1 -e ignore jexec one ifconfig ${bridge} addm ${ep}
+
+ # adding an interface with an IPv6 link-local address
+ ep=$(jexec one ifconfig epair create)
+ jexec one ifconfig ${ep} inet6 -ifdisabled auto_linklocal up
+ atf_check -s exit:1 -e ignore jexec one ifconfig ${bridge} addm ${ep}
+
+ # adding an IPv4 address to a member
+ ep=$(jexec one ifconfig epair create)
+ jexec one ifconfig ${bridge} addm ${ep}
+ atf_check -s exit:1 -e ignore jexec one ifconfig ${ep} inet 192.0.2.2/32
+
+ # adding an IPv6 address to a member
+ ep=$(jexec one ifconfig epair create)
+ jexec one ifconfig ${bridge} addm ${ep}
+ atf_check -s exit:1 -e ignore jexec one ifconfig ${ep} inet6 2001:db8::1/128
+}
+
+member_ifaddrs_disabled_cleanup()
+{
+ vnet_cleanup
+}
+
+#
+# Test kern/287150: when member_ifaddrs=0, and a physical interface which is in
+# a bridge also has a vlan(4) on it, tagged packets are not correctly passed to
+# vlan(4).
+atf_test_case "member_ifaddrs_vlan" "cleanup"
+member_ifaddrs_vlan_head()
+{
+ atf_set descr 'kern/287150: vlan and bridge on the same interface'
+ atf_set require.user root
+}
+
+member_ifaddrs_vlan_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epone=$(vnet_mkepair)
+ eptwo=$(vnet_mkepair)
+
+ # The first jail has an epair with an IP address on vlan 20.
+ vnet_mkjail one ${epone}a
+ atf_check -s exit:0 jexec one ifconfig ${epone}a up
+ atf_check -s exit:0 jexec one \
+ ifconfig ${epone}a.20 create inet 192.0.2.1/24 up
+
+ # The second jail has an epair with an IP address on vlan 20,
+ # which is also in a bridge.
+ vnet_mkjail two ${epone}b
+
+ jexec two ifconfig
+ atf_check -s exit:0 -o save:bridge jexec two ifconfig bridge create
+ bridge=$(cat bridge)
+ atf_check -s exit:0 jexec two ifconfig ${bridge} addm ${epone}b up
+
+ atf_check -s exit:0 -o ignore jexec two \
+ sysctl net.link.bridge.member_ifaddrs=0
+ atf_check -s exit:0 jexec two ifconfig ${epone}b up
+ atf_check -s exit:0 jexec two \
+ ifconfig ${epone}b.20 create inet 192.0.2.2/24 up
+
+ # Make sure the two jails can communicate over the vlan.
+ atf_check -s exit:0 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:0 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+}
+
+member_ifaddrs_vlan_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "vlan_pvid" "cleanup"
+vlan_pvid_head()
+{
+ atf_set descr 'bridge with two ports with pvid and vlanfilter set'
+ atf_set require.user root
+}
+
+vlan_pvid_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epone=$(vnet_mkepair)
+ eptwo=$(vnet_mkepair)
+
+ vnet_mkjail one ${epone}b
+ vnet_mkjail two ${eptwo}b
+
+ jexec one ifconfig ${epone}b 192.0.2.1/24 up
+ jexec two ifconfig ${eptwo}b 192.0.2.2/24 up
+
+ bridge=$(vnet_mkbridge)
+
+ ifconfig ${bridge} vlanfilter up
+ ifconfig ${epone}a up
+ ifconfig ${eptwo}a up
+ ifconfig ${bridge} addm ${epone}a untagged 20
+ ifconfig ${bridge} addm ${eptwo}a untagged 20
+
+ # With VLAN filtering enabled, traffic should be passed.
+ atf_check -s exit:0 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:0 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+
+ # Removed the untagged VLAN on one port; traffic should not be passed.
+ ifconfig ${bridge} -ifuntagged ${epone}a
+ atf_check -s exit:2 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:2 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+}
+
+vlan_pvid_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "vlan_pvid_filtered" "cleanup"
+vlan_pvid_filtered_head()
+{
+ atf_set descr 'bridge with two ports with different pvids'
+ atf_set require.user root
+}
+
+vlan_pvid_filtered_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epone=$(vnet_mkepair)
+ eptwo=$(vnet_mkepair)
+
+ vnet_mkjail one ${epone}b
+ vnet_mkjail two ${eptwo}b
+
+ atf_check -s exit:0 jexec one ifconfig ${epone}b 192.0.2.1/24 up
+ atf_check -s exit:0 jexec two ifconfig ${eptwo}b 192.0.2.2/24 up
+
+ bridge=$(vnet_mkbridge)
+
+ atf_check -s exit:0 ifconfig ${bridge} vlanfilter up
+ atf_check -s exit:0 ifconfig ${epone}a up
+ atf_check -s exit:0 ifconfig ${eptwo}a up
+ atf_check -s exit:0 ifconfig ${bridge} addm ${epone}a untagged 20
+ atf_check -s exit:0 ifconfig ${bridge} addm ${eptwo}a untagged 30
+
+ atf_check -s exit:2 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:2 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+}
+
+vlan_pvid_filtered_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "vlan_pvid_tagged" "cleanup"
+vlan_pvid_tagged_head()
+{
+ atf_set descr 'bridge pvid with tagged frames for pvid'
+ atf_set require.user root
+}
+
+vlan_pvid_tagged_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epone=$(vnet_mkepair)
+ eptwo=$(vnet_mkepair)
+
+ vnet_mkjail one ${epone}b
+ vnet_mkjail two ${eptwo}b
+
+ # Create two tagged interfaces on the appropriate VLANs
+ atf_check -s exit:0 jexec one ifconfig ${epone}b up
+ atf_check -s exit:0 jexec one ifconfig ${epone}b.20 \
+ create 192.0.2.1/24 up
+ atf_check -s exit:0 jexec two ifconfig ${eptwo}b up
+ atf_check -s exit:0 jexec two ifconfig ${eptwo}b.20 \
+ create 192.0.2.2/24 up
+
+ bridge=$(vnet_mkbridge)
+
+ atf_check -s exit:0 ifconfig ${bridge} vlanfilter up
+ atf_check -s exit:0 ifconfig ${epone}a up
+ atf_check -s exit:0 ifconfig ${eptwo}a up
+ atf_check -s exit:0 ifconfig ${bridge} addm ${epone}a untagged 20
+ atf_check -s exit:0 ifconfig ${bridge} addm ${eptwo}a untagged 20
+
+ # Tagged frames should not be passed.
+ atf_check -s exit:2 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:2 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+}
+
+vlan_pvid_tagged_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "vlan_pvid_1q" "cleanup"
+vlan_pvid_1q_head()
+{
+ atf_set descr '802.1q tag addition and removal'
+ atf_set require.user root
+}
+
+vlan_pvid_1q_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epone=$(vnet_mkepair)
+ eptwo=$(vnet_mkepair)
+
+ vnet_mkjail one ${epone}b
+ vnet_mkjail two ${eptwo}b
+
+ # Set up one jail with an access port, and the other with a trunk port.
+ # This forces the bridge to add and remove .1q tags to bridge the
+ # traffic.
+
+ atf_check -s exit:0 jexec one ifconfig ${epone}b 192.0.2.1/24 up
+ atf_check -s exit:0 jexec two ifconfig ${eptwo}b up
+ atf_check -s exit:0 jexec two ifconfig ${eptwo}b.20 create 192.0.2.2/24 up
+
+ bridge=$(vnet_mkbridge)
+
+ atf_check -s exit:0 ifconfig ${bridge} vlanfilter up
+ atf_check -s exit:0 ifconfig ${bridge} addm ${epone}a untagged 20
+ atf_check -s exit:0 ifconfig ${bridge} addm ${eptwo}a tagged 20
+
+ atf_check -s exit:0 ifconfig ${epone}a up
+ atf_check -s exit:0 ifconfig ${eptwo}a up
+
+ atf_check -s exit:0 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:0 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+}
+
+vlan_pvid_1q_cleanup()
+{
+ vnet_cleanup
+}
+
+#
+# Test vlan filtering.
+#
+atf_test_case "vlan_filtering" "cleanup"
+vlan_filtering_head()
+{
+ atf_set descr 'tagged traffic with filtering'
+ atf_set require.user root
+}
+
+vlan_filtering_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epone=$(vnet_mkepair)
+ eptwo=$(vnet_mkepair)
+
+ vnet_mkjail one ${epone}b
+ vnet_mkjail two ${eptwo}b
+
+ atf_check -s exit:0 jexec one ifconfig ${epone}b up
+ atf_check -s exit:0 jexec one ifconfig ${epone}b.20 \
+ create 192.0.2.1/24 up
+ atf_check -s exit:0 jexec two ifconfig ${eptwo}b up
+ atf_check -s exit:0 jexec two ifconfig ${eptwo}b.20 \
+ create 192.0.2.2/24 up
+
+ bridge=$(vnet_mkbridge)
+
+ atf_check -s exit:0 ifconfig ${bridge} vlanfilter up
+ atf_check -s exit:0 ifconfig ${epone}a up
+ atf_check -s exit:0 ifconfig ${eptwo}a up
+ atf_check -s exit:0 ifconfig ${bridge} addm ${epone}a
+ atf_check -s exit:0 ifconfig ${bridge} addm ${eptwo}a
+
+ # Right now there are no VLANs on the access list, so everything
+ # should be blocked.
+ atf_check -s exit:2 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:2 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+
+ # Set the untagged vlan on both ports to 20 and make sure traffic is
+ # still blocked. We intentionally do not pass tagged traffic for the
+ # untagged vlan.
+ atf_check -s exit:0 ifconfig ${bridge} ifuntagged ${epone}a 20
+ atf_check -s exit:0 ifconfig ${bridge} ifuntagged ${eptwo}a 20
+
+ atf_check -s exit:2 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:2 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+
+ atf_check -s exit:0 ifconfig ${bridge} -ifuntagged ${epone}a
+ atf_check -s exit:0 ifconfig ${bridge} -ifuntagged ${eptwo}a
+
+ # Add VLANs 10-30 to the access list; now access should be allowed.
+ atf_check -s exit:0 ifconfig ${bridge} +iftagged ${epone}a 10-30
+ atf_check -s exit:0 ifconfig ${bridge} +iftagged ${eptwo}a 10-30
+ atf_check -s exit:0 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:0 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+
+ # Remove vlan 20 from the access list, now access should be blocked
+ # again.
+ atf_check -s exit:0 ifconfig ${bridge} -iftagged ${epone}a 20
+ atf_check -s exit:0 ifconfig ${bridge} -iftagged ${eptwo}a 20
+ atf_check -s exit:2 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:2 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+}
+
+vlan_filtering_cleanup()
+{
+ vnet_cleanup
+}
+
+#
+# Test the ifconfig 'iftagged' option.
+#
+atf_test_case "vlan_ifconfig_iftagged" "cleanup"
+vlan_ifconfig_iftagged_head()
+{
+ atf_set descr 'test the ifconfig iftagged option'
+ atf_set require.user root
+}
+
+vlan_ifconfig_iftagged_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ ep=$(vnet_mkepair)
+ bridge=$(vnet_mkbridge)
+ atf_check -s exit:0 ifconfig ${bridge} vlanfilter up
+
+ atf_check -s exit:0 ifconfig ${bridge} addm ${ep}a
+ atf_check -s exit:0 ifconfig ${ep}a up
+
+ # To start with, no vlans should be configured.
+ atf_check -s exit:0 -o not-match:"tagged" ifconfig ${bridge}
+
+ # Add vlans 100-149.
+ atf_check -s exit:0 ifconfig ${bridge} iftagged ${ep}a 100-149
+ atf_check -s exit:0 -o match:"tagged 100-149" ifconfig ${bridge}
+
+ # Replace the vlan list with 139-199.
+ atf_check -s exit:0 ifconfig ${bridge} iftagged ${ep}a 139-199
+ atf_check -s exit:0 -o match:"tagged 139-199" ifconfig ${bridge}
+
+ # Add vlans 100-170.
+ atf_check -s exit:0 ifconfig ${bridge} +iftagged ${ep}a 100-170
+ atf_check -s exit:0 -o match:"tagged 100-199" ifconfig ${bridge}
+
+ # Remove vlans 104, 105, and 150-159
+ atf_check -s exit:0 ifconfig ${bridge} -iftagged ${ep}a 104,105,150-159
+ atf_check -s exit:0 -o match:"tagged 100-103,106-149,160-199" \
+ ifconfig ${bridge}
+
+ # Remove the entire vlan list.
+ atf_check -s exit:0 ifconfig ${bridge} iftagged ${ep}a none
+ atf_check -s exit:0 -o not-match:"tagged" ifconfig ${bridge}
+
+ # Test some invalid vlans sets.
+ for bad_vlan in -1 0 4096 4097 foo 0-10 4000-5000 foo-40 40-foo; do
+ atf_check -s exit:1 -e ignore \
+ ifconfig ${bridge} iftagged "$bad_vlan"
+ done
+}
+
+vlan_ifconfig_iftagged_cleanup()
+{
+ vnet_cleanup
+}
+
+#
+# Test a vlan(4) "SVI" interface on top of a bridge.
+#
+atf_test_case "vlan_svi" "cleanup"
+vlan_svi_head()
+{
+ atf_set descr 'vlan bridge with an SVI'
+ atf_set require.user root
+}
+
+vlan_svi_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epone=$(vnet_mkepair)
+
+ vnet_mkjail one ${epone}b
+
+ atf_check -s exit:0 jexec one ifconfig ${epone}b up
+ atf_check -s exit:0 jexec one ifconfig ${epone}b.20 \
+ create 192.0.2.1/24 up
+
+ bridge=$(vnet_mkbridge)
+
+ atf_check -s exit:0 ifconfig ${bridge} vlanfilter up
+ atf_check -s exit:0 ifconfig ${epone}a up
+ atf_check -s exit:0 ifconfig ${bridge} addm ${epone}a tagged 20
+
+ svi=$(vnet_mkvlan)
+ atf_check -s exit:0 ifconfig ${svi} vlan 20 vlandev ${bridge}
+ atf_check -s exit:0 ifconfig ${svi} inet 192.0.2.2/24 up
+
+ atf_check -s exit:0 -o ignore ping -c 3 -t 1 192.0.2.1
+}
+
+vlan_svi_cleanup()
+{
+ vnet_cleanup
+}
+
+#
+# Test QinQ (802.1ad).
+#
+atf_test_case "vlan_qinq" "cleanup"
+vlan_qinq_head()
+{
+ atf_set descr 'vlan filtering with QinQ traffic'
+ atf_set require.user root
+}
+
+vlan_qinq_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epone=$(vnet_mkepair)
+ eptwo=$(vnet_mkepair)
+
+ vnet_mkjail one ${epone}b
+ vnet_mkjail two ${eptwo}b
+
+ # Create a QinQ trunk between the two jails. The outer (provider) tag
+ # is 5, and the inner tag is 10.
+
+ atf_check -s exit:0 jexec one ifconfig ${epone}b up
+ atf_check -s exit:0 jexec one \
+ ifconfig ${epone}b.5 create vlanproto 802.1ad up
+ atf_check -s exit:0 jexec one \
+ ifconfig ${epone}b.5.10 create inet 192.0.2.1/24 up
+
+ atf_check -s exit:0 jexec two ifconfig ${eptwo}b up
+ atf_check -s exit:0 jexec two ifconfig \
+ ${eptwo}b.5 create vlanproto 802.1ad up
+ atf_check -s exit:0 jexec two ifconfig \
+ ${eptwo}b.5.10 create inet 192.0.2.2/24 up
+
+ bridge=$(vnet_mkbridge)
+
+ atf_check -s exit:0 ifconfig ${bridge} vlanfilter defqinq up
+ atf_check -s exit:0 ifconfig ${epone}a up
+ atf_check -s exit:0 ifconfig ${eptwo}a up
+ atf_check -s exit:0 ifconfig ${bridge} addm ${epone}a
+ atf_check -s exit:0 ifconfig ${bridge} addm ${eptwo}a
+
+ # Right now there are no VLANs on the access list, so everything
+ # should be blocked.
+ atf_check -s exit:2 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:2 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+
+ # Add the provider tag to the access list; now traffic should be passed.
+ atf_check -s exit:0 ifconfig ${bridge} +iftagged ${epone}a 5
+ atf_check -s exit:0 ifconfig ${bridge} +iftagged ${eptwo}a 5
+ atf_check -s exit:0 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:0 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+
+ # Remove the qinq flag from one of the interfaces; traffic should
+ # be blocked again.
+ atf_check -s exit:0 ifconfig ${bridge} -qinq ${epone}a
+ atf_check -s exit:2 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:2 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+}
+
+vlan_qinq_cleanup()
+{
+ vnet_cleanup
+}
+
+# Adding a bridge SVI to a bridge should not be allowed.
+atf_test_case "bridge_svi_in_bridge" "cleanup"
+bridge_svi_in_bridge_head()
+{
+ atf_set descr 'adding a bridge SVI to a bridge is not allowed (1)'
+ atf_set require.user root
+}
+
+bridge_svi_in_bridge_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ bridge=$(vnet_mkbridge)
+ atf_check -s exit:0 ifconfig ${bridge}.1 create
+ atf_check -s exit:1 -e ignore ifconfig ${bridge} addm ${bridge}.1
+}
+
+bridge_svi_in_bridge_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "vlan_untagged" "cleanup"
+vlan_untagged_head()
+{
+ atf_set descr 'bridge with two ports with untagged set'
+ atf_set require.user root
+}
+
+vlan_untagged_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epone=$(vnet_mkepair)
+ eptwo=$(vnet_mkepair)
+
+ vnet_mkjail one ${epone}b
+ vnet_mkjail two ${eptwo}b
+
+ jexec one ifconfig ${epone}b 192.0.2.1/24 up
+ jexec two ifconfig ${eptwo}b 192.0.2.2/24 up
+
+ bridge=$(vnet_mkbridge)
+
+ ifconfig ${bridge} up
+ ifconfig ${epone}a up
+ ifconfig ${eptwo}a up
+ ifconfig ${bridge} addm ${epone}a untagged 20
+ ifconfig ${bridge} addm ${eptwo}a untagged 30
+
+ # With two ports on different VLANs, traffic should not be passed.
+ atf_check -s exit:2 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:2 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+
+ # Move the second port to VLAN 20; now traffic should be passed.
+ atf_check -s exit:0 ifconfig ${bridge} ifuntagged ${eptwo}a 20
+ atf_check -s exit:0 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:0 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+
+ # Remove the first's port untagged config, now traffic should
+ # not pass again.
+ atf_check -s exit:0 ifconfig ${bridge} -ifuntagged ${epone}a
+ atf_check -s exit:2 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:2 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+}
+
+vlan_untagged_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "vlan_defuntagged" "cleanup"
+vlan_defuntagged_head()
+{
+ atf_set descr 'defuntagged (defpvid) bridge option'
+ atf_set require.user root
+}
+
+vlan_defuntagged_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ bridge=$(vnet_mkbridge)
+
+ # Invalid VLAN IDs
+ atf_check -s exit:1 -ematch:"invalid vlan id: 0" \
+ ifconfig ${bridge} defuntagged 0
+ atf_check -s exit:1 -ematch:"invalid vlan id: 4095" \
+ ifconfig ${bridge} defuntagged 4095
+ atf_check -s exit:1 -ematch:"invalid vlan id: 5000" \
+ ifconfig ${bridge} defuntagged 5000
+
+ # Check the bridge option is set and cleared correctly
+ atf_check -s exit:0 -onot-match:"defuntagged=" \
+ ifconfig ${bridge}
+
+ atf_check -s exit:0 ifconfig ${bridge} defuntagged 10
+ atf_check -s exit:0 -omatch:"defuntagged=10$" \
+ ifconfig ${bridge}
+
+ atf_check -s exit:0 ifconfig ${bridge} -defuntagged
+ atf_check -s exit:0 -onot-match:"defuntagged=" \
+ ifconfig ${bridge}
+
+ # Check the untagged option is correctly set on a member
+ atf_check -s exit:0 ifconfig ${bridge} defuntagged 10
+
+ epair=$(vnet_mkepair)
+ atf_check -s exit:0 ifconfig ${bridge} addm ${epair}a
+
+ tag=$(ifconfig ${bridge} | sed -Ene \
+ "/member: ${epair}a/ { N;s/.*untagged ([0-9]+).*/\\1/p;q; }")
+ if [ "$tag" != "10" ]; then
+ atf_fail "wrong untagged vlan: ${tag}"
+ fi
+}
+
+vlan_defuntagged_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "bridge_transmit_ipv4_unicast"
+ atf_add_test_case "stp"
+ atf_add_test_case "stp_vlan"
+ atf_add_test_case "static"
+ atf_add_test_case "vstatic"
+ atf_add_test_case "span"
+ atf_add_test_case "inherit_mac"
+ atf_add_test_case "delete_with_members"
+ atf_add_test_case "mac_conflict"
+ atf_add_test_case "stp_validation"
+ atf_add_test_case "gif"
+ atf_add_test_case "mtu"
+ atf_add_test_case "vlan"
+ atf_add_test_case "many_bridge_members"
+ atf_add_test_case "member_ifaddrs_enabled"
+ atf_add_test_case "member_ifaddrs_disabled"
+ atf_add_test_case "member_ifaddrs_vlan"
+ atf_add_test_case "vlan_pvid"
+ atf_add_test_case "vlan_pvid_1q"
+ atf_add_test_case "vlan_pvid_filtered"
+ atf_add_test_case "vlan_pvid_tagged"
+ atf_add_test_case "vlan_filtering"
+ atf_add_test_case "vlan_ifconfig_iftagged"
+ atf_add_test_case "vlan_svi"
+ atf_add_test_case "vlan_qinq"
+ atf_add_test_case "vlan_untagged"
+ atf_add_test_case "vlan_defuntagged"
+ atf_add_test_case "bridge_svi_in_bridge"
+}
diff --git a/tests/sys/net/if_clone_test.sh b/tests/sys/net/if_clone_test.sh
new file mode 100755
index 000000000000..864ff86a7d44
--- /dev/null
+++ b/tests/sys/net/if_clone_test.sh
@@ -0,0 +1,574 @@
+#
+# Copyright (c) 2014 Spectra Logic Corporation
+# All rights reserved.
+#
+# 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,
+# without modification.
+# 2. Redistributions in binary form must reproduce at minimum a disclaimer
+# substantially similar to the "NO WARRANTY" disclaimer below
+# ("Disclaimer") and any redistribution must be conditioned upon
+# including a substantially similar Disclaimer requirement for further
+# binary redistribution.
+#
+# NO WARRANTY
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+#
+# Authors: Alan Somers (Spectra Logic Corporation)
+#
+
+# Outline:
+# For each cloned interface type, do three tests
+# 1) Create and destroy it
+# 2) Create, up, and destroy it
+# 3) Create, disable IPv6 auto address assignment, up, and destroy it
+
+TESTLEN=10 # seconds
+
+atf_test_case epair_stress cleanup
+epair_stress_head()
+{
+ atf_set "descr" "Simultaneously create and destroy an epair(4)"
+ atf_set "require.user" "root"
+}
+epair_stress_body()
+{
+ do_stress "epair"
+}
+epair_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case epair_up_stress cleanup
+epair_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and detroy an epair(4)"
+ atf_set "require.user" "root"
+}
+epair_up_stress_body()
+{
+ do_up_stress "epair" "" ""
+}
+epair_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case epair_destroy_race cleanup
+epair_destroy_race_head()
+{
+ atf_set "descr" "Race if_detach() and if_vmove()"
+ atf_set "require.user" "root"
+}
+epair_destroy_race_body()
+{
+ for i in `seq 1 10`
+ do
+ epair_a=$(ifconfig epair create)
+ echo $epair_a >> devices_to_cleanup
+ epair_b=${epair_a%a}b
+
+ jail -c vnet name="epair_destroy" nopersist path=/ \
+ host.hostname="epair_destroy" vnet.interface="$epair_b" \
+ command=sh -c "ifconfig $epair_b 192.0.2.1/24; sleep 0.1"&
+ pid=$!
+ ifconfig "$epair_a" destroy
+ wait $pid
+ done
+ true
+}
+epair_destroy_race_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case epair_ipv6_up_stress cleanup
+epair_ipv6_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy an epair(4) with IPv6"
+ atf_set "require.user" "root"
+}
+epair_ipv6_up_stress_body()
+{
+ do_up_stress "epair" "6" ""
+}
+epair_ipv6_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case faith_stress cleanup
+faith_stress_head()
+{
+ atf_set "descr" "Simultaneously create and destroy a faith(4)"
+ atf_set "require.user" "root"
+}
+faith_stress_body()
+{
+ do_stress "faith"
+}
+faith_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case faith_up_stress cleanup
+faith_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy a faith(4)"
+ atf_set "require.user" "root"
+}
+faith_up_stress_body()
+{
+ do_up_stress "faith" "" ""
+}
+faith_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case faith_ipv6_up_stress cleanup
+faith_ipv6_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy a faith(4) with IPv6"
+ atf_set "require.user" "root"
+}
+faith_ipv6_up_stress_body()
+{
+ do_up_stress "faith" "6" ""
+}
+faith_ipv6_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case gif_stress cleanup
+gif_stress_head()
+{
+ atf_set "descr" "Simultaneously create and destroy a gif(4)"
+ atf_set "require.user" "root"
+}
+gif_stress_body()
+{
+ do_stress "gif"
+}
+gif_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case gif_up_stress cleanup
+gif_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy a gif(4)"
+ atf_set "require.user" "root"
+}
+gif_up_stress_body()
+{
+ do_up_stress "gif" "" "p2p"
+}
+gif_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case gif_ipv6_up_stress cleanup
+gif_ipv6_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy a gif(4) with IPv6"
+ atf_set "require.user" "root"
+}
+gif_ipv6_up_stress_body()
+{
+ do_up_stress "gif" "6" "p2p"
+}
+gif_ipv6_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case lo_stress cleanup
+lo_stress_head()
+{
+ atf_set "descr" "Simultaneously create and destroy an lo(4)"
+ atf_set "require.user" "root"
+}
+lo_stress_body()
+{
+ do_stress "lo"
+}
+lo_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case lo_up_stress cleanup
+lo_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy an lo(4)"
+ atf_set "require.user" "root"
+}
+lo_up_stress_body()
+{
+ do_up_stress "lo" "" ""
+}
+lo_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case lo_ipv6_up_stress cleanup
+lo_ipv6_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy an lo(4) with IPv6"
+ atf_set "require.user" "root"
+}
+lo_ipv6_up_stress_body()
+{
+ do_up_stress "lo" "6" ""
+}
+lo_ipv6_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case tap_stress cleanup
+tap_stress_head()
+{
+ atf_set "descr" "Simultaneously create and destroy a tap(4)"
+ atf_set "require.user" "root"
+}
+tap_stress_body()
+{
+ do_stress "tap"
+}
+tap_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case tap_up_stress cleanup
+tap_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy a tap(4)"
+ atf_set "require.user" "root"
+}
+tap_up_stress_body()
+{
+ do_up_stress "tap" "" ""
+}
+tap_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case tap_ipv6_up_stress cleanup
+tap_ipv6_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy a tap(4) with IPv6"
+ atf_set "require.user" "root"
+}
+tap_ipv6_up_stress_body()
+{
+ do_up_stress "tap" "6" ""
+}
+tap_ipv6_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case tun_stress cleanup
+tun_stress_head()
+{
+ atf_set "descr" "Simultaneously create and destroy a tun(4)"
+ atf_set "require.user" "root"
+}
+tun_stress_body()
+{
+ do_stress "tun"
+}
+tun_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case tun_up_stress cleanup
+tun_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy a tun(4)"
+ atf_set "require.user" "root"
+}
+tun_up_stress_body()
+{
+ do_up_stress "tun" "" "p2p"
+}
+tun_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case tun_ipv6_up_stress cleanup
+tun_ipv6_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy a tun(4) with IPv6"
+ atf_set "require.user" "root"
+}
+tun_ipv6_up_stress_body()
+{
+ do_up_stress "tun" "6" "p2p"
+}
+tun_ipv6_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case vlan_stress cleanup
+vlan_stress_head()
+{
+ atf_set "descr" "Simultaneously create and destroy a vlan(4)"
+ atf_set "require.user" "root"
+}
+vlan_stress_body()
+{
+ do_stress "vlan"
+}
+vlan_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case vlan_up_stress cleanup
+vlan_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy a vlan(4)"
+ atf_set "require.user" "root"
+}
+vlan_up_stress_body()
+{
+ do_up_stress "vlan" "" ""
+}
+vlan_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case vlan_ipv6_up_stress cleanup
+vlan_ipv6_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy a vlan(4) with IPv6"
+ atf_set "require.user" "root"
+}
+vlan_ipv6_up_stress_body()
+{
+ do_up_stress "vlan" "6" ""
+}
+vlan_ipv6_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case vmnet_stress cleanup
+vmnet_stress_head()
+{
+ atf_set "descr" "Simultaneously create and destroy a vmnet(4)"
+ atf_set "require.user" "root"
+}
+vmnet_stress_body()
+{
+ do_stress "vmnet"
+}
+vmnet_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case vmnet_up_stress cleanup
+vmnet_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy a vmnet(4)"
+ atf_set "require.user" "root"
+}
+vmnet_up_stress_body()
+{
+ do_up_stress "vmnet" "" ""
+}
+vmnet_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_test_case vmnet_ipv6_up_stress cleanup
+vmnet_ipv6_up_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy a vmnet(4) with IPv6"
+ atf_set "require.user" "root"
+}
+vmnet_ipv6_up_stress_body()
+{
+ do_up_stress "vmnet" "6" ""
+}
+vmnet_ipv6_up_stress_cleanup()
+{
+ cleanup_ifaces
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case epair_ipv6_up_stress
+ atf_add_test_case epair_stress
+ atf_add_test_case epair_up_stress
+ atf_add_test_case epair_destroy_race
+ atf_add_test_case faith_ipv6_up_stress
+ atf_add_test_case faith_stress
+ atf_add_test_case faith_up_stress
+ atf_add_test_case gif_ipv6_up_stress
+ atf_add_test_case gif_stress
+ atf_add_test_case gif_up_stress
+ # Don't test lagg; it has its own test program
+ atf_add_test_case lo_ipv6_up_stress
+ atf_add_test_case lo_stress
+ atf_add_test_case lo_up_stress
+ atf_add_test_case tap_ipv6_up_stress
+ atf_add_test_case tap_stress
+ atf_add_test_case tap_up_stress
+ atf_add_test_case tun_ipv6_up_stress
+ atf_add_test_case tun_stress
+ atf_add_test_case tun_up_stress
+ atf_add_test_case vlan_ipv6_up_stress
+ atf_add_test_case vlan_stress
+ atf_add_test_case vlan_up_stress
+ atf_add_test_case vmnet_ipv6_up_stress
+ atf_add_test_case vmnet_stress
+ atf_add_test_case vmnet_up_stress
+}
+
+do_stress()
+{
+ local IFACE CREATOR_PID DESTROYER_PID
+
+ IFACE=`get_iface $1`
+
+ # First thread: create the interface
+ while true; do
+ ifconfig ${IFACE%a} create 2>/dev/null && \
+ echo -n . >> creator_count.txt
+ done &
+ CREATOR_PID=$!
+
+ # Second thread: destroy the lagg
+ while true; do
+ ifconfig $IFACE destroy 2>/dev/null && \
+ echo -n . >> destroyer_count.txt
+ done &
+ DESTROYER_PID=$!
+
+ sleep ${TESTLEN}
+ kill $CREATOR_PID
+ kill $DESTROYER_PID
+ echo "Created ${IFACE%a} `stat -f %z creator_count.txt` times."
+ echo "Destroyed it `stat -f %z destroyer_count.txt` times."
+}
+
+# Implement the up stress tests
+# Parameters
+# $1 Interface class, etc "lo" or "tap"
+# $2 "6" to enable IPv6 auto address assignment, anything else otherwise
+# $3 p2p for point to point interfaces, anything else for normal interfaces
+do_up_stress()
+{
+ local ADDR DSTADDR MASK MEAN_SLEEP_SECONDS MAX_SLEEP_USECS \
+ IFACE IPV6 P2P SRCDIR LOOP_PID ipv6_cmd up_cmd
+
+ # Configure the interface to use an RFC5737 nonrouteable addresses
+ ADDR="192.0.2.2"
+ DSTADDR="192.0.2.3"
+ MASK="24"
+ # ifconfig takes about 10ms to run. To increase race coverage,
+ # randomly delay the two commands relative to each other by 5ms either
+ # way.
+ MEAN_SLEEP_SECONDS=.005
+ MAX_SLEEP_USECS=10000
+
+ IFACE=`get_iface $1`
+ IPV6=$2
+ P2P=$3
+
+ SRCDIR=$( atf_get_srcdir )
+ if [ "$IPV6" = 6 ]; then
+ ipv6_cmd="true"
+ else
+ ipv6_cmd="ifconfig $IFACE inet6 ifdisabled"
+ fi
+ if [ "$P2P" = "p2p" ]; then
+ up_cmd="ifconfig $IFACE up ${ADDR}/${MASK} ${DSTADDR}"
+ else
+ up_cmd="ifconfig $IFACE up ${ADDR}/${MASK}"
+ fi
+ while true; do
+ eval "$ipv6_cmd"
+ { sleep ${MEAN_SLEEP_SECONDS} && \
+ eval "$up_cmd" 2> /dev/null &&
+ echo -n . >> up_count.txt ; } &
+ { ${SRCDIR}/randsleep ${MAX_SLEEP_USECS} && \
+ ifconfig $IFACE destroy &&
+ echo -n . >> destroy_count.txt ; } &
+ wait
+ ifconfig ${IFACE%a} create
+ done &
+ LOOP_PID=$!
+
+ sleep ${TESTLEN}
+ kill $LOOP_PID
+ echo "Upped ${IFACE} `stat -f %z up_count.txt` times."
+ echo "Destroyed it `stat -f %z destroy_count.txt` times."
+}
+
+# Creates a new cloned interface, registers it for cleanup, and echoes it
+# params: $1 Interface class name (tap, gif, etc)
+get_iface()
+{
+ local CLASS DEV N
+
+ CLASS=$1
+ N=0
+ while ! ifconfig ${CLASS}${N} create > /dev/null 2>&1; do
+ if [ "$N" -ge 8 ]; then
+ atf_skip "Could not create a ${CLASS} interface"
+ else
+ N=$(($N + 1))
+ fi
+ done
+ if [ ${CLASS} = "epair" ]; then
+ DEV=${CLASS}${N}a
+ else
+ DEV=${CLASS}${N}
+ fi
+ # Record the device so we can clean it up later
+ echo ${DEV} >> "devices_to_cleanup"
+ echo ${DEV}
+}
+
+
+cleanup_ifaces()
+{
+ local DEV
+
+ for DEV in `cat "devices_to_cleanup"`; do
+ ifconfig ${DEV} destroy
+ done
+ true
+}
diff --git a/tests/sys/net/if_epair.c b/tests/sys/net/if_epair.c
new file mode 100644
index 000000000000..5ee4a48aea86
--- /dev/null
+++ b/tests/sys/net/if_epair.c
@@ -0,0 +1,75 @@
+/*-
+ * Copyright (c) 2020 Kristof Provost <kp@FreeBSD.org>
+ *
+ * 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.
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <net/if.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <strings.h>
+
+#include <atf-c.h>
+#include "freebsd_test_suite/macros.h"
+
+ATF_TC(params);
+ATF_TC_HEAD(params, tc)
+{
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "require.kmods", "if_epair");
+}
+
+ATF_TC_BODY(params, tc)
+{
+ struct ifreq ifr;
+ int s;
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (s < 0)
+ atf_tc_fail("Failed to create socket");
+
+ bzero(&ifr, sizeof(ifr));
+ ifr.ifr_data = (caddr_t)-1;
+ (void) strlcpy(ifr.ifr_name, "epair", sizeof(ifr.ifr_name));
+
+ if (ioctl(s, SIOCIFCREATE2, &ifr) < 0)
+ atf_tc_fail("Failed to create interface");
+
+ if (ioctl(s, SIOCIFDESTROY, &ifr) < 0)
+ atf_tc_fail("Failed to destroy interface");
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, params);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/net/if_epair_test.sh b/tests/sys/net/if_epair_test.sh
new file mode 100644
index 000000000000..e1d0c0266b2d
--- /dev/null
+++ b/tests/sys/net/if_epair_test.sh
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2023 Rubicon Communications, LLC (Netgate)
+#
+# 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.
+
+. $(atf_get_srcdir)/../common/vnet.subr
+
+atf_test_case "pcp" "cleanup"
+pcp_head()
+{
+ atf_set descr 'Test PCP over if_epair. PR#270736'
+ atf_set require.user root
+}
+
+pcp_body()
+{
+ vnet_init
+
+ j="if_epair_test_pcp"
+
+ epair=$(vnet_mkepair)
+
+ vnet_mkjail ${j}one ${epair}a
+ vnet_mkjail ${j}two ${epair}b
+
+ jexec ${j}one ifconfig ${epair}a 192.0.2.1/24 pcp 3 up
+ jexec ${j}two ifconfig ${epair}b 192.0.2.2/24 up
+
+ atf_check -s exit:0 -o ignore \
+ jexec ${j}one ping -c 1 192.0.2.2
+
+ # Now set a different PCP. This used to lead to double tagging and failed pin.
+ atf_check -s exit:0 -o ignore \
+ jexec ${j}one ping -C5 -c 1 192.0.2.2
+}
+
+pcp_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "pcp"
+}
diff --git a/tests/sys/net/if_gif.sh b/tests/sys/net/if_gif.sh
new file mode 100644
index 000000000000..bff88f9e75b6
--- /dev/null
+++ b/tests/sys/net/if_gif.sh
@@ -0,0 +1,365 @@
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2021 Rubicon Communications, LLC (Netgate)
+#
+# 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.
+
+. $(atf_get_srcdir)/../common/vnet.subr
+
+atf_test_case "4in4" "cleanup"
+4in4_head()
+{
+ atf_set descr 'IPv4 in IPv4 tunnel'
+ atf_set require.user root
+}
+
+4in4_body()
+{
+ vnet_init
+ if ! kldstat -q -m if_gif; then
+ atf_skip "This test requires if_gif"
+ fi
+
+ epair=$(vnet_mkepair)
+
+ vnet_mkjail one ${epair}a
+ jexec one ifconfig ${epair}a 192.0.2.1/24 up
+ gone=$(jexec one ifconfig gif create)
+ jexec one ifconfig $gone tunnel 192.0.2.1 192.0.2.2
+ jexec one ifconfig $gone inet 198.51.100.1/24 198.51.100.2 up
+
+ vnet_mkjail two ${epair}b
+ jexec two ifconfig ${epair}b 192.0.2.2/24 up
+ gtwo=$(jexec two ifconfig gif create)
+ jexec two ifconfig $gtwo tunnel 192.0.2.2 192.0.2.1
+ jexec two ifconfig $gtwo inet 198.51.100.2/24 198.51.100.1 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 192.0.2.2
+
+ # Tunnel test
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 198.51.100.2
+ atf_check -s exit:0 -o ignore \
+ jexec two ping -c 1 198.51.100.1
+}
+
+4in4_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "6in4" "cleanup"
+6in4_head()
+{
+ atf_set descr 'IPv6 in IPv4 tunnel'
+ atf_set require.user root
+}
+
+6in4_body()
+{
+ vnet_init
+ if ! kldstat -q -m if_gif; then
+ atf_skip "This test requires if_gif"
+ fi
+
+ epair=$(vnet_mkepair)
+
+ vnet_mkjail one ${epair}a
+ jexec one ifconfig ${epair}a 192.0.2.1/24 up
+ gone=$(jexec one ifconfig gif create)
+ jexec one ifconfig $gone tunnel 192.0.2.1 192.0.2.2
+ jexec one ifconfig $gone inet6 no_dad 2001:db8:1::1/64 up
+
+ vnet_mkjail two ${epair}b
+ jexec two ifconfig ${epair}b 192.0.2.2/24 up
+ gtwo=$(jexec two ifconfig gif create)
+ jexec two ifconfig $gtwo tunnel 192.0.2.2 192.0.2.1
+ jexec two ifconfig $gtwo inet6 no_dad 2001:db8:1::2/64 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 192.0.2.2
+
+ # Tunnel test
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -6 -c 1 2001:db8:1::2
+ atf_check -s exit:0 -o ignore \
+ jexec two ping -6 -c 1 2001:db8:1::1
+}
+
+6in4_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "4in6" "cleanup"
+4in6_head()
+{
+ atf_set descr 'IPv4 in IPv6 tunnel'
+ atf_set require.user root
+}
+
+4in6_body()
+{
+ vnet_init
+ if ! kldstat -q -m if_gif; then
+ atf_skip "This test requires if_gif"
+ fi
+
+ epair=$(vnet_mkepair)
+
+ vnet_mkjail one ${epair}a
+ jexec one ifconfig ${epair}a inet6 no_dad 2001:db8::1/64 up
+ gone=$(jexec one ifconfig gif create)
+ jexec one ifconfig $gone inet6 tunnel 2001:db8::1 2001:db8::2
+ jexec one ifconfig $gone inet 198.51.100.1/24 198.51.100.2 up
+
+ vnet_mkjail two ${epair}b
+ jexec two ifconfig ${epair}b inet6 no_dad 2001:db8::2/64 up
+ gtwo=$(jexec two ifconfig gif create)
+ jexec two ifconfig $gtwo inet6 tunnel 2001:db8::2 2001:db8::1
+ jexec two ifconfig $gtwo inet 198.51.100.2/24 198.51.100.1 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -6 -c 1 2001:db8::2
+
+ # Tunnel test
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 198.51.100.2
+ atf_check -s exit:0 -o ignore \
+ jexec two ping -c 1 198.51.100.1
+}
+
+4in6_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "6in6" "cleanup"
+6in6_head()
+{
+ atf_set descr 'IPv6 in IPv6 tunnel'
+ atf_set require.user root
+}
+
+6in6_body()
+{
+ vnet_init
+ if ! kldstat -q -m if_gif; then
+ atf_skip "This test requires if_gif"
+ fi
+
+ epair=$(vnet_mkepair)
+
+ vnet_mkjail one ${epair}a
+ jexec one ifconfig ${epair}a inet6 no_dad 2001:db8::1/64 up
+ gone=$(jexec one ifconfig gif create)
+ jexec one ifconfig $gone inet6 tunnel 2001:db8::1 2001:db8::2
+ jexec one ifconfig $gone inet6 no_dad 2001:db8:1::1/64 up
+
+ vnet_mkjail two ${epair}b
+ jexec two ifconfig ${epair}b inet6 no_dad 2001:db8::2/64 up
+ gtwo=$(jexec two ifconfig gif create)
+ jexec two ifconfig $gtwo inet6 tunnel 2001:db8::2 2001:db8::1
+ jexec two ifconfig $gtwo inet6 no_dad 2001:db8:1::2/64 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -6 -c 1 2001:db8::2
+
+ # Tunnel test
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -6 -c 1 2001:db8:1::2
+ atf_check -s exit:0 -o ignore \
+ jexec two ping -6 -c 1 2001:db8:1::1
+}
+
+6in6_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "etherip" "cleanup"
+etherip_head()
+{
+ atf_set descr 'EtherIP regression'
+ atf_set require.user root
+}
+
+etherip_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ if ! kldstat -q -m if_gif; then
+ atf_skip "This test requires if_gif"
+ fi
+
+ epair=$(vnet_mkepair)
+
+ vnet_mkjail one ${epair}a
+ gone=$(jexec one ifconfig gif create)
+ jexec one ifconfig ${epair}a 192.0.2.1/24 up
+ jexec one ifconfig $gone tunnel 192.0.2.1 192.0.2.2
+ jexec one ifconfig $gone 198.51.100.1/24 198.51.100.2 up
+ jexec one ifconfig $gone inet6 no_dad 2001:db8:1::1/64
+
+ bone=$(jexec one ifconfig bridge create)
+ jexec one ifconfig $bone addm $gone
+ jexec one ifconfig $bone 192.168.169.253/24 up
+ jexec one ifconfig $bone inet6 no_dad 2001:db8:2::1/64
+
+ vnet_mkjail two ${epair}b
+ gtwo=$(jexec two ifconfig gif create)
+ jexec two ifconfig ${epair}b 192.0.2.2/24 up
+ jexec two ifconfig $gtwo tunnel 192.0.2.2 192.0.2.1
+ jexec two ifconfig $gtwo 198.51.100.2/24 198.51.100.1 up
+ jexec two ifconfig $gtwo inet6 no_dad 2001:db8:1::2/64
+
+ btwo=$(jexec two ifconfig bridge create)
+ jexec two ifconfig $btwo addm $gtwo
+ jexec two ifconfig $btwo 192.168.169.254/24 up
+ jexec two ifconfig $btwo inet6 no_dad 2001:db8:2::2/64
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 192.0.2.2
+
+ # EtherIP tunnel test
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 192.168.169.254
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -6 -c 1 2001:db8:2::2
+ atf_check -s exit:0 -o ignore \
+ jexec two ping -c 1 192.168.169.253
+ atf_check -s exit:0 -o ignore \
+ jexec two ping -6 -c 1 2001:db8:2::1
+
+ # EtherIP should not affect normal IPv[46] over IPv4 tunnel
+ # See bugzilla PR 227450
+ # IPv4 in IPv4 Tunnel test
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 198.51.100.2
+ atf_check -s exit:0 -o ignore \
+ jexec two ping -c 1 198.51.100.1
+
+ # IPv6 in IPv4 tunnel test
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -6 -c 1 2001:db8:1::2
+ atf_check -s exit:0 -o ignore \
+ jexec two ping -6 -c 1 2001:db8:1::1
+}
+
+etherip_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "etherip6" "cleanup"
+etherip6_head()
+{
+ atf_set descr 'EtherIP over IPv6 regression'
+ atf_set require.user root
+}
+
+etherip6_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ if ! kldstat -q -m if_gif; then
+ atf_skip "This test requires if_gif"
+ fi
+
+ epair=$(vnet_mkepair)
+
+ vnet_mkjail one ${epair}a
+ gone=$(jexec one ifconfig gif create)
+ jexec one ifconfig ${epair}a inet6 no_dad 2001:db8::1/64 up
+ jexec one ifconfig $gone inet6 tunnel 2001:db8::1 2001:db8::2
+ jexec one ifconfig $gone 198.51.100.1/24 198.51.100.2 up
+ jexec one ifconfig $gone inet6 no_dad 2001:db8:1::1/64
+
+ bone=$(jexec one ifconfig bridge create)
+ jexec one ifconfig $bone addm $gone
+ jexec one ifconfig $bone 192.168.169.253/24 up
+ jexec one ifconfig $bone inet6 no_dad 2001:db8:2::1/64
+
+ vnet_mkjail two ${epair}b
+ gtwo=$(jexec two ifconfig gif create)
+ jexec two ifconfig ${epair}b inet6 no_dad 2001:db8::2/64 up
+ jexec two ifconfig $gtwo inet6 tunnel 2001:db8::2 2001:db8::1
+ jexec two ifconfig $gtwo 198.51.100.2/24 198.51.100.1 up
+ jexec two ifconfig $gtwo inet6 no_dad 2001:db8:1::2/64
+
+ btwo=$(jexec two ifconfig bridge create)
+ jexec two ifconfig $btwo addm $gtwo
+ jexec two ifconfig $btwo 192.168.169.254/24 up
+ jexec two ifconfig $btwo inet6 no_dad 2001:db8:2::2/64
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -6 -c 1 2001:db8::2
+
+ # EtherIP tunnel test
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 192.168.169.254
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -6 -c 1 2001:db8:2::2
+ atf_check -s exit:0 -o ignore \
+ jexec two ping -c 1 192.168.169.253
+ atf_check -s exit:0 -o ignore \
+ jexec two ping -6 -c 1 2001:db8:2::1
+
+ # EtherIP should not affect normal IPv[46] over IPv6 tunnel
+ # See bugzilla PR 227450
+ # IPv4 in IPv6 Tunnel test
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 198.51.100.2
+ atf_check -s exit:0 -o ignore \
+ jexec two ping -c 1 198.51.100.1
+
+ # IPv6 in IPv6 tunnel test
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -6 -c 1 2001:db8:1::2
+ atf_check -s exit:0 -o ignore \
+ jexec two ping -6 -c 1 2001:db8:1::1
+}
+
+etherip6_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "4in4"
+ atf_add_test_case "6in4"
+ atf_add_test_case "4in6"
+ atf_add_test_case "6in6"
+ atf_add_test_case "etherip"
+ atf_add_test_case "etherip6"
+}
diff --git a/tests/sys/net/if_lagg_test.sh b/tests/sys/net/if_lagg_test.sh
new file mode 100755
index 000000000000..e2b998599991
--- /dev/null
+++ b/tests/sys/net/if_lagg_test.sh
@@ -0,0 +1,463 @@
+#
+# Copyright (c) 2014 Spectra Logic Corporation
+# All rights reserved.
+#
+# 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,
+# without modification.
+# 2. Redistributions in binary form must reproduce at minimum a disclaimer
+# substantially similar to the "NO WARRANTY" disclaimer below
+# ("Disclaimer") and any redistribution must be conditioned upon
+# including a substantially similar Disclaimer requirement for further
+# binary redistribution.
+#
+# NO WARRANTY
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+#
+# Authors: Alan Somers (Spectra Logic Corporation)
+#
+
+atf_test_case create cleanup
+create_head()
+{
+ atf_set "descr" "Create a lagg and assign an address"
+ atf_set "require.user" "root"
+}
+create_body()
+{
+ local TAP0 TAP1 LAGG MAC
+
+ # Configure the lagg interface to use an RFC5737 nonrouteable addresses
+ ADDR="192.0.2.2"
+ MASK="24"
+
+ TAP0=`get_tap`
+ TAP1=`get_tap`
+ LAGG=`get_lagg`
+
+ # Create the lagg
+ ifconfig $TAP0 up
+ ifconfig $TAP1 up
+ atf_check ifconfig $LAGG up laggport $TAP0 laggport $TAP1 \
+ ${ADDR}/${MASK}
+ atf_check -o match:"inet ${ADDR}" ifconfig $LAGG
+ atf_check -o match:"laggport: ${TAP0}" ifconfig $LAGG
+ atf_check -o match:"laggport: ${TAP1}" ifconfig $LAGG
+
+ # Check that all members have the same MAC
+ MAC=`ifconfig $LAGG | awk '/ether/ {print $2}'`
+ atf_check -o match:"ether ${MAC}" ifconfig $TAP0
+ atf_check -o match:"ether ${MAC}" ifconfig $TAP1
+
+ # Check that no members have an IPv6 link-local address. IPv6
+ # link-local addresses should never be merged in any way to prevent
+ # scope violation.
+ atf_check -o not-match:"inet6 fe80:" ifconfig $TAP0
+ atf_check -o not-match:"inet6 fe80:" ifconfig $TAP1
+}
+create_cleanup()
+{
+ cleanup_tap_and_lagg
+}
+
+atf_test_case status_stress cleanup
+status_stress_head()
+{
+ atf_set "descr" "Simultaneously query a lagg while also creating or destroying it."
+ atf_set "require.user" "root"
+}
+status_stress_body()
+{
+ local TAP0 TAP1 LAGG MAC
+
+ # Configure the lagg interface to use an RFC5737 nonrouteable addresses
+ ADDR="192.0.2.2"
+ MASK="24"
+
+ TAP0=`get_tap`
+ TAP1=`get_tap`
+ TAP2=`get_tap`
+ TAP3=`get_tap`
+ LAGG=`get_lagg`
+
+ # Up the lagg's children
+ ifconfig $TAP0 inet6 ifdisabled up
+ ifconfig $TAP1 inet6 ifdisabled up
+ ifconfig $TAP2 inet6 ifdisabled up
+ ifconfig $TAP3 inet6 ifdisabled up
+
+ # First thread: create and destroy the lagg
+ while true; do
+ ifconfig $LAGG destroy 2>&1
+ ifconfig $LAGG create 2>/dev/null
+ ifconfig $LAGG inet6 ifdisabled
+ ifconfig $LAGG up laggport $TAP0 laggport $TAP1 laggport $TAP2\
+ laggport $TAP3 ${ADDR}/${MASK} 2>/dev/null
+ echo -n . >> creator_count.txt
+ done &
+ CREATOR_PID=$!
+
+ # Second thread: Query the lagg's status
+ while true; do
+ ifconfig -am 2> /dev/null > /dev/null
+ echo -n . >> querier_count.txt
+ done &
+ QUERIER_PID=$!
+
+ sleep 60
+ kill $CREATOR_PID
+ kill $QUERIER_PID
+ echo "Created the lagg `stat -f %z creator_count.txt` times."
+ echo "Queried its status `stat -f %z querier_count.txt` times"
+}
+status_stress_cleanup()
+{
+ cleanup_tap_and_lagg
+}
+
+atf_test_case create_destroy_stress cleanup
+create_destroy_stress_head()
+{
+ atf_set "descr" "Simultaneously create and destroy a lagg"
+ atf_set "require.user" "root"
+}
+create_destroy_stress_body()
+{
+ local TAP0 TAP1 LAGG MAC
+
+ TAP0=`get_tap`
+ TAP1=`get_tap`
+ TAP2=`get_tap`
+ TAP3=`get_tap`
+ LAGG=`get_lagg`
+
+ # Up the lagg's children
+ ifconfig $TAP0 inet6 ifdisabled up
+ ifconfig $TAP1 inet6 ifdisabled up
+ ifconfig $TAP2 inet6 ifdisabled up
+ ifconfig $TAP3 inet6 ifdisabled up
+
+ # First thread: create the lagg
+ while true; do
+ ifconfig $LAGG create 2>/dev/null && \
+ echo -n . >> creator_count.txt
+ done &
+ CREATOR_PID=$!
+
+ # Second thread: destroy the lagg
+ while true; do
+ ifconfig $LAGG destroy 2>/dev/null && \
+ echo -n . >> destroyer_count.txt
+ done &
+ DESTROYER_PID=$!
+
+ sleep 60
+ kill $CREATOR_PID
+ kill $DESTROYER_PID
+ echo "Created the lagg `stat -f %z creator_count.txt` times."
+ echo "Destroyed it `stat -f %z destroyer_count.txt` times."
+}
+create_destroy_stress_cleanup()
+{
+ cleanup_tap_and_lagg
+}
+
+# This test regresses a panic that is particular to LACP. If the child's link
+# state changes while the lagg is being destroyed, lacp_linkstate can
+# use-after-free. The problem is compounded by two factors:
+# 1) In SpectraBSD, downing the parent will also down the child
+# 2) The cxgbe driver will show the link state as "no carrier" as soon as you
+# down the interface.
+# TeamTrack: P2_30328
+atf_test_case lacp_linkstate_destroy_stress cleanup
+lacp_linkstate_destroy_stress_head()
+{
+ atf_set "descr" "Simultaneously destroy an LACP lagg and change its childrens link states"
+ atf_set "require.user" "root"
+}
+lacp_linkstate_destroy_stress_body()
+{
+ local TAP0 TAP1 LAGG MAC SRCDIR
+
+ # Configure the lagg interface to use an RFC5737 nonrouteable addresses
+ ADDR="192.0.2.2"
+ MASK="24"
+ # ifconfig takes about 10ms to run. To increase race coverage,
+ # randomly delay the two commands relative to each other by 5ms either
+ # way.
+ MEAN_SLEEP_SECONDS=.005
+ MAX_SLEEP_USECS=10000
+
+ TAP0=`get_tap`
+ TAP1=`get_tap`
+ LAGG=`get_lagg`
+
+ # Up the lagg's children
+ ifconfig $TAP0 inet6 ifdisabled up
+ ifconfig $TAP1 inet6 ifdisabled up
+
+ SRCDIR=$( atf_get_srcdir )
+ while true; do
+ ifconfig $LAGG inet6 ifdisabled
+ # We must open the tap devices to change their link states
+ cat /dev/$TAP0 > /dev/null &
+ CAT0_PID=$!
+ cat /dev/$TAP1 > /dev/null &
+ CAT1_PID=$!
+ ifconfig $LAGG up laggport $TAP0 laggport $TAP1 \
+ ${ADDR}/${MASK} 2> /dev/null &&
+ { sleep ${MEAN_SLEEP_SECONDS} && \
+ kill $CAT0_PID &&
+ kill $CAT1_PID &&
+ echo -n . >> linkstate_count.txt ; } &
+ { ${SRCDIR}/randsleep ${MAX_SLEEP_USECS} && \
+ ifconfig $LAGG destroy &&
+ echo -n . >> destroy_count.txt ; } &
+ wait
+ ifconfig $LAGG create
+ done &
+ LOOP_PID=$!
+
+ sleep 60
+ kill $LOOP_PID
+ echo "Disconnected the children `stat -f %z linkstate_count.txt` times."
+ echo "Destroyed the lagg `stat -f %z destroy_count.txt` times."
+}
+lacp_linkstate_destroy_stress_cleanup()
+{
+ cleanup_tap_and_lagg
+}
+
+atf_test_case up_destroy_stress cleanup
+up_destroy_stress_head()
+{
+ atf_set "descr" "Simultaneously up and destroy a lagg"
+ atf_set "require.user" "root"
+}
+up_destroy_stress_body()
+{
+ local TAP0 TAP1 LAGG MAC SRCDIR
+
+ # Configure the lagg interface to use an RFC5737 nonrouteable addresses
+ ADDR="192.0.2.2"
+ MASK="24"
+ # ifconfig takes about 10ms to run. To increase race coverage,
+ # randomly delay the two commands relative to each other by 5ms either
+ # way.
+ MEAN_SLEEP_SECONDS=.005
+ MAX_SLEEP_USECS=10000
+
+ TAP0=`get_tap`
+ TAP1=`get_tap`
+ TAP2=`get_tap`
+ TAP3=`get_tap`
+ LAGG=`get_lagg`
+
+ # Up the lagg's children
+ ifconfig $TAP0 inet6 ifdisabled up
+ ifconfig $TAP1 inet6 ifdisabled up
+ ifconfig $TAP2 inet6 ifdisabled up
+ ifconfig $TAP3 inet6 ifdisabled up
+
+ SRCDIR=$( atf_get_srcdir )
+ while true; do
+ ifconfig $LAGG inet6 ifdisabled
+ { sleep ${MEAN_SLEEP_SECONDS} && \
+ ifconfig $LAGG up laggport $TAP0 laggport $TAP1 \
+ laggport $TAP2 laggport $TAP3 \
+ ${ADDR}/${MASK} 2> /dev/null &&
+ echo -n . >> up_count.txt ; } &
+ { ${SRCDIR}/randsleep ${MAX_SLEEP_USECS} && \
+ ifconfig $LAGG destroy &&
+ echo -n . >> destroy_count.txt ; } &
+ wait
+ ifconfig $LAGG create
+ done &
+ LOOP_PID=$!
+
+ sleep 60
+ kill $LOOP_PID
+ echo "Upped the lagg `stat -f %z up_count.txt` times."
+ echo "Destroyed it `stat -f %z destroy_count.txt` times."
+}
+up_destroy_stress_cleanup()
+{
+ cleanup_tap_and_lagg
+}
+
+atf_test_case set_ether cleanup
+set_ether_head()
+{
+ atf_set "descr" "Set a lagg's ethernet address"
+ atf_set "require.user" "root"
+}
+set_ether_body()
+{
+ local TAP0 TAP1 LAGG MAC
+
+ # Configure the lagg interface to use an RFC5737 nonrouteable addresses
+ ADDR="192.0.2.2"
+ MASK="24"
+ MAC="00:11:22:33:44:55"
+
+ TAP0=`get_tap`
+ TAP1=`get_tap`
+ LAGG=`get_lagg`
+
+ # Create the lagg
+ ifconfig $TAP0 up
+ ifconfig $TAP1 up
+ atf_check ifconfig $LAGG up laggport $TAP0 laggport $TAP1 \
+ ${ADDR}/${MASK}
+
+ # Change the lagg's ethernet address
+ atf_check ifconfig $LAGG ether ${MAC}
+
+ # Check that all members have the same MAC
+ atf_check -o match:"ether ${MAC}" ifconfig $LAGG
+ atf_check -o match:"ether ${MAC}" ifconfig $TAP0
+ atf_check -o match:"ether ${MAC}" ifconfig $TAP1
+}
+set_ether_cleanup()
+{
+ cleanup_tap_and_lagg
+}
+
+atf_test_case updown cleanup
+updown_head()
+{
+ atf_set "descr" "upping or downing a lagg ups or downs its children"
+ atf_set "require.user" "root"
+}
+updown_body()
+{
+ local TAP0 TAP1 LAGG MAC
+
+ # Configure the lagg interface to use an RFC5737 nonrouteable addresses
+ ADDR="192.0.2.2"
+ MASK="24"
+ MAC="00:11:22:33:44:55"
+
+ TAP0=`get_tap`
+ TAP1=`get_tap`
+ LAGG=`get_lagg`
+
+ # Create the lagg
+ ifconfig $TAP0 up
+ ifconfig $TAP1 up
+ atf_check ifconfig $LAGG up laggport $TAP0 laggport $TAP1 \
+ ${ADDR}/${MASK}
+
+ # Down the lagg
+ ifconfig $LAGG down
+ atf_check -o not-match:"flags=.*\<UP\>" ifconfig $LAGG
+ atf_check -o not-match:"flags=.*\<UP\>" ifconfig $TAP0
+ atf_check -o not-match:"flags=.*\<UP\>" ifconfig $TAP1
+ # Up the lagg again
+ ifconfig $LAGG up
+ atf_check -o match:"flags=.*\<UP\>" ifconfig $LAGG
+ atf_check -o match:"flags=.*\<UP\>" ifconfig $TAP0
+ atf_check -o match:"flags=.*\<UP\>" ifconfig $TAP1
+
+ # Check that no members have acquired an IPv6 link-local address by
+ # virtue of being upped. IPv6 link-local addresses should never be
+ # merged in any way to prevent scope violation.
+ atf_check -o not-match:"inet6 fe80:" ifconfig $TAP0
+ atf_check -o not-match:"inet6 fe80:" ifconfig $TAP1
+}
+updown_cleanup()
+{
+ cleanup_tap_and_lagg
+}
+
+# Check for lock-order reversals. For best results, this test should be run
+# last.
+atf_test_case witness
+witness_head()
+{
+ atf_set "descr" "Check witness(4) for lock-order reversals in if_lagg"
+}
+witness_body()
+{
+ if [ "$(atf_config_get ci false)" = "true" ]; then
+ atf_skip "https://bugs.freebsd.org/244163 and https://bugs.freebsd.org/251726"
+ fi
+ if [ `sysctl -n debug.witness.watch` -ne 1 ]; then
+ atf_skip "witness(4) is not enabled"
+ fi
+ if `sysctl -n debug.witness.badstacks | grep -q 'at lagg_'`; then
+ sysctl debug.witness.badstacks
+ atf_fail "Lock-order reversals involving if_lagg.c detected"
+ fi
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case create
+ atf_add_test_case create_destroy_stress
+ atf_add_test_case lacp_linkstate_destroy_stress
+ atf_add_test_case set_ether
+ atf_add_test_case status_stress
+ atf_add_test_case up_destroy_stress
+ atf_add_test_case updown
+ # For best results, keep the witness test last
+ atf_add_test_case witness
+}
+
+
+# Creates a new tap(4) interface, registers it for cleanup, and echoes it
+get_tap()
+{
+ local TAPN=0
+ while ! ifconfig tap${TAPN} create > /dev/null 2>&1; do
+ if [ "$TAPN" -ge 8 ]; then
+ atf_skip "Could not create a tap(4) interface"
+ else
+ TAPN=$(($TAPN + 1))
+ fi
+ done
+ local TAPD=tap${TAPN}
+ # Record the TAP device so we can clean it up later
+ echo ${TAPD} >> "devices_to_cleanup"
+ echo ${TAPD}
+}
+
+# Creates a new lagg(4) interface, registers it for cleanup, and echoes it
+get_lagg()
+{
+ local LAGGN=0
+ while ! ifconfig lagg${LAGGN} create > /dev/null 2>&1; do
+ if [ "$LAGGN" -ge 8 ]; then
+ atf_skip "Could not create a lagg(4) interface"
+ else
+ LAGGN=$(($LAGGN + 1))
+ fi
+ done
+ local LAGGD=lagg${LAGGN}
+ # Record the lagg device so we can clean it up later
+ echo ${LAGGD} >> "devices_to_cleanup"
+ echo ${LAGGD}
+}
+
+cleanup_tap_and_lagg()
+{
+ local DEV
+
+ for DEV in `cat "devices_to_cleanup"`; do
+ ifconfig ${DEV} destroy
+ done
+ true
+}
diff --git a/tests/sys/net/if_ovpn/Makefile b/tests/sys/net/if_ovpn/Makefile
new file mode 100644
index 000000000000..85746226e122
--- /dev/null
+++ b/tests/sys/net/if_ovpn/Makefile
@@ -0,0 +1,30 @@
+.include <src.opts.mk>
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/net/if_ovpn
+
+.if ${MK_PF} != "no"
+ATF_TESTS_SH+= if_ovpn
+TEST_METADATA.if_ovpn+= execenv="jail"
+TEST_METADATA.if_ovpn+= execenv_jail_params="vnet allow.raw_sockets"
+.endif
+ATF_TESTS_C+= if_ovpn_c
+
+LIBADD+= nv
+
+TESTS_SUBDIRS+= ccd
+
+${PACKAGE}FILES+= \
+ ca.crt \
+ client.crt \
+ client.key \
+ client2.crt \
+ client2.key \
+ dh.pem \
+ server.crt \
+ server.key \
+ user.pass \
+ utils.subr
+
+.include <bsd.test.mk>
diff --git a/tests/sys/net/if_ovpn/ca.crt b/tests/sys/net/if_ovpn/ca.crt
new file mode 100644
index 000000000000..4bdde726d12b
--- /dev/null
+++ b/tests/sys/net/if_ovpn/ca.crt
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFrzCCA5egAwIBAgIUFByUexsc+WCQtaEQZCg+nrJaFDowDQYJKoZIhvcNAQEL
+BQAwZjELMAkGA1UEBhMCS0cxCzAJBgNVBAgMAk5BMRAwDgYDVQQHDAdCSVNIS0VL
+MRUwEwYDVQQKDAxPcGVuVlBOLVRFU1QxITAfBgkqhkiG9w0BCQEWEm1lQG15aG9z
+dC5teWRvbWFpbjAgFw0yMjA0MjcxNDM1NTZaGA8yMTIyMDQwMzE0MzU1NlowZjEL
+MAkGA1UEBhMCS0cxCzAJBgNVBAgMAk5BMRAwDgYDVQQHDAdCSVNIS0VLMRUwEwYD
+VQQKDAxPcGVuVlBOLVRFU1QxITAfBgkqhkiG9w0BCQEWEm1lQG15aG9zdC5teWRv
+bWFpbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJnak7swgeJta01H
+4pNQte3S0bdEGRvyhkzY8HIxe/pdz371fSL40iBQ29PVZseMVOsQIrIolhma4qTy
+tMyU9VEfJRlBj8hdlbIpb8FDsvMOFyC+7+GxHdkAQlFV5fUj7JydnxiY+L87W7WE
+VaTy9ZRtuIXhvkVZYTHzvVwXwL5e5EdcJKHsdam49ty4rs6CE7J06GYnwmLk/nDw
+vZZhHTB5V+vNeurRoxKyYvUXePTCqh6cGh2as061JxU1j1vj3cTNVlPymO85Etl1
+t4uEHjXyLVK2+OCcauQGn6QT02xqFLRsbk42gWOksFj50NDQVtp71JIMAmwzz2yi
++cqz1qqdyOlfLeP2oow1jYa3Hhht5Es90m6YkeF/073eLW/nuVsoD57PZ1rnClX+
+9Zs24LAN/vnXR1gK/nAZsv9CNtRdWt9yTSmq/oGZj4kWU154XxRLfcDa9gj5X+g9
+Z4w6YaJtsa8XaOelPWnBg/JXLrdE731DwyDRqlSPYgwzU6g3CgjRV3/cJs/RaxWB
+Au1Tdpd2T5KbeLivYJnhNApE0J4CxXrudfglAaZV8tG0SK8F07Sd0uS4Sa83I1Bh
+IjAFcjBcMxv1gWNZN903V0etmCkIwONlqTSWKNYGV+9EQ1moHDDXRUK14He95AQ7
+ZNadjsqZuDGl925HW4wjk2HLa42FAgMBAAGjUzBRMB0GA1UdDgQWBBSIBxjB/i4J
+Ln3guRuDqVTeCbEWfjAfBgNVHSMEGDAWgBSIBxjB/i4JLn3guRuDqVTeCbEWfjAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAMrtwU2YfLGp2QWH33
+qH4dZh92iaSApEDwIggBj+wrc/6LbbS+Mm6UTOj9SYNcDZpj/ia4m1OXV7dC1adZ
+wGk94qBknwgxPkxS0f3HLlvYDnSWCOTP5CJPfdQWPRX0WQO8l5s+tIigLaioS76N
+C/g+I7ytPYbCN7sZRgQJr+vGxOxaBD8GrSq6/brTSUMCHUjE/ylFZ9ykBtmXTVIb
+u9WsnWTyA7h5Nzhkh0VvN0o/EhlgRpUdT1661QlIvWsyfd6sxrtLum4h88DUkjw9
+qlMDTnkhWUfyPg8kS99dLodnxp1QeW0ISeWpAucJuOvu3ode/N3lOrxq88OrZQNJ
+upQsdUxLEU6DzQlvBd2s4d8Ghvk3l65u688cE6dXIcNPEp78wy/IXkVvTRTstpuA
+Ep9ZNwrEvaPQDxBJ6a2sPKwXst1NZZgmPQG2ZbpQfCQtJ0zYZWpI/LytiC/05joi
+/aGh01GN7nODt9U7rtZtCQjjmIlK7fuBJLL9yQXcpzT5sItdQSkn9QuCBlnlxMqx
+felbaNPxTLJVqCilqlx/xaybDljduKLvJouR+l/UjrXz+n02lzxPQ3FIr8/vJlVf
+EFbSmkzS3C/O5gXUxHTq44z6LbnosjyiPEB2J7n5kvsA8HTynZU4GCrHa5LLG1eg
+1odsgCIYiAwNBCbPtWWykUHXhA==
+-----END CERTIFICATE-----
diff --git a/tests/sys/net/if_ovpn/ca.key b/tests/sys/net/if_ovpn/ca.key
new file mode 100644
index 000000000000..79537a84c6a0
--- /dev/null
+++ b/tests/sys/net/if_ovpn/ca.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAmdqTuzCB4m1rTUfik1C17dLRt0QZG/KGTNjwcjF7+l3PfvV9
+IvjSIFDb09Vmx4xU6xAisiiWGZripPK0zJT1UR8lGUGPyF2VsilvwUOy8w4XIL7v
+4bEd2QBCUVXl9SPsnJ2fGJj4vztbtYRVpPL1lG24heG+RVlhMfO9XBfAvl7kR1wk
+oex1qbj23LiuzoITsnToZifCYuT+cPC9lmEdMHlX68166tGjErJi9Rd49MKqHpwa
+HZqzTrUnFTWPW+PdxM1WU/KY7zkS2XW3i4QeNfItUrb44Jxq5AafpBPTbGoUtGxu
+TjaBY6SwWPnQ0NBW2nvUkgwCbDPPbKL5yrPWqp3I6V8t4/aijDWNhrceGG3kSz3S
+bpiR4X/Tvd4tb+e5WygPns9nWucKVf71mzbgsA3++ddHWAr+cBmy/0I21F1a33JN
+Kar+gZmPiRZTXnhfFEt9wNr2CPlf6D1njDphom2xrxdo56U9acGD8lcut0TvfUPD
+INGqVI9iDDNTqDcKCNFXf9wmz9FrFYEC7VN2l3ZPkpt4uK9gmeE0CkTQngLFeu51
++CUBplXy0bRIrwXTtJ3S5LhJrzcjUGEiMAVyMFwzG/WBY1k33TdXR62YKQjA42Wp
+NJYo1gZX70RDWagcMNdFQrXgd73kBDtk1p2Oypm4MaX3bkdbjCOTYctrjYUCAwEA
+AQKCAgBCSLw6+nQK5E9FVUIVa8Flu90kUs9qjfs0YoZ8/DrIq9/4d7U4+vA0ggGj
+ax5DvH7dYu8/yGKal3Mr03D3bvmdwIhQMEHM9hzHTDjcj9kqBBkMv5ZDqvYMBqOL
+vTE8gwSumO3xP/5zDRS+yEvJq+8Hypmj/JTn9dD9H2Cmq4kbu3AoGleh92jd/VVF
+1L9jMwFfciSp1llWGAhTCjTz89UKjEzHx5g8UKMsK/ScWUeAU9nNJD3QIVU9BKjY
+52FjGnHyFvEpa6xd7D3luGSiVAJcBYFnUHM6+cjHgt8GZpBA9hcDgG+pfKwUDOD5
+BG/ldszkhlMXtNraWRv1VuW9YeuKnTwagxNLuqYOe2PyFq9sYcpHP8pSspKlun6M
+JnTZUrb8E6DdteW9ITEmtTH2jYIR0COgRYg0xFVbPjS1D2JH94H1pXeDGw8UgdGX
+H1QhrQqtBafqweWdPEoGLG7dhZqSCptuYzA/o+53oFo/B0EPmG0G5oAx1d0VJrVR
+6+bEfX5+3eWnAnTw138WAczXlVIvQoD+r7TJY+smD2RCYAJChI/3ZPiQkhj/WOMV
+ueELkapracgnsGxcT+UyqV1351sNkje7+DwKUsnP/vJZxfV0LGPIMx9ykYEwKpKh
+EvDjusV7Y1FOYqJDkl0veIG532L4ndwu5SZCBdcTzmSUlQLgoQKCAQEAxx29a5SU
+YupoLbEPw3gWoGFCbcDQ9EJ3N/s3g4T34QeBWzpwDhx2NZsNbLAn9LXYUTRAd8gr
++3Isu5WY71Kctk8Q134eBB0Gsnu51j1LUeorgUlKgQ3nCbADti0sKRv8jSaGyTXi
+r42qUOo9n+gQUdNVdKPNvy8gkcp2A1RfqxedZVZ5UcC0xVdoF661sFkLBGZXx6HP
+y8HK3qk0W3KkVMpbj3TH/URaA7bEYuY9J2StV4ywP8oM+MdRvR5067niI11kuQ6B
+JO+vQcbyWgnP5o0gWuh6WlwwXkXxazPRMwq3XuBbBxjs6VirSdrWHWW+oLacpFcD
+Y/eyRW5caN+fSQKCAQEAxc6ZCoxwFPV3lf82lMKaXgMMM/9Z1R0L6+yqUmqZafbC
+kxib68Z7a2jY1FgAy5903M/MOv+sL/OON7xWbc6+fsyn2XoOvQwdrdst2olEmUi1
+UuxVHTg1gNNP9MW0YAHJVernCYyQc0BI+aVKL55IpFBudiiPe8Y4qXBy5cwynJ7w
+TQACy4jv/DOdcib9JWEzE6RB2Q7ClbIaVztnYFOI4bPSabsiJnAQPKI18ka7k0CR
+0mVYUxIOJ2N84+XV1wZ2MeVfUzlwzHmx7+vPdkCiwdKrbtpjoyv4DGCxvIAudGLm
+0TcFVOK9oCil7qLLzbgFPyYBZX7gxfxSJvcgFMkwXQKCAQEAwLucuiFbcFOM+41D
+wOTgoeUCs9HLcGNVmq5kEb4HYZ4uK+vowv/xu/mViPsJ8eiCtjdpn4f2arEdc4Ve
+P2krn5vwpWXCECE4dlMkkqdJ3MRZ0A7tOvYGCG6DaTdYY4JfdxEvrlumTF9H2IKj
+m8C46zswoHJdmQ047WWXzalB4Q4+n/SQAf4R/GKaszG9VDEcZOnbVbTeuk+e2t/V
+eh8BycEF11omqpQavTWP6lsKHrNoxjG7+ELPQ49LI0/zxKhsp+aitC3B+8q1TWoQ
+8+5Detpn0xbsN8K8XsQ85pOFj250CDYKZlhOGaBmTFqynkn5tv6LqNdAxObhfCtS
+74BlYQKCAQByzuS44KY1I/vSzZxKX2Dla/NrQqxLK16+AlEhIMoGXLi2U7Q79qmv
+v90J8kIT7WsQtnMdU0QHWN+UrfWkKjkas4JAkb14ME4RmINWshFkvnSvuof0O6mi
+KgPgV9fHWYIYIg0S18kHe6pfa3ZRiRc0d5KFdilBd91vStsFUa2WhhGHP5hftg1E
+Xljl5odLaM0Se2XUq+J4rDTpqIrpt9Jc3dgkkf7SPHzQFH4nLrK0VufMLBJFtNcO
+OYpFZCLneNKlRzI6xb4YkBGc5Us2oXFV+gaSgqMOE/kWhhDjDaro1naNu9eWWzwg
+dzdH+Kk9r68r5c0tsaSYhUjRYOH37oXpAoIBAQDAqjYwd64AKFnppbt5eAJtdJtd
+BatS6DfNxYl6jmF0E9+ZUIALdmyxB/Y8Grng5kpd8VQHW5LDzX+ABrrD99+hSPDe
+quq6S6JSvXfrGa5EGMkC6zkPFvppjYj4u+VVqn0sRKPLAa3tmXLECJ+lORX4LVhe
+rG2/AmvG5YVqYBTgbBi+cYXToHXUp1D/qQLXN+8PvCvvWVCzfVYr25TsD+5UEbvc
+TQA9WtwHy/xjzLx11IrI9xlDBPzfGQVStnroP6MkMe3ACc0pg/17ktY5MiFnzLpK
+o58qMXWZMrYyDvlo9PBOYLFXL41yJMhZeJjP80Kk/L1EnDiTDHuU8qFltbD3
+-----END RSA PRIVATE KEY-----
diff --git a/tests/sys/net/if_ovpn/ccd/Makefile b/tests/sys/net/if_ovpn/ccd/Makefile
new file mode 100644
index 000000000000..2d3fefa1f321
--- /dev/null
+++ b/tests/sys/net/if_ovpn/ccd/Makefile
@@ -0,0 +1,8 @@
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/net/if_ovpn/ccd
+
+${PACKAGE}FILES+= \
+ Test-Client2
+
+.include <bsd.test.mk>
diff --git a/tests/sys/net/if_ovpn/ccd/Test-Client2 b/tests/sys/net/if_ovpn/ccd/Test-Client2
new file mode 100644
index 000000000000..b378ad0d4394
--- /dev/null
+++ b/tests/sys/net/if_ovpn/ccd/Test-Client2
@@ -0,0 +1,2 @@
+iroute 203.0.113.0 255.255.255.0
+ifconfig-push 198.51.100.3 255.255.255.0
diff --git a/tests/sys/net/if_ovpn/client.crt b/tests/sys/net/if_ovpn/client.crt
new file mode 100644
index 000000000000..92ba8ad1ba4b
--- /dev/null
+++ b/tests/sys/net/if_ovpn/client.crt
@@ -0,0 +1,123 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1048686 (0x10006e)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: C=KG, ST=NA, L=BISHKEK, O=OpenVPN-TEST/emailAddress=me@myhost.mydomain
+ Validity
+ Not Before: Apr 27 15:00:37 2022 GMT
+ Not After : Apr 3 15:00:37 2122 GMT
+ Subject: C=KG, ST=NA, O=OpenVPN-TEST, CN=Test-Client/emailAddress=me@myhost.mydomain
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public-Key: (4096 bit)
+ Modulus:
+ 00:f0:86:38:95:a6:e0:76:52:eb:25:57:8b:47:2a:
+ e1:17:db:9b:84:64:33:0c:70:91:74:ae:f5:39:cc:
+ a7:9e:37:3c:d2:32:df:cd:57:8e:21:00:c9:43:b6:
+ b3:d3:a8:fc:15:84:c4:15:86:d8:d6:69:5b:d4:9b:
+ 19:c9:8e:71:9f:1d:12:72:89:e3:7b:db:80:6e:dc:
+ 69:fd:98:eb:22:33:ac:59:4a:57:bd:6d:48:60:fd:
+ 89:8c:94:fd:64:24:cf:98:ea:31:c0:20:50:38:2e:
+ c6:f3:67:54:c1:ea:70:13:a4:34:fd:38:59:9c:64:
+ bf:11:f1:ed:01:46:08:31:c8:de:32:13:47:38:81:
+ 84:4d:f6:00:d3:8c:ee:6f:71:a1:5a:b1:34:60:95:
+ 25:67:7f:4c:d5:86:09:0b:dc:75:a1:e5:aa:05:74:
+ 0f:e8:f1:b1:2a:63:be:53:cb:d8:a3:9f:f1:1a:c6:
+ fb:c8:c5:ec:6c:34:86:13:c7:e4:83:d2:11:66:2f:
+ ee:8e:19:8e:e5:da:0c:59:09:b3:c6:35:aa:7e:88:
+ 15:eb:53:29:cd:f6:a5:c4:d2:af:72:28:b0:a8:f5:
+ a4:38:5b:ab:9f:e0:db:f1:b9:e4:ca:d0:e8:c7:dd:
+ 95:81:c9:75:e2:23:74:30:59:b0:ca:74:b1:fe:86:
+ 0d:7c:5a:f3:5d:bb:42:75:7d:48:51:d7:6a:ee:93:
+ d2:e4:30:2a:5c:65:56:f4:5e:74:97:e1:7e:ae:2c:
+ f7:da:95:12:e0:1a:dd:f5:07:c0:4b:85:90:45:d1:
+ b0:61:ec:90:ab:20:c3:55:78:6c:da:bb:48:4f:33:
+ 61:04:4f:8d:1a:e4:57:8a:cb:e1:ea:db:8f:f3:9f:
+ d4:98:5f:27:dd:20:9e:76:35:54:75:ab:ef:74:6b:
+ 77:93:02:e9:79:a4:0b:83:a4:ff:fd:3d:bd:a5:e3:
+ 96:b8:78:13:5a:91:7d:bd:a2:90:54:9d:07:87:fd:
+ 62:e2:d9:01:9c:50:8b:d4:7c:a4:28:f6:31:2b:9a:
+ f1:6f:6f:85:71:7e:71:b2:bc:6d:97:e7:fc:8c:5e:
+ 97:85:c1:6a:61:10:c1:e5:b4:db:52:db:20:e3:42:
+ 8f:fa:48:4c:27:87:0f:05:0d:6d:93:4e:2f:a1:36:
+ 58:16:73:9f:61:68:d5:cb:67:1b:5d:41:c2:e6:6f:
+ e6:ca:e1:f3:b6:92:c1:48:72:3f:a9:84:3b:1f:9b:
+ 3d:73:85:46:f2:f7:dc:5e:de:e9:18:47:24:f4:7d:
+ 46:e1:0e:2e:5a:4a:9a:4e:f1:e5:7c:71:d0:7b:9e:
+ 62:3d:43:a3:62:9e:55:0a:77:a6:98:31:b7:a1:11:
+ b3:58:35
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ Netscape Comment:
+ OpenSSL Generated Certificate
+ X509v3 Subject Key Identifier:
+ FC:B5:C3:D3:A0:B5:6C:8F:3D:52:4F:07:03:08:0A:BD:9D:A8:94:3A
+ X509v3 Authority Key Identifier:
+ keyid:88:07:18:C1:FE:2E:09:2E:7D:E0:B9:1B:83:A9:54:DE:09:B1:16:7E
+
+ Signature Algorithm: sha256WithRSAEncryption
+ 5d:26:45:db:9f:d6:c7:86:a0:22:85:f2:fd:4a:41:3b:3a:2f:
+ 81:1b:93:38:e7:0e:81:bf:bc:5f:47:95:94:92:a6:12:47:78:
+ 74:68:65:fd:e0:99:b2:08:d5:2b:fa:aa:05:13:88:7f:00:e8:
+ cb:17:b0:04:4d:d1:6e:80:4b:11:1b:71:45:b9:61:c2:66:14:
+ e6:86:d9:13:a0:7d:63:14:fe:41:7c:86:42:c8:53:0e:04:da:
+ 1b:28:cc:a4:e8:ff:f4:b0:73:4b:c0:a3:d7:be:7c:2c:2a:e5:
+ aa:3e:8b:ce:07:8b:b2:62:a4:7d:f0:b3:75:39:02:10:f0:a8:
+ b9:d8:1f:70:4d:d1:b0:68:46:43:02:bc:8b:15:e6:df:5d:c3:
+ ae:e3:89:80:48:64:35:9b:0b:2b:d6:75:38:96:0d:6c:f1:cb:
+ 03:91:ec:75:58:3b:fb:f7:78:cf:38:58:9b:a6:04:48:fc:aa:
+ c0:fa:a3:9c:da:c3:26:e0:82:9a:0e:0e:2b:2f:50:00:56:7f:
+ d5:ab:87:61:dd:bb:34:23:af:38:5f:ea:40:72:cf:46:38:31:
+ 8c:a3:68:1c:a1:84:62:03:05:7e:92:46:1b:0f:e2:a3:47:d3:
+ a2:c5:f9:e8:7b:d1:0a:20:63:d6:ca:01:05:7f:3f:4c:4f:d5:
+ 6c:51:e8:ee:82:35:37:9b:1e:e8:76:6d:05:50:88:43:cc:8c:
+ 20:81:09:a9:76:57:97:7b:bc:38:14:d5:3e:38:b1:a5:7e:51:
+ b2:67:9b:50:05:00:1b:24:90:cc:57:e1:b1:27:3e:50:09:0b:
+ bc:9c:0e:b3:d1:08:80:30:d6:28:85:6c:4d:9f:d2:ea:96:de:
+ 6f:0d:25:0c:03:94:65:4e:88:aa:d8:81:78:49:44:09:4d:85:
+ c8:db:8c:57:be:6d:49:97:2b:a5:28:97:e3:99:ea:f1:b7:46:
+ 2e:a6:dc:85:1c:d6:66:6e:dd:a9:db:d6:d3:34:71:95:0a:6d:
+ bb:47:b5:18:b5:7e:95:92:9b:53:f9:9b:a3:6c:09:2c:e2:d0:
+ d3:9a:4e:31:21:0b:18:b6:b4:fc:65:8a:a2:e5:b9:c8:f5:4a:
+ 92:3f:4e:de:db:e5:3a:bf:22:4e:39:b6:ae:09:d1:1b:84:f5:
+ e6:53:6d:c2:8e:24:26:58:58:80:aa:8d:dd:54:21:9e:7b:c1:
+ 01:f8:94:cc:a6:c0:32:9a:4a:0c:b4:f5:b8:e7:b0:5c:c2:18:
+ 57:1e:49:93:72:c9:01:91:b7:ea:a1:0b:fa:f0:33:0d:ff:55:
+ b6:fb:07:30:85:47:ab:cd:05:4e:cc:a2:49:91:0b:7d:b7:a4:
+ bb:43:ea:bb:f9:95:bb:e9
+-----BEGIN CERTIFICATE-----
+MIIFyjCCA7KgAwIBAgIDEABuMA0GCSqGSIb3DQEBCwUAMGYxCzAJBgNVBAYTAktH
+MQswCQYDVQQIDAJOQTEQMA4GA1UEBwwHQklTSEtFSzEVMBMGA1UECgwMT3BlblZQ
+Ti1URVNUMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21haW4wIBcNMjIw
+NDI3MTUwMDM3WhgPMjEyMjA0MDMxNTAwMzdaMGoxCzAJBgNVBAYTAktHMQswCQYD
+VQQIDAJOQTEVMBMGA1UECgwMT3BlblZQTi1URVNUMRQwEgYDVQQDDAtUZXN0LUNs
+aWVudDEhMB8GCSqGSIb3DQEJARYSbWVAbXlob3N0Lm15ZG9tYWluMIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA8IY4labgdlLrJVeLRyrhF9ubhGQzDHCR
+dK71Ocynnjc80jLfzVeOIQDJQ7az06j8FYTEFYbY1mlb1JsZyY5xnx0Sconje9uA
+btxp/ZjrIjOsWUpXvW1IYP2JjJT9ZCTPmOoxwCBQOC7G82dUwepwE6Q0/ThZnGS/
+EfHtAUYIMcjeMhNHOIGETfYA04zub3GhWrE0YJUlZ39M1YYJC9x1oeWqBXQP6PGx
+KmO+U8vYo5/xGsb7yMXsbDSGE8fkg9IRZi/ujhmO5doMWQmzxjWqfogV61Mpzfal
+xNKvciiwqPWkOFurn+Db8bnkytDox92Vgcl14iN0MFmwynSx/oYNfFrzXbtCdX1I
+Uddq7pPS5DAqXGVW9F50l+F+riz32pUS4Brd9QfAS4WQRdGwYeyQqyDDVXhs2rtI
+TzNhBE+NGuRXisvh6tuP85/UmF8n3SCedjVUdavvdGt3kwLpeaQLg6T//T29peOW
+uHgTWpF9vaKQVJ0Hh/1i4tkBnFCL1HykKPYxK5rxb2+FcX5xsrxtl+f8jF6XhcFq
+YRDB5bTbUtsg40KP+khMJ4cPBQ1tk04voTZYFnOfYWjVy2cbXUHC5m/myuHztpLB
+SHI/qYQ7H5s9c4VG8vfcXt7pGEck9H1G4Q4uWkqaTvHlfHHQe55iPUOjYp5VCnem
+mDG3oRGzWDUCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3Bl
+blNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFPy1w9OgtWyPPVJP
+BwMICr2dqJQ6MB8GA1UdIwQYMBaAFIgHGMH+LgkufeC5G4OpVN4JsRZ+MA0GCSqG
+SIb3DQEBCwUAA4ICAQBdJkXbn9bHhqAihfL9SkE7Oi+BG5M45w6Bv7xfR5WUkqYS
+R3h0aGX94JmyCNUr+qoFE4h/AOjLF7AETdFugEsRG3FFuWHCZhTmhtkToH1jFP5B
+fIZCyFMOBNobKMyk6P/0sHNLwKPXvnwsKuWqPovOB4uyYqR98LN1OQIQ8Ki52B9w
+TdGwaEZDAryLFebfXcOu44mASGQ1mwsr1nU4lg1s8csDkex1WDv793jPOFibpgRI
+/KrA+qOc2sMm4IKaDg4rL1AAVn/Vq4dh3bs0I684X+pAcs9GODGMo2gcoYRiAwV+
+kkYbD+KjR9Oixfnoe9EKIGPWygEFfz9MT9VsUejugjU3mx7odm0FUIhDzIwggQmp
+dleXe7w4FNU+OLGlflGyZ5tQBQAbJJDMV+GxJz5QCQu8nA6z0QiAMNYohWxNn9Lq
+lt5vDSUMA5RlToiq2IF4SUQJTYXI24xXvm1JlyulKJfjmerxt0YuptyFHNZmbt2p
+29bTNHGVCm27R7UYtX6VkptT+ZujbAks4tDTmk4xIQsYtrT8ZYqi5bnI9UqSP07e
+2+U6vyJOObauCdEbhPXmU23CjiQmWFiAqo3dVCGee8EB+JTMpsAymkoMtPW457Bc
+whhXHkmTcskBkbfqoQv68DMN/1W2+wcwhUerzQVOzKJJkQt9t6S7Q+q7+ZW76Q==
+-----END CERTIFICATE-----
diff --git a/tests/sys/net/if_ovpn/client.key b/tests/sys/net/if_ovpn/client.key
new file mode 100644
index 000000000000..7ad255b52556
--- /dev/null
+++ b/tests/sys/net/if_ovpn/client.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/tests/sys/net/if_ovpn/client2.crt b/tests/sys/net/if_ovpn/client2.crt
new file mode 100644
index 000000000000..83aec7eedaa0
--- /dev/null
+++ b/tests/sys/net/if_ovpn/client2.crt
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFbTCCA1UCFC6I/36G1ZhmNxvabxL+BppMd38jMA0GCSqGSIb3DQEBCwUAMGYx
+CzAJBgNVBAYTAktHMQswCQYDVQQIDAJOQTEQMA4GA1UEBwwHQklTSEtFSzEVMBMG
+A1UECgwMT3BlblZQTi1URVNUMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlk
+b21haW4wIBcNMjIwNjE1MTIwNzQzWhgPMjEyMjA1MjIxMjA3NDNaMH4xCzAJBgNV
+BAYTAktHMQswCQYDVQQIDAJOQTEQMA4GA1UEBwwHQklTSEtFSzEVMBMGA1UECgwM
+T3BlblZQTi1URVNUMRUwEwYDVQQDDAxUZXN0LUNsaWVudDIxIjAgBgkqhkiG9w0B
+CQEWE21lMkBteWhvc3QubXlkb21haW4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQDteW+ZsfahA+NJHgTycmGejCIw/jwbVpaFlwYLBe39OsDK44XUjVn1
+i8k4Vce9F1UcGeY9scyLZ797Ify5Sm59ejVkm2EriuA/jQeNpr8A0HxjcmEcn/G5
+5cM/zZYj7f9Bfj+XVgHG0zHVfD9PItwEUHKNp3hVr/86FwbnHKpcQK/QjYlDOFZB
+wiIxDUSpaMLT7eFUqLOem1ZmnBd0qT3GPjBJsbpzzK+LZd9V0brvIc8XCnoUGs2V
+wzsg8oRCpVpQsKUNrW3mid9lCJQvRAm6j0/14nZHm3sP5BroOTOzcLKiWuYMwizs
+QkkEYP0G9ZtipbIhAdnDB4FgjF+9arH3IXw3UZxXNPguA2UasuqcCwiwyp2aPNAf
+G0sIv3rvOGyTp0QfhrsQW0/xcJxfYlMONHft9kvuhC9ITKaH1ei8iQuFhm2QZCrO
+f/jEf8d6nckpM3GAp/WIze49HZgdVfAIGV3+DcF2u/gwBjKsRe9W4KN5GxLQEx0x
+gWLJN34O340N/Sy+NX82KP/kO/Zb3N1rKVmDIZx49ZJy1eN/Kt7pl0+AqifZzneu
+pLl9nziwe0csUtCQbIJHZQQon6vwDQVR3VuGwMra/sayxZDY5IOwueEm62/cJhoQ
+rxGknCM99WPhJau3S0gBV1nsH7M37AQxyHhC7q3ambdpEqzUDzf3XwIDAQABMA0G
+CSqGSIb3DQEBCwUAA4ICAQBtV12w72Yflc0bIJ3IsnQ1om820Fx8/0Ndr9GD8vov
+XXupazyuQmfRBpB0qcVR0tStxJrf8S19WRiLFM2UJexT4H8A3Rp788IESYo5JytV
+kAvTtJ+LE74EIRXt9M3II5vFaGiFRyozN7Vdr8mUJO5sXNJaZPQkOsAta652J2JV
+Qy5rOgAUEylUWZMVKkmSAdU4LGVgJC86XA9eQGtqtbXj09v3YW/EPsobCi0YbFYS
+5WgGCunqw7zT4Ko8KP+horaV/bQWZKnKIb3e5xDh9Zkm48RBRU4pYZ0VoOSp1xAy
+qzn/818NVPfhKWSXxLFBVWgsIzLO825vH5WEaQNgg+vfq2/AZcfl6UNGn5dufkAk
+73t5dNq46H2Z6t02dfOQ7U4tduCUPbWmPXD/kjFqryQ4GXNR8TMKLf6GZRKD5nOt
+KRfrkPL4tbsWL8WY9c5KQRC/vaLXETuuavDMVp0AFwTz846tB2njjyTc5jFcTgfY
+X8PgUw/miJszbQd6Z9HTDTTH0osv+VNXE5MCYPWe3QaobBJGRjaPJyO5OA/SXZa+
++9XCXyEBdVvckHpc4yHK9ATlCeiouDi45lzlnXpvuQz6VXwB8v4JKB/qqFlrzO2E
+09yAyw3qPH43TBbgvJwtpD+g6k9VvE7ojHS4fl2epyQAm/orT6RLLHMHEkaYqRCU
+2A==
+-----END CERTIFICATE-----
diff --git a/tests/sys/net/if_ovpn/client2.key b/tests/sys/net/if_ovpn/client2.key
new file mode 100644
index 000000000000..7e5c6857de1c
--- /dev/null
+++ b/tests/sys/net/if_ovpn/client2.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/tests/sys/net/if_ovpn/dh.pem b/tests/sys/net/if_ovpn/dh.pem
new file mode 100644
index 000000000000..8eda59aa139e
--- /dev/null
+++ b/tests/sys/net/if_ovpn/dh.pem
@@ -0,0 +1,8 @@
+-----BEGIN DH PARAMETERS-----
+MIIBCAKCAQEArdnA32xujHPlPI+jPffHSoMUZ+b5gRz1H1Lw9//Gugm5TAsRiYrB
+t2BDSsMKvAjyqN+i5SJv4TOk98kRRKB27iPvyXmiL945VaDQl/UehCySjYlGFUjW
+9nuo+JwQxeSbw0TLiSYoYJZQ8X1CxPl9mgJl277O4cW1Gc8I/bWa+ipU/4K5wv3h
+GI8nt+6A0jN3M/KebotMP101G4k0l0qsY4oRMTmP+z3oAP0qU9NZ1jiuMFVzRlNp
+5FdYF7ctrH+tBF+QmyT4SRKSED4wE4oX6gp420NaBhIEQifIj75wlMDtxQlpkN+x
+QkjsEbPlaPKHGQ4uupssChVUi8IM2yq5EwIBAg==
+-----END DH PARAMETERS-----
diff --git a/tests/sys/net/if_ovpn/if_ovpn.sh b/tests/sys/net/if_ovpn/if_ovpn.sh
new file mode 100644
index 000000000000..9dafce2242d8
--- /dev/null
+++ b/tests/sys/net/if_ovpn/if_ovpn.sh
@@ -0,0 +1,1504 @@
+##
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2022 Rubicon Communications, LLC ("Netgate")
+#
+# 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.
+
+. $(atf_get_srcdir)/utils.subr
+. $(atf_get_srcdir)/../../netpfil/pf/utils.subr
+
+atf_test_case "4in4" "cleanup"
+4in4_head()
+{
+ atf_set descr 'IPv4 in IPv4 tunnel'
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+4in4_body()
+{
+ ovpn_init
+
+ l=$(vnet_mkepair)
+
+ vnet_mkjail a ${l}a
+ jexec a ifconfig ${l}a 192.0.2.1/24 up
+ vnet_mkjail b ${l}b
+ jexec b ifconfig ${l}b 192.0.2.2/24 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore jexec a ping -c 1 192.0.2.2
+
+ ovpn_start a "
+ dev ovpn0
+ dev-type tun
+ proto udp4
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ local 192.0.2.1
+ server 198.51.100.0 255.255.255.0
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 100 600
+ "
+ ovpn_start b "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 192.0.2.1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+
+ # Give the tunnel time to come up
+ sleep 10
+
+ atf_check -s exit:0 -o ignore jexec b ping -c 1 198.51.100.1
+
+ echo 'foo' | jexec b nc -u -w 2 192.0.2.1 1194
+ atf_check -s exit:0 -o ignore jexec b ping -c 3 198.51.100.1
+
+ # Test routing loop protection
+ jexec b route add 192.0.2.1 198.51.100.1
+ atf_check -s exit:2 -o ignore jexec b ping -t 1 -c 1 198.51.100.1
+}
+
+4in4_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_test_case "bz283426" "cleanup"
+bz283426_head()
+{
+ atf_set descr 'FreeBSD Bugzilla 283426'
+ atf_set require.user root
+ atf_set require.progs openvpn python3
+}
+
+bz283426_body()
+{
+ ovpn_init
+
+ l=$(vnet_mkepair)
+
+ vnet_mkjail a ${l}a
+ jexec a ifconfig ${l}a 192.0.2.1/24 up
+ vnet_mkjail b ${l}b
+ jexec b ifconfig ${l}b 192.0.2.2/24 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore jexec a ping -c 1 192.0.2.2
+
+ ovpn_start a "
+ dev ovpn0
+ dev-type tun
+ proto udp4
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ bind 0.0.0.0:1194
+ server 198.51.100.0 255.255.255.0
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 100 600
+ "
+ ovpn_start b "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 192.0.2.1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+
+ # Give the tunnel time to come up
+ sleep 10
+
+ atf_check -s exit:0 -o ignore jexec b ping -c 1 198.51.100.1
+
+ # Send a broadcast packet in the outer link.
+ echo "import socket as sk
+s = sk.socket(sk.AF_INET, sk.SOCK_DGRAM)
+s.setsockopt(sk.SOL_SOCKET, sk.SO_BROADCAST, 1)
+s.sendto(b'x' * 1000, ('192.0.2.255', 1194))" | jexec b python3
+
+ atf_check -s exit:0 -o ignore jexec b ping -c 3 198.51.100.1
+}
+
+bz283426_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_test_case "4mapped" "cleanup"
+4mapped_head()
+{
+ atf_set descr 'IPv4 mapped addresses'
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+4mapped_body()
+{
+ ovpn_init
+
+ l=$(vnet_mkepair)
+
+ vnet_mkjail a ${l}a
+ jexec a ifconfig ${l}a 192.0.2.1/24 up
+ vnet_mkjail b ${l}b
+ jexec b ifconfig ${l}b 192.0.2.2/24 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore jexec a ping -c 1 192.0.2.2
+
+ #jexec a ifconfig ${l}a
+
+ ovpn_start a "
+ dev ovpn0
+ dev-type tun
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ server 198.51.100.0 255.255.255.0
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 100 600
+ "
+ ovpn_start b "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 192.0.2.1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+
+ # Give the tunnel time to come up
+ sleep 10
+
+ atf_check -s exit:0 -o ignore jexec b ping -c 3 198.51.100.1
+}
+
+4mapped_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_test_case "6in4" "cleanup"
+6in4_head()
+{
+ atf_set descr 'IPv6 in IPv4 tunnel'
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+6in4_body()
+{
+ ovpn_init
+
+ l=$(vnet_mkepair)
+
+ vnet_mkjail a ${l}a
+ jexec a ifconfig ${l}a 192.0.2.1/24 up
+ vnet_mkjail b ${l}b
+ jexec b ifconfig ${l}b 192.0.2.2/24 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore jexec a ping -c 1 192.0.2.2
+
+ ovpn_start a "
+ dev ovpn0
+ dev-type tun
+ proto udp
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ local 192.0.2.1
+ server-ipv6 2001:db8:1::/64
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 100 600
+ "
+ ovpn_start b "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 192.0.2.1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+
+ # Give the tunnel time to come up
+ sleep 10
+
+ atf_check -s exit:0 -o ignore jexec b ping6 -c 3 2001:db8:1::1
+}
+
+6in4_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_test_case "4in6" "cleanup"
+4in6_head()
+{
+ atf_set descr 'IPv4 in IPv6 tunnel'
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+4in6_body()
+{
+ ovpn_init
+
+ l=$(vnet_mkepair)
+
+ vnet_mkjail a ${l}a
+ jexec a ifconfig ${l}a inet6 2001:db8::1/64 up no_dad
+ vnet_mkjail b ${l}b
+ jexec b ifconfig ${l}b inet6 2001:db8::2/64 up no_dad
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore jexec a ping6 -c 1 2001:db8::2
+
+ ovpn_start a "
+ dev ovpn0
+ dev-type tun
+ proto udp6
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ local 2001:db8::1
+ server 198.51.100.0 255.255.255.0
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 100 600
+ "
+ ovpn_start b "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 2001:db8::1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+
+ dd if=/dev/random of=test.img bs=1024 count=1024
+ cat test.img | jexec a nc -N -l 1234 &
+
+ # Give the tunnel time to come up
+ sleep 10
+
+ atf_check -s exit:0 -o ignore jexec b ping -c 3 198.51.100.1
+
+ # MTU sweep
+ for i in `seq 1000 1500`
+ do
+ atf_check -s exit:0 -o ignore jexec b \
+ ping -c 1 -s $i 198.51.100.1
+ done
+
+ rcvmd5=$(jexec b nc -N -w 3 198.51.100.1 1234 | md5)
+ md5=$(md5 test.img)
+
+ if [ $md5 != $rcvmd5 ];
+ then
+ atf_fail "Transmit corruption!"
+ fi
+}
+
+4in6_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_test_case "6in6" "cleanup"
+6in6_head()
+{
+ atf_set descr 'IPv6 in IPv6 tunnel'
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+6in6_body()
+{
+ ovpn_init
+
+ l=$(vnet_mkepair)
+
+ vnet_mkjail a ${l}a
+ jexec a ifconfig ${l}a inet6 2001:db8::1/64 up no_dad
+ vnet_mkjail b ${l}b
+ jexec b ifconfig ${l}b inet6 2001:db8::2/64 up no_dad
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore jexec a ping6 -c 1 2001:db8::2
+
+ ovpn_start a "
+ dev ovpn0
+ dev-type tun
+ proto udp6
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ local 2001:db8::1
+ server-ipv6 2001:db8:1::/64
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 100 600
+ "
+ ovpn_start b "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 2001:db8::1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+
+ # Give the tunnel time to come up
+ sleep 10
+
+ atf_check -s exit:0 -o ignore jexec b ping6 -c 3 2001:db8:1::1
+ atf_check -s exit:0 -o ignore jexec b ping6 -c 3 -z 16 2001:db8:1::1
+
+ # Test routing loop protection
+ jexec b route add -6 2001:db8::1 2001:db8:1::1
+ atf_check -s exit:2 -o ignore jexec b ping6 -t 1 -c 3 2001:db8:1::1
+}
+
+6in6_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_test_case "linklocal" "cleanup"
+linklocal_head()
+{
+ atf_set descr 'Use IPv6 link-local addresses'
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+linklocal_body()
+{
+ ovpn_init
+ ovpn_check_version 2.7.0
+
+ l=$(vnet_mkepair)
+
+ vnet_mkjail a ${l}a
+ jexec a ifconfig ${l}a inet6 fe80::a/64 up no_dad
+ vnet_mkjail b ${l}b
+ jexec b ifconfig ${l}b inet6 fe80::b/64 up no_dad
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore jexec a ping6 -c 1 fe80::b%${l}a
+
+ ovpn_start a "
+ dev ovpn0
+ dev-type tun
+ proto udp6
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ local fe80::a%${l}a
+ server-ipv6 2001:db8:1::/64
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 100 600
+ "
+ ovpn_start b "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote fe80::a%${l}b
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+
+ # Give the tunnel time to come up
+ sleep 10
+ jexec a ifconfig
+
+ atf_check -s exit:0 -o ignore jexec b ping6 -c 3 2001:db8:1::1
+ atf_check -s exit:0 -o ignore jexec b ping6 -c 3 -z 16 2001:db8:1::1
+}
+
+linklocal_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_test_case "timeout_client" "cleanup"
+timeout_client_head()
+{
+ atf_set descr 'IPv4 in IPv4 tunnel'
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+timeout_client_body()
+{
+ ovpn_init
+
+ l=$(vnet_mkepair)
+
+ vnet_mkjail a ${l}a
+ jexec a ifconfig ${l}a 192.0.2.1/24 up
+ jexec a ifconfig lo0 127.0.0.1/8 up
+ vnet_mkjail b ${l}b
+ jexec b ifconfig ${l}b 192.0.2.2/24 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore jexec a ping -c 1 192.0.2.2
+
+ ovpn_start a "
+ dev ovpn0
+ dev-type tun
+ proto udp4
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ local 192.0.2.1
+ server 198.51.100.0 255.255.255.0
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 2 10
+
+ management 192.0.2.1 1234
+ "
+ ovpn_start b "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 192.0.2.1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 2 10
+ "
+
+ # Give the tunnel time to come up
+ sleep 10
+
+ atf_check -s exit:0 -o ignore jexec b ping -c 3 198.51.100.1
+
+ # Kill the client
+ jexec b killall openvpn
+
+ # Now wait for the server to notice
+ sleep 15
+
+ while echo "status" | jexec a nc -N 192.0.2.1 1234 | grep 192.0.2.2; do
+ echo "Client disconnect not discovered"
+ sleep 1
+ done
+}
+
+timeout_client_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_test_case "explicit_exit" "cleanup"
+explicit_exit_head()
+{
+ atf_set descr 'Test explicit exit notification'
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+explicit_exit_body()
+{
+ ovpn_init
+
+ l=$(vnet_mkepair)
+
+ vnet_mkjail a ${l}a
+ jexec a ifconfig ${l}a 192.0.2.1/24 up
+ jexec a ifconfig lo0 127.0.0.1/8 up
+ vnet_mkjail b ${l}b
+ jexec b ifconfig ${l}b 192.0.2.2/24 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore jexec a ping -c 1 192.0.2.2
+
+ ovpn_start a "
+ dev ovpn0
+ dev-type tun
+ proto udp4
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ local 192.0.2.1
+ server 198.51.100.0 255.255.255.0
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ management 192.0.2.1 1234
+ "
+ ovpn_start b "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 192.0.2.1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ explicit-exit-notify
+ "
+
+ # Give the tunnel time to come up
+ sleep 10
+
+ atf_check -s exit:0 -o ignore jexec b ping -c 3 198.51.100.1
+
+ if ! echo "status" | jexec a nc -N 192.0.2.1 1234 | grep 192.0.2.2; then
+ atf_fail "Client not found in status list!"
+ fi
+
+ # Kill the client
+ jexec b killall openvpn
+
+ while echo "status" | jexec a nc -N 192.0.2.1 1234 | grep 192.0.2.2; do
+ jexec a ps auxf
+ echo "Client disconnect not discovered"
+ sleep 1
+ done
+}
+
+explicit_exit_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_test_case "multi_client" "cleanup"
+multi_client_head()
+{
+ atf_set descr 'Multiple simultaneous clients'
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+multi_client_body()
+{
+ ovpn_init
+ vnet_init_bridge
+
+ bridge=$(vnet_mkbridge)
+ srv=$(vnet_mkepair)
+ one=$(vnet_mkepair)
+ two=$(vnet_mkepair)
+
+ ifconfig ${bridge} up
+
+ ifconfig ${srv}a up
+ ifconfig ${bridge} addm ${srv}a
+ ifconfig ${one}a up
+ ifconfig ${bridge} addm ${one}a
+ ifconfig ${two}a up
+ ifconfig ${bridge} addm ${two}a
+
+ vnet_mkjail srv ${srv}b
+ jexec srv ifconfig ${srv}b 192.0.2.1/24 up
+ vnet_mkjail one ${one}b
+ jexec one ifconfig ${one}b 192.0.2.2/24 up
+ vnet_mkjail two ${two}b
+ jexec two ifconfig ${two}b 192.0.2.3/24 up
+ jexec two ifconfig lo0 127.0.0.1/8 up
+ jexec two ifconfig lo0 inet alias 203.0.113.1/24
+
+ # Sanity checks
+ atf_check -s exit:0 -o ignore jexec one ping -c 1 192.0.2.1
+ atf_check -s exit:0 -o ignore jexec two ping -c 1 192.0.2.1
+
+ jexec srv sysctl net.inet.ip.forwarding=1
+
+ ovpn_start srv "
+ dev ovpn0
+ dev-type tun
+ proto udp4
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ local 192.0.2.1
+ server 198.51.100.0 255.255.255.0
+
+ push \"route 203.0.113.0 255.255.255.0 198.51.100.1\"
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ duplicate-cn
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 100 600
+
+ client-config-dir $(atf_get_srcdir)/ccd
+ "
+ ovpn_start one "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 192.0.2.1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+ ovpn_start two "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 192.0.2.1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client2.crt
+ key $(atf_get_srcdir)/client2.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+
+ # Give the tunnel time to come up
+ sleep 10
+
+ atf_check -s exit:0 -o ignore jexec one ping -c 3 198.51.100.1
+ atf_check -s exit:0 -o ignore jexec two ping -c 3 198.51.100.1
+
+ # Client-to-client communication
+ atf_check -s exit:0 -o ignore jexec one ping -c 3 198.51.100.3
+ atf_check -s exit:0 -o ignore jexec two ping -c 3 198.51.100.2
+
+ # iroute test
+ atf_check -s exit:0 -o ignore jexec one ping -c 3 203.0.113.1
+}
+
+multi_client_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_test_case "route_to" "cleanup"
+route_to_head()
+{
+ atf_set descr "Test pf's route-to with OpenVPN tunnels"
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+route_to_body()
+{
+ pft_init
+ ovpn_init
+
+ l=$(vnet_mkepair)
+ n=$(vnet_mkepair)
+
+ vnet_mkjail a ${l}a
+ jexec a ifconfig ${l}a 192.0.2.1/24 up
+ vnet_mkjail b ${l}b ${n}a
+ jexec b ifconfig ${l}b 192.0.2.2/24 up
+ jexec b ifconfig ${n}a up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore jexec a ping -c 1 192.0.2.2
+
+ ovpn_start a "
+ dev ovpn0
+ dev-type tun
+ proto udp4
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ local 192.0.2.1
+ server 198.51.100.0 255.255.255.0
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 100 600
+ "
+ ovpn_start b "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 192.0.2.1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+
+ # Give the tunnel time to come up
+ sleep 10
+ jexec a ifconfig ovpn0 inet alias 198.51.100.254/24
+
+ # Check the tunnel
+ atf_check -s exit:0 -o ignore jexec b ping -c 1 -S 198.51.100.2 198.51.100.1
+ atf_check -s exit:0 -o ignore jexec b ping -c 1 -S 198.51.100.2 198.51.100.254
+
+ # Break our route to .254 so that we need a route-to to make things work.
+ jexec b ifconfig ${n}a 203.0.113.1/24 up
+ jexec b route add 198.51.100.254 -interface ${n}a
+
+ # Make sure it's broken.
+ atf_check -s exit:2 -o ignore jexec b ping -c 1 -S 198.51.100.2 198.51.100.254
+
+ jexec b pfctl -e
+ pft_set_rules b \
+ "pass out route-to (tun0 198.51.100.1) proto icmp from 198.51.100.2 "
+ atf_check -s exit:0 -o ignore jexec b ping -c 3 -S 198.51.100.2 198.51.100.254
+}
+
+route_to_cleanup()
+{
+ ovpn_cleanup
+ pft_cleanup
+}
+
+atf_test_case "ra" "cleanup"
+ra_head()
+{
+ atf_set descr 'Remote access with multiple clients'
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+ra_body()
+{
+ ovpn_init
+ vnet_init_bridge
+
+ bridge=$(vnet_mkbridge)
+ srv=$(vnet_mkepair)
+ lan=$(vnet_mkepair)
+ one=$(vnet_mkepair)
+ two=$(vnet_mkepair)
+
+ ifconfig ${bridge} up
+
+ ifconfig ${srv}a up
+ ifconfig ${bridge} addm ${srv}a
+ ifconfig ${one}a up
+ ifconfig ${bridge} addm ${one}a
+ ifconfig ${two}a up
+ ifconfig ${bridge} addm ${two}a
+
+ vnet_mkjail srv ${srv}b ${lan}a
+ jexec srv ifconfig lo0 inet 127.0.0.1/8 up
+ jexec srv ifconfig ${srv}b 192.0.2.1/24 up
+ jexec srv ifconfig ${lan}a 203.0.113.1/24 up
+ vnet_mkjail lan ${lan}b
+ jexec lan ifconfig lo0 inet 127.0.0.1/8 up
+ jexec lan ifconfig ${lan}b 203.0.113.2/24 up
+ jexec lan route add default 203.0.113.1
+ vnet_mkjail one ${one}b
+ jexec one ifconfig lo0 inet 127.0.0.1/8 up
+ jexec one ifconfig ${one}b 192.0.2.2/24 up
+ vnet_mkjail two ${two}b
+ jexec two ifconfig lo0 inet 127.0.0.1/8 up
+ jexec two ifconfig ${two}b 192.0.2.3/24 up
+
+ # Sanity checks
+ atf_check -s exit:0 -o ignore jexec one ping -c 1 192.0.2.1
+ atf_check -s exit:0 -o ignore jexec two ping -c 1 192.0.2.1
+ atf_check -s exit:0 -o ignore jexec srv ping -c 1 203.0.113.2
+
+ jexec srv sysctl net.inet.ip.forwarding=1
+
+ ovpn_start srv "
+ dev ovpn0
+ dev-type tun
+ proto udp4
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ local 192.0.2.1
+ server 198.51.100.0 255.255.255.0
+
+ push \"route 203.0.113.0 255.255.255.0\"
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ duplicate-cn
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 100 600
+ "
+ ovpn_start one "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 192.0.2.1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+ sleep 2
+ ovpn_start two "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 192.0.2.1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client2.crt
+ key $(atf_get_srcdir)/client2.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+
+ # Give the tunnel time to come up
+ sleep 10
+
+ atf_check -s exit:0 -o ignore jexec one ping -c 1 198.51.100.1
+ atf_check -s exit:0 -o ignore jexec two ping -c 1 198.51.100.1
+
+ # Client-to-client communication
+ atf_check -s exit:0 -o ignore jexec one ping -c 1 198.51.100.3
+ atf_check -s exit:0 -o ignore jexec one ping -c 1 198.51.100.2
+ atf_check -s exit:0 -o ignore jexec two ping -c 1 198.51.100.2
+ atf_check -s exit:0 -o ignore jexec two ping -c 1 198.51.100.3
+
+ # RA test
+ atf_check -s exit:0 -o ignore jexec one ping -c 1 203.0.113.1
+ atf_check -s exit:0 -o ignore jexec two ping -c 1 203.0.113.1
+
+ atf_check -s exit:0 -o ignore jexec srv ping -c 1 -S 203.0.113.1 198.51.100.2
+ atf_check -s exit:0 -o ignore jexec srv ping -c 1 -S 203.0.113.1 198.51.100.3
+
+ atf_check -s exit:0 -o ignore jexec one ping -c 1 203.0.113.2
+ atf_check -s exit:0 -o ignore jexec two ping -c 1 203.0.113.2
+
+ atf_check -s exit:0 -o ignore jexec lan ping -c 1 198.51.100.1
+ atf_check -s exit:0 -o ignore jexec lan ping -c 1 198.51.100.2
+ atf_check -s exit:0 -o ignore jexec lan ping -c 1 198.51.100.3
+ atf_check -s exit:2 -o ignore jexec lan ping -c 1 198.51.100.4
+}
+
+ra_cleanup()
+{
+ ovpn_cleanup
+}
+
+ovpn_algo_body()
+{
+ algo=$1
+
+ ovpn_init
+
+ l=$(vnet_mkepair)
+
+ vnet_mkjail a ${l}a
+ jexec a ifconfig ${l}a 192.0.2.1/24 up
+ vnet_mkjail b ${l}b
+ jexec b ifconfig ${l}b 192.0.2.2/24 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore jexec a ping -c 1 192.0.2.2
+
+ ovpn_start a "
+ dev ovpn0
+ dev-type tun
+ proto udp4
+
+ cipher ${algo}
+ data-ciphers ${algo}
+ auth SHA256
+
+ local 192.0.2.1
+ server 198.51.100.0 255.255.255.0
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 100 600
+ "
+ ovpn_start b "
+ dev tun0
+ dev-type tun
+
+ client
+
+ cipher ${algo}
+ data-ciphers ${algo}
+
+ remote 192.0.2.1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+
+ # Give the tunnel time to come up
+ sleep 10
+
+ atf_check -s exit:0 -o ignore jexec b ping -c 3 198.51.100.1
+}
+
+atf_test_case "chacha" "cleanup"
+chacha_head()
+{
+ atf_set descr 'Test DCO with the chacha algorithm'
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+chacha_body()
+{
+ ovpn_algo_body CHACHA20-POLY1305
+}
+
+chacha_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_test_case "gcm_128" "cleanup"
+gcm_128_head()
+{
+ atf_set descr 'Test DCO with AES-128-GCM'
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+gcm_128_body()
+{
+ ovpn_algo_body AES-128-GCM
+}
+
+gcm_128_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_test_case "destroy_unused" "cleanup"
+destroy_unused_head()
+{
+ atf_set descr 'Destroy an if_ovpn interface before it is used'
+ atf_set require.user root
+}
+
+destroy_unused_body()
+{
+ ovpn_init
+
+ intf=$(ifconfig ovpn create)
+ atf_check -s exit:0 \
+ ifconfig ${intf} destroy
+}
+
+destroy_unused_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_test_case "multihome4" "cleanup"
+multihome4_head()
+{
+ atf_set descr 'Test multihome IPv4 with OpenVPN'
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+multihome4_body()
+{
+ pft_init
+ ovpn_init
+
+ l=$(vnet_mkepair)
+
+ vnet_mkjail a ${l}a
+ atf_check jexec a ifconfig ${l}a inet 192.0.2.1/24
+ atf_check jexec a ifconfig ${l}a alias 192.0.2.2/24
+ vnet_mkjail b ${l}b
+ atf_check jexec b ifconfig ${l}b inet 192.0.2.3/24
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore jexec b ping -c 1 192.0.2.1
+ atf_check -s exit:0 -o ignore jexec b ping -c 1 192.0.2.2
+
+ ovpn_start a "
+ dev ovpn0
+ dev-type tun
+ proto udp4
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ multihome
+ server 198.51.100.0 255.255.255.0
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 100 600
+ "
+ ovpn_start b "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 192.0.2.2
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+
+ # Block packets from the primary address, openvpn should only use the
+ # configured remote address.
+ jexec b pfctl -e
+ pft_set_rules b \
+ "block in quick from 192.0.2.1 to any" \
+ "pass all"
+
+ # Give the tunnel time to come up
+ sleep 10
+
+ atf_check -s exit:0 -o ignore jexec b ping -c 3 198.51.100.1
+}
+
+multihome4_cleanup()
+{
+ ovpn_cleanup
+ pft_cleanup
+}
+
+multihome6_head()
+{
+ atf_set descr 'Test multihome IPv6 with OpenVPN'
+ atf_set require.user root
+ atf_set require.progs openvpn
+}
+
+multihome6_body()
+{
+ ovpn_init
+
+ l=$(vnet_mkepair)
+
+ vnet_mkjail a ${l}a
+ atf_check jexec a ifconfig ${l}a inet6 2001:db8::1/64 no_dad
+ atf_check jexec a ifconfig ${l}a inet6 alias 2001:db8::2/64 no_dad
+ vnet_mkjail b ${l}b
+ atf_check jexec b ifconfig ${l}b inet6 2001:db8::3/64 no_dad
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore jexec b ping6 -c 1 2001:db8::1
+ atf_check -s exit:0 -o ignore jexec b ping6 -c 1 2001:db8::2
+
+ ovpn_start a "
+ dev ovpn0
+ dev-type tun
+ proto udp6
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ multihome
+ server-ipv6 2001:db8:1::/64
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 100 600
+ "
+ ovpn_start b "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 2001:db8::2
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 100 600
+ "
+
+ # Block packets from the primary address, openvpn should only use the
+ # configured remote address.
+ jexec b pfctl -e
+ pft_set_rules b \
+ "block in quick from 2001:db8::1 to any" \
+ "pass all"
+
+ # Give the tunnel time to come up
+ sleep 10
+
+ atf_check -s exit:0 -o ignore jexec b ping6 -c 3 2001:db8:1::1
+ atf_check -s exit:0 -o ignore jexec b ping6 -c 3 -z 16 2001:db8:1::1
+}
+
+multihome6_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_test_case "float" "cleanup"
+float_head()
+{
+ atf_set descr 'Test peer float notification'
+ atf_set require.user root
+}
+
+float_body()
+{
+ ovpn_init
+ ovpn_check_version 2.7.0
+
+ l=$(vnet_mkepair)
+
+ vnet_mkjail a ${l}a
+ jexec a ifconfig ${l}a 192.0.2.1/24 up
+ jexec a ifconfig lo0 127.0.0.1/8 up
+ vnet_mkjail b ${l}b
+ jexec b ifconfig ${l}b 192.0.2.2/24 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore jexec a ping -c 1 192.0.2.2
+
+ ovpn_start a "
+ dev ovpn0
+ dev-type tun
+ proto udp4
+
+ cipher AES-256-GCM
+ auth SHA256
+
+ local 192.0.2.1
+ server 198.51.100.0 255.255.255.0
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/server.crt
+ key $(atf_get_srcdir)/server.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ mode server
+ script-security 2
+ auth-user-pass-verify /usr/bin/true via-env
+ topology subnet
+
+ keepalive 2 10
+
+ management 192.0.2.1 1234
+ "
+ ovpn_start b "
+ dev tun0
+ dev-type tun
+
+ client
+
+ remote 192.0.2.1
+ auth-user-pass $(atf_get_srcdir)/user.pass
+
+ ca $(atf_get_srcdir)/ca.crt
+ cert $(atf_get_srcdir)/client.crt
+ key $(atf_get_srcdir)/client.key
+ dh $(atf_get_srcdir)/dh.pem
+
+ keepalive 2 10
+ "
+
+ # Give the tunnel time to come up
+ sleep 10
+
+ atf_check -s exit:0 -o ignore jexec b ping -c 3 198.51.100.1
+
+ # We expect the client on 192.0.2.2
+ if ! echo "status" | jexec a nc -N 192.0.2.1 1234 | grep 192.0.2.2; then
+ atf_fail "Client not found in status list!"
+ fi
+
+ # Now change the client IP
+ jexec b ifconfig ${l}b 192.0.2.3/24 up
+
+ # And wait for keepalives to trigger the float notification
+ sleep 5
+
+ # So the client now has the new address in userspace
+ if ! echo "status" | jexec a nc -N 192.0.2.1 1234 | grep 192.0.2.3; then
+ atf_fail "Client not found in status list!"
+ fi
+}
+
+float_cleanup()
+{
+ ovpn_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "4in4"
+ atf_add_test_case "bz283426"
+ atf_add_test_case "4mapped"
+ atf_add_test_case "6in4"
+ atf_add_test_case "6in6"
+ atf_add_test_case "4in6"
+ atf_add_test_case "linklocal"
+ atf_add_test_case "timeout_client"
+ atf_add_test_case "explicit_exit"
+ atf_add_test_case "multi_client"
+ atf_add_test_case "route_to"
+ atf_add_test_case "ra"
+ atf_add_test_case "chacha"
+ atf_add_test_case "gcm_128"
+ atf_add_test_case "destroy_unused"
+ atf_add_test_case "multihome4"
+ atf_add_test_case "multihome6"
+ atf_add_test_case "float"
+}
diff --git a/tests/sys/net/if_ovpn/if_ovpn_c.c b/tests/sys/net/if_ovpn/if_ovpn_c.c
new file mode 100644
index 000000000000..7b558f1975dd
--- /dev/null
+++ b/tests/sys/net/if_ovpn/if_ovpn_c.c
@@ -0,0 +1,132 @@
+//#include <sys/param.h>
+#include <stdio.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <sys/linker.h>
+#include <sys/ioctl.h>
+#include <sys/nv.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include <atf-c.h>
+
+#define OVPN_NEW_PEER _IO ('D', 1)
+
+static nvlist_t *
+fake_sockaddr(void)
+{
+ uint32_t addr = htonl(INADDR_LOOPBACK);
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+
+ nvlist_add_number(nvl, "af", AF_INET);
+ nvlist_add_binary(nvl, "address", &addr, 4);
+ nvlist_add_number(nvl, "port", 1024);
+
+ return (nvl);
+}
+
+static char ovpn_ifname[IFNAMSIZ];
+static int ovpn_fd;
+
+static int
+create_interface(int fd)
+{
+ int ret;
+ struct ifreq ifr;
+
+ bzero(&ifr, sizeof(ifr));
+
+ /* Create ovpnx first, then rename it. */
+ snprintf(ifr.ifr_name, IFNAMSIZ, "ovpn");
+ ret = ioctl(fd, SIOCIFCREATE2, &ifr);
+ if (ret)
+ return (ret);
+
+ snprintf(ovpn_ifname, IFNAMSIZ, "%s", ifr.ifr_name);
+ printf("Created %s\n", ovpn_ifname);
+
+ return (0);
+}
+
+static void
+destroy_interface(int fd)
+{
+ int ret;
+ struct ifreq ifr;
+
+ if (ovpn_ifname[0] == 0)
+ return;
+
+ printf("Destroy %s\n", ovpn_ifname);
+
+ bzero(&ifr, sizeof(ifr));
+ snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ovpn_ifname);
+
+ ret = ioctl(fd, SIOCIFDESTROY, &ifr);
+ if (ret)
+ atf_tc_fail("Failed to destroy interface");
+
+ ovpn_ifname[0] = 0;
+}
+
+ATF_TC_WITH_CLEANUP(tcp);
+ATF_TC_HEAD(tcp, tc)
+{
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "require.kmods", "if_ovpn");
+}
+
+ATF_TC_BODY(tcp, tc)
+{
+ struct ifdrv drv;
+ struct sockaddr_in sock_in;
+ int ret;
+ nvlist_t *nvl;
+
+ ovpn_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+
+ /* Kick off a connect so there's a local address set, which we need for
+ * ovpn_new_peer() to get to the critical point. */
+ bzero(&sock_in, sizeof(sock_in));
+ sock_in.sin_family = AF_INET;
+ sock_in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sock_in.sin_port = htons(1024);
+ connect(ovpn_fd, (struct sockaddr *)&sock_in, sizeof(sock_in));
+
+ ret = create_interface(ovpn_fd);
+ if (ret)
+ atf_tc_fail("Failed to create interface");
+
+ nvl = nvlist_create(0);
+
+ nvlist_add_number(nvl, "peerid", 0);
+ nvlist_add_number(nvl, "fd", ovpn_fd);
+ nvlist_add_nvlist(nvl, "remote", fake_sockaddr());
+
+ bzero(&drv, sizeof(drv));
+ snprintf(drv.ifd_name, IFNAMSIZ, "%s", ovpn_ifname);
+ drv.ifd_cmd = OVPN_NEW_PEER;
+ drv.ifd_data = nvlist_pack(nvl, &drv.ifd_len);
+
+ ret = ioctl(ovpn_fd, SIOCSDRVSPEC, &drv);
+ ATF_CHECK_EQ(ret, -1);
+ ATF_CHECK_EQ(errno, EPROTOTYPE);
+}
+
+ATF_TC_CLEANUP(tcp, tc)
+{
+ destroy_interface(ovpn_fd);
+ close(ovpn_fd);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, tcp);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/net/if_ovpn/server.crt b/tests/sys/net/if_ovpn/server.crt
new file mode 100644
index 000000000000..e4166fa2e0ae
--- /dev/null
+++ b/tests/sys/net/if_ovpn/server.crt
@@ -0,0 +1,123 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1048687 (0x10006f)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: C=KG, ST=NA, L=BISHKEK, O=OpenVPN-TEST/emailAddress=me@myhost.mydomain
+ Validity
+ Not Before: Apr 27 15:01:41 2022 GMT
+ Not After : Apr 3 15:01:41 2122 GMT
+ Subject: C=KG, ST=NA, O=OpenVPN-TEST, CN=Test-Server/emailAddress=me@myhost.mydomain
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public-Key: (4096 bit)
+ Modulus:
+ 00:b1:4c:f3:d9:6e:39:ac:d7:8f:78:e9:37:dd:ae:
+ 73:f4:d6:77:84:42:3e:2a:76:76:6f:71:6a:b4:45:
+ a9:e3:84:0e:ee:3d:18:20:47:9d:fb:d1:ca:bb:d7:
+ cd:d5:e3:b8:3d:1b:9e:c1:f5:26:72:4a:bb:fe:4e:
+ ec:49:06:c0:ff:21:f4:b5:5c:72:fc:c7:3e:27:86:
+ 03:65:e2:d8:f7:5c:c7:23:16:82:ab:ee:81:7d:44:
+ 41:a0:34:06:14:19:08:7a:47:69:5e:b6:aa:6f:74:
+ 08:4f:13:ca:1d:b1:d8:2e:3a:a7:41:ec:e0:3e:b4:
+ 54:b2:7c:2e:dd:ee:f5:07:92:ed:f2:64:62:2f:7a:
+ c2:8e:f0:50:2d:f6:2b:1c:9d:1d:db:25:04:1e:b5:
+ 0d:18:c8:a4:b6:1e:cc:05:a1:10:74:e2:4c:98:32:
+ 44:6c:95:94:18:a0:64:0d:32:6b:84:f9:25:4d:04:
+ 0d:39:73:23:cf:b0:5a:ab:c0:ff:ec:c1:6b:ed:fa:
+ 9b:26:d0:45:d5:0e:75:72:d6:2f:36:26:fe:2d:bc:
+ 50:e0:a0:14:d4:34:e0:10:cf:aa:6a:46:79:7d:dc:
+ 30:e7:6c:c0:44:3e:fc:20:dd:e1:05:b2:a2:2f:aa:
+ 06:76:dd:33:44:19:8e:5c:54:50:d0:2b:a8:03:06:
+ ec:31:1c:48:1b:39:51:52:0f:44:b0:90:d5:29:c0:
+ b9:ae:e2:74:af:e2:08:c7:b2:e5:4e:71:f0:88:33:
+ 97:16:92:69:0b:48:8c:25:7c:8e:20:7c:8a:0f:32:
+ e6:15:90:02:33:d6:00:4f:1d:c9:7e:ef:5c:af:5f:
+ b4:f9:c5:8b:7b:c8:47:34:4d:85:80:f2:a9:3c:e0:
+ 53:d0:b7:15:59:67:0e:1b:17:6d:9b:ed:a8:14:e3:
+ 90:9d:6e:3a:83:ae:6f:0c:c6:58:2f:e6:41:f2:67:
+ b5:7c:86:97:98:55:59:14:a0:0a:f4:5f:2e:8d:ae:
+ e2:d9:68:a9:34:b1:c3:0f:44:04:9e:81:a5:45:8b:
+ 2c:82:a9:6c:ea:e7:a8:dd:27:8b:0e:0c:8c:14:35:
+ c8:86:01:1e:69:43:93:cb:57:c8:9d:43:d9:8e:22:
+ de:f8:27:e4:44:1c:3b:58:47:10:13:5f:ca:85:8b:
+ a6:ee:5a:27:17:13:9e:72:c5:a6:d1:60:e3:ec:69:
+ f0:2f:d0:d8:25:a1:80:c3:03:6b:5d:32:21:26:31:
+ 48:b7:ce:d2:32:2c:e9:1e:34:eb:21:e7:8e:a9:26:
+ 86:e6:a1:ba:0f:4a:8a:05:eb:4b:02:7a:65:e4:d3:
+ 39:da:10:3a:e6:0e:6f:22:ca:a0:2f:b7:7b:1e:65:
+ 05:73:d7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ Netscape Comment:
+ OpenSSL Generated Certificate
+ X509v3 Subject Key Identifier:
+ 4E:69:D9:D4:F1:8C:C7:F0:7D:02:79:DD:00:AB:5C:0B:61:42:F5:E2
+ X509v3 Authority Key Identifier:
+ keyid:88:07:18:C1:FE:2E:09:2E:7D:E0:B9:1B:83:A9:54:DE:09:B1:16:7E
+
+ Signature Algorithm: sha256WithRSAEncryption
+ 10:40:c5:a6:b4:4f:10:e1:ff:d4:fd:68:6d:b1:f4:02:7e:18:
+ 03:99:d2:fa:ce:24:b9:10:c3:d7:18:48:e6:9c:4c:b2:45:39:
+ a8:bc:7b:1f:66:bf:1a:bc:d5:22:f6:bc:61:e3:87:4d:d4:c8:
+ dc:ea:ee:5f:ad:95:94:e0:17:ff:7f:d5:b6:bd:a7:5f:2b:9d:
+ a4:5b:65:58:9f:83:c6:91:6f:d9:d9:1b:2e:e8:19:d8:d7:35:
+ ef:07:5d:1b:cd:89:2b:b3:d1:0c:bd:41:99:fc:54:fe:44:03:
+ a6:25:06:3a:e3:f5:3e:a1:9e:de:6b:7c:8e:dc:71:32:a9:2b:
+ 48:06:b7:72:f3:e4:38:fd:88:c0:62:48:d3:48:81:30:9e:ac:
+ 3f:d9:c6:40:92:98:39:7f:ec:bb:b8:8d:25:a0:c0:ed:c3:be:
+ 3c:df:54:42:3c:5d:2d:48:f5:35:b1:e9:b5:2f:0b:53:f1:fa:
+ 30:56:da:7d:2e:46:7d:7e:27:59:e5:ab:19:7b:be:9c:77:df:
+ 5d:6d:94:d7:3d:3b:45:09:0d:4e:a6:3e:2d:6d:95:76:78:af:
+ 03:d9:62:5d:64:14:f6:9b:36:a1:e4:6c:07:07:7f:ae:31:e6:
+ 69:6b:56:e6:42:6b:f6:de:24:ae:12:6a:58:13:31:9c:1a:87:
+ 01:a5:57:57:1f:0a:9e:16:85:30:c9:95:46:d1:05:70:df:39:
+ 80:fb:75:b1:44:43:e3:ba:8e:ef:c0:4d:db:9d:53:6c:32:e0:
+ 69:c8:74:b3:24:51:db:f9:7b:fb:0e:bc:61:0e:f3:56:31:2e:
+ 29:51:ed:dc:93:14:13:d0:6b:ab:88:d4:ae:e7:41:c2:da:7b:
+ 73:ec:d1:b9:49:07:85:73:e3:75:ed:ea:e8:48:09:01:45:52:
+ 58:05:37:bf:f8:5e:74:61:5f:1e:b2:db:1f:49:62:d1:ab:8f:
+ e6:d4:3e:69:1d:da:0b:93:88:3e:1c:2e:f4:03:32:9e:75:df:
+ af:65:0d:1c:cf:fd:35:36:f7:a5:93:01:11:69:2a:7b:76:8a:
+ 52:bb:e3:e9:b0:dc:e8:5b:78:65:3d:e9:36:84:8f:03:b3:ed:
+ 43:88:1f:97:71:ce:c6:c1:9e:e2:5d:51:b5:1a:bd:c6:fe:f1:
+ e2:c3:6c:95:6d:08:06:5b:b1:13:4b:57:36:07:c4:81:ea:d8:
+ f7:e4:2a:27:ce:75:1d:fd:ca:e4:82:a3:d1:0b:75:a3:77:8e:
+ c0:2b:34:87:2b:84:17:8c:23:e0:6d:fb:18:01:06:56:4b:f5:
+ 39:bc:3e:59:3d:ed:68:17:c0:12:66:56:0e:34:64:95:b2:84:
+ 99:de:71:94:2a:ca:a6:70
+-----BEGIN CERTIFICATE-----
+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
+-----END CERTIFICATE-----
diff --git a/tests/sys/net/if_ovpn/server.key b/tests/sys/net/if_ovpn/server.key
new file mode 100644
index 000000000000..f35d6fcd4563
--- /dev/null
+++ b/tests/sys/net/if_ovpn/server.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/tests/sys/net/if_ovpn/user.pass b/tests/sys/net/if_ovpn/user.pass
new file mode 100644
index 000000000000..59d468ee39af
--- /dev/null
+++ b/tests/sys/net/if_ovpn/user.pass
@@ -0,0 +1,2 @@
+username
+password
diff --git a/tests/sys/net/if_ovpn/utils.subr b/tests/sys/net/if_ovpn/utils.subr
new file mode 100644
index 000000000000..fbe7dc98630a
--- /dev/null
+++ b/tests/sys/net/if_ovpn/utils.subr
@@ -0,0 +1,92 @@
+##
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2022 Rubicon Communications, LLC ("Netgate")
+#
+# 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.
+
+. $(atf_get_srcdir)/../../common/vnet.subr
+
+ovpn_init()
+{
+ vnet_init
+
+ if ! kldstat -q -m if_ovpn; then
+ atf_skip "This test requires if_ovpn"
+ fi
+
+ has_dco=$(openvpn --version 2>&1 | grep '\[DCO\]')
+ if [ -z "$has_dco" ]; then
+ atf_skip "openvpn binary does not support DCO"
+ fi
+}
+
+ovpn_check_version()
+{
+ expected=$1
+
+ expected_minor=$(echo $expected |
+ awk '{ split($1, ver, "\."); print(ver[2]); }')
+ actual_minor=$(openvpn --version 2>&1 |
+ awk 'NR == 1 \
+ { \
+ split($2, ver, "\."); \
+ split(ver[2], minor, "_"); \
+ print(minor[1]); \
+ }')
+
+ if [ ${actual_minor} -lt ${expected_minor} ]; then
+ atf_skip "OpenVPN version < ${expected}"
+ fi
+}
+
+ovpn_cleanup()
+{
+ for jail in `cat ovpn_jails.lst | sort -u`
+ do
+ cat ovpn_${jail}.log| sed s/^/\[${jail}\]\ /
+ done
+
+ vnet_cleanup
+}
+
+ovpn_start()
+{
+ jail=$1
+ cfg=$2
+
+ echo ${jail} >> ovpn_jails.lst
+
+ dir=$(pwd)
+
+ echo "Start" >> ovpn_${jail}.log
+ echo "=====" >> ovpn_${jail}.log
+
+ echo "$cfg" > ovpn_${jail}.ovpn
+
+ echo "Jail $jail:"
+ echo "==========="
+ cat ovpn_${jail}.ovpn
+
+ jexec $jail sh -c "cd ${dir} &&
+ openvpn --config ovpn_${jail}.ovpn >> ovpn_${jail}.log &"
+}
diff --git a/tests/sys/net/if_stf.sh b/tests/sys/net/if_stf.sh
new file mode 100644
index 000000000000..71b00f308014
--- /dev/null
+++ b/tests/sys/net/if_stf.sh
@@ -0,0 +1,203 @@
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2021 Rubicon Communications, LLC (Netgate)
+#
+# 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.
+
+. $(atf_get_srcdir)/../common/vnet.subr
+
+atf_test_case "6to4" "cleanup"
+6to4_head()
+{
+ atf_set descr 'Test 6to4'
+ atf_set require.user root
+}
+
+6to4_body()
+{
+ vnet_init
+ if ! kldstat -q -m if_stf; then
+ atf_skip "This test requires if_stf"
+ fi
+ if ! kldstat -q -m if_gif; then
+ atf_skip "This test requires if_gif"
+ fi
+
+ epair=$(vnet_mkepair)
+
+ vnet_mkjail relay ${epair}a
+ jexec relay ifconfig lo0 inet6 2001:db8::1/64 up
+ jexec relay ifconfig ${epair}a 192.0.2.1/24 up
+ # Simple gif to terminate 6to4
+ gif=$(jexec relay ifconfig gif create)
+ jexec relay ifconfig $gif inet6 2002:c000:0201::1/64 up
+ jexec relay ifconfig $gif tunnel 192.0.2.1 192.0.2.2
+ jexec relay route -6 add default -interface $gif
+
+ vnet_mkjail client ${epair}b
+ jexec client ifconfig lo0 up
+ jexec client ifconfig ${epair}b 192.0.2.2/24 up
+ stf=$(jexec client ifconfig stf create)
+ jexec client ifconfig $stf inet6 2002:c000:0202::1/32 up
+ jexec client route -6 add default 2002:c000:0201::1
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore \
+ jexec client ping -c 1 192.0.2.1
+ # 6to4 direct
+ atf_check -s exit:0 -o ignore \
+ jexec client ping6 -c 1 2002:c000:0201::1
+
+ # "Wider internet"
+ atf_check -s exit:0 -o ignore \
+ jexec client ping6 -c 1 2001:db8::1
+}
+
+6to4_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "6rd" "cleanup"
+6rd_head()
+{
+ atf_set descr '6RD test'
+ atf_set require.user root
+}
+
+6rd_body()
+{
+ vnet_init
+
+ if ! kldstat -q -m if_stf; then
+ atf_skip "This test requires if_stf"
+ fi
+ if ! kldstat -q -m if_gif; then
+ atf_skip "This test requires if_gif"
+ fi
+
+ epair=$(vnet_mkepair)
+ vnet_mkjail br ${epair}a
+ jexec br ifconfig ${epair}a 192.0.2.1/24 up
+
+ # Simple gif to terminate the 6RD tunnel
+ gif=$(jexec br ifconfig gif create)
+ jexec br ifconfig lo0 inet6 2001:db9::1/64 up
+ jexec br ifconfig $gif inet6 2001:db8::/64 up
+ jexec br ifconfig $gif tunnel 192.0.2.1 192.0.2.2
+ jexec br route -6 add default -interface $gif
+
+ vnet_mkjail client ${epair}b
+ jexec client ifconfig lo0 up
+ jexec client ifconfig ${epair}b 192.0.2.2/24 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore \
+ jexec client ping -c 1 192.0.2.1
+
+ stf=$(jexec client ifconfig stf create)
+
+ jexec client ifconfig $stf stfv4br 192.0.2.1
+ jexec client ifconfig $stf stfv4net 192.0.2.2/32
+ jexec client ifconfig $stf inet6 2001:db8:c000:0202::1/32 up
+ jexec client route -6 add default -interface $stf
+
+ atf_check -s exit:0 -o ignore \
+ jexec client ping6 -c 1 2001:db9::1
+}
+
+6rd_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "6rd_peer" "cleanup"
+6rd_peer_head()
+{
+ atf_set descr '6RD peer test'
+ atf_set require.user root
+}
+
+6rd_peer_body()
+{
+ vnet_init
+
+ if ! kldstat -q -m if_stf; then
+ atf_skip "This test requires if_stf"
+ fi
+
+ epair=$(vnet_mkepair)
+
+ vnet_mkjail one ${epair}a
+ jexec one ifconfig lo0 up
+ jexec one ifconfig ${epair}a 192.0.2.1/24 up
+ stf_one=$(jexec one ifconfig stf create)
+ jexec one ifconfig $stf_one stfv4br 192.0.2.3
+ jexec one ifconfig $stf_one stfv4net 192.0.2.1/32
+ jexec one ifconfig $stf_one inet6 2001:db8:c000:0201::1/32 up
+ jexec one route -6 add default -interface $stf_one
+
+ vnet_mkjail two ${epair}b
+ jexec two ifconfig lo0 up
+ jexec two ifconfig ${epair}b 192.0.2.2/24 up
+ stf_two=$(jexec two ifconfig stf create)
+ jexec two ifconfig $stf_two stfv4br 192.0.2.3
+ jexec two ifconfig $stf_two stfv4net 192.0.2.2/32
+ jexec two ifconfig $stf_two inet6 2001:db8:c000:0202::1/32 up
+ jexec two route -6 add default -interface $stf_two
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore \
+ jexec one ping -c 1 192.0.2.2
+
+ # Test 6rd
+ atf_check -s exit:0 -o ignore \
+ jexec one ping6 -c 1 2001:db8:c000:0202::1
+ atf_check -s exit:0 -o ignore \
+ jexec two ping6 -c 1 2001:db8:c000:0201::1
+
+ # Shorter prefixes, for both v4 and v6
+ jexec one ifconfig $stf_one inet6 2001:db8:c000:0201::1 delete
+ jexec one ifconfig $stf_one inet6 2001:0201::1/16
+ jexec one ifconfig $stf_one stfv4net 192.0.2.1/16
+ jexec two ifconfig $stf_two inet6 2001:db8:c000:0202::1 delete
+ jexec two ifconfig $stf_two inet6 2001:0202::1/16
+ jexec two ifconfig $stf_two stfv4net 192.0.2.2/16
+
+ atf_check -s exit:0 -o ignore \
+ jexec one ping6 -c 1 2001:0202::1
+ atf_check -s exit:0 -o ignore \
+ jexec two ping6 -c 1 2001:0201::1
+}
+
+6rd_peer_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "6to4"
+ atf_add_test_case "6rd"
+ atf_add_test_case "6rd_peer"
+}
diff --git a/tests/sys/net/if_tun_test.sh b/tests/sys/net/if_tun_test.sh
new file mode 100755
index 000000000000..f4ce7800272e
--- /dev/null
+++ b/tests/sys/net/if_tun_test.sh
@@ -0,0 +1,85 @@
+
+. $(atf_get_srcdir)/../common/vnet.subr
+
+atf_test_case "235704" "cleanup"
+235704_head()
+{
+ atf_set descr "Test PR #235704"
+ atf_set require.user root
+}
+235704_body()
+{
+ vnet_init
+ vnet_mkjail one
+
+ tun=$(jexec one ifconfig tun create)
+ jexec one ifconfig ${tun} name foo
+ atf_check -s exit:0 jexec one ifconfig foo destroy
+}
+235704_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "basic" "cleanup"
+basic_head()
+{
+ atf_set descr "Test if_tun using nc"
+ atf_set require.user root
+}
+basic_body()
+{
+ vnet_init
+
+ epair=$(vnet_mkepair)
+
+ tun_duke=$(ifconfig tun create)
+ tun_bass=$(ifconfig tun create)
+
+ vnet_mkjail duke ${epair}a ${tun_duke}
+ vnet_mkjail bass ${epair}b ${tun_bass}
+
+ jexec duke ifconfig ${epair}a inet 10.0.0.1/24 up
+ jexec bass ifconfig ${epair}b inet 10.0.0.2/24 up
+
+ jexec duke nc -u -l --tun /dev/${tun_duke} 10.0.0.1 2600 &
+ jexec bass nc -u --tun /dev/${tun_bass} 10.0.0.1 2600 &
+
+ jexec duke ifconfig ${tun_duke} inet 10.100.0.1/24 10.100.0.2 up
+ jexec bass ifconfig ${tun_bass} inet 10.100.0.2/24 10.100.0.1 up
+
+ atf_check -s exit:0 -o ignore \
+ jexec bass ping -c 1 10.100.0.1
+}
+basic_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "transient" "cleanup"
+transient_head()
+{
+ atf_set descr "Test transient tunnel support"
+ atf_set require.user root
+}
+transient_body()
+{
+ vnet_init
+ vnet_mkjail one
+
+ tun=$(jexec one ifconfig tun create)
+ atf_check -s exit:0 -o not-empty jexec one ifconfig ${tun}
+ jexec one $(atf_get_srcdir)/transient_tuntap /dev/${tun}
+ atf_check -s not-exit:0 -e not-empty jexec one ifconfig ${tun}
+}
+transient_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "235704"
+ atf_add_test_case "basic"
+ atf_add_test_case "transient"
+}
diff --git a/tests/sys/net/if_vlan.sh b/tests/sys/net/if_vlan.sh
new file mode 100755
index 000000000000..8122203337e2
--- /dev/null
+++ b/tests/sys/net/if_vlan.sh
@@ -0,0 +1,373 @@
+
+. $(atf_get_srcdir)/../common/vnet.subr
+
+atf_test_case "basic" "cleanup"
+basic_head()
+{
+ atf_set descr 'Basic VLAN test'
+ atf_set require.user root
+}
+
+basic_body()
+{
+ vnet_init
+
+ epair_vlan=$(vnet_mkepair)
+
+ vnet_mkjail alcatraz ${epair_vlan}a
+ vnet_mkjail singsing ${epair_vlan}b
+
+ vlan0=$(jexec alcatraz ifconfig vlan create vlandev ${epair_vlan}a \
+ vlan 42)
+ jexec alcatraz ifconfig ${epair_vlan}a up
+ jexec alcatraz ifconfig ${vlan0} 10.0.0.1/24 up
+
+ vlan1=$(jexec singsing ifconfig vlan create)
+
+ # Test associating the physical interface
+ atf_check -s exit:0 \
+ jexec singsing ifconfig ${vlan1} vlandev ${epair_vlan}b vlan 42
+
+ jexec singsing ifconfig ${epair_vlan}b up
+ jexec singsing ifconfig ${vlan1} 10.0.0.2/24 up
+
+ atf_check -s exit:0 -o ignore jexec singsing ping -c 1 10.0.0.1
+
+ # Test changing the vlan ID
+ atf_check -s exit:0 \
+ jexec singsing ifconfig ${vlan1} vlandev ${epair_vlan}b vlan 43
+ atf_check -s exit:2 -o ignore jexec singsing ping -c 1 10.0.0.1
+
+ # And change back
+ # Test changing the vlan ID
+ atf_check -s exit:0 \
+ jexec singsing ifconfig ${vlan1} vlan 42 vlandev ${epair_vlan}b
+ atf_check -s exit:0 -o ignore jexec singsing ping -c 1 10.0.0.1
+}
+
+basic_cleanup()
+{
+ vnet_cleanup
+}
+
+# Simple Q-in-Q (802.1Q over 802.1ad)
+
+atf_test_case "qinq_simple" "cleanup"
+qinq_simple_head()
+{
+ atf_set descr 'Simple Q-in-Q test (802.1Q over 802.1ad)'
+ atf_set require.user root
+}
+
+qinq_simple_body()
+{
+ vnet_init
+
+ epair_qinq=$(vnet_mkepair)
+
+ vnet_mkjail jqinq0 ${epair_qinq}a
+ vnet_mkjail jqinq1 ${epair_qinq}b
+
+ vlan5a=$(jexec jqinq0 ifconfig vlan create \
+ vlandev ${epair_qinq}a vlan 5 vlanproto 802.1ad)
+ vlan42a=$(jexec jqinq0 ifconfig vlan create \
+ vlandev ${vlan5a} vlan 42 vlanproto 802.1q)
+ jexec jqinq0 ifconfig ${epair_qinq}a up
+ jexec jqinq0 ifconfig ${vlan5a} up
+ jexec jqinq0 ifconfig ${vlan42a} 10.5.42.1/24 up
+
+ vlan5b=$(jexec jqinq1 ifconfig vlan create \
+ vlandev ${epair_qinq}b vlan 5 vlanproto 802.1ad)
+ vlan42b=$(jexec jqinq1 ifconfig vlan create \
+ vlandev ${vlan5b} vlan 42 vlanproto 802.1q)
+ jexec jqinq1 ifconfig ${epair_qinq}b up
+ jexec jqinq1 ifconfig ${vlan5b} up
+ jexec jqinq1 ifconfig ${vlan42b} 10.5.42.2/24 up
+
+ atf_check -s exit:0 -o ignore jexec jqinq1 ping -c 1 10.5.42.1
+}
+
+qinq_simple_cleanup()
+{
+ vnet_cleanup
+}
+
+# Deep Q-in-Q (802.1Q over 802.1ad over 802.1ad)
+
+atf_test_case "qinq_deep" "cleanup"
+qinq_deep_head()
+{
+ atf_set descr 'Deep Q-in-Q test (802.1Q over 802.1ad over 802.1ad)'
+ atf_set require.user root
+}
+
+qinq_deep_body()
+{
+ vnet_init
+
+ epair_qinq=$(vnet_mkepair)
+
+ vnet_mkjail jqinq2 ${epair_qinq}a
+ vnet_mkjail jqinq3 ${epair_qinq}b
+
+ vlan5a=$(jexec jqinq2 ifconfig vlan create \
+ vlandev ${epair_qinq}a vlan 5 vlanproto 802.1ad)
+ vlan6a=$(jexec jqinq2 ifconfig vlan create \
+ vlandev ${vlan5a} vlan 6 vlanproto 802.1ad)
+ vlan42a=$(jexec jqinq2 ifconfig vlan create \
+ vlandev ${vlan6a} vlan 42 vlanproto 802.1q)
+ jexec jqinq2 ifconfig ${epair_qinq}a up
+ jexec jqinq2 ifconfig ${vlan5a} up
+ jexec jqinq2 ifconfig ${vlan6a} up
+ jexec jqinq2 ifconfig ${vlan42a} 10.6.42.1/24 up
+
+ vlan5b=$(jexec jqinq3 ifconfig vlan create \
+ vlandev ${epair_qinq}b vlan 5 vlanproto 802.1ad)
+ vlan6b=$(jexec jqinq3 ifconfig vlan create \
+ vlandev ${vlan5b} vlan 6 vlanproto 802.1ad)
+ vlan42b=$(jexec jqinq3 ifconfig vlan create \
+ vlandev ${vlan6b} vlan 42 vlanproto 802.1q)
+ jexec jqinq3 ifconfig ${epair_qinq}b up
+ jexec jqinq3 ifconfig ${vlan5b} up
+ jexec jqinq3 ifconfig ${vlan6b} up
+ jexec jqinq3 ifconfig ${vlan42b} 10.6.42.2/24 up
+
+ atf_check -s exit:0 -o ignore jexec jqinq3 ping -c 1 10.6.42.1
+}
+
+qinq_deep_cleanup()
+{
+ vnet_cleanup
+}
+
+# Legacy Q-in-Q (802.1Q over 802.1Q)
+
+atf_test_case "qinq_legacy" "cleanup"
+qinq_legacy_head()
+{
+ atf_set descr 'Legacy Q-in-Q test (802.1Q over 802.1Q)'
+ atf_set require.user root
+}
+
+qinq_legacy_body()
+{
+ vnet_init
+
+ epair_qinq=$(vnet_mkepair)
+
+ vnet_mkjail jqinq4 ${epair_qinq}a
+ vnet_mkjail jqinq5 ${epair_qinq}b
+
+ vlan5a=$(jexec jqinq4 ifconfig vlan create \
+ vlandev ${epair_qinq}a vlan 5)
+ vlan42a=$(jexec jqinq4 ifconfig vlan create \
+ vlandev ${vlan5a} vlan 42)
+ jexec jqinq4 ifconfig ${epair_qinq}a up
+ jexec jqinq4 ifconfig ${vlan5a} up
+ jexec jqinq4 ifconfig ${vlan42a} 10.5.42.1/24 up
+
+ vlan5b=$(jexec jqinq5 ifconfig vlan create \
+ vlandev ${epair_qinq}b vlan 5)
+ vlan42b=$(jexec jqinq5 ifconfig vlan create \
+ vlandev ${vlan5b} vlan 42)
+ jexec jqinq5 ifconfig ${epair_qinq}b up
+ jexec jqinq5 ifconfig ${vlan5b} up
+ jexec jqinq5 ifconfig ${vlan42b} 10.5.42.2/24 up
+
+ atf_check -s exit:0 -o ignore jexec jqinq5 ping -c 1 10.5.42.1
+}
+
+qinq_legacy_cleanup()
+{
+ vnet_cleanup
+}
+
+# Simple Q-in-Q with dot notation
+
+atf_test_case "qinq_dot" "cleanup"
+qinq_dot_head()
+{
+ atf_set descr 'Simple Q-in-Q test with dot notation'
+ atf_set require.user root
+}
+
+qinq_dot_body()
+{
+ vnet_init
+
+ epair_qinq=$(vnet_mkepair)
+
+ vnet_mkjail jqinq6 ${epair_qinq}a
+ vnet_mkjail jqinq7 ${epair_qinq}b
+
+ jexec jqinq6 ifconfig vlan5 create \
+ vlandev ${epair_qinq}a vlan 5 vlanproto 802.1ad
+ jexec jqinq6 ifconfig vlan5.42 create \
+ vlanproto 802.1q
+ jexec jqinq6 ifconfig ${epair_qinq}a up
+ jexec jqinq6 ifconfig vlan5 up
+ jexec jqinq6 ifconfig vlan5.42 10.5.42.1/24 up
+
+ vlan5b=$(jexec jqinq7 ifconfig vlan create \
+ vlandev ${epair_qinq}b vlan 5 vlanproto 802.1ad)
+ vlan42b=$(jexec jqinq7 ifconfig vlan create \
+ vlandev ${vlan5b} vlan 42 vlanproto 802.1q)
+ jexec jqinq7 ifconfig ${epair_qinq}b up
+ jexec jqinq7 ifconfig ${vlan5b} up
+ jexec jqinq7 ifconfig ${vlan42b} 10.5.42.2/24 up
+
+ atf_check -s exit:0 -o ignore jexec jqinq7 ping -c 1 10.5.42.1
+}
+
+qinq_dot_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "qinq_setflags" "cleanup"
+qinq_setflags_head()
+{
+ atf_set descr 'Test setting flags on a QinQ device'
+ atf_set require.user root
+}
+
+qinq_setflags_body()
+{
+ vnet_init
+
+ epair=$(vnet_mkepair)
+
+ ifconfig ${epair}a up
+ vlan1=$(ifconfig vlan create)
+ ifconfig $vlan1 vlan 1 vlandev ${epair}a
+ vlan2=$(ifconfig vlan create)
+ ifconfig $vlan2 vlan 2 vlandev $vlan1
+
+ # This panics, incorrect locking
+ ifconfig $vlan2 promisc
+}
+
+qinq_setflags_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "bpf_pcp" "cleanup"
+bpf_pcp_head()
+{
+ atf_set descr 'Set VLAN PCP through BPF'
+ atf_set require.user root
+ atf_set require.progs python3 scapy
+}
+
+bpf_pcp_body()
+{
+ vnet_init
+
+ epair=$(vnet_mkepair)
+
+ ifconfig ${epair}a up
+
+ vnet_mkjail alcatraz ${epair}b
+ vlan=$(jexec alcatraz ifconfig vlan create)
+ jexec alcatraz ifconfig ${vlan} vlan 42 vlandev ${epair}b
+ jexec alcatraz ifconfig ${vlan} up
+ jexec alcatraz ifconfig ${epair}b up
+
+ jexec alcatraz sysctl net.link.vlan.mtag_pcp=1
+
+ jexec alcatraz dhclient ${vlan} &
+ atf_check -s exit:1 -o ignore -e ignore $(atf_get_srcdir)/pcp.py \
+ --expect-pcp 6 \
+ --recvif ${epair}a
+
+ jexec alcatraz killall dhclient
+ sleep 1
+
+ jexec alcatraz dhclient -c $(atf_get_srcdir)/dhclient_pcp.conf ${vlan} &
+ atf_check -s exit:0 -o ignore -e ignore $(atf_get_srcdir)/pcp.py \
+ --expect-pcp 6 \
+ --recvif ${epair}a
+}
+
+bpf_pcp_cleanup()
+{
+ sysctl net.link.vlan.mtag_pcp=0
+ jexec alcatraz killall dhclient
+ vnet_cleanup
+}
+
+atf_test_case "conflict_id" "cleanup"
+conflict_id_head()
+{
+ atf_set descr 'Test conflicting VLAN IDs, PR #279195'
+ atf_set require.user root
+}
+
+conflict_id_body()
+{
+ vnet_init
+
+ epair=$(vnet_mkepair)
+
+ vnet_mkjail alcatraz ${epair}b
+ vlan_a=$(jexec alcatraz ifconfig vlan create)
+ vlan_b=$(jexec alcatraz ifconfig vlan create)
+
+ jexec alcatraz ifconfig ${vlan_a} vlan 100 vlandev ${epair}b
+ jexec alcatraz ifconfig ${vlan_b} vlan 101 vlandev ${epair}b
+
+ atf_check -s exit:1 -o ignore -e ignore \
+ jexec alcatraz ifconfig ${vlan_a} vlan 101
+
+ atf_check -s exit:0 -o match:"vlan: 100" \
+ jexec alcatraz ifconfig ${vlan_a}
+
+ atf_check -s exit:0 -o ignore -e ignore \
+ jexec alcatraz ifconfig ${vlan_a} vlan 100
+}
+
+conflict_id_cleanup()
+{
+ vnet_cleanup
+
+}
+
+# If a vlan interface is in a bridge, changing the vlandev to refer to
+# a bridge should not be allowed.
+atf_test_case "bridge_vlandev" "cleanup"
+bridge_vlandev_head()
+{
+ atf_set descr 'transforming a bridge member vlan into an SVI is not allowed'
+ atf_set require.user root
+}
+
+bridge_vlandev_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ bridge=$(vnet_mkbridge)
+ vlan=$(vnet_mkvlan)
+
+ atf_check -s exit:0 ifconfig ${bridge} addm ${vlan}
+ atf_check -s exit:1 -e ignore ifconfig ${vlan} vlan 1 vlandev ${bridge}
+}
+
+bridge_vlandev_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "basic"
+ atf_add_test_case "qinq_simple"
+ atf_add_test_case "qinq_deep"
+ atf_add_test_case "qinq_legacy"
+ atf_add_test_case "qinq_dot"
+ atf_add_test_case "qinq_setflags"
+ atf_add_test_case "bpf_pcp"
+ atf_add_test_case "conflict_id"
+ atf_add_test_case "bridge_vlandev"
+}
diff --git a/tests/sys/net/if_wg.sh b/tests/sys/net/if_wg.sh
new file mode 100644
index 000000000000..1f51d86c8efa
--- /dev/null
+++ b/tests/sys/net/if_wg.sh
@@ -0,0 +1,633 @@
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2021 The FreeBSD Foundation
+#
+# This software was developed by Mark Johnston under sponsorship
+# from the FreeBSD Foundation.
+#
+# 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.
+
+. $(atf_get_srcdir)/../common/vnet.subr
+
+atf_test_case "wg_basic" "cleanup"
+wg_basic_head()
+{
+ atf_set descr 'Create a wg(4) tunnel over an epair and pass traffic between jails'
+ atf_set require.user root
+ atf_set require.kmods if_wg
+}
+
+wg_basic_body()
+{
+ local epair pri1 pri2 pub1 pub2 wg1 wg2
+ local endpoint1 endpoint2 tunnel1 tunnel2
+
+ pri1=$(wg genkey)
+ pri2=$(wg genkey)
+
+ endpoint1=192.168.2.1
+ endpoint2=192.168.2.2
+ tunnel1=169.254.0.1
+ tunnel2=169.254.0.2
+
+ epair=$(vnet_mkepair)
+
+ vnet_init
+
+ vnet_mkjail wgtest1 ${epair}a
+ vnet_mkjail wgtest2 ${epair}b
+
+ jexec wgtest1 ifconfig ${epair}a ${endpoint1}/24 up
+ jexec wgtest2 ifconfig ${epair}b ${endpoint2}/24 up
+
+ wg1=$(jexec wgtest1 ifconfig wg create)
+ echo "$pri1" | jexec wgtest1 wg set $wg1 listen-port 12345 \
+ private-key /dev/stdin
+ pub1=$(jexec wgtest1 wg show $wg1 public-key)
+ wg2=$(jexec wgtest2 ifconfig wg create)
+ echo "$pri2" | jexec wgtest2 wg set $wg2 listen-port 12345 \
+ private-key /dev/stdin
+ pub2=$(jexec wgtest2 wg show $wg2 public-key)
+
+ atf_check -s exit:0 -o ignore \
+ jexec wgtest1 wg set $wg1 peer "$pub2" \
+ endpoint ${endpoint2}:12345 allowed-ips ${tunnel2}/32
+ atf_check -s exit:0 \
+ jexec wgtest1 ifconfig $wg1 inet ${tunnel1}/24 up
+
+ atf_check -s exit:0 -o ignore \
+ jexec wgtest2 wg set $wg2 peer "$pub1" \
+ endpoint ${endpoint1}:12345 allowed-ips ${tunnel1}/32
+ atf_check -s exit:0 \
+ jexec wgtest2 ifconfig $wg2 inet ${tunnel2}/24 up
+
+ # Generous timeout since the handshake takes some time.
+ atf_check -s exit:0 -o ignore jexec wgtest1 ping -c 1 -t 5 $tunnel2
+ atf_check -s exit:0 -o ignore jexec wgtest2 ping -c 1 $tunnel1
+}
+
+wg_basic_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "wg_basic_crossaf" "cleanup"
+wg_basic_crossaf_head()
+{
+ atf_set descr 'Create a wg(4) tunnel and pass IPv4 traffic over an IPv6 nexthop'
+ atf_set require.user root
+}
+
+wg_basic_crossaf_body()
+{
+ local epair pri1 pri2 pub1 pub2 wg1 wg2
+ local endpoint1 endpoint2 tunnel1 tunnel2
+ local testnet testlocal testremote
+
+ kldload -n if_wg || atf_skip "This test requires if_wg and could not load it"
+
+ pri1=$(wg genkey)
+ pri2=$(wg genkey)
+
+ endpoint1=192.168.2.1
+ endpoint2=192.168.2.2
+ tunnel1=2001:db8:1::1
+ tunnel2=2001:db8:1::2
+
+ testnet=192.168.3.0/24
+ testlocal=192.168.3.1
+ testremote=192.168.3.2
+
+ epair=$(vnet_mkepair)
+
+ vnet_init
+
+ vnet_mkjail wgtest1 ${epair}a
+ vnet_mkjail wgtest2 ${epair}b
+
+ jexec wgtest1 ifconfig ${epair}a ${endpoint1}/24 up
+ jexec wgtest2 ifconfig ${epair}b ${endpoint2}/24 up
+
+ wg1=$(jexec wgtest1 ifconfig wg create)
+ echo "$pri1" | jexec wgtest1 wg set $wg1 listen-port 12345 \
+ private-key /dev/stdin
+ pub1=$(jexec wgtest1 wg show $wg1 public-key)
+ wg2=$(jexec wgtest2 ifconfig wg create)
+ echo "$pri2" | jexec wgtest2 wg set $wg2 listen-port 12345 \
+ private-key /dev/stdin
+ pub2=$(jexec wgtest2 wg show $wg2 public-key)
+
+ atf_check -s exit:0 -o ignore \
+ jexec wgtest1 wg set $wg1 peer "$pub2" \
+ endpoint ${endpoint2}:12345 allowed-ips ${tunnel2}/128,${testnet}
+ atf_check -s exit:0 \
+ jexec wgtest1 ifconfig $wg1 inet6 ${tunnel1}/64 up
+
+ atf_check -s exit:0 -o ignore \
+ jexec wgtest2 wg set $wg2 peer "$pub1" \
+ endpoint ${endpoint1}:12345 allowed-ips ${tunnel1}/128,${testnet}
+ atf_check -s exit:0 \
+ jexec wgtest2 ifconfig $wg2 inet6 ${tunnel2}/64 up
+
+ atf_check -s exit:0 jexec wgtest1 ifconfig $wg1 inet ${testlocal}/32
+ atf_check -s exit:0 jexec wgtest2 ifconfig $wg2 inet ${testremote}/32
+
+ # Generous timeout since the handshake takes some time.
+ atf_check -s exit:0 -o ignore jexec wgtest1 ping -c 1 -t 5 "$tunnel2"
+
+ # Setup our IPv6 endpoint and routing
+ atf_check -s exit:0 -o ignore \
+ jexec wgtest1 route add -inet ${testnet} -inet6 "$tunnel2"
+ atf_check -s exit:0 -o ignore \
+ jexec wgtest2 route add -inet ${testnet} -inet6 "$tunnel1"
+ # Now ping an address on the other side
+ atf_check -s exit:0 -o ignore jexec wgtest1 ping -c 1 -t 3 ${testremote}
+}
+
+wg_basic_crossaf_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "wg_basic_netmap" "cleanup"
+wg_basic_netmap_head()
+{
+ atf_set descr 'Create a wg(4) tunnel over an epair and pass traffic between jails with netmap'
+ atf_set require.user root
+ atf_set require.kmods if_wg netmap
+}
+
+wg_basic_netmap_body()
+{
+ local epair pri1 pri2 pub1 pub2 wg1 wg2
+ local endpoint1 endpoint2 tunnel1 tunnel2 tunnel3 tunnel4
+ local pid status
+
+ pri1=$(wg genkey)
+ pri2=$(wg genkey)
+
+ endpoint1=192.168.2.1
+ endpoint2=192.168.2.2
+ tunnel1=192.168.3.1
+ tunnel2=192.168.3.2
+ tunnel3=192.168.3.3
+ tunnel4=192.168.3.4
+
+ epair=$(vnet_mkepair)
+
+ vnet_init
+
+ vnet_mkjail wgtest1 ${epair}a
+ vnet_mkjail wgtest2 ${epair}b
+
+ jexec wgtest1 ifconfig ${epair}a ${endpoint1}/24 up
+ jexec wgtest2 ifconfig ${epair}b ${endpoint2}/24 up
+
+ wg1=$(jexec wgtest1 ifconfig wg create)
+ echo "$pri1" | jexec wgtest1 wg set $wg1 listen-port 12345 \
+ private-key /dev/stdin
+ pub1=$(jexec wgtest1 wg show $wg1 public-key)
+ wg2=$(jexec wgtest2 ifconfig wg create)
+ echo "$pri2" | jexec wgtest2 wg set $wg2 listen-port 12345 \
+ private-key /dev/stdin
+ pub2=$(jexec wgtest2 wg show $wg2 public-key)
+
+ atf_check -s exit:0 -o ignore \
+ jexec wgtest1 wg set $wg1 peer "$pub2" \
+ endpoint ${endpoint2}:12345 allowed-ips ${tunnel2}/32,${tunnel4}/32
+ atf_check -s exit:0 \
+ jexec wgtest1 ifconfig $wg1 inet ${tunnel1}/24 up
+
+ atf_check -s exit:0 -o ignore \
+ jexec wgtest2 wg set $wg2 peer "$pub1" \
+ endpoint ${endpoint1}:12345 allowed-ips ${tunnel1}/32,${tunnel3}/32
+ atf_check -s exit:0 \
+ jexec wgtest2 ifconfig $wg2 inet ${tunnel2}/24 up
+
+ atf_check -s exit:0 -o ignore \
+ jexec wgtest1 sysctl net.inet.ip.forwarding=1
+ atf_check -s exit:0 -o ignore \
+ jexec wgtest2 sysctl net.inet.ip.forwarding=1
+
+ jexec wgtest1 $(atf_get_srcdir)/bridge -w 0 -i netmap:wg0 -i netmap:wg0^ &
+ pid=$!
+
+ # Generous timeout since the handshake takes some time.
+ atf_check -s exit:0 -o ignore jexec wgtest1 ping -c 1 -t 5 $tunnel2
+ atf_check -s exit:0 -o ignore jexec wgtest2 ping -c 1 $tunnel1
+
+ # Verify that we cannot ping non-existent tunnel addresses. In general
+ # the remote side should respond with an ICMP message.
+ atf_check -s exit:2 -o ignore jexec wgtest1 ping -c 1 -t 2 $tunnel4
+ atf_check -s exit:2 -o ignore jexec wgtest2 ping -c 1 -t 2 $tunnel3
+
+ # Make sure that the bridge is still functional.
+ atf_check -s exit:0 -o ignore jexec wgtest1 ping -c 1 $tunnel2
+ atf_check -s exit:0 -o ignore jexec wgtest2 ping -c 1 $tunnel1
+
+ atf_check -s exit:0 kill -TERM $pid
+ wait $pid
+ status=$?
+
+ # Make sure that SIGTERM was received and handled.
+ atf_check_equal $status 143
+}
+
+wg_basic_netmap_cleanup()
+{
+ vnet_cleanup
+}
+
+# The kernel is expected to silently ignore any attempt to add a peer with a
+# public key identical to the host's.
+atf_test_case "wg_key_peerdev_shared" "cleanup"
+wg_key_peerdev_shared_head()
+{
+ atf_set descr 'Create a wg(4) interface with a shared pubkey between device and a peer'
+ atf_set require.user root
+ atf_set require.kmods if_wg
+}
+
+wg_key_peerdev_shared_body()
+{
+ local epair pri1 pub1 wg1
+ local endpoint1 tunnel1
+
+ pri1=$(wg genkey)
+
+ endpoint1=192.168.2.1
+ tunnel1=169.254.0.1
+
+ vnet_mkjail wgtest1
+
+ wg1=$(jexec wgtest1 ifconfig wg create)
+ echo "$pri1" | jexec wgtest1 wg set $wg1 listen-port 12345 \
+ private-key /dev/stdin
+ pub1=$(jexec wgtest1 wg show $wg1 public-key)
+
+ atf_check -s exit:0 \
+ jexec wgtest1 wg set ${wg1} peer "${pub1}" \
+ allowed-ips "${tunnel1}/32"
+
+ atf_check -o empty jexec wgtest1 wg show ${wg1} peers
+}
+
+wg_key_peerdev_shared_cleanup()
+{
+ vnet_cleanup
+}
+
+# When a wg(8) interface has a private key reassigned that corresponds to the
+# public key already on a peer, the kernel is expected to deconfigure the peer
+# to resolve the conflict.
+atf_test_case "wg_key_peerdev_makeshared" "cleanup"
+wg_key_peerdev_makeshared_head()
+{
+ atf_set descr 'Create a wg(4) interface and assign peer key to device'
+ atf_set require.progs wg
+}
+
+wg_key_peerdev_makeshared_body()
+{
+ local epair pri1 pub1 pri2 wg1 wg2
+ local endpoint1 tunnel1
+
+ pri1=$(wg genkey)
+ pri2=$(wg genkey)
+
+ endpoint1=192.168.2.1
+ tunnel1=169.254.0.1
+
+ vnet_mkjail wgtest1
+
+ wg1=$(jexec wgtest1 ifconfig wg create)
+ echo "$pri1" | jexec wgtest1 wg set $wg1 listen-port 12345 \
+ private-key /dev/stdin
+ pub1=$(jexec wgtest1 wg show $wg1 public-key)
+ wg2=$(jexec wgtest1 ifconfig wg create)
+ echo "$pri2" | jexec wgtest1 wg set $wg2 listen-port 12345 \
+ private-key /dev/stdin
+
+ atf_check -s exit:0 -o ignore \
+ jexec wgtest1 wg set ${wg2} peer "${pub1}" \
+ allowed-ips "${tunnel1}/32"
+
+ atf_check -o not-empty jexec wgtest1 wg show ${wg2} peers
+
+ jexec wgtest1 sh -c "echo '${pri1}' > pri1"
+
+ atf_check -s exit:0 \
+ jexec wgtest1 wg set ${wg2} private-key pri1
+
+ atf_check -o empty jexec wgtest1 wg show ${wg2} peers
+}
+
+wg_key_peerdev_makeshared_cleanup()
+{
+ vnet_cleanup
+}
+
+# The kernel is expected to create the wg socket in the jail context that the
+# wg interface was created in, even if the interface is moved to a different
+# vnet.
+atf_test_case "wg_vnet_parent_routing" "cleanup"
+wg_vnet_parent_routing_head()
+{
+ atf_set descr 'Create a wg(4) tunnel without epairs and pass traffic between jails'
+ atf_set require.user root
+ atf_set require.kmods if_wg
+}
+
+wg_vnet_parent_routing_body()
+{
+ local pri1 pri2 pub1 pub2 wg1 wg2
+ local tunnel1 tunnel2
+
+ pri1=$(wg genkey)
+ pri2=$(wg genkey)
+
+ tunnel1=169.254.0.1
+ tunnel2=169.254.0.2
+
+ vnet_init
+
+ wg1=$(ifconfig wg create)
+ wg2=$(ifconfig wg create)
+
+ vnet_mkjail wgtest1 ${wg1}
+ vnet_mkjail wgtest2 ${wg2}
+
+ echo "$pri1" | jexec wgtest1 wg set $wg1 listen-port 12345 \
+ private-key /dev/stdin
+ pub1=$(jexec wgtest1 wg show $wg1 public-key)
+ echo "$pri2" | jexec wgtest2 wg set $wg2 listen-port 12346 \
+ private-key /dev/stdin
+ pub2=$(jexec wgtest2 wg show $wg2 public-key)
+
+ atf_check -s exit:0 -o ignore \
+ jexec wgtest1 wg set $wg1 peer "$pub2" \
+ endpoint 127.0.0.1:12346 allowed-ips ${tunnel2}/32
+ atf_check -s exit:0 \
+ jexec wgtest1 ifconfig $wg1 inet ${tunnel1}/24 up
+
+ atf_check -s exit:0 -o ignore \
+ jexec wgtest2 wg set $wg2 peer "$pub1" \
+ endpoint 127.0.0.1:12345 allowed-ips ${tunnel1}/32
+ atf_check -s exit:0 \
+ jexec wgtest2 ifconfig $wg2 inet ${tunnel2}/24 up
+
+ # Sanity check ICMP counters; should clearly be nothing on these new
+ # jails. We'll check them as we go to ensure that the ICMP packets
+ # generated really are being handled by the jails' vnets.
+ atf_check -o not-match:"histogram" jexec wgtest1 netstat -s -p icmp
+ atf_check -o not-match:"histogram" jexec wgtest2 netstat -s -p icmp
+
+ # Generous timeout since the handshake takes some time.
+ atf_check -s exit:0 -o ignore jexec wgtest1 ping -c 1 -t 5 $tunnel2
+ atf_check -o match:"echo reply: 1" jexec wgtest1 netstat -s -p icmp
+ atf_check -o match:"echo: 1" jexec wgtest2 netstat -s -p icmp
+
+ atf_check -s exit:0 -o ignore jexec wgtest2 ping -c 1 $tunnel1
+ atf_check -o match:"echo reply: 1" jexec wgtest2 netstat -s -p icmp
+ atf_check -o match:"echo: 1" jexec wgtest1 netstat -s -p icmp
+}
+
+wg_vnet_parent_routing_cleanup()
+{
+ vnet_cleanup
+}
+
+# The kernel should now allow removing a single allowed-ip without having to
+# replace the whole list. We can't really test the atomicity of it all that
+# easily, but we'll trust that it worked right if just that addr/mask is gone.
+atf_test_case "wg_allowedip_incremental" "cleanup"
+wg_allowedip_incremental_head()
+{
+ atf_set descr "Add/remove allowed-ips from a peer with the +/- incremental syntax"
+ atf_set require.user root
+}
+
+wg_allowedip_incremental_body()
+{
+ local pri1 pri2 pub1 pub2 wg1
+ local tunnel1 tunnel2 tunnel3
+
+ kldload -n if_wg || atf_skip "This test requires if_wg and could not load it"
+
+ pri1=$(wg genkey)
+ pri2=$(wg genkey)
+ pub2=$(echo "$pri2" | wg pubkey)
+
+ tunnel1=169.254.0.1
+ tunnel2=169.254.0.2
+ tunnel3=169.254.0.3
+
+ vnet_mkjail wgtest1
+
+ wg1=$(jexec wgtest1 ifconfig wg create)
+ echo "$pri1" | jexec wgtest1 wg set $wg1 private-key /dev/stdin
+ pub1=$(jexec wgtest1 wg show $wg1 public-key)
+
+ atf_check -s exit:0 \
+ jexec wgtest1 wg set $wg1 peer $pub2 \
+ allowed-ips "${tunnel1}/32,${tunnel2}/32"
+
+ atf_check -o save:wg.allowed jexec wgtest1 wg show $wg1 allowed-ips
+ atf_check grep -q "${tunnel1}/32" wg.allowed
+ atf_check grep -q "${tunnel2}/32" wg.allowed
+
+ atf_check -s exit:0 \
+ jexec wgtest1 wg set $wg1 peer $pub2 \
+ allowed-ips "-${tunnel2}/32"
+
+ atf_check -o save:wg-2.allowed jexec wgtest1 wg show $wg1 allowed-ips
+ atf_check grep -q "${tunnel1}/32" wg-2.allowed
+ atf_check -s not-exit:0 grep -q "${tunnel2}/32" wg-2.allowed
+
+ atf_check -s exit:0 \
+ jexec wgtest1 wg set $wg1 peer $pub2 \
+ allowed-ips "+${tunnel2}/32"
+
+ atf_check -o save:wg-3.allowed jexec wgtest1 wg show $wg1 allowed-ips
+ atf_check grep -q "${tunnel1}/32" wg-3.allowed
+ atf_check grep -q "${tunnel2}/32" wg-3.allowed
+
+ # Now attempt to add the address yet again to confirm that it's not
+ # harmful.
+ atf_check -s exit:0 \
+ jexec wgtest1 wg set $wg1 peer $pub2 \
+ allowed-ips "+${tunnel2}/32"
+
+ atf_check -o save:wg-4.allowed -x \
+ "jexec wgtest1 wg show $wg1 allowed-ips | cut -f2 | tr ' ' '\n'"
+ atf_check -o match:"2 wg-4.allowed$" wc -l wg-4.allowed
+
+ # Finally, let's try removing an address that we never had at all and
+ # confirm that we still have our two addresses.
+ atf_check -s exit:0 \
+ jexec wgtest1 wg set $wg1 peer $pub2 \
+ allowed-ips "-${tunnel3}/32"
+
+ atf_check -o save:wg-5.allowed -x \
+ "jexec wgtest1 wg show $wg1 allowed-ips | cut -f2 | tr ' ' '\n'"
+ atf_check cmp -s wg-4.allowed wg-5.allowed
+}
+
+wg_allowedip_incremental_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "wg_allowedip_incremental_inet6" "cleanup"
+wg_allowedip_incremental_inet6_head()
+{
+ atf_set descr "Add/remove IPv6 allowed-ips from a peer with the +/- incremental syntax"
+ atf_set require.user root
+}
+
+wg_allowedip_incremental_inet6_body()
+{
+ local pri1 pri2 pub1 pub2 wg1
+ local tunnel1 tunnel2
+
+ kldload -n if_wg || atf_skip "This test requires if_wg and could not load it"
+
+ pri1=$(wg genkey)
+ pri2=$(wg genkey)
+ pub2=$(echo "$pri2" | wg pubkey)
+
+ tunnel1=2001:db8:1::1
+ tunnel2=2001:db8:1::2
+
+ vnet_mkjail wgtest1
+
+ wg1=$(jexec wgtest1 ifconfig wg create)
+ echo "$pri1" | jexec wgtest1 wg set $wg1 private-key /dev/stdin
+ pub1=$(jexec wgtest1 wg show $wg1 public-key)
+
+ atf_check -s exit:0 \
+ jexec wgtest1 wg set $wg1 peer $pub2 \
+ allowed-ips "${tunnel1}/128"
+ atf_check -o save:wg.allowed jexec wgtest1 wg show $wg1 allowed-ips
+ atf_check grep -q "${tunnel1}/128" wg.allowed
+
+ atf_check -s exit:0 \
+ jexec wgtest1 wg set $wg1 peer $pub2 \
+ allowed-ips "+${tunnel2}/128"
+ atf_check -o save:wg-2.allowed jexec wgtest1 wg show $wg1 allowed-ips
+ atf_check grep -q "${tunnel1}/128" wg-2.allowed
+ atf_check grep -q "${tunnel2}/128" wg-2.allowed
+
+ atf_check -s exit:0 \
+ jexec wgtest1 wg set $wg1 peer $pub2 \
+ allowed-ips "-${tunnel1}/128"
+ atf_check -o save:wg-3.allowed jexec wgtest1 wg show $wg1 allowed-ips
+ atf_check -s not-exit:0 grep -q "${tunnel1}/128" wg-3.allowed
+ atf_check grep -q "${tunnel2}/128" wg-3.allowed
+}
+
+wg_allowedip_incremental_inet6_cleanup()
+{
+ vnet_cleanup
+}
+
+
+atf_test_case "wg_allowedip_incremental_stealing" "cleanup"
+wg_allowedip_incremental_stealing_head()
+{
+ atf_set descr "Add/remove allowed-ips from a peer with the +/- incremental syntax to steal"
+ atf_set require.user root
+}
+
+wg_allowedip_incremental_stealing_body()
+{
+ local pri1 pri2 pri3 pub1 pub2 pub3 wg1
+ local regex2 regex3
+ local tunnel1 tunnel2
+
+ kldload -n if_wg || atf_skip "This test requires if_wg and could not load it"
+
+ pri1=$(wg genkey)
+ pri2=$(wg genkey)
+ pri3=$(wg genkey)
+ pub2=$(echo "$pri2" | wg pubkey)
+ pub3=$(echo "$pri3" | wg pubkey)
+
+ regex2=$(echo "$pub2" | sed -e 's/[+]/[+]/g')
+ regex3=$(echo "$pub3" | sed -e 's/[+]/[+]/g')
+
+ tunnel1=169.254.0.1
+ tunnel2=169.254.0.2
+ tunnel3=169.254.0.3
+
+ vnet_mkjail wgtest1
+
+ wg1=$(jexec wgtest1 ifconfig wg create)
+ echo "$pri1" | jexec wgtest1 wg set $wg1 private-key /dev/stdin
+ pub1=$(jexec wgtest1 wg show $wg1 public-key)
+
+ atf_check -s exit:0 \
+ jexec wgtest1 wg set $wg1 peer $pub2 \
+ allowed-ips "${tunnel1}/32,${tunnel2}/32"
+
+ atf_check -s exit:0 \
+ jexec wgtest1 wg set $wg1 peer $pub3 \
+ allowed-ips "${tunnel3}/32"
+
+ # First, confirm that the negative syntax doesn't do anything because
+ # we have the wrong peer.
+ atf_check -s exit:0 \
+ jexec wgtest1 wg set $wg1 peer $pub2 \
+ allowed-ips "-${tunnel3}/32"
+
+ atf_check -o save:wg.allowed jexec wgtest1 wg show $wg1 allowed-ips
+ atf_check grep -Eq "^${regex3}.+${tunnel3}/32" wg.allowed
+
+ # Next, steal it with an incremental move and check that it moved.
+ atf_check -s exit:0 \
+ jexec wgtest1 wg set $wg1 peer $pub2 \
+ allowed-ips "+${tunnel3}/32"
+
+ atf_check -o save:wg-2.allowed jexec wgtest1 wg show $wg1 allowed-ips
+
+ atf_check grep -Eq "^${regex2}.+${tunnel3}/32" wg-2.allowed
+ atf_check grep -Evq "^${regex3}.+${tunnel3}/32" wg-2.allowed
+}
+
+wg_allowedip_incremental_stealing_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "wg_basic"
+ atf_add_test_case "wg_basic_crossaf"
+ atf_add_test_case "wg_basic_netmap"
+ atf_add_test_case "wg_key_peerdev_shared"
+ atf_add_test_case "wg_key_peerdev_makeshared"
+ atf_add_test_case "wg_vnet_parent_routing"
+ atf_add_test_case "wg_allowedip_incremental"
+ atf_add_test_case "wg_allowedip_incremental_inet6"
+ atf_add_test_case "wg_allowedip_incremental_stealing"
+}
diff --git a/tests/sys/net/pcp.py b/tests/sys/net/pcp.py
new file mode 100644
index 000000000000..c0b6d4efc3b0
--- /dev/null
+++ b/tests/sys/net/pcp.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2021 Rubicon Communications, LLC (Netgate).
+#
+# 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.
+
+import argparse
+import logging
+logging.getLogger("scapy").setLevel(logging.CRITICAL)
+import scapy.all as sp
+import sys
+import os
+curdir = os.path.dirname(os.path.realpath(__file__))
+netpfil_common = curdir + "/../netpfil/common"
+sys.path.append(netpfil_common)
+from sniffer import Sniffer
+
+def check_pcp(args, packet):
+ vlan = packet.getlayer(sp.Dot1Q)
+
+ if vlan is None:
+ return False
+
+ if not packet.getlayer(sp.BOOTP):
+ return False
+
+ if vlan.prio == int(args.expect_pcp[0]):
+ return True
+
+ return False
+
+def main():
+ parser = argparse.ArgumentParser("pcp.py",
+ description="PCP test tool")
+ parser.add_argument('--recvif', nargs=1,
+ required=True,
+ help='The interface where to look for packets to check')
+ parser.add_argument('--expect-pcp', nargs=1,
+ help='The expected PCP value on VLAN packets')
+
+ args = parser.parse_args()
+
+ sniffer = Sniffer(args, check_pcp, args.recvif[0], timeout=20)
+
+ sniffer.join()
+
+ if sniffer.correctPackets:
+ sys.exit(0)
+
+ sys.exit(1)
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/sys/net/randsleep.c b/tests/sys/net/randsleep.c
new file mode 100644
index 000000000000..55f1fff45617
--- /dev/null
+++ b/tests/sys/net/randsleep.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2014 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * 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,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+ *
+ * Authors: Alan Somers (Spectra Logic Corporation)
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define RANDOM_MAX ((1U<<31) - 1)
+
+int main(int argc, char** argv){
+ useconds_t max_usecs, usecs;
+ double frac;
+
+ if (argc != 2) {
+ printf("Usage: randsleep <max_microseconds>\n");
+ exit(2);
+ }
+
+ errno = 0;
+ max_usecs = (useconds_t)strtol(argv[1], NULL, 0);
+ if (errno != 0) {
+ perror("strtol");
+ exit(1);
+ }
+ srandomdev();
+ frac = (double)random() / (double)RANDOM_MAX;
+ usecs = (useconds_t)((double)max_usecs * frac);
+ usleep(usecs);
+
+ return (0);
+}
diff --git a/tests/sys/net/routing/Makefile b/tests/sys/net/routing/Makefile
new file mode 100644
index 000000000000..c725d23f15d1
--- /dev/null
+++ b/tests/sys/net/routing/Makefile
@@ -0,0 +1,21 @@
+PACKAGE= tests
+WARNS?= 1
+
+TESTSDIR= ${TESTSBASE}/sys/net/routing
+
+ATF_TESTS_C += test_rtsock_l3
+ATF_TESTS_C += test_rtsock_lladdr
+ATF_TESTS_C += test_rtsock_ops
+ATF_TESTS_PYTEST += test_routing_l3.py
+ATF_TESTS_PYTEST += test_rtsock_multipath.py
+
+${PACKAGE}FILES+= generic_cleanup.sh
+${PACKAGE}FILESMODE_generic_cleanup.sh=0555
+
+# Most of the tests operates on a common IPv4/IPv6 prefix,
+# so running them in parallel will lead to weird results.
+TEST_METADATA+= is_exclusive=true
+
+CFLAGS+= -I${.CURDIR:H:H:H}
+
+.include <bsd.test.mk>
diff --git a/tests/sys/net/routing/generic_cleanup.sh b/tests/sys/net/routing/generic_cleanup.sh
new file mode 100755
index 000000000000..4c9ded969d1f
--- /dev/null
+++ b/tests/sys/net/routing/generic_cleanup.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+#-
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2020 Alexander V. Chernikov
+#
+# 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.
+#
+#
+
+
+srcdir=`dirname $0`
+. ${srcdir}/../../common/vnet.subr
+
+vnet_cleanup
+
diff --git a/tests/sys/net/routing/params.h b/tests/sys/net/routing/params.h
new file mode 100644
index 000000000000..f66678bceca9
--- /dev/null
+++ b/tests/sys/net/routing/params.h
@@ -0,0 +1,36 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Alexander V. Chernikov
+ *
+ * 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.
+ */
+
+#ifndef _NET_ROUTING_PARAMS_H_
+#define _NET_ROUTING_PARAMS_H_
+
+/* files to store state */
+#define JAILS_FNAME "created_jails.lst"
+#define IFACES_FNAME "created_interfaces.lst"
+
+#endif
+
diff --git a/tests/sys/net/routing/rtsock_common.h b/tests/sys/net/routing/rtsock_common.h
new file mode 100644
index 000000000000..eed4a348ad74
--- /dev/null
+++ b/tests/sys/net/routing/rtsock_common.h
@@ -0,0 +1,882 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Alexander V. Chernikov
+ *
+ * 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.
+ */
+
+#ifndef _NET_ROUTING_RTSOCK_COMMON_H_
+#define _NET_ROUTING_RTSOCK_COMMON_H_
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <poll.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/jail.h>
+#include <sys/linker.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+
+#include <arpa/inet.h>
+#include <net/ethernet.h>
+
+#include <netinet/in.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/nd6.h>
+
+#include <ifaddrs.h>
+
+#include <errno.h>
+#include <err.h>
+#include <sysexits.h>
+
+#include <atf-c.h>
+#include "freebsd_test_suite/macros.h"
+
+#include "rtsock_print.h"
+#include "params.h"
+
+void rtsock_update_rtm_len(struct rt_msghdr *rtm);
+void rtsock_validate_message(char *buffer, ssize_t len);
+void rtsock_add_rtm_sa(struct rt_msghdr *rtm, int addr_type, struct sockaddr *sa);
+
+void file_append_line(char *fname, char *text);
+
+static int _rtm_seq = 42;
+
+
+/*
+ * Checks if the interface cloner module is present for @name.
+ */
+static int
+_check_cloner(char *name)
+{
+ struct if_clonereq ifcr;
+ char *cp, *buf;
+ int idx;
+ int s;
+ int found = 0;
+
+ s = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ if (s == -1)
+ err(1, "socket(AF_LOCAL,SOCK_DGRAM)");
+
+ memset(&ifcr, 0, sizeof(ifcr));
+
+ if (ioctl(s, SIOCIFGCLONERS, &ifcr) < 0)
+ err(1, "SIOCIFGCLONERS for count");
+
+ buf = malloc(ifcr.ifcr_total * IFNAMSIZ);
+ if (buf == NULL)
+ err(1, "unable to allocate cloner name buffer");
+
+ ifcr.ifcr_count = ifcr.ifcr_total;
+ ifcr.ifcr_buffer = buf;
+
+ if (ioctl(s, SIOCIFGCLONERS, &ifcr) < 0)
+ err(1, "SIOCIFGCLONERS for names");
+
+ /*
+ * In case some disappeared in the mean time, clamp it down.
+ */
+ if (ifcr.ifcr_count > ifcr.ifcr_total)
+ ifcr.ifcr_count = ifcr.ifcr_total;
+
+ for (cp = buf, idx = 0; idx < ifcr.ifcr_count; idx++, cp += IFNAMSIZ) {
+ if (!strcmp(cp, name)) {
+ found = 1;
+ break;
+ }
+ }
+
+ free(buf);
+ close(s);
+
+ return (found);
+}
+
+static char *
+iface_create(char *ifname_orig)
+{
+ struct ifreq ifr;
+ int s;
+ char prefix[IFNAMSIZ], ifname[IFNAMSIZ], *result;
+
+ char *src, *dst;
+ for (src = ifname_orig, dst = prefix; *src && isalpha(*src); src++)
+ *dst++ = *src;
+ *dst = '\0';
+
+ memset(&ifr, 0, sizeof(struct ifreq));
+
+ s = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ strlcpy(ifr.ifr_name, ifname_orig, sizeof(ifr.ifr_name));
+
+ RLOG("creating iface %s %s", prefix, ifr.ifr_name);
+ if (ioctl(s, SIOCIFCREATE2, &ifr) < 0)
+ err(1, "SIOCIFCREATE2");
+
+ strlcpy(ifname, ifr.ifr_name, IFNAMSIZ);
+ RLOG("created interface %s", ifname);
+
+ result = strdup(ifname);
+
+ file_append_line(IFACES_FNAME, ifname);
+ if (strstr(ifname, "epair") == ifname) {
+ /* call returned epairXXXa, need to add epairXXXb */
+ ifname[strlen(ifname) - 1] = 'b';
+ file_append_line(IFACES_FNAME, ifname);
+ }
+
+ return (result);
+}
+
+static int
+iface_destroy(char *ifname)
+{
+ struct ifreq ifr;
+ int s;
+
+ s = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+
+ RLOG("destroying interface %s", ifname);
+ if (ioctl(s, SIOCIFDESTROY, &ifr) < 0)
+ return (0);
+
+ return (1);
+}
+
+/*
+ * Open tunneling device such as tuntap and returns fd.
+ */
+int
+iface_open(char *ifname)
+{
+ char path[256];
+
+ snprintf(path, sizeof(path), "/dev/%s", ifname);
+
+ RLOG("opening interface %s", ifname);
+ int fd = open(path, O_RDWR|O_EXCL);
+ if (fd == -1) {
+ RLOG_ERRNO("unable to open interface %s", ifname);
+ return (-1);
+ }
+
+ return (fd);
+}
+
+/*
+ * Sets primary IPv4 addr.
+ * Returns 0 on success.
+ */
+static inline int
+iface_setup_addr(char *ifname, char *addr, int plen)
+{
+ char cmd[512];
+ char *af;
+
+ if (strchr(addr, ':'))
+ af = "inet6";
+ else
+ af = "inet";
+ RLOG("setting af_%s %s/%d on %s", af, addr, plen, ifname);
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s %s %s/%d", ifname,
+ af, addr, plen);
+
+ return system(cmd);
+}
+
+/*
+ * Removes primary IPv4 prefix.
+ * Returns 0 on success.
+ */
+static inline int
+iface_delete_addr(char *ifname, char *addr)
+{
+ char cmd[512];
+
+ if (strchr(addr, ':')) {
+ RLOG("removing IPv6 %s from %s", addr, ifname);
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s inet6 %s delete", ifname, addr);
+ } else {
+ RLOG("removing IPv4 %s from %s", addr, ifname);
+ snprintf(cmd, sizeof(cmd), "/sbin/ifconfig %s -alias %s", ifname, addr);
+ }
+
+ return system(cmd);
+}
+
+int
+iface_turn_up(char *ifname)
+{
+ struct ifreq ifr;
+ int s;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ RLOG_ERRNO("socket");
+ return (-1);
+ }
+ memset(&ifr, 0, sizeof(struct ifreq));
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ if (ioctl(s, SIOCGIFFLAGS, (caddr_t)&ifr) < 0) {
+ RLOG_ERRNO("ioctl(SIOCGIFFLAGS)");
+ return (-1);
+ }
+ /* Update flags */
+ if ((ifr.ifr_flags & IFF_UP) == 0) {
+ ifr.ifr_flags |= IFF_UP;
+ if (ioctl(s, SIOCSIFFLAGS, (caddr_t)&ifr) < 0) {
+ RLOG_ERRNO("ioctl(SIOSGIFFLAGS)");
+ return (-1);
+ }
+ RLOG("turned interface %s up", ifname);
+ }
+
+ return (0);
+}
+
+/*
+ * Removes ND6_IFF_IFDISABLED from IPv6 interface flags.
+ * Returns 0 on success.
+ */
+int
+iface_enable_ipv6(char *ifname)
+{
+ struct in6_ndireq nd;
+ int s;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ err(1, "socket");
+ }
+ memset(&nd, 0, sizeof(nd));
+ strlcpy(nd.ifname, ifname, sizeof(nd.ifname));
+ if (ioctl(s, SIOCGIFINFO_IN6, (caddr_t)&nd) < 0) {
+ RLOG_ERRNO("ioctl(SIOCGIFINFO_IN6)");
+ return (-1);
+ }
+ /* Update flags */
+ if ((nd.ndi.flags & ND6_IFF_IFDISABLED) != 0) {
+ nd.ndi.flags &= ~ND6_IFF_IFDISABLED;
+ if (ioctl(s, SIOCSIFINFO_IN6, (caddr_t)&nd) < 0) {
+ RLOG_ERRNO("ioctl(SIOCSIFINFO_IN6)");
+ return (-1);
+ }
+ RLOG("enabled IPv6 for %s", ifname);
+ }
+
+ return (0);
+}
+
+void
+file_append_line(char *fname, char *text)
+{
+ FILE *f;
+
+ f = fopen(fname, "a");
+ fputs(text, f);
+ fputs("\n", f);
+ fclose(f);
+}
+
+static int
+vnet_wait_interface(char *vnet_name, char *ifname)
+{
+ char buf[512], cmd[512], *line, *token;
+ FILE *fp;
+ int i;
+
+ snprintf(cmd, sizeof(cmd), "/usr/sbin/jexec %s /sbin/ifconfig -l", vnet_name);
+ for (int i = 0; i < 50; i++) {
+ fp = popen(cmd, "r");
+ line = fgets(buf, sizeof(buf), fp);
+ /* cut last\n */
+ if (line[0])
+ line[strlen(line)-1] = '\0';
+ while ((token = strsep(&line, " ")) != NULL) {
+ if (strcmp(token, ifname) == 0)
+ return (1);
+ }
+
+ /* sleep 100ms */
+ usleep(1000 * 100);
+ }
+
+ return (0);
+}
+
+void
+vnet_switch(char *vnet_name, char **ifnames, int count)
+{
+ char buf[512], cmd[512], *line;
+ FILE *fp;
+ int jid, len, ret;
+
+ RLOG("switching to vnet %s with interface(s) %s", vnet_name, ifnames[0]);
+ len = snprintf(cmd, sizeof(cmd),
+ "/usr/sbin/jail -i -c name=%s persist vnet", vnet_name);
+ for (int i = 0; i < count && len < sizeof(cmd); i++) {
+ len += snprintf(&cmd[len], sizeof(cmd) - len,
+ " vnet.interface=%s", ifnames[i]);
+ }
+ RLOG("jail cmd: \"%s\"\n", cmd);
+
+ fp = popen(cmd, "r");
+ if (fp == NULL)
+ atf_tc_fail("jail creation failed");
+ line = fgets(buf, sizeof(buf), fp);
+ if (line == NULL)
+ atf_tc_fail("empty output from jail(8)");
+ jid = strtol(line, NULL, 10);
+ if (jid <= 0) {
+ atf_tc_fail("invalid jail output: %s", line);
+ }
+
+ RLOG("created jail jid=%d", jid);
+ file_append_line(JAILS_FNAME, vnet_name);
+
+ /* Wait while interface appearsh inside vnet */
+ for (int i = 0; i < count; i++) {
+ if (vnet_wait_interface(vnet_name, ifnames[i]))
+ continue;
+ atf_tc_fail("unable to move interface %s to jail %s",
+ ifnames[i], vnet_name);
+ }
+
+ if (jail_attach(jid) == -1) {
+ RLOG_ERRNO("jail %s attach failed: ret=%d", vnet_name, errno);
+ atf_tc_fail("jail attach failed");
+ }
+
+ RLOG("attached to the jail");
+}
+
+void
+vnet_switch_one(char *vnet_name, char *ifname)
+{
+ char *ifnames[1];
+
+ ifnames[0] = ifname;
+ vnet_switch(vnet_name, ifnames, 1);
+}
+
+
+#define SA_F_IGNORE_IFNAME 0x01
+#define SA_F_IGNORE_IFTYPE 0x02
+#define SA_F_IGNORE_MEMCMP 0x04
+int
+sa_equal_msg_flags(const struct sockaddr *a, const struct sockaddr *b, char *msg, size_t sz, int flags)
+{
+ char a_s[64], b_s[64];
+ const struct sockaddr_in *a4, *b4;
+ const struct sockaddr_in6 *a6, *b6;
+ const struct sockaddr_dl *al, *bl;
+
+ if (a == NULL) {
+ snprintf(msg, sz, "first sa is NULL");
+ return 0;
+ }
+ if (b == NULL) {
+ snprintf(msg, sz, "second sa is NULL");
+ return 0;
+ }
+
+ if (a->sa_family != b->sa_family) {
+ snprintf(msg, sz, "family: %d vs %d", a->sa_family, b->sa_family);
+ return 0;
+ }
+ if (a->sa_len != b->sa_len) {
+ snprintf(msg, sz, "len: %d vs %d", a->sa_len, b->sa_len);
+ return 0;
+ }
+
+ switch (a->sa_family) {
+ case AF_INET:
+ a4 = (const struct sockaddr_in *)a;
+ b4 = (const struct sockaddr_in *)b;
+ if (a4->sin_addr.s_addr != b4->sin_addr.s_addr) {
+ inet_ntop(AF_INET, &a4->sin_addr, a_s, sizeof(a_s));
+ inet_ntop(AF_INET, &b4->sin_addr, b_s, sizeof(b_s));
+ snprintf(msg, sz, "addr diff: %s vs %s", a_s, b_s);
+ return 0;
+ }
+ if (a4->sin_port != b4->sin_port) {
+ snprintf(msg, sz, "port diff: %d vs %d",
+ ntohs(a4->sin_port), ntohs(b4->sin_port));
+ //return 0;
+ }
+ const uint32_t *a32, *b32;
+ a32 = (const uint32_t *)a4->sin_zero;
+ b32 = (const uint32_t *)b4->sin_zero;
+ if ((*a32 != *b32) || (*(a32 + 1) != *(b32 + 1))) {
+ snprintf(msg, sz, "zero diff: 0x%08X%08X vs 0x%08X%08X",
+ ntohl(*a32), ntohl(*(a32 + 1)),
+ ntohl(*b32), ntohl(*(b32 + 1)));
+ return 0;
+ }
+ return 1;
+ case AF_INET6:
+ a6 = (const struct sockaddr_in6 *)a;
+ b6 = (const struct sockaddr_in6 *)b;
+ if (!IN6_ARE_ADDR_EQUAL(&a6->sin6_addr, &b6->sin6_addr)) {
+ inet_ntop(AF_INET6, &a6->sin6_addr, a_s, sizeof(a_s));
+ inet_ntop(AF_INET6, &b6->sin6_addr, b_s, sizeof(b_s));
+ snprintf(msg, sz, "addr diff: %s vs %s", a_s, b_s);
+ return 0;
+ }
+ if (a6->sin6_scope_id != b6->sin6_scope_id) {
+ snprintf(msg, sz, "scope diff: %u vs %u", a6->sin6_scope_id, b6->sin6_scope_id);
+ return 0;
+ }
+ break;
+ case AF_LINK:
+ al = (const struct sockaddr_dl *)a;
+ bl = (const struct sockaddr_dl *)b;
+
+ if (al->sdl_index != bl->sdl_index) {
+ snprintf(msg, sz, "sdl_index diff: %u vs %u", al->sdl_index, bl->sdl_index);
+ return 0;
+ }
+
+ if ((al->sdl_alen != bl->sdl_alen) || (memcmp(LLADDR(al), LLADDR(bl), al->sdl_alen) != 0)) {
+ char abuf[64], bbuf[64];
+ sa_print_hd(abuf, sizeof(abuf), LLADDR(al), al->sdl_alen);
+ sa_print_hd(bbuf, sizeof(bbuf), LLADDR(bl), bl->sdl_alen);
+ snprintf(msg, sz, "sdl_alen diff: {%s} (%d) vs {%s} (%d)",
+ abuf, al->sdl_alen, bbuf, bl->sdl_alen);
+ return 0;
+ }
+
+ if (((flags & SA_F_IGNORE_IFTYPE) == 0) && (al->sdl_type != bl->sdl_type)) {
+ snprintf(msg, sz, "sdl_type diff: %u vs %u", al->sdl_type, bl->sdl_type);
+ return 0;
+ }
+
+ if (((flags & SA_F_IGNORE_IFNAME) == 0) && ((al->sdl_nlen != bl->sdl_nlen) ||
+ (memcmp(al->sdl_data, bl->sdl_data, al->sdl_nlen) != 0))) {
+ char abuf[64], bbuf[64];
+ memcpy(abuf, al->sdl_data, al->sdl_nlen);
+ abuf[al->sdl_nlen] = '\0';
+ memcpy(bbuf, bl->sdl_data, bl->sdl_nlen);
+ abuf[bl->sdl_nlen] = '\0';
+ snprintf(msg, sz, "sdl_nlen diff: {%s} (%d) vs {%s} (%d)",
+ abuf, al->sdl_nlen, bbuf, bl->sdl_nlen);
+ return 0;
+ }
+
+ if (flags & SA_F_IGNORE_MEMCMP)
+ return 1;
+ break;
+ }
+
+ if (memcmp(a, b, a->sa_len)) {
+ int i;
+ for (i = 0; i < a->sa_len; i++)
+ if (((const char *)a)[i] != ((const char *)b)[i])
+ break;
+
+ sa_print(a, 1);
+ sa_print(b, 1);
+
+ snprintf(msg, sz, "overall memcmp() reports diff for af %d offset %d",
+ a->sa_family, i);
+ return 0;
+ }
+ return 1;
+}
+
+int
+sa_equal_msg(const struct sockaddr *a, const struct sockaddr *b, char *msg, size_t sz)
+{
+
+ return sa_equal_msg_flags(a, b, msg, sz, 0);
+}
+
+void
+sa_fill_mask4(struct sockaddr_in *sin, int plen)
+{
+
+ memset(sin, 0, sizeof(struct sockaddr_in));
+ sin->sin_family = AF_INET;
+ sin->sin_len = sizeof(struct sockaddr_in);
+ sin->sin_addr.s_addr = htonl(plen ? ~((1 << (32 - plen)) - 1) : 0);
+}
+
+void
+sa_fill_mask6(struct sockaddr_in6 *sin6, uint8_t mask)
+{
+ uint32_t *cp;
+
+ memset(sin6, 0, sizeof(struct sockaddr_in6));
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+
+ for (cp = (uint32_t *)&sin6->sin6_addr; mask >= 32; mask -= 32)
+ *cp++ = 0xFFFFFFFF;
+ if (mask > 0)
+ *cp = htonl(mask ? ~((1 << (32 - mask)) - 1) : 0);
+}
+
+/* 52:54:00:14:e3:10 */
+#define ETHER_MAC_MAX_LENGTH 17
+
+int
+sa_convert_str_to_sa(const char *_addr, struct sockaddr *sa)
+{
+ int error;
+
+ int af = AF_UNSPEC;
+
+ char *addr = strdup(_addr);
+ int retcode = 0;
+
+ /* classify AF by str */
+ if (strchr(addr, ':')) {
+ /* inet6 or ether */
+ char *k;
+ int delim_cnt = 0;
+ for (k = addr; *k; k++)
+ if (*k == ':')
+ delim_cnt++;
+ af = AF_INET6;
+
+ if (delim_cnt == 5) {
+ k = strchr(addr, '%');
+ if (k != NULL && (k - addr) <= ETHER_MAC_MAX_LENGTH)
+ af = AF_LINK;
+ }
+ } else if (strchr(addr, '.'))
+ af = AF_INET;
+
+ /* */
+ char *delimiter;
+ int ifindex = 0;
+ char *ifname = NULL;
+ if ((delimiter = strchr(addr, '%')) != NULL) {
+ *delimiter = '\0';
+ ifname = delimiter + 1;
+ ifindex = if_nametoindex(ifname);
+ if (ifindex == 0)
+ RLOG("unable to find ifindex for '%s'", ifname);
+ else
+ RLOG("if %s mapped to %d", ifname, ifindex);
+ }
+
+ if (af == AF_INET6) {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
+ memset(sin6, 0, sizeof(struct sockaddr_in6));
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_scope_id = ifindex;
+ error = inet_pton(AF_INET6, addr, &sin6->sin6_addr);
+ if (error != 1)
+ RLOG_ERRNO("inet_ntop() failed: ret=%d", error);
+ else
+ retcode = 1;
+ } else if (af == AF_INET) {
+ struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+ memset(sin, 0, sizeof(struct sockaddr_in));
+ sin->sin_family = AF_INET;
+ sin->sin_len = sizeof(struct sockaddr_in);
+ error = inet_pton(AF_INET, addr, &sin->sin_addr);
+ if (error != 1)
+ RLOG("inet_ntop() failed: ret=%d", error);
+ else
+ retcode = 1;
+ } else if (af == AF_LINK) {
+ struct sockaddr_dl *sdl = (struct sockaddr_dl *)sa;
+ memset(sdl, 0, sizeof(struct sockaddr_dl));
+ sdl->sdl_family = AF_LINK;
+ sdl->sdl_len = sizeof(struct sockaddr_dl);
+ sdl->sdl_index = ifindex;
+ sdl->sdl_alen = 6;
+ struct ether_addr *ea = (struct ether_addr *)LLADDR(sdl);
+ if (ether_aton_r(addr, ea) == NULL)
+ RLOG("ether_aton() failed");
+ else
+ retcode = 1;
+ }
+
+ return (retcode);
+}
+
+
+int
+rtsock_setup_socket()
+{
+ int fd;
+ int af = AF_UNSPEC; /* 0 to capture messages from all AFs */
+ fd = socket(PF_ROUTE, SOCK_RAW, af);
+
+ ATF_REQUIRE_MSG(fd != -1, "rtsock open failed: %s", strerror(errno));
+
+ /* Listen for our messages */
+ int on = 1;
+ if (setsockopt(fd, SOL_SOCKET,SO_USELOOPBACK, &on, sizeof(on)) < 0)
+ RLOG_ERRNO("setsockopt failed");
+
+ return (fd);
+}
+
+ssize_t
+rtsock_send_rtm(int fd, struct rt_msghdr *rtm)
+{
+ int my_errno;
+ ssize_t len;
+
+ rtsock_update_rtm_len(rtm);
+
+ len = write(fd, rtm, rtm->rtm_msglen);
+ my_errno = errno;
+ RTSOCK_ATF_REQUIRE_MSG(rtm, len == rtm->rtm_msglen,
+ "rtsock write failed: want %d got %zd (%s)",
+ rtm->rtm_msglen, len, strerror(my_errno));
+
+ return (len);
+}
+
+struct rt_msghdr *
+rtsock_read_rtm(int fd, char *buffer, size_t buflen)
+{
+ ssize_t len;
+ struct pollfd pfd;
+ int poll_delay = 5 * 1000; /* 5 seconds */
+
+ /* Check for the data available to read first */
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fd;
+ pfd.events = POLLIN;
+
+ if (poll(&pfd, 1, poll_delay) == 0)
+ ATF_REQUIRE_MSG(1 == 0, "rtsock read timed out (%d seconds passed)",
+ poll_delay / 1000);
+
+ len = read(fd, buffer, buflen);
+ int my_errno = errno;
+ ATF_REQUIRE_MSG(len > 0, "rtsock read failed: %s", strerror(my_errno));
+
+ rtsock_validate_message(buffer, len);
+ return ((struct rt_msghdr *)buffer);
+}
+
+struct rt_msghdr *
+rtsock_read_rtm_reply(int fd, char *buffer, size_t buflen, int seq)
+{
+ struct rt_msghdr *rtm;
+ int found = 0;
+
+ while (true) {
+ rtm = rtsock_read_rtm(fd, buffer, buflen);
+ if (rtm->rtm_pid == getpid() && rtm->rtm_seq == seq)
+ found = 1;
+ if (found)
+ RLOG("--- MATCHED RTSOCK MESSAGE ---");
+ else
+ RLOG("--- SKIPPED RTSOCK MESSAGE ---");
+ rtsock_print_rtm(rtm);
+ if (found)
+ return (rtm);
+ }
+
+ /* NOTREACHED */
+}
+
+void
+rtsock_prepare_route_message_base(struct rt_msghdr *rtm, int cmd)
+{
+
+ memset(rtm, 0, sizeof(struct rt_msghdr));
+ rtm->rtm_type = cmd;
+ rtm->rtm_version = RTM_VERSION;
+ rtm->rtm_seq = _rtm_seq++;
+}
+
+void
+rtsock_prepare_route_message(struct rt_msghdr *rtm, int cmd, struct sockaddr *dst,
+ struct sockaddr *mask, struct sockaddr *gw)
+{
+
+ rtsock_prepare_route_message_base(rtm, cmd);
+ if (dst != NULL)
+ rtsock_add_rtm_sa(rtm, RTA_DST, dst);
+
+ if (gw != NULL) {
+ rtsock_add_rtm_sa(rtm, RTA_GATEWAY, gw);
+ rtm->rtm_flags |= RTF_GATEWAY;
+ }
+
+ if (mask != NULL)
+ rtsock_add_rtm_sa(rtm, RTA_NETMASK, mask);
+}
+
+void
+rtsock_add_rtm_sa(struct rt_msghdr *rtm, int addr_type, struct sockaddr *sa)
+{
+ char *ptr = (char *)(rtm + 1);
+ for (int i = 0; i < RTAX_MAX; i++) {
+ if (rtm->rtm_addrs & (1 << i)) {
+ /* add */
+ ptr += ALIGN(((struct sockaddr *)ptr)->sa_len);
+ }
+ }
+
+ rtm->rtm_addrs |= addr_type;
+ memcpy(ptr, sa, sa->sa_len);
+}
+
+struct sockaddr *
+rtsock_find_rtm_sa(struct rt_msghdr *rtm, int addr_type)
+{
+ char *ptr = (char *)(rtm + 1);
+ for (int i = 0; i < RTAX_MAX; i++) {
+ if (rtm->rtm_addrs & (1 << i)) {
+ if (addr_type == (1 << i))
+ return ((struct sockaddr *)ptr);
+ /* add */
+ ptr += ALIGN(((struct sockaddr *)ptr)->sa_len);
+ }
+ }
+
+ return (NULL);
+}
+
+size_t
+rtsock_calc_rtm_len(struct rt_msghdr *rtm)
+{
+ size_t len = sizeof(struct rt_msghdr);
+
+ char *ptr = (char *)(rtm + 1);
+ for (int i = 0; i < RTAX_MAX; i++) {
+ if (rtm->rtm_addrs & (1 << i)) {
+ /* add */
+ int sa_len = ALIGN(((struct sockaddr *)ptr)->sa_len);
+ len += sa_len;
+ ptr += sa_len;
+ }
+ }
+
+ return len;
+}
+
+void
+rtsock_update_rtm_len(struct rt_msghdr *rtm)
+{
+
+ rtm->rtm_msglen = rtsock_calc_rtm_len(rtm);
+}
+
+static void
+_validate_message_sockaddrs(char *buffer, int rtm_len, size_t offset, int rtm_addrs)
+{
+ struct sockaddr *sa;
+ size_t parsed_len = offset;
+
+ /* Offset denotes initial header size */
+ sa = (struct sockaddr *)(buffer + offset);
+
+ for (int i = 0; i < RTAX_MAX; i++) {
+ if ((rtm_addrs & (1 << i)) == 0)
+ continue;
+ parsed_len += SA_SIZE(sa);
+ RTSOCK_ATF_REQUIRE_MSG((struct rt_msghdr *)buffer, parsed_len <= rtm_len,
+ "SA %d: len %d exceeds msg size %d", i, (int)sa->sa_len, rtm_len);
+ if (sa->sa_family == AF_LINK) {
+ struct sockaddr_dl *sdl = (struct sockaddr_dl *)sa;
+ int data_len = sdl->sdl_nlen + sdl->sdl_alen;
+ data_len += offsetof(struct sockaddr_dl, sdl_data);
+
+ RTSOCK_ATF_REQUIRE_MSG((struct rt_msghdr *)buffer,
+ data_len <= rtm_len,
+ "AF_LINK data size exceeds total len: %u vs %u, nlen=%d alen=%d",
+ data_len, rtm_len, sdl->sdl_nlen, sdl->sdl_alen);
+ }
+ sa = (struct sockaddr *)((char *)sa + SA_SIZE(sa));
+ }
+}
+
+/*
+ * Raises error if base syntax checks fails.
+ */
+void
+rtsock_validate_message(char *buffer, ssize_t len)
+{
+ struct rt_msghdr *rtm;
+
+ ATF_REQUIRE_MSG(len > 0, "read() return %zd, error: %s", len, strerror(errno));
+
+ rtm = (struct rt_msghdr *)buffer;
+ ATF_REQUIRE_MSG(rtm->rtm_version == RTM_VERSION, "unknown RTM_VERSION: expected %d got %d",
+ RTM_VERSION, rtm->rtm_version);
+ ATF_REQUIRE_MSG(rtm->rtm_msglen <= len, "wrong message length: expected %d got %d",
+ (int)len, (int)rtm->rtm_msglen);
+
+ switch (rtm->rtm_type) {
+ case RTM_GET:
+ case RTM_ADD:
+ case RTM_DELETE:
+ case RTM_CHANGE:
+ _validate_message_sockaddrs(buffer, rtm->rtm_msglen,
+ sizeof(struct rt_msghdr), rtm->rtm_addrs);
+ break;
+ case RTM_DELADDR:
+ case RTM_NEWADDR:
+ _validate_message_sockaddrs(buffer, rtm->rtm_msglen,
+ sizeof(struct ifa_msghdr), ((struct ifa_msghdr *)buffer)->ifam_addrs);
+ break;
+ }
+}
+
+void
+rtsock_validate_pid_ours(struct rt_msghdr *rtm)
+{
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_pid == getpid(), "expected pid %d, got %d",
+ getpid(), rtm->rtm_pid);
+}
+
+void
+rtsock_validate_pid_user(struct rt_msghdr *rtm)
+{
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_pid > 0, "expected non-zero pid, got %d",
+ rtm->rtm_pid);
+}
+
+void
+rtsock_validate_pid_kernel(struct rt_msghdr *rtm)
+{
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_pid == 0, "expected zero pid, got %d",
+ rtm->rtm_pid);
+}
+
+#endif
diff --git a/tests/sys/net/routing/rtsock_config.h b/tests/sys/net/routing/rtsock_config.h
new file mode 100644
index 000000000000..345fc13f167c
--- /dev/null
+++ b/tests/sys/net/routing/rtsock_config.h
@@ -0,0 +1,175 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Alexander V. Chernikov
+ *
+ * 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.
+ */
+
+#ifndef _NET_ROUTING_RTSOCK_CONFIG_H_
+#define _NET_ROUTING_RTSOCK_CONFIG_H_
+
+#include "params.h"
+
+struct rtsock_config_options {
+ int num_interfaces; /* number of interfaces to create */
+};
+
+struct rtsock_test_config {
+ int ifindex;
+ char net4_str[INET_ADDRSTRLEN];
+ char addr4_str[INET_ADDRSTRLEN];
+ char net6_str[INET6_ADDRSTRLEN];
+ char addr6_str[INET6_ADDRSTRLEN];
+ struct sockaddr_in net4;
+ struct sockaddr_in mask4;
+ struct sockaddr_in addr4;
+ struct sockaddr_in6 net6;
+ struct sockaddr_in6 mask6;
+ struct sockaddr_in6 addr6;
+ int plen4;
+ int plen6;
+ char *remote_lladdr;
+ char *ifname;
+ char **ifnames;
+ bool autocreated_interface;
+ int rtsock_fd;
+ int num_interfaces;
+};
+
+struct rtsock_test_config *
+config_setup(const atf_tc_t *tc, struct rtsock_config_options *co)
+{
+ struct rtsock_config_options default_co;
+ struct rtsock_test_config *c;
+ char buf[64], *s;
+ const char *key;
+ int mask;
+
+ if (co == NULL) {
+ bzero(&default_co, sizeof(default_co));
+ co = &default_co;
+ co->num_interfaces = 1;
+ }
+
+ c = calloc(1, sizeof(struct rtsock_test_config));
+ c->rtsock_fd = -1;
+
+ key = atf_tc_get_config_var_wd(tc, "rtsock.v4prefix", "192.0.2.0/24");
+ strlcpy(buf, key, sizeof(buf));
+ if ((s = strchr(buf, '/')) == NULL)
+ return (NULL);
+ *s++ = '\0';
+ mask = strtol(s, NULL, 10);
+ if (mask < 0 || mask > 32)
+ return (NULL);
+ c->plen4 = mask;
+ inet_pton(AF_INET, buf, &c->net4.sin_addr);
+
+ c->net4.sin_len = sizeof(struct sockaddr_in);
+ c->net4.sin_family = AF_INET;
+ c->addr4.sin_len = sizeof(struct sockaddr_in);
+ c->addr4.sin_family = AF_INET;
+
+ sa_fill_mask4(&c->mask4, c->plen4);
+
+ /* Fill in interface IPv4 address. Assume the first address in net */
+ c->addr4.sin_addr.s_addr = htonl(ntohl(c->net4.sin_addr.s_addr) + 1);
+ inet_ntop(AF_INET, &c->net4.sin_addr, c->net4_str, INET_ADDRSTRLEN);
+ inet_ntop(AF_INET, &c->addr4.sin_addr, c->addr4_str, INET_ADDRSTRLEN);
+
+ key = atf_tc_get_config_var_wd(tc, "rtsock.v6prefix", "2001:DB8::/32");
+ strlcpy(buf, key, sizeof(buf));
+ if ((s = strchr(buf, '/')) == NULL)
+ return (NULL);
+ *s++ = '\0';
+ mask = strtol(s, NULL, 10);
+ if (mask < 0 || mask > 128)
+ return (NULL);
+ c->plen6 = mask;
+
+ inet_pton(AF_INET6, buf, &c->net6.sin6_addr);
+
+ c->net6.sin6_len = sizeof(struct sockaddr_in6);
+ c->net6.sin6_family = AF_INET6;
+ c->addr6.sin6_len = sizeof(struct sockaddr_in6);
+ c->addr6.sin6_family = AF_INET6;
+
+ sa_fill_mask6(&c->mask6, c->plen6);
+
+ /* Fill in interface IPv6 address. Assume the first address in net */
+ memcpy(&c->addr6.sin6_addr, &c->net6.sin6_addr, sizeof(struct in6_addr));
+#define _s6_addr32 __u6_addr.__u6_addr32
+ c->addr6.sin6_addr._s6_addr32[3] = htonl(ntohl(c->net6.sin6_addr._s6_addr32[3]) + 1);
+#undef _s6_addr32
+ inet_ntop(AF_INET6, &c->net6.sin6_addr, c->net6_str, INET6_ADDRSTRLEN);
+ inet_ntop(AF_INET6, &c->addr6.sin6_addr, c->addr6_str, INET6_ADDRSTRLEN);
+
+ ATF_CHECK_ERRNO(0, true);
+
+ if (co->num_interfaces > 0) {
+ /* Try loading if_epair and if that fails skip the test. */
+ kldload("if_epair");
+ ATF_REQUIRE_KERNEL_MODULE("if_epair");
+ /* Clear errno for the following tests. */
+ errno = 0;
+
+ c->ifnames = calloc(co->num_interfaces, sizeof(char *));
+ for (int i = 0; i < co->num_interfaces; i++)
+ c->ifnames[i] = iface_create("epair");
+
+ c->ifname = c->ifnames[0];
+ c->ifindex = if_nametoindex(c->ifname);
+ ATF_REQUIRE_MSG(c->ifindex != 0, "interface %s not found",
+ c->ifname);
+ }
+ c->num_interfaces = co->num_interfaces;
+
+ c->remote_lladdr = strdup(atf_tc_get_config_var_wd(tc,
+ "rtsock.remote_lladdr", "00:00:5E:00:53:42"));
+
+ return (c);
+}
+
+void
+config_generic_cleanup(const atf_tc_t *tc)
+{
+ const char *srcdir = atf_tc_get_config_var(tc, "srcdir");
+ char cmd[512];
+ int ret;
+
+ snprintf(cmd, sizeof(cmd), "%s/generic_cleanup.sh", srcdir);
+ ret = system(cmd);
+ if (ret != 0)
+ RLOG("'%s' failed, error %d", cmd, ret);
+}
+
+void
+config_describe_root_test(atf_tc_t *tc, char *test_descr)
+{
+
+ atf_tc_set_md_var(tc, "descr", test_descr);
+ // Adding/deleting prefix requires root privileges
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+
+#endif
diff --git a/tests/sys/net/routing/rtsock_print.h b/tests/sys/net/routing/rtsock_print.h
new file mode 100644
index 000000000000..61d70dc55670
--- /dev/null
+++ b/tests/sys/net/routing/rtsock_print.h
@@ -0,0 +1,412 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Alexander V. Chernikov
+ *
+ * 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.
+ */
+
+#ifndef _NET_ROUTING_RTSOCK_PRINT_H_
+#define _NET_ROUTING_RTSOCK_PRINT_H_
+
+
+#define RLOG(_fmt, ...) printf("%s: " _fmt "\n", __func__, ##__VA_ARGS__)
+#define RLOG_ERRNO(_fmt, ...) do { \
+ printf("%s: " _fmt, __func__, ##__VA_ARGS__); \
+ printf(": %s\n", strerror(errno)); \
+} while(0)
+
+#define RTSOCK_ATF_REQUIRE_MSG(_rtm, _cond, _fmt, ...) do { \
+ if (!(_cond)) { \
+ printf("-- CONDITION FAILED, rtm dump --\n\n");\
+ rtsock_print_message(_rtm); \
+ rtsock_print_table(AF_INET); \
+ rtsock_print_table(AF_INET6); \
+ printf("===================================\n");\
+ } \
+ ATF_REQUIRE_MSG(_cond, _fmt, ##__VA_ARGS__); \
+} while (0);
+
+#define RTSOCKHD_ATF_REQUIRE_MSG(_rtm, _cond, _fmt, ...) do { \
+ if (!(_cond)) { \
+ printf("-- CONDITION FAILED, rtm hexdump--\n\n");\
+ rtsock_print_message_hd(_rtm); \
+ } \
+ ATF_REQUIRE_MSG(_cond, _fmt, ##__VA_ARGS__); \
+} while (0);
+
+
+/* from route.c */
+static const char *const msgtypes[] = {
+ "",
+ "RTM_ADD",
+ "RTM_DELETE",
+ "RTM_CHANGE",
+ "RTM_GET",
+ "RTM_LOSING",
+ "RTM_REDIRECT",
+ "RTM_MISS",
+ "RTM_LOCK",
+ "RTM_OLDADD",
+ "RTM_OLDDEL",
+ "RTM_RESOLVE",
+ "RTM_NEWADDR",
+ "RTM_DELADDR",
+ "RTM_IFINFO",
+ "RTM_NEWMADDR",
+ "RTM_DELMADDR",
+ "RTM_IFANNOUNCE",
+ "RTM_IEEE80211",
+};
+
+static const char metricnames[] =
+ "\011weight\010rttvar\7rtt\6ssthresh\5sendpipe\4recvpipe\3expire"
+ "\1mtu";
+static const char routeflags[] =
+ "\1UP\2GATEWAY\3HOST\4REJECT\5DYNAMIC\6MODIFIED\7DONE"
+ "\012XRESOLVE\013LLINFO\014STATIC\015BLACKHOLE"
+ "\017PROTO2\020PROTO1\021PRCLONING\022WASCLONED\023PROTO3"
+ "\024FIXEDMTU\025PINNED\026LOCAL\027BROADCAST\030MULTICAST\035STICKY";
+static const char ifnetflags[] =
+ "\1UP\2BROADCAST\3DEBUG\4LOOPBACK\5PTP\6b6\7RUNNING\010NOARP"
+ "\011PPROMISC\012ALLMULTI\013OACTIVE\014SIMPLEX\015LINK0\016LINK1"
+ "\017LINK2\020MULTICAST";
+static const char addrnames[] =
+ "\1DST\2GATEWAY\3NETMASK\4GENMASK\5IFP\6IFA\7AUTHOR\010BRD";
+
+static int
+_printb(char *buf, size_t bufsize, int b, const char *str)
+{
+ int i;
+ int gotsome = 0;
+
+ char *pbuf = buf;
+
+ if (b == 0) {
+ *pbuf = '\0';
+ return (0);
+ }
+ while ((i = *str++) != 0) {
+ if (b & (1 << (i-1))) {
+ if (gotsome == 0)
+ i = '<';
+ else
+ i = ',';
+ *pbuf++ = i;
+ gotsome = 1;
+ for (; (i = *str) > 32; str++)
+ *pbuf++ = i;
+ } else
+ while (*str > 32)
+ str++;
+ }
+ if (gotsome)
+ *pbuf++ = '>';
+ *pbuf = '\0';
+
+ return (int)(pbuf - buf);
+}
+
+const char *
+rtsock_print_cmdtype(int cmd)
+{
+
+ return (msgtypes[cmd]);
+}
+
+char *
+rtsock_print_rtm_flags(char *buf, int buflen, int rtm_flags)
+{
+
+ _printb(buf, buflen, rtm_flags, routeflags);
+ return (buf);
+}
+
+
+#define _PRINTX(fmt, ...) do { \
+ one_len = snprintf(ptr, rem_len, fmt, __VA_ARGS__); \
+ ptr += one_len; \
+ rem_len -= one_len; \
+} while(0)
+
+
+void
+sa_print_hd(char *buf, int buflen, const char *data, int len)
+{
+ char *ptr;
+ int one_len, rem_len;
+
+ ptr = buf;
+ rem_len = buflen;
+
+ const char *last_char = NULL;
+ unsigned char v;
+ int repeat_count = 0;
+ for (int i = 0; i < len; i++) {
+ if (last_char && *last_char == data[i] && data[i] == 0x00) {
+ repeat_count++;
+ continue;
+ }
+
+ if (repeat_count > 1) {
+ _PRINTX("{%d}", repeat_count);
+ repeat_count = 0;
+ }
+
+ v = ((const unsigned char *)data)[i];
+ if (last_char == NULL)
+ _PRINTX("x%02X", v);
+ else
+ _PRINTX(", x%02X", v);
+
+ last_char = &data[i];
+ repeat_count = 1;
+ }
+
+ if (repeat_count > 1)
+ snprintf(ptr, rem_len, "{%d}", repeat_count);
+}
+
+#undef _PRINTX
+
+void
+sa_print(const struct sockaddr *sa, int include_hexdump)
+{
+ char hdbuf[512], abuf[64];
+ char ifbuf[128];
+ const struct sockaddr_dl *sdl;
+ const struct sockaddr_in6 *sin6;
+ const struct sockaddr_in *sin;
+ int i;
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ sin = (struct sockaddr_in *)sa;
+ inet_ntop(AF_INET, &sin->sin_addr, abuf, sizeof(abuf));
+ printf(" af=inet len=%d addr=%s", sa->sa_len, abuf);
+ break;
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)sa;
+ inet_ntop(AF_INET6, &sin6->sin6_addr, abuf, sizeof(abuf));
+ int scope_id = sin6->sin6_scope_id;
+ printf(" af=inet6 len=%d addr=%s", sa->sa_len, abuf);
+ if (scope_id != 0) {
+ memset(ifbuf, 0, sizeof(ifbuf));
+ if_indextoname(scope_id, ifbuf);
+ printf(" scope_id=%d if_name=%s", scope_id, ifbuf);
+ }
+ break;
+ case AF_LINK:
+ sdl = (const struct sockaddr_dl *)sa;
+ int sdl_index = sdl->sdl_index;
+ if (sdl_index != 0) {
+ memset(ifbuf, 0, sizeof(ifbuf));
+ if_indextoname(sdl_index, ifbuf);
+ printf(" af=link len=%d sdl_index=%d if_name=%s", sdl->sdl_len, sdl_index, ifbuf);
+ }
+ if (sdl->sdl_nlen) {
+ char _ifname[IFNAMSIZ];
+ memcpy(_ifname, sdl->sdl_data, sdl->sdl_nlen);
+ _ifname[sdl->sdl_nlen] = '\0';
+ printf(" name=%s", _ifname);
+ }
+ if (sdl->sdl_alen) {
+ printf(" addr=");
+ const char *lladdr = LLADDR(sdl);
+ for (int i = 0; i < sdl->sdl_alen; i++) {
+ if (i + 1 < sdl->sdl_alen)
+ printf("%02X:", ((const unsigned char *)lladdr)[i]);
+ else
+ printf("%02X", ((const unsigned char *)lladdr)[i]);
+ }
+ }
+ break;
+ default:
+ printf(" af=%d len=%d", sa->sa_family, sa->sa_len);
+ }
+
+ if (include_hexdump) {
+ sa_print_hd(hdbuf, sizeof(hdbuf), ((char *)sa), sa->sa_len);
+ printf(" hd={%s}", hdbuf);
+ }
+ printf("\n");
+}
+
+/*
+got message of size 240 on Mon Dec 16 09:23:31 2019
+RTM_ADD: Add Route: len 240, pid: 25534, seq 2, errno 0, flags:<HOST,DONE,LLINFO,STATIC>
+locks: inits:
+sockaddrs: <DST,GATEWAY>
+*/
+
+void
+rtsock_print_rtm(struct rt_msghdr *rtm)
+{
+ struct timeval tv;
+ struct tm tm_res;
+ char buf[64];
+
+ gettimeofday(&tv, NULL);
+ localtime_r(&tv.tv_sec, &tm_res);
+ strftime(buf, sizeof(buf), "%F %T", &tm_res);
+ printf("Got message of size %hu on %s\n", rtm->rtm_msglen, buf);
+
+ char flags_buf[256];
+ rtsock_print_rtm_flags(flags_buf, sizeof(flags_buf), rtm->rtm_flags);
+
+ printf("%s: len %hu, pid: %d, seq %d, errno %d, flags: %s\n", msgtypes[rtm->rtm_type],
+ rtm->rtm_msglen, rtm->rtm_pid, rtm->rtm_seq, rtm->rtm_errno, flags_buf);
+
+ if (rtm->rtm_inits > 0) {
+ _printb(flags_buf, sizeof(flags_buf), rtm->rtm_inits, metricnames);
+ printf("metrics: %s\n", flags_buf);
+ if (rtm->rtm_inits & RTV_MTU)
+ printf("mtu: %lu\n", rtm->rtm_rmx.rmx_mtu);
+ if (rtm->rtm_inits & RTV_EXPIRE) {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ printf("expire: %d (%lu raw)\n",
+ (int)(rtm->rtm_rmx.rmx_expire - tv.tv_sec), rtm->rtm_rmx.rmx_expire);
+ }
+ }
+
+ _printb(flags_buf, sizeof(flags_buf), rtm->rtm_addrs, addrnames);
+ printf("sockaddrs: 0x%X %s\n", rtm->rtm_addrs, flags_buf);
+
+ char *ptr = (char *)(rtm + 1);
+ for (int i = 0; i < RTAX_MAX; i++) {
+ if (rtm->rtm_addrs & (1 << i)) {
+ struct sockaddr *sa = (struct sockaddr *)ptr;
+ sa_print(sa, 1);
+
+ /* add */
+ ptr += ALIGN(((struct sockaddr *)ptr)->sa_len);
+ }
+ }
+
+ printf("\n");
+
+}
+
+void
+rtsock_print_ifa(struct ifa_msghdr *ifam)
+{
+ struct timeval tv;
+ struct tm tm_res;
+ char buf[64];
+
+ gettimeofday(&tv, NULL);
+ localtime_r(&tv.tv_sec, &tm_res);
+ strftime(buf, sizeof(buf), "%F %T", &tm_res);
+ printf("Got message of size %hu on %s\n", ifam->ifam_msglen, buf);
+
+ char flags_buf[256];
+ _printb(flags_buf, sizeof(flags_buf), ifam->ifam_flags, routeflags);
+
+ printf("%s: len %hu, ifindex: %d, flags: %s\n", msgtypes[ifam->ifam_type],
+ ifam->ifam_msglen, ifam->ifam_index, flags_buf);
+
+ _printb(flags_buf, sizeof(flags_buf), ifam->ifam_addrs, addrnames);
+ printf("sockaddrs: 0x%X %s\n", ifam->ifam_addrs, flags_buf);
+
+ char *ptr = (char *)(ifam + 1);
+ for (int i = 0; i < RTAX_MAX; i++) {
+ if (ifam->ifam_addrs & (1 << i)) {
+ struct sockaddr *sa = (struct sockaddr *)ptr;
+ sa_print(sa, 1);
+
+ /* add */
+ ptr += ALIGN(((struct sockaddr *)ptr)->sa_len);
+ }
+ }
+
+ printf("\n");
+
+}
+
+void
+rtsock_print_message_hd(struct rt_msghdr *rtm)
+{
+ struct timeval tv;
+ struct tm tm_res;
+ char buf[64];
+ char dumpbuf[2048];
+
+ gettimeofday(&tv, NULL);
+ localtime_r(&tv.tv_sec, &tm_res);
+ strftime(buf, sizeof(buf), "%F %T", &tm_res);
+ printf("Got message type %s of size %hu on %s\n",
+ rtsock_print_cmdtype(rtm->rtm_type),
+ rtm->rtm_msglen, buf);
+
+ sa_print_hd(dumpbuf, sizeof(dumpbuf), (char *)rtm, rtm->rtm_msglen);
+ printf(" %s\n", dumpbuf);
+}
+
+void
+rtsock_print_message(struct rt_msghdr *rtm)
+{
+
+ switch (rtm->rtm_type) {
+ case RTM_GET:
+ case RTM_ADD:
+ case RTM_DELETE:
+ case RTM_CHANGE:
+ rtsock_print_rtm(rtm);
+ break;
+ case RTM_DELADDR:
+ case RTM_NEWADDR:
+ rtsock_print_ifa((struct ifa_msghdr *)rtm);
+ break;
+ default:
+ printf("unknown rt message type %X\n", rtm->rtm_type);
+ }
+}
+
+static void
+print_command(char *cmd)
+{
+ char line[1024];
+
+ FILE *fp = popen(cmd, "r");
+ if (fp != NULL) {
+ while (fgets(line, sizeof(line), fp) != NULL)
+ printf("%s", line);
+ pclose(fp);
+ }
+}
+
+void
+rtsock_print_table(int family)
+{
+ char cmdbuf[128];
+ char *key = (family == AF_INET) ? "4" : "6";
+
+ snprintf(cmdbuf, sizeof(cmdbuf), "/usr/bin/netstat -%srnW", key);
+ printf("==== %s ===\n", cmdbuf);
+ print_command(cmdbuf);
+ snprintf(cmdbuf, sizeof(cmdbuf), "/usr/bin/netstat -%sonW", key);
+ printf("==== %s ===\n", cmdbuf);
+ print_command(cmdbuf);
+}
+
+#endif
diff --git a/tests/sys/net/routing/test_routing_l3.py b/tests/sys/net/routing/test_routing_l3.py
new file mode 100755
index 000000000000..3a3822293424
--- /dev/null
+++ b/tests/sys/net/routing/test_routing_l3.py
@@ -0,0 +1,120 @@
+import ipaddress
+import socket
+
+import pytest
+from atf_python.sys.net.rtsock import RtConst
+from atf_python.sys.net.rtsock import Rtsock
+from atf_python.sys.net.rtsock import RtsockRtMessage
+from atf_python.sys.net.rtsock import SaHelper
+from atf_python.sys.net.tools import ToolsHelper
+from atf_python.sys.net.vnet import SingleVnetTestTemplate
+from atf_python.sys.net.vnet import VnetTestTemplate
+
+
+class TestIfOps(VnetTestTemplate):
+ TOPOLOGY = {
+ "vnet1": {"ifaces": ["if1", "if2"]},
+ "if1": {"prefixes4": [], "prefixes6": []},
+ "if2": {"prefixes4": [], "prefixes6": []},
+ }
+
+ @pytest.mark.parametrize("family", ["inet", "inet6"])
+ @pytest.mark.require_user("root")
+ def test_change_prefix_route(self, family):
+ """Tests that prefix route changes to the new one upon addr deletion"""
+ vnet = self.vnet_map["vnet1"]
+ first_iface = vnet.iface_alias_map["if1"]
+ second_iface = vnet.iface_alias_map["if2"]
+ if family == "inet":
+ first_addr = ipaddress.ip_interface("192.0.2.1/24")
+ second_addr = ipaddress.ip_interface("192.0.2.2/24")
+ else:
+ first_addr = ipaddress.ip_interface("2001:db8::1/64")
+ second_addr = ipaddress.ip_interface("2001:db8::2/64")
+
+ first_iface.setup_addr(str(first_addr))
+ second_iface.setup_addr(str(second_addr))
+
+ # At this time prefix should be pointing to the first interface
+ routes = ToolsHelper.get_routes(family)
+ px = [r for r in routes if r["destination"] == str(first_addr.network)][0]
+ assert px["interface-name"] == first_iface.name
+
+ # Now delete address from the first interface and verify switchover
+ first_iface.delete_addr(first_addr.ip)
+
+ routes = ToolsHelper.get_routes(family)
+ px = [r for r in routes if r["destination"] == str(first_addr.network)][0]
+ assert px["interface-name"] == second_iface.name
+
+ @pytest.mark.parametrize(
+ "family",
+ [
+ "inet",
+ pytest.param("inet6", marks=pytest.mark.xfail(reason="currently fails")),
+ ],
+ )
+ @pytest.mark.require_user("root")
+ def test_change_prefix_route_same_iface(self, family):
+ """Tests that prefix route changes to the new ifa upon addr deletion"""
+ vnet = self.vnet_map["vnet1"]
+ first_iface = vnet.iface_alias_map["if1"]
+
+ if family == "inet":
+ first_addr = ipaddress.ip_interface("192.0.2.1/24")
+ second_addr = ipaddress.ip_interface("192.0.2.2/24")
+ else:
+ first_addr = ipaddress.ip_interface("2001:db8::1/64")
+ second_addr = ipaddress.ip_interface("2001:db8::2/64")
+
+ first_iface.setup_addr(str(first_addr))
+ first_iface.setup_addr(str(second_addr))
+
+ # At this time prefix should be pointing to the first interface
+ routes = ToolsHelper.get_routes(family)
+ px = [r for r in routes if r["destination"] == str(first_addr.network)][0]
+ assert px["interface-name"] == first_iface.name
+
+ # Now delete address from the first interface and verify switchover
+ first_iface.delete_addr(str(first_addr.ip))
+
+ routes = ToolsHelper.get_routes(family)
+ px = [r for r in routes if r["destination"] == str(first_addr.network)][0]
+ nhop_kidx = px["nhop"]
+ assert px["interface-name"] == first_iface.name
+ nhops = ToolsHelper.get_nhops(family)
+ nh = [nh for nh in nhops if nh["index"] == nhop_kidx][0]
+ assert nh["ifa"] == str(second_addr.ip)
+
+
+class TestRouteCornerCase1(SingleVnetTestTemplate):
+ @pytest.mark.parametrize("family", ["inet", "inet6"])
+ @pytest.mark.require_user("root")
+ def test_add_direct_route_p2p_wo_ifa(self, family):
+
+ tun_ifname = ToolsHelper.get_output("/sbin/ifconfig tun create").rstrip()
+ tun_ifindex = socket.if_nametoindex(tun_ifname)
+ assert tun_ifindex > 0
+ rtsock = Rtsock()
+
+ if family == "inet":
+ prefix = "172.16.0.0/12"
+ else:
+ prefix = "2a02:6b8::/64"
+ IFT_ETHER = 0x06
+ gw_link = SaHelper.link_sa(ifindex=tun_ifindex, iftype=IFT_ETHER)
+
+ msg = rtsock.new_rtm_add(prefix, gw_link)
+ msg.add_link_attr(RtConst.RTA_IFP, tun_ifindex)
+ rtsock.write_message(msg)
+
+ data = rtsock.read_data(msg.rtm_seq)
+ msg_in = RtsockRtMessage.from_bytes(data)
+ msg_in.print_in_message()
+
+ desired_sa = {
+ RtConst.RTA_DST: msg.get_sa(RtConst.RTA_DST),
+ RtConst.RTA_NETMASK: msg.get_sa(RtConst.RTA_NETMASK),
+ }
+
+ msg_in.verify(msg.rtm_type, desired_sa)
diff --git a/tests/sys/net/routing/test_rtsock_l3.c b/tests/sys/net/routing/test_rtsock_l3.c
new file mode 100644
index 000000000000..cdfc63d604bf
--- /dev/null
+++ b/tests/sys/net/routing/test_rtsock_l3.c
@@ -0,0 +1,1421 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Alexander V. Chernikov
+ *
+ * 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.
+ */
+
+#include "rtsock_common.h"
+#include "rtsock_config.h"
+#include "sys/types.h"
+#include <sys/time.h>
+#include <sys/ioctl.h>
+
+#include "net/bpf.h"
+
+static void
+jump_vnet(struct rtsock_test_config *c, const atf_tc_t *tc)
+{
+ char vnet_name[512];
+
+ snprintf(vnet_name, sizeof(vnet_name), "vt-%s", atf_tc_get_ident(tc));
+ RLOG("jumping to %s", vnet_name);
+
+ vnet_switch(vnet_name, c->ifnames, c->num_interfaces);
+
+ /* Update ifindex cache */
+ c->ifindex = if_nametoindex(c->ifname);
+}
+
+static inline struct rtsock_test_config *
+presetup_ipv6_iface(const atf_tc_t *tc)
+{
+ struct rtsock_test_config *c;
+ int ret;
+
+ c = config_setup(tc, NULL);
+
+ jump_vnet(c, tc);
+
+ ret = iface_turn_up(c->ifname);
+ ATF_REQUIRE_MSG(ret == 0, "Unable to turn up %s", c->ifname);
+
+ ret = iface_enable_ipv6(c->ifname);
+ ATF_REQUIRE_MSG(ret == 0, "Unable to enable IPv6 on %s", c->ifname);
+ ATF_REQUIRE_ERRNO(0, true);
+
+ return (c);
+}
+
+static inline struct rtsock_test_config *
+presetup_ipv6(const atf_tc_t *tc)
+{
+ struct rtsock_test_config *c;
+ int ret;
+
+ c = presetup_ipv6_iface(tc);
+
+ ret = iface_setup_addr(c->ifname, c->addr6_str, c->plen6);
+
+ c->rtsock_fd = rtsock_setup_socket();
+ ATF_REQUIRE_ERRNO(0, true);
+
+ return (c);
+}
+
+static inline struct rtsock_test_config *
+presetup_ipv4_iface(const atf_tc_t *tc)
+{
+ struct rtsock_test_config *c;
+ int ret;
+
+ c = config_setup(tc, NULL);
+ ATF_REQUIRE(c != NULL);
+
+ jump_vnet(c, tc);
+
+ ret = iface_turn_up(c->ifname);
+ ATF_REQUIRE_MSG(ret == 0, "Unable to turn up %s", c->ifname);
+ ATF_REQUIRE_ERRNO(0, true);
+
+ return (c);
+}
+
+static inline struct rtsock_test_config *
+presetup_ipv4(const atf_tc_t *tc)
+{
+ struct rtsock_test_config *c;
+ int ret;
+
+ c = presetup_ipv4_iface(tc);
+
+ /* assumes ifconfig doing IFF_UP */
+ ret = iface_setup_addr(c->ifname, c->addr4_str, c->plen4);
+ ATF_REQUIRE_MSG(ret == 0, "ifconfig failed");
+
+ c->rtsock_fd = rtsock_setup_socket();
+ ATF_REQUIRE_ERRNO(0, true);
+
+ return (c);
+}
+
+
+static void
+prepare_v4_network(struct rtsock_test_config *c, struct sockaddr_in *dst,
+ struct sockaddr_in *mask, struct sockaddr_in *gw)
+{
+ /* Create IPv4 subnetwork with smaller prefix */
+ sa_fill_mask4(mask, c->plen4 + 1);
+ *dst = c->net4;
+ /* Calculate GW as last-net-address - 1 */
+ *gw = c->net4;
+ gw->sin_addr.s_addr = htonl((ntohl(c->net4.sin_addr.s_addr) | ~ntohl(c->mask4.sin_addr.s_addr)) - 1);
+ sa_print((struct sockaddr *)dst, 0);
+ sa_print((struct sockaddr *)mask, 0);
+ sa_print((struct sockaddr *)gw, 0);
+}
+
+static void
+prepare_v6_network(struct rtsock_test_config *c, struct sockaddr_in6 *dst,
+ struct sockaddr_in6 *mask, struct sockaddr_in6 *gw)
+{
+ /* Create IPv6 subnetwork with smaller prefix */
+ sa_fill_mask6(mask, c->plen6 + 1);
+ *dst = c->net6;
+ /* Calculate GW as last-net-address - 1 */
+ *gw = c->net6;
+#define _s6_addr32 __u6_addr.__u6_addr32
+ gw->sin6_addr._s6_addr32[0] = htonl((ntohl(gw->sin6_addr._s6_addr32[0]) | ~ntohl(c->mask6.sin6_addr._s6_addr32[0])));
+ gw->sin6_addr._s6_addr32[1] = htonl((ntohl(gw->sin6_addr._s6_addr32[1]) | ~ntohl(c->mask6.sin6_addr._s6_addr32[1])));
+ gw->sin6_addr._s6_addr32[2] = htonl((ntohl(gw->sin6_addr._s6_addr32[2]) | ~ntohl(c->mask6.sin6_addr._s6_addr32[2])));
+ gw->sin6_addr._s6_addr32[3] = htonl((ntohl(gw->sin6_addr._s6_addr32[3]) | ~ntohl(c->mask6.sin6_addr._s6_addr32[3])) - 1);
+#undef _s6_addr32
+ sa_print((struct sockaddr *)dst, 0);
+ sa_print((struct sockaddr *)mask, 0);
+ sa_print((struct sockaddr *)gw, 0);
+}
+
+static void
+prepare_route_message(struct rt_msghdr *rtm, int cmd, struct sockaddr *dst,
+ struct sockaddr *mask, struct sockaddr *gw)
+{
+
+ rtsock_prepare_route_message(rtm, cmd, dst, mask, gw);
+
+ if (cmd == RTM_ADD || cmd == RTM_CHANGE)
+ rtm->rtm_flags |= RTF_STATIC;
+}
+
+static void
+verify_route_message(struct rt_msghdr *rtm, int cmd, struct sockaddr *dst,
+ struct sockaddr *mask, struct sockaddr *gw)
+{
+ char msg[512];
+ struct sockaddr *sa;
+ int ret;
+
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_type == cmd,
+ "expected %s message, got %d (%s)", rtsock_print_cmdtype(cmd),
+ rtm->rtm_type, rtsock_print_cmdtype(rtm->rtm_type));
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_errno == 0,
+ "got got errno %d as message reply", rtm->rtm_errno);
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->_rtm_spare1 == 0,
+ "expected rtm_spare==0, got %d", rtm->_rtm_spare1);
+
+ /* kernel MAY return more sockaddrs, including RTA_IFP / RTA_IFA, so verify the needed ones */
+ if (dst != NULL) {
+ sa = rtsock_find_rtm_sa(rtm, RTA_DST);
+ RTSOCK_ATF_REQUIRE_MSG(rtm, sa != NULL, "DST is not set");
+ ret = sa_equal_msg(sa, dst, msg, sizeof(msg));
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "DST sa diff: %s", msg);
+ }
+
+ if (mask != NULL) {
+ sa = rtsock_find_rtm_sa(rtm, RTA_NETMASK);
+ RTSOCK_ATF_REQUIRE_MSG(rtm, sa != NULL, "NETMASK is not set");
+ ret = sa_equal_msg(sa, mask, msg, sizeof(msg));
+ ret = 1;
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "NETMASK sa diff: %s", msg);
+ }
+
+ if (gw != NULL) {
+ sa = rtsock_find_rtm_sa(rtm, RTA_GATEWAY);
+ RTSOCK_ATF_REQUIRE_MSG(rtm, sa != NULL, "GATEWAY is not set");
+ ret = sa_equal_msg(sa, gw, msg, sizeof(msg));
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "GATEWAY sa diff: %s", msg);
+ }
+}
+
+static void
+verify_route_message_extra(struct rt_msghdr *rtm, int ifindex, int rtm_flags)
+{
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_index == ifindex,
+ "expected ifindex %d, got %d", ifindex, rtm->rtm_index);
+
+ if (rtm->rtm_flags != rtm_flags) {
+ char got_flags[64], expected_flags[64];
+ rtsock_print_rtm_flags(got_flags, sizeof(got_flags),
+ rtm->rtm_flags);
+ rtsock_print_rtm_flags(expected_flags, sizeof(expected_flags),
+ rtm_flags);
+
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_flags == rtm_flags,
+ "expected flags: 0x%X %s, got 0x%X %s",
+ rtm_flags, expected_flags,
+ rtm->rtm_flags, got_flags);
+ }
+}
+
+static void
+verify_link_gateway(struct rt_msghdr *rtm, int ifindex)
+{
+ struct sockaddr *sa;
+ struct sockaddr_dl *sdl;
+
+ sa = rtsock_find_rtm_sa(rtm, RTA_GATEWAY);
+ RTSOCK_ATF_REQUIRE_MSG(rtm, sa != NULL, "GATEWAY is not set");
+ RTSOCK_ATF_REQUIRE_MSG(rtm, sa->sa_family == AF_LINK, "GW sa family is %d", sa->sa_family);
+ sdl = (struct sockaddr_dl *)sa;
+ RTSOCK_ATF_REQUIRE_MSG(rtm, sdl->sdl_index == ifindex, "GW ifindex is %d", sdl->sdl_index);
+}
+
+/* TESTS */
+
+#define DECLARE_TEST_VARS \
+ char buffer[2048]; \
+ struct rtsock_test_config *c; \
+ struct rt_msghdr *rtm = (struct rt_msghdr *)buffer; \
+ struct sockaddr *sa; \
+ int ret; \
+ \
+
+#define DESCRIBE_ROOT_TEST(_msg) config_describe_root_test(tc, _msg)
+#define CLEANUP_AFTER_TEST config_generic_cleanup(tc)
+
+#define RTM_DECLARE_ROOT_TEST(_name, _descr) \
+ATF_TC_WITH_CLEANUP(_name); \
+ATF_TC_HEAD(_name, tc) \
+{ \
+ DESCRIBE_ROOT_TEST(_descr); \
+} \
+ATF_TC_CLEANUP(_name, tc) \
+{ \
+ CLEANUP_AFTER_TEST; \
+}
+
+ATF_TC_WITH_CLEANUP(rtm_get_v4_exact_success);
+ATF_TC_HEAD(rtm_get_v4_exact_success, tc)
+{
+ DESCRIBE_ROOT_TEST("Tests RTM_GET with exact prefix lookup on an interface prefix");
+}
+
+ATF_TC_BODY(rtm_get_v4_exact_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv4(tc);
+
+ prepare_route_message(rtm, RTM_GET, (struct sockaddr *)&c->net4,
+ (struct sockaddr *)&c->mask4, NULL);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ /*
+ * RTM_GET: Report Metrics: len 240, pid: 45072, seq 42, errno 0, flags: <UP,DONE,PINNED>
+ * sockaddrs: 0x7 <DST,GATEWAY,NETMASK>
+ * af=inet len=16 addr=192.0.2.0 hd={10, 02, 00{2}, C0, 00, 02, 00{9}}
+ * af=link len=54 sdl_index=3 if_name=tap4242 hd={36, 12, 03, 00, 06, 00{49}}
+ * af=inet len=16 addr=255.255.255.0 hd={10, 02, FF{5}, 00{9}}
+ */
+
+ verify_route_message(rtm, RTM_GET, (struct sockaddr *)&c->net4,
+ (struct sockaddr *)&c->mask4, NULL);
+
+ verify_route_message_extra(rtm, c->ifindex, RTF_UP | RTF_DONE | RTF_PINNED);
+
+ /* Explicitly verify gateway for the interface route */
+ verify_link_gateway(rtm, c->ifindex);
+ sa = rtsock_find_rtm_sa(rtm, RTA_GATEWAY);
+ RTSOCK_ATF_REQUIRE_MSG(rtm, sa != NULL, "GATEWAY is not set");
+ RTSOCK_ATF_REQUIRE_MSG(rtm, sa->sa_family == AF_LINK, "GW sa family is %d", sa->sa_family);
+ struct sockaddr_dl *sdl = (struct sockaddr_dl *)sa;
+ RTSOCK_ATF_REQUIRE_MSG(rtm, sdl->sdl_index == c->ifindex, "GW ifindex is %d", sdl->sdl_index);
+}
+
+ATF_TC_CLEANUP(rtm_get_v4_exact_success, tc)
+{
+ CLEANUP_AFTER_TEST;
+}
+
+ATF_TC_WITH_CLEANUP(rtm_get_v4_lpm_success);
+ATF_TC_HEAD(rtm_get_v4_lpm_success, tc)
+{
+ DESCRIBE_ROOT_TEST("Tests RTM_GET with address lookup on an existing prefix");
+}
+
+ATF_TC_BODY(rtm_get_v4_lpm_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv4(tc);
+
+ prepare_route_message(rtm, RTM_GET, (struct sockaddr *)&c->net4, NULL, NULL);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ /*
+ * RTM_GET: Report Metrics: len 312, pid: 67074, seq 1, errno 0, flags:<UP,DONE,PINNED>
+ * locks: inits:
+ * sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA>
+ * 10.0.0.0 link#1 255.255.255.0 vtnet0:52.54.0.42.f.ef 10.0.0.157
+ */
+
+ verify_route_message(rtm, RTM_GET, (struct sockaddr *)&c->net4,
+ (struct sockaddr *)&c->mask4, NULL);
+
+ verify_route_message_extra(rtm, c->ifindex, RTF_UP | RTF_DONE | RTF_PINNED);
+}
+
+ATF_TC_CLEANUP(rtm_get_v4_lpm_success, tc)
+{
+ CLEANUP_AFTER_TEST;
+}
+
+
+ATF_TC_WITH_CLEANUP(rtm_get_v4_empty_dst_failure);
+ATF_TC_HEAD(rtm_get_v4_empty_dst_failure, tc)
+{
+
+ DESCRIBE_ROOT_TEST("Tests RTM_GET with empty DST addr");
+}
+
+ATF_TC_BODY(rtm_get_v4_empty_dst_failure, tc)
+{
+ DECLARE_TEST_VARS;
+ struct rtsock_config_options co;
+
+ bzero(&co, sizeof(co));
+ co.num_interfaces = 0;
+
+ c = config_setup(tc,&co);
+ c->rtsock_fd = rtsock_setup_socket();
+
+ rtsock_prepare_route_message(rtm, RTM_GET, NULL,
+ (struct sockaddr *)&c->mask4, NULL);
+ rtsock_update_rtm_len(rtm);
+
+ ATF_CHECK_ERRNO(EINVAL, write(c->rtsock_fd, rtm, rtm->rtm_msglen) == -1);
+}
+
+ATF_TC_CLEANUP(rtm_get_v4_empty_dst_failure, tc)
+{
+ CLEANUP_AFTER_TEST;
+}
+
+ATF_TC_WITH_CLEANUP(rtm_get_v4_hostbits_success);
+ATF_TC_HEAD(rtm_get_v4_hostbits_success, tc)
+{
+ DESCRIBE_ROOT_TEST("Tests RTM_GET with prefix with some hosts-bits set");
+}
+
+ATF_TC_BODY(rtm_get_v4_hostbits_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv4(tc);
+
+ /* Q the same prefix */
+ rtsock_prepare_route_message(rtm, RTM_GET, (struct sockaddr *)&c->addr4,
+ (struct sockaddr *)&c->mask4, NULL);
+ rtsock_update_rtm_len(rtm);
+
+ ATF_REQUIRE_ERRNO(0, true);
+ ATF_CHECK_ERRNO(0, write(c->rtsock_fd, rtm, rtm->rtm_msglen) > 0);
+}
+
+ATF_TC_CLEANUP(rtm_get_v4_hostbits_success, tc)
+{
+ CLEANUP_AFTER_TEST;
+}
+
+ATF_TC_WITH_CLEANUP(rtm_add_v4_gw_direct_success);
+ATF_TC_HEAD(rtm_add_v4_gw_direct_success, tc)
+{
+ DESCRIBE_ROOT_TEST("Tests IPv4 route addition with directly-reachable GW specified by IP");
+}
+
+ATF_TC_BODY(rtm_add_v4_gw_direct_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv4(tc);
+
+ /* Create IPv4 subnetwork with smaller prefix */
+ struct sockaddr_in mask4;
+ struct sockaddr_in net4;
+ struct sockaddr_in gw4;
+ prepare_v4_network(c, &net4, &mask4, &gw4);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, (struct sockaddr *)&gw4);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ /*
+ * RTM_ADD: Add Route: len 200, pid: 46068, seq 42, errno 0, flags:<GATEWAY,DONE,STATIC>
+ * locks: inits:
+ * sockaddrs: <DST,GATEWAY,NETMASK>
+ * 192.0.2.0 192.0.2.254 255.255.255.128
+ */
+
+ verify_route_message(rtm, RTM_ADD, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, (struct sockaddr *)&gw4);
+ verify_route_message_extra(rtm, c->ifindex,
+ RTF_UP | RTF_DONE | RTF_GATEWAY | RTF_STATIC);
+}
+
+ATF_TC_CLEANUP(rtm_add_v4_gw_direct_success, tc)
+{
+ CLEANUP_AFTER_TEST;
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_add_v4_no_rtf_host_success,
+ "Tests success with netmask sa and RTF_HOST inconsistency");
+
+ATF_TC_BODY(rtm_add_v4_no_rtf_host_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv4(tc);
+
+ /* Create IPv4 subnetwork with smaller prefix */
+ struct sockaddr_in mask4;
+ struct sockaddr_in net4;
+ struct sockaddr_in gw4;
+ prepare_v4_network(c, &net4, &mask4, &gw4);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&net4,
+ NULL, (struct sockaddr *)&gw4);
+ rtsock_update_rtm_len(rtm);
+
+ /* RTF_HOST is NOT specified, while netmask is empty */
+ ATF_REQUIRE_ERRNO(0, true);
+ ATF_CHECK_ERRNO(0, write(c->rtsock_fd, rtm, rtm->rtm_msglen) > 0);
+}
+
+ATF_TC_WITH_CLEANUP(rtm_del_v4_prefix_nogw_success);
+ATF_TC_HEAD(rtm_del_v4_prefix_nogw_success, tc)
+{
+ DESCRIBE_ROOT_TEST("Tests IPv4 route removal without specifying gateway");
+}
+
+ATF_TC_BODY(rtm_del_v4_prefix_nogw_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv4(tc);
+
+ /* Create IPv4 subnetwork with smaller prefix */
+ struct sockaddr_in mask4;
+ struct sockaddr_in net4;
+ struct sockaddr_in gw4;
+ prepare_v4_network(c, &net4, &mask4, &gw4);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, (struct sockaddr *)&gw4);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ /* Route has been added successfully, try to delete it */
+ prepare_route_message(rtm, RTM_DELETE, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, NULL);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ /*
+ * RTM_DELETE: Delete Route: len 200, pid: 46417, seq 43, errno 0, flags: <GATEWAY,DONE,STATIC>
+ * sockaddrs: 0x7 <DST,GATEWAY,NETMASK>
+ * af=inet len=16 addr=192.0.2.0 hd={10, 02, 00{2}, C0, 00, 02, 00{9}}
+ * af=inet len=16 addr=192.0.2.254 hd={10, 02, 00{2}, C0, 00, 02, FE, 00{8}}
+ * af=inet len=16 addr=255.255.255.128 hd={10, 02, FF{5}, 80, 00{8}}
+ */
+ verify_route_message(rtm, RTM_DELETE, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, (struct sockaddr *)&gw4);
+
+ verify_route_message_extra(rtm, c->ifindex, RTF_DONE | RTF_GATEWAY | RTF_STATIC);
+}
+
+ATF_TC_CLEANUP(rtm_del_v4_prefix_nogw_success, tc)
+{
+ CLEANUP_AFTER_TEST;
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_change_v4_gw_success,
+ "Tests IPv4 gateway change");
+
+ATF_TC_BODY(rtm_change_v4_gw_success, tc)
+{
+ DECLARE_TEST_VARS;
+ struct rtsock_config_options co;
+
+ bzero(&co, sizeof(co));
+ co.num_interfaces = 2;
+
+ c = config_setup(tc, &co);
+ jump_vnet(c, tc);
+
+ ret = iface_turn_up(c->ifnames[0]);
+ ATF_REQUIRE_MSG(ret == 0, "Unable to turn up %s", c->ifnames[0]);
+ ret = iface_turn_up(c->ifnames[1]);
+ ATF_REQUIRE_MSG(ret == 0, "Unable to turn up %s", c->ifnames[1]);
+
+ ret = iface_setup_addr(c->ifnames[0], c->addr4_str, c->plen4);
+ ATF_REQUIRE_MSG(ret == 0, "ifconfig failed");
+
+ /* Use 198.51.100.0/24 "TEST-NET-2" for the second interface */
+ ret = iface_setup_addr(c->ifnames[1], "198.51.100.1", 24);
+ ATF_REQUIRE_MSG(ret == 0, "ifconfig failed");
+
+ c->rtsock_fd = rtsock_setup_socket();
+
+ /* Create IPv4 subnetwork with smaller prefix */
+ struct sockaddr_in mask4;
+ struct sockaddr_in net4;
+ struct sockaddr_in gw4;
+ prepare_v4_network(c, &net4, &mask4, &gw4);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, (struct sockaddr *)&gw4);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ verify_route_message(rtm, RTM_ADD, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, (struct sockaddr *)&gw4);
+
+ /* Change gateway to the one on desiding on the other interface */
+ inet_pton(AF_INET, "198.51.100.2", &gw4.sin_addr.s_addr);
+ prepare_route_message(rtm, RTM_CHANGE, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, (struct sockaddr *)&gw4);
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ verify_route_message(rtm, RTM_CHANGE, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, (struct sockaddr *)&gw4);
+
+ verify_route_message_extra(rtm, if_nametoindex(c->ifnames[1]),
+ RTF_UP | RTF_DONE | RTF_GATEWAY | RTF_STATIC);
+
+ /* Verify the change has actually taken place */
+ prepare_route_message(rtm, RTM_GET, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, NULL);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ /*
+ * RTM_GET: len 200, pid: 3894, seq 44, errno 0, flags: <UP,GATEWAY,DONE,STATIC>
+ * sockaddrs: 0x7 <DST,GATEWAY,NETMASK>
+ * af=inet len=16 addr=192.0.2.0 hd={x10, x02, x00{2}, xC0, x00, x02, x00{9}}
+ * af=inet len=16 addr=198.51.100.2 hd={x10, x02, x00{2}, xC6, x33, x64, x02, x00{8}}
+ * af=inet len=16 addr=255.255.255.128 hd={x10, x02, xFF, xFF, xFF, xFF, xFF, x80, x00{8}}
+ */
+
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+ verify_route_message_extra(rtm, if_nametoindex(c->ifnames[1]),
+ RTF_UP | RTF_DONE | RTF_GATEWAY | RTF_STATIC);
+
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_change_v4_mtu_success,
+ "Tests IPv4 path mtu change");
+
+ATF_TC_BODY(rtm_change_v4_mtu_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ unsigned long test_mtu = 1442;
+
+ c = presetup_ipv4(tc);
+
+ /* Create IPv4 subnetwork with smaller prefix */
+ struct sockaddr_in mask4;
+ struct sockaddr_in net4;
+ struct sockaddr_in gw4;
+ prepare_v4_network(c, &net4, &mask4, &gw4);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, (struct sockaddr *)&gw4);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ /* Change MTU */
+ prepare_route_message(rtm, RTM_CHANGE, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, NULL);
+ rtm->rtm_inits |= RTV_MTU;
+ rtm->rtm_rmx.rmx_mtu = test_mtu;
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ verify_route_message(rtm, RTM_CHANGE, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, NULL);
+
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_rmx.rmx_mtu == test_mtu,
+ "expected mtu: %lu, got %lu", test_mtu, rtm->rtm_rmx.rmx_mtu);
+
+ /* Verify the change has actually taken place */
+ prepare_route_message(rtm, RTM_GET, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, NULL);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_rmx.rmx_mtu == test_mtu,
+ "expected mtu: %lu, got %lu", test_mtu, rtm->rtm_rmx.rmx_mtu);
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_change_v4_flags_success,
+ "Tests IPv4 path flags change");
+
+ATF_TC_BODY(rtm_change_v4_flags_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ uint32_t test_flags = RTF_PROTO1 | RTF_PROTO2 | RTF_PROTO3 | RTF_STATIC;
+ uint32_t desired_flags;
+
+ c = presetup_ipv4(tc);
+
+ /* Create IPv4 subnetwork with smaller prefix */
+ struct sockaddr_in mask4;
+ struct sockaddr_in net4;
+ struct sockaddr_in gw4;
+ prepare_v4_network(c, &net4, &mask4, &gw4);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, (struct sockaddr *)&gw4);
+
+ /* Set test flags during route addition */
+ desired_flags = RTF_UP | RTF_DONE | RTF_GATEWAY | test_flags;
+ rtm->rtm_flags |= test_flags;
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ /* Change flags */
+ prepare_route_message(rtm, RTM_CHANGE, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, NULL);
+ rtm->rtm_flags &= ~test_flags;
+ desired_flags &= ~test_flags;
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ /* Verify updated flags */
+ verify_route_message_extra(rtm, c->ifindex, desired_flags | RTF_DONE);
+
+ /* Verify the change has actually taken place */
+ prepare_route_message(rtm, RTM_GET, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, NULL);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ verify_route_message_extra(rtm, c->ifindex, desired_flags | RTF_DONE);
+}
+
+
+ATF_TC_WITH_CLEANUP(rtm_add_v6_gu_gw_gu_direct_success);
+ATF_TC_HEAD(rtm_add_v6_gu_gw_gu_direct_success, tc)
+{
+ DESCRIBE_ROOT_TEST("Tests IPv6 global unicast prefix addition with directly-reachable GU GW");
+}
+
+ATF_TC_BODY(rtm_add_v6_gu_gw_gu_direct_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv6(tc);
+
+ /* Create IPv6 subnetwork with smaller prefix */
+ struct sockaddr_in6 mask6;
+ struct sockaddr_in6 net6;
+ struct sockaddr_in6 gw6;
+ prepare_v6_network(c, &net6, &mask6, &gw6);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, (struct sockaddr *)&gw6);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ /*
+ * RTM_ADD: Add Route: len 200, pid: 46068, seq 42, errno 0, flags:<GATEWAY,DONE,STATIC>
+ * locks: inits:
+ * sockaddrs: <DST,GATEWAY,NETMASK>
+ * 192.0.2.0 192.0.2.254 255.255.255.128
+ */
+
+ verify_route_message(rtm, RTM_ADD, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, (struct sockaddr *)&gw6);
+
+ verify_route_message_extra(rtm, c->ifindex,
+ RTF_UP | RTF_DONE | RTF_GATEWAY | RTF_STATIC);
+}
+
+ATF_TC_CLEANUP(rtm_add_v6_gu_gw_gu_direct_success, tc)
+{
+ CLEANUP_AFTER_TEST;
+}
+
+ATF_TC_WITH_CLEANUP(rtm_del_v6_gu_prefix_nogw_success);
+ATF_TC_HEAD(rtm_del_v6_gu_prefix_nogw_success, tc)
+{
+
+ DESCRIBE_ROOT_TEST("Tests IPv6 global unicast prefix removal without specifying gateway");
+}
+
+ATF_TC_BODY(rtm_del_v6_gu_prefix_nogw_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv6(tc);
+
+ /* Create IPv6 subnetwork with smaller prefix */
+ struct sockaddr_in6 mask6;
+ struct sockaddr_in6 net6;
+ struct sockaddr_in6 gw6;
+ prepare_v6_network(c, &net6, &mask6, &gw6);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, (struct sockaddr *)&gw6);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ /* Route has been added successfully, try to delete it */
+ prepare_route_message(rtm, RTM_DELETE, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, NULL);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ /*
+ * RTM_DELETE: Delete Route: len 200, pid: 46417, seq 43, errno 0, flags: <GATEWAY,DONE,STATIC>
+ * sockaddrs: 0x7 <DST,GATEWAY,NETMASK>
+ * af=inet len=16 addr=192.0.2.0 hd={10, 02, 00{2}, C0, 00, 02, 00{9}}
+ * af=inet len=16 addr=192.0.2.254 hd={10, 02, 00{2}, C0, 00, 02, FE, 00{8}}
+ * af=inet len=16 addr=255.255.255.128 hd={10, 02, FF{5}, 80, 00{8}}
+ */
+
+ verify_route_message(rtm, RTM_DELETE, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, (struct sockaddr *)&gw6);
+ verify_route_message_extra(rtm, c->ifindex, RTF_DONE | RTF_GATEWAY | RTF_STATIC);
+}
+
+ATF_TC_CLEANUP(rtm_del_v6_gu_prefix_nogw_success, tc)
+{
+ CLEANUP_AFTER_TEST;
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_change_v6_gw_success,
+ "Tests IPv6 gateway change");
+
+ATF_TC_BODY(rtm_change_v6_gw_success, tc)
+{
+ DECLARE_TEST_VARS;
+ struct rtsock_config_options co;
+
+ bzero(&co, sizeof(co));
+ co.num_interfaces = 2;
+
+ c = config_setup(tc, &co);
+ jump_vnet(c, tc);
+
+ ret = iface_turn_up(c->ifnames[0]);
+ ATF_REQUIRE_MSG(ret == 0, "Unable to turn up %s", c->ifnames[0]);
+ ret = iface_turn_up(c->ifnames[1]);
+ ATF_REQUIRE_MSG(ret == 0, "Unable to turn up %s", c->ifnames[1]);
+
+ ret = iface_enable_ipv6(c->ifnames[0]);
+ ATF_REQUIRE_MSG(ret == 0, "Unable to enable IPv6 on %s", c->ifnames[0]);
+ ret = iface_enable_ipv6(c->ifnames[1]);
+ ATF_REQUIRE_MSG(ret == 0, "Unable to enable IPv6 on %s", c->ifnames[1]);
+
+ ret = iface_setup_addr(c->ifnames[0], c->addr6_str, c->plen6);
+ ATF_REQUIRE_MSG(ret == 0, "ifconfig failed");
+
+ ret = iface_setup_addr(c->ifnames[1], "2001:DB8:4242::1", 64);
+ ATF_REQUIRE_MSG(ret == 0, "ifconfig failed");
+
+ c->rtsock_fd = rtsock_setup_socket();
+
+ /* Create IPv6 subnetwork with smaller prefix */
+ struct sockaddr_in6 mask6;
+ struct sockaddr_in6 net6;
+ struct sockaddr_in6 gw6;
+ prepare_v6_network(c, &net6, &mask6, &gw6);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, (struct sockaddr *)&gw6);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ verify_route_message(rtm, RTM_ADD, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, (struct sockaddr *)&gw6);
+
+ /* Change gateway to the one on residing on the other interface */
+ inet_pton(AF_INET6, "2001:DB8:4242::4242", &gw6.sin6_addr);
+ prepare_route_message(rtm, RTM_CHANGE, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, (struct sockaddr *)&gw6);
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ verify_route_message(rtm, RTM_CHANGE, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, (struct sockaddr *)&gw6);
+
+ verify_route_message_extra(rtm, if_nametoindex(c->ifnames[1]),
+ RTF_UP | RTF_DONE | RTF_GATEWAY | RTF_STATIC);
+
+ /* Verify the change has actually taken place */
+ prepare_route_message(rtm, RTM_GET, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, NULL);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ /*
+ * RTM_GET: len 248, pid: 2268, seq 44, errno 0, flags: <UP,GATEWAY,DONE,STATIC>
+ * sockaddrs: 0x7 <DST,GATEWAY,NETMASK>
+ * af=inet6 len=28 addr=2001:db8:: hd={x1C, x1C, x00{6}, x20, x01, x0D, xB8, x00{16}}
+ * af=inet6 len=28 addr=2001:db8:4242::4242 hd={x1C, x1C, x00{6}, x20, x01, x0D, xB8, x42, x42, x00{8}, x42, x42, x00{4}}
+ * af=inet6 len=28 addr=ffff:ffff:8000:: hd={x1C, x1C, xFF, xFF, xFF, xFF, xFF, xFF, xFF, xFF, xFF, xFF, x80, x00{15}}
+ */
+
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+ verify_route_message_extra(rtm, if_nametoindex(c->ifnames[1]),
+ RTF_UP | RTF_DONE | RTF_GATEWAY | RTF_STATIC);
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_change_v6_mtu_success,
+ "Tests IPv6 path mtu change");
+
+ATF_TC_BODY(rtm_change_v6_mtu_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ unsigned long test_mtu = 1442;
+
+ c = presetup_ipv6(tc);
+
+ /* Create IPv6 subnetwork with smaller prefix */
+ struct sockaddr_in6 mask6;
+ struct sockaddr_in6 net6;
+ struct sockaddr_in6 gw6;
+ prepare_v6_network(c, &net6, &mask6, &gw6);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, (struct sockaddr *)&gw6);
+
+ /* Send route add */
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ /* Change MTU */
+ prepare_route_message(rtm, RTM_CHANGE, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, NULL);
+ rtm->rtm_inits |= RTV_MTU;
+ rtm->rtm_rmx.rmx_mtu = test_mtu;
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ verify_route_message(rtm, RTM_CHANGE, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, NULL);
+
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_rmx.rmx_mtu == test_mtu,
+ "expected mtu: %lu, got %lu", test_mtu, rtm->rtm_rmx.rmx_mtu);
+
+ /* Verify the change has actually taken place */
+ prepare_route_message(rtm, RTM_GET, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, NULL);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_rmx.rmx_mtu == test_mtu,
+ "expected mtu: %lu, got %lu", test_mtu, rtm->rtm_rmx.rmx_mtu);
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_change_v6_flags_success,
+ "Tests IPv6 path flags change");
+
+ATF_TC_BODY(rtm_change_v6_flags_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ uint32_t test_flags = RTF_PROTO1 | RTF_PROTO2 | RTF_PROTO3 | RTF_STATIC;
+ uint32_t desired_flags;
+
+ c = presetup_ipv6(tc);
+
+ /* Create IPv6 subnetwork with smaller prefix */
+ struct sockaddr_in6 mask6;
+ struct sockaddr_in6 net6;
+ struct sockaddr_in6 gw6;
+ prepare_v6_network(c, &net6, &mask6, &gw6);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, (struct sockaddr *)&gw6);
+
+ /* Set test flags during route addition */
+ desired_flags = RTF_UP | RTF_DONE | RTF_GATEWAY | test_flags;
+ rtm->rtm_flags |= test_flags;
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ /* Change flags */
+ prepare_route_message(rtm, RTM_CHANGE, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, NULL);
+ rtm->rtm_flags &= ~test_flags;
+ desired_flags &= ~test_flags;
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ /* Verify updated flags */
+ verify_route_message_extra(rtm, c->ifindex, desired_flags | RTF_DONE);
+
+ /* Verify the change has actually taken place */
+ prepare_route_message(rtm, RTM_GET, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, NULL);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ verify_route_message_extra(rtm, c->ifindex, desired_flags | RTF_DONE);
+}
+
+ATF_TC_WITH_CLEANUP(rtm_add_v4_temporal1_success);
+ATF_TC_HEAD(rtm_add_v4_temporal1_success, tc)
+{
+ DESCRIBE_ROOT_TEST("Tests IPv4 route expiration with expire time set");
+}
+
+ATF_TC_BODY(rtm_add_v4_temporal1_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv4(tc);
+
+ /* Create IPv4 subnetwork with smaller prefix */
+ struct sockaddr_in mask4;
+ struct sockaddr_in net4;
+ struct sockaddr_in gw4;
+ prepare_v4_network(c, &net4, &mask4, &gw4);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, (struct sockaddr *)&gw4);
+
+ /* Set expire time to now */
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ rtm->rtm_rmx.rmx_expire = tv.tv_sec - 1;
+ rtm->rtm_inits |= RTV_EXPIRE;
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+ ATF_REQUIRE_MSG(rtm != NULL, "unable to get rtsock reply for RTM_ADD");
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_inits & RTV_EXPIRE, "RTV_EXPIRE not set");
+
+ /* The next should be route deletion */
+ rtm = rtsock_read_rtm(c->rtsock_fd, buffer, sizeof(buffer));
+
+ verify_route_message(rtm, RTM_DELETE, (struct sockaddr *)&net4,
+ (struct sockaddr *)&mask4, (struct sockaddr *)&gw4);
+
+ verify_route_message_extra(rtm, c->ifindex,
+ RTF_DONE | RTF_GATEWAY | RTF_STATIC);
+}
+
+ATF_TC_CLEANUP(rtm_add_v4_temporal1_success, tc)
+{
+ CLEANUP_AFTER_TEST;
+}
+
+ATF_TC_WITH_CLEANUP(rtm_add_v6_temporal1_success);
+ATF_TC_HEAD(rtm_add_v6_temporal1_success, tc)
+{
+ DESCRIBE_ROOT_TEST("Tests IPv6 global unicast prefix addition with directly-reachable GU GW");
+}
+
+ATF_TC_BODY(rtm_add_v6_temporal1_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv6(tc);
+
+ /* Create IPv6 subnetwork with smaller prefix */
+ struct sockaddr_in6 mask6;
+ struct sockaddr_in6 net6;
+ struct sockaddr_in6 gw6;
+ prepare_v6_network(c, &net6, &mask6, &gw6);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, (struct sockaddr *)&gw6);
+
+ /* Set expire time to now */
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ rtm->rtm_rmx.rmx_expire = tv.tv_sec - 1;
+ rtm->rtm_inits |= RTV_EXPIRE;
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+ ATF_REQUIRE_MSG(rtm != NULL, "unable to get rtsock reply for RTM_ADD");
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_inits & RTV_EXPIRE, "RTV_EXPIRE not set");
+
+ /* The next should be route deletion */
+ rtm = rtsock_read_rtm(c->rtsock_fd, buffer, sizeof(buffer));
+
+ verify_route_message(rtm, RTM_DELETE, (struct sockaddr *)&net6,
+ (struct sockaddr *)&mask6, (struct sockaddr *)&gw6);
+
+ verify_route_message_extra(rtm, c->ifindex,
+ RTF_DONE | RTF_GATEWAY | RTF_STATIC);
+}
+
+ATF_TC_CLEANUP(rtm_add_v6_temporal1_success, tc)
+{
+ CLEANUP_AFTER_TEST;
+}
+
+/* Interface address messages tests */
+
+RTM_DECLARE_ROOT_TEST(rtm_add_v6_gu_ifa_hostroute_success,
+ "Tests validness for /128 host route announce after ifaddr assignment");
+
+ATF_TC_BODY(rtm_add_v6_gu_ifa_hostroute_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv6_iface(tc);
+
+ c->rtsock_fd = rtsock_setup_socket();
+
+ ret = iface_setup_addr(c->ifname, c->addr6_str, c->plen6);
+
+ /*
+ * There will be multiple.
+ * RTM_ADD without llinfo.
+ */
+
+ while (true) {
+ rtm = rtsock_read_rtm(c->rtsock_fd, buffer, sizeof(buffer));
+ if ((rtm->rtm_type == RTM_ADD) && ((rtm->rtm_flags & RTF_LLINFO) == 0))
+ break;
+ }
+ /* This should be a message for the host route */
+
+ verify_route_message(rtm, RTM_ADD, (struct sockaddr *)&c->addr6, NULL, NULL);
+ rtsock_validate_pid_kernel(rtm);
+ /* No netmask should be set */
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtsock_find_rtm_sa(rtm, RTA_NETMASK) == NULL, "netmask is set");
+
+ /* gateway should be link sdl with ifindex of an address interface */
+ verify_link_gateway(rtm, c->ifindex);
+
+ int expected_rt_flags = RTF_UP | RTF_HOST | RTF_DONE | RTF_STATIC | RTF_PINNED;
+ verify_route_message_extra(rtm, if_nametoindex("lo0"), expected_rt_flags);
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_add_v6_gu_ifa_prefixroute_success,
+ "Tests validness for the prefix route announce after ifaddr assignment");
+
+ATF_TC_BODY(rtm_add_v6_gu_ifa_prefixroute_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv6_iface(tc);
+
+ c->rtsock_fd = rtsock_setup_socket();
+
+ ret = iface_setup_addr(c->ifname, c->addr6_str, c->plen6);
+
+ /*
+ * Multiple RTM_ADD messages will be generated:
+ * 1) lladdr mapping (RTF_LLDATA)
+ * 2) host route (one w/o netmask)
+ * 3) prefix route
+ */
+
+ while (true) {
+ rtm = rtsock_read_rtm(c->rtsock_fd, buffer, sizeof(buffer));
+ /* Find RTM_ADD with netmask - this should skip both host route and LLADDR */
+ if ((rtm->rtm_type == RTM_ADD) && (rtsock_find_rtm_sa(rtm, RTA_NETMASK)))
+ break;
+ }
+
+ /* This should be a message for the prefix route */
+ verify_route_message(rtm, RTM_ADD, (struct sockaddr *)&c->net6,
+ (struct sockaddr *)&c->mask6, NULL);
+
+ /* gateway should be link sdl with ifindex of an address interface */
+ verify_link_gateway(rtm, c->ifindex);
+
+ int expected_rt_flags = RTF_UP | RTF_DONE | RTF_PINNED;
+ verify_route_message_extra(rtm, c->ifindex, expected_rt_flags);
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_add_v6_gu_ifa_ordered_success,
+ "Tests ordering of the messages for IPv6 global unicast ifaddr assignment");
+
+ATF_TC_BODY(rtm_add_v6_gu_ifa_ordered_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv6_iface(tc);
+
+ c->rtsock_fd = rtsock_setup_socket();
+
+ ret = iface_setup_addr(c->ifname, c->addr6_str, c->plen6);
+
+ int count = 0, tries = 0;
+
+ enum msgtype {
+ MSG_IFADDR,
+ MSG_HOSTROUTE,
+ MSG_PREFIXROUTE,
+ MSG_MAX,
+ };
+
+ int msg_array[MSG_MAX];
+
+ bzero(msg_array, sizeof(msg_array));
+
+ while (count < 3 && tries < 20) {
+ rtm = rtsock_read_rtm(c->rtsock_fd, buffer, sizeof(buffer));
+ tries++;
+ /* Classify */
+ if (rtm->rtm_type == RTM_NEWADDR) {
+ RLOG("MSG_IFADDR: %d", count);
+ msg_array[MSG_IFADDR] = count++;
+ continue;
+ }
+
+ /* Find RTM_ADD with netmask - this should skip both host route and LLADDR */
+ if ((rtm->rtm_type == RTM_ADD) && (rtsock_find_rtm_sa(rtm, RTA_NETMASK))) {
+ RLOG("MSG_PREFIXROUTE: %d", count);
+ msg_array[MSG_PREFIXROUTE] = count++;
+ continue;
+ }
+
+ if ((rtm->rtm_type == RTM_ADD) && ((rtm->rtm_flags & RTF_LLDATA) == 0)) {
+ RLOG("MSG_HOSTROUTE: %d", count);
+ msg_array[MSG_HOSTROUTE] = count++;
+ continue;
+ }
+
+ RLOG("skipping msg type %s, try: %d", rtsock_print_cmdtype(rtm->rtm_type),
+ tries);
+ }
+
+ /* TODO: verify multicast */
+ ATF_REQUIRE_MSG(count == 3, "Received only %d/3 messages", count);
+ ATF_REQUIRE_MSG(msg_array[MSG_IFADDR] == 0, "ifaddr message is not the first");
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_del_v6_gu_ifa_hostroute_success,
+ "Tests validness for /128 host route removal after ifaddr removal");
+
+ATF_TC_BODY(rtm_del_v6_gu_ifa_hostroute_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv6_iface(tc);
+
+ ret = iface_setup_addr(c->ifname, c->addr6_str, c->plen6);
+
+ c->rtsock_fd = rtsock_setup_socket();
+
+ ret = iface_delete_addr(c->ifname, c->addr6_str);
+
+ while (true) {
+ rtm = rtsock_read_rtm(c->rtsock_fd, buffer, sizeof(buffer));
+ if ((rtm->rtm_type == RTM_DELETE) &&
+ ((rtm->rtm_flags & RTF_LLINFO) == 0) &&
+ rtsock_find_rtm_sa(rtm, RTA_NETMASK) == NULL)
+ break;
+ }
+ /* This should be a message for the host route */
+
+ verify_route_message(rtm, RTM_DELETE, (struct sockaddr *)&c->addr6, NULL, NULL);
+ rtsock_validate_pid_kernel(rtm);
+ /* No netmask should be set */
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtsock_find_rtm_sa(rtm, RTA_NETMASK) == NULL, "netmask is set");
+
+ /* gateway should be link sdl with ifindex of an address interface */
+ verify_link_gateway(rtm, c->ifindex);
+
+ /* XXX: consider passing ifindex in rtm_index as done in RTM_ADD. */
+ int expected_rt_flags = RTF_HOST | RTF_DONE | RTF_STATIC | RTF_PINNED;
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_flags == expected_rt_flags,
+ "expected rtm flags: 0x%X, got 0x%X", expected_rt_flags, rtm->rtm_flags);
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_del_v6_gu_ifa_prefixroute_success,
+ "Tests validness for the prefix route removal after ifaddr assignment");
+
+ATF_TC_BODY(rtm_del_v6_gu_ifa_prefixroute_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv6_iface(tc);
+
+ ret = iface_setup_addr(c->ifname, c->addr6_str, c->plen6);
+
+ c->rtsock_fd = rtsock_setup_socket();
+
+ ret = iface_delete_addr(c->ifname, c->addr6_str);
+
+ while (true) {
+ rtm = rtsock_read_rtm(c->rtsock_fd, buffer, sizeof(buffer));
+ /* Find RTM_DELETE with netmask - this should skip both host route and LLADDR */
+ if ((rtm->rtm_type == RTM_DELETE) && (rtsock_find_rtm_sa(rtm, RTA_NETMASK)))
+ break;
+ }
+
+ /* This should be a message for the prefix route */
+ verify_route_message(rtm, RTM_DELETE, (struct sockaddr *)&c->net6,
+ (struct sockaddr *)&c->mask6, NULL);
+
+ /* gateway should be link sdl with ifindex of an address interface */
+ verify_link_gateway(rtm, c->ifindex);
+
+ int expected_rt_flags = RTF_DONE | RTF_PINNED;
+ verify_route_message_extra(rtm, c->ifindex, expected_rt_flags);
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_add_v4_gu_ifa_prefixroute_success,
+ "Tests validness for the prefix route announce after ifaddr assignment");
+
+ATF_TC_BODY(rtm_add_v4_gu_ifa_prefixroute_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv4_iface(tc);
+
+ c->rtsock_fd = rtsock_setup_socket();
+
+ ret = iface_setup_addr(c->ifname, c->addr6_str, c->plen6);
+
+ /*
+ * Multiple RTM_ADD messages will be generated:
+ * 1) lladdr mapping (RTF_LLDATA)
+ * 3) prefix route
+ */
+
+ while (true) {
+ rtm = rtsock_read_rtm(c->rtsock_fd, buffer, sizeof(buffer));
+ /* Find RTM_ADD with netmask - this should skip both host route and LLADDR */
+ if ((rtm->rtm_type == RTM_ADD) && (rtsock_find_rtm_sa(rtm, RTA_NETMASK)))
+ break;
+ }
+
+ /* This should be a message for the prefix route */
+ verify_route_message(rtm, RTM_ADD, (struct sockaddr *)&c->net4,
+ (struct sockaddr *)&c->mask4, NULL);
+
+ /* gateway should be link sdl with ifindex of an address interface */
+ verify_link_gateway(rtm, c->ifindex);
+
+ int expected_rt_flags = RTF_UP | RTF_DONE | RTF_PINNED;
+ verify_route_message_extra(rtm, c->ifindex, expected_rt_flags);
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_add_v4_gu_ifa_ordered_success,
+ "Tests ordering of the messages for IPv4 unicast ifaddr assignment");
+
+ATF_TC_BODY(rtm_add_v4_gu_ifa_ordered_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv4_iface(tc);
+
+ c->rtsock_fd = rtsock_setup_socket();
+
+ ret = iface_setup_addr(c->ifname, c->addr4_str, c->plen4);
+
+ int count = 0, tries = 0;
+
+ enum msgtype {
+ MSG_IFADDR,
+ MSG_PREFIXROUTE,
+ MSG_MAX,
+ };
+
+ int msg_array[MSG_MAX];
+
+ bzero(msg_array, sizeof(msg_array));
+
+ while (count < 2 && tries < 20) {
+ rtm = rtsock_read_rtm(c->rtsock_fd, buffer, sizeof(buffer));
+ tries++;
+ /* Classify */
+ if (rtm->rtm_type == RTM_NEWADDR) {
+ RLOG("MSG_IFADDR: %d", count);
+ msg_array[MSG_IFADDR] = count++;
+ continue;
+ }
+
+ /* Find RTM_ADD with netmask - this should skip both host route and LLADDR */
+ if ((rtm->rtm_type == RTM_ADD) && (rtsock_find_rtm_sa(rtm, RTA_NETMASK))) {
+ RLOG("MSG_PREFIXROUTE: %d", count);
+ msg_array[MSG_PREFIXROUTE] = count++;
+ continue;
+ }
+
+ RLOG("skipping msg type %s, try: %d", rtsock_print_cmdtype(rtm->rtm_type),
+ tries);
+ }
+
+ /* TODO: verify multicast */
+ ATF_REQUIRE_MSG(count == 2, "Received only %d/2 messages", count);
+ ATF_REQUIRE_MSG(msg_array[MSG_IFADDR] == 0, "ifaddr message is not the first");
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_del_v4_gu_ifa_prefixroute_success,
+ "Tests validness for the prefix route removal after ifaddr assignment");
+
+ATF_TC_BODY(rtm_del_v4_gu_ifa_prefixroute_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv4_iface(tc);
+
+
+ ret = iface_setup_addr(c->ifname, c->addr4_str, c->plen4);
+
+ c->rtsock_fd = rtsock_setup_socket();
+
+ ret = iface_delete_addr(c->ifname, c->addr4_str);
+
+ while (true) {
+ rtm = rtsock_read_rtm(c->rtsock_fd, buffer, sizeof(buffer));
+ /* Find RTM_ADD with netmask - this should skip both host route and LLADDR */
+ if ((rtm->rtm_type == RTM_DELETE) && (rtsock_find_rtm_sa(rtm, RTA_NETMASK)))
+ break;
+ }
+
+ /* This should be a message for the prefix route */
+ verify_route_message(rtm, RTM_DELETE, (struct sockaddr *)&c->net4,
+ (struct sockaddr *)&c->mask4, NULL);
+
+ /* gateway should be link sdl with ifindex of an address interface */
+ verify_link_gateway(rtm, c->ifindex);
+
+ int expected_rt_flags = RTF_DONE | RTF_PINNED;
+ verify_route_message_extra(rtm, c->ifindex, expected_rt_flags);
+}
+
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, rtm_get_v4_exact_success);
+ ATF_TP_ADD_TC(tp, rtm_get_v4_lpm_success);
+ ATF_TP_ADD_TC(tp, rtm_get_v4_hostbits_success);
+ ATF_TP_ADD_TC(tp, rtm_get_v4_empty_dst_failure);
+ ATF_TP_ADD_TC(tp, rtm_add_v4_no_rtf_host_success);
+ ATF_TP_ADD_TC(tp, rtm_add_v4_gw_direct_success);
+ ATF_TP_ADD_TC(tp, rtm_del_v4_prefix_nogw_success);
+ ATF_TP_ADD_TC(tp, rtm_add_v6_gu_gw_gu_direct_success);
+ ATF_TP_ADD_TC(tp, rtm_del_v6_gu_prefix_nogw_success);
+ ATF_TP_ADD_TC(tp, rtm_change_v4_gw_success);
+ ATF_TP_ADD_TC(tp, rtm_change_v4_mtu_success);
+ ATF_TP_ADD_TC(tp, rtm_change_v4_flags_success);
+ ATF_TP_ADD_TC(tp, rtm_change_v6_gw_success);
+ ATF_TP_ADD_TC(tp, rtm_change_v6_mtu_success);
+ ATF_TP_ADD_TC(tp, rtm_change_v6_flags_success);
+ /* ifaddr tests */
+ ATF_TP_ADD_TC(tp, rtm_add_v6_gu_ifa_hostroute_success);
+ ATF_TP_ADD_TC(tp, rtm_add_v6_gu_ifa_prefixroute_success);
+ ATF_TP_ADD_TC(tp, rtm_add_v6_gu_ifa_ordered_success);
+ ATF_TP_ADD_TC(tp, rtm_del_v6_gu_ifa_hostroute_success);
+ ATF_TP_ADD_TC(tp, rtm_del_v6_gu_ifa_prefixroute_success);
+ ATF_TP_ADD_TC(tp, rtm_add_v4_gu_ifa_ordered_success);
+ ATF_TP_ADD_TC(tp, rtm_del_v4_gu_ifa_prefixroute_success);
+ /* temporal routes */
+ ATF_TP_ADD_TC(tp, rtm_add_v4_temporal1_success);
+ ATF_TP_ADD_TC(tp, rtm_add_v6_temporal1_success);
+
+ return (atf_no_error());
+}
+
diff --git a/tests/sys/net/routing/test_rtsock_lladdr.c b/tests/sys/net/routing/test_rtsock_lladdr.c
new file mode 100644
index 000000000000..072a4e31f7ce
--- /dev/null
+++ b/tests/sys/net/routing/test_rtsock_lladdr.c
@@ -0,0 +1,416 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Alexander V. Chernikov
+ *
+ * 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.
+ */
+
+#include "rtsock_common.h"
+#include "rtsock_config.h"
+
+static void
+jump_vnet(struct rtsock_test_config *c, const atf_tc_t *tc)
+{
+ char vnet_name[512];
+
+ snprintf(vnet_name, sizeof(vnet_name), "vt-%s", atf_tc_get_ident(tc));
+ RLOG("jumping to %s", vnet_name);
+
+ vnet_switch_one(vnet_name, c->ifname);
+
+ /* Update ifindex cache */
+ c->ifindex = if_nametoindex(c->ifname);
+}
+
+static inline struct rtsock_test_config *
+presetup_ipv6(const atf_tc_t *tc)
+{
+ struct rtsock_test_config *c;
+ int ret;
+
+ c = config_setup(tc, NULL);
+
+ jump_vnet(c, tc);
+
+ ret = iface_turn_up(c->ifname);
+ ATF_REQUIRE_MSG(ret == 0, "Unable to turn up %s", c->ifname);
+ ret = iface_enable_ipv6(c->ifname);
+ ATF_REQUIRE_MSG(ret == 0, "Unable to enable IPv6 on %s", c->ifname);
+
+ c->rtsock_fd = rtsock_setup_socket();
+
+ return (c);
+}
+
+static inline struct rtsock_test_config *
+presetup_ipv4(const atf_tc_t *tc)
+{
+ struct rtsock_test_config *c;
+ int ret;
+
+ c = config_setup(tc, NULL);
+
+ jump_vnet(c, tc);
+
+ /* assumes ifconfig doing IFF_UP */
+ ret = iface_setup_addr(c->ifname, c->addr4_str, c->plen4);
+ ATF_REQUIRE_MSG(ret == 0, "ifconfig failed");
+
+ c->rtsock_fd = rtsock_setup_socket();
+
+ return (c);
+}
+
+static void
+prepare_route_message(struct rt_msghdr *rtm, int cmd, struct sockaddr *dst,
+ struct sockaddr *gw)
+{
+
+ rtsock_prepare_route_message(rtm, cmd, dst, NULL, gw);
+
+ rtm->rtm_flags |= (RTF_HOST | RTF_STATIC | RTF_LLDATA);
+}
+
+/* TESTS */
+#define DECLARE_TEST_VARS \
+ char buffer[2048], msg[512]; \
+ ssize_t len; \
+ int ret; \
+ struct rtsock_test_config *c; \
+ struct rt_msghdr *rtm = (struct rt_msghdr *)buffer; \
+ struct sockaddr *sa; \
+ \
+
+#define DECLARE_CLEANUP_VARS \
+ struct rtsock_test_config *c = config_setup(tc); \
+ \
+
+#define DESCRIBE_ROOT_TEST(_msg) config_describe_root_test(tc, _msg)
+#define CLEANUP_AFTER_TEST config_generic_cleanup(tc)
+
+#define RTM_DECLARE_ROOT_TEST(_name, _descr) \
+ATF_TC_WITH_CLEANUP(_name); \
+ATF_TC_HEAD(_name, tc) \
+{ \
+ DESCRIBE_ROOT_TEST(_descr); \
+} \
+ATF_TC_CLEANUP(_name, tc) \
+{ \
+ CLEANUP_AFTER_TEST; \
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_add_v6_ll_lle_success, "Tests addition of link-local IPv6 ND entry");
+ATF_TC_BODY(rtm_add_v6_ll_lle_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv6(tc);
+
+ char str_buf[128];
+ struct sockaddr_in6 sin6;
+ /* Interface here is optional. XXX: verify kernel side. */
+ char *v6addr = "fe80::4242:4242";
+ snprintf(str_buf, sizeof(str_buf), "%s%%%s", v6addr, c->ifname);
+ sa_convert_str_to_sa(str_buf, (struct sockaddr *)&sin6);
+
+ struct sockaddr_dl ether;
+ snprintf(str_buf, sizeof(str_buf), "%s%%%s", c->remote_lladdr, c->ifname);
+ sa_convert_str_to_sa(str_buf, (struct sockaddr *)&ether);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&sin6, (struct sockaddr *)&ether);
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ /*
+ * Got message of size 240 on 2019-12-17 15:06:51
+ * RTM_ADD: Add Route: len 240, pid: 0, seq 0, errno 0, flags: <UP,HOST,DONE,LLINFO>
+ * sockaddrs: 0x3 <DST,GATEWAY>
+ * af=inet6 len=28 addr=fe80::4242:4242 scope_id=3 if_name=tap4242
+ * af=link len=54 sdl_index=3 if_name=tap4242 addr=52:54:00:14:E3:10
+ */
+
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ sa = rtsock_find_rtm_sa(rtm, RTA_DST);
+ ret = sa_equal_msg(sa, (struct sockaddr *)&sin6, msg, sizeof(msg));
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "DST sa diff: %s", msg);
+
+ sa = rtsock_find_rtm_sa(rtm, RTA_GATEWAY);
+ int sa_flags = SA_F_IGNORE_IFNAME | SA_F_IGNORE_IFTYPE | SA_F_IGNORE_MEMCMP;
+ ret = sa_equal_msg_flags(sa, (struct sockaddr *)&ether, msg, sizeof(msg), sa_flags);
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "GATEWAY sa diff: %s", msg);
+
+#if 0
+ /* Disable the check until https://reviews.freebsd.org/D22003 merge */
+ /* Some additional checks to verify kernel has filled in interface data */
+ struct sockaddr_dl *sdl = (struct sockaddr_dl *)sa;
+ RTSOCK_ATF_REQUIRE_MSG(rtm, sdl->sdl_type > 0, "sdl_type not set");
+#endif
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_add_v6_gu_lle_success, "Tests addition of global IPv6 ND entry");
+ATF_TC_BODY(rtm_add_v6_gu_lle_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv6(tc);
+
+ char str_buf[128];
+
+ struct sockaddr_in6 sin6;
+ sin6 = c->net6;
+#define _s6_addr32 __u6_addr.__u6_addr32
+ sin6.sin6_addr._s6_addr32[3] = htonl(0x42424242);
+#undef _s6_addr32
+
+ struct sockaddr_dl ether;
+ snprintf(str_buf, sizeof(str_buf), "%s%%%s", c->remote_lladdr, c->ifname);
+ sa_convert_str_to_sa(str_buf, (struct sockaddr *)&ether);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&sin6, (struct sockaddr *)&ether);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ /*
+ * Got message of size 240 on 2019-12-17 14:56:43
+ * RTM_ADD: Add Route: len 240, pid: 0, seq 0, errno 0, flags: <UP,HOST,DONE,LLINFO>
+ * sockaddrs: 0x3 <DST,GATEWAY>
+ * af=inet6 len=28 addr=2001:db8::4242:4242
+ * af=link len=54 sdl_index=3 if_name=tap4242 addr=52:54:00:14:E3:10
+ */
+
+ /* XXX: where is uRPF?! this should fail */
+
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ sa = rtsock_find_rtm_sa(rtm, RTA_DST);
+ ret = sa_equal_msg(sa, (struct sockaddr *)&sin6, msg, sizeof(msg));
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "DST sa diff: %s", msg);
+
+ sa = rtsock_find_rtm_sa(rtm, RTA_GATEWAY);
+ int sa_flags = SA_F_IGNORE_IFNAME | SA_F_IGNORE_IFTYPE | SA_F_IGNORE_MEMCMP;
+ ret = sa_equal_msg_flags(sa, (struct sockaddr *)&ether, msg, sizeof(msg), sa_flags);
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "GATEWAY sa diff: %s", msg);
+
+#if 0
+ /* Disable the check until https://reviews.freebsd.org/D22003 merge */
+ /* Some additional checks to verify kernel has filled in interface data */
+ struct sockaddr_dl *sdl = (struct sockaddr_dl *)sa;
+ RTSOCK_ATF_REQUIRE_MSG(rtm, sdl->sdl_type > 0, "sdl_type not set");
+#endif
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_add_v4_gu_lle_success, "Tests addition of IPv4 ARP entry");
+ATF_TC_BODY(rtm_add_v4_gu_lle_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv4(tc);
+
+ char str_buf[128];
+
+ struct sockaddr_in sin;
+ sin = c->addr4;
+ /* Use the next IPv4 address after self */
+ sin.sin_addr.s_addr = htonl(ntohl(sin.sin_addr.s_addr) + 1);
+
+ struct sockaddr_dl ether;
+ snprintf(str_buf, sizeof(str_buf), "%s%%%s", c->remote_lladdr, c->ifname);
+ sa_convert_str_to_sa(str_buf, (struct sockaddr *)&ether);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&sin, (struct sockaddr *)&ether);
+
+ len = rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ /*
+ * RTM_ADD: Add Route: len 224, pid: 43131, seq 42, errno 0, flags: <HOST,DONE,LLINFO,STATIC>
+ * sockaddrs: 0x3 <DST,GATEWAY>
+ * af=inet len=16 addr=192.0.2.2
+ * af=link len=54 sdl_index=3 if_name=tap4242 addr=52:54:00:14:E3:10
+ */
+
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ sa = rtsock_find_rtm_sa(rtm, RTA_DST);
+ ret = sa_equal_msg(sa, (struct sockaddr *)&sin, msg, sizeof(msg));
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "DST sa diff: %s", msg);
+
+ sa = rtsock_find_rtm_sa(rtm, RTA_GATEWAY);
+ int sa_flags = SA_F_IGNORE_IFNAME | SA_F_IGNORE_IFTYPE | SA_F_IGNORE_MEMCMP;
+ ret = sa_equal_msg_flags(sa, (struct sockaddr *)&ether, msg, sizeof(msg), sa_flags);
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "GATEWAY sa diff: %s", msg);
+
+ /*
+ * TODO: Currently kernel code does not set sdl_type, contrary to IPv6.
+ */
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_del_v6_ll_lle_success, "Tests removal of link-local IPv6 ND entry");
+ATF_TC_BODY(rtm_del_v6_ll_lle_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv6(tc);
+
+ char str_buf[128];
+
+ struct sockaddr_in6 sin6;
+ /* Interface here is optional. XXX: verify kernel side. */
+ char *v6addr = "fe80::4242:4242";
+ snprintf(str_buf, sizeof(str_buf), "%s%%%s", v6addr, c->ifname);
+ sa_convert_str_to_sa(str_buf, (struct sockaddr *)&sin6);
+
+ struct sockaddr_dl ether;
+ snprintf(str_buf, sizeof(str_buf), "%s%%%s", c->remote_lladdr, c->ifname);
+ sa_convert_str_to_sa(str_buf, (struct sockaddr *)&ether);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&sin6, (struct sockaddr *)&ether);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ /* Successfully added an entry, let's try to remove it. */
+ prepare_route_message(rtm, RTM_DELETE, (struct sockaddr *)&sin6, (struct sockaddr *)&ether);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_type == RTM_DELETE, "rtm_type is not delete");
+
+ sa = rtsock_find_rtm_sa(rtm, RTA_DST);
+ ret = sa_equal_msg(sa, (struct sockaddr *)&sin6, msg, sizeof(msg));
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "DST sa diff: %s", msg);
+
+ sa = rtsock_find_rtm_sa(rtm, RTA_GATEWAY);
+ int sa_flags = SA_F_IGNORE_IFNAME | SA_F_IGNORE_IFTYPE | SA_F_IGNORE_MEMCMP;
+ ret = sa_equal_msg_flags(sa, (struct sockaddr *)&ether, msg, sizeof(msg), sa_flags);
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "GATEWAY sa diff: %s", msg);
+
+ /*
+ * TODO: Currently kernel code does not set sdl_type on delete.
+ */
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_del_v6_gu_lle_success, "Tests removal of global IPv6 ND entry");
+ATF_TC_BODY(rtm_del_v6_gu_lle_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv6(tc);
+
+ char str_buf[128];
+
+ struct sockaddr_in6 sin6;
+ sin6 = c->net6;
+#define _s6_addr32 __u6_addr.__u6_addr32
+ sin6.sin6_addr._s6_addr32[3] = htonl(0x42424242);
+#undef _s6_addr32
+
+ struct sockaddr_dl ether;
+ snprintf(str_buf, sizeof(str_buf), "%s%%%s", c->remote_lladdr, c->ifname);
+ sa_convert_str_to_sa(str_buf, (struct sockaddr *)&ether);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&sin6, (struct sockaddr *)&ether);
+
+ len = rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ /* Successfully added an entry, let's try to remove it. */
+ prepare_route_message(rtm, RTM_DELETE, (struct sockaddr *)&sin6, (struct sockaddr *)&ether);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_type == RTM_DELETE, "rtm_type is not delete");
+
+ sa = rtsock_find_rtm_sa(rtm, RTA_DST);
+ ret = sa_equal_msg(sa, (struct sockaddr *)&sin6, msg, sizeof(msg));
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "DST sa diff: %s", msg);
+
+ sa = rtsock_find_rtm_sa(rtm, RTA_GATEWAY);
+ int sa_flags = SA_F_IGNORE_IFNAME | SA_F_IGNORE_IFTYPE | SA_F_IGNORE_MEMCMP;
+ ret = sa_equal_msg_flags(sa, (struct sockaddr *)&ether, msg, sizeof(msg), sa_flags);
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "GATEWAY sa diff: %s", msg);
+
+ /*
+ * TODO: Currently kernel code does not set sdl_type on delete.
+ */
+}
+
+RTM_DECLARE_ROOT_TEST(rtm_del_v4_gu_lle_success, "Tests removal of IPv4 ARP entry");
+ATF_TC_BODY(rtm_del_v4_gu_lle_success, tc)
+{
+ DECLARE_TEST_VARS;
+
+ c = presetup_ipv4(tc);
+
+ char str_buf[128];
+
+ struct sockaddr_in sin;
+ sin = c->addr4;
+ /* Use the next IPv4 address after self */
+ sin.sin_addr.s_addr = htonl(ntohl(sin.sin_addr.s_addr) + 1);
+
+ struct sockaddr_dl ether;
+ snprintf(str_buf, sizeof(str_buf), "%s%%%s", c->remote_lladdr, c->ifname);
+ sa_convert_str_to_sa(str_buf, (struct sockaddr *)&ether);
+
+ prepare_route_message(rtm, RTM_ADD, (struct sockaddr *)&sin, (struct sockaddr *)&ether);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ /* We successfully added an entry, let's try to remove it. */
+ prepare_route_message(rtm, RTM_DELETE, (struct sockaddr *)&sin, (struct sockaddr *)&ether);
+
+ rtsock_send_rtm(c->rtsock_fd, rtm);
+
+ rtm = rtsock_read_rtm_reply(c->rtsock_fd, buffer, sizeof(buffer), rtm->rtm_seq);
+
+ RTSOCK_ATF_REQUIRE_MSG(rtm, rtm->rtm_type == RTM_DELETE, "rtm_type is not delete");
+
+ sa = rtsock_find_rtm_sa(rtm, RTA_DST);
+ ret = sa_equal_msg(sa, (struct sockaddr *)&sin, msg, sizeof(msg));
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "DST sa diff: %s", msg);
+
+ sa = rtsock_find_rtm_sa(rtm, RTA_GATEWAY);
+ int sa_flags = SA_F_IGNORE_IFNAME | SA_F_IGNORE_IFTYPE | SA_F_IGNORE_MEMCMP;
+ ret = sa_equal_msg_flags(sa, (struct sockaddr *)&ether, msg, sizeof(msg), sa_flags);
+ RTSOCK_ATF_REQUIRE_MSG(rtm, ret != 0, "GATEWAY sa diff: %s", msg);
+
+ /*
+ * TODO: Currently kernel code does not set sdl_type, contrary to IPv6.
+ */
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, rtm_add_v6_ll_lle_success);
+ ATF_TP_ADD_TC(tp, rtm_add_v6_gu_lle_success);
+ ATF_TP_ADD_TC(tp, rtm_add_v4_gu_lle_success);
+ ATF_TP_ADD_TC(tp, rtm_del_v6_ll_lle_success);
+ ATF_TP_ADD_TC(tp, rtm_del_v6_gu_lle_success);
+ ATF_TP_ADD_TC(tp, rtm_del_v4_gu_lle_success);
+
+ return (atf_no_error());
+}
+
+
diff --git a/tests/sys/net/routing/test_rtsock_multipath.py b/tests/sys/net/routing/test_rtsock_multipath.py
new file mode 100755
index 000000000000..f4734d2f65bb
--- /dev/null
+++ b/tests/sys/net/routing/test_rtsock_multipath.py
@@ -0,0 +1,271 @@
+import pytest
+from atf_python.sys.net.rtsock import RtConst
+from atf_python.sys.net.rtsock import Rtsock
+from atf_python.sys.net.rtsock import RtsockRtMessage
+from atf_python.sys.net.tools import ToolsHelper
+from atf_python.sys.net.vnet import SingleVnetTestTemplate
+
+
+class TestRtmMultipath(SingleVnetTestTemplate):
+ def setup_method(self, method):
+ method_name = method.__name__
+ if "multipath4" in method_name:
+ self.IPV4_PREFIXES = ["192.0.2.1/24"]
+ self.PREFIX = "128.66.0.0/24"
+ elif "multipath6" in method_name:
+ self.IPV6_PREFIXES = ["2001:db8::1/64"]
+ self.PREFIX = "2001:db8:0:ddbb::/64"
+ super().setup_method(method)
+ self.rtsock = Rtsock()
+
+ def get_prefix_routes(self):
+ family = "inet6" if ":" in self.PREFIX else "inet"
+ routes = ToolsHelper.get_routes(family)
+ return [r for r in routes if r["destination"] == self.PREFIX]
+
+ @pytest.mark.parametrize(
+ "gws",
+ [
+ pytest.param(["+.10=2", "+.5=3"], id="transition_multi"),
+ pytest.param(["+.10=2", "+.5=3", "-.10=2"], id="transition_single1"),
+ pytest.param(["+.10=2", "+.5=3", "-.5=3"], id="transition_single2"),
+ pytest.param(
+ ["+.10", "+.11", "+.50", "+.13", "+.145", "+.72"], id="correctness1"
+ ),
+ pytest.param(
+ ["+.10", "+.11", "+.50", "-.50", "+.145", "+.72"], id="correctness2"
+ ),
+ pytest.param(["+.10=1", "+.5=2"], id="weight1"),
+ pytest.param(["+.10=2", "+.5=7"], id="weight2"),
+ pytest.param(["+.10=13", "+.5=21"], id="weight3_max"),
+ pytest.param(["+.10=2", "+.5=3", "~.5=4"], id="change_new_weight1"),
+ pytest.param(["+.10=2", "+.5=3", "~.10=3"], id="change_new_weight2"),
+ pytest.param(
+ ["+.10=2", "+.5=3", "+.7=4", "~.10=3"], id="change_new_weight3"
+ ),
+ pytest.param(["+.10=2", "+.5=3", "~.5=3"], id="change_same_weight1"),
+ pytest.param(
+ ["+.10=2", "+.5=3", "+.7=4", "~.5=3"], id="change_same_weight2"
+ ),
+ ],
+ )
+ @pytest.mark.require_user("root")
+ def test_rtm_multipath4(self, gws):
+ """Tests RTM_ADD with IPv4 dest transitioning to multipath"""
+ self._test_rtm_multipath(gws, "192.0.2")
+
+ @pytest.mark.parametrize(
+ "gws",
+ [
+ pytest.param(["+:10=2", "+:5=3"], id="transition_multi"),
+ pytest.param(["+:10=2", "+:5=3", "-:10=2"], id="transition_single1"),
+ pytest.param(["+:10=2", "+:5=3", "-:5=3"], id="transition_single2"),
+ pytest.param(
+ ["+:10", "+:11", "+:50", "+:13", "+:145", "+:72"], id="correctness1"
+ ),
+ pytest.param(
+ ["+:10", "+:11", "+:50", "-:50", "+:145", "+:72"], id="correctness2"
+ ),
+ pytest.param(["+:10=1", "+:5=2"], id="weight1"),
+ pytest.param(["+:10=2", "+:5=7"], id="weight2"),
+ pytest.param(["+:10=13", "+:5=21"], id="weight3_max"),
+ pytest.param(["+:10=13", "+:5=21"], id="weight3_max"),
+ pytest.param(["+:10=2", "+:5=3", "~:5=4"], id="change_new_weight1"),
+ pytest.param(["+:10=2", "+:5=3", "~:10=3"], id="change_new_weight2"),
+ pytest.param(
+ ["+:10=2", "+:5=3", "+:7=4", "~:10=3"], id="change_new_weight3"
+ ),
+ pytest.param(["+:10=2", "+:5=3", "~:5=3"], id="change_same_weight1"),
+ pytest.param(
+ ["+:10=2", "+:5=3", "+:7=4", "~:5=3"], id="change_same_weight2"
+ ),
+ ],
+ )
+ @pytest.mark.require_user("root")
+ def test_rtm_multipath6(self, gws):
+ """Tests RTM_ADD with IPv6 dest transitioning to multipath"""
+ self._test_rtm_multipath(gws, "2001:db8:")
+
+ def _test_rtm_multipath(self, gws, gw_prefix: str):
+ desired_map = {}
+ for gw_act in gws:
+ # GW format: <+-~>GW[=weight]
+ if "=" in gw_act:
+ arr = gw_act[1:].split("=")
+ weight = int(arr[1])
+ gw = gw_prefix + arr[0]
+ else:
+ weight = None
+ gw = gw_prefix + gw_act[1:]
+ if gw_act[0] == "+":
+ msg = self.rtsock.new_rtm_add(self.PREFIX, gw)
+ desired_map[gw] = self.rtsock.get_weight(weight)
+ elif gw_act[0] == "-":
+ msg = self.rtsock.new_rtm_del(self.PREFIX, gw)
+ del desired_map[gw]
+ else:
+ msg = self.rtsock.new_rtm_change(self.PREFIX, gw)
+ desired_map[gw] = self.rtsock.get_weight(weight)
+
+ msg.rtm_flags = RtConst.RTF_GATEWAY
+ if weight:
+ msg.rtm_inits |= RtConst.RTV_WEIGHT
+ msg.rtm_rmx.rmx_weight = weight
+ # Prepare SAs to check for
+ desired_sa = {
+ RtConst.RTA_DST: msg.get_sa(RtConst.RTA_DST),
+ RtConst.RTA_NETMASK: msg.get_sa(RtConst.RTA_NETMASK),
+ RtConst.RTA_GATEWAY: msg.get_sa(RtConst.RTA_GATEWAY),
+ }
+ self.rtsock.write_message(msg)
+
+ data = self.rtsock.read_data(msg.rtm_seq)
+ msg_in = RtsockRtMessage.from_bytes(data)
+ msg_in.print_in_message()
+ msg_in.verify(msg.rtm_type, desired_sa)
+ assert msg_in.rtm_rmx.rmx_weight == self.rtsock.get_weight(weight)
+
+ routes = self.get_prefix_routes()
+ derived_map = {r["gateway"]: r["weight"] for r in routes}
+ assert derived_map == desired_map
+
+ @pytest.mark.require_user("root")
+ def test_rtm_multipath4_add_same_eexist(self):
+ """Tests adding same IPv4 gw to the multipath group (EEXIST)"""
+ gws = ["192.0.2.10", "192.0.2.11", "192.0.2.11"]
+ self._test_rtm_multipath_add_same_eexist(gws)
+
+ @pytest.mark.require_user("root")
+ def test_rtm_multipath6_add_same_eexist(self):
+ """Tests adding same IPv4 gw to the multipath group (EEXIST)"""
+ gws = ["2001:db8::10", "2001:db8::11", "2001:db8::11"]
+ self._test_rtm_multipath_add_same_eexist(gws)
+
+ def _test_rtm_multipath_add_same_eexist(self, gws):
+ for idx, gw in enumerate(gws):
+ msg = self.rtsock.new_rtm_add(self.PREFIX, gw)
+ msg.rtm_flags = RtConst.RTF_GATEWAY
+ try:
+ self.rtsock.write_message(msg)
+ except FileExistsError as e:
+ if idx != 2:
+ raise
+ print("Succcessfully raised {}".format(e))
+
+ @pytest.mark.require_user("root")
+ def test_rtm_multipath4_del_unknown_esrch(self):
+ """Tests deleting non-existing dest from the multipath group (ESRCH)"""
+ gws = ["192.0.2.10", "192.0.2.11"]
+ self._test_rtm_multipath_del_unknown_esrch(gws, "192.0.2.7")
+
+ @pytest.mark.require_user("root")
+ def test_rtm_multipath6_del_unknown_esrch(self):
+ """Tests deleting non-existing dest from the multipath group (ESRCH)"""
+ gws = ["2001:db8::10", "2001:db8::11"]
+ self._test_rtm_multipath_del_unknown_esrch(gws, "2001:db8::7")
+
+ @pytest.mark.require_user("root")
+ def _test_rtm_multipath_del_unknown_esrch(self, gws, target_gw):
+ for gw in gws:
+ msg = self.rtsock.new_rtm_add(self.PREFIX, gw)
+ msg.rtm_flags = RtConst.RTF_GATEWAY
+ self.rtsock.write_message(msg)
+ msg = self.rtsock.new_rtm_del(self.PREFIX, target_gw)
+ msg.rtm_flags = RtConst.RTF_GATEWAY
+ try:
+ self.rtsock.write_message(msg)
+ except ProcessLookupError as e:
+ print("Succcessfully raised {}".format(e))
+
+ @pytest.mark.require_user("root")
+ def test_rtm_multipath4_change_unknown_esrch(self):
+ """Tests changing non-existing dest in the multipath group (ESRCH)"""
+ gws = ["192.0.2.10", "192.0.2.11"]
+ self._test_rtm_multipath_change_unknown_esrch(gws, "192.0.2.7")
+
+ @pytest.mark.require_user("root")
+ def test_rtm_multipath6_change_unknown_esrch(self):
+ """Tests changing non-existing dest in the multipath group (ESRCH)"""
+ gws = ["2001:db8::10", "2001:db8::11"]
+ self._test_rtm_multipath_change_unknown_esrch(gws, "2001:db8::7")
+
+ @pytest.mark.require_user("root")
+ def _test_rtm_multipath_change_unknown_esrch(self, gws, target_gw):
+ for gw in gws:
+ msg = self.rtsock.new_rtm_add(self.PREFIX, gw)
+ msg.rtm_flags = RtConst.RTF_GATEWAY
+ self.rtsock.write_message(msg)
+ msg = self.rtsock.new_rtm_change(self.PREFIX, target_gw)
+ msg.rtm_flags = RtConst.RTF_GATEWAY
+ try:
+ self.rtsock.write_message(msg)
+ except ProcessLookupError as e:
+ print("Succcessfully raised {}".format(e))
+
+ @pytest.mark.require_user("root")
+ def test_rtm_multipath4_add_zero_weight(self):
+ """Tests RTM_ADD with dest transitioning to multipath"""
+
+ desired_map = {}
+ for gw in ["192.0.2.10", "192.0.2.11", "192.0.2.13"]:
+ msg = self.rtsock.new_rtm_add(self.PREFIX, gw)
+ msg.rtm_flags = RtConst.RTF_GATEWAY
+ msg.rtm_rmx.rmx_weight = 0
+ msg.rtm_inits |= RtConst.RTV_WEIGHT
+ self.rtsock.write_message(msg)
+ desired_map[gw] = self.rtsock.get_weight(0)
+
+ routes = self.get_prefix_routes()
+ derived_map = {r["gateway"]: r["weight"] for r in routes}
+ assert derived_map == desired_map
+
+ @pytest.mark.require_user("root")
+ def test_rtm_multipath4_getroute(self):
+ """Tests RTM_GET with exact prefix lookup on the multipath group"""
+ gws = ["192.0.2.10", "192.0.2.11", "192.0.2.13"]
+ return self._test_rtm_multipath_getroute(gws)
+
+ @pytest.mark.require_user("root")
+ def test_rtm_multipath6_getroute(self):
+ """Tests RTM_GET with exact prefix lookup on the multipath group"""
+ gws = ["2001:db8::10", "2001:db8::11", "2001:db8::13"]
+ return self._test_rtm_multipath_getroute(gws)
+
+ def _test_rtm_multipath_getroute(self, gws):
+ valid_gws = []
+ for gw in gws:
+ msg = self.rtsock.new_rtm_add(self.PREFIX, gw)
+ msg.rtm_flags = RtConst.RTF_GATEWAY
+ self.rtsock.write_message(msg)
+
+ desired_sa = {
+ RtConst.RTA_DST: msg.get_sa(RtConst.RTA_DST),
+ RtConst.RTA_NETMASK: msg.get_sa(RtConst.RTA_NETMASK),
+ }
+ valid_gws.append(msg.get_sa(RtConst.RTA_GATEWAY))
+
+ msg_get = RtsockRtMessage(
+ RtConst.RTM_GET,
+ self.rtsock.get_seq(),
+ msg.get_sa(RtConst.RTA_DST),
+ msg.get_sa(RtConst.RTA_NETMASK),
+ )
+ self.rtsock.write_message(msg_get)
+
+ data = self.rtsock.read_data(msg_get.rtm_seq)
+ msg_in = RtsockRtMessage.from_bytes(data)
+ msg_in.print_in_message()
+ msg_in.verify(RtConst.RTM_GET, desired_sa)
+
+ # Additionally, check that the gateway is among the valid
+ # gateways
+ gw_found = False
+ gw_in = msg_in.get_sa(RtConst.RTA_GATEWAY)
+ for valid_gw in valid_gws:
+ try:
+ assert valid_gw == gw_in
+ gw_found = True
+ break
+ except AssertionError:
+ pass
+ assert gw_found is True
diff --git a/tests/sys/net/routing/test_rtsock_ops.c b/tests/sys/net/routing/test_rtsock_ops.c
new file mode 100644
index 000000000000..ab4be93cc1af
--- /dev/null
+++ b/tests/sys/net/routing/test_rtsock_ops.c
@@ -0,0 +1,53 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2023 Alexander V. Chernikov
+ *
+ * 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.
+ */
+
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <sys/socket.h>
+#include <net/route.h>
+
+#include <atf-c.h>
+
+ATF_TC(socket_rtsock_openclose);
+ATF_TC_HEAD(socket_rtsock_openclose, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "test successful open/close");
+}
+
+ATF_TC_BODY(socket_rtsock_openclose, tc)
+{
+ int s = socket(PF_ROUTE, SOCK_RAW, 0);
+ ATF_CHECK(s >= 0);
+ ATF_CHECK_ERRNO(0, close(s) == 0);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, socket_rtsock_openclose);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/net/stp.py b/tests/sys/net/stp.py
new file mode 100644
index 000000000000..dc6634fb7279
--- /dev/null
+++ b/tests/sys/net/stp.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2021 Kristof Provost <kp@FreeBSD.org>
+#
+# 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.
+#
+
+import argparse
+import logging
+logging.getLogger("scapy").setLevel(logging.CRITICAL)
+import scapy.all as sp
+import sys
+import os
+curdir = os.path.dirname(os.path.realpath(__file__))
+netpfil_common = curdir + "/../netpfil/common"
+sys.path.append(netpfil_common)
+from sniffer import Sniffer
+
+def check_stp(args, packet):
+ stp = packet.getlayer(sp.STP)
+ if stp is None:
+ return False
+
+ if stp.rootmac != "00:0c:29:01:01:01":
+ return False
+
+ # Ensure we don't get confused by valid STP packets generated by if_bridge
+ if (stp.maxage >= 6 and stp.maxage <= 40) and \
+ (stp.hellotime >= 1 and stp.hellotime <= 2) and \
+ (stp.fwddelay >= 4 and stp.fwddelay <= 30):
+ return False
+
+ print("This packet should have been dropped")
+ print(packet.show())
+ return True
+
+def invalid_stp(send_if):
+ llc = sp.Ether(src="00:0c:29:0b:91:0a", dst="01:80:C2:00:00:00") \
+ / sp.LLC()
+
+ # Bad maxage
+ stp = llc / sp.STP(proto=0, rootid=32768, rootmac="00:0c:29:01:01:01", \
+ bridgeid=32768, bridgemac="00:0c:29:01:01:01", \
+ portid=0x8007, maxage=41, hellotime=2, fwddelay=30)
+ sp.sendp(stp, iface=send_if, verbose=False)
+ stp = llc / sp.STP(proto=0, rootid=32768, rootmac="00:0c:29:01:01:01", \
+ bridgeid=32768, bridgemac="00:0c:29:01:01:01", \
+ portid=0x8007, maxage=5, hellotime=2, fwddelay=30)
+ sp.sendp(stp, iface=send_if, verbose=False)
+
+ # Bad hellotime
+ stp = llc / sp.STP(proto=0, rootid=32768, rootmac="00:0c:29:01:01:01", \
+ bridgeid=32768, bridgemac="00:0c:29:01:01:01", \
+ portid=0x8007, maxage=40, hellotime=3, fwddelay=30)
+ sp.sendp(stp, iface=send_if, verbose=False)
+ stp = llc / sp.STP(proto=0, rootid=32768, rootmac="00:0c:29:01:01:01", \
+ bridgeid=32768, bridgemac="00:0c:29:01:01:01", \
+ portid=0x8007, maxage=40, hellotime=1, fwddelay=30)
+ sp.sendp(stp, iface=send_if, verbose=False)
+
+ # Bad fwddelay
+ stp = llc / sp.STP(proto=0, rootid=32768, rootmac="00:0c:29:01:01:01", \
+ bridgeid=32768, bridgemac="00:0c:29:01:01:01", \
+ portid=0x8007, maxage=40, hellotime=2, fwddelay=31)
+ sp.sendp(stp, iface=send_if, verbose=False)
+ stp = llc / sp.STP(proto=0, rootid=32768, rootmac="00:0c:29:01:01:01", \
+ bridgeid=32768, bridgemac="00:0c:29:01:01:01", \
+ portid=0x8007, maxage=40, hellotime=2, fwddelay=3)
+ sp.sendp(stp, iface=send_if, verbose=False)
+
+def main():
+ parser = argparse.ArgumentParser("stp.py",
+ description="STP test tool")
+ parser.add_argument('--sendif', nargs=1,
+ required=True,
+ help='The interface through which the packet(s) will be sent')
+ parser.add_argument('--recvif', nargs=1,
+ help='The interface on which to expect the ICMP echo request')
+
+ args = parser.parse_args()
+
+ sniffer = Sniffer(args, check_stp, args.recvif[0])
+
+ invalid_stp(args.sendif[0])
+
+ sniffer.join()
+
+ # The 'correct' packet is a corrupt STP packet, so it shouldn't turn up.
+ if sniffer.correctPackets:
+ sys.exit(1)
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/sys/net/transient_tuntap.c b/tests/sys/net/transient_tuntap.c
new file mode 100644
index 000000000000..b0cf43064317
--- /dev/null
+++ b/tests/sys/net/transient_tuntap.c
@@ -0,0 +1,54 @@
+/*-
+ * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * This test simply configures the tunnel as transient and exits. By the time
+ * we return, the tunnel should be gone because the last reference disappears.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <net/if_tun.h>
+#include <net/if_tap.h>
+
+#include <assert.h>
+#include <err.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+int
+main(int argc, char *argv[])
+{
+ unsigned long tunreq;
+ const char *tundev;
+ int one = 1, tunfd;
+
+ assert(argc > 1);
+ tundev = argv[1];
+
+ tunfd = open(tundev, O_RDWR);
+ assert(tunfd >= 0);
+
+ /*
+ * These are technically the same request, but we'll use the technically
+ * correct one just in case.
+ */
+ if (strstr(tundev, "tun") != NULL) {
+ tunreq = TUNSTRANSIENT;
+ } else {
+ assert(strstr(tundev, "tap") != NULL);
+ tunreq = TAPSTRANSIENT;
+ }
+
+ if (ioctl(tunfd, tunreq, &one) == -1)
+ err(1, "ioctl");
+
+ /* Final close should destroy the tunnel automagically. */
+ close(tunfd);
+
+ return (0);
+}