xref: /linux/tools/testing/selftests/net/netfilter/nft_tproxy_udp.sh (revision 3a39d672e7f48b8d6b91a09afa4b55352773b4b5)
1#!/bin/bash
2#
3# This tests tproxy on the following scenario:
4#
5#                         +------------+
6# +-------+               |  nsrouter  |                  +-------+
7# |ns1    |.99          .1|            |.1             .99|    ns2|
8# |   eth0|---------------|veth0  veth1|------------------|eth0   |
9# |       |  10.0.1.0/24  |            |   10.0.2.0/24    |       |
10# +-------+  dead:1::/64  |    veth2   |   dead:2::/64    +-------+
11#                         +------------+
12#                                |.1
13#                                |
14#                                |
15#                                |                        +-------+
16#                                |                     .99|    ns3|
17#                                +------------------------|eth0   |
18#                                       10.0.3.0/24       |       |
19#                                       dead:3::/64       +-------+
20#
21# The tproxy implementation acts as an echo server so the client
22# must receive the same message it sent if it has been proxied.
23# If is not proxied the servers return PONG_NS# with the number
24# of the namespace the server is running.
25# shellcheck disable=SC2162,SC2317
26
27source lib.sh
28ret=0
29# UDP is slow
30timeout=15
31
32cleanup()
33{
34	ip netns pids "$ns1" | xargs kill 2>/dev/null
35	ip netns pids "$ns2" | xargs kill 2>/dev/null
36	ip netns pids "$ns3" | xargs kill 2>/dev/null
37	ip netns pids "$nsrouter" | xargs kill 2>/dev/null
38
39	cleanup_all_ns
40}
41
42checktool "nft --version" "test without nft tool"
43checktool "socat -h" "run test without socat"
44
45trap cleanup EXIT
46setup_ns ns1 ns2 ns3 nsrouter
47
48if ! ip link add veth0 netns "$nsrouter" type veth peer name eth0 netns "$ns1" > /dev/null 2>&1; then
49    echo "SKIP: No virtual ethernet pair device support in kernel"
50    exit $ksft_skip
51fi
52ip link add veth1 netns "$nsrouter" type veth peer name eth0 netns "$ns2"
53ip link add veth2 netns "$nsrouter" type veth peer name eth0 netns "$ns3"
54
55ip -net "$nsrouter" link set veth0 up
56ip -net "$nsrouter" addr add 10.0.1.1/24 dev veth0
57ip -net "$nsrouter" addr add dead:1::1/64 dev veth0 nodad
58
59ip -net "$nsrouter" link set veth1 up
60ip -net "$nsrouter" addr add 10.0.2.1/24 dev veth1
61ip -net "$nsrouter" addr add dead:2::1/64 dev veth1 nodad
62
63ip -net "$nsrouter" link set veth2 up
64ip -net "$nsrouter" addr add 10.0.3.1/24 dev veth2
65ip -net "$nsrouter" addr add dead:3::1/64 dev veth2 nodad
66
67ip -net "$ns1" link set eth0 up
68ip -net "$ns2" link set eth0 up
69ip -net "$ns3" link set eth0 up
70
71ip -net "$ns1" addr add 10.0.1.99/24 dev eth0
72ip -net "$ns1" addr add dead:1::99/64 dev eth0 nodad
73ip -net "$ns1" route add default via 10.0.1.1
74ip -net "$ns1" route add default via dead:1::1
75
76ip -net "$ns2" addr add 10.0.2.99/24 dev eth0
77ip -net "$ns2" addr add dead:2::99/64 dev eth0 nodad
78ip -net "$ns2" route add default via 10.0.2.1
79ip -net "$ns2" route add default via dead:2::1
80
81ip -net "$ns3" addr add 10.0.3.99/24 dev eth0
82ip -net "$ns3" addr add dead:3::99/64 dev eth0 nodad
83ip -net "$ns3" route add default via 10.0.3.1
84ip -net "$ns3" route add default via dead:3::1
85
86ip netns exec "$nsrouter" sysctl net.ipv6.conf.all.forwarding=1 > /dev/null
87ip netns exec "$nsrouter" sysctl net.ipv4.conf.veth0.forwarding=1 > /dev/null
88ip netns exec "$nsrouter" sysctl net.ipv4.conf.veth1.forwarding=1 > /dev/null
89ip netns exec "$nsrouter" sysctl net.ipv4.conf.veth2.forwarding=1 > /dev/null
90
91test_ping() {
92  if ! ip netns exec "$ns1" ping -c 1 -q 10.0.2.99 > /dev/null; then
93	return 1
94  fi
95
96  if ! ip netns exec "$ns1" ping -c 1 -q dead:2::99 > /dev/null; then
97	return 2
98  fi
99
100  if ! ip netns exec "$ns1" ping -c 1 -q 10.0.3.99 > /dev/null; then
101	return 1
102  fi
103
104  if ! ip netns exec "$ns1" ping -c 1 -q dead:3::99 > /dev/null; then
105	return 2
106  fi
107
108  return 0
109}
110
111test_ping_router() {
112  if ! ip netns exec "$ns1" ping -c 1 -q 10.0.2.1 > /dev/null; then
113	return 3
114  fi
115
116  if ! ip netns exec "$ns1" ping -c 1 -q dead:2::1 > /dev/null; then
117	return 4
118  fi
119
120  return 0
121}
122
123
124listener_ready()
125{
126	local ns="$1"
127	local port="$2"
128	local proto="$3"
129	ss -N "$ns" -ln "$proto" -o "sport = :$port" | grep -q "$port"
130}
131
132test_tproxy_udp_forward()
133{
134	local ip_proto="$1"
135
136	local expect_ns1_ns2="I_M_PROXIED"
137	local expect_ns1_ns3="PONG_NS3"
138	local expect_nsrouter_ns2="PONG_NS2"
139	local expect_nsrouter_ns3="PONG_NS3"
140
141	# derived variables
142	local testname="test_${ip_proto}_udp_forward"
143	local socat_ipproto
144	local ns1_ip
145	local ns2_ip
146	local ns3_ip
147	local ns1_ip_port
148	local ns2_ip_port
149	local ns3_ip_port
150	local ip_command
151
152	# socat 1.8.0 has a bug that requires to specify the IP family to bind (fixed in 1.8.0.1)
153	case $ip_proto in
154	"ip")
155		socat_ipproto="-4"
156		ns1_ip=10.0.1.99
157		ns2_ip=10.0.2.99
158		ns3_ip=10.0.3.99
159		ns1_ip_port="$ns1_ip:18888"
160		ns2_ip_port="$ns2_ip:8080"
161		ns3_ip_port="$ns3_ip:8080"
162		ip_command="ip"
163	;;
164	"ip6")
165		socat_ipproto="-6"
166		ns1_ip=dead:1::99
167		ns2_ip=dead:2::99
168		ns3_ip=dead:3::99
169		ns1_ip_port="[$ns1_ip]:18888"
170		ns2_ip_port="[$ns2_ip]:8080"
171		ns3_ip_port="[$ns3_ip]:8080"
172		ip_command="ip -6"
173	;;
174	*)
175	echo "FAIL: unsupported protocol"
176	exit 255
177	;;
178	esac
179
180	# shellcheck disable=SC2046 # Intended splitting of ip_command
181	ip netns exec "$nsrouter" $ip_command rule add fwmark 1 table 100
182	ip netns exec "$nsrouter" $ip_command route add local "$ns2_ip" dev lo table 100
183	ip netns exec "$nsrouter" nft -f /dev/stdin <<EOF
184flush ruleset
185table inet filter {
186	chain divert {
187		type filter hook prerouting priority 0; policy accept;
188		$ip_proto daddr $ns2_ip udp dport 8080 tproxy $ip_proto to :12345 meta mark set 1 accept
189	}
190}
191EOF
192
193	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="$ns2_ip_port" 2>/dev/null &
194	local tproxy_pid=$!
195
196	timeout "$timeout" ip netns exec "$ns2" socat "$socat_ipproto" udp-listen:8080,fork SYSTEM:"echo PONG_NS2" 2>/dev/null &
197	local server2_pid=$!
198
199	timeout "$timeout" ip netns exec "$ns3" socat "$socat_ipproto" udp-listen:8080,fork SYSTEM:"echo PONG_NS3" 2>/dev/null &
200	local server3_pid=$!
201
202	busywait "$BUSYWAIT_TIMEOUT" listener_ready "$nsrouter" 12345 "-u"
203	busywait "$BUSYWAIT_TIMEOUT" listener_ready "$ns2" 8080 "-u"
204	busywait "$BUSYWAIT_TIMEOUT" listener_ready "$ns3" 8080 "-u"
205
206	local result
207	# request from ns1 to ns2 (forwarded traffic)
208	result=$(echo I_M_PROXIED | ip netns exec "$ns1" socat -t 2 -T 2 STDIO udp:"$ns2_ip_port",sourceport=18888)
209	if [ "$result" == "$expect_ns1_ns2" ] ;then
210		echo "PASS: tproxy test $testname: ns1 got reply \"$result\" connecting to ns2"
211	else
212		echo "ERROR: tproxy test $testname: ns1 got reply \"$result\" connecting to ns2, not \"${expect_ns1_ns2}\" as intended"
213		ret=1
214	fi
215
216	# request from ns1 to ns3 (forwarded traffic)
217	result=$(echo I_M_PROXIED | ip netns exec "$ns1" socat -t 2 -T 2 STDIO udp:"$ns3_ip_port")
218	if [ "$result" = "$expect_ns1_ns3" ] ;then
219		echo "PASS: tproxy test $testname: ns1 got reply \"$result\" connecting to ns3"
220	else
221		echo "ERROR: tproxy test $testname: ns1 got reply \"$result\" connecting to ns3, not \"$expect_ns1_ns3\" as intended"
222		ret=1
223	fi
224
225	# request from nsrouter to ns2 (localy originated traffic)
226	result=$(echo I_M_PROXIED | ip netns exec "$nsrouter" socat -t 2 -T 2 STDIO udp:"$ns2_ip_port")
227	if [ "$result" == "$expect_nsrouter_ns2" ] ;then
228		echo "PASS: tproxy test $testname: nsrouter got reply \"$result\" connecting to ns2"
229	else
230		echo "ERROR: tproxy test $testname: nsrouter got reply \"$result\" connecting to ns2, not \"$expect_nsrouter_ns2\" as intended"
231		ret=1
232	fi
233
234	# request from nsrouter to ns3 (localy originated traffic)
235	result=$(echo I_M_PROXIED | ip netns exec "$nsrouter" socat -t 2 -T 2 STDIO udp:"$ns3_ip_port")
236	if [ "$result" = "$expect_nsrouter_ns3" ] ;then
237		echo "PASS: tproxy test $testname: nsrouter got reply \"$result\" connecting to ns3"
238	else
239		echo "ERROR: tproxy test $testname: nsrouter got reply \"$result\" connecting to ns3, not \"$expect_nsrouter_ns3\"  as intended"
240		ret=1
241	fi
242
243	# cleanup
244	kill "$tproxy_pid" "$server2_pid" "$server3_pid" 2>/dev/null
245	# shellcheck disable=SC2046 # Intended splitting of ip_command
246	ip netns exec "$nsrouter" $ip_command rule del fwmark 1 table 100
247	ip netns exec "$nsrouter" $ip_command route flush table 100
248}
249
250
251if test_ping; then
252	# queue bypass works (rules were skipped, no listener)
253	echo "PASS: ${ns1} can reach ${ns2}"
254else
255	echo "FAIL: ${ns1} cannot reach ${ns2}: $ret" 1>&2
256	exit $ret
257fi
258
259test_tproxy_udp_forward "ip"
260test_tproxy_udp_forward "ip6"
261
262exit $ret
263