Add a test where a host behind a bridge on the router moves to another
port of that bridge while its flow is offloaded, as a station moving
between two access points attached to ports of one bridge. The host keeps
its address and mac on a bridge of its own and roams by moving that
bridge's port, so only the fdb changes. A sender streams tcp to the host;
the test checks that the flow was offloaded before the roam, that the
host's received byte counter advances after it, and that the flow is
offloaded again. The test runs once with plain veth bridge ports and once
with vlan devices as bridge ports.

Without the teardown the reply direction keeps going to the old port and
the transfer stalls. CONFIG_NET_SWITCHDEV is needed for the bridge to
notify the fdb move.

Signed-off-by: Julius Bairaktaris <[email protected]>
Assisted-by: LLM
---
 tools/testing/selftests/net/netfilter/config  |   1 +
 .../selftests/net/netfilter/nft_flowtable.sh  | 140 ++++++++++++++++++
 2 files changed, 141 insertions(+)

diff --git a/tools/testing/selftests/net/netfilter/config 
b/tools/testing/selftests/net/netfilter/config
index c3c121b6f300..d4e7249cb5b2 100644
--- a/tools/testing/selftests/net/netfilter/config
+++ b/tools/testing/selftests/net/netfilter/config
@@ -60,6 +60,7 @@ CONFIG_NET_NS=y
 CONFIG_NET_PKTGEN=m
 CONFIG_NET_SCH_HTB=m
 CONFIG_NET_SCH_NETEM=m
+CONFIG_NET_SWITCHDEV=y
 CONFIG_NET_VRF=y
 CONFIG_NF_CONNTRACK=m
 CONFIG_NF_CONNTRACK_EVENTS=y
diff --git a/tools/testing/selftests/net/netfilter/nft_flowtable.sh 
b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
index 449c518bd947..0d9f7d771fac 100755
--- a/tools/testing/selftests/net/netfilter/nft_flowtable.sh
+++ b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
@@ -851,6 +851,146 @@ test_ipip
 
 test_bridge
 
