Skip to content

Commit 7e37e0e

Browse files
aojeaummakynes
authored andcommitted
selftests: netfilter: nft_tproxy.sh: add tcp tests
The TPROXY functionality is widely used, however, there are only mptcp selftests covering this feature. The selftests represent the most common scenarios and can also be used as selfdocumentation of the feature. UDP and TCP testcases are split in different files because of the different nature of the protocols, specially due to the challenges that present to reliable test UDP due to the connectionless nature of the protocol. UDP only covers the scenarios involving the prerouting hook. The UDP tests are signfinicantly slower than the TCP ones, hence they use a larger timeout, it takes 20 seconds to run the full UDP suite on a 48 vCPU Intel(R) Xeon(R) CPU @2.60GHz. Signed-off-by: Antonio Ojea <[email protected]> Signed-off-by: Pablo Neira Ayuso <[email protected]>
1 parent a57856c commit 7e37e0e

File tree

4 files changed

+623
-0
lines changed

4 files changed

+623
-0
lines changed

tools/testing/selftests/net/netfilter/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ TEST_PROGS += nft_nat.sh
2727
TEST_PROGS += nft_nat_zones.sh
2828
TEST_PROGS += nft_queue.sh
2929
TEST_PROGS += nft_synproxy.sh
30+
TEST_PROGS += nft_tproxy_tcp.sh
31+
TEST_PROGS += nft_tproxy_udp.sh
3032
TEST_PROGS += nft_zones_many.sh
3133
TEST_PROGS += rpath.sh
3234
TEST_PROGS += xt_string.sh

