]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
tests: shell: add a test case for dup expression
authorFlorian Westphal <fw@strlen.de>
Wed, 6 May 2026 12:22:40 +0000 (14:22 +0200)
committerFlorian Westphal <fw@strlen.de>
Thu, 7 May 2026 22:08:51 +0000 (00:08 +0200)
Signed-off-by: Florian Westphal <fw@strlen.de>
tests/shell/testcases/packetpath/dumps/dup.nodump [new file with mode: 0644]
tests/shell/testcases/packetpath/dumps/dup_ip.nodump [new file with mode: 0644]
tests/shell/testcases/packetpath/dup [new file with mode: 0755]
tests/shell/testcases/packetpath/dup_ip [new file with mode: 0755]

diff --git a/tests/shell/testcases/packetpath/dumps/dup.nodump b/tests/shell/testcases/packetpath/dumps/dup.nodump
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/shell/testcases/packetpath/dumps/dup_ip.nodump b/tests/shell/testcases/packetpath/dumps/dup_ip.nodump
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/shell/testcases/packetpath/dup b/tests/shell/testcases/packetpath/dup
new file mode 100755 (executable)
index 0000000..a58fd1e
--- /dev/null
@@ -0,0 +1,160 @@
+#!/bin/bash
+
+# Test case for nftables dup expression with bridge
+# Tests that packets are forwarded to the correct device using "dup to"
+# Setup: ns1 (sender) -> ns2 (bridge) -> ns3/ns4 (receivers)
+
+rnd=$(mktemp -u XXXXXXXX)
+ns1="nft1dupbr-$rnd"
+ns2="nft2dupbr-$rnd"
+ns3="nft3dupbr-$rnd"
+ns4="nft4dupbr-$rnd"
+
+cleanup() {
+       ip netns del "$ns1" 2>/dev/null
+       ip netns del "$ns2" 2>/dev/null
+       ip netns del "$ns3" 2>/dev/null
+       ip netns del "$ns4" 2>/dev/null
+}
+
+die() {
+       local n="$1"
+
+       ip netns exec "$n" $NFT list ruleset
+
+       echo "ns2 (router)"
+       ip netns exec "$ns2" $NFT list ruleset
+       exit 1
+}
+
+ping_and_check() {
+       ip netns exec "$ns1" ping -c 1 -W 1 10.0.1.4 >/dev/null
+
+       # Check that packets arrived in both ns3 and ns4
+       for n in "$ns3" "$ns4"; do
+               ip netns exec "$n" $NFT "list counter netdev rxt rxc" | grep -q 'packets 1 '
+               if [ $? -ne 0 ]; then
+                       echo "ERROR: counter not incremented in $n"
+                       die "$n"
+               fi
+       done
+}
+
+trap cleanup EXIT
+
+set -e
+
+# Create network namespaces
+for n in "$ns1" "$ns2" "$ns3" "$ns4"; do
+       ip netns add "$n"
+       ip -net "$n" link set lo up
+done
+
+# Create veth pairs:
+# ns1(veth0) <-> (veth1)ns2 bridge port
+# ns3(veth0) <-> (veth3)ns2 bridge port
+# ns4(veth0) <-> (veth4)ns2 bridge port
+ip link add veth0 netns "$ns1" type veth peer name veth1 netns "$ns2"
+ip link add veth0 netns "$ns3" type veth peer name veth3 netns "$ns2"
+ip link add veth0 netns "$ns4" type veth peer name veth4 netns "$ns2"
+
+# Create bridge in ns2
+ip -net "$ns2" link add br0 type bridge
+
+# Add veth devices to bridge in ns2
+ip -net "$ns2" link set veth1 master br0
+ip -net "$ns2" link set veth3 master br0
+ip -net "$ns2" link set veth4 master br0
+
+# Set up IP addresses
+ip -net "$ns1" addr add 10.0.1.1/24 dev veth0
+ip -net "$ns2" addr add 10.0.1.2/24 dev br0
+ip -net "$ns3" addr add 10.0.1.3/24 dev veth0
+ip -net "$ns4" addr add 10.0.1.4/24 dev veth0
+
+# Bring up interfaces
+ip -net "$ns1" link set veth0 up
+ip -net "$ns3" link set veth0 up
+ip -net "$ns4" link set veth0 up
+
+ip -net "$ns2" link set veth1 up
+ip -net "$ns2" link set veth3 up
+ip -net "$ns2" link set veth4 up
+ip -net "$ns2" link set br0 up
+
+# Validate setup:
+ip netns exec "$ns1" ping -q -c 1 10.0.1.3
+ip netns exec "$ns1" ping -q -c 1 10.0.1.4
+
+# Add nftables rule in ns2 to forward packets from veth1 to veth4
+ip netns exec "$ns2" $NFT -f /dev/stdin <<"EOF"
+table netdev dup_bridge_test {
+       chain ingress_veth1 {
+               type filter hook ingress device veth1 priority 0; policy accept;
+
+               # Dup ICMP packets destined for ns4 also to veth3 (ns3)
+               ip daddr 10.0.1.4 ip protocol icmp counter dup to "veth3"
+       }
+}
+EOF
+
+[ $? -ne 0 ] && exit 1
+
+# so ingress hook in ns3 picks up packet for ns4 mac
+ip -net "$ns3" link set veth0 promisc on
+
+for n in "$ns3" "$ns4"; do
+ip netns exec "$n" $NFT -f /dev/stdin <<"EOF"
+table netdev rxt {
+       counter rxc { }
+
+       chain in_veth0 {
+               type filter hook ingress device veth0 priority 0; policy accept;
+               ip protocol icmp counter name "rxc"
+       }
+}
+EOF
+done
+
+# Verify normal bridge forwarding still works for non-ICMP traffic
+# Test that ns3/ns4 are reachable from ns1 without dup interference.
+ip netns exec "$ns1" arping -c 1 -I veth0 10.0.1.3 >/dev/null
+ip netns exec "$ns1" arping -c 1 -I veth0 10.0.1.4 >/dev/null
+
+set +e
+ping_and_check
+
+# again, but use egress hook.
+ip netns exec "$ns2" $NFT -f /dev/stdin <<"EOF"
+flush ruleset
+table netdev dup_test {
+       chain egress_veth4 {
+               type filter hook egress device veth4 priority 0; policy accept;
+               ip protocol icmp counter dup to "veth3"
+       }
+}
+EOF
+[ $? -ne 0 ] && exit 1
+
+echo "Egress ruleset loaded"
+ip netns exec "$ns3" $NFT "reset counter netdev rxt rxc"
+ip netns exec "$ns4" $NFT "reset counter netdev rxt rxc"
+ping_and_check
+
+ip netns exec "$ns4" $NFT "reset counter netdev rxt rxc"
+ip netns exec "$ns3" $NFT "reset counter netdev rxt rxc"
+ip netns exec "$ns2" $NFT -f /dev/stdin <<"EOF"
+flush ruleset
+table netdev dup_test {
+       chain egress_veth4 {
+               type filter hook egress device veth4 priority 0; policy accept;
+               ip protocol icmp counter dup to "veth4"
+       }
+}
+EOF
+[ $? -ne 0 ] && exit 1
+
+# assert dup-to-self doesn't crash.
+ip netns exec "$ns1" ping -c 1 -W 1 10.0.1.4
+
+exit 0
diff --git a/tests/shell/testcases/packetpath/dup_ip b/tests/shell/testcases/packetpath/dup_ip
new file mode 100755 (executable)
index 0000000..ce57f60
--- /dev/null
@@ -0,0 +1,149 @@
+#!/bin/bash
+
+# Test case for nftables 'dup to' in ip family.
+
+# ns1 tx to n3, ns4 via ns2 (routing)
+# if ok:
+#    add rules to ns2 to dup to ns3
+#    add ns4 ip to ns3
+#    send packet to ns3 and ns4 ip address
+#    check both arrived at ns3
+#    check no packet arrived at ns4
+#
+# Then repeat with ns2 having 'egress' ruleset.
+
+rnd=$(mktemp -u XXXXXXXX)
+ns1="nft1in-$rnd"      # tx
+ns2="nft2in-$rnd"      # dup (nft rules)
+ns3="nft3in-$rnd"      # rx
+ns4="nft4in-$rnd"      # rx
+
+cleanup() {
+       ip netns del "$ns1" 2>/dev/null
+       ip netns del "$ns2" 2>/dev/null
+       ip netns del "$ns3" 2>/dev/null
+       ip netns del "$ns4" 2>/dev/null
+}
+
+die() {
+       local n="$1"
+
+       ip netns exec "$n" $NFT list ruleset
+
+       echo "ns2 (router)"
+       ip netns exec "$ns2" $NFT list ruleset
+       exit 1
+}
+
+trap cleanup EXIT
+
+ping_and_check() {
+       ip netns exec "$ns1" ping -c 1 -W 1 10.0.4.1 >/dev/null
+
+       # Check that packets arrived in both ns3 and ns4
+       for n in "$ns3" "$ns4"; do
+               ip netns exec "$n" $NFT "list counter inet rxt rxc" | grep -q 'packets 1 '
+               if [ $? -ne 0 ]; then
+                       echo "ERROR: counter not incremented in $n"
+                       die "$n"
+               fi
+       done
+}
+
+set -e
+set -x
+
+for n in "$ns1" "$ns2" "$ns3" "$ns4"; do
+       ip netns add "$n"
+       ip -net "$n" link set lo up
+done
+
+
+# Create veth pairs: ns1(veth0) <-> (veth1)ns2
+#                    ns2(veth3) <-> (veth0)ns3
+#                    ns2(veth4) <-> (veth0)ns4
+ip link add veth0 netns "$ns1" type veth peer name veth1 netns "$ns2"
+ip link add veth3 netns "$ns2" type veth peer name veth0 netns "$ns3"
+ip link add veth4 netns "$ns2" type veth peer name veth0 netns "$ns4"
+
+# Set up addresses
+ip -net "$ns1" addr add 10.0.1.1/24 dev veth0
+
+ip -net "$ns2" addr add 10.0.1.2/24 dev veth1
+ip -net "$ns2" addr add 10.0.3.2/24 dev veth3
+ip -net "$ns2" addr add 10.0.4.2/24 dev veth4
+
+ip -net "$ns3" addr add 10.0.3.1/24 dev veth0
+ip -net "$ns4" addr add 10.0.4.1/24 dev veth0
+
+# Bring up interfaces
+ip -net "$ns1" link set veth0 up
+
+ip -net "$ns2" link set veth1 up
+ip -net "$ns2" link set veth3 up
+ip -net "$ns2" link set veth4 up
+
+ip -net "$ns3" link set veth0 up
+ip -net "$ns4" link set veth0 up
+
+ip netns exec "$ns2" sysctl -q net.ipv4.ip_forward=1
+
+ip -net "$ns1" route add default via 10.0.1.2
+
+ip -net "$ns3" route add default via 10.0.3.2 dev veth0
+ip -net "$ns4" route add default via 10.0.4.2 dev veth0
+
+# Validate setup:
+ip netns exec "$ns1" ping -q -c 1 10.0.3.1
+ip netns exec "$ns1" ping -q -c 1 10.0.4.1
+
+# Add nftables rule in ns2 to forward packets from veth1 to veth3
+# Packets arriving on veth1 (10.0.1.x) will be forwarded to veth3
+ip netns exec "$ns2" $NFT -f /dev/stdin <<"EOF"
+table ip dup_test {
+       chain pre {
+               type filter hook prerouting priority 0; policy accept;
+               # Forward ICMP packets to 10.0.3.1 via neigh
+               meta iifname veth1 ip protocol icmp counter dup to 10.0.3.1 device "veth3"
+       }
+}
+EOF
+[ $? -ne 0 ] && exit 1
+
+for n in "$ns3" "$ns4"; do
+ip netns exec "$n" $NFT -f /dev/stdin <<"EOF"
+table inet rxt {
+       counter rxc { }
+       counter rxc { }
+
+       chain in_veth0 {
+               type filter hook input priority 0; policy accept;
+               ip protocol icmp counter name "rxc"
+       }
+}
+EOF
+done
+
+# duplicate address so stack accepts .4.1 too
+ip -net "$ns3" addr add 10.0.4.1/32 dev veth0
+
+set +e
+ping_and_check
+
+ip netns exec "$ns2" $NFT -f /dev/stdin <<"EOF"
+delete chain ip dup_test pre
+table ip dup_test {
+       chain post {
+               type filter hook postrouting priority 0; policy accept;
+               meta oifname "veth4" ip protocol icmp counter dup to 10.0.3.1 device "veth3"
+       }
+}
+EOF
+[ $? -ne 0 ] && exit 1
+
+echo "Egress ruleset loaded"
+ip netns exec "$ns3" $NFT "reset counter inet rxt rxc"
+ip netns exec "$ns4" $NFT "reset counter inet rxt rxc"
+ping_and_check
+
+exit 0