[Bug 1773] tproxy with nftables collides with nat entries

bugzilla-daemon at netfilter.org bugzilla-daemon at netfilter.org
Thu Sep 12 22:36:15 CEST 2024


https://bugzilla.netfilter.org/show_bug.cgi?id=1773

--- Comment #4 from Antonio Ojea <antonio.ojea.garcia at gmail.com> ---
Testcase using selftest to reproduce the problem, you can see that if you
remove the DNAT rules it "transparently proxy" the connection



diff --git a/tools/testing/selftests/net/netfilter/nft_tproxy_udp_nat.sh
b/tools/testing/selftests/net/netfilter/nft_tproxy_udp_nat.sh
new file mode 100755
index 000000000000..f0b261a019a3
--- /dev/null
+++ b/tools/testing/selftests/net/netfilter/nft_tproxy_udp_nat.sh
@@ -0,0 +1,283 @@
+#!/bin/bash
+#
+# This tests tproxy on the following scenario:
+#
+#                         +------------+
+# +-------+               |  nsrouter  |                  +-------+
+# |ns1    |.99          .1|            |.1             .99|    ns2|
+# |   eth0|---------------|veth0  veth1|------------------|eth0   |
+# |       |  10.0.1.0/24  |            |   10.0.2.0/24    |       |
+# +-------+  dead:1::/64  |    veth2   |   dead:2::/64    +-------+
+#                         +------------+
+#                                |.1
+#                                |
+#                                |
+#                                |                        +-------+
+#                                |                     .99|    ns3|
+#                                +------------------------|eth0   |
+#                                       10.0.3.0/24       |       |
+#                                       dead:3::/64       +-------+
+#
+# The tproxy implementation acts as an echo server so the client
+# must receive the same message it sent if it has been proxied.
+# If is not proxied the servers return PONG_NS# with the number
+# of the namespace the server is running.
+# shellcheck disable=SC2162,SC2317
+
+source lib.sh
+ret=0
+# UDP is slow
+timeout=30
+
+cleanup()
+{
+       ip netns pids "$ns1" | xargs kill 2>/dev/null
+       ip netns pids "$ns2" | xargs kill 2>/dev/null
+       ip netns pids "$ns3" | xargs kill 2>/dev/null
+       ip netns pids "$nsrouter" | xargs kill 2>/dev/null
+
+       cleanup_all_ns
+}
+
+checktool "nft --version" "test without nft tool"
+checktool "socat -h" "run test without socat"
+
+trap cleanup EXIT
+setup_ns ns1 ns2 ns3 nsrouter
+
+if ! ip link add veth0 netns "$nsrouter" type veth peer name eth0 netns "$ns1"
> /dev/null 2>&1; then
+    echo "SKIP: No virtual ethernet pair device support in kernel"
+    exit $ksft_skip
+fi
+ip link add veth1 netns "$nsrouter" type veth peer name eth0 netns "$ns2"
+ip link add veth2 netns "$nsrouter" type veth peer name eth0 netns "$ns3"
+
+ip -net "$nsrouter" link set veth0 up
+ip -net "$nsrouter" addr add 10.0.1.1/24 dev veth0
+ip -net "$nsrouter" addr add dead:1::1/64 dev veth0 nodad
+
+ip -net "$nsrouter" link set veth1 up
+ip -net "$nsrouter" addr add 10.0.2.1/24 dev veth1
+ip -net "$nsrouter" addr add dead:2::1/64 dev veth1 nodad
+
+ip -net "$nsrouter" link set veth2 up
+ip -net "$nsrouter" addr add 10.0.3.1/24 dev veth2
+ip -net "$nsrouter" addr add dead:3::1/64 dev veth2 nodad
+
+ip -net "$ns1" link set eth0 up
+ip -net "$ns2" link set eth0 up
+ip -net "$ns3" link set eth0 up
+
+ip -net "$ns1" addr add 10.0.1.99/24 dev eth0
+ip -net "$ns1" addr add dead:1::99/64 dev eth0 nodad
+ip -net "$ns1" route add default via 10.0.1.1
+ip -net "$ns1" route add default via dead:1::1
+
+ip -net "$ns2" addr add 10.0.2.99/24 dev eth0
+ip -net "$ns2" addr add dead:2::99/64 dev eth0 nodad
+ip -net "$ns2" route add default via 10.0.2.1
+ip -net "$ns2" route add default via dead:2::1
+
+ip -net "$ns3" addr add 10.0.3.99/24 dev eth0
+ip -net "$ns3" addr add dead:3::99/64 dev eth0 nodad
+ip -net "$ns3" route add default via 10.0.3.1
+ip -net "$ns3" route add default via dead:3::1
+
+ip netns exec "$nsrouter" sysctl net.ipv6.conf.all.forwarding=1 > /dev/null
+ip netns exec "$nsrouter" sysctl net.ipv4.conf.veth0.forwarding=1 > /dev/null
+ip netns exec "$nsrouter" sysctl net.ipv4.conf.veth1.forwarding=1 > /dev/null
+ip netns exec "$nsrouter" sysctl net.ipv4.conf.veth2.forwarding=1 > /dev/null
+
+test_ping() {
+  if ! ip netns exec "$ns1" ping -c 1 -q 10.0.2.99 > /dev/null; then
+       return 1
+  fi
+
+  if ! ip netns exec "$ns1" ping -c 1 -q dead:2::99 > /dev/null; then
+       return 2
+  fi
+
+  if ! ip netns exec "$ns1" ping -c 1 -q 10.0.3.99 > /dev/null; then
+       return 1
+  fi
+
+  if ! ip netns exec "$ns1" ping -c 1 -q dead:3::99 > /dev/null; then
+       return 2
+  fi
+
+  return 0
+}
+
+test_ping_router() {
+  if ! ip netns exec "$ns1" ping -c 1 -q 10.0.2.1 > /dev/null; then
+       return 3
+  fi
+
+  if ! ip netns exec "$ns1" ping -c 1 -q dead:2::1 > /dev/null; then
+       return 4
+  fi
+
+  return 0
+}
+
+
+listener_ready()
+{
+       local ns="$1"
+       local port="$2"
+       local proto="$3"
+       ss -N "$ns" -ln "$proto" -o "sport = :$port" | grep -q "$port"
+}
+
+test_tproxy_udp_forward_nat()
+{
+       local ip_proto="$1"
+
+       local expect_ns1_ns2="I_M_PROXIED"
+       local expect_ns1_ns3="PONG_NS3"
+       local expect_nsrouter_ns2="PONG_NS2"
+       local expect_nsrouter_ns3="PONG_NS3"
+
+       # derived variables
+       local testname="test_${ip_proto}_udp_forward_dnat"
+       local socat_ipproto
+       local virtual_ip
+       local virtual_ip_port
+       local ns1_ip
+       local ns2_ip
+       local ns3_ip
+       local ns1_ip_port
+       local ns2_ip_port
+       local ns3_ip_port
+       local ip_command
+
+       # socat 1.8.0 has a bug that requires to specify the IP family to bind
(fixed in 1.8.0.1)
+       case $ip_proto in
+       "ip")
+               socat_ipproto="-4"
+               virtual_ip=10.100.100.10
+               virtual_ip_port="$virtual_ip:8080"
+               ns1_ip=10.0.1.99
+               ns2_ip=10.0.2.99
+               ns3_ip=10.0.3.99
+               ns1_ip_port="$ns1_ip:18888"
+               ns2_ip_port="$ns2_ip:8080"
+               ns3_ip_port="$ns3_ip:8080"
+               ip_command="ip"
+       ;;
+       "ip6")
+               socat_ipproto="-6"
+               virtual_ip=dead:100:100::10
+               virtual_ip_port="[$virtual_ip]:8080"
+               ns1_ip=dead:1::99
+               ns2_ip=dead:2::99
+               ns3_ip=dead:3::99
+               ns1_ip_port="[$ns1_ip]:18888"
+               ns2_ip_port="[$ns2_ip]:8080"
+               ns3_ip_port="[$ns3_ip]:8080"
+               ip_command="ip -6"
+       ;;
+       *)
+       echo "FAIL: unsupported protocol"
+       exit 255
+       ;;
+       esac
+
+       # shellcheck disable=SC2046 # Intended splitting of ip_command
+       ip netns exec "$nsrouter" $ip_command rule add fwmark 1 table 100
+       ip netns exec "$nsrouter" $ip_command route add local "$virtual_ip" dev
lo table 100
+       ip netns exec "$nsrouter" nft -f /dev/stdin <<EOF
+flush ruleset
+table inet filter {
+       chain divert {
+               type filter hook prerouting priority -100; policy accept;
+               $ip_proto daddr $virtual_ip udp dport 8080 tproxy $ip_proto to
:12345 meta mark set 1 accept
+       }
+       # Removing this chain makes the first connection to succeed
+       chain PREROUTING {
+               type nat hook prerouting priority 1; policy accept;
+               $ip_proto daddr $virtual_ip udp dport 8080 dnat to numgen inc
mod 2 map { 0 : $ns2_ip , 1: $ns3_ip }
+       }
+}
+EOF
+
+       timeout "$timeout" ip netns exec "$nsrouter" socat -u "$socat_ipproto"
udp-listen:12345,fork,ip-transparent,reuseport
udp:"$ns1_ip_port",ip-transparent,reuseport,bind="$virtual_ip_port" 2>/dev/null
&
+       local tproxy_pid=$!
+
+       timeout "$timeout" ip netns exec "$ns2" socat "$socat_ipproto"
udp-listen:8080,fork SYSTEM:"echo PONG_NS2" 2>/dev/null &
+       local server2_pid=$!
+
+       timeout "$timeout" ip netns exec "$ns3" socat "$socat_ipproto"
udp-listen:8080,fork SYSTEM:"echo PONG_NS3" 2>/dev/null &
+       local server3_pid=$!
+
+       busywait "$BUSYWAIT_TIMEOUT" listener_ready "$nsrouter" 12345 "-u"
+       busywait "$BUSYWAIT_TIMEOUT" listener_ready "$ns2" 8080 "-u"
+       busywait "$BUSYWAIT_TIMEOUT" listener_ready "$ns3" 8080 "-u"
+
+       local result
+       # request from ns1 to ns2 (forwarded traffic)
+       result=$(echo I_M_PROXIED | ip netns exec "$ns1" socat -t 2 -T 2 STDIO
udp:"$virtual_ip_port",sourceport=18888)
+       if [ "$result" == "$expect_ns1_ns2" ] ;then
+               echo "PASS: tproxy test $testname: ns1 got reply \"$result\"
connecting to $virtual_ip_port"
+       else
+               echo "ERROR: tproxy test $testname: ns1 got reply \"$result\"
connecting to $virtual_ip_port, not \"${expect_ns1_ns2}\" as intended"
+               ret=1
+       fi
+
+       # request from ns1 to ns3 (forwarded traffic)
+       result=$(echo I_M_PROXIED | ip netns exec "$ns1" socat -t 2 -T 2 STDIO
udp:"$ns3_ip_port")
+       if [ "$result" = "$expect_ns1_ns3" ] ;then
+               echo "PASS: tproxy test $testname: ns1 got reply \"$result\"
connecting to ns3"
+       else
+               echo "ERROR: tproxy test $testname: ns1 got reply \"$result\"
connecting to ns3, not \"$expect_ns1_ns3\" as intended"
+               ret=1
+       fi
+
+       # request from nsrouter to ns2 (localy originated traffic)
+       result=$(echo I_M_PROXIED | ip netns exec "$nsrouter" socat -t 2 -T 2
STDIO udp:"$ns2_ip_port")
+       if [ "$result" == "$expect_nsrouter_ns2" ] ;then
+               echo "PASS: tproxy test $testname: nsrouter got reply
\"$result\" connecting to ns2"
+       else
+               echo "ERROR: tproxy test $testname: nsrouter got reply
\"$result\" connecting to ns2, not \"$expect_nsrouter_ns2\" as intended"
+               ret=1
+       fi
+
+       # request from nsrouter to ns3 (localy originated traffic)
+       result=$(echo I_M_PROXIED | ip netns exec "$nsrouter" socat -t 2 -T 2
STDIO udp:"$ns3_ip_port")
+       if [ "$result" = "$expect_nsrouter_ns3" ] ;then
+               echo "PASS: tproxy test $testname: nsrouter got reply
\"$result\" connecting to ns3"
+       else
+               echo "ERROR: tproxy test $testname: nsrouter got reply
\"$result\" connecting to ns3, not \"$expect_nsrouter_ns3\"  as intended"
+               ret=1
+       fi
+
+       # request from ns1 to ns2 (forwarded traffic)
+       result=$(echo I_M_PROXIED | ip netns exec "$ns1" socat -t 2 -T 2 STDIO
udp:"$virtual_ip_port",sourceport=18888)
+       if [ "$result" == "$expect_ns1_ns2" ] ;then
+               echo "PASS: tproxy test $testname: ns1 got reply \"$result\"
connecting to $virtual_ip_port"
+       else
+               echo "ERROR: tproxy test $testname: ns1 got reply \"$result\"
connecting to $virtual_ip_port, not \"${expect_ns1_ns2}\" as intended"
+               ret=1
+       fi
+
+
+       # cleanup
+       kill "$tproxy_pid" "$server2_pid" "$server3_pid" 2>/dev/null
+       # shellcheck disable=SC2046 # Intended splitting of ip_command
+       ip netns exec "$nsrouter" $ip_command rule del fwmark 1 table 100
+       ip netns exec "$nsrouter" $ip_command route flush table 100
+}
+
+
+if test_ping; then
+       # queue bypass works (rules were skipped, no listener)
+       echo "PASS: ${ns1} can reach ${ns2}"
+else
+       echo "FAIL: ${ns1} cannot reach ${ns2}: $ret" 1>&2
+       exit $ret
+fi
+
+test_tproxy_udp_forward_nat "ip"
+test_tproxy_udp_forward_nat "ip6"
+
+exit $ret
(END)
+       # cleanup
+       kill "$tproxy_pid" "$server2_pid" "$server3_pid" 2>/dev/null
+       # shellcheck disable=SC2046 # Intended splitting of ip_command
+       ip netns exec "$nsrouter" $ip_command rule del fwmark 1 table 100
+       ip netns exec "$nsrouter" $ip_command route flush table 100
+}
+
+
+if test_ping; then
+       # queue bypass works (rules were skipped, no listener)
+       echo "PASS: ${ns1} can reach ${ns2}"
+else
+       echo "FAIL: ${ns1} cannot reach ${ns2}: $ret" 1>&2
+       exit $ret
+fi
+
+test_tproxy_udp_forward_nat "ip"
+test_tproxy_udp_forward_nat "ip6"
+
+exit $ret

-- 
You are receiving this mail because:
You are watching all bug changes.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.netfilter.org/pipermail/netfilter-buglog/attachments/20240912/cfbebfb3/attachment-0001.html>


More information about the netfilter-buglog mailing list