tools/testing/selftests/net/netfilter/config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ CONFIG_NFT_QUEUE=m
8181
CONFIG_NFT_QUOTA=m
8282
CONFIG_NFT_REDIR=m
8383
CONFIG_NFT_SYNPROXY=m
84+
CONFIG_NFT_TPROXY=m
8485
CONFIG_VETH=m
8586
CONFIG_VLAN_8021Q=m
8687
CONFIG_XFRM_USER=m
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
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+
#
26+
# shellcheck disable=SC2162,SC2317
27+
28+
source lib.sh
29+
ret=0
30+
timeout=5
31+
32+
cleanup()
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+
42+
checktool "nft --version" "test without nft tool"
43+
checktool "socat -h" "run test without socat"
44+
45+
trap cleanup EXIT
46+
setup_ns ns1 ns2 ns3 nsrouter
47+
48+
if ! 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
51+
fi
52+
ip link add veth1 netns "$nsrouter" type veth peer name eth0 netns "$ns2"
53+
ip link add veth2 netns "$nsrouter" type veth peer name eth0 netns "$ns3"
54+
55+
ip -net "$nsrouter" link set veth0 up
56+
ip -net "$nsrouter" addr add 10.0.1.1/24 dev veth0
57+
ip -net "$nsrouter" addr add dead:1::1/64 dev veth0 nodad
58+
59+
ip -net "$nsrouter" link set veth1 up
60+
ip -net "$nsrouter" addr add 10.0.2.1/24 dev veth1
61+
ip -net "$nsrouter" addr add dead:2::1/64 dev veth1 nodad
62+
63+
ip -net "$nsrouter" link set veth2 up
64+
ip -net "$nsrouter" addr add 10.0.3.1/24 dev veth2
65+
ip -net "$nsrouter" addr add dead:3::1/64 dev veth2 nodad
66+
67+
ip -net "$ns1" link set eth0 up
68+
ip -net "$ns2" link set eth0 up
69+
ip -net "$ns3" link set eth0 up
70+
71+
ip -net "$ns1" addr add 10.0.1.99/24 dev eth0
72+
ip -net "$ns1" addr add dead:1::99/64 dev eth0 nodad
73+
ip -net "$ns1" route add default via 10.0.1.1
74+
ip -net "$ns1" route add default via dead:1::1
75+
76+
ip -net "$ns2" addr add 10.0.2.99/24 dev eth0
77+
ip -net "$ns2" addr add dead:2::99/64 dev eth0 nodad
78+
ip -net "$ns2" route add default via 10.0.2.1
79+
ip -net "$ns2" route add default via dead:2::1
80+
81+
ip -net "$ns3" addr add 10.0.3.99/24 dev eth0
82+
ip -net "$ns3" addr add dead:3::99/64 dev eth0 nodad
83+
ip -net "$ns3" route add default via 10.0.3.1
84+
ip -net "$ns3" route add default via dead:3::1
85+
86+
ip netns exec "$nsrouter" sysctl net.ipv6.conf.all.forwarding=1 > /dev/null
87+
ip netns exec "$nsrouter" sysctl net.ipv4.conf.veth0.forwarding=1 > /dev/null
88+
ip netns exec "$nsrouter" sysctl net.ipv4.conf.veth1.forwarding=1 > /dev/null
89+
ip netns exec "$nsrouter" sysctl net.ipv4.conf.veth2.forwarding=1 > /dev/null
90+
91+
test_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+
111+
test_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+
124+
listener_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+
132+
test_tproxy()
133+
{
134+
local traffic_origin="$1"
135+
local ip_proto="$2"
136+
local expect_ns1_ns2="$3"
137+
local expect_ns1_ns3="$4"
138+
local expect_nsrouter_ns2="$5"
139+
local expect_nsrouter_ns3="$6"
140+
141+
# derived variables
142+
local testname="test_${ip_proto}_tcp_${traffic_origin}"
143+
local socat_ipproto
144+
local ns1_ip
145+
local ns2_ip
146+
local ns3_ip
147+
local ns2_target
148+
local ns3_target
149+
local nftables_subject
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+
ns2_target="tcp:$ns2_ip:8080"
160+
ns3_target="tcp:$ns3_ip:8080"
161+
nftables_subject="ip daddr $ns2_ip tcp dport 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+
ns2_target="tcp:[$ns2_ip]:8080"
170+
ns3_target="tcp:[$ns3_ip]:8080"
171+
nftables_subject="ip6 daddr $ns2_ip tcp dport 8080"
172+
ip_command="ip -6"
173+
;;
174+
*)
175+
echo "FAIL: unsupported protocol"
176+
exit 255
177+
;;
178+
esac
179+
180+
case $traffic_origin in
181+
# to capture the local originated traffic we need to mark the outgoing
182+
# traffic so the policy based routing rule redirects it and can be processed
183+
# in the prerouting chain.
184+
"local")
185+
nftables_rules="
186+
flush ruleset
187+
table inet filter {
188+
chain divert {
189+
type filter hook prerouting priority 0; policy accept;
190+
$nftables_subject tproxy $ip_proto to :12345 meta mark set 1 accept
191+
}
192+
chain output {
193+
type route hook output priority 0; policy accept;
194+
$nftables_subject meta mark set 1 accept
195+
}
196+
}"
197+
;;
198+
"forward")
199+
nftables_rules="
200+
flush ruleset
201+
table inet filter {
202+
chain divert {
203+
type filter hook prerouting priority 0; policy accept;
204+
$nftables_subject tproxy $ip_proto to :12345 meta mark set 1 accept
205+
}
206+
}"
207+
;;
208+
*)
209+
echo "FAIL: unsupported parameter for traffic origin"
210+
exit 255
211+
;;
212+
esac
213+
214+
# shellcheck disable=SC2046 # Intended splitting of ip_command
215+
ip netns exec "$nsrouter" $ip_command rule add fwmark 1 table 100
216+
ip netns exec "$nsrouter" $ip_command route add local "${ns2_ip}" dev lo table 100
217+
echo "$nftables_rules" | ip netns exec "$nsrouter" nft -f /dev/stdin
218+
219+
timeout "$timeout" ip netns exec "$nsrouter" socat "$socat_ipproto" tcp-listen:12345,fork,ip-transparent SYSTEM:"cat" 2>/dev/null &
220+
local tproxy_pid=$!
221+
222+
timeout "$timeout" ip netns exec "$ns2" socat "$socat_ipproto" tcp-listen:8080,fork SYSTEM:"echo PONG_NS2" 2>/dev/null &
223+
local server2_pid=$!
224+
225+
timeout "$timeout" ip netns exec "$ns3" socat "$socat_ipproto" tcp-listen:8080,fork SYSTEM:"echo PONG_NS3" 2>/dev/null &
226+
local server3_pid=$!
227+
228+
busywait "$BUSYWAIT_TIMEOUT" listener_ready "$nsrouter" 12345 "-t"
229+
busywait "$BUSYWAIT_TIMEOUT" listener_ready "$ns2" 8080 "-t"
230+
busywait "$BUSYWAIT_TIMEOUT" listener_ready "$ns3" 8080 "-t"
231+
232+
local result
233+
# request from ns1 to ns2 (forwarded traffic)
234+
result=$(echo I_M_PROXIED | ip netns exec "$ns1" socat -t 2 -T 2 STDIO "$ns2_target")
235+
if [ "$result" == "$expect_ns1_ns2" ] ;then
236+
echo "PASS: tproxy test $testname: ns1 got reply \"$result\" connecting to ns2"
237+
else
238+
echo "ERROR: tproxy test $testname: ns1 got reply \"$result\" connecting to ns2, not \"${expect_ns1_ns2}\" as intended"
239+
ret=1
240+
fi
241+
242+
# request from ns1 to ns3(forwarded traffic)
243+
result=$(echo I_M_PROXIED | ip netns exec "$ns1" socat -t 2 -T 2 STDIO "$ns3_target")
244+
if [ "$result" = "$expect_ns1_ns3" ] ;then
245+
echo "PASS: tproxy test $testname: ns1 got reply \"$result\" connecting to ns3"
246+
else
247+
echo "ERROR: tproxy test $testname: ns1 got reply \"$result\" connecting to ns3, not \"$expect_ns1_ns3\" as intended"
248+
ret=1
249+
fi
250+
251+
# request from nsrouter to ns2 (localy originated traffic)
252+
result=$(echo I_M_PROXIED | ip netns exec "$nsrouter" socat -t 2 -T 2 STDIO "$ns2_target")
253+
if [ "$result" == "$expect_nsrouter_ns2" ] ;then
254+
echo "PASS: tproxy test $testname: nsrouter got reply \"$result\" connecting to ns2"
255+
else
256+
echo "ERROR: tproxy test $testname: nsrouter got reply \"$result\" connecting to ns2, not \"$expect_nsrouter_ns2\" as intended"
257+
ret=1
258+
fi
259+
260+
# request from nsrouter to ns3 (localy originated traffic)
261+
result=$(echo I_M_PROXIED | ip netns exec "$nsrouter" socat -t 2 -T 2 STDIO "$ns3_target")
262+
if [ "$result" = "$expect_nsrouter_ns3" ] ;then
263+
echo "PASS: tproxy test $testname: nsrouter got reply \"$result\" connecting to ns3"
264+
else
265+
echo "ERROR: tproxy test $testname: nsrouter got reply \"$result\" connecting to ns3, not \"$expect_nsrouter_ns3\" as intended"
266+
ret=1
267+
fi
268+
269+
# cleanup
270+
kill "$tproxy_pid" "$server2_pid" "$server3_pid" 2>/dev/null
271+
# shellcheck disable=SC2046 # Intended splitting of ip_command
272+
ip netns exec "$nsrouter" $ip_command rule del fwmark 1 table 100
273+
ip netns exec "$nsrouter" $ip_command route flush table 100
274+
}
275+
276+
277+
test_ipv4_tcp_forward()
278+
{
279+
local traffic_origin="forward"
280+
local ip_proto="ip"
281+
local expect_ns1_ns2="I_M_PROXIED"
282+
local expect_ns1_ns3="PONG_NS3"
283+
local expect_nsrouter_ns2="PONG_NS2"
284+
local expect_nsrouter_ns3="PONG_NS3"
285+
286+
test_tproxy "$traffic_origin" \
287+
"$ip_proto" \
288+
"$expect_ns1_ns2" \
289+
"$expect_ns1_ns3" \
290+
"$expect_nsrouter_ns2" \
291+
"$expect_nsrouter_ns3"
292+
}
293+
294+
test_ipv4_tcp_local()
295+
{
296+
local traffic_origin="local"
297+
local ip_proto="ip"
298+
local expect_ns1_ns2="I_M_PROXIED"
299+
local expect_ns1_ns3="PONG_NS3"
300+
local expect_nsrouter_ns2="I_M_PROXIED"
301+
local expect_nsrouter_ns3="PONG_NS3"
302+
303+
test_tproxy "$traffic_origin" \
304+
"$ip_proto" \
305+
"$expect_ns1_ns2" \
306+
"$expect_ns1_ns3" \
307+
"$expect_nsrouter_ns2" \
308+
"$expect_nsrouter_ns3"
309+
}
310+
311+
test_ipv6_tcp_forward()
312+
{
313+
local traffic_origin="forward"
314+
local ip_proto="ip6"
315+
local expect_ns1_ns2="I_M_PROXIED"
316+
local expect_ns1_ns3="PONG_NS3"
317+
local expect_nsrouter_ns2="PONG_NS2"
318+
local expect_nsrouter_ns3="PONG_NS3"
319+
320+
test_tproxy "$traffic_origin" \
321+
"$ip_proto" \
322+
"$expect_ns1_ns2" \
323+
"$expect_ns1_ns3" \
324+
"$expect_nsrouter_ns2" \
325+
"$expect_nsrouter_ns3"
326+
}
327+
328+
test_ipv6_tcp_local()
329+
{
330+
local traffic_origin="local"
331+
local ip_proto="ip6"
332+
local expect_ns1_ns2="I_M_PROXIED"
333+
local expect_ns1_ns3="PONG_NS3"
334+
local expect_nsrouter_ns2="I_M_PROXIED"
335+
local expect_nsrouter_ns3="PONG_NS3"
336+
337+
test_tproxy "$traffic_origin" \
338+
"$ip_proto" \
339+
"$expect_ns1_ns2" \
340+
"$expect_ns1_ns3" \
341+
"$expect_nsrouter_ns2" \
342+
"$expect_nsrouter_ns3"
343+
}
344+
345+
if test_ping; then
346+
# queue bypass works (rules were skipped, no listener)
347+
echo "PASS: ${ns1} can reach ${ns2}"
348+
else
349+
echo "FAIL: ${ns1} cannot reach ${ns2}: $ret" 1>&2
350+
exit $ret
351+
fi
352+
353+
test_ipv4_tcp_forward
354+
test_ipv4_tcp_local
355+
test_ipv6_tcp_forward
356+
test_ipv6_tcp_local
357+
358+
exit $ret

0 commit comments

Comments
 (0)