+# Roam a host between two ports of a bridge on the router while its flow is
+# offloaded, as a station moving between two access points attached to ports
+# of one bridge. The host keeps its address and mac on a bridge of its own
+# and roams by moving that bridge's port, so only the fdb changes. With a vid
+# the bridge ports are vlan devices, whose ifindex differs from the device
+# the flow transmits on. A flow that stays on the fast path leaves no more
+# than a few packets in the forward chain, the counter tells offloaded from
+# slow path.
+test_bridge_roam()
+{
+       local vid=$1
+       local p0=sr0 p1=sr1 r0=rr0 r1=rr1
+       local what="roaming on bridge${vid:+ with vlan ports}"
+       local dev old new slow cpid
+
+       setup_ns rns1 rnsr rns2
+
+       if ! ip -net "$rnsr" link add br0 type bridge 2>/dev/null; then
+               echo "SKIP: could not add bridge for $what"
+               [ "$ret" -eq 0 ] && ret=$ksft_skip
+               return
+       fi
+
+       ip link add sr0 netns "$rns1" type veth peer name rr0 netns "$rnsr"
+       ip link add sr1 netns "$rns1" type veth peer name rr1 netns "$rnsr"
+       ip link add rw0 netns "$rnsr" type veth peer name w0 netns "$rns2"
+       for dev in sr0 sr1; do
+               ip -net "$rns1" link set "$dev" up
+       done
+       for dev in rr0 rr1; do
+               ip -net "$rnsr" link set "$dev" up
+       done
+
+       if [ -n "$vid" ]; then
+               for dev in sr0 sr1; do
+                       ip -net "$rns1" link add link "$dev" name "$dev.$vid" 
type vlan id "$vid"
+                       ip -net "$rns1" link set "$dev.$vid" up
+               done
+               for dev in rr0 rr1; do
+                       ip -net "$rnsr" link add link "$dev" name "$dev.$vid" 
type vlan id "$vid"
+                       ip -net "$rnsr" link set "$dev.$vid" up
+               done
+               p0=sr0.$vid; p1=sr1.$vid; r0=rr0.$vid; r1=rr1.$vid
+       fi
+
+       ip -net "$rnsr" link set "$r0" master br0
+       ip -net "$rnsr" link set "$r1" master br0
+       ip -net "$rnsr" addr add 10.9.1.1/24 dev br0
+       ip -net "$rnsr" link set br0 up
+       ip -net "$rnsr" addr add 10.9.2.1/24 dev rw0
+       ip -net "$rnsr" link set rw0 up
+       ip netns exec "$rnsr" sysctl -q net.ipv4.ip_forward=1
+
+       ip -net "$rns1" link add stbr type bridge
+       ip -net "$rns1" link set stbr address 02:00:00:09:01:99
+       ip -net "$rns1" link set "$p0" master stbr
+       ip -net "$rns1" link set stbr up
+       ip -net "$rns1" addr add 10.9.1.99/24 dev stbr
+       ip -net "$rns1" route add default via 10.9.1.1
+
+       ip -net "$rns2" addr add 10.9.2.99/24 dev w0
+       ip -net "$rns2" link set w0 up
+       ip -net "$rns2" route add default via 10.9.2.1
+
+       if ! ip netns exec "$rnsr" nft -f - <<EOF
+table inet filter {
+  counter roam_repl { }
+  flowtable f1 { hook ingress priority 0; devices = { rr0, rr1, rw0 }; }
+  chain forward {
+    type filter hook forward priority 0; policy accept;
+    meta oif "rw0" tcp dport 12345 flow add @f1
+    meta iif "rw0" tcp sport 12345 counter name roam_repl flow add @f1
+  }
+}
+EOF
+       then
+               echo "SKIP: could not load ruleset for $what"
+               [ "$ret" -eq 0 ] && ret=$ksft_skip
+               return
+       fi
+
+       # the host discards what it receives, its socket byte counter is read
+       rx_bytes() { ip netns exec "$rns1" ss -tin "dst 10.9.2.99" 2>/dev/null |
+               grep -o 'bytes_received:[0-9]*' | head -1 | cut -d: -f2; }
+       rx_started() { local n; n=$(rx_bytes); [ "${n:-0}" -gt 0 ]; }
+       # forward chain packets of the reply direction since the last call
+       slow_pkts() { ip netns exec "$rnsr" nft reset counter inet filter 
roam_repl |
+               grep -o 'packets [0-9]*' | cut -d' ' -f2; }
+       roam_listener_ready() { ss -N "$rns2" -lnt -o "sport = :12345" | grep 
-q 12345; }
+
+       timeout "$SOCAT_TIMEOUT" ip netns exec "$rns2" socat -u \
+               OPEN:/dev/zero TCP-LISTEN:12345,reuseaddr &
+       busywait 1000 roam_listener_ready
+       timeout "$SOCAT_TIMEOUT" ip netns exec "$rns1" socat -u \
+               TCP:10.9.2.99:12345 OPEN:/dev/null &
+       cpid=$!
+
+       busywait "$BUSYWAIT_TIMEOUT" rx_started
+       sleep 2
+       slow_pkts > /dev/null
+       sleep 2
+       slow=$(slow_pkts)
+       if [ "${slow:-0}" -gt 100 ]; then
+               echo "FAIL: $what: $slow forward chain packets before the roam" 
1>&2
+               ret=1
+               kill "$cpid" 2>/dev/null
+               return
+       fi
+
+       # roam: move the host bridge to the other port and send on it
+       ip -net "$rns1" link set "$p0" nomaster
+       ip -net "$rns1" link set "$p1" master stbr
+       ip netns exec "$rns1" ping -c 2 -W 1 -q 10.9.1.1 >/dev/null 2>&1
+
+       # the bytes in flight at the roam drain within the first seconds; a
+       # stale flow delivers nothing after that, a torn down one resumes and
+       # is offloaded again.
+       sleep 4
+       old=$(rx_bytes)
+       sleep 4
+       new=$(rx_bytes)
+       slow_pkts > /dev/null
+       sleep 2
+       slow=$(slow_pkts)
+       kill "$cpid" 2>/dev/null
+
+       if [ "${new:-0}" -le "${old:-0}" ]; then
+               echo "FAIL: $what: transfer stalled after the roam" 1>&2
+               ret=1
+       elif [ "${slow:-0}" -gt 100 ]; then
+               echo "FAIL: $what: $slow forward chain packets after the roam" 
1>&2
+               ret=1
+       else
+               echo "PASS: flow offload for $what"
+       fi
+}
+
+test_bridge_roam ""
+test_bridge_roam 100
+
 KEY_SHA="0x"$(ps -af | sha1sum | cut -d " " -f 1)
 KEY_AES="0x"$(ps -af | md5sum | cut -d " " -f 1)
 SPI1=$RANDOM
-- 
2.53.0


Reply via email to