On Tue, Sep 30, 2025 at 6:16 PM Martin Kalcok <[email protected]>
wrote:

> Macro NS_CHECK_CONNECTIVITY is introduced to provide a more comprehensive
> way to check connectivity between network namespaces. It takes source
> namespace, destination namespace and destination IP as arguments, and
> it verifies that source NS can reach destination NS on the provided IP.
> Executed checks include:
>   * simple ping
>   * Exchange of multiple requests and replies over TCP
>   * Exchange of multiple requests and replies over UDP
>
> First usage of this macro is in
> 'DNAT and SNAT on distributed router - N/S' tests that were currently not
> equipped to detect NAT failures after the initial connection was
> established.
>
> Related macro NS_CHECK_CONNECTIVITY_V6 is provided with same
> functionality, but expecitng IPv6 destination IP address.
>
> Signed-off-by: Martin Kalcok <[email protected]>
> ---
>  tests/system-common-macros.at |  71 ++++++++++++++++++
>  tests/system-ovn.at           | 136 +++++++++-------------------------
>  2 files changed, 105 insertions(+), 102 deletions(-)
>
> diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at
> index 251a4c0b8..1f2b2b4eb 100644
> --- a/tests/system-common-macros.at
> +++ b/tests/system-common-macros.at
> @@ -614,3 +614,74 @@ m4_define([OVN_NEIGH_EQUAL],
>  m4_define([OVN_NEIGH_V6_EQUAL],
>    [OVS_WAIT_UNTIL_EQUAL([ip -6 neigh show dev $1 $2 | sed -e
> 's|[[[[:space:]]]]*$||g' | grep $3 | sort], [$4])
>  ])
> +
> +# NS_CHECK_CONNECTIVITY__([NS_SRC], [NS_DST], [IP_ADDR], [SET_IF_V6])
> +#
> +# Ensures that network namespace NS_SRC can reach NS_DST on IP_ADDR. The
> check performs:
> +#   * Ping
> +#   * Exchange of multiple messages over TCP
> +#   * Exchange of multiple messages over UDP
> +#
> +# If SET_IF_V6 argument is provided with any value, the IP_ADDR is
> treated as IPv6 address.
> +m4_define([NS_CHECK_CONNECTIVITY__],
> +[
> +    ns_src="$1"
> +    ns_dst="$2"
> +    ip="$3"
> +    is_v6="$4"
> +
> +    proto="-4"
> +    if test -n "$is_v6"; then
> +        proto="-6"
> +    fi
> +    # Start a simple TCP and UDP echo server that replies with "ack
> <received_msg>".
> +    NETNS_DAEMONIZE($ns_dst, [nc $proto -l -p 1235 -d 0.1 -c '/bin/cat'],
> [nc-$ns_dst-$ip-tcp.pid])
> +    NETNS_DAEMONIZE($ns_dst, [nc $proto -l -u -p 1234 -d 0.1 -c
> '/bin/cat'], [nc-$ns_dst-$ip-udp.pid])
> +
> +    # Ensure that the destination NS can be pinged on the specified IP
> +    NS_CHECK_EXEC([$ns_src], [ping $proto -q -c 3 -i 0.3 -w 2 $ip |
> FORMAT_PING], \
> +    [0], [dnl
> +3 packets transmitted, 3 received, 0% packet loss, time 0ms
> +])
> +
> +    # Exchange multiple messages over TCP and UDP to verify connectivity
> +    # Note(mkalcok): Server replies are printed to file, because STDOUT
> is captured by the nc.
> +    :> ./tcp_data
> +    :> ./udp_data
> +
> +    NETNS_DAEMONIZE($ns_src, [nc $proto $ip 1235 -d 0.1 -c 'for i in
> $(seq 1 3); do echo "tcp_data $i"; read msg; echo "$msg" >>./tcp_data;
> done'], [nc_$ns_src-$ip-tcp.pid])
> +    NETNS_DAEMONIZE($ns_src, [nc $proto -u $ip 1234 -d 0.1 -c 'for i in
> $(seq 1 3); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data;
> done'], [nc_$ns_src-$ip-udp.pid])
> +
> +    OVS_WAIT_FOR_OUTPUT([cat ./tcp_data],  [0], [dnl
> +tcp_data 1
> +tcp_data 2
> +tcp_data 3
> +])
> +    OVS_WAIT_FOR_OUTPUT([cat ./udp_data], [0], [dnl
> +udp_data 1
> +udp_data 2
> +udp_data 3
> +])
> +
> +    # cleanup echo processes
> +    kill $(cat nc-$ns_dst-$ip-tcp.pid)
> +    kill $(cat nc-$ns_dst-$ip-udp.pid)
> +    kill $(cat nc_$ns_src-$ip-tcp.pid)
> +    kill $(cat nc_$ns_src-$ip-udp.pid)
> +])
> +
> +# NS_CHECK_CONNECTIVITY([NS_SRC], [NS_DST], [IPV4_ADDR])
> +#
> +# IPv4 version of NS_CHECK_CONNECTIVITY__.
> +m4_define([NS_CHECK_CONNECTIVITY],
> +[
> +    NS_CHECK_CONNECTIVITY__([$1], [$2], [$3])
> +])
> +
> +# NS_CHECK_CONNECTIVITY_V6([NS_SRC], [NS_DST], [IPV6_ADDR])
> +#
> +# IPv6 version of NS_CHECK_CONNECTIVITY__.
> +m4_define([NS_CHECK_CONNECTIVITY_V6],
> +[
> +    NS_CHECK_CONNECTIVITY__([$1], [$2], [$3], [yes])
> +])
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index 8e356df6f..f84290594 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -3777,46 +3777,49 @@ AT_CHECK([ovn-nbctl lr-route-add R1 10.0.0.0/24
> 172.16.1.2])
>  check ovn-nbctl --wait=hv sync
>  OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=172.16.1.1)'])
>
> -# North-South DNAT: 'alice1' pings 'foo1' using 172.16.1.3.
> -NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.3 |
> FORMAT_PING], \
> -[0], [dnl
> -3 packets transmitted, 3 received, 0% packet loss, time 0ms
> -])
> +# North-South DNAT: 'alice1' reaches 'foo1' on 172.16.1.3.
> +NS_CHECK_CONNECTIVITY([alice1], [foo1], [172.16.1.3])
>
>  # We verify that DNAT indeed happened via 'dump-conntrack' command.
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.3) | \
>  sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>
>  
> icmp,orig=(src=172.16.1.2,dst=172.16.1.3,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=172.16.1.2,id=<cleared>,type=0,code=0),zone=<cleared>
>
> +tcp,orig=(src=172.16.1.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>
> +udp,orig=(src=172.16.1.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>
>  ])
>
> -# South-North SNAT: 'foo2' pings 'alice1'. But 'alice1' receives traffic
> +# South-North SNAT: 'foo2' reaches 'alice1'. But 'alice1' receives traffic
>  # from 172.16.1.4
> -NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 172.16.1.2 |
> FORMAT_PING], \
> -[0], [dnl
> -3 packets transmitted, 3 received, 0% packet loss, time 0ms
> -])
> +NS_CHECK_CONNECTIVITY([foo2], [alice1], 172.16.1.2)
>
>  # We verify that SNAT indeed happened via 'dump-conntrack' command.
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.4) | \
>  sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>
>  
> icmp,orig=(src=192.168.1.3,dst=172.16.1.2,id=<cleared>,type=8,code=0),reply=(src=172.16.1.2,dst=172.16.1.4,id=<cleared>,type=0,code=0),zone=<cleared>
>
> +tcp,orig=(src=192.168.1.3,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=172.16.1.4,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>
> +udp,orig=(src=192.168.1.3,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=172.16.1.4,sport=<cleared>,dport=<cleared>),zone=<cleared>
>  ])
>
>  AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>
> -# South-North SNAT: 'bar1' pings 'alice1'. But 'alice1' receives traffic
> +# South-North SNAT: 'bar1' reaches 'alice1'. But 'alice1' receives traffic
>  # from 172.16.1.1
> -NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.2 |
> FORMAT_PING], \
> -[0], [dnl
> -3 packets transmitted, 3 received, 0% packet loss, time 0ms
> -])
> +NS_CHECK_CONNECTIVITY([bar1], [alice1], 172.16.1.2)
>
>  # We verify that SNAT indeed happened via 'dump-conntrack' command.
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.1) | \
>  sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>
>  
> icmp,orig=(src=192.168.2.2,dst=172.16.1.2,id=<cleared>,type=8,code=0),reply=(src=172.16.1.2,dst=172.16.1.1,id=<cleared>,type=0,code=0),zone=<cleared>
>
> +tcp,orig=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=172.16.1.1,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>
> +udp,orig=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=172.16.1.1,sport=<cleared>,dport=<cleared>),zone=<cleared>
>  ])
>
> +# North-South Direct (Bypassing DNAT): 'alice1' reaches 'foo1' using
> 192.168.1.2
> +NS_CHECK_CONNECTIVITY([alice1], [foo1], [192.168.1.2])
> +
> +# North-South Direct (Bypassing SNAT): 'alice1' reaches 'bar1' using
> 192.168.2.2
> +NS_CHECK_CONNECTIVITY([alice1], [bar1], [192.168.2.2])
> +
>  # Try to ping external network
>  NETNS_START_TCPDUMP([ext-net], [-n -c 3 -i ext-veth dst 172.16.1.3 and
> icmp], [ext-net])
>  AT_CHECK([ovn-nbctl lr-nat-del R1 snat])
> @@ -3825,43 +3828,6 @@ NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2
> 10.0.0.1 | FORMAT_PING], \
>  3 packets transmitted, 3 received, 0% packet loss, time 0ms
>  ])
>
> -# test_connectivity_from_ext takes parameters 'vm' and 'ip'. It tests
> -# icmp, udp and tcp connectivity from external network to the 'vm' on
> -# the specified 'ip'.
> -test_connectivity_from_ext() {
> -    local vm=$1; shift
> -    local ip=$1; shift
> -
> -    # Start listening daemon TCP connections.
> -    NETNS_DAEMONIZE($vm, [nc -l -k 1235], [nc-$vm-$ip-tcp.pid])
> -
> -    # Ensure that vm can be pinged on the specified IP
> -    NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 $ip | FORMAT_PING],
> \
> -    [0], [dnl
> -3 packets transmitted, 3 received, 0% packet loss, time 0ms
> -])
> -
> -    # Perform two consecutive UDP connections to the specified IP
> -    NETNS_DAEMONIZE($vm, [nc -l -u 1234], [nc-$vm-$ip-udp.pid])
> -    NS_CHECK_EXEC([alice1], [nc -u $ip 1234 -p 2000 -z])
> -    kill $(cat nc-$vm-$ip-udp.pid)
> -
> -    NETNS_DAEMONIZE($vm, [nc -l -u 1234], [nc-$vm-$ip-udp.pid])
> -    NS_CHECK_EXEC([alice1], [nc -u $ip 1234 -p 2000 -z])
> -    kill $(cat nc-$vm-$ip-udp.pid)
> -
> -    # Send data over TCP connection to the specified IP
> -    NS_CHECK_EXEC([alice1], [echo "TCP test" | nc --send-only $ip 1235])
> -}
> -
> -# Test access from external network to the internal IP of a VM that
> -# has also configured DNAT
> -test_connectivity_from_ext foo1 192.168.1.2
> -
> -# Test access from external network to the internal IP of a VM that
> -# does not have DNAT
> -test_connectivity_from_ext bar1 192.168.2.2
> -
>  OVS_WAIT_UNTIL([
>      total_pkts=$(cat ext-net.tcpdump | wc -l)
>      test "${total_pkts}" = "3"
> @@ -3980,26 +3946,22 @@ AT_CHECK([ovn-nbctl lr-nat-add R1 snat fd20::1
> ::/0])
>  check ovn-nbctl --wait=hv sync
>  OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=fd20::1)'])
>
> -# North-South DNAT: 'alice1' pings 'foo1' using fd20::3
> -NS_CHECK_EXEC([alice1], [ping6 -q -c 3 -i 0.3 -w 2 fd20::3 |
> FORMAT_PING], \
> -[0], [dnl
> -3 packets transmitted, 3 received, 0% packet loss, time 0ms
> -])
> +# North-South DNAT: 'alice1' reaches 'foo1' using fd20::3
> +NS_CHECK_CONNECTIVITY_V6([alice1], [foo1], [fd20::3])
>
>  # We verify that DNAT indeed happened via 'dump-conntrack' command.
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd20::3) | \
>  sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>
>  
> icmpv6,orig=(src=fd20::2,dst=fd20::3,id=<cleared>,type=128,code=0),reply=(src=fd11::2,dst=fd20::2,id=<cleared>,type=129,code=0),zone=<cleared>
>
> +tcp,orig=(src=fd20::2,dst=fd20::3,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd20::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>
> +udp,orig=(src=fd20::2,dst=fd20::3,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd20::2,sport=<cleared>,dport=<cleared>),zone=<cleared>
>  ])
>
>  AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>
> -# South-North SNAT: 'foo2' pings 'alice1'. But 'alice1' receives traffic
> +# South-North SNAT: 'foo2' reaches 'alice1'. But 'alice1' receives traffic
>  # from fd20::4
> -NS_CHECK_EXEC([foo2], [ping6 -q -c 3 -i 0.3 -w 2 fd20::2 | FORMAT_PING], \
> -[0], [dnl
> -3 packets transmitted, 3 received, 0% packet loss, time 0ms
> -])
> +NS_CHECK_CONNECTIVITY_V6([foo2], [alice1], fd20::2)
>
>  ovs-appctl dpctl/dump-conntrack | grep icmpv6
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd11::3) | \
> @@ -4010,59 +3972,29 @@ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0],
> [dnl
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd20::4) | \
>  sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>
>  
> icmpv6,orig=(src=fd11::3,dst=fd20::2,id=<cleared>,type=128,code=0),reply=(src=fd20::2,dst=fd20::4,id=<cleared>,type=129,code=0),zone=<cleared>
>
> +tcp,orig=(src=fd11::3,dst=fd20::2,sport=<cleared>,dport=<cleared>),reply=(src=fd20::2,dst=fd20::4,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>
> +udp,orig=(src=fd11::3,dst=fd20::2,sport=<cleared>,dport=<cleared>),reply=(src=fd20::2,dst=fd20::4,sport=<cleared>,dport=<cleared>),zone=<cleared>
>  ])
>
>  AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>
> -# South-North SNAT: 'bar1' pings 'alice1'. But 'alice1' receives traffic
> +# South-North SNAT: 'bar1' reaches 'alice1'. But 'alice1' receives traffic
>  # from fd20::1
> -NS_CHECK_EXEC([bar1], [ping6 -q -c 3 -i 0.3 -w 2 fd20::2 | FORMAT_PING], \
> -[0], [dnl
> -3 packets transmitted, 3 received, 0% packet loss, time 0ms
> -])
> +NS_CHECK_CONNECTIVITY_V6([bar1], [alice1], fd20::2)
>
>  # We verify that SNAT indeed happened via 'dump-conntrack' command.
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd20::1) | \
>  sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>
>  
> icmpv6,orig=(src=fd12::2,dst=fd20::2,id=<cleared>,type=128,code=0),reply=(src=fd20::2,dst=fd20::1,id=<cleared>,type=129,code=0),zone=<cleared>
>
> +tcp,orig=(src=fd12::2,dst=fd20::2,sport=<cleared>,dport=<cleared>),reply=(src=fd20::2,dst=fd20::1,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
>
> +udp,orig=(src=fd12::2,dst=fd20::2,sport=<cleared>,dport=<cleared>),reply=(src=fd20::2,dst=fd20::1,sport=<cleared>,dport=<cleared>),zone=<cleared>
>  ])
>
> -# test_connectivity_from_ext takes parameters 'vm' and 'ip'. It tests
> -# icmp, udp and tcp connectivity from external network to the 'vm' on
> -# the specified 'ip'.
> -test_connectivity_from_ext() {
> -    local vm=$1; shift
> -    local ip=$1; shift
> -
> -    # Start listening daemon for TCP connections.
> -    NETNS_DAEMONIZE($vm, [nc -6 -l -k 1235], [nc-$vm-$ip-tcp.pid])
> -
> -    # Ensure that vm can be pinged on the specified IP
> -    NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 $ip | FORMAT_PING],
> \
> -    [0], [dnl
> -3 packets transmitted, 3 received, 0% packet loss, time 0ms
> -])
> -
> -    # Perform two consecutive UDP connections to the specified IP
> -    NETNS_DAEMONIZE($vm, [nc -6 -l -u 1234], [nc-$vm-$ip-udp.pid])
> -    NS_CHECK_EXEC([alice1], [nc -u $ip 1234 -p 2000 -z])
> -    kill $(cat nc-$vm-$ip-udp.pid)
> -
> -    NETNS_DAEMONIZE($vm, [nc -6 -l -u 1234], [nc-$vm-$ip-udp.pid])
> -    NS_CHECK_EXEC([alice1], [nc -u $ip 1234 -p 2000 -z])
> -    kill $(cat nc-$vm-$ip-udp.pid)
> -
> -    # Send data over TCP connection to the specified IP
> -    NS_CHECK_EXEC([alice1], [echo "TCP test" | nc --send-only $ip 1235])
> -}
> -
> -# Test access from external network to the internal IP of a VM that
> -# has also configured DNAT
> -test_connectivity_from_ext foo1 fd11::2
> +# North-South Direct (Bypassing DNAT): 'alice1' reaches 'foo1' using
> fd11::2
> +NS_CHECK_CONNECTIVITY_V6([alice1], [foo1], [fd11::2])
>
> -# Test access from external network to the internal IP of a VM that
> -# does not have DNAT
> -test_connectivity_from_ext bar1 fd12::2
> +# North-South Direct (Bypassing SNAT): 'alice1' reaches 'bar1' using
> fd12::2
> +NS_CHECK_CONNECTIVITY_V6([alice1], [bar1], [fd12::2])
>
>  OVN_CLEANUP_CONTROLLER([hv1])
>
> --
> 2.48.1
>
>
Thank you Martin,

I went ahead and merged this into main and backported down to  24.09.

Regards,
Ales
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to