diff --git a/tests/sys/netpfil/pf/nat64.sh b/tests/sys/netpfil/pf/nat64.sh index da95a7bf9893..f92a69f2abce 100644 --- a/tests/sys/netpfil/pf/nat64.sh +++ b/tests/sys/netpfil/pf/nat64.sh @@ -1,1065 +1,1065 @@ # # SPDX-License-Identifier: BSD-2-Clause # # Copyright (c) 2024 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 nat64_setup_base() { pft_init epair_link=$(vnet_mkepair) epair=$(vnet_mkepair) ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad route -6 add default 2001:db8::1 vnet_mkjail rtr ${epair}b ${epair_link}a jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up vnet_mkjail dst ${epair_link}b jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up jexec dst route add default 192.0.2.1 # Sanity checks atf_check -s exit:0 -o ignore \ ping6 -c 1 2001:db8::1 atf_check -s exit:0 -o ignore \ jexec dst ping -c 1 192.0.2.1 jexec rtr pfctl -e } nat64_setup_in() { nat64_setup_base pft_set_rules rtr \ "set reassemble yes" \ "set state-policy if-bound" \ "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)" } nat64_setup_out() { nat64_setup_base jexec rtr sysctl net.inet6.ip6.forwarding=1 # AF translation happens post-routing, traffic must be directed # towards the outbound interface using routes for the original AF. # jexec rtr ifconfig ${epair_link}a inet6 2001:db8:2::1/64 up no_dad jexec rtr route add -inet6 64:ff9b::/96 -iface ${epair_link}a; pft_set_rules rtr \ "set reassemble yes" \ "set state-policy if-bound" \ "pass quick inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ "pass in quick on ${epair}b from any to 64:ff9b::/96" \ "pass out quick on ${epair_link}a from any to 64:ff9b::/96 af-to inet from (${epair_link}a)" \ "block" } atf_test_case "icmp_echo_in" "cleanup" icmp_echo_in_head() { atf_set descr 'Basic NAT64 ICMP echo test on inbound interface' atf_set require.user root } icmp_echo_in_body() { nat64_setup_in # One ping atf_check -s exit:0 -o ignore \ ping6 -c 1 64:ff9b::192.0.2.2 # Make sure packets make it even when state is established atf_check -s exit:0 \ -o match:'5 packets transmitted, 5 packets received, 0.0% packet loss' \ ping6 -c 5 64:ff9b::192.0.2.2 } icmp_echo_in_cleanup() { pft_cleanup } atf_test_case "icmp_echo_out" "cleanup" icmp_echo_out_head() { atf_set descr 'Basic NAT64 ICMP echo test on outbound interface' atf_set require.user root } icmp_echo_out_body() { nat64_setup_out # One ping atf_check -s exit:0 -o ignore \ ping6 -c 1 64:ff9b::192.0.2.2 # Make sure packets make it even when state is established atf_check -s exit:0 \ -o match:'5 packets transmitted, 5 packets received, 0.0% packet loss' \ ping6 -c 5 64:ff9b::192.0.2.2 } icmp_echo_out_cleanup() { pft_cleanup } atf_test_case "fragmentation_in" "cleanup" fragmentation_in_head() { atf_set descr 'Test fragmented packets on inbound interface' atf_set require.user root } fragmentation_in_body() { nat64_setup_in atf_check -s exit:0 -o ignore \ ping6 -c 1 -s 1280 64:ff9b::192.0.2.2 atf_check -s exit:0 \ -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \ ping6 -c 3 -s 2000 64:ff9b::192.0.2.2 atf_check -s exit:0 \ -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \ ping6 -c 3 -s 10000 -b 20000 64:ff9b::192.0.2.2 } fragmentation_in_cleanup() { pft_cleanup } atf_test_case "fragmentation_out" "cleanup" fragmentation_out_head() { atf_set descr 'Test fragmented packets on outbound interface' atf_set require.user root } fragmentation_out_body() { nat64_setup_out atf_check -s exit:0 -o ignore \ ping6 -c 1 -s 1280 64:ff9b::192.0.2.2 atf_check -s exit:0 \ -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \ ping6 -c 3 -s 2000 64:ff9b::192.0.2.2 atf_check -s exit:0 \ -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \ ping6 -c 3 -s 10000 -b 20000 64:ff9b::192.0.2.2 } fragmentation_out_cleanup() { pft_cleanup } atf_test_case "tcp_in" "cleanup" tcp_in_head() { atf_set descr 'TCP NAT64 test on inbound interface' atf_set require.user root } tcp_in_body() { nat64_setup_in echo "foo" | jexec dst nc -l 1234 & # Sanity check & delay for nc startup atf_check -s exit:0 -o ignore \ - ping6 -c 1 64:ff9b::192.0.2.2 + ping6 -c 3 64:ff9b::192.0.2.2 rcv=$(nc -w 3 -6 64:ff9b::c000:202 1234) if [ "${rcv}" != "foo" ]; then echo "rcv=${rcv}" atf_fail "Failed to connect to TCP server" fi } tcp_in_cleanup() { pft_cleanup } atf_test_case "tcp_out" "cleanup" tcp_out_head() { atf_set descr 'TCP NAT64 test on outbound interface' atf_set require.user root } tcp_out_body() { nat64_setup_out echo "foo" | jexec dst nc -l 1234 & # Sanity check & delay for nc startup atf_check -s exit:0 -o ignore \ - ping6 -c 1 64:ff9b::192.0.2.2 + ping6 -c 3 64:ff9b::192.0.2.2 rcv=$(nc -w 3 -6 64:ff9b::c000:202 1234) if [ "${rcv}" != "foo" ]; then echo "rcv=${rcv}" atf_fail "Failed to connect to TCP server" fi } tcp_out_cleanup() { pft_cleanup } atf_test_case "udp_in" "cleanup" udp_in_head() { atf_set descr 'UDP NAT64 test on inbound interface' atf_set require.user root } udp_in_body() { nat64_setup_in echo "foo" | jexec dst nc -u -l 1234 & # Sanity check & delay for nc startup atf_check -s exit:0 -o ignore \ - ping6 -c 1 64:ff9b::192.0.2.2 + ping6 -c 3 64:ff9b::192.0.2.2 rcv=$(echo bar | nc -w 3 -6 -u 64:ff9b::c000:202 1234) if [ "${rcv}" != "foo" ]; then echo "rcv=${rcv}" atf_fail "Failed to connect to UDP server" fi } udp_in_cleanup() { pft_cleanup } atf_test_case "udp_out" "cleanup" udp_out_head() { atf_set descr 'UDP NAT64 test on outbound interface' atf_set require.user root } udp_out_body() { nat64_setup_out echo "foo" | jexec dst nc -u -l 1234 & # Sanity check & delay for nc startup atf_check -s exit:0 -o ignore \ - ping6 -c 1 64:ff9b::192.0.2.2 + ping6 -c 3 64:ff9b::192.0.2.2 rcv=$(echo bar | nc -w 3 -6 -u 64:ff9b::c000:202 1234) if [ "${rcv}" != "foo" ]; then echo "rcv=${rcv}" atf_fail "Failed to connect to UDP server" fi } udp_out_cleanup() { pft_cleanup } atf_test_case "sctp_in" "cleanup" sctp_in_head() { atf_set descr 'SCTP NAT64 test on inbound interface' atf_set require.user root } sctp_in_body() { nat64_setup_in if ! kldstat -q -m sctp; then atf_skip "This test requires SCTP" fi echo "foo" | jexec dst nc --sctp -N -l 1234 & # Sanity check & delay for nc startup atf_check -s exit:0 -o ignore \ - ping6 -c 1 64:ff9b::192.0.2.2 + ping6 -c 3 64:ff9b::192.0.2.2 rcv=$(echo bar | nc --sctp -w 3 -6 64:ff9b::c000:202 1234) if [ "${rcv}" != "foo" ]; then echo "rcv=${rcv}" atf_fail "Failed to connect to SCTP server" fi } sctp_in_cleanup() { pft_cleanup } atf_test_case "sctp_out" "cleanup" sctp_out_head() { atf_set descr 'SCTP NAT64 test on outbound interface' atf_set require.user root } sctp_out_body() { nat64_setup_out if ! kldstat -q -m sctp; then atf_skip "This test requires SCTP" fi echo "foo" | jexec dst nc --sctp -N -l 1234 & # Sanity check & delay for nc startup atf_check -s exit:0 -o ignore \ - ping6 -c 1 64:ff9b::192.0.2.2 + ping6 -c 3 64:ff9b::192.0.2.2 rcv=$(echo bar | nc --sctp -w 3 -6 64:ff9b::c000:202 1234) if [ "${rcv}" != "foo" ]; then echo "rcv=${rcv}" atf_fail "Failed to connect to SCTP server" fi } sctp_out_cleanup() { pft_cleanup } atf_test_case "tos" "cleanup" tos_head() { atf_set descr 'ToS translation test' atf_set require.user root } tos_body() { nat64_setup_in # Ensure we can distinguish ToS on the destination jexec dst pfctl -e pft_set_rules dst \ "pass" \ "block in inet tos 8" atf_check -s exit:0 -o ignore \ ping6 -c 1 -z 4 64:ff9b::192.0.2.2 atf_check -s exit:2 -o ignore \ ping6 -c 1 -z 8 64:ff9b::192.0.2.2 atf_check -s exit:0 -o ignore \ ping6 -c 1 -z 16 64:ff9b::192.0.2.2 jexec dst pfctl -sr -vv } tos_cleanup() { pft_cleanup } atf_test_case "no_v4" "cleanup" no_v4_head() { atf_set descr 'Test error handling when there is no IPv4 address to translate to' atf_set require.user root } no_v4_body() { pft_init epair_link=$(vnet_mkepair) epair=$(vnet_mkepair) ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad route -6 add default 2001:db8::1 vnet_mkjail rtr ${epair}b ${epair_link}a jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad vnet_mkjail dst ${epair_link}b jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up jexec dst route add default 192.0.2.1 # Sanity check atf_check -s exit:0 -o ignore \ ping6 -c 1 2001:db8::1 jexec rtr pfctl -e pft_set_rules rtr \ "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)" atf_check -s exit:2 -o ignore \ ping6 -c 3 64:ff9b::192.0.2.2 } no_v4_cleanup() { pft_cleanup } atf_test_case "range" "cleanup" range_head() { atf_set descr 'Test using an address range for the IPv4 side' atf_set require.user root } range_body() { pft_init epair_link=$(vnet_mkepair) epair=$(vnet_mkepair) ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad route -6 add default 2001:db8::1 vnet_mkjail rtr ${epair}b ${epair_link}a jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad jexec rtr ifconfig ${epair_link}a 192.0.2.2/24 up jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up vnet_mkjail dst ${epair_link}b jexec dst ifconfig ${epair_link}b 192.0.2.254/24 up jexec dst route add default 192.0.2.2 # Sanity checks atf_check -s exit:0 -o ignore \ jexec rtr ping -c 1 192.0.2.254 atf_check -s exit:0 -o ignore \ ping6 -c 1 2001:db8::1 atf_check -s exit:0 -o ignore \ jexec dst ping -c 1 192.0.2.2 atf_check -s exit:0 -o ignore \ jexec dst ping -c 1 192.0.2.3 jexec rtr pfctl -e pft_set_rules rtr \ "set reassemble yes" \ "set state-policy if-bound" \ "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from 192.0.2.2/31 round-robin" # Use pf to count sources jexec dst pfctl -e pft_set_rules dst \ "pass" atf_check -s exit:0 -o ignore \ ping6 -c 1 64:ff9b::192.0.2.254 atf_check -s exit:0 -o ignore \ ping6 -c 1 64:ff9b::192.0.2.254 # Verify on dst that we saw different source addresses atf_check -s exit:0 -o match:".*192.0.2.2.*" \ jexec dst pfctl -ss atf_check -s exit:0 -o match:".*192.0.2.3.*" \ jexec dst pfctl -ss } range_cleanup() { pft_cleanup } atf_test_case "pool" "cleanup" pool_head() { atf_set descr 'Use a pool of IPv4 addresses' atf_set require.user root } pool_body() { pft_init epair_link=$(vnet_mkepair) epair=$(vnet_mkepair) ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad route -6 add default 2001:db8::1 vnet_mkjail rtr ${epair}b ${epair_link}a jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.4/24 up vnet_mkjail dst ${epair_link}b jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up jexec dst route add default 192.0.2.1 # Sanity checks atf_check -s exit:0 -o ignore \ ping6 -c 1 2001:db8::1 atf_check -s exit:0 -o ignore \ jexec dst ping -c 1 192.0.2.1 jexec rtr pfctl -e pft_set_rules rtr \ "set reassemble yes" \ "set state-policy if-bound" \ "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from { 192.0.2.1, 192.0.2.3, 192.0.2.4 } round-robin" # Use pf to count sources jexec dst pfctl -e pft_set_rules dst \ "pass" atf_check -s exit:0 -o ignore \ ping6 -c 1 64:ff9b::192.0.2.2 atf_check -s exit:0 -o ignore \ ping6 -c 1 64:ff9b::192.0.2.2 atf_check -s exit:0 -o ignore \ ping6 -c 1 64:ff9b::192.0.2.2 # Verify on dst that we saw different source addresses atf_check -s exit:0 -o match:".*192.0.2.1.*" \ jexec dst pfctl -ss atf_check -s exit:0 -o match:".*192.0.2.3.*" \ jexec dst pfctl -ss atf_check -s exit:0 -o match:".*192.0.2.4.*" \ jexec dst pfctl -ss } pool_cleanup() { pft_cleanup } atf_test_case "table" table_head() { atf_set descr 'Check table restrictions' atf_set require.user root } table_body() { pft_init # Round-robin and random are allowed echo "pass in on epair inet6 from any to 64:ff9b::/96 af-to inet from round-robin" | \ atf_check -s exit:0 \ pfctl -f - echo "pass in on epair inet6 from any to 64:ff9b::/96 af-to inet from random" | \ atf_check -s exit:0 \ pfctl -f - # bitmask is not echo "pass in on epair inet6 from any to 64:ff9b::/96 af-to inet from bitmask" | \ atf_check -s exit:1 \ -e match:"tables are not supported by pool type" \ pfctl -f - } table_cleanup() { pft_cleanup } atf_test_case "table_range" "cleanup" table_range_head() { atf_set descr 'Test using an address range within a table for the IPv4 side' atf_set require.user root } table_range_body() { pft_init epair_link=$(vnet_mkepair) epair=$(vnet_mkepair) ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad route -6 add default 2001:db8::1 vnet_mkjail rtr ${epair}b ${epair_link}a jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad jexec rtr ifconfig ${epair_link}a 192.0.2.2/24 up jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up vnet_mkjail dst ${epair_link}b jexec dst ifconfig ${epair_link}b 192.0.2.254/24 up jexec dst route add default 192.0.2.2 # Sanity checks atf_check -s exit:0 -o ignore \ ping6 -c 1 2001:db8::1 atf_check -s exit:0 -o ignore \ jexec dst ping -c 1 192.0.2.2 jexec rtr pfctl -e pft_set_rules rtr \ "set reassemble yes" \ "set state-policy if-bound" \ "table { 192.0.2.2/31 }" \ "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from round-robin" # Use pf to count sources jexec dst pfctl -e pft_set_rules dst \ "pass" atf_check -s exit:0 -o ignore \ ping6 -c 1 64:ff9b::192.0.2.254 atf_check -s exit:0 -o ignore \ ping6 -c 1 64:ff9b::192.0.2.254 # Verify on dst that we saw different source addresses atf_check -s exit:0 -o match:".*192.0.2.2.*" \ jexec dst pfctl -ss atf_check -s exit:0 -o match:".*192.0.2.3.*" \ jexec dst pfctl -ss } table_range_cleanup() { pft_cleanup } table_common_body() { pool_type=$1 pft_init epair_link=$(vnet_mkepair) epair=$(vnet_mkepair) ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad route -6 add default 2001:db8::1 vnet_mkjail rtr ${epair}b ${epair_link}a jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.4/24 up vnet_mkjail dst ${epair_link}b jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up jexec dst route add default 192.0.2.1 # Sanity checks atf_check -s exit:0 -o ignore \ ping6 -c 1 2001:db8::1 atf_check -s exit:0 -o ignore \ jexec dst ping -c 1 192.0.2.1 jexec rtr pfctl -e pft_set_rules rtr \ "set reassemble yes" \ "set state-policy if-bound" \ "table { 192.0.2.1, 192.0.2.3, 192.0.2.4 }" \ "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from ${pool_type}" # Use pf to count sources jexec dst pfctl -e pft_set_rules dst \ "pass" atf_check -s exit:0 -o ignore \ ping6 -c 1 64:ff9b::192.0.2.2 atf_check -s exit:0 -o ignore \ ping6 -c 1 64:ff9b::192.0.2.2 atf_check -s exit:0 -o ignore \ ping6 -c 1 64:ff9b::192.0.2.2 # XXX We can't reasonably check pool type random because it's random. It may end # up choosing the same source IP for all three connections. if [ "${pool_type}" == "round-robin" ]; then # Verify on dst that we saw different source addresses atf_check -s exit:0 -o match:".*192.0.2.1.*" \ jexec dst pfctl -ss atf_check -s exit:0 -o match:".*192.0.2.3.*" \ jexec dst pfctl -ss atf_check -s exit:0 -o match:".*192.0.2.4.*" \ jexec dst pfctl -ss fi } atf_test_case "table_round_robin" "cleanup" table_round_robin_head() { atf_set descr 'Use a table of IPv4 addresses in round-robin mode' atf_set require.user root } table_round_robin_body() { table_common_body round-robin } table_round_robin_cleanup() { pft_cleanup } atf_test_case "table_random" "cleanup" table_random_head() { atf_set descr 'Use a table of IPv4 addresses in random mode' atf_set require.user root } table_random_body() { table_common_body random } table_random_cleanup() { pft_cleanup } atf_test_case "dummynet" "cleanup" dummynet_head() { atf_set descr 'Test dummynet on af-to rules' atf_set require.user root } dummynet_body() { pft_init dummynet_init epair_link=$(vnet_mkepair) epair=$(vnet_mkepair) ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad route -6 add default 2001:db8::1 vnet_mkjail rtr ${epair}b ${epair_link}a jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up vnet_mkjail dst ${epair_link}b jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up jexec dst route add default 192.0.2.1 # Sanity checks atf_check -s exit:0 -o ignore \ ping6 -c 1 2001:db8::1 atf_check -s exit:0 -o ignore \ jexec dst ping -c 1 192.0.2.1 jexec rtr pfctl -e jexec rtr dnctl pipe 1 config delay 600 pft_set_rules rtr \ "set reassemble yes" \ "set state-policy if-bound" \ "pass in on ${epair}b inet6 from any to 64:ff9b::/96 dnpipe 1 af-to inet from (${epair_link}a)" # The ping request will pass, but take 1.2 seconds (.6 in, .6 out) # So this works: atf_check -s exit:0 -o ignore \ ping6 -c 1 -t 2 64:ff9b::192.0.2.2 # But this times out: atf_check -s exit:2 -o ignore \ ping6 -c 1 -t 1 64:ff9b::192.0.2.2 } dummynet_cleanup() { pft_cleanup } atf_test_case "gateway6" "cleanup" gateway6_head() { atf_set descr 'NAT64 with a routing hop on the v6 side' atf_set require.user root } gateway6_body() { pft_init epair_lan_link=$(vnet_mkepair) epair_link=$(vnet_mkepair) epair=$(vnet_mkepair) ifconfig ${epair}a inet6 2001:db8:1::2/64 up no_dad route -6 add default 2001:db8:1::1 vnet_mkjail lan_rtr ${epair}b ${epair_lan_link}a jexec lan_rtr ifconfig ${epair}b inet6 2001:db8:1::1/64 up no_dad jexec lan_rtr ifconfig ${epair_lan_link}a inet6 2001:db8::2/64 up no_dad jexec lan_rtr route -6 add default 2001:db8::1 jexec lan_rtr sysctl net.inet6.ip6.forwarding=1 vnet_mkjail rtr ${epair_lan_link}b ${epair_link}a jexec rtr ifconfig ${epair_lan_link}b inet6 2001:db8::1/64 up no_dad jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up jexec rtr route -6 add default 2001:db8::2 vnet_mkjail dst ${epair_link}b jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up jexec dst route add default 192.0.2.1 # Sanity checks atf_check -s exit:0 -o ignore \ ping6 -c 1 2001:db8:1::1 atf_check -s exit:0 -o ignore \ ping6 -c 1 2001:db8::1 atf_check -s exit:0 -o ignore \ jexec dst ping -c 1 192.0.2.1 jexec rtr pfctl -e pft_set_rules rtr \ "set reassemble yes" \ "set state-policy if-bound" \ "pass in on ${epair_lan_link}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)" # One ping atf_check -s exit:0 -o ignore \ ping6 -c 1 64:ff9b::192.0.2.2 # Make sure packets make it even when state is established atf_check -s exit:0 \ -o match:'5 packets transmitted, 5 packets received, 0.0% packet loss' \ ping6 -c 5 64:ff9b::192.0.2.2 } gateway6_cleanup() { pft_cleanup } atf_test_case "route_to" "cleanup" route_to_head() { atf_set descr 'Test route-to on af-to rules' atf_set require.user root } route_to_body() { pft_init epair_link=$(vnet_mkepair) epair_null=$(vnet_mkepair) epair=$(vnet_mkepair) ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad route -6 add default 2001:db8::1 vnet_mkjail rtr ${epair}b ${epair_link}a ${epair_null}a jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad jexec rtr ifconfig ${epair_null}a 192.0.2.3/24 up jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up vnet_mkjail dst ${epair_link}b jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up jexec dst route add default 192.0.2.1 # Sanity checks atf_check -s exit:0 -o ignore \ ping6 -c 1 2001:db8::1 jexec rtr pfctl -e pft_set_rules rtr \ "set reassemble yes" \ "set state-policy if-bound" \ "pass in on ${epair}b route-to (${epair_link}a 192.0.2.2) inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)" atf_check -s exit:0 -o ignore \ ping6 -c 3 64:ff9b::192.0.2.2 states=$(mktemp) || exit 1 jexec rtr pfctl -qvvss | normalize_pfctl_s > $states for state_regexp in \ "${epair}b ipv6-icmp 192.0.2.1:.* \(2001:db8::2\[[0-9]+\]\) -> 192.0.2.2:8 \(64:ff9b::c000:202\[[0-9]+\]\).*4:2 pkts.*route-to: 192.0.2.2@${epair_link}a" \ ; do grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" done } route_to_cleanup() { pft_cleanup } atf_test_case "reply_to" "cleanup" reply_to_head() { atf_set descr 'Test reply-to on af-to rules' atf_set require.user root } reply_to_body() { pft_init epair_link=$(vnet_mkepair) epair=$(vnet_mkepair) ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad route -6 add default 2001:db8::1 vnet_mkjail rtr ${epair}b ${epair_link}a jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up vnet_mkjail dst ${epair_link}b jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up jexec dst route add default 192.0.2.1 # Sanity checks atf_check -s exit:0 -o ignore \ ping6 -c 1 2001:db8::1 jexec rtr pfctl -e pft_set_rules rtr \ "set reassemble yes" \ "set state-policy if-bound" \ "pass in on ${epair}b reply-to (${epair}b 2001:db8::2) inet6 from any to 64:ff9b::/96 af-to inet from 192.0.2.1" atf_check -s exit:0 -o ignore \ ping6 -c 3 64:ff9b::192.0.2.2 } reply_to_cleanup() { pft_cleanup } atf_test_case "v6_gateway" "cleanup" v6_gateway_head() { atf_set descr 'nat64 when the IPv4 gateway is given by an IPv6 address' atf_set require.user root } v6_gateway_body() { pft_init epair_wan_two=$(vnet_mkepair) epair_wan_one=$(vnet_mkepair) epair_lan=$(vnet_mkepair) ifconfig ${epair_lan}a inet6 2001:db8::2/64 up no_dad route -6 add default 2001:db8::1 vnet_mkjail rtr ${epair_lan}b ${epair_wan_one}a jexec rtr ifconfig ${epair_lan}b inet6 2001:db8::1/64 up no_dad jexec rtr ifconfig ${epair_wan_one}a 192.0.2.1/24 up jexec rtr ifconfig ${epair_wan_one}a inet6 -ifdisabled jexec rtr route add default -inet6 fe80::1%${epair_wan_one}a #jexec rtr route add default 192.0.2.2 vnet_mkjail wan_one ${epair_wan_one}b ${epair_wan_two}a jexec wan_one ifconfig ${epair_wan_one}b 192.0.2.2/24 up jexec wan_one ifconfig ${epair_wan_one}b inet6 fe80::1/64 jexec wan_one ifconfig ${epair_wan_two}a 198.51.100.2/24 up jexec wan_one route add default 192.0.2.1 jexec wan_one sysctl net.inet.ip.forwarding=1 vnet_mkjail wan_two ${epair_wan_two}b jexec wan_two ifconfig ${epair_wan_two}b 198.51.100.1/24 up jexec wan_two route add default 198.51.100.2 # Sanity checks atf_check -s exit:0 -o ignore \ ping6 -c 1 2001:db8::1 atf_check -s exit:0 -o ignore \ jexec rtr ping -c 1 192.0.2.2 atf_check -s exit:0 -o ignore \ jexec rtr ping -c 1 198.51.100.1 jexec rtr pfctl -e pft_set_rules rtr \ "set reassemble yes" \ "set state-policy if-bound" \ "pass in on ${epair_lan}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_wan_one}a)" atf_check -s exit:0 -o ignore \ ping6 -c 3 64:ff9b::192.0.2.2 atf_check -s exit:0 -o ignore \ ping6 -c 3 64:ff9b::198.51.100.1 } v6_gateway_cleanup() { pft_cleanup } atf_init_test_cases() { atf_add_test_case "icmp_echo_in" atf_add_test_case "icmp_echo_out" atf_add_test_case "fragmentation_in" atf_add_test_case "fragmentation_out" atf_add_test_case "tcp_in" atf_add_test_case "tcp_out" atf_add_test_case "udp_in" atf_add_test_case "udp_out" atf_add_test_case "sctp_in" atf_add_test_case "sctp_out" atf_add_test_case "tos" atf_add_test_case "no_v4" atf_add_test_case "range" atf_add_test_case "pool" atf_add_test_case "table" atf_add_test_case "table_range" atf_add_test_case "table_round_robin" atf_add_test_case "table_random" atf_add_test_case "dummynet" atf_add_test_case "gateway6" atf_add_test_case "route_to" atf_add_test_case "reply_to" atf_add_test_case "v6_gateway" } diff --git a/tests/sys/netpfil/pf/sctp.py b/tests/sys/netpfil/pf/sctp.py index da42ce527195..f492f26b63a1 100644 --- a/tests/sys/netpfil/pf/sctp.py +++ b/tests/sys/netpfil/pf/sctp.py @@ -1,713 +1,746 @@ import pytest import ctypes import socket import ipaddress import re from atf_python.sys.net.tools import ToolsHelper from atf_python.sys.net.vnet import VnetTestTemplate import time SCTP_UNORDERED = 0x0400 SCTP_NODELAY = 0x00000004 SCTP_SET_PEER_PRIMARY_ADDR = 0x00000006 SCTP_PRIMARY_ADDR = 0x00000007 SCTP_BINDX_ADD_ADDR = 0x00008001 SCTP_BINDX_REM_ADDR = 0x00008002 class sockaddr_in(ctypes.Structure): _fields_ = [ ('sin_len', ctypes.c_uint8), ('sin_family', ctypes.c_uint8), ('sin_port', ctypes.c_uint16), ('sin_addr', ctypes.c_uint32), ('sin_zero', ctypes.c_int8 * 8) ] class sockaddr_in6(ctypes.Structure): _fields_ = [ ('sin6_len', ctypes.c_uint8), ('sin6_family', ctypes.c_uint8), ('sin6_port', ctypes.c_uint16), ('sin6_flowinfo', ctypes.c_uint32), ('sin6_addr', ctypes.c_uint8 * 16), ('sin6_scope_id', ctypes.c_uint32) ] class sockaddr_storage(ctypes.Union): _fields_ = [ ("v4", sockaddr_in), ("v6", sockaddr_in6) ] class sctp_sndrcvinfo(ctypes.Structure): _fields_ = [ ('sinfo_stream', ctypes.c_uint16), ('sinfo_ssn', ctypes.c_uint16), ('sinfo_flags', ctypes.c_uint16), ('sinfo_ppid', ctypes.c_uint32), ('sinfo_context', ctypes.c_uint32), ('sinfo_timetolive', ctypes.c_uint32), ('sinfo_tsn', ctypes.c_uint32), ('sinfo_cumtsn', ctypes.c_uint32), ('sinfo_assoc_id', ctypes.c_uint32), ] class sctp_setprim(ctypes.Structure): _fields_ = [ ('ssp_addr', sockaddr_storage), ('ssp_pad', ctypes.c_int8 * (128 - 16)), ('ssp_assoc_id', ctypes.c_uint32), ('ssp_padding', ctypes.c_uint32) ] def to_sockaddr(ip, port): ip = ipaddress.ip_address(ip) if ip.version == 4: addr = sockaddr_in() addr.sin_len = ctypes.sizeof(addr) addr.sin_family = socket.AF_INET addr.sin_port = socket.htons(port) addr.sin_addr = socket.htonl(int.from_bytes(ip.packed, byteorder='big')) else: assert ip.version == 6 addr = sockaddr_in6() addr.sin6_len = ctypes.sizeof(addr) addr.sin6_family = socket.AF_INET6 addr.sin6_port = socket.htons(port) for i in range(0, 16): addr.sin6_addr[i] = ip.packed[i] return addr class SCTPServer: def __init__(self, family, port=1234): self._libc = ctypes.CDLL("libc.so.7", use_errno=True) self._listen_fd = self._libc.socket(family, socket.SOCK_STREAM, socket.IPPROTO_SCTP) if self._listen_fd == -1: raise Exception("Failed to create socket") if family == socket.AF_INET: srvaddr = sockaddr_in() srvaddr.sin_len = ctypes.sizeof(srvaddr) srvaddr.sin_family = socket.AF_INET srvaddr.sin_port = socket.htons(port) srvaddr.sin_addr = socket.INADDR_ANY else: srvaddr = sockaddr_in6() srvaddr.sin6_len = ctypes.sizeof(srvaddr) srvaddr.sin6_family = family srvaddr.sin6_port = socket.htons(port) # Leave sin_addr empty, because ANY is zero ret = self._libc.bind(self._listen_fd, ctypes.pointer(srvaddr), ctypes.sizeof(srvaddr)) if ret == -1: raise Exception("Failed to bind: %d" % ctypes.get_errno()) ret = self._libc.listen(self._listen_fd, 2) if ret == -1: raise Exception("Failed to listen") def _to_string(self, buf): return ''.join([chr(int.from_bytes(i, byteorder='big')) for i in buf]).rstrip('\x00') def accept(self, vnet): fd = self._libc.accept(self._listen_fd, 0, 0) if fd < 0: raise Exception("Failed to accept") print("SCTPServer: connection opened") while True: rcvinfo = sctp_sndrcvinfo() flags = ctypes.c_int() buf = ctypes.create_string_buffer(128) # Receive a single message, and inform the other vnet about it. ret = self._libc.sctp_recvmsg(fd, ctypes.cast(buf, ctypes.c_void_p), 128, 0, 0, ctypes.pointer(rcvinfo), ctypes.pointer(flags)) if ret < 0: print("SCTPServer: connection closed") return if ret == 0: continue rcvd = {} rcvd['ppid'] = socket.ntohl(rcvinfo.sinfo_ppid) rcvd['data'] = self._to_string(buf) rcvd['len'] = ret print(rcvd) vnet.pipe.send(rcvd) class SCTPClient: def __init__(self, ip, port=1234, fromaddr=None): self._libc = ctypes.CDLL("libc.so.7", use_errno=True) if ipaddress.ip_address(ip).version == 4: family = socket.AF_INET else: family = socket.AF_INET6 self._fd = self._libc.socket(family, socket.SOCK_STREAM, socket.IPPROTO_SCTP) if self._fd == -1: raise Exception("Failed to open socket") if fromaddr is not None: addr = to_sockaddr(fromaddr, 0) ret = self._libc.bind(self._fd, ctypes.pointer(addr), ctypes.sizeof(addr)) if ret != 0: print("bind() => %d", ctypes.get_errno()) raise addr = to_sockaddr(ip, port) ret = self._libc.connect(self._fd, ctypes.pointer(addr), ctypes.sizeof(addr)) if ret == -1: raise Exception("Failed to connect") # Enable NODELAY, because otherwise the sending host may wait for SACK # on a data chunk we've removed enable = ctypes.c_int(1) ret = self._libc.setsockopt(self._fd, socket.IPPROTO_SCTP, SCTP_NODELAY, ctypes.pointer(enable), 4) def newpeer(self, addr): print("newpeer(%s)" % (addr)) setp = sctp_setprim() a = to_sockaddr(addr, 0) if type(a) is sockaddr_in: setp.ssp_addr.v4 = a else: assert type(a) is sockaddr_in6 setp.ssp_addr.v6 = a ret = self._libc.setsockopt(self._fd, socket.IPPROTO_SCTP, SCTP_PRIMARY_ADDR, ctypes.pointer(setp), ctypes.sizeof(setp)) if ret != 0: print("errno %d" % ctypes.get_errno()) raise Exception(ctypes.get_errno()) def newprimary(self, addr): print("newprimary(%s)" % (addr)) # Strictly speaking needs to be struct sctp_setpeerprim, but that's # identical to sctp_setprim setp = sctp_setprim() a = to_sockaddr(addr, 0) if type(a) is sockaddr_in: setp.ssp_addr.v4 = a else: assert type(a) is sockaddr_in6 setp.ssp_addr.v6 = a ret = self._libc.setsockopt(self._fd, socket.IPPROTO_SCTP, SCTP_SET_PEER_PRIMARY_ADDR, ctypes.pointer(setp), ctypes.sizeof(setp)) if ret != 0: print("errno %d" % ctypes.get_errno()) raise def bindx(self, addr, add): print("bindx(%s, %s)" % (addr, add)) addr = to_sockaddr(addr, 0) if add: flag = SCTP_BINDX_ADD_ADDR else: flag = SCTP_BINDX_REM_ADDR ret = self._libc.sctp_bindx(self._fd, ctypes.pointer(addr), 1, flag) if ret != 0: print("sctp_bindx() errno %d" % ctypes.get_errno()) raise def send(self, buf, ppid, ordered=False): flags = 0 if not ordered: flags = SCTP_UNORDERED ppid = socket.htonl(ppid) ret = self._libc.sctp_sendmsg(self._fd, ctypes.c_char_p(buf), len(buf), ctypes.c_void_p(0), 0, ppid, flags, 0, 0, 0) if ret < 0: raise Exception("Failed to send message") def close(self): self._libc.close(self._fd) self._fd = -1 class TestSCTP(VnetTestTemplate): REQUIRED_MODULES = ["sctp", "pf"] TOPOLOGY = { "vnet1": {"ifaces": ["if1"]}, "vnet2": {"ifaces": ["if1"]}, "if1": {"prefixes4": [("192.0.2.1/24", "192.0.2.2/24")]}, } def vnet2_handler(self, vnet): # Give ourself a second IP address, for multihome testing ifname = vnet.iface_alias_map["if1"].name ToolsHelper.print_output("/sbin/ifconfig %s inet alias 192.0.2.3/24" % ifname) # Start an SCTP server process, pipe the ppid + data back to the other vnet? srv = SCTPServer(socket.AF_INET, port=1234) while True: srv.accept(vnet) @pytest.mark.require_user("root") def test_multihome(self): srv_vnet = self.vnet_map["vnet2"] ToolsHelper.print_output("/sbin/pfctl -e") ToolsHelper.pf_rules([ "block proto sctp", "pass inet proto sctp to 192.0.2.0/24", "pass on lo"]) + # Give the server some time to come up + time.sleep(3) + # Sanity check, we can communicate with the primary address. client = SCTPClient("192.0.2.3", 1234) client.send(b"hello", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "hello" try: client.newpeer("192.0.2.2") client.send(b"world", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "world" finally: # Debug output ToolsHelper.print_output("/sbin/pfctl -ss") ToolsHelper.print_output("/sbin/pfctl -sr -vv") # Check that we have a state for 192.0.2.3 and 192.0.2.2 to 192.0.2.1 states = ToolsHelper.get_output("/sbin/pfctl -ss") assert re.search(r"all sctp 192.0.2.1:.*192.0.2.3:1234", states) assert re.search(r"all sctp 192.0.2.1:.*192.0.2.2:1234", states) @pytest.mark.require_user("root") def test_multihome_asconf(self): srv_vnet = self.vnet_map["vnet2"] # Assign a second IP to ourselves ToolsHelper.print_output("/sbin/ifconfig %s inet alias 192.0.2.10/24" % self.vnet.iface_alias_map["if1"].name) ToolsHelper.print_output("/sbin/pfctl -e") ToolsHelper.pf_rules([ "block proto sctp", "pass on lo", "pass inet proto sctp from 192.0.2.0/24"]) + # Give the server some time to come up + time.sleep(3) + # Sanity check, we can communicate with the primary address. client = SCTPClient("192.0.2.3", 1234, "192.0.2.1") client.send(b"hello", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "hello" # Now add our second address to the connection client.bindx("192.0.2.10", True) # We can still communicate client.send(b"world", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "world" # Now change to a different peer address try: client.newprimary("192.0.2.10") client.send(b"!", 0) rcvd = self.wait_object(srv_vnet.pipe, 5) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "!" finally: # Debug output ToolsHelper.print_output("/sbin/pfctl -ss -vv") # Ensure we have the states we'd expect states = ToolsHelper.get_output("/sbin/pfctl -ss") assert re.search(r"all sctp 192.0.2.1:.*192.0.2.3:1234", states) assert re.search(r"all sctp 192.0.2.10:.*192.0.2.3:1234", states) # Now remove 192.0.2.1 as an address client.bindx("192.0.2.1", False) # We can still communicate try: client.send(b"More data", 0) rcvd = self.wait_object(srv_vnet.pipe, 5) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] =="More data" finally: # Debug output ToolsHelper.print_output("/sbin/pfctl -ss -vv") # Verify that state is closing states = ToolsHelper.get_output("/sbin/pfctl -ss") assert re.search(r"all sctp 192.0.2.1:.*192.0.2.3:1234.*SHUTDOWN", states) @pytest.mark.require_user("root") def test_permutation_if_bound(self): # Test that we generate all permutations of src/dst addresses. # Assign two addresses to each end, and check for the expected states srv_vnet = self.vnet_map["vnet2"] ifname = self.vnet_map["vnet1"].iface_alias_map["if1"].name ToolsHelper.print_output("/sbin/ifconfig %s inet alias 192.0.2.4/24" % ifname) ToolsHelper.print_output("/sbin/pfctl -e") ToolsHelper.pf_rules([ "set state-policy if-bound", "block proto sctp", "pass on lo", "pass inet proto sctp to 192.0.2.0/24"]) + # Give the server some time to come up + time.sleep(3) + # Sanity check, we can communicate with the primary address. client = SCTPClient("192.0.2.3", 1234) client.send(b"hello", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "hello" # Check that we have a state for 192.0.2.3 and 192.0.2.2 to 192.0.2.1, but also to 192.0.2.4 states = ToolsHelper.get_output("/sbin/pfctl -ss") print(states) assert re.search(r"epair.*sctp 192.0.2.1:.*192.0.2.3:1234", states) assert re.search(r"epair.*sctp 192.0.2.1:.*192.0.2.2:1234", states) assert re.search(r"epair.*sctp 192.0.2.4:.*192.0.2.3:1234", states) assert re.search(r"epair.*sctp 192.0.2.4:.*192.0.2.2:1234", states) @pytest.mark.require_user("root") def test_permutation_floating(self): # Test that we generate all permutations of src/dst addresses. # Assign two addresses to each end, and check for the expected states srv_vnet = self.vnet_map["vnet2"] ifname = self.vnet_map["vnet1"].iface_alias_map["if1"].name ToolsHelper.print_output("/sbin/ifconfig %s inet alias 192.0.2.4/24" % ifname) ToolsHelper.print_output("/sbin/pfctl -e") ToolsHelper.pf_rules([ "block proto sctp", "pass on lo", "pass inet proto sctp to 192.0.2.0/24"]) + # Give the server some time to come up + time.sleep(3) + # Sanity check, we can communicate with the primary address. client = SCTPClient("192.0.2.3", 1234) client.send(b"hello", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "hello" # Check that we have a state for 192.0.2.3 and 192.0.2.2 to 192.0.2.1, but also to 192.0.2.4 states = ToolsHelper.get_output("/sbin/pfctl -ss") print(states) assert re.search(r"all sctp 192.0.2.1:.*192.0.2.3:1234", states) assert re.search(r"all sctp 192.0.2.1:.*192.0.2.2:1234", states) assert re.search(r"all sctp 192.0.2.4:.*192.0.2.3:1234", states) assert re.search(r"all sctp 192.0.2.4:.*192.0.2.2:1234", states) @pytest.mark.require_user("root") def test_limit_addresses(self): srv_vnet = self.vnet_map["vnet2"] ifname = self.vnet_map["vnet1"].iface_alias_map["if1"].name for i in range(0, 16): ToolsHelper.print_output("/sbin/ifconfig %s inet alias 192.0.2.%d/24" % (ifname, 4 + i)) ToolsHelper.print_output("/sbin/pfctl -e") ToolsHelper.pf_rules([ "block proto sctp", "pass on lo", "pass inet proto sctp to 192.0.2.0/24"]) + # Give the server some time to come up + time.sleep(3) + # Set up a connection, which will try to create states for all addresses # we have assigned client = SCTPClient("192.0.2.3", 1234) client.send(b"hello", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "hello" # But the number should be limited to 9 (original + 8 extra) states = ToolsHelper.get_output("/sbin/pfctl -ss | grep 192.0.2.2") print(states) assert(states.count('\n') <= 9) @pytest.mark.require_user("root") def test_disallow_related(self): srv_vnet = self.vnet_map["vnet2"] ToolsHelper.print_output("/sbin/pfctl -e") ToolsHelper.pf_rules([ "block proto sctp", "pass inet proto sctp to 192.0.2.3", "pass on lo"]) + # Give the server some time to come up + time.sleep(3) + # Sanity check, we can communicate with the primary address. client = SCTPClient("192.0.2.3", 1234) client.send(b"hello", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "hello" # This shouldn't work success=False try: client.newpeer("192.0.2.2") client.send(b"world", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "world" success=True except: success=False assert not success # Check that we have a state for 192.0.2.3, but not 192.0.2.2 to 192.0.2.1 states = ToolsHelper.get_output("/sbin/pfctl -ss") assert re.search(r"all sctp 192.0.2.1:.*192.0.2.3:1234", states) assert not re.search(r"all sctp 192.0.2.1:.*192.0.2.2:1234", states) @pytest.mark.require_user("root") def test_allow_related(self): srv_vnet = self.vnet_map["vnet2"] ToolsHelper.print_output("/sbin/pfctl -e") ToolsHelper.pf_rules([ "set state-policy if-bound", "block proto sctp", "pass inet proto sctp to 192.0.2.3 keep state (allow-related)", "pass on lo"]) + # Give the server some time to come up + time.sleep(3) + # Sanity check, we can communicate with the primary address. client = SCTPClient("192.0.2.3", 1234) client.send(b"hello", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "hello" success=False try: client.newpeer("192.0.2.2") client.send(b"world", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "world" success=True finally: # Debug output ToolsHelper.print_output("/sbin/pfctl -ss") ToolsHelper.print_output("/sbin/pfctl -sr -vv") assert success # Check that we have a state for 192.0.2.3 and 192.0.2.2 to 192.0.2.1 states = ToolsHelper.get_output("/sbin/pfctl -ss") assert re.search(r"epair.*sctp 192.0.2.1:.*192.0.2.3:1234", states) assert re.search(r"epair.*sctp 192.0.2.1:.*192.0.2.2:1234", states) class TestSCTPv6(VnetTestTemplate): REQUIRED_MODULES = ["sctp", "pf"] TOPOLOGY = { "vnet1": {"ifaces": ["if1"]}, "vnet2": {"ifaces": ["if1"]}, "if1": {"prefixes6": [("2001:db8::1/64", "2001:db8::2/64")]}, } def vnet2_handler(self, vnet): # Give ourself a second IP address, for multihome testing ifname = vnet.iface_alias_map["if1"].name ToolsHelper.print_output("/sbin/ifconfig %s inet6 alias 2001:db8::3/64" % ifname) # Start an SCTP server process, pipe the ppid + data back to the other vnet? srv = SCTPServer(socket.AF_INET6, port=1234) while True: srv.accept(vnet) @pytest.mark.require_user("root") def test_multihome(self): srv_vnet = self.vnet_map["vnet2"] ToolsHelper.print_output("/sbin/pfctl -e") ToolsHelper.pf_rules([ "block proto sctp", "pass on lo", "pass inet6 proto sctp to 2001:db8::0/64"]) + # Give the server some time to come up + time.sleep(3) + # Sanity check, we can communicate with the primary address. client = SCTPClient("2001:db8::3", 1234) client.send(b"hello", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "hello" # Now change to a different peer address try: client.newpeer("2001:db8::2") client.send(b"world", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "world" finally: # Debug output ToolsHelper.print_output("/sbin/pfctl -ss -vv") # Check that we have the expected states states = ToolsHelper.get_output("/sbin/pfctl -ss") assert re.search(r"all sctp 2001:db8::1\[.*2001:db8::3\[1234\]", states) assert re.search(r"all sctp 2001:db8::1\[.*2001:db8::2\[1234\]", states) @pytest.mark.require_user("root") def test_multihome_asconf(self): srv_vnet = self.vnet_map["vnet2"] # Assign a second IP to ourselves ToolsHelper.print_output("/sbin/ifconfig %s inet6 alias 2001:db8::10/64" % self.vnet.iface_alias_map["if1"].name) ToolsHelper.print_output("/sbin/pfctl -e") ToolsHelper.pf_rules([ "block proto sctp", "pass on lo", "pass inet6 proto sctp from 2001:db8::/64"]) + # Give the server some time to come up + time.sleep(3) + # Sanity check, we can communicate with the primary address. client = SCTPClient("2001:db8::3", 1234, "2001:db8::1") client.send(b"hello", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "hello" # Now add our second address to the connection client.bindx("2001:db8::10", True) # We can still communicate client.send(b"world", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "world" # Now change to a different peer address try: client.newprimary("2001:db8::10") client.send(b"!", 0) rcvd = self.wait_object(srv_vnet.pipe, 5) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "!" finally: # Debug output ToolsHelper.print_output("/sbin/pfctl -ss -vv") # Check that we have the expected states states = ToolsHelper.get_output("/sbin/pfctl -ss") assert re.search(r"all sctp 2001:db8::1\[.*2001:db8::3\[1234\]", states) assert re.search(r"all sctp 2001:db8::10\[.*2001:db8::3\[1234\]", states) # Now remove 2001:db8::1 as an address client.bindx("2001:db8::1", False) # Wecan still communicate try: client.send(b"More data", 0) rcvd = self.wait_object(srv_vnet.pipe, 5) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "More data" finally: # Debug output ToolsHelper.print_output("/sbin/pfctl -ss -vv") # Verify that the state is closing states = ToolsHelper.get_output("/sbin/pfctl -ss") assert re.search(r"all sctp 2001:db8::1\[.*2001:db8::3\[1234\].*SHUTDOWN", states) @pytest.mark.require_user("root") def test_permutation(self): # Test that we generate all permutations of src/dst addresses. # Assign two addresses to each end, and check for the expected states srv_vnet = self.vnet_map["vnet2"] ifname = self.vnet_map["vnet1"].iface_alias_map["if1"].name ToolsHelper.print_output("/sbin/ifconfig %s inet6 alias 2001:db8::4/64" % ifname) ToolsHelper.print_output("/sbin/pfctl -e") ToolsHelper.pf_rules([ "set state-policy if-bound", "block proto sctp", "pass on lo", "pass inet6 proto sctp to 2001:db8::0/64"]) + # Give the server some time to come up + time.sleep(3) + # Sanity check, we can communicate with the primary address. client = SCTPClient("2001:db8::3", 1234) client.send(b"hello", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "hello" # Check that we have a state for 2001:db8::3 and 2001:db8::2 to 2001:db8::1, but also to 2001:db8::4 states = ToolsHelper.get_output("/sbin/pfctl -ss") print(states) assert re.search(r"epair.*sctp 2001:db8::1\[.*2001:db8::2\[1234\]", states) assert re.search(r"epair.*sctp 2001:db8::1\[.*2001:db8::3\[1234\]", states) assert re.search(r"epair.*sctp 2001:db8::4\[.*2001:db8::2\[1234\]", states) assert re.search(r"epair.*sctp 2001:db8::4\[.*2001:db8::3\[1234\]", states) @pytest.mark.require_user("root") def test_permutation_floating(self): # Test that we generate all permutations of src/dst addresses. # Assign two addresses to each end, and check for the expected states srv_vnet = self.vnet_map["vnet2"] ifname = self.vnet_map["vnet1"].iface_alias_map["if1"].name ToolsHelper.print_output("/sbin/ifconfig %s inet6 alias 2001:db8::4/64" % ifname) ToolsHelper.print_output("/sbin/pfctl -e") ToolsHelper.pf_rules([ "block proto sctp", "pass on lo", "pass inet6 proto sctp to 2001:db8::0/64"]) + # Give the server some time to come up + time.sleep(3) + # Sanity check, we can communicate with the primary address. client = SCTPClient("2001:db8::3", 1234) client.send(b"hello", 0) rcvd = self.wait_object(srv_vnet.pipe) print(rcvd) assert rcvd['ppid'] == 0 assert rcvd['data'] == "hello" # Check that we have a state for 2001:db8::3 and 2001:db8::2 to 2001:db8::1, but also to 2001:db8::4 states = ToolsHelper.get_output("/sbin/pfctl -ss") print(states) assert re.search(r"all sctp 2001:db8::1\[.*2001:db8::2\[1234\]", states) assert re.search(r"all sctp 2001:db8::1\[.*2001:db8::3\[1234\]", states) assert re.search(r"all sctp 2001:db8::4\[.*2001:db8::2\[1234\]", states) assert re.search(r"all sctp 2001:db8::4\[.*2001:db8::3\[1234\]", states)