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 28source lib.sh 29ret=0 30timeout=5 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() 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=" 186flush ruleset 187table 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=" 200flush ruleset 201table 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 277test_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 294test_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 311test_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 328test_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 345if test_ping; then 346 # queue bypass works (rules were skipped, no listener) 347 echo "PASS: ${ns1} can reach ${ns2}" 348else 349 echo "FAIL: ${ns1} cannot reach ${ns2}: $ret" 1>&2 350 exit $ret 351fi 352 353test_ipv4_tcp_forward 354test_ipv4_tcp_local 355test_ipv6_tcp_forward 356test_ipv6_tcp_local 357 358exit $ret 359