diff --git a/tests/atf_python/sys/net/tools.py b/tests/atf_python/sys/net/tools.py --- a/tests/atf_python/sys/net/tools.py +++ b/tests/atf_python/sys/net/tools.py @@ -30,13 +30,15 @@ def set_sysctl(cls, oid, val): cls.get_output("sysctl {}={}".format(oid, val)) + @classmethod + def get_netstat_raw(cls, params: str): + out = cls.get_output("{} {} --libxo json".format(cls.NETSTAT_PATH, params)) + return json.loads(out) + @classmethod def get_routes(cls, family: str, fibnum: int = 0): family_key = {"inet": "-4", "inet6": "-6"}.get(family) - out = cls.get_output( - "{} {} -rnW -F {} --libxo json".format(cls.NETSTAT_PATH, family_key, fibnum) - ) - js = json.loads(out) + js = cls.get_netstat_raw("{} -rnW -F {}".format(family_key, fibnum)) js = js["statistics"]["route-information"]["route-table"]["rt-family"] if js: return js[0]["rt-entry"] @@ -46,10 +48,7 @@ @classmethod def get_nhops(cls, family: str, fibnum: int = 0): family_key = {"inet": "-4", "inet6": "-6"}.get(family) - out = cls.get_output( - "{} {} -onW -F {} --libxo json".format(cls.NETSTAT_PATH, family_key, fibnum) - ) - js = json.loads(out) + js = cls.get_netstat_raw("{} -onW -F {}".format(family_key, fibnum)) js = js["statistics"]["route-nhop-information"]["nhop-table"]["rt-family"] if js: return js[0]["nh-entry"] diff --git a/tests/atf_python/sys/net/vnet.py b/tests/atf_python/sys/net/vnet.py --- a/tests/atf_python/sys/net/vnet.py +++ b/tests/atf_python/sys/net/vnet.py @@ -69,11 +69,29 @@ d = self.addr_map["inet6"] return d[next(iter(d))] + @property + def first_ipv6_ll(self): + out = self.run_cmd("ifconfig {} inet6".format(self.name)) + addrs = [l.strip() for l in out.splitlines() if "inet6" in l] + for addr in addrs: + parts = addr.split() + addr = parts[1] + plen = parts[3] + if addr.startswith("fe80"): + addr = addr.split("%")[0] + ll_addr = ipaddress.ip_interface("{}%{}".format(addr, plen)) + return ll_addr + @property def first_ipv4(self): d = self.addr_map["inet"] return d[next(iter(d))] + @property + def mac(self): + out = self.run_cmd("ifconfig {} ether".format(self.name)) + return [l.strip() for l in out.splitlines() if "ether" in l][0].split()[1] + def set_vnet(self, vnet_name: str): self.vnet_name = vnet_name diff --git a/tests/sys/common/Makefile b/tests/sys/common/Makefile --- a/tests/sys/common/Makefile +++ b/tests/sys/common/Makefile @@ -4,7 +4,6 @@ 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 diff --git a/tests/sys/common/sender.py b/tests/sys/common/sender.py deleted file mode 100755 --- a/tests/sys/common/sender.py +++ /dev/null @@ -1,203 +0,0 @@ -#!/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. -# -# $FreeBSD$ -# - - -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/netinet/Makefile b/tests/sys/netinet/Makefile --- a/tests/sys/netinet/Makefile +++ b/tests/sys/netinet/Makefile @@ -7,6 +7,7 @@ TESTS_SUBDIRS+= libalias +ATF_TESTS_PYTEST=test_ip_forward.py ATF_TESTS_C= ip_reass_test \ so_reuseport_lb_test \ socket_afinet \ diff --git a/tests/sys/netinet/forward.sh b/tests/sys/netinet/forward.sh deleted file mode 100755 --- a/tests/sys/netinet/forward.sh +++ /dev/null @@ -1,273 +0,0 @@ -#!/usr/bin/env atf-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. -# -# $FreeBSD$ -# - -. $(atf_get_srcdir)/../common/vnet.subr - -atf_test_case "fwd_ip_icmp_iface_fast_success" "cleanup" -fwd_ip_icmp_iface_fast_success_head() { - - atf_set descr 'Test valid IPv4 on-stick fastforwarding to iface' - atf_set require.user root - atf_set require.progs scapy -} - -fwd_ip_icmp_iface_fast_success_body() { - - vnet_init - - ip4a="192.0.2.1" - ip4b="192.0.2.2" - plen=29 - src_ip="192.0.2.3" - - script_name="../common/sender.py" - - epair=$(vnet_mkepair) - ifconfig ${epair}a up - ifconfig ${epair}a inet ${ip4a}/${plen} - - jname="v4t-fwd_ip_icmp_iface_fast_success" - vnet_mkjail ${jname} ${epair}b - jexec ${jname} ifconfig ${epair}b up - jexec ${jname} ifconfig ${epair}b inet ${ip4b}/${plen} - - # Get router ip/mac - jail_ip=${ip4b} - jail_mac=`jexec ${jname} ifconfig ${epair}b ether | awk '$1~/ether/{print$2}'` - - our_mac=`ifconfig ${epair}a ether | awk '$1~/ether/{print$2}'` - - jexec ${jname} sysctl net.inet.ip.forwarding=1 - # As we're doing router-on-the-stick, turn sending IP redirects off: - jexec ${jname} sysctl net.inet.ip.redirect=0 - - # echo "LOCAL: ${local_ip} ${local_mac}" - # echo "REMOTE: ${remote_rtr_ip} ${remote_rtr_mac}" - - atf_check -s exit:0 $(atf_get_srcdir)/${script_name} \ - --test_name fwd_ip_icmp_fast \ - --smac ${our_mac} --dmac ${jail_mac} \ - --sip ${src_ip} --dip ${ip4a} \ - --iface ${epair}a - - # check counters are valid - atf_check -o match:'1 packet forwarded \(1 packet fast forwarded\)' jexec ${jname} netstat -sp ip -} - -fwd_ip_icmp_iface_fast_success_cleanup() { - - vnet_cleanup -} - -atf_test_case "fwd_ip_icmp_gw_fast_success" "cleanup" -fwd_ip_icmp_gw_fast_success_head() { - - atf_set descr 'Test valid IPv4 on-stick fastforwarding to gw' - atf_set require.user root - atf_set require.progs scapy -} - -fwd_ip_icmp_gw_fast_success_body() { - - vnet_init - - ip4a="192.0.2.1" - ip4b="192.0.2.2" - plen=29 - src_ip="192.0.2.3" - dst_ip="192.0.2.4" - - script_name="../common/sender.py" - - epair=$(vnet_mkepair) - ifconfig ${epair}a up - ifconfig ${epair}a inet ${ip4a}/${plen} - - jname="v4t-fwd_ip_icmp_gw_fast_success" - vnet_mkjail ${jname} ${epair}b - jexec ${jname} ifconfig ${epair}b up - jexec ${jname} ifconfig ${epair}b inet ${ip4b}/${plen} - - # Get router ip/mac - jail_ip=${ip4b} - jail_mac=`jexec ${jname} ifconfig ${epair}b ether | awk '$1~/ether/{print$2}'` - - our_mac=`ifconfig ${epair}a ether | awk '$1~/ether/{print$2}'` - - jexec ${jname} sysctl net.inet.ip.forwarding=1 - # As we're doing router-on-the-stick, turn sending IP redirects off: - jexec ${jname} sysctl net.inet.ip.redirect=0 - - # Add host route - jexec ${jname} route -4 add -host ${dst_ip} ${ip4a} - - # echo "LOCAL: ${local_ip} ${local_mac}" - # echo "REMOTE: ${remote_rtr_ip} ${remote_rtr_mac}" - - atf_check -s exit:0 $(atf_get_srcdir)/${script_name} \ - --test_name fwd_ip_icmp_fast \ - --smac ${our_mac} --dmac ${jail_mac} \ - --sip ${src_ip} --dip ${dst_ip} \ - --iface ${epair}a - - # check counters are valid - atf_check -o match:'1 packet forwarded \(1 packet fast forwarded\)' jexec ${jname} netstat -sp ip -} - -fwd_ip_icmp_gw_fast_success_cleanup() { - - vnet_cleanup -} - -atf_test_case "fwd_ip_icmp_iface_slow_success" "cleanup" -fwd_ip_icmp_iface_slow_success_head() { - - atf_set descr 'Test valid IPv4 on-stick "slow" forwarding to iface' - atf_set require.user root - atf_set require.progs scapy -} - -fwd_ip_icmp_iface_slow_success_body() { - - vnet_init - - ip4a="192.0.2.1" - ip4b="192.0.2.2" - plen=29 - src_ip="192.0.2.3" - - script_name="../common/sender.py" - - epair=$(vnet_mkepair) - ifconfig ${epair}a up - ifconfig ${epair}a inet ${ip4a}/${plen} - - jname="v4t-fwd_ip_icmp_iface_slow_success" - vnet_mkjail ${jname} ${epair}b - jexec ${jname} ifconfig ${epair}b up - jexec ${jname} ifconfig ${epair}b inet ${ip4b}/${plen} - - # Get router ip/mac - jail_ip=${ip4b} - jail_mac=`jexec ${jname} ifconfig ${epair}b ether | awk '$1~/ether/{print$2}'` - - our_mac=`ifconfig ${epair}a ether | awk '$1~/ether/{print$2}'` - - jexec ${jname} sysctl net.inet.ip.forwarding=1 - # As we're doing router-on-the-stick, turn sending IP redirects off: - jexec ${jname} sysctl net.inet.ip.redirect=0 - - # Generate packet with options to force slow-path - atf_check -s exit:0 $(atf_get_srcdir)/${script_name} \ - --test_name fwd_ip_icmp_slow \ - --smac ${our_mac} --dmac ${jail_mac} \ - --sip ${src_ip} --dip ${ip4a} \ - --iface ${epair}a - - # check counters are valid - atf_check -o match:'1 packet forwarded \(0 packets fast forwarded\)' jexec ${jname} netstat -sp ip -} - -fwd_ip_icmp_iface_slow_success_cleanup() { - - vnet_cleanup -} - -atf_test_case "fwd_ip_icmp_gw_slow_success" "cleanup" -fwd_ip_icmp_gw_slow_success_head() { - - atf_set descr 'Test valid IPv4 on-stick "slow" forwarding to gw' - atf_set require.user root - atf_set require.progs scapy -} - -fwd_ip_icmp_gw_slow_success_body() { - - vnet_init - - ip4a="192.0.2.1" - ip4b="192.0.2.2" - plen=29 - src_ip="192.0.2.3" - dst_ip="192.0.2.4" - - script_name="../common/sender.py" - - epair=$(vnet_mkepair) - ifconfig ${epair}a up - ifconfig ${epair}a inet ${ip4a}/${plen} - - jname="v4t-fwd_ip_icmp_gw_slow_success" - vnet_mkjail ${jname} ${epair}b - jexec ${jname} ifconfig ${epair}b up - jexec ${jname} ifconfig ${epair}b inet ${ip4b}/${plen} - - # Get router ip/mac - jail_ip=${ip4b} - jail_mac=`jexec ${jname} ifconfig ${epair}b ether | awk '$1~/ether/{print$2}'` - - our_mac=`ifconfig ${epair}a ether | awk '$1~/ether/{print$2}'` - - jexec ${jname} sysctl net.inet.ip.forwarding=1 - # As we're doing router-on-the-stick, turn sending IP redirects off: - jexec ${jname} sysctl net.inet.ip.redirect=0 - - # Add host route - jexec ${jname} route -4 add -host ${dst_ip} ${ip4a} - - # echo "LOCAL: ${local_ip} ${local_mac}" - # echo "REMOTE: ${remote_rtr_ip} ${remote_rtr_mac}" - - atf_check -s exit:0 $(atf_get_srcdir)/${script_name} \ - --test_name fwd_ip_icmp_fast \ - --smac ${our_mac} --dmac ${jail_mac} \ - --sip ${src_ip} --dip ${dst_ip} \ - --iface ${epair}a - - # check counters are valid - atf_check -o match:'1 packet forwarded \(1 packet fast forwarded\)' jexec ${jname} netstat -sp ip -} - -fwd_ip_icmp_gw_slow_success_cleanup() { - - vnet_cleanup -} - -atf_init_test_cases() -{ - - atf_add_test_case "fwd_ip_icmp_iface_fast_success" - atf_add_test_case "fwd_ip_icmp_gw_fast_success" - atf_add_test_case "fwd_ip_icmp_iface_slow_success" - atf_add_test_case "fwd_ip_icmp_gw_slow_success" -} - -# end - diff --git a/tests/sys/netinet/test_ip_forward.py b/tests/sys/netinet/test_ip_forward.py new file mode 100644 --- /dev/null +++ b/tests/sys/netinet/test_ip_forward.py @@ -0,0 +1,139 @@ +from functools import partial + +import pytest +from atf_python.sys.net.tools import ToolsHelper +from atf_python.sys.net.vnet import VnetTestTemplate + +sc = None + + +class TestIPForward(VnetTestTemplate): + TOPOLOGY = { + "vnet1": {"ifaces": ["if1"]}, + "vnet2": {"ifaces": ["if1"]}, + "if1": {"prefixes4": [("192.0.2.1/24", "192.0.2.2/24")]}, + } + + def setup_method(self, method): + global sc + if sc is None: + import scapy.all as _sc + + sc = _sc + super().setup_method(method) + + def enable_forwarding(self): + ToolsHelper.set_sysctl("net.inet.ip.forwarding", 1) + + def disable_redirects(self): + ToolsHelper.set_sysctl("net.inet.ip.redirect", 0) + + def _send_frame(self, iface_name, pkt): + sc.sendp(pkt, iface=iface_name, verbose=False) + + @classmethod + def _check_forwarded_ip_packet(cls, 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) + + @classmethod + def check_forwarded_ip_packets(cls, orig_pkt, fwd_packets): + assert len(fwd_packets) > 0 + fwd_pkt = fwd_packets[-1] + try: + cls._check_forwarded_ip_packet(orig_pkt, fwd_pkt) + except Exception: + print("Original packet:") + orig_pkt.show() + print("Forwarded packet:") + fwd_pkt.show() + for idx, a_packet in enumerate(fwd_packets): + print("{}: {}".format(idx, a_packet.summary())) + raise + + def vnet2_handler(self, vnet): + self.enable_forwarding() + + ip_type, gw_addr, fwd_type = self.wait_object(vnet.pipe) + + if fwd_type == "fast": + # As we're doing router-on-the-stick, turn sending IP redirects off + # to enable the fast path + self.disable_redirects() + + if ip_type != "if": + gw = str(gw_addr.ip) + ToolsHelper.print_output("route add -4 -host 192.0.2.4 {}".format(gw)) + + # Send our MAC so the other jail can form a packet + self.send_object(vnet.pipe, vnet.iface_alias_map["if1"].mac) + # Wait for the request to send statistics + self.wait_object(vnet.pipe) + js = ToolsHelper.get_netstat_raw("-sp ip") + self.send_object(vnet.pipe, js) + + @pytest.mark.parametrize("fwd_type", ["fast", "slow"]) + @pytest.mark.parametrize("ip_type", ["if", "gw"]) + def test_success(self, ip_type, fwd_type): + "Test valid IPv4 global unicast fast-forwarding to IP/interface gw" + second_vnet = self.vnet_map["vnet2"] + iface = self.vnet.iface_alias_map["if1"] + + if ip_type == "gw": + gw = iface.first_ipv4 + target_ipv4 = "192.0.2.4" + elif ip_type == "if": + gw = "" + target_ipv4 = str(iface.first_ipv4.ip) + + # Send the desired GW and forwarding type + self.send_object(second_vnet.pipe, (ip_type, gw, fwd_type)) + # Wait till the second vnet inits & provide us with its mac + dst_mac = self.wait_object(second_vnet.pipe) + + # Prepare the packet to output to the forwarding vnet + e = sc.Ether(src=iface.mac, dst=dst_mac) + if fwd_type == "slow": + # Add IP option to switch to 'normal' IP processing + ip_opts = [sc.IPOption_Stream_Id(security=0xFFFF)] + ip = sc.IP(src=str(iface.first_ipv4.ip), dst=target_ipv4, options=ip_opts) + else: + ip = sc.IP(src=str(iface.first_ipv4.ip), dst=target_ipv4) + icmp = sc.ICMP(type="echo-request") + pkt = e / ip / icmp + send_cb = partial(self._send_frame, iface.name, pkt) + + def filter_f(x): + return x.src == dst_mac and x.type == 0x0800 + + fwd_packets = sc.sniff( + iface=iface.name, + started_callback=send_cb, + stop_filter=filter_f, + lfilter=filter_f, + timeout=5, + ) + self.check_forwarded_ip_packets(pkt, fwd_packets) + + # Finally, check if the forwarder has properly recorded stats + self.send_object(second_vnet.pipe, "") + js = self.wait_object(second_vnet.pipe) + assert js["statistics"]["ip"]["forwarded-packets"] == 1 diff --git a/tests/sys/netinet6/Makefile b/tests/sys/netinet6/Makefile --- a/tests/sys/netinet6/Makefile +++ b/tests/sys/netinet6/Makefile @@ -6,6 +6,7 @@ FILESDIR= ${TESTSDIR} ATF_TESTS_PYTEST= test_ip6_output.py +ATF_TESTS_PYTEST+= test_ip6_forward.py ATF_TESTS_SH= \ exthdr \ mld \ diff --git a/tests/sys/netinet6/forward6.sh b/tests/sys/netinet6/forward6.sh deleted file mode 100755 --- a/tests/sys/netinet6/forward6.sh +++ /dev/null @@ -1,482 +0,0 @@ -#!/usr/bin/env atf-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. -# -# $FreeBSD$ -# - -. $(atf_get_srcdir)/../common/vnet.subr - -atf_test_case "fwd_ip6_gu_icmp_iface_fast_success" "cleanup" -fwd_ip6_gu_icmp_iface_fast_success_head() { - - atf_set descr 'Test valid IPv6 global unicast fast-forwarding to interface' - atf_set require.user root - atf_set require.progs scapy -} - -fwd_ip6_gu_icmp_iface_fast_success_body() { - - ids=65529 - id=`printf "%x" ${ids}` - if [ $$ -gt 65535 ]; then - xl=`printf "%x" $(($$ - 65535))` - yl="1" - else - xl=`printf "%x" $$` - yl="" - fi - - vnet_init - - ip6a="2001:db8:6666:0000:${yl}:${id}:1:${xl}" - ip6b="2001:db8:6666:0000:${yl}:${id}:2:${xl}" - plen=96 - - src_ip="2001:db8:6666:0000:${yl}:${id}:3:${xl}" - - script_name="../common/sender.py" - - epair=$(vnet_mkepair) - ifconfig ${epair}a up - ifconfig ${epair}a inet6 ${ip6a}/${plen} - - jname="v6t-${id}-${yl}-${xl}" - vnet_mkjail ${jname} ${epair}b - jexec ${jname} ifconfig ${epair}b up - jexec ${jname} ifconfig ${epair}b inet6 ${ip6b}/${plen} - - jail_mac=`jexec ${jname} ifconfig ${epair}b ether | awk '$1~/ether/{print$2}'` - - our_mac=`ifconfig ${epair}a ether | awk '$1~/ether/{print$2}'` - - # wait for DAD to complete - while [ `jexec ${jname} ifconfig ${epair}b inet6 | grep -c tentative` != "0" ]; do - sleep 0.1 - done - while [ `ifconfig ${epair}a inet6 | grep -c tentative` != "0" ]; do - sleep 0.1 - done - - jexec ${jname} sysctl net.inet6.ip6.forwarding=1 - # As we're doing router-on-the-stick, turn sending IP redirects off: - jexec ${jname} sysctl net.inet6.ip6.redirect=0 - - atf_check -s exit:0 $(atf_get_srcdir)/${script_name} \ - --test_name fwd_ip6_icmp \ - --smac ${our_mac} --dmac ${jail_mac} \ - --sip ${src_ip} --dip ${ip6a} \ - --iface ${epair}a - - # check counters are valid - atf_check -o match:'1 packet forwarded' jexec ${jname} netstat -sp ip6 -} - -fwd_ip6_gu_icmp_iface_fast_success_cleanup() { - - vnet_cleanup -} - -atf_test_case "fwd_ip6_gu_icmp_gw_gu_fast_success" "cleanup" -fwd_ip6_gu_icmp_gw_gu_fast_success_head() { - - atf_set descr 'Test valid IPv6 global unicast fast-forwarding to GU gw' - atf_set require.user root - atf_set require.progs scapy -} - -fwd_ip6_gu_icmp_gw_gu_fast_success_body() { - - ids=65528 - id=`printf "%x" ${ids}` - if [ $$ -gt 65535 ]; then - xl=`printf "%x" $(($$ - 65535))` - yl="1" - else - xl=`printf "%x" $$` - yl="" - fi - - vnet_init - - ip6a="2001:db8:6666:0000:${yl}:${id}:1:${xl}" - ip6b="2001:db8:6666:0000:${yl}:${id}:2:${xl}" - plen=96 - - src_ip="2001:db8:6666:0000:${yl}:${id}:3:${xl}" - dst_ip="2001:db8:6666:0000:${yl}:${id}:4:${xl}" - - script_name="../common/sender.py" - - epair=$(vnet_mkepair) - ifconfig ${epair}a up - ifconfig ${epair}a inet6 ${ip6a}/${plen} - - jname="v6t-${id}-${yl}-${xl}" - vnet_mkjail ${jname} ${epair}b - jexec ${jname} ifconfig ${epair}b up - jexec ${jname} ifconfig ${epair}b inet6 ${ip6b}/${plen} - - jail_mac=`jexec ${jname} ifconfig ${epair}b ether | awk '$1~/ether/{print$2}'` - - our_mac=`ifconfig ${epair}a ether | awk '$1~/ether/{print$2}'` - - # wait for DAD to complete - while [ `jexec ${jname} ifconfig ${epair}b inet6 | grep -c tentative` != "0" ]; do - sleep 0.1 - done - while [ `ifconfig ${epair}a inet6 | grep -c tentative` != "0" ]; do - sleep 0.1 - done - - # Add static route back to us - jexec ${jname} route add -6 -host ${dst_ip} ${ip6a} - - jexec ${jname} sysctl net.inet6.ip6.forwarding=1 - # As we're doing router-on-the-stick, turn sending IP redirects off: - jexec ${jname} sysctl net.inet6.ip6.redirect=0 - - atf_check -s exit:0 $(atf_get_srcdir)/${script_name} \ - --test_name fwd_ip6_icmp \ - --smac ${our_mac} --dmac ${jail_mac} \ - --sip ${src_ip} --dip ${dst_ip} \ - --iface ${epair}a - - # check counters are valid - atf_check -o match:'1 packet forwarded' jexec ${jname} netstat -sp ip6 -} - -fwd_ip6_gu_icmp_gw_gu_fast_success_cleanup() { - - vnet_cleanup -} - -atf_test_case "fwd_ip6_gu_icmp_gw_ll_fast_success" "cleanup" -fwd_ip6_gu_icmp_gw_ll_fast_success_head() { - - atf_set descr 'Test valid IPv6 global unicast fast-forwarding to LL gw' - atf_set require.user root - atf_set require.progs scapy -} - -fwd_ip6_gu_icmp_gw_ll_fast_success_body() { - - ids=65527 - id=`printf "%x" ${ids}` - if [ $$ -gt 65535 ]; then - xl=`printf "%x" $(($$ - 65535))` - yl="1" - else - xl=`printf "%x" $$` - yl="" - fi - - vnet_init - - ip6a="2001:db8:6666:0000:${yl}:${id}:1:${xl}" - ip6b="2001:db8:6666:0000:${yl}:${id}:2:${xl}" - plen=96 - - src_ip="2001:db8:6666:0000:${yl}:${id}:3:${xl}" - dst_ip="2001:db8:6666:0000:${yl}:${id}:4:${xl}" - - script_name="../common/sender.py" - - epair=$(vnet_mkepair) - ifconfig ${epair}a up - ifconfig ${epair}a inet6 ${ip6a}/${plen} - - jname="v6t-${id}-${yl}-${xl}" - vnet_mkjail ${jname} ${epair}b - jexec ${jname} ifconfig ${epair}b up - jexec ${jname} ifconfig ${epair}b inet6 ${ip6b}/${plen} - - jail_mac=`jexec ${jname} ifconfig ${epair}b ether | awk '$1~/ether/{print$2}'` - - our_mac=`ifconfig ${epair}a ether | awk '$1~/ether/{print$2}'` - our_ll_ip=`ifconfig ${epair}a inet6 | awk '$1~/inet6/&& $2~/^fe80:/{print$2}' | awk -F% '{print$1}'` - - # wait for DAD to complete - while [ `jexec ${jname} ifconfig ${epair}b inet6 | grep -c tentative` != "0" ]; do - sleep 0.1 - done - while [ `ifconfig ${epair}a inet6 | grep -c tentative` != "0" ]; do - sleep 0.1 - done - - # Add static route back to us - atf_check -s exit:0 -o ignore jexec ${jname} route add -6 -host ${dst_ip} ${our_ll_ip}%${epair}b - - jexec ${jname} sysctl net.inet6.ip6.forwarding=1 - # As we're doing router-on-the-stick, turn sending IP redirects off: - jexec ${jname} sysctl net.inet6.ip6.redirect=0 - - atf_check -s exit:0 $(atf_get_srcdir)/${script_name} \ - --test_name fwd_ip6_icmp \ - --smac ${our_mac} --dmac ${jail_mac} \ - --sip ${src_ip} --dip ${dst_ip} \ - --iface ${epair}a - - # check counters are valid - atf_check -o match:'1 packet forwarded' jexec ${jname} netstat -sp ip6 -} - -fwd_ip6_gu_icmp_gw_ll_fast_success_cleanup() { - - vnet_cleanup -} - -atf_test_case "fwd_ip6_gu_icmp_iface_slow_success" "cleanup" -fwd_ip6_gu_icmp_iface_slow_success_head() { - - atf_set descr 'Test valid IPv6 global unicast fast-forwarding to interface' - atf_set require.user root - atf_set require.progs scapy -} - -fwd_ip6_gu_icmp_iface_slow_success_body() { - - ids=65526 - id=`printf "%x" ${ids}` - if [ $$ -gt 65535 ]; then - xl=`printf "%x" $(($$ - 65535))` - yl="1" - else - xl=`printf "%x" $$` - yl="" - fi - - vnet_init - - ip6a="2001:db8:6666:0000:${yl}:${id}:1:${xl}" - ip6b="2001:db8:6666:0000:${yl}:${id}:2:${xl}" - plen=96 - - src_ip="2001:db8:6666:0000:${yl}:${id}:3:${xl}" - - script_name="../common/sender.py" - - epair=$(vnet_mkepair) - ifconfig ${epair}a up - ifconfig ${epair}a inet6 ${ip6a}/${plen} - - jname="v6t-${id}-${yl}-${xl}" - vnet_mkjail ${jname} ${epair}b - jexec ${jname} ifconfig ${epair}b up - jexec ${jname} ifconfig ${epair}b inet6 ${ip6b}/${plen} - - jail_mac=`jexec ${jname} ifconfig ${epair}b ether | awk '$1~/ether/{print$2}'` - - our_mac=`ifconfig ${epair}a ether | awk '$1~/ether/{print$2}'` - - # wait for DAD to complete - while [ `jexec ${jname} ifconfig ${epair}b inet6 | grep -c tentative` != "0" ]; do - sleep 0.1 - done - while [ `ifconfig ${epair}a inet6 | grep -c tentative` != "0" ]; do - sleep 0.1 - done - - jexec ${jname} sysctl net.inet6.ip6.forwarding=1 - # Do not turn off route redirects to ensure slow path is on - - atf_check -s exit:0 $(atf_get_srcdir)/${script_name} \ - --test_name fwd_ip6_icmp \ - --smac ${our_mac} --dmac ${jail_mac} \ - --sip ${src_ip} --dip ${ip6a} \ - --iface ${epair}a - - # check counters are valid - atf_check -o match:'1 packet forwarded' jexec ${jname} netstat -sp ip6 -} - -fwd_ip6_gu_icmp_iface_slow_success_cleanup() { - - vnet_cleanup -} - -atf_test_case "fwd_ip6_gu_icmp_gw_gu_slow_success" "cleanup" -fwd_ip6_gu_icmp_gw_gu_slow_success_head() { - - atf_set descr 'Test valid IPv6 global unicast fast-forwarding to GU gw' - atf_set require.user root - atf_set require.progs scapy -} - -fwd_ip6_gu_icmp_gw_gu_slow_success_body() { - - ids=65525 - id=`printf "%x" ${ids}` - if [ $$ -gt 65535 ]; then - xl=`printf "%x" $(($$ - 65535))` - yl="1" - else - xl=`printf "%x" $$` - yl="" - fi - - vnet_init - - ip6a="2001:db8:6666:0000:${yl}:${id}:1:${xl}" - ip6b="2001:db8:6666:0000:${yl}:${id}:2:${xl}" - plen=96 - - src_ip="2001:db8:6666:0000:${yl}:${id}:3:${xl}" - dst_ip="2001:db8:6666:0000:${yl}:${id}:4:${xl}" - - script_name="../common/sender.py" - - epair=$(vnet_mkepair) - ifconfig ${epair}a up - ifconfig ${epair}a inet6 ${ip6a}/${plen} - - jname="v6t-${id}-${yl}-${xl}" - vnet_mkjail ${jname} ${epair}b - jexec ${jname} ifconfig ${epair}b up - jexec ${jname} ifconfig ${epair}b inet6 ${ip6b}/${plen} - - jail_mac=`jexec ${jname} ifconfig ${epair}b ether | awk '$1~/ether/{print$2}'` - - our_mac=`ifconfig ${epair}a ether | awk '$1~/ether/{print$2}'` - - # wait for DAD to complete - while [ `jexec ${jname} ifconfig ${epair}b inet6 | grep -c tentative` != "0" ]; do - sleep 0.1 - done - while [ `ifconfig ${epair}a inet6 | grep -c tentative` != "0" ]; do - sleep 0.1 - done - - # Add static route back to us - jexec ${jname} route add -6 -host ${dst_ip} ${ip6a} - - jexec ${jname} sysctl net.inet6.ip6.forwarding=1 - # Do not turn off route redirects to ensure slow path is on - - # atf_check -s exit:0 - $(atf_get_srcdir)/${script_name} \ - --test_name fwd_ip6_icmp \ - --smac ${our_mac} --dmac ${jail_mac} \ - --sip ${src_ip} --dip ${dst_ip} \ - --iface ${epair}a - jexec ${jname} netstat -sp ip6 - - # check counters are valid - atf_check -o match:'1 packet forwarded' jexec ${jname} netstat -sp ip6 -} - -fwd_ip6_gu_icmp_gw_gu_slow_success_cleanup() { - - vnet_cleanup -} - -atf_test_case "fwd_ip6_gu_icmp_gw_ll_slow_success" "cleanup" -fwd_ip6_gu_icmp_gw_ll_slow_success_head() { - - atf_set descr 'Test valid IPv6 global unicast fast-forwarding to LL gw' - atf_set require.user root - atf_set require.progs scapy -} - -fwd_ip6_gu_icmp_gw_ll_slow_success_body() { - - ids=65524 - id=`printf "%x" ${ids}` - if [ $$ -gt 65535 ]; then - xl=`printf "%x" $(($$ - 65535))` - yl="1" - else - xl=`printf "%x" $$` - yl="" - fi - - vnet_init - - ip6a="2001:db8:6666:0000:${yl}:${id}:1:${xl}" - ip6b="2001:db8:6666:0000:${yl}:${id}:2:${xl}" - plen=96 - - src_ip="2001:db8:6666:0000:${yl}:${id}:3:${xl}" - dst_ip="2001:db8:6666:0000:${yl}:${id}:4:${xl}" - - script_name="../common/sender.py" - - epair=$(vnet_mkepair) - ifconfig ${epair}a up - ifconfig ${epair}a inet6 ${ip6a}/${plen} - - jname="v6t-${id}-${yl}-${xl}" - vnet_mkjail ${jname} ${epair}b - jexec ${jname} ifconfig ${epair}b up - jexec ${jname} ifconfig ${epair}b inet6 ${ip6b}/${plen} - - jail_mac=`jexec ${jname} ifconfig ${epair}b ether | awk '$1~/ether/{print$2}'` - - our_mac=`ifconfig ${epair}a ether | awk '$1~/ether/{print$2}'` - our_ll_ip=`ifconfig ${epair}a inet6 | awk '$1~/inet6/&& $2~/^fe80:/{print$2}' | awk -F% '{print$1}'` - - # wait for DAD to complete - while [ `jexec ${jname} ifconfig ${epair}b inet6 | grep -c tentative` != "0" ]; do - sleep 0.1 - done - while [ `ifconfig ${epair}a inet6 | grep -c tentative` != "0" ]; do - sleep 0.1 - done - - # Add static route back to us - atf_check -s exit:0 -o ignore jexec ${jname} route add -6 -host ${dst_ip} ${our_ll_ip}%${epair}b - - jexec ${jname} sysctl net.inet6.ip6.forwarding=1 - # Do not turn off route redirects to ensure slow path is on - - atf_check -s exit:0 $(atf_get_srcdir)/${script_name} \ - --test_name fwd_ip6_icmp \ - --smac ${our_mac} --dmac ${jail_mac} \ - --sip ${src_ip} --dip ${dst_ip} \ - --iface ${epair}a - - # check counters are valid - atf_check -o match:'1 packet forwarded' jexec ${jname} netstat -sp ip6 -} - -fwd_ip6_gu_icmp_gw_ll_slow_success_cleanup() { - - vnet_cleanup -} - -atf_init_test_cases() -{ - - atf_add_test_case "fwd_ip6_gu_icmp_iface_fast_success" - atf_add_test_case "fwd_ip6_gu_icmp_gw_gu_fast_success" - atf_add_test_case "fwd_ip6_gu_icmp_gw_ll_fast_success" - atf_add_test_case "fwd_ip6_gu_icmp_iface_slow_success" - atf_add_test_case "fwd_ip6_gu_icmp_gw_gu_slow_success" - atf_add_test_case "fwd_ip6_gu_icmp_gw_ll_slow_success" -} - -# end - diff --git a/tests/sys/netinet6/test_ip6_forward.py b/tests/sys/netinet6/test_ip6_forward.py new file mode 100644 --- /dev/null +++ b/tests/sys/netinet6/test_ip6_forward.py @@ -0,0 +1,138 @@ +from functools import partial + +import pytest +import scapy.all as sc +from atf_python.sys.net.tools import ToolsHelper +from atf_python.sys.net.vnet import VnetTestTemplate + + +def is_icmp6_echo_request(pkt): + return ( + pkt.type == 0x86DD and pkt.payload.nh == 58 and pkt.payload.payload.type == 128 + ) + + +class TestIP6Forward(VnetTestTemplate): + TOPOLOGY = { + "vnet1": {"ifaces": ["if1", "if2"]}, + "vnet2": {"ifaces": ["if1", "if2"]}, + "if1": {"prefixes6": [("2001:db8:a::1/64", "2001:db8:a::2/64")]}, + "if2": {"prefixes6": [("2001:db8:b::1/64", "2001:db8:b::2/64")]}, + } + + def enable_forwarding(self): + ToolsHelper.set_sysctl("net.inet6.ip6.forwarding", 1) + + def disable_redirects(self): + ToolsHelper.set_sysctl("net.inet6.ip6.redirect", 0) + + def _send_frame(self, iface_name, pkt): + sc.sendp(pkt, iface=iface_name, verbose=False) + + @staticmethod + def _check_forwarded_frame(orig_pkt, fwd_pkt): + assert orig_pkt.src == fwd_pkt.dst + assert orig_pkt.dst == fwd_pkt.src + assert len(orig_pkt) == len(fwd_pkt) + + @classmethod + def _check_forwarded_ip6_packet(cls, orig_pkt, fwd_pkt): + """ + Checks that forwarded ICMP packet @fwd_pkt is the same as + @orig_pkt. Assumes router-on-the-stick forwarding behaviour: + * src/dst macs are swapped + * TTL is decremented + """ + # Check ether fields + cls._check_forwarded_frame(orig_pkt, 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) + + @classmethod + def check_forwarded_ip6_packets(cls, orig_pkt, fwd_packets): + assert len(fwd_packets) > 0 + fwd_pkt = fwd_packets[-1] + try: + cls._check_forwarded_ip6_packet(orig_pkt, fwd_pkt) + except Exception: + print("Original packet:") + orig_pkt.show() + print("Forwarded packet:") + fwd_pkt.show() + for idx, a_packet in enumerate(fwd_packets): + print("{}: {}".format(idx, a_packet.summary())) + raise + + def vnet2_handler(self, vnet): + self.enable_forwarding() + + ip_type, gw_addr, fwd_type = self.wait_object(vnet.pipe) + + if fwd_type == "fast": + # As we're doing router-on-the-stick, turn sending IP redirects off + # to enable the fast path + self.disable_redirects() + + if ip_type != "if": + gw = str(gw_addr.ip) + if gw_addr.is_link_local: + gw = "{}%{}".format(gw, vnet.iface_alias_map["if1"].name) + ToolsHelper.print_output("route add -6 -net 2001:db8:f::/64 {}".format(gw)) + + # Send our MAC so the other jail can form a packet + self.send_object(vnet.pipe, vnet.iface_alias_map["if1"].mac) + # Wait for the request to send statistics + self.wait_object(vnet.pipe) + js = ToolsHelper.get_netstat_raw("-sp ip6") + self.send_object(vnet.pipe, js) + + @pytest.mark.parametrize("fwd_type", ["fast", "slow"]) + @pytest.mark.parametrize("ip_type", ["if", "gu", "ll"]) + def test_success(self, ip_type, fwd_type): + "Test valid IPv6 global unicast fast-forwarding to GU/LL/interface gw" + second_vnet = self.vnet_map["vnet2"] + iface = self.vnet.iface_alias_map["if1"] + + target_ipv6 = "2001:db8:f::1" + if ip_type == "gu": + gw = iface.first_ipv6 + elif ip_type == "ll": + gw = iface.first_ipv6_ll + elif ip_type == "if": + gw = "" + target_ipv6 = str(iface.first_ipv6.ip) + + # Send the desired GW and forwarding type + self.send_object(second_vnet.pipe, (ip_type, gw, fwd_type)) + # Wait till the second vnet inits & provide us with its mac + dst_mac = self.wait_object(second_vnet.pipe) + + # Prepare the packet to output to the forwarding vnet + e = sc.Ether(src=iface.mac, dst=dst_mac) + ip = sc.IPv6(src=str(iface.first_ipv6.ip), dst=target_ipv6) + icmp = sc.ICMPv6EchoRequest() + pkt = e / ip / icmp + send_cb = partial(self._send_frame, iface.name, pkt) + + def filter_f(x): + return x.src == dst_mac and is_icmp6_echo_request(x) + + fwd_packets = sc.sniff( + iface=iface.name, + started_callback=send_cb, + stop_filter=filter_f, + lfilter=filter_f, + timeout=5, + ) + self.check_forwarded_ip6_packets(pkt, fwd_packets) + + # Finally, check if the forwarder has properly recorded stats + self.send_object(second_vnet.pipe, "") + js = self.wait_object(second_vnet.pipe) + assert js["statistics"]["ip6"]["forwarded-packets"] == 1