aboutsummaryrefslogtreecommitdiff
path: root/tests/sys/common
diff options
context:
space:
mode:
Diffstat (limited to 'tests/sys/common')
-rw-r--r--tests/sys/common/Makefile12
-rwxr-xr-xtests/sys/common/divert.py82
-rwxr-xr-xtests/sys/common/net_receiver.py115
-rwxr-xr-xtests/sys/common/sender.py202
-rw-r--r--tests/sys/common/vnet.subr137
5 files changed, 548 insertions, 0 deletions
diff --git a/tests/sys/common/Makefile b/tests/sys/common/Makefile
new file mode 100644
index 000000000000..269aa9d52f9a
--- /dev/null
+++ b/tests/sys/common/Makefile
@@ -0,0 +1,12 @@
+PACKAGE= tests
+TESTSDIR= ${TESTSBASE}/sys/common
+${PACKAGE}FILES+= vnet.subr
+${PACKAGE}FILES+= divert.py
+${PACKAGE}FILES+= sender.py
+${PACKAGE}FILES+= net_receiver.py
+
+${PACKAGE}FILESMODE_divert.py=0555
+${PACKAGE}FILESMODE_sender.py=0555
+${PACKAGE}FILESMODE_net_receiver.py=0555
+
+.include <bsd.test.mk>
diff --git a/tests/sys/common/divert.py b/tests/sys/common/divert.py
new file mode 100755
index 000000000000..830b61a585a0
--- /dev/null
+++ b/tests/sys/common/divert.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+# -
+# 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.
+#
+#
+
+
+from socket import socket, PF_DIVERT, SOCK_RAW
+import logging
+logging.getLogger("scapy").setLevel(logging.CRITICAL)
+import scapy.all as sc
+import argparse
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description='divert socket tester')
+ parser.add_argument('--dip', type=str, help='destination packet IP')
+ parser.add_argument('--sip', type=str, help='source packet IP')
+ parser.add_argument('--divert_port', type=int, default=6668,
+ help='divert port to use')
+ parser.add_argument('--test_name', type=str, required=True,
+ help='test name to run')
+ return parser.parse_args()
+
+
+def ipdivert_ip_output_remote_success(args):
+ packet = sc.IP(dst=args.dip) / sc.ICMP(type='echo-request')
+ with socket(PF_DIVERT, SOCK_RAW, 0) as s:
+ s.bind(('0.0.0.0', args.divert_port))
+ s.sendto(bytes(packet), ('0.0.0.0', 0))
+
+
+def ipdivert_ip6_output_remote_success(args):
+ packet = sc.IPv6(dst=args.dip) / sc.ICMPv6EchoRequest()
+ with socket(PF_DIVERT, SOCK_RAW, 0) as s:
+ s.bind(('0.0.0.0', args.divert_port))
+ s.sendto(bytes(packet), ('0.0.0.0', 0))
+
+
+def ipdivert_ip_input_local_success(args):
+ """Sends IPv4 packet to OS stack as inbound local packet."""
+ packet = sc.IP(dst=args.dip,src=args.sip) / sc.ICMP(type='echo-request')
+ with socket(PF_DIVERT, SOCK_RAW, 0) as s:
+ s.bind(('0.0.0.0', args.divert_port))
+ s.sendto(bytes(packet), (args.dip, 0))
+
+
+# XXX: IPv6 local divert is currently not supported
+# TODO: add IPv4 ifname output verification
+
+
+def main():
+ args = parse_args()
+ test_ptr = globals()[args.test_name]
+ test_ptr(args)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/sys/common/net_receiver.py b/tests/sys/common/net_receiver.py
new file mode 100755
index 000000000000..63c8a6927fd2
--- /dev/null
+++ b/tests/sys/common/net_receiver.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+# -
+# 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.
+#
+#
+
+
+from functools import partial
+import socket
+import select
+import argparse
+import time
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description='divert socket tester')
+ parser.add_argument('--sip', type=str, default='', help='IP to listen on')
+ parser.add_argument('--family', type=str, help='inet/inet6')
+ parser.add_argument('--ports', type=str, help='packet ports 1,2,3')
+ parser.add_argument('--match_str', type=str, help='match string to use')
+ parser.add_argument('--count', type=int, default=1,
+ help='Number of messages to receive')
+ parser.add_argument('--test_name', type=str, required=True,
+ help='test name to run')
+ return parser.parse_args()
+
+
+def test_listen_tcp(args):
+ if args.family == 'inet6':
+ fam = socket.AF_INET6
+ else:
+ fam = socket.AF_INET
+ sockets = []
+ ports = [int(port) for port in args.ports.split(',')]
+ for port in ports:
+ s = socket.socket(fam, socket.SOCK_STREAM)
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ s.setblocking(0)
+ s.bind((args.sip, port))
+ print('binding on {}:{}'.format(args.sip, port))
+ s.listen(5)
+ sockets.append(s)
+ inputs = sockets
+ count = 0
+ while count < args.count:
+ readable, writable, exceptional = select.select(inputs, [], inputs)
+ for s in readable:
+ (c, address) = s.accept()
+ print('C: {}'.format(address))
+ data = c.recv(9000)
+ if args.match_str and args.match_str.encode('utf-8') != data:
+ raise Exception('Expected "{}" but got "{}"'.format(
+ args.match_str, data.decode('utf-8')))
+ count += 1
+ c.close()
+
+
+def test_listen_udp(args):
+ if args.family == 'inet6':
+ fam = socket.AF_INET6
+ else:
+ fam = socket.AF_INET
+ sockets = []
+ ports = [int(port) for port in args.ports.split(',')]
+ for port in ports:
+ s = socket.socket(fam, socket.SOCK_DGRAM)
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ s.setblocking(0)
+ s.bind((args.sip, port))
+ print('binding on {}:{}'.format(args.sip, port))
+ sockets.append(s)
+ inputs = sockets
+ count = 0
+ while count < args.count:
+ readable, writable, exceptional = select.select(inputs, [], inputs)
+ for s in readable:
+ (data, address) = s.recvfrom(9000)
+ print('C: {}'.format(address))
+ if args.match_str and args.match_str.encode('utf-8') != data:
+ raise Exception('Expected "{}" but got "{}"'.format(
+ args.match_str, data.decode('utf-8')))
+ count += 1
+
+
+def main():
+ args = parse_args()
+ test_ptr = globals()[args.test_name]
+ test_ptr(args)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/sys/common/sender.py b/tests/sys/common/sender.py
new file mode 100755
index 000000000000..b2a64371e8bb
--- /dev/null
+++ b/tests/sys/common/sender.py
@@ -0,0 +1,202 @@
+#!/usr/bin/env python
+# -
+# 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.
+#
+#
+
+
+from functools import partial
+import socket
+import logging
+logging.getLogger("scapy").setLevel(logging.CRITICAL)
+import scapy.all as sc
+import argparse
+import time
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description='divert socket tester')
+ parser.add_argument('--dip', type=str, help='destination packet IP')
+ parser.add_argument('--sip', type=str, help='source packet IP')
+ parser.add_argument('--dmac', type=str, help='packet dst mac')
+ parser.add_argument('--smac', type=str, help='packet src mac')
+ parser.add_argument('--iface', type=str, help='interface to use')
+ parser.add_argument('--test_name', type=str, required=True,
+ help='test name to run')
+ return parser.parse_args()
+
+
+def send_packet(args, pkt):
+ sc.sendp(pkt, iface=args.iface, verbose=False)
+
+
+def is_icmp6_echo_request(pkt):
+ return pkt.type == 0x86DD and pkt.payload.nh == 58 and \
+ pkt.payload.payload.type == 128
+
+
+def check_forwarded_ip_packet(orig_pkt, fwd_pkt):
+ """
+ Checks that forwarded ICMP packet @fwd_ptk is the same as
+ @orig_pkt. Assumes router-on-the-stick forwarding behaviour:
+ * src/dst macs are swapped
+ * TTL is decremented
+ """
+ # Check ether fields
+ assert orig_pkt.src == fwd_pkt.dst
+ assert orig_pkt.dst == fwd_pkt.src
+ assert len(orig_pkt) == len(fwd_pkt)
+ # Check IP
+ fwd_ip = fwd_pkt[sc.IP]
+ orig_ip = orig_pkt[sc.IP]
+ assert orig_ip.src == orig_ip.src
+ assert orig_ip.dst == fwd_ip.dst
+ assert orig_ip.ttl == fwd_ip.ttl + 1
+ # Check ICMP
+ fwd_icmp = fwd_ip[sc.ICMP]
+ orig_icmp = orig_ip[sc.ICMP]
+ assert bytes(orig_ip.payload) == bytes(fwd_ip.payload)
+
+
+def fwd_ip_icmp_fast(args):
+ """
+ Sends ICMP packet via args.iface interface.
+ Receives and checks the forwarded packet.
+ Assumes forwarding router decrements TTL
+ """
+
+ def filter_f(x):
+ return x.src == args.dmac and x.type == 0x0800
+
+ e = sc.Ether(src=args.smac, dst=args.dmac)
+ ip = sc.IP(src=args.sip, dst=args.dip)
+ icmp = sc.ICMP(type='echo-request')
+ pkt = e / ip / icmp
+
+ send_cb = partial(send_packet, args, pkt)
+ packets = sc.sniff(iface=args.iface, started_callback=send_cb,
+ stop_filter=filter_f, lfilter=filter_f, timeout=5)
+ assert len(packets) > 0
+ fwd_pkt = packets[-1]
+ try:
+ check_forwarded_ip_packet(pkt, fwd_pkt)
+ except Exception as e:
+ print('Original packet:')
+ pkt.show()
+ print('Forwarded packet:')
+ fwd_pkt.show()
+ for a_packet in packets:
+ a_packet.summary()
+ raise Exception from e
+
+
+def fwd_ip_icmp_slow(args):
+ """
+ Sends ICMP packet via args.iface interface.
+ Forces slow path processing by introducing IP option.
+ Receives and checks the forwarded packet.
+ Assumes forwarding router decrements TTL
+ """
+
+ def filter_f(x):
+ return x.src == args.dmac and x.type == 0x0800
+
+ e = sc.Ether(src=args.smac, dst=args.dmac)
+ # Add IP option to switch to 'normal' IP processing
+ stream_id = sc.IPOption_Stream_Id(security=0xFFFF)
+ ip = sc.IP(src=args.sip, dst=args.dip,
+ options=[sc.IPOption_Stream_Id(security=0xFFFF)])
+ icmp = sc.ICMP(type='echo-request')
+ pkt = e / ip / icmp
+
+ send_cb = partial(send_packet, args, pkt)
+ packets = sc.sniff(iface=args.iface, started_callback=send_cb,
+ stop_filter=filter_f, lfilter=filter_f, timeout=5)
+ assert len(packets) > 0
+ check_forwarded_ip_packet(pkt, packets[-1])
+
+
+def check_forwarded_ip6_packet(orig_pkt, fwd_pkt):
+ """
+ Checks that forwarded ICMP packet @fwd_ptk is the same as
+ @orig_pkt. Assumes router-on-the-stick forwarding behaviour:
+ * src/dst macs are swapped
+ * TTL is decremented
+ """
+ # Check ether fields
+ assert orig_pkt.src == fwd_pkt.dst
+ assert orig_pkt.dst == fwd_pkt.src
+ assert len(orig_pkt) == len(fwd_pkt)
+ # Check IP
+ fwd_ip = fwd_pkt[sc.IPv6]
+ orig_ip = orig_pkt[sc.IPv6]
+ assert orig_ip.src == orig_ip.src
+ assert orig_ip.dst == fwd_ip.dst
+ assert orig_ip.hlim == fwd_ip.hlim + 1
+ # Check ICMPv6
+ assert bytes(orig_ip.payload) == bytes(fwd_ip.payload)
+
+
+def fwd_ip6_icmp(args):
+ """
+ Sends ICMPv6 packet via args.iface interface.
+ Receives and checks the forwarded packet.
+ Assumes forwarding router decrements TTL
+ """
+
+ def filter_f(x):
+ return x.src == args.dmac and is_icmp6_echo_request(x)
+
+ e = sc.Ether(src=args.smac, dst=args.dmac)
+ ip = sc.IPv6(src=args.sip, dst=args.dip)
+ icmp = sc.ICMPv6EchoRequest()
+ pkt = e / ip / icmp
+
+ send_cb = partial(send_packet, args, pkt)
+ packets = sc.sniff(iface=args.iface, started_callback=send_cb,
+ stop_filter=filter_f, lfilter=filter_f, timeout=5)
+ assert len(packets) > 0
+ fwd_pkt = packets[-1]
+ try:
+ check_forwarded_ip6_packet(pkt, fwd_pkt)
+ except Exception as e:
+ print('Original packet:')
+ pkt.show()
+ print('Forwarded packet:')
+ fwd_pkt.show()
+ for idx, a_packet in enumerate(packets):
+ print('{}: {}'.format(idx, a_packet.summary()))
+ raise Exception from e
+
+
+def main():
+ args = parse_args()
+ test_ptr = globals()[args.test_name]
+ test_ptr(args)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/sys/common/vnet.subr b/tests/sys/common/vnet.subr
new file mode 100644
index 000000000000..0a32e6caf813
--- /dev/null
+++ b/tests/sys/common/vnet.subr
@@ -0,0 +1,137 @@
+# VNET/jail utility functions
+##
+
+list_interface()
+{
+ echo $1 >> created_interfaces.lst
+}
+
+unlist_interface()
+{
+ sed -i "" /^$1\$/d created_interfaces.lst
+}
+
+_vnet_check_req()
+{
+ type=$1
+
+ if kldstat -q -n if_${type}.ko; then
+ return
+ fi
+
+ if ! kldload -n -q if_${type}; then
+ atf_skip "if_${type}.ko is required to run this test."
+ return
+ fi
+}
+
+vnet_init()
+{
+ if [ "`sysctl -i -n kern.features.vimage`" != 1 ]; then
+ atf_skip "This test requires VIMAGE"
+ fi
+
+ # Check if we can create if_epair or if_bridge interfaces.
+ # We may be running in a jail already, unable to load modules.
+ # If so, skip this test because it very likely (but not certainly)
+ # wants at least one of those
+ _vnet_check_req epair
+ _vnet_check_req bridge
+}
+
+vnet_mkepair()
+{
+ ifname=$(ifconfig epair create)
+ # When transmit checksum offloading is enabled, if_epair does not
+ # compute checksums, it just marks packets that this computation still
+ # needs to be done. However, some test cases verify the checksum.
+ # Therefore disable this for IPv4 and IPv6.
+ ifconfig ${ifname} -txcsum -txcsum6
+ list_interface $ifname
+ list_interface ${ifname%a}b
+ echo ${ifname%a}
+}
+
+vnet_init_bridge()
+{
+ if ! kldstat -q -m if_bridge; then
+ atf_skip "This test requires if_bridge"
+ fi
+}
+
+vnet_mkbridge()
+{
+ ifname=$(ifconfig bridge create)
+ list_interface $ifname
+ echo ${ifname}
+}
+
+vnet_mkvlan()
+{
+ ifname=$(ifconfig vlan create)
+ list_interface $ifname
+ echo ${ifname}
+}
+
+vnet_mkloopback()
+{
+ ifname=$(ifconfig lo create)
+ list_interface $ifname
+ echo ${ifname}
+}
+
+vnet_mkjail()
+{
+ jailname=$1
+ shift
+
+ vnet_interfaces=
+ for ifname in $@
+ do
+ vnet_interfaces="${vnet_interfaces} vnet.interface=${ifname}"
+ unlist_interface $ifname
+ done
+ jail -c name=${jailname} persist vnet ${vnet_interfaces}
+
+ echo $jailname $@ >> created_jails.lst
+}
+
+vnet_ifmove()
+{
+ ifname=$1
+ jailname=$2
+
+ ifconfig ${ifname} vnet ${jailname}
+ unlist_interface $ifname
+ sed -i "" "/^${jailname}/s/\$/ ${ifname}/" created_jails.lst
+}
+
+vnet_ifrename_jail()
+{
+ jailname=$1
+ ifname=$2
+ ifnewname=$3
+
+ ifconfig -j ${jailname} $ifname name $ifnewname
+ sed -i "" "/^${jailname}/s/${ifname}/${ifnewname}/" created_jails.lst
+}
+
+vnet_cleanup()
+{
+ if [ -f created_jails.lst ]; then
+ while read jailname ifnames; do
+ for ifname in ${ifnames}; do
+ ifconfig -j ${jailname} ${ifname} destroy
+ done
+ jail -r ${jailname}
+ done < created_jails.lst
+ rm created_jails.lst
+ fi
+
+ if [ -f created_interfaces.lst ]; then
+ while read ifname; do
+ ifconfig ${ifname} destroy
+ done < created_interfaces.lst
+ rm created_interfaces.lst
+ fi
